From 0da0063cfa00c2b3287455a6abb541f431c7b01d Mon Sep 17 00:00:00 2001 From: Mudit Gupta Date: Tue, 30 Jul 2019 19:02:08 +0530 Subject: [PATCH] Merging 3.0.0 changes into old master (#775) * PLCR voting module * WIP * Clean up types & tags * Fix test cases * Check that there is a non-zero supply when creating a dividend * improvements and fixes * factory fix * minor fix * improve the PLCR voting * add more functionality and add test cases * Ported STO fixes from dev-2.1.0 (#591) * Ported STO fixes from dev-2.1.0 * Revert when cap reached instead of failing silently * Restored missing require * USDTSTO granularity edge case fixed * add more tests * add overflow check * add overflow check * GTM minor refactoring * gtm test fix * MATM optimizations * Fixed matm tests * minor fixes * add comments * minor comment * Added a test case * Minor optimization * add getTokensByPartition in GTM and BTM * Minor optimization in verify transfer * add test for the VRTM getTokensByPartition * add the pause functionality in the getTokensByPartition() * Get subset of investors at a checkpoint (#611) * Updated checkpoint event and optimized st * Optimized dividends push payment * Added test * Minor datastore optimization * Test fixed * Add acknowledgement for irrevocable actions using EIP712 (#599) * Require signed user ack * Added test * Small fix * WIP * Cleanup * More cleanup * Cleaned up tests * Skip acknowledgement tests in coverage * Test fix * Merge fix * add pause effects in LTM * Allow to define fee in Poly * fix test * Updated factory deployments * Updated STR tests * Tests fixed * Added code comment to oracle * Add STR tests * Simplified change currency functions * Updated STR tests * Added module factory tests * Fix timestamp for checkpoints tests * Cosmetic changes * Fix timestamp for checkpoints tests * Skip cp module in coverage * Port dividend fix (#610) * port the 100% tax withholding changes * minor size optimization * add reclaim functions in every module * minor fixes * restrict the changes if dividend is expired * Allow token name change (#600) * Allow owner to change name * Reduce size * Update ST interface * Added update name event * Updated OZ * Updated storage verification test * Moved BTM, LTM, Vesting out of experimental * Typo fix * Test fix * minor fixes * start proposal Id from 1 instead of 0 * BTM optimizations and bug fix * Bug fix * Typo fix * Added clash checker script * Beutified function selector clash check script * Throw on finding a clash * Added circle CI job * Upgradable tokens (#602) * wip * Fixes for migration * Broken :-( * Fixes * Fix tests * Some more fixes * Lots more logic * Add ability to add modules as archived * Add a test * some more tests * Remove test file * Cleanup some test cases * Fix module / token versioning * Fix more tests * More version checking & fixes * Remove badtest * Reduce size of mock contract * Some updates * Remove unnecessary constructors * Updates * Merge fixes * Minor update * Merge fixes * implement all functions of ERC1410 * LTM optimizations and bug fix * minor fix * Vesting escrow wallet optimizations and bug fix * remove the approvals mapping * Added test cases for bug fixes * reduce the ST code size upto 23.84 KB * remove unnecessary mocks contract and cutdown the ST size till 23.72 KB * Increased max module types (#636) Increased the number of module types that the STFactory must check for potential incompatibilities before allowing the token to upgrade. * add test cases and fix bugs * resolve conflicts and add test cases * minor test fixes * minor test fix * Increase solidity coverage memory limit * Raised memory limits of truffle and ganache * refactoring voting modules & adding functionality * Refresh / Upgrade tokens (#632) * Added refresh token function * Reduced STR size * Added test cases for refreshToken * reuse function * Added refresh token event * fix compiler error * Port the VRTM audit fixes (#635) * port the VRTM audit fixes * cleanup code * move voting modules from experimental * fix test & add test * fix test for weighted vote module * add more tests * increase test coverage * increase more coverage * Minor optimizations (#628) * Optimized datastore batch functions * fixed ST mock compiler warnings * Optimized 10^18 * Name null check updated * GPM optimizations * CTM optimization * GTM optimization * Transfer type made enum * Added compiler version to supress warning * Minor STR optimization * Minor verify transfer optimizations * Added set bytes function * Marked initialize function of STR non-payable * Deleted registry updater * Added comments for pre computed hashes * Added more pre computed hashes * batch functions optimized * minor ST optimizations * Can transfer minor optimization * Reduced mock contract size * Removed dead code * Minor changes * Increased STR compatibility (#640) * Made STR backwards compatible * Fixed tests * Added test case * Merge fix * Hardcode version checks * Fixed pclr voting test * Revert prettification This reverts commit 7efad951a68fc0362f65fd3ef722e799b0c50704. * build fix * Typo fix * Fixed tests * Updated and patched soldiity docgen * Update changelog for some extent * Copy dev-2.1.0 CLI into dev-3.0.0 * Fix Errors + add missing functions & Events * Add ISecurityTokenRegistry to contract abis + fix OZ ERC20 abi * Fix/Update ST20Generator for V3.0.0. * Add ISecurityToken to contract abis * Blue Bull - because I like it * Add events and some public constant getters to ISecurityToken * Token Manager CLI Fixes * revert yarn.lock changes * more token manager fixes * More CLI STM and TM fixes * WIP: More TM CLI fixes and updates * Update as per PR #669 to master * TM CLI updates for setting and checking investor flags * CLI Fixes for remaining transfer manager modules * Port Combine modify whitelist commands (ref PR #667) * STO CLI 3.0.0 fixes * Transfer CLI fixes * Investor CLI fixes * dividend manager CLI fixes * Contract Manager CLI Fixes and updates * Minor ST20 Generator change * Permission Manager Cli Fixes * Pin solc to 0.5.8 * made solc executable * Transfer managers with version * usd and poly fees for STR * Labeled modules Holder count * CLI Treasury wallet * CLI Change token name * CLI ST Documents * CLI controller transfer renamed * CLI partitions and operators * CLI inputs with limits abstracted to input.js * Moved OZ from devDependencies to dependencies (#707) * Approved Audit fixes (#705) * Add StatusCode library * make inline library * remove the helpers/PolyToken.sol * remove the IBoot from the contracts (#687) * cleanup (#686) * remove unnecessary modifiers (#685) * Remove KindMath from TokenLib (#684) * cleanup * remove the KindMath * Marked some functions as external (#678) * Removed redundant pause/unpause (#676) * Made onlyModuleOrOwner definition consisitent (#674) * Made onlyModuleOrOwner definition consisitent * Added braces * Added braces * minor undeflow fix in vesting ewallet (#673) * Updated license to Apache 2.0 * Update LICENSE * [3.20] Don't ask for name when registering ticker (#706) * Removed storing token name when registering ticker * Added backwards compatible functions * Updated tests to use latest functions * Renamed functions for easy testing on truffle * Fixed test * [3.38]: Add ST storage layout check script in CI pipeline (#704) * add st storage layout check script in CI pipeline * add info comment * 3.30 Remove OwnedProxy (#701) * Audit fix * Fix * Moved variable to storage contract * [3.31] Removed take usage fee (#700) * Removed takeUsageFee * Test fix * tests fixed * [3.5, 3.7, 3.8] Fix custom modules (#698) * Fix custom modules * Remove unnecessary modifier * Fix some test cases * disallow creation of 0 supply dividend (#697) * Added break in deleteDelegate (#696) * Added 0 length name check (#695) * Audit 3.4 & 3.14 Fix ST upgrades (#694) * Fixes + test cases * Small change in test * Add fixes for 3.14 * Fix setProtocolFactory() (#689) * Remove inconsistency of the index value (#688) * remove the unneccessary code from partitionsOf() (#681) * Added constructors (#672) * Added constructors The constructors prevents an exploit in case we forget to initialize the implementation contracts. Ideally, we should allways remember to initialize implementation contracts as well. * Migration fixed * test fix * test fix * Add the ability to configure the new STR atomically * Fix the returnPartition function (#680) * fix the returnPartition function * minor fix * Remove comment from codebase (#714) * Update interfaces to named parameters (#708) * WIP * Change some values * Make some mappings public for automatic getters * Put back truffle version * updated transfer manager results (#693) * Allow Custom oracle in USDTieredSTO (#691) * Added custom oracles to USDTSTO * Updated custom oracles logic * Pinned solidity version (#675) * Pinned solidity version * Fix merge conflicts * minor transfer optimization (#668) * Fix the BalanceOfPartition audit item (#679) * fix balance of partition function * fix balance of Partition function * return balance in the parent implementation of the getTokensByPartition() * Extra Items (#702) * minor improvements * permission fixes * Synchronise the ISTR with STR (#682) * synchronise the ISTR with STR * fix the interface problem * minor fixes * add some function in interface * add comment in STR * consistency in interfaces of contracts * improve the st interface * remove shadow declaration * add #706 new function declartion in the ISTR * add missing functions in interface * Specific contract type used (#683) * specific contract type used * add more type contract * remove the redundant KindMath * remove unnecessary casting * Merge fixes * Fix PLCR Proposal inconsistency (#713) * fix the proposal * minor styling and comments addition * Fix contract size issue. * Update licenses - Apache 2.0 As per https://spdx.org/licenses/ * Fix canTransfer spec (#709) * Fix spec * Fix conflict * Fix merge conflict * Small fixes * Revert package.json change * Fix test case * Set solcjs as default compiler (#716) * Set solcjs as default compiler If you want to use native solc, set the environemnt variable POLYMATH_NATIVE_SOLC as true. * Removed native solc from travis * Removed native solc version query from travis * CLI common selectToken inlcuding tokensByDelegate * Merge Dev-3.0.0 interfaces * Fix underflow in BlacklistTM (#721) * CLI Refresh security token * CLI Permissions updated * CLI Provider validator regex * Update changelog after the audit fixes * Fixed errors * Update CHANGELOG.md * Update CHANGELOG.md * Update CHANGELOG.md * Issue with forceburn wording and no controllerredeem listed * CLI _owner issue fixed * CLI NewSecurityToken event renamed * Revert "CLI NewSecurityToken event renamed" This reverts commit 4a7fbbedc3fd5ae10b33ffdb9e2220b123fcdc3b. * CLI ST20 granularity support * Verify transfer on each TM Show which module failed in a TransferFailure Messages improved * Protocol upgrade fixes (#733) * Update tags, types, lower & upper bounds (#725) * Make useModule backwards compatible * Make deployToken backwards compatible (#726) * Make deployToken backwards compatible * Small fix for scheduledCheckpoint * Updated STR interface * Removed extra event * Fix interface mismatch * cleaning up as per the audit recommendation (#722) * TakeFee was removed. * ISTR script (#731) * Added Interface sync script * Added interface check script to CI * return 1 on error * Use local truffle in scripts * Removed npx dependency * minor fixes * CLI Permission manager refactoring * Fixed CTM verifyTransfer bug (#736) * Fixed CTM canTransferBug * Added test case * Added comment for devs * Move some variables / functions to internal (#737) Move some variables / functions to internal * CLI Fix * Minor fix * CLI Wallet manager * Added VEW to migrations * CLI VEW schedules in batches * Styling * Added note for valid templates * Added wallet modules to ST manager * Fixed bug at checking balance * fix: getTreasuryWallet function added to DividendCheckpoint.sol * Minor fixes * Update with v3 contract addresses * Add files via upload * Update README.md --- .circleci/config.yml | 54 +- .gitignore | 1 + .solcover.js | 4 +- .travis.yml | 11 +- CHANGELOG.md | 107 +- CLI/commands/IO/input.js | 125 + CLI/commands/IO/output.js | 41 + CLI/commands/ST20Generator.js | 77 +- CLI/commands/common/common_functions.js | 298 +- CLI/commands/common/constants.js | 24 +- CLI/commands/common/global.js | 2 +- CLI/commands/common/permissions_list.js | 99 +- CLI/commands/contract_manager.js | 153 +- CLI/commands/dividends_manager.js | 201 +- CLI/commands/helpers/contract_abis.js | 25 +- CLI/commands/investor_portal.js | 150 +- CLI/commands/permission_manager.js | 386 +-- CLI/commands/sto_manager.js | 602 ++-- CLI/commands/strMigrator.js | 10 +- CLI/commands/token_manager.js | 425 +-- CLI/commands/transfer.js | 8 +- CLI/commands/transfer_manager.js | 1364 ++++----- CLI/commands/wallet_manager.js | 657 +++++ CLI/data/Transfer/GTM/flag_data.csv | 10 + .../VRTM/add_custom_restriction_data.csv | 6 +- .../VRTM/modify_custom_restriction_data.csv | 6 +- CLI/data/Wallet/VEW/add_schedule_data.csv | 3 + .../VEW/add_schedule_from_template_data.csv | 3 + CLI/data/Wallet/VEW/modify_schedule_data.csv | 3 + CLI/data/Wallet/VEW/revoke_schedule_data.csv | 3 + CLI/package.json | 2 +- CLI/polymath-cli.js | 47 +- CLI/yarn.lock | 51 +- CONTRIBUTING.md | 48 +- LICENSE | 222 +- README.md | 97 +- ...lymath DividendCheckpoint Audit Report.pdf | Bin 175032 -> 0 bytes contracts/FeatureRegistry.sol | 11 +- contracts/Migrations.sol | 8 +- contracts/ModuleRegistry.sol | 270 +- contracts/Pausable.sol | 13 +- contracts/PolymathRegistry.sol | 17 +- contracts/ReclaimTokens.sol | 7 +- contracts/RegistryUpdater.sol | 26 - contracts/STRGetter.sol | 272 ++ contracts/SecurityTokenRegistry.sol | 843 +++--- contracts/datastore/DataStore.sol | 423 +++ contracts/datastore/DataStoreFactory.sol | 18 + contracts/datastore/DataStoreProxy.sol | 36 + contracts/datastore/DataStoreStorage.sol | 24 + contracts/external/IMedianizer.sol | 9 +- contracts/external/oraclizeAPI.sol | 1637 ++++++----- contracts/helpers/PolyToken.sol | 198 -- contracts/interfaces/IBoot.sol | 10 - contracts/interfaces/ICheckPermission.sol | 14 + contracts/interfaces/IDataStore.sol | 136 + contracts/interfaces/IFeatureRegistry.sol | 14 +- contracts/interfaces/IModule.sol | 12 +- contracts/interfaces/IModuleFactory.sol | 92 +- contracts/interfaces/IModuleRegistry.sol | 103 +- contracts/interfaces/IOracle.sol | 11 +- contracts/interfaces/IOwnable.sol | 5 +- .../interfaces/{IERC20.sol => IPoly.sol} | 22 +- contracts/interfaces/IPolymathRegistry.sol | 15 +- contracts/interfaces/ISTFactory.sol | 53 +- contracts/interfaces/ISTO.sol | 22 +- contracts/interfaces/ISecurityToken.sol | 685 ++++- .../interfaces/ISecurityTokenRegistry.sol | 393 ++- contracts/interfaces/ITransferManager.sol | 29 + contracts/interfaces/IUSDTieredSTOProxy.sol | 24 - .../interfaces/IUpgradableTokenFactory.sol | 14 + contracts/interfaces/IVoting.sol | 60 + contracts/interfaces/token/IERC1410.sol | 51 + contracts/interfaces/token/IERC1594.sol | 29 + contracts/interfaces/token/IERC1643.sol | 19 + contracts/interfaces/token/IERC1644.sol | 28 + .../libraries/BokkyPooBahsDateTimeLibrary.sol | 9 +- contracts/libraries/DecimalMath.sol | 17 +- contracts/libraries/Encoder.sol | 15 +- contracts/libraries/KindMath.sol | 53 - contracts/libraries/StatusCodes.sol | 21 + contracts/libraries/TokenLib.sol | 436 ++- contracts/libraries/Util.sol | 27 +- contracts/libraries/VersionUtils.sol | 137 +- contracts/libraries/VolumeRestrictionLib.sol | 182 +- contracts/mocks/{ => Dummy}/DummySTO.sol | 43 +- contracts/mocks/Dummy/DummySTOFactory.sol | 49 + contracts/mocks/Dummy/DummySTOProxy.sol | 37 + contracts/mocks/Dummy/DummySTOStorage.sol | 15 + contracts/mocks/DummySTOFactory.sol | 70 - contracts/mocks/FunctionSigClash1.sol | 6 + contracts/mocks/FunctionSigClash2.sol | 6 + contracts/mocks/MockBurnFactory.sol | 38 +- contracts/mocks/MockCountTransferManager.sol | 31 + contracts/mocks/MockFactory.sol | 35 +- contracts/mocks/MockModuleRegistry.sol | 6 +- contracts/mocks/MockOracle.sol | 5 +- contracts/mocks/MockPolyOracle.sol | 7 +- contracts/mocks/MockRedemptionManager.sol | 58 +- contracts/mocks/MockSTGetter.sol | 24 + contracts/mocks/MockSTRGetter.sol | 16 + contracts/mocks/MockSecurityTokenLogic.sol | 66 + contracts/mocks/MockWrongTypeFactory.sol | 30 +- contracts/mocks/PolyTokenFaucet.sol | 40 +- contracts/mocks/SecurityTokenMock.sol | 16 + contracts/mocks/SecurityTokenRegistryMock.sol | 20 +- contracts/mocks/TestSTOFactory.sol | 29 +- contracts/modules/Burn/IBurn.sol | 2 +- .../{ => Dividend}/DividendCheckpoint.sol | 898 +++--- .../ERC20}/ERC20DividendCheckpoint.sol | 558 ++-- .../ERC20/ERC20DividendCheckpointFactory.sol | 48 + .../ERC20/ERC20DividendCheckpointProxy.sol | 32 + .../ERC20}/ERC20DividendCheckpointStorage.sol | 5 +- .../Ether}/EtherDividendCheckpoint.sol | 437 +-- .../Ether/EtherDividendCheckpointFactory.sol | 48 + .../Ether/EtherDividendCheckpointProxy.sol | 31 + .../ERC20DividendCheckpointFactory.sol | 79 - .../EtherDividendCheckpointFactory.sol | 79 - contracts/modules/Checkpoint/ICheckpoint.sol | 2 +- .../Voting/PLCR/PLCRVotingCheckpoint.sol | 395 +++ .../PLCR/PLCRVotingCheckpointFactory.sol | 49 + .../Voting/PLCR/PLCRVotingCheckpointProxy.sol | 32 + .../PLCR/PLCRVotingCheckpointStorage.sol | 27 + .../Transparent/WeightedVoteCheckpoint.sol | 289 ++ .../WeightedVoteCheckpointFactory.sol | 48 + .../WeightedVoteCheckpointProxy.sol | 32 + .../WeightedVoteCheckpointStorage.sol | 19 + .../Checkpoint/Voting/VotingCheckpoint.sol | 56 + .../Experimental/Burn/TrackedRedemption.sol | 21 +- .../Burn/TrackedRedemptionFactory.sol | 67 +- .../Mixed/ScheduledCheckpoint.sol | 134 +- .../Mixed/ScheduledCheckpointFactory.sol | 102 +- .../BlacklistTransferManagerFactory.sol | 70 - .../TransferManager/KYCTransferManager.sol | 102 + .../KYCTransferManagerFactory.sol | 42 + .../LockUpTransferManagerFactory.sol | 70 - .../TransferManager/SignedTransferManager.sol | 164 ++ .../SignedTransferManagerFactory.sol | 44 + .../modules/Experimental/Wallet/IWallet.sol | 19 - .../Wallet/VestingEscrowWalletFactory.sol | 76 - contracts/modules/Module.sol | 76 +- contracts/modules/ModuleFactory.sol | 181 +- contracts/modules/ModuleStorage.sol | 30 - .../GeneralPermissionManager.sol | 120 +- .../GeneralPermissionManagerFactory.sol | 68 +- .../GeneralPermissionManagerProxy.sol | 36 + .../GeneralPermissionManagerStorage.sol | 15 + .../PermissionManager/IPermissionManager.sol | 33 +- .../modules/STO/{ => Capped}/CappedSTO.sol | 79 +- .../modules/STO/Capped/CappedSTOFactory.sol | 50 + .../modules/STO/Capped/CappedSTOProxy.sol | 37 + .../modules/STO/Capped/CappedSTOStorage.sol | 19 + contracts/modules/STO/CappedSTOFactory.sol | 74 - .../modules/STO/{ => PreSale}/PreSaleSTO.sol | 45 +- .../modules/STO/PreSale/PreSaleSTOFactory.sol | 48 + .../modules/STO/PreSale/PreSaleSTOProxy.sol | 32 + .../modules/STO/PreSale/PreSaleSTOStorage.sol | 10 + contracts/modules/STO/PreSaleSTOFactory.sol | 72 - contracts/modules/STO/STO.sol | 62 +- .../STO/{ => USDTiered}/USDTieredSTO.sol | 499 ++-- .../STO/USDTiered/USDTieredSTOFactory.sol | 51 + .../STO/USDTiered/USDTieredSTOProxy.sol | 25 + .../STO/USDTiered}/USDTieredSTOStorage.sol | 48 +- contracts/modules/STO/USDTieredSTOFactory.sol | 79 - .../BTM}/BlacklistTransferManager.sol | 350 ++- .../BTM/BlacklistTransferManagerFactory.sol | 47 + .../BTM/BlacklistTransferManagerProxy.sol | 35 + .../BTM/BlacklistTransferManagerStorage.sol | 31 + .../CTM/CountTransferManager.sol | 132 + .../CTM/CountTransferManagerFactory.sol | 48 + .../CTM/CountTransferManagerProxy.sol | 35 + .../CTM/CountTransferManagerStorage.sol | 11 + .../TransferManager/CountTransferManager.sol | 89 - .../CountTransferManagerFactory.sol | 70 - .../GTM/GeneralTransferManager.sol | 704 +++++ .../GTM/GeneralTransferManagerFactory.sol | 53 + .../GTM/GeneralTransferManagerProxy.sol | 34 + .../GTM/GeneralTransferManagerStorage.sol | 39 + .../GeneralTransferManager.sol | 402 --- .../GeneralTransferManagerFactory.sol | 77 - .../TransferManager/ITransferManager.sol | 28 - .../LTM}/LockUpTransferManager.sol | 375 ++- .../LTM/LockUpTransferManagerFactory.sol | 52 + .../LTM/LockUpTransferManagerProxy.sol | 35 + .../LTM/LockUpTransferManagerStorage.sol | 29 + .../ManualApprovalTransferManager.sol | 195 +- .../ManualApprovalTransferManagerFactory.sol | 52 + .../ManualApprovalTransferManagerProxy.sol | 35 + .../ManualApprovalTransferManagerStorage.sol | 24 + .../ManualApprovalTransferManagerFactory.sol | 70 - .../{ => PTM}/PercentageTransferManager.sol | 112 +- .../PTM/PercentageTransferManagerFactory.sol | 48 + .../PTM/PercentageTransferManagerProxy.sol | 30 + .../PTM/PercentageTransferManagerStorage.sol | 17 + .../PercentageTransferManagerFactory.sol | 71 - .../TransferManager/TransferManager.sol | 33 + .../{ => VRTM}/VolumeRestrictionTM.sol | 725 ++--- .../VRTM/VolumeRestrictionTMFactory.sol | 49 + .../VRTM}/VolumeRestrictionTMProxy.sol | 16 +- .../VRTM/VolumeRestrictionTMStorage.sol | 75 + .../VolumeRestrictionTMFactory.sol | 75 - contracts/modules/UpgradableModuleFactory.sol | 139 + .../Wallet/VestingEscrowWallet.sol | 100 +- .../Wallet/VestingEscrowWalletFactory.sol | 45 + .../Wallet}/VestingEscrowWalletProxy.sol | 18 +- .../Wallet}/VestingEscrowWalletStorage.sol | 8 +- contracts/modules/Wallet/Wallet.sol | 11 + contracts/oracles/MakerDAOOracle.sol | 27 +- contracts/oracles/PolyOracle.sol | 37 +- contracts/oracles/StableOracle.sol | 113 + .../proxy/ERC20DividendCheckpointProxy.sol | 31 - .../proxy/EtherDividendCheckpointProxy.sol | 30 - .../proxy/GeneralTransferManagerProxy.sol | 30 - contracts/proxy/ModuleRegistryProxy.sol | 5 +- contracts/proxy/OwnedProxy.sol | 91 - contracts/proxy/OwnedUpgradeabilityProxy.sol | 25 +- contracts/proxy/Proxy.sol | 12 +- .../proxy/SecurityTokenRegistryProxy.sol | 5 +- contracts/proxy/USDTieredSTOProxy.sol | 32 - contracts/proxy/UpgradeabilityProxy.sol | 11 +- contracts/storage/EternalStorage.sol | 43 +- .../storage/GeneralTransferManagerStorage.sol | 55 - .../storage/VolumeRestrictionTMStorage.sol | 67 - .../Dividend}/DividendCheckpointStorage.sol | 10 +- .../Voting/VotingCheckpointStorage.sol | 8 + contracts/storage/modules/ModuleStorage.sol | 33 + contracts/storage/modules/STO/ISTOStorage.sol | 24 + .../{ => storage}/modules/STO/STOStorage.sol | 8 +- contracts/tokens/OZStorage.sol | 31 + contracts/tokens/STFactory.sol | 215 +- contracts/tokens/STGetter.sol | 267 ++ contracts/tokens/SecurityToken.sol | 1343 +++++---- contracts/tokens/SecurityTokenProxy.sol | 42 + contracts/tokens/SecurityTokenStorage.sol | 134 + docs/ethereum_status_codes.md | 46 + docs/investor_flags.md | 28 + docs/permissions_list.md | 135 +- migrations/1_deploy_token.js | 15 +- migrations/2_deploy_contracts.js | 962 +++--- package.json | 53 +- scripts/ISTRCheck.js | 118 + scripts/calculateSize.js | 45 +- scripts/clashCheck.js | 56 + scripts/compareStorageLayout.js | 226 +- scripts/deploy.sh | 9 - scripts/docs.sh | 28 +- scripts/encoders/encode_CappedSTO.js | 75 +- scripts/gasUsage.sh | 8 + scripts/patch.js | 43 +- scripts/test.sh | 19 +- scripts/tokenInfo-v2.js | 74 +- scripts/wintest.cmd | 4 +- test/a_poly_oracle.js | 87 +- test/b_capped_sto.js | 641 ++-- test/c_checkpoints.js | 160 +- test/d_count_transfer_manager.js | 563 ++-- test/e_erc20_dividends.js | 810 +++-- test/f_ether_dividends.js | 736 ++--- test/g_general_permission_manager.js | 362 ++- test/h_general_transfer_manager.js | 1003 +++++-- test/helpers/contracts/PolyToken.sol | 10 +- test/helpers/createInstances.js | 346 ++- test/helpers/encodeCall.js | 6 + test/helpers/latestTime.js | 9 +- test/helpers/signData.js | 213 +- test/helpers/time.js | 87 +- test/helpers/utils.js | 24 +- test/i_Issuance.js | 142 +- test/j_manual_approval_transfer_manager.js | 326 +- test/k_module_registry.js | 318 +- test/l_percentage_transfer_manager.js | 238 +- test/m_presale_sto.js | 197 +- test/n_security_token_registry.js | 980 ++++--- test/o_security_token.js | 1773 ++++++++--- test/p_usd_tiered_sto.js | 2611 ++++++++--------- test/q_usd_tiered_sto_sim.js | 570 ++-- test/r_concurrent_STO.js | 97 +- test/s_v130_to_v140_upgrade.js | 479 --- test/t_security_token_registry_proxy.js | 134 +- test/u_module_registry_proxy.js | 85 +- test/v_tracked_redemptions.js | 127 +- test/w_lockup_transfer_manager.js | 476 +-- test/x_scheduled_checkpoints.js | 805 +++-- test/y_volume_restriction_tm.js | 1444 ++++----- test/z_blacklist_transfer_manager.js | 568 ++-- .../z_fuzz_test_adding_removing_modules_ST.js | 89 +- ...zer_volumn_restriction_transfer_manager.js | 230 +- test/z_general_permission_manager_fuzzer.js | 563 ++-- test/z_vesting_escrow_wallet.js | 592 ++-- test/za_datastore.js | 480 +++ test/zb_signed_transfer_manager.js | 309 ++ test/zc_plcr_voting_checkpoint.js | 732 +++++ test/zd_weighted_vote_checkpoint.js | 524 ++++ truffle-ci.js | 21 +- truffle-config-gas.js | 81 + truffle-config.js | 27 +- upgrade.js | 19 + yarn.lock | 1518 +++++----- 298 files changed, 30686 insertions(+), 18611 deletions(-) create mode 100644 CLI/commands/IO/input.js create mode 100644 CLI/commands/IO/output.js create mode 100644 CLI/commands/wallet_manager.js create mode 100644 CLI/data/Transfer/GTM/flag_data.csv create mode 100644 CLI/data/Wallet/VEW/add_schedule_data.csv create mode 100644 CLI/data/Wallet/VEW/add_schedule_from_template_data.csv create mode 100644 CLI/data/Wallet/VEW/modify_schedule_data.csv create mode 100644 CLI/data/Wallet/VEW/revoke_schedule_data.csv delete mode 100644 audit reports/Polymath DividendCheckpoint Audit Report.pdf delete mode 100644 contracts/RegistryUpdater.sol create mode 100644 contracts/STRGetter.sol create mode 100644 contracts/datastore/DataStore.sol create mode 100644 contracts/datastore/DataStoreFactory.sol create mode 100644 contracts/datastore/DataStoreProxy.sol create mode 100644 contracts/datastore/DataStoreStorage.sol delete mode 100644 contracts/helpers/PolyToken.sol delete mode 100644 contracts/interfaces/IBoot.sol create mode 100644 contracts/interfaces/ICheckPermission.sol create mode 100644 contracts/interfaces/IDataStore.sol rename contracts/interfaces/{IERC20.sol => IPoly.sol} (62%) create mode 100644 contracts/interfaces/ITransferManager.sol delete mode 100644 contracts/interfaces/IUSDTieredSTOProxy.sol create mode 100644 contracts/interfaces/IUpgradableTokenFactory.sol create mode 100644 contracts/interfaces/IVoting.sol create mode 100644 contracts/interfaces/token/IERC1410.sol create mode 100644 contracts/interfaces/token/IERC1594.sol create mode 100644 contracts/interfaces/token/IERC1643.sol create mode 100644 contracts/interfaces/token/IERC1644.sol delete mode 100644 contracts/libraries/KindMath.sol create mode 100644 contracts/libraries/StatusCodes.sol rename contracts/mocks/{ => Dummy}/DummySTO.sol (66%) create mode 100644 contracts/mocks/Dummy/DummySTOFactory.sol create mode 100644 contracts/mocks/Dummy/DummySTOProxy.sol create mode 100644 contracts/mocks/Dummy/DummySTOStorage.sol delete mode 100644 contracts/mocks/DummySTOFactory.sol create mode 100644 contracts/mocks/FunctionSigClash1.sol create mode 100644 contracts/mocks/FunctionSigClash2.sol create mode 100644 contracts/mocks/MockCountTransferManager.sol create mode 100644 contracts/mocks/MockSTGetter.sol create mode 100644 contracts/mocks/MockSTRGetter.sol create mode 100644 contracts/mocks/MockSecurityTokenLogic.sol create mode 100644 contracts/mocks/SecurityTokenMock.sol rename contracts/modules/Checkpoint/{ => Dividend}/DividendCheckpoint.sol (75%) rename contracts/modules/Checkpoint/{ => Dividend/ERC20}/ERC20DividendCheckpoint.sol (79%) create mode 100644 contracts/modules/Checkpoint/Dividend/ERC20/ERC20DividendCheckpointFactory.sol create mode 100644 contracts/modules/Checkpoint/Dividend/ERC20/ERC20DividendCheckpointProxy.sol rename contracts/modules/Checkpoint/{ => Dividend/ERC20}/ERC20DividendCheckpointStorage.sol (69%) rename contracts/modules/Checkpoint/{ => Dividend/Ether}/EtherDividendCheckpoint.sol (80%) create mode 100644 contracts/modules/Checkpoint/Dividend/Ether/EtherDividendCheckpointFactory.sol create mode 100644 contracts/modules/Checkpoint/Dividend/Ether/EtherDividendCheckpointProxy.sol delete mode 100644 contracts/modules/Checkpoint/ERC20DividendCheckpointFactory.sol delete mode 100644 contracts/modules/Checkpoint/EtherDividendCheckpointFactory.sol create mode 100644 contracts/modules/Checkpoint/Voting/PLCR/PLCRVotingCheckpoint.sol create mode 100644 contracts/modules/Checkpoint/Voting/PLCR/PLCRVotingCheckpointFactory.sol create mode 100644 contracts/modules/Checkpoint/Voting/PLCR/PLCRVotingCheckpointProxy.sol create mode 100644 contracts/modules/Checkpoint/Voting/PLCR/PLCRVotingCheckpointStorage.sol create mode 100644 contracts/modules/Checkpoint/Voting/Transparent/WeightedVoteCheckpoint.sol create mode 100644 contracts/modules/Checkpoint/Voting/Transparent/WeightedVoteCheckpointFactory.sol create mode 100644 contracts/modules/Checkpoint/Voting/Transparent/WeightedVoteCheckpointProxy.sol create mode 100644 contracts/modules/Checkpoint/Voting/Transparent/WeightedVoteCheckpointStorage.sol create mode 100644 contracts/modules/Checkpoint/Voting/VotingCheckpoint.sol delete mode 100644 contracts/modules/Experimental/TransferManager/BlacklistTransferManagerFactory.sol create mode 100644 contracts/modules/Experimental/TransferManager/KYCTransferManager.sol create mode 100644 contracts/modules/Experimental/TransferManager/KYCTransferManagerFactory.sol delete mode 100644 contracts/modules/Experimental/TransferManager/LockUpTransferManagerFactory.sol create mode 100644 contracts/modules/Experimental/TransferManager/SignedTransferManager.sol create mode 100644 contracts/modules/Experimental/TransferManager/SignedTransferManagerFactory.sol delete mode 100644 contracts/modules/Experimental/Wallet/IWallet.sol delete mode 100644 contracts/modules/Experimental/Wallet/VestingEscrowWalletFactory.sol delete mode 100644 contracts/modules/ModuleStorage.sol create mode 100644 contracts/modules/PermissionManager/GeneralPermissionManagerProxy.sol create mode 100644 contracts/modules/PermissionManager/GeneralPermissionManagerStorage.sol rename contracts/modules/STO/{ => Capped}/CappedSTO.sol (80%) create mode 100644 contracts/modules/STO/Capped/CappedSTOFactory.sol create mode 100644 contracts/modules/STO/Capped/CappedSTOProxy.sol create mode 100644 contracts/modules/STO/Capped/CappedSTOStorage.sol delete mode 100644 contracts/modules/STO/CappedSTOFactory.sol rename contracts/modules/STO/{ => PreSale}/PreSaleSTO.sol (76%) create mode 100644 contracts/modules/STO/PreSale/PreSaleSTOFactory.sol create mode 100644 contracts/modules/STO/PreSale/PreSaleSTOProxy.sol create mode 100644 contracts/modules/STO/PreSale/PreSaleSTOStorage.sol delete mode 100644 contracts/modules/STO/PreSaleSTOFactory.sol rename contracts/modules/STO/{ => USDTiered}/USDTieredSTO.sol (62%) create mode 100644 contracts/modules/STO/USDTiered/USDTieredSTOFactory.sol create mode 100644 contracts/modules/STO/USDTiered/USDTieredSTOProxy.sol rename contracts/{storage => modules/STO/USDTiered}/USDTieredSTOStorage.sol (63%) delete mode 100644 contracts/modules/STO/USDTieredSTOFactory.sol rename contracts/modules/{Experimental/TransferManager => TransferManager/BTM}/BlacklistTransferManager.sol (57%) create mode 100644 contracts/modules/TransferManager/BTM/BlacklistTransferManagerFactory.sol create mode 100644 contracts/modules/TransferManager/BTM/BlacklistTransferManagerProxy.sol create mode 100644 contracts/modules/TransferManager/BTM/BlacklistTransferManagerStorage.sol create mode 100644 contracts/modules/TransferManager/CTM/CountTransferManager.sol create mode 100644 contracts/modules/TransferManager/CTM/CountTransferManagerFactory.sol create mode 100644 contracts/modules/TransferManager/CTM/CountTransferManagerProxy.sol create mode 100644 contracts/modules/TransferManager/CTM/CountTransferManagerStorage.sol delete mode 100644 contracts/modules/TransferManager/CountTransferManager.sol delete mode 100644 contracts/modules/TransferManager/CountTransferManagerFactory.sol create mode 100644 contracts/modules/TransferManager/GTM/GeneralTransferManager.sol create mode 100644 contracts/modules/TransferManager/GTM/GeneralTransferManagerFactory.sol create mode 100644 contracts/modules/TransferManager/GTM/GeneralTransferManagerProxy.sol create mode 100644 contracts/modules/TransferManager/GTM/GeneralTransferManagerStorage.sol delete mode 100644 contracts/modules/TransferManager/GeneralTransferManager.sol delete mode 100644 contracts/modules/TransferManager/GeneralTransferManagerFactory.sol delete mode 100644 contracts/modules/TransferManager/ITransferManager.sol rename contracts/modules/{Experimental/TransferManager => TransferManager/LTM}/LockUpTransferManager.sol (72%) create mode 100644 contracts/modules/TransferManager/LTM/LockUpTransferManagerFactory.sol create mode 100644 contracts/modules/TransferManager/LTM/LockUpTransferManagerProxy.sol create mode 100644 contracts/modules/TransferManager/LTM/LockUpTransferManagerStorage.sol rename contracts/modules/TransferManager/{ => MATM}/ManualApprovalTransferManager.sol (74%) create mode 100644 contracts/modules/TransferManager/MATM/ManualApprovalTransferManagerFactory.sol create mode 100644 contracts/modules/TransferManager/MATM/ManualApprovalTransferManagerProxy.sol create mode 100644 contracts/modules/TransferManager/MATM/ManualApprovalTransferManagerStorage.sol delete mode 100644 contracts/modules/TransferManager/ManualApprovalTransferManagerFactory.sol rename contracts/modules/TransferManager/{ => PTM}/PercentageTransferManager.sol (61%) create mode 100644 contracts/modules/TransferManager/PTM/PercentageTransferManagerFactory.sol create mode 100644 contracts/modules/TransferManager/PTM/PercentageTransferManagerProxy.sol create mode 100644 contracts/modules/TransferManager/PTM/PercentageTransferManagerStorage.sol delete mode 100644 contracts/modules/TransferManager/PercentageTransferManagerFactory.sol create mode 100644 contracts/modules/TransferManager/TransferManager.sol rename contracts/modules/TransferManager/{ => VRTM}/VolumeRestrictionTM.sol (67%) create mode 100644 contracts/modules/TransferManager/VRTM/VolumeRestrictionTMFactory.sol rename contracts/{proxy => modules/TransferManager/VRTM}/VolumeRestrictionTMProxy.sol (58%) create mode 100644 contracts/modules/TransferManager/VRTM/VolumeRestrictionTMStorage.sol delete mode 100644 contracts/modules/TransferManager/VolumeRestrictionTMFactory.sol create mode 100644 contracts/modules/UpgradableModuleFactory.sol rename contracts/modules/{Experimental => }/Wallet/VestingEscrowWallet.sol (90%) create mode 100644 contracts/modules/Wallet/VestingEscrowWalletFactory.sol rename contracts/{proxy => modules/Wallet}/VestingEscrowWalletProxy.sol (58%) rename contracts/{storage => modules/Wallet}/VestingEscrowWalletStorage.sol (97%) create mode 100644 contracts/modules/Wallet/Wallet.sol create mode 100644 contracts/oracles/StableOracle.sol delete mode 100644 contracts/proxy/ERC20DividendCheckpointProxy.sol delete mode 100644 contracts/proxy/EtherDividendCheckpointProxy.sol delete mode 100644 contracts/proxy/GeneralTransferManagerProxy.sol delete mode 100644 contracts/proxy/OwnedProxy.sol delete mode 100644 contracts/proxy/USDTieredSTOProxy.sol delete mode 100644 contracts/storage/GeneralTransferManagerStorage.sol delete mode 100644 contracts/storage/VolumeRestrictionTMStorage.sol rename contracts/{modules/Checkpoint => storage/modules/Checkpoint/Dividend}/DividendCheckpointStorage.sol (89%) create mode 100644 contracts/storage/modules/Checkpoint/Voting/VotingCheckpointStorage.sol create mode 100644 contracts/storage/modules/ModuleStorage.sol create mode 100644 contracts/storage/modules/STO/ISTOStorage.sol rename contracts/{ => storage}/modules/STO/STOStorage.sol (76%) create mode 100644 contracts/tokens/OZStorage.sol create mode 100644 contracts/tokens/STGetter.sol create mode 100644 contracts/tokens/SecurityTokenProxy.sol create mode 100644 contracts/tokens/SecurityTokenStorage.sol create mode 100644 docs/ethereum_status_codes.md create mode 100644 docs/investor_flags.md create mode 100644 scripts/ISTRCheck.js create mode 100644 scripts/clashCheck.js delete mode 100755 scripts/deploy.sh create mode 100755 scripts/gasUsage.sh delete mode 100644 test/s_v130_to_v140_upgrade.js create mode 100644 test/za_datastore.js create mode 100644 test/zb_signed_transfer_manager.js create mode 100644 test/zc_plcr_voting_checkpoint.js create mode 100644 test/zd_weighted_vote_checkpoint.js create mode 100644 truffle-config-gas.js create mode 100644 upgrade.js diff --git a/.circleci/config.yml b/.circleci/config.yml index 1b4347b84..efdf87dd2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,7 +2,7 @@ version: 2 jobs: build: docker: - - image: maxsam4/solidity-kit:0.4.24 + - image: maxsam4/solidity-kit steps: - checkout - restore_cache: @@ -17,7 +17,7 @@ jobs: - node_modules test: docker: - - image: maxsam4/solidity-kit:0.4.24 + - image: maxsam4/solidity-kit parallelism: 2 steps: - checkout @@ -37,7 +37,7 @@ jobs: path: ./test-results/mocha/results.xml coverage: docker: - - image: maxsam4/solidity-kit:0.4.24 + - image: maxsam4/solidity-kit steps: - checkout - restore_cache: @@ -45,9 +45,8 @@ jobs: - run: yarn install - run: node --version - run: truffle version - - run: node_modules/.bin/truffle version - - run: - command: scripts/coverage.sh + - run: + command: npm run coverage no_output_timeout: 1h - save_cache: key: dependency-cache-{{ checksum "package.json" }} @@ -55,9 +54,9 @@ jobs: - node_modules - store_artifacts: path: ./coverage/lcov.info - deploy_kovan: + clash-check: docker: - - image: maxsam4/solidity-kit:0.4.24 + - image: maxsam4/solidity-kit steps: - checkout - restore_cache: @@ -65,26 +64,49 @@ jobs: - run: yarn install - run: node --version - run: truffle version - - run: mv truffle-ci.js truffle-config.js - - run: npm run deploy-kovan + - run: npm run clash-check + - run: npm run istr-check + - save_cache: + key: dependency-cache-{{ checksum "package.json" }} + paths: + - node_modules + st-storage-layout-check: + docker: + - image: maxsam4/solidity-kit + steps: + - checkout + - restore_cache: + key: dependency-cache-{{ checksum "package.json" }} + - run: yarn install + - run: node --version + - run: truffle version + - run: npm run st-storage-layout-check - save_cache: key: dependency-cache-{{ checksum "package.json" }} paths: - node_modules docs: docker: - - image: maxsam4/solidity-kit:0.4.24 + - image: maxsam4/solidity-kit steps: - checkout + - restore_cache: + key: dependency-cache-{{ checksum "package.json" }} - run: yarn install - run: node --version - run: truffle version - run: npm run docs + - save_cache: + key: dependency-cache-{{ checksum "package.json" }} + paths: + - node_modules workflows: version: 2 commit: jobs: - coverage + - clash-check + - st-storage-layout-check daily-builds: triggers: - schedule: @@ -105,13 +127,3 @@ workflows: branches: only: - master - deploy: - jobs: - - deploy_kovan: - filters: - branches: - only: - - master - - dev-2.1.0 - - dev-2.2.0 - - dev-3.0.0 diff --git a/.gitignore b/.gitignore index dac654947..97604bff8 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,4 @@ extract/ extract.py extract.zip /test-results +.env diff --git a/.solcover.js b/.solcover.js index dac86d6e3..3af735e83 100644 --- a/.solcover.js +++ b/.solcover.js @@ -2,8 +2,8 @@ module.exports = { norpc: true, port: 8545, copyPackages: ['openzeppelin-solidity'], - testCommand: 'node ../node_modules/.bin/truffle test `find test/*.js ! -name a_poly_oracle.js -and ! -name s_v130_to_v140_upgrade.js -and ! -name q_usd_tiered_sto_sim.js -and ! -name z_general_permission_manager_fuzzer.js` --network coverage', + testCommand: 'node --max-old-space-size=3500 ../node_modules/.bin/truffle test `find test/*.js ! -name a_poly_oracle.js -and ! -name s_v130_to_v140_upgrade.js -and ! -name q_usd_tiered_sto_sim.js -and ! -name z_general_permission_manager_fuzzer.js` --network coverage', deepSkip: true, skipFiles: ['external', 'flat', 'helpers', 'mocks', 'oracles', 'libraries/KindMath.sol', 'libraries/BokkyPooBahsDateTimeLibrary.sol', 'storage', 'modules/Experimental'], - forceParse: ['mocks', 'oracles', 'modules/Experimental'] + forceParse: ['mocks', 'oracles', 'helpers', 'modules/Experimental'] }; diff --git a/.travis.yml b/.travis.yml index b2fb9bdc4..f95062929 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,9 +6,12 @@ cache: - node_modules jobs: include: - - stage: test - before_script: truffle version - script: travis_wait 120 sleep infinity & npm run test + - stage: Test + install: + - yarn install + before_script: + - truffle version + script: npm run test 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 + 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= diff --git a/CHANGELOG.md b/CHANGELOG.md index 836b840d5..a622364f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,101 @@ # Changelog All notable changes to this project will be documented in this file. +# v3.0.0 - Release Candidate + +[__3.0.0__](https://www.npmjs.com/package/polymath-core?activeTab=readme) __10-11-18__ + +## SecurityToken +* Added new function `addModuleWithLabel()` which takes an extra param `_label` that used for giving the customize label to the module for display purpose. #428 +* Changed the first three params in Security Token `event ModuleAdded()` to be indexed for search. Params are `unit8[] types`, `bytes32 _name` and `address _moduleFactory` +* Fixed `addModule` function to be backwards compatible and call the new `addModuleWithLabel` function with an empty label. +* Fixed event `ModuleAdded` to also emit `_label`. +* Fixed function `getModule` to also return the respective module label. +* Added datastore that is used to store data like investor list that is shared among modules. +* `getInvestorCount()` now returns length of investor array that is everyone who ever held some st or has kyc data attached. +* `holderCount()` returns the number of current st holders. +* Added flags for Investors. Accredited and canbuyfromsto are now flags. +* Add `_archived` flag in `addModule()` to allow issuers to add archived modules into the ST. +* Add `upgradeModule()` function to upgrade the already attached module. It can only be called by owner of the token. +* Add `upgradeToken()` function to upgrade the token to the latest version. Can only be executed by the owner of the token. +* Add `changeDataStore()` function to change the dataStore contract address attached with the ST. +* Issuer is allowed to change the name of the token using the `changeName()` function. +* Add `changeTreasuryWallet()` funtion to change the treasury wallet of the token. +* `transferWithData()` & `transferFromWithData()` function doesn't return the bool anymore. +* Introduced `balanceOfByPartition()` function to read the balance of the token holder according to the given partition. +* Add `transferByPartition()` function to transfer the tokens as per the given partition. +* Removed `verifyTransfer()` function. +* Add `authorizeOperator`, `revokeOperator`, `authorizeOperatorByPartition`, `revokeOperatorByPartition`, `operatorTransferByPartition` as the operator functions. +* Remove `freezeMinting()` and introduced `freezeIssuance()`. +* Rename the `mintWithData()` function to `issue()` function and `mintMulti()` to `issueMulti()`, remove `mint` function. +* Rename the `burnWithData()` function to `redeem()` function and `burnFromWithData()` to `redeemFrom()`, remove `burn` function. +* Add `redeemByPartition()` & `operatorRedeemByPartition()` function. +* Add `issueByPartition()` to issue the tokens as per given partition. +* `disableController()` now takes the sender signature to confirm the operation. +* Introduce `canTransfer()`, `canTransferFrom()` & `canTransferByPartition()` to validate the transfer before actually executing it. +* Add document specific functions `setDocument()`, `removeDocument()`. +* Rename `forceTransfer()` to `controllerTransfer()` similarly `forceBurn()` to `controllerRedeem()`. +* Add `isControllable()` to know whether the controller functions are allowed to execute or not. +* Add `isIssuable()`, `getInvestorsSubsetAt()`, `getTreasuryWallet()`, `isOperator()`, `isOperatorForPartition()`, `partitionsOf()`, `getDocument()`, `getAllDocument()` functions as getters to support ST functionality. +* Remove the `bool` return parameter from the `canTransfer` & `canTransferFrom` function. +* Rename `increaseApproval()` & `decreaseApproval()` to `increaseAllowance()` & `decreaseAllowance()` respectively. + +## STR +* Introduce new contract `STRGetter.sol`. It only contains the getter functions of the STR. +* Replaced `updatePolyTokenAddress()` function with `updateFromRegistry()` in `SecurityTokenRegistry`. +* Migrate all the getters of `SecurityTokenRegistry.sol` to `STRGetter.sol` contract. +* Removed `_polyToken` parameter from `initialize` function in `SecurityTokenRegistry`. +* Allows an explicit token factory version to be used during creation of securityToken. +* Rename the `getProtocolVersion()` to `getLatestProtocolVersion()`. +* Add `generateNewSecurityToken()` function to generate the 3.0 tokens. +* Add `refreshSecurityToken()` function to update the 2.x tokens to 3.0 tokens. +* Add `changeFeesAmountAndCurrency()` function to sets the ticker registration and ST launch fee amount and currency. +* Rename `setProtocolVersion()` to `setProtocolFactory()`, Add `removeProtocolFactory()`. +* Add `getTokensByDelegate()`, `getSTFactoryAddressOfVersion()`, `getLatestProtocolVersion()`, `getIsFeeInPoly()` some getters. +* Add `RegisterTicker` event with two new parameters `_registrationFeePoly` & `_registrationFeeUsd`. (Note- there is 2 events with the same name to maintain the backwards compatibility). +* Add `NewSecurityToken` event with three new parameters `_usdFee`, `_polyFee` & `_protocolVersion`. (Note- there is 2 events with the same name to maintain the backwards compatibility). +* Add `registerNewTicker()` function to register the ticker for `3.0.0` release. NB- `registerTicker()` is also present in the code to maintain the backwards compatibility. +* Add `modifyExistingTicker()` to modify the ticker & `modifyExistingSecurityToken()` to modify the ST. +* Add `tickerAvailable()` to get the status of ticker availability. + +## MR +* Add `_isUpgrade` param in function `useModule()`. NB - `useModule()` function exists with the same arguments to maintain the backwards compatibility. +* Add `isCompatibleModule()` to check whether the given module and ST is compatible or not. +* Remove `_isVerified` param from the `verifyModule()` function and introduced `unverifyModule()` function to unverify module. +* Removed `getReputationByFactory()`. +* `getFactoryDetails()` is now return one extra parameter i.e address of the factory owner. +* `getAllModulesByType()` add new function to return array that contains the list of addresses of module factory contracts. + +## GeneralTransferManager +* `modifyWhitelist()` function renamed to `modifyKYCData()`. +* Added functions to modify and get flags +* `canBuyFromSto` is now `canNotBuyFromSto` and it is the flag `1` +* GTM logic reworked. Now, instead of flags like allowAllTransfers, there is a matrix of transfer requirements that must be fulfilled based on type of transfer. +* Remove `changeSigningAddress()`, `changeAllowAllTransfers()`, `changeAllowAllWhitelistTransfers()`, `changeAllowAllWhitelistIssuances()`, `changeAllowAllBurnTransfers`. +* Introduced `modifyTransferRequirements()` & `modifyTransferRequirementsMulti()` to modify the transfer requirements. +* Add `modifyInvestorFlag()` & `modifyInvestorFlagMulti()` function to modify the flag. +* `modifyWhitelistSigned()` rename to `modifyKYCDataSigned()`. Add `modifyKYCDataSignedMulti`. +* Add `getAllInvestorFlags()`, `getInvestorFlag()`,`getInvestorFlags()`, `getAllKYCData()`, `getKYCData()` & `getTokensByPartition()`. + +## USDTiererdSTO +* Removed `changeAccredited()` function. +* `buyWithETH()`, `buyWithPOLY()`, `buyWithUSD()`, `buyWithETHRateLimited()`, `buyWithPOLYRateLimited()` & `buyWithUSDRateLimited()` will return spentUSD, spentValue & amount of mint tokens. +* Remove `buyTokensView()` function. +* Add `modifyOracle()` function allow issuer to add their personalize oracles. + +## Modules +* Introduced BTM, LTM, Voting and VEW modules. +* Remove the `_usageCost` variable when deploying any module factory. +* Remove `takeUsageFee()` function from evey module. +* Remove `changeUsageCost()` & `usageCostInPoly()` from every module factory. +* Remove `takeFee()` function. +* `getTreasuryWallet()` function added to `DividendCheckpoint.sol`. + +## Generalize +* Removed `_polyAddress` parameter from constructors of all modules and module factories. +* All modules are upgradeable now. +* Permission types are only `ADMIN` and `OPERATOR` now. + # v2.1.0 - Release Candidate [__2.1.0__](https://www.npmjs.com/package/polymath-core?activeTab=readme) __13-09-18__ @@ -113,13 +208,15 @@ volume traded in a given rolling period. * Add `getReputationOfFactory()` & `getModuleListOfType()` functions to get the array type data from the ModuleRegistry contract. * Add `_setupCost` in `LogGenerateModuleFromFactory` event. * Add new function `getAllModulesByName()`, To get the list of modules having the same name. #198. -* Add new function `modifyTickerDetails()`, To modify the details of undeployed ticker. #230 +* Add new function `modifyTickerDetails()`, To modify the details of undeployed ticker. #230 + + ## Fixed * 0x0 and duplicate address in exclusions are no longer allowed in dividend modules. * All permissions are denied if no permission manager is active. * 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 +* Generalize the event when any new token get registered with the polymath ecosystem. `LogNewSecurityToken` should emit _ticker_, _name_, _securityTokenAddress_, _owner_, _addedAt_, _registrant_ respectively. #230 * Change the function name of `withdraPoly` to `withdrawERC20` and make the function generalize to extract tokens from the ST contract. parmeters are contract address and the value need to extract from the securityToken. ## Removed @@ -351,7 +448,7 @@ allowed) * __buyTokensWithPoly__ has only one argument called `_investedPoly` only. Beneficiary Address should be its msg.sender. * __getRaiseEther()__ function name changed to __getRaisedEther()__. * __getRaisePoly()__ function name changed to __getRaisedPoly()__. -* `LogModuleAdded` emit one more variable called ___budget__. +* `LogModuleAdded` emit one more variable called __budget__. * `modules` mapping in the securityToken contract now returns __the array of ModuleData__. ## Removed @@ -363,7 +460,7 @@ allowed) ## Added * ModuleRegistry contract will provide the list of modules by there types. -* `SecurityTokenRegistry` is now working on the basis of the proxy version of the securitytoken contract. For that SecurityTokenRegistry has one more variable in the constructor called _STVersionProxy . +* `SecurityTokenRegistry` is now working on the basis of the proxy version of the securitytoken contract. For that SecurityTokenRegistry has one more variable in the constructor called _STVersionProxy_ . * `setProtocolVersion` new function added in the SecurityTokenRegistry to set the protocol version followed to generate the securityToken. Only be called by the `polymath admin`. * `SecurityToken` now have the integration with polyToken. At the time of `addModule()` SecurityToken approve the cost of the module to moduleFactory as the spender. * New function `withdrawPoly(uint256 _amount)` is added to withdrawal the unused POLY from the securityToken contract. Called only by the owner of the contract. @@ -390,7 +487,7 @@ allowed) * Deployment of the securityToken is now performed by the proxy contracts and call being generated form the SecurityTokenRegistry. * `TickerRegistrar` renamed as `TickerRegistry`. * TickerRegistry is now Ownable contract. -* `setTokenRegistrar` functio of TickerRegistry renamed to `setTokenRegistry`. +* `setTokenRegistrar` function of TickerRegistry renamed to `setTokenRegistry`. * SecurityToken constructor has one change in the variable. i.e `_moduleRegistry` contract address is replaced by the `_owner` address. * Their is no `_perm` parameter in the `addModule()` function of the securityToken contract. Now only 4 parameters left. * Type of Mudules changed diff --git a/CLI/commands/IO/input.js b/CLI/commands/IO/input.js new file mode 100644 index 000000000..dbec903a7 --- /dev/null +++ b/CLI/commands/IO/input.js @@ -0,0 +1,125 @@ +var readlineSync = require('readline-sync'); + +function readAddress(message, defaultValue) { + return readlineSync.question(message, { + limit: function (input) { + return web3.utils.isAddress(input); + }, + limitMessage: "Must be a valid address", + defaultInput: defaultValue + }); +} + +function readMultipleAddresses(message) { + return readlineSync.question(message, { + limit: function (input) { + return input === '' || input.split(",").every(a => web3.utils.isAddress(a)); + }, + limitMessage: `All addresses must be valid` + }); +} + +function readPercentage(message, defaultValue) { + return readlineSync.question(`${message} (number between 0-100): `, { + limit: function (input) { + return (parseFloat(input) >= 0 && parseFloat(input) <= 100); + }, + limitMessage: 'Must be a value between 0 and 100', + defaultInput: defaultValue + }); +} + +function readNumberGreaterThan(minValue, message, defaultValue) { + return readlineSync.question(message, { + limit: function (input) { + return parseFloat(input) > minValue; + }, + limitMessage: `Must be greater than ${minValue}`, + defaultInput: defaultValue + }); +} + +function readNumberGreaterThanOrEqual(minValue, message, defaultValue) { + return readlineSync.question(message, { + limit: function (input) { + return parseFloat(input) >= minValue; + }, + limitMessage: `Must be greater than or equal ${minValue}`, + defaultInput: defaultValue + }); +} + +function readNumberLessThan(maxValue, message, defaultValue) { + return readlineSync.question(message, { + limit: function (input) { + return parseFloat(input) < maxValue; + }, + limitMessage: `Must be less than ${maxValue}`, + defaultInput: defaultValue + }); +} + +function readNumberLessThanOrEqual(maxValue, message, defaultValue) { + return readlineSync.question(message, { + limit: function (input) { + return parseFloat(input) < maxValue; + }, + limitMessage: `Must be less than or equal ${maxValue}`, + defaultInput: defaultValue + }); +} + +function readNumberBetween(minValue, maxValue, message, defaultValue) { + return readlineSync.question(message, { + limit: function (input) { + return parseFloat(input) >= minValue && parseFloat(input) <= maxValue; + }, + limitMessage: `Must be betwwen ${minValue} and ${maxValue}`, + defaultInput: defaultValue + }); +} + +function readStringNonEmpty(message, defaultValue) { + return readlineSync.question(message, { + limit: function (input) { + return input.length > 0; + }, + limitMessage: "Must be a valid string", + defaultInput: defaultValue + }); +} + +function readStringNonEmptyWithMaxBinarySize(maxBinarySize, message, defaultValue) { + return readlineSync.question(message, { + limit: function (input) { + return input.length > 0 && getBinarySize(input) < maxBinarySize + }, + limitMessage: `Must be a valid string with binary size less than ${maxBinarySize}`, + defaultInput: defaultValue + }); +} + +function readDateInTheFuture(message, defaultValue) { + const now = Math.floor(Date.now() / 1000); + return readlineSync.question(message, { + limit: function (input) { + return parseInt(input) >= now; + }, + limitMessage: `Must be a future date`, + defaultInput: defaultValue + }); +} + +module.exports = { + readAddress, + readMultipleAddresses, + readPercentage, + readNumberGreaterThan, + readNumberGreaterThanOrEqual, + readNumberLessThan, + readNumberLessThanOrEqual, + readNumberBetween, + readStringNonEmpty, + readStringNonEmptyWithMaxBinarySize, + readDateInTheFuture +} \ No newline at end of file diff --git a/CLI/commands/IO/output.js b/CLI/commands/IO/output.js new file mode 100644 index 000000000..6cb5e1c47 --- /dev/null +++ b/CLI/commands/IO/output.js @@ -0,0 +1,41 @@ +function logBalance(address, symbol, balance) { + console.log(`Balance of ${address}: ${balance} ${symbol}`); +} + +function logUnlockedBalance() { + if (balanceUnlocked !== totalBalance) { + console.log(`Balance of ${address}: ${balanceUnlocked} ${tokenSymbol} unlocked (${totalBalance} ${tokenSymbol} total)`); + } else { + console.log(`Balance of ${address}: ${totalBalance} ${tokenSymbol}`); + } +} + +function logUnlockedBalanceWithPercentage(address, tokenSymbol, balanceUnlocked, totalBalance, totalSupply) { + let percentage = totalSupply !== '0' ? ` - ${parseFloat(totalBalance) / parseFloat(totalSupply) * 100}% of total supply` : ''; + if (balanceUnlocked !== totalBalance) { + console.log(`Balance of ${address}: ${balanceUnlocked} ${tokenSymbol} unlocked (${totalBalance} ${tokenSymbol} total${percentage})`); + } else { + console.log(`Balance of ${address}: ${totalBalance} ${tokenSymbol}${percentage}`); + } +} + +function logBalanceAtCheckpoint(address, tokenSymbol, checkpoint, balance) { + console.log(`Balance of ${address} at checkpoint ${checkpoint}: ${balance} ${tokenSymbol}`); +} + +function logTotalSupply(tokenSymbol, totalSupply) { + console.log(`Total supply is: ${totalSupply} ${tokenSymbol}`); +} + +function logTotalSupplyAtCheckpoint(tokenSymbol, checkpoint, totalSupply) { + console.log(`TotalSupply at checkpoint ${checkpoint} is: ${totalSupply} ${tokenSymbol}`); +} + +module.exports = { + logBalance, + logUnlockedBalance, + logBalanceAtCheckpoint, + logUnlockedBalanceWithPercentage, + logTotalSupply, + logTotalSupplyAtCheckpoint +} diff --git a/CLI/commands/ST20Generator.js b/CLI/commands/ST20Generator.js index 2b9311172..2381f8c65 100644 --- a/CLI/commands/ST20Generator.js +++ b/CLI/commands/ST20Generator.js @@ -6,8 +6,9 @@ const tokenManager = require('./token_manager'); const contracts = require('./helpers/contract_addresses'); const abis = require('./helpers/contract_abis'); const common = require('./common/common_functions'); +const input = require('./IO/input'); -//////////////////////// +// let securityTokenRegistryAddress; let tokenSymbol; let tokenLaunched; @@ -16,7 +17,7 @@ let tokenLaunched; let securityTokenRegistry; let polyToken; -async function executeApp(_ticker, _transferOwnership, _name, _details, _divisible) { +async function executeApp (_ticker, _transferOwnership, _name, _details, _divisible) { common.logAsciiBull(); console.log("********************************************"); console.log("Welcome to the Command-Line ST-20 Generator."); @@ -37,15 +38,14 @@ async function executeApp(_ticker, _transferOwnership, _name, _details, _divisib } } catch (err) { console.log(err); - return; } }; -async function setup() { +async function setup () { try { securityTokenRegistryAddress = await contracts.securityTokenRegistry(); - let securityTokenRegistryABI = abis.securityTokenRegistry(); - securityTokenRegistry = new web3.eth.Contract(securityTokenRegistryABI, securityTokenRegistryAddress); + let iSecurityTokenRegistryABI = abis.iSecurityTokenRegistry(); + securityTokenRegistry = new web3.eth.Contract(iSecurityTokenRegistryABI, securityTokenRegistryAddress); securityTokenRegistry.setProvider(web3.currentProvider); let polytokenAddress = await contracts.polyToken(); @@ -59,14 +59,16 @@ async function setup() { } } -async function step_ticker_registration(_ticker) { +async function step_ticker_registration (_ticker) { console.log(chalk.blue('\nToken Symbol Registration')); - let regFee = web3.utils.fromWei(await securityTokenRegistry.methods.getTickerRegistrationFee().call()); let available = false; - while (!available) { - console.log(chalk.yellow(`\nRegistering the new token symbol requires ${regFee} POLY & deducted from '${Issuer.address}', Current balance is ${(web3.utils.fromWei(await polyToken.methods.balanceOf(Issuer.address).call()))} POLY\n`)); + const regFee = await securityTokenRegistry.methods.getFees(web3.utils.keccak256('tickerRegFee')).call(); + const polyFee = web3.utils.fromWei(regFee.polyFee); + const usdFee = web3.utils.fromWei(regFee.usdFee); + + console.log(chalk.yellow(`\nRegistering the new token symbol requires ${polyFee} POLY (${usdFee} USD) & deducted from '${Issuer.address}', Current balance is ${(web3.utils.fromWei(await polyToken.methods.balanceOf(Issuer.address).call()))} POLY\n`)); if (typeof _ticker !== 'undefined') { tokenSymbol = _ticker; @@ -76,13 +78,13 @@ async function step_ticker_registration(_ticker) { } let details = await securityTokenRegistry.methods.getTickerDetails(tokenSymbol).call(); - if (new BigNumber(details[1]).toNumber() == 0) { + if (new BigNumber(details[1]).toNumber() === 0) { // If it has no registration date, it is available available = true; - await approvePoly(securityTokenRegistryAddress, regFee); + await approvePoly(securityTokenRegistryAddress, polyFee); let registerTickerAction = securityTokenRegistry.methods.registerTicker(Issuer.address, tokenSymbol, ""); await common.sendTransaction(registerTickerAction, { factor: 1.5 }); - } else if (details[0] == Issuer.address) { + } else if (details[0] === Issuer.address) { // If it has registration date and its owner is Issuer available = true; tokenLaunched = details[4]; @@ -93,18 +95,13 @@ async function step_ticker_registration(_ticker) { } } -async function step_transfer_ticker_ownership(_transferOwnership) { +async function step_transfer_ticker_ownership (_transferOwnership) { let newOwner = null; - if (typeof _transferOwnership !== 'undefined' && _transferOwnership != 'false') { + if (typeof _transferOwnership !== 'undefined' && _transferOwnership !== 'false') { newOwner = _transferOwnership; console.log(`Transfer ownership to: ${newOwner}`); - } else if (_transferOwnership != 'false' && readlineSync.keyInYNStrict(`Do you want to transfer the ownership of ${tokenSymbol} ticker?`)) { - newOwner = readlineSync.question('Enter the address that will be the new owner: ', { - limit: function (input) { - return web3.utils.isAddress(input); - }, - limitMessage: "Must be a valid address" - }); + } else if (_transferOwnership !== 'false' && readlineSync.keyInYNStrict(`Do you want to transfer the ownership of ${tokenSymbol} ticker?`)) { + newOwner = input.readAddress('Enter the address that will be the new owner: '); } if (newOwner) { @@ -116,11 +113,13 @@ async function step_transfer_ticker_ownership(_transferOwnership) { } } -async function step_token_deploy(_name, _details, _divisible) { +async function step_token_deploy (_name, _details, _divisible) { console.log(chalk.blue('\nToken Creation - Token Deployment')); - let launchFee = web3.utils.fromWei(await securityTokenRegistry.methods.getSecurityTokenLaunchFee().call()); - console.log(chalk.green(`\nToken deployment requires ${launchFee} POLY & deducted from '${Issuer.address}', Current balance is ${(web3.utils.fromWei(await polyToken.methods.balanceOf(Issuer.address).call()))} POLY\n`)); + const launchFee = await securityTokenRegistry.methods.getFees(web3.utils.keccak256('stLaunchFee')).call(); + const polyFee = web3.utils.fromWei(launchFee.polyFee); + const usdFee = web3.utils.fromWei(launchFee.usdFee); + console.log(chalk.yellow(`\nToken deployment requires ${polyFee} POLY (${usdFee} USD) & deducted from '${Issuer.address}', Current balance is ${(web3.utils.fromWei(await polyToken.methods.balanceOf(Issuer.address).call()))} POLY\n`)); let tokenName; if (typeof _name !== 'undefined') { @@ -140,43 +139,41 @@ async function step_token_deploy(_name, _details, _divisible) { let divisibility; if (typeof _divisible !== 'undefined') { - divisibility = _divisible.toString() == 'true'; + divisibility = _divisible.toString() === 'true'; console.log(`Divisible: ${divisibility.toString()}`) } else { let divisible = readlineSync.question('Press "N" for Non-divisible type token or hit Enter for divisible type token (Default): '); - divisibility = (divisible != 'N' && divisible != 'n'); + divisibility = (divisible !== 'N' && divisible !== 'n'); } - await approvePoly(securityTokenRegistryAddress, launchFee); - let generateSecurityTokenAction = securityTokenRegistry.methods.generateSecurityToken(tokenName, tokenSymbol, tokenDetails, divisibility); + let treasuryWallet = input.readAddress('Enter the treasury address for the token (' + Issuer.address + '): ', Issuer.address); + await approvePoly(securityTokenRegistryAddress, polyFee); + let generateSecurityTokenAction = securityTokenRegistry.methods.generateNewSecurityToken(tokenName, tokenSymbol, tokenDetails, divisibility, treasuryWallet, 0); let receipt = await common.sendTransaction(generateSecurityTokenAction); let event = common.getEventFromLogs(securityTokenRegistry._jsonInterface, receipt.logs, 'NewSecurityToken'); console.log(chalk.green(`Security Token has been successfully deployed at address ${event._securityTokenAddress}`)); } -////////////////////// // HELPER FUNCTIONS // -////////////////////// -async function selectTicker() { +async function selectTicker () { let result; - let userTickers = (await securityTokenRegistry.methods.getTickersByOwner(Issuer.address).call()).map(t => web3.utils.hexToAscii(t)); + let userTickers = (await securityTokenRegistry.methods.getTickersByOwner(Issuer.address).call()).map(t => web3.utils.hexToUtf8(t)); let options = await Promise.all(userTickers.map(async function (t) { let tickerDetails = await securityTokenRegistry.methods.getTickerDetails(t).call(); let tickerInfo; if (tickerDetails[4]) { - tickerInfo = `Token launched at ${(await securityTokenRegistry.methods.getSecurityTokenAddress(t).call())}`; + tickerInfo = `- Token launched at ${(await securityTokenRegistry.methods.getSecurityTokenAddress(t).call())}`; } else { - tickerInfo = `Expires at ${moment.unix(tickerDetails[2]).format('MMMM Do YYYY, HH:mm:ss')}`; + tickerInfo = `- Expires at ${moment.unix(tickerDetails[2]).format('MMMM Do YYYY, HH:mm:ss')}`; } - return `${t} - ${tickerInfo}`; + return `${t} ${tickerInfo}`; })); options.push('Register a new ticker'); let index = readlineSync.keyInSelect(options, 'Select a ticker:'); - if (index == -1) { + if (index === -1) { process.exit(0); - } else if (index == options.length - 1) { + } else if (index === options.length - 1) { result = readlineSync.question('Enter a symbol for your new ticker: '); } else { result = userTickers[index]; @@ -185,7 +182,7 @@ async function selectTicker() { return result; } -async function approvePoly(spender, fee) { +async function approvePoly (spender, fee) { polyBalance = await polyToken.methods.balanceOf(Issuer.address).call(); let requiredAmount = web3.utils.toWei(fee.toString()); if (parseInt(polyBalance) >= parseInt(requiredAmount)) { diff --git a/CLI/commands/common/common_functions.js b/CLI/commands/common/common_functions.js index e549271f3..2b8fdcc0e 100644 --- a/CLI/commands/common/common_functions.js +++ b/CLI/commands/common/common_functions.js @@ -2,8 +2,98 @@ const chalk = require('chalk'); const Tx = require('ethereumjs-tx'); const permissionsList = require('./permissions_list'); const abis = require('../helpers/contract_abis'); +const input = require('../IO/input'); const readlineSync = require('readline-sync'); +async function addModule (securityToken, polyToken, factoryAddress, moduleABI, getInitializeData, configFile) { + const moduleFactoryABI = abis.moduleFactory(); + const moduleFactory = new web3.eth.Contract(moduleFactoryABI, factoryAddress); + moduleFactory.setProvider(web3.currentProvider); + + const moduleName = web3.utils.hexToUtf8(await moduleFactory.methods.name().call()); + const moduleFee = new web3.utils.BN(await moduleFactory.methods.setupCostInPoly().call()); + let transferAmount = new web3.utils.BN(0); + if (moduleFee.gt(new web3.utils.BN(0))) { + const contractBalance = new web3.utils.BN(await polyToken.methods.balanceOf(securityToken._address).call()); + if (contractBalance.lt(moduleFee)) { + transferAmount = moduleFee.sub(contractBalance); + const ownerBalance = new web3.utils.BN(await polyToken.methods.balanceOf(Issuer.address).call()); + if (ownerBalance.lt(transferAmount)) { + console.log(chalk.red(`\n**************************************************************************************************************************************************`)); + console.log(chalk.red(`Not enough balance to pay the ${moduleName} fee, Requires ${web3.utils.fromWei(transferAmount)} POLY but have ${web3.utils.fromWei(ownerBalance)} POLY. Access POLY faucet to get the POLY to complete this txn`)); + console.log(chalk.red(`**************************************************************************************************************************************************\n`)); + process.exit(0); + } + } + } + + let bytes = web3.utils.fromAscii('', 16); + if (typeof getInitializeData !== 'undefined') { + bytes = await getInitializeData(moduleABI, configFile); + } + const addModuleArchived = readlineSync.keyInYNStrict('Do you want to add this module archived?'); + const moduleLabel = web3.utils.toHex(readlineSync.question('Enter a label to help you to identify this module: ')); + + if (transferAmount.gt(new web3.utils.BN(0))) { + let transferAction = polyToken.methods.transfer(securityToken._address, transferAmount); + let transferReceipt = await this.sendTransaction(transferAction, { factor: 2 }); + let transferEvent = this.getEventFromLogs(polyToken._jsonInterface, transferReceipt.logs, 'Transfer'); + console.log(`Number of POLY sent: ${web3.utils.fromWei(new web3.utils.BN(transferEvent.value))}`); + } + let addModuleAction = securityToken.methods.addModuleWithLabel(factoryAddress, bytes, moduleFee, 0, moduleLabel, addModuleArchived); + let receipt = await this.sendTransaction(addModuleAction); + let event = this.getEventFromLogs(securityToken._jsonInterface, receipt.logs, 'ModuleAdded'); + console.log(`${moduleName} deployed at address: ${event._module}`); + return event._module; +} + +async function getAvailableModules(moduleRegistry, moduleType, stAddress) { + let availableModules = await moduleRegistry.methods.getModulesByTypeAndToken(moduleType, stAddress).call(); + let moduleList = await Promise.all(availableModules.map(async function (m) { + let moduleFactoryABI = abis.moduleFactory(); + let moduleFactory = new web3.eth.Contract(moduleFactoryABI, m); + let moduleName = web3.utils.hexToUtf8(await moduleFactory.methods.name().call()); + let moduleVersion = await moduleFactory.methods.version().call(); + return { name: moduleName, version: moduleVersion, factoryAddress: m }; + })); + return moduleList; +} + +async function getAllModulesByType (securityToken, type) { + function ModuleInfo (_moduleType, _name, _address, _factoryAddress, _archived, _paused, _version, _label) { + this.name = _name; + this.type = _moduleType; + this.address = _address; + this.factoryAddress = _factoryAddress; + this.archived = _archived; + this.paused = _paused; + this.version = _version; + this.label = _label; + } + + let modules = []; + let allModules = await securityToken.methods.getModulesByType(type).call(); + for (let i = 0; i < allModules.length; i++) { + let details = await securityToken.methods.getModule(allModules[i]).call(); + let contractTemp = new web3.eth.Contract(abis.moduleABI(), details[1]); + let pausedTemp = await contractTemp.methods.paused().call(); + let factory = new web3.eth.Contract(abis.moduleFactory(), details[2]); + let versionTemp = await factory.methods.version().call(); + modules.push(new ModuleInfo( + type, + web3.utils.hexToUtf8(details[0]), + details[1], + details[2], + details[3], + pausedTemp, + versionTemp, + web3.utils.hexToUtf8(details[5]) + )); + } + + return modules; +} + function connect(abi, address) { contractRegistry = new web3.eth.Contract(abi, address); contractRegistry.setProvider(web3.currentProvider); @@ -11,27 +101,21 @@ function connect(abi, address) { }; async function queryModifyWhiteList(currentTransferManager) { - let investor = readlineSync.question('Enter the address to whitelist: ', { - limit: function (input) { - return web3.utils.isAddress(input); - }, - limitMessage: "Must be a valid address" - }); - let now = Math.floor(Date.now() / 1000); - let fromTime = readlineSync.questionInt(`Enter the time (Unix Epoch time) when the sale lockup period ends and the investor can freely sell his tokens (now = ${now}): `, { defaultInput: now }); - let toTime = readlineSync.questionInt(`Enter the time (Unix Epoch time) when the purchase lockup period ends and the investor can freely purchase tokens from others (now = ${now}): `, { defaultInput: now }); - let oneYearFromNow = Math.floor(Date.now() / 1000 + (60 * 60 * 24 * 365)); - let expiryTime = readlineSync.questionInt(`Enter the time until the investors KYC will be valid (after this time expires, the investor must re-do KYC) (1 year from now = ${oneYearFromNow}): `, { defaultInput: oneYearFromNow }); - let canBuyFromSTO = readlineSync.keyInYNStrict('Can the investor buy from security token offerings?'); - let modifyWhitelistAction = currentTransferManager.methods.modifyWhitelist(investor, fromTime, toTime, expiryTime, canBuyFromSTO); - let modifyWhitelistReceipt = await sendTransaction(modifyWhitelistAction); - let moduleVersion = await getModuleVersion(currentTransferManager); - if (moduleVersion != '1.0.0') { - let modifyWhitelistEvent = getEventFromLogs(currentTransferManager._jsonInterface, modifyWhitelistReceipt.logs, 'ModifyWhitelist'); - console.log(chalk.green(`${modifyWhitelistEvent._investor} has been whitelisted sucessfully!`)); - } else { - console.log(chalk.green(`${investor} has been whitelisted sucessfully!`)); - } + let investor = input.readAddress('Enter the address to whitelist: '); + let now = Math.floor(Date.now() / 1000); + let canSendAfter = readlineSync.questionInt(`Enter the time (Unix Epoch time) when the sale lockup period ends and the investor can freely transfer his tokens (now = ${now}): `, { defaultInput: now }); + let canReceiveAfter = readlineSync.questionInt(`Enter the time (Unix Epoch time) when the purchase lockup period ends and the investor can freely receive tokens from others (now = ${now}): `, { defaultInput: now }); + let oneYearFromNow = Math.floor(Date.now() / 1000 + (60 * 60 * 24 * 365)); + let expiryTime = readlineSync.questionInt(`Enter the time until the investors KYC will be valid (after this time expires, the investor must re-do KYC) (1 year from now = ${oneYearFromNow}): `, { defaultInput: oneYearFromNow }); + let modifyWhitelistAction = currentTransferManager.methods.modifyKYCData(investor, canSendAfter, canReceiveAfter, expiryTime); + let modifyWhitelistReceipt = await sendTransaction(modifyWhitelistAction); + let moduleVersion = await getModuleVersion(currentTransferManager); + if (moduleVersion != '1.0.0') { + let modifyWhitelistEvent = getEventFromLogs(currentTransferManager._jsonInterface, modifyWhitelistReceipt.logs, 'ModifyKYCData'); + console.log(chalk.green(`${modifyWhitelistEvent._investor} has been whitelisted sucessfully!`)); + } else { + console.log(chalk.green(`${investor} has been whitelisted sucessfully!`)); + } } async function getModuleVersion(currentTransferManager) { @@ -48,7 +132,7 @@ async function checkPermission(contractName, functionName, contractRegistry) { return true } else { let stAddress = await contractRegistry.methods.securityToken().call(); - let securityToken = connect(abis.securityToken(), stAddress); + let securityToken = connect(abis.iSecurityToken(), stAddress); let stOwner = await securityToken.methods.owner().call(); if (stOwner == Issuer.address) { return true @@ -82,11 +166,11 @@ async function getGasLimit(options, action) { async function checkPermissions(action) { let contractRegistry = connect(action._parent.options.jsonInterface, action._parent._address); - //NOTE this is a condition to verify if the transaction comes from a module or not. + //NOTE this is a condition to verify if the transaction comes from a module or not. if (contractRegistry.methods.hasOwnProperty('factory')) { let moduleAddress = await contractRegistry.methods.factory().call(); let moduleRegistry = connect(abis.moduleFactory(), moduleAddress); - let parentModule = await moduleRegistry.methods.getName().call(); + let parentModule = await moduleRegistry.methods.name().call(); let result = await checkPermission(web3.utils.hexToUtf8(parentModule), action._method.name, contractRegistry); if (!result) { console.log("You haven't the right permissions to execute this method."); @@ -96,48 +180,92 @@ async function checkPermissions(action) { return } +async function selectToken(securityTokenRegistry, tokenSymbol) { + if (typeof tokenSymbol === 'undefined') { + const tokensByOwner = await securityTokenRegistry.methods.getTokensByOwner(Issuer.address).call(); + const tokensByDelegate = await securityTokenRegistry.methods.getTokensByDelegate(Issuer.address).call(); + const userTokens = tokensByOwner.concat(tokensByDelegate); + const tokenDataArray = await Promise.all(userTokens.map(async function (t) { + const tokenData = await securityTokenRegistry.methods.getSecurityTokenData(t).call(); + return { symbol: tokenData[0], address: t }; + })); + const options = tokenDataArray.map(function (t) { + return `${t.symbol} - Deployed at ${t.address}`; + }); + options.push('Enter token symbol manually'); + + const index = readlineSync.keyInSelect(options, 'Select a token:', { cancel: 'Exit' }); + const selected = index !== -1 ? options[index] : 'Exit'; + switch (selected) { + case 'Enter token symbol manually': + tokenSymbol = input.readStringNonEmpty('Enter the token symbol: '); + break; + case 'Exit': + tokenSymbol = ''; + break; + default: + tokenSymbol = tokenDataArray[index].symbol; + break; + } + } + + let securityToken = null; + if (tokenSymbol !== '') { + const securityTokenAddress = await securityTokenRegistry.methods.getSecurityTokenAddress(tokenSymbol).call(); + if (securityTokenAddress === '0x0000000000000000000000000000000000000000') { + console.log(chalk.red(`Selected Security Token ${tokenSymbol} does not exist.`)); + } else { + const iSecurityTokenABI = abis.iSecurityToken(); + securityToken = new web3.eth.Contract(iSecurityTokenABI, securityTokenAddress); + securityToken.setProvider(web3.currentProvider); + } + } + + return securityToken; +} + async function sendTransaction(action, options) { - await checkPermissions(action); + await checkPermissions(action); - options = getFinalOptions(options); - let gasLimit = await getGasLimit(options, action); + options = getFinalOptions(options); + let gasLimit = await getGasLimit(options, action); - console.log(chalk.black.bgYellowBright(`---- Transaction executed: ${action._method.name} - Gas limit provided: ${gasLimit} ----`)); + console.log(chalk.black.bgYellowBright(`---- Transaction executed: ${action._method.name} - Gas limit provided: ${gasLimit} ----`)); - let nonce = await web3.eth.getTransactionCount(options.from.address); - if (nonce < options.minNonce) { - nonce = minNonce; - } - let abi = action.encodeABI(); - let parameter = { - from: options.from.address, - to: action._parent._address, - data: abi, - gasLimit: gasLimit, - gasPrice: options.gasPrice, - nonce: nonce, - value: web3.utils.toHex(options.value) - }; - - const transaction = new Tx(parameter); - transaction.sign(Buffer.from(options.from.privateKey.replace('0x', ''), 'hex')); - return await web3.eth.sendSignedTransaction('0x' + transaction.serialize().toString('hex')) - .on('transactionHash', function (hash) { - console.log(` - Your transaction is being processed. Please wait... - TxHash: ${hash}` - ); - }) - .on('receipt', function (receipt) { - console.log(` - Congratulations! The transaction was successfully completed. - Gas used: ${receipt.gasUsed} - Gas spent: ${web3.utils.fromWei((new web3.utils.BN(options.gasPrice)).mul(new web3.utils.BN(receipt.gasUsed)))} Ether - Review it on Etherscan. - TxHash: ${receipt.transactionHash}\n` - ); - }); + let nonce = await web3.eth.getTransactionCount(options.from.address); + if (nonce < options.minNonce) { + nonce = minNonce; + } + let abi = action.encodeABI(); + let parameter = { + from: options.from.address, + to: action._parent._address, + data: abi, + gasLimit: gasLimit, + gasPrice: options.gasPrice, + nonce: nonce, + value: web3.utils.toHex(options.value) }; + const transaction = new Tx(parameter); + transaction.sign(Buffer.from(options.from.privateKey.replace('0x', ''), 'hex')); + return await web3.eth.sendSignedTransaction('0x' + transaction.serialize().toString('hex')) + .on('transactionHash', function (hash) { + console.log(` +Your transaction is being processed. Please wait... +TxHash: ${hash}` + ); + }) + .on('receipt', function (receipt) { + console.log(` +Congratulations! The transaction was successfully completed. +Gas used: ${receipt.gasUsed} - Gas spent: ${web3.utils.fromWei((new web3.utils.BN(options.gasPrice)).mul(new web3.utils.BN(receipt.gasUsed)))} Ether +Review it on Etherscan. +TxHash: ${receipt.transactionHash}\n` + ); + }); +}; + function getEventFromLogs(jsonInterface, logs, eventName) { let eventJsonInterface = jsonInterface.find(o => o.name === eventName && o.type === 'event'); let log = logs.find(l => l.topics.includes(eventJsonInterface.signature)); @@ -157,28 +285,28 @@ module.exports = { return (days + " days, " + hrs + " Hrs, " + mnts + " Minutes, " + seconds + " Seconds"); }, logAsciiBull: function () { - console.log(` - /######%%, /#( - ##########%%%%%, ,%%%. % - *#############%%%%%##%%%%%%# ## - (################%%%%#####%%%%//###%, - .####################%%%%#########/ - (#########%%############%%%%%%%%%#%%% - ,(%#%%%%%%%%%%%%############%%%%%%%###%%%. - (######%%###%%%%%%%%##############%%%%%####%%%* - /#######%%%%######%%%%##########%###,.%######%%%( - #%%%%%#######%%%%%%###########%%%%%*###### /####%%%# - #. ,%%####%%%%%%%(/#%%%%%%%%( #%#### ,#%/ - *#%( .%%%##%%%%%% .%%%#* - .%%%%#%%%% .%%%###( - %%%#####% (%%. - #%###(, - *#%# - %%# - * - &% - %%%. -`); + console.log(chalk.blue(` + /######%%, /#( + ##########%%%%%, ,%%%. % + *#############%%%%%##%%%%%%# ## + (################%%%%#####%%%%//###%, + .####################%%%%#########/ + (#########%%############%%%%%%%%%#%%% + ,(%#%%%%%%%%%%%%############%%%%%%%###%%%. + (######%%###%%%%%%%%##############%%%%%####%%%* + /#######%%%%######%%%%##########%###,.%######%%%( + #%%%%%#######%%%%%%###########%%%%%*###### /####%%%# + #. ,%%####%%%%%%%(/#%%%%%%%%( #%#### ,#%/ + *#%( .%%%##%%%%%% .%%%#* + .%%%%#%%%% .%%%###( + %%%#####% (%%. + #%###(, + *#%# + %%# + * + &% + %%%. +`)); }, getNonce: async function (from) { return (await web3.eth.getTransactionCount(from.address, "pending")); @@ -210,5 +338,9 @@ module.exports = { }, sendTransaction, getEventFromLogs, - queryModifyWhiteList + queryModifyWhiteList, + addModule, + getAvailableModules, + getAllModulesByType, + selectToken }; diff --git a/CLI/commands/common/constants.js b/CLI/commands/common/constants.js index d121fd2da..1b19e5195 100644 --- a/CLI/commands/common/constants.js +++ b/CLI/commands/common/constants.js @@ -4,7 +4,8 @@ module.exports = Object.freeze({ TRANSFER: 2, STO: 3, DIVIDENDS: 4, - BURN: 5 + BURN: 5, + WALLET: 7 }, DURATION: { seconds: function (val) { @@ -32,5 +33,22 @@ module.exports = Object.freeze({ STABLE: 2 }, DEFAULT_BATCH_SIZE: 75, - ADDRESS_ZERO: '0x0000000000000000000000000000000000000000' -}); \ No newline at end of file + ADDRESS_ZERO: '0x0000000000000000000000000000000000000000', + TRASFER_RESULT: { + INVALID: '0', + NA: '1', + VALID: '2', + FORCE_VALID: '3' + }, + TRANSFER_STATUS_CODES: { + TransferFailure: '0x50', + TransferSuccess: '0x51', + InsufficientBalance: '0x52', + InsufficientAllowance: '0x53', + TransfersHalted: '0x54', + FundsLocked: '0x55', + InvalidSender: '0x56', + InvalidReceiver: '0x57', + InvalidOperator: '0x58' + } +}); diff --git a/CLI/commands/common/global.js b/CLI/commands/common/global.js index e088c01d8..717e05909 100644 --- a/CLI/commands/common/global.js +++ b/CLI/commands/common/global.js @@ -25,7 +25,7 @@ function getGasPrice(networkId) { } function providerValidator(url) { - var expression = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}(\.[a-z]{2,4})*\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)/g; + var expression = /^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]+$/gm; var regex = new RegExp(expression); return url.match(regex); } diff --git a/CLI/commands/common/permissions_list.js b/CLI/commands/common/permissions_list.js index 8f74c15e7..dcea50e77 100644 --- a/CLI/commands/common/permissions_list.js +++ b/CLI/commands/common/permissions_list.js @@ -1,70 +1,79 @@ function getPermissionList() { return { ERC20DividendCheckpoint: { - pushDividendPayment: "DISTRIBUTE", - pushDividendPaymentToAddresses: "DISTRIBUTE", - setDefaultExcluded: "MANAGE", - setWithholding: "MANAGE", - setWithholdingFixed: "MANAGE", - createDividend: "MANAGE", - createDividendWithCheckpoint: "MANAGE", - createDividendWithExclusions: "MANAGE", - createDividendWithCheckpointAndExclusions: "MANAGE", - reclaimDividend: "MANAGE", - withdrawWithholding: "MANAGE", - createCheckpoint: "CHECKPOINT" + pushDividendPayment: "OPERATOR", + pushDividendPaymentToAddresses: "OPERATOR", + createCheckpoint: "OPERATOR", + reclaimDividend: "OPERATOR", + withdrawWithholding: "OPERATOR", + setDefaultExcluded: "ADMIN", + setWithholding: "ADMIN", + setWithholdingFixed: "ADMIN", + createDividend: "ADMIN", + createDividendWithCheckpoint: "ADMIN", + createDividendWithExclusions: "ADMIN", + createDividendWithCheckpointAndExclusions: "ADMIN" }, EtherDividendCheckpoint: { - pushDividendPayment: "DISTRIBUTE", - pushDividendPaymentToAddresses: "DISTRIBUTE", - setDefaultExcluded: "MANAGE", - setWithholding: "MANAGE", - setWithholdingFixed: "MANAGE", - createDividend: "MANAGE", - createDividendWithCheckpoint: "MANAGE", - createDividendWithExclusions: "MANAGE", - createDividendWithCheckpointAndExclusions: "MANAGE", - reclaimDividend: "MANAGE", - withdrawWithholding: "MANAGE", - createCheckpoint: "CHECKPOINT" + pushDividendPayment: "OPERATOR", + pushDividendPaymentToAddresses: "OPERATOR", + createCheckpoint: "OPERATOR", + reclaimDividend: "OPERATOR", + withdrawWithholding: "OPERATOR", + setDefaultExcluded: "ADMIN", + setWithholding: "ADMIN", + setWithholdingFixed: "ADMIN", + createDividend: "ADMIN", + createDividendWithCheckpoint: "ADMIN", + createDividendWithExclusions: "ADMIN", + createDividendWithCheckpointAndExclusions: "ADMIN" }, GeneralPermissionManager: { - addDelegate: "CHANGE_PERMISSION", - changePermission: "CHANGE_PERMISSION", - changePermissionMulti: "CHANGE_PERMISSION" + addDelegate: "ADMIN", + deleteDelegate: "ADMIN", + changePermission: "ADMIN", + changePermissionMulti: "ADMIN" }, USDTieredSTO: { modifyFunding: "ONLY_OWNER", modifyLimits: "ONLY_OWNER", modifyTiers: "ONLY_OWNER", + modifyTimes: "ONLY_OWNER", modifyAddresses: "ONLY_OWNER", finalize: "ONLY_OWNER", - changeAccredited: "ONLY_OWNER" + changeNonAccreditedLimit: "ONLY_OWNER", + changeAllowBeneficialInvestments: "ONLY_OWNER" }, PreSaleSTO: { - allocateTokens: "PRE_SALE_ADMIN", - allocateTokensMulti: "PRE_SALE_ADMIN" + allocateTokens: "ADMIN", + allocateTokensMulti: "ADMIN" }, CountTransferManager: { changeHolderCount: "ADMIN" }, GeneralTransferManager: { - changeIssuanceAddress: "FLAGS", - changeSigningAddress: "FLAGS", - changeAllowAllTransfers: "FLAGS", - changeAllowAllWhitelistTransfers: "FLAGS", - changeAllowAllWhitelistIssuances: "FLAGS", - changeAllowAllBurnTransfers: "FLAGS", - modifyWhitelist: "WHITELIST", - modifyWhitelistMulti: "WHITELIST" + changeIssuanceAddress: "ADMIN", + changeSigningAddress: "ADMIN", + changeAllowAllTransfers: "ADMIN", + changeAllowAllWhitelistTransfers: "ADMIN", + changeAllowAllWhitelistIssuances: "ADMIN", + changeAllowAllBurnTransfers: "ADMIN", + modifyKYCData: "ADMIN", + modifyKYCDataMulti: "ADMIN", + modifyInvestorFlag: "ADMIN", + modifyInvestorFlagMulti: "ADMIN" }, ManualApprovalTransferManager: { - addManualApproval: "TRANSFER_APPROVAL", - revokeManualApproval: "TRANSFER_APPROVAL", + addManualApproval: "ADMIN", + addManualApprovalMulti: "ADMIN", + modifyManualApproval: "ADMIN", + modifyManualApprovalMulti: "ADMIN", + revokeManualApproval: "ADMIN", + revokeManualApprovalMulti: "ADMIN" }, PercentageTransferManager: { - modifyWhitelist: "WHITELIST", - modifyWhitelistMulti: "WHITELIST", + modifyWhitelist: "ADMIN", + modifyWhitelistMulti: "ADMIN", setAllowPrimaryIssuance: "ADMIN", changeHolderPercentage: "ADMIN" }, @@ -101,9 +110,10 @@ function getPermissionList() { }, VestingEscrowWallet: { changeTreasuryWallet: "ONLY_OWNER", + sendToTreasury: "OPERATOR", + pushAvailableTokens: "OPERATOR", + pushAvailableTokensMulti: "OPERATOR", depositTokens: "ADMIN", - sendToTreasury: "ADMIN", - pushAvailableTokens: "ADMIN", addTemplate: "ADMIN", removeTemplate: "ADMIN", addSchedule: "ADMIN", @@ -111,7 +121,6 @@ function getPermissionList() { modifySchedule: "ADMIN", revokeSchedule: "ADMIN", revokeAllSchedules: "ADMIN", - pushAvailableTokensMulti: "ADMIN", addScheduleMulti: "ADMIN", addScheduleFromTemplateMulti: "ADMIN", revokeSchedulesMulti: "ADMIN", diff --git a/CLI/commands/contract_manager.js b/CLI/commands/contract_manager.js index cce93444e..f0676a216 100644 --- a/CLI/commands/contract_manager.js +++ b/CLI/commands/contract_manager.js @@ -4,34 +4,35 @@ var common = require('./common/common_functions'); var gbl = require('./common/global'); var contracts = require('./helpers/contract_addresses'); var abis = require('./helpers/contract_abis'); +const input = require('./IO/input'); +const { table } = require('table'); +const moment = require('moment'); // App flow let currentContract = null; -async function executeApp() { - +async function executeApp () { common.logAsciiBull(); console.log("*********************************************"); console.log("Welcome to the Command-Line Contract Manager."); console.log("*********************************************"); console.log("Issuer Account: " + Issuer.address + "\n"); - //await setup(); + // await setup(); try { await selectContract(); } catch (err) { console.log(err); - return; } }; -async function selectContract() { - console.log('\n\x1b[34m%s\x1b[0m',"Contract Manager - Contract selection"); +async function selectContract () { + console.log('\n\x1b[34m%s\x1b[0m', "Contract Manager - Contract selection"); let contractList = ['PolymathRegistry', 'FeatureRegistry', 'SecurityTokenRegistry', 'ModuleRegistry']; while (!currentContract) { let index = readlineSync.keyInSelect(contractList, `Select a contract: `); - let selected = index == -1 ? 'CANCEL' : contractList[index]; + let selected = index === -1 ? 'CANCEL' : contractList[index]; switch (selected) { case 'PolymathRegistry': console.log(chalk.red(` @@ -47,7 +48,7 @@ async function selectContract() { break; case 'SecurityTokenRegistry': let strAdress = await contracts.securityTokenRegistry(); - let strABI = abis.securityTokenRegistry(); + let strABI = abis.iSecurityTokenRegistry(); currentContract = new web3.eth.Contract(strABI, strAdress); await strActions(); break; @@ -63,23 +64,41 @@ async function selectContract() { } } -async function strActions() { - console.log('\n\x1b[34m%s\x1b[0m',"Security Token Registry - Main menu"); - let contractOwner = await currentContract.methods.owner().call(); +async function strActions () { + console.log('\n\x1b[34m%s\x1b[0m', "Security Token Registry - Main menu"); + + const tokensGenerated = await currentContract.methods.getTokens().call(); + const latestProtocolVersion = await currentContract.methods.getLatestProtocolVersion().call(); + const expiryLimit = Math.floor(parseInt(await currentContract.methods.getExpiryLimit().call()) / 60 / 60 / 24); + const areFeesInPoly = await currentContract.methods.getIsFeeInPoly().call(); + const tickerRegFee = await currentContract.methods.getFees(web3.utils.keccak256('tickerRegFee')).call(); + const stLaunchFee = await currentContract.methods.getFees(web3.utils.keccak256('stLaunchFee')).call(); + console.log(`Tokens generated: ${tokensGenerated.length}`); + console.log(`Latest protocol version: ${latestProtocolVersion[0]}.${latestProtocolVersion[1]}.${latestProtocolVersion[2]}`); + console.log(`Current expiry limit: ${expiryLimit} days`); + console.log(`Fees:`); + if (areFeesInPoly) { + console.log(` - Ticker registration: ${web3.utils.fromWei(tickerRegFee.polyFee)} POLY`); + console.log(` - ST launch: ${web3.utils.fromWei(stLaunchFee.polyFee)} POLY`); + } else { + console.log(` - Ticker registration: ${web3.utils.fromWei(tickerRegFee.usdFee)} USD`); + console.log(` - ST launch: ${web3.utils.fromWei(stLaunchFee.usdFee)} USD`); + } - if (contractOwner != Issuer.address) { + let contractOwner = await currentContract.methods.owner().call(); + if (contractOwner !== Issuer.address) { console.log(chalk.red(`You are not the owner of this contract. Current owner is ${contractOwner}`)); currentContract = null; } else { - let actions = ['Modify Ticker', 'Remove Ticker', 'Modify SecurityToken', 'Change Expiry Limit', 'Change registration fee', 'Change ST launch fee']; + let actions = ['Modify Ticker', 'Remove Ticker', 'Modify SecurityToken', 'Change Expiry Limit', 'Change registration fee', 'Change ST launch fee', 'Change fee type', 'Get all token details']; let index = readlineSync.keyInSelect(actions, 'What do you want to do? '); - let selected = index == -1 ? 'CANCEL' : actions[index]; + let selected = index === -1 ? 'CANCEL' : actions[index]; switch (selected) { case 'Modify Ticker': let tickerToModify = readlineSync.question('Enter the token symbol that you want to add or modify: '); let tickerToModifyDetails = await currentContract.methods.getTickerDetails(tickerToModify).call(); - if (tickerToModifyDetails[1] == 0) { - console.log(chalk.yellow(`${ticker} is not registered.`)); + if (tickerToModifyDetails[1] === 0) { + console.log(chalk.yellow(`${tickerToModify} is not registered.`)); } else { console.log(`\n-- Current Ticker details --`); console.log(` Owner: ${tickerToModifyDetails[0]}`); @@ -88,41 +107,31 @@ async function strActions() { console.log(` Token name: ${tickerToModifyDetails[3]}`); console.log(` Status: ${tickerToModifyDetails[4] ? 'Deployed' : 'Not deployed'}\n`); } - let tickerOwner = readlineSync.question(`Enter the token owner: `, { - limit: function(input) { - return web3.utils.isAddress(input); - }, - limitMessage: "Must be a valid address" - }); + let tickerOwner = input.readAddress(`Enter the token owner: `); let tickerSTName = readlineSync.question(`Enter the token name: `); let tickerRegistrationDate = readlineSync.question(`Enter the Unix Epoch time on which ticker get registered: `); let tickerExpiryDate = readlineSync.question(`Enter the Unix Epoch time on wich the ticker will expire: `); let tickerStatus = readlineSync.keyInYNStrict(`Is the token deployed?`); let modifyTickerAction = currentContract.methods.modifyTicker(tickerOwner, tickerToModify, tickerSTName, tickerRegistrationDate, tickerExpiryDate, tickerStatus); - await common.sendTransaction(modifyTickerAction, {factor: 1.5}); + await common.sendTransaction(modifyTickerAction, { factor: 1.5 }); console.log(chalk.green(`Ticker has been updated successfully`)); break; case 'Remove Ticker': let tickerToRemove = readlineSync.question('Enter the token symbol that you want to remove: '); let tickerToRemoveDetails = await currentContract.methods.getTickerDetails(tickerToRemove).call(); - if (tickerToRemoveDetails[1] == 0) { - console.log(chalk.yellow(`${ticker} does not exist.`)); + if (tickerToRemoveDetails[1] === 0) { + console.log(chalk.yellow(`${tickerToRemove} does not exist.`)); } else { let removeTickerAction = currentContract.methods.removeTicker(tickerToRemove); - await common.sendTransaction(removeTickerAction, {factor: 3}); + await common.sendTransaction(removeTickerAction, { factor: 3 }); console.log(chalk.green(`Ticker has been removed successfully`)); } break; case 'Modify SecurityToken': - let stAddress = readlineSync.question('Enter the security token address that you want to add or modify: ', { - limit: function(input) { - return web3.utils.isAddress(input); - }, - limitMessage: "Must be a valid address" - }); + let stAddress = input.readAddress('Enter the security token address that you want to add or modify: '); let ticker; let stData = await currentContract.methods.getSecurityTokenData(stAddress).call(); - if (stData[1] == '0x0000000000000000000000000000000000000000') { + if (stData[1] === '0x0000000000000000000000000000000000000000') { console.log(chalk.yellow(`Currently there are no security token registered at ${stAddress}`)); ticker = readlineSync.question('Enter the token symbol that you want to register: '); } else { @@ -133,7 +142,7 @@ async function strActions() { console.log(` Deployed at: ${stData[3]}`); } let tickerDetails = await currentContract.methods.getTickerDetails(ticker).call(); - if (tickerDetails[1] == 0) { + if (tickerDetails[1] === 0) { console.log(chalk.yellow(`${ticker} is not registered.`)); } else { console.log(`-- Current Ticker details --`); @@ -141,44 +150,80 @@ async function strActions() { console.log(` Token name: ${tickerDetails[3]}\n`); } let name = readlineSync.question(`Enter the token name: `); - let owner = readlineSync.question(`Enter the token owner: `, { - limit: function(input) { - return web3.utils.isAddress(input); - }, - limitMessage: "Must be a valid address" - }); + let owner = input.readAddress('Enter the token owner: '); let tokenDetails = readlineSync.question(`Enter the token details: `); let deployedAt = readlineSync.questionInt(`Enter the Unix Epoch timestamp at which security token was deployed: `); let modifySTAction = currentContract.methods.modifySecurityToken(name, ticker, owner, stAddress, tokenDetails, deployedAt); - await common.sendTransaction(modifySTAction, {factor: 1.5}); + await common.sendTransaction(modifySTAction, { factor: 1.5 }); console.log(chalk.green(`Security Token has been updated successfully`)); break; case 'Change Expiry Limit': - let currentExpiryLimit = await currentContract.methods.getExpiryLimit().call(); - console.log(chalk.yellow(`Current expiry limit is ${Math.floor(parseInt(currentExpiryLimit)/60/60/24)} days`)); let newExpiryLimit = gbl.constants.DURATION.days(readlineSync.questionInt('Enter a new value in days for expiry limit: ')); let changeExpiryLimitAction = currentContract.methods.changeExpiryLimit(newExpiryLimit); let changeExpiryLimitReceipt = await common.sendTransaction(changeExpiryLimitAction); let changeExpiryLimitEvent = common.getEventFromLogs(currentContract._jsonInterface, changeExpiryLimitReceipt.logs, 'ChangeExpiryLimit'); - console.log(chalk.green(`Expiry limit was changed successfully. New limit is ${Math.floor(parseInt(changeExpiryLimitEvent._newExpiry)/60/60/24)} days\n`)); + console.log(chalk.green(`Expiry limit was changed successfully. New limit is ${Math.floor(parseInt(changeExpiryLimitEvent._newExpiry) / 60 / 60 / 24)} days\n`)); break; case 'Change registration fee': - let currentRegFee = web3.utils.fromWei(await currentContract.methods.getTickerRegistrationFee().call()); - console.log(chalk.yellow(`\nCurrent ticker registration fee is ${currentRegFee} POLY`)); - let newRegFee = web3.utils.toWei(readlineSync.questionInt('Enter a new value in POLY for ticker registration fee: ').toString()); + let newRegFee; + if (areFeesInPoly) { + console.log(chalk.yellow("\nFee is set in POLY")); + console.log(chalk.yellow(`Current ticker registration fee is ${web3.utils.fromWei(tickerRegFee.polyFee)} POLY (= ${web3.utils.fromWei(tickerRegFee.usdFee)} USD)`)); + newRegFee = web3.utils.toWei(readlineSync.questionInt('Enter a new value in POLY for ticker registration fee: ').toString()); + } else { + console.log(chalk.yellow("\nFee is set in USD")); + console.log(chalk.yellow(`Current ticker registration fee is ${web3.utils.fromWei(tickerRegFee.usdFee)} USD (= ${web3.utils.fromWei(tickerRegFee.polyFee)} POLY)`)); + newRegFee = web3.utils.toWei(readlineSync.questionInt('Enter a new value in USD for ticker registration fee: ').toString()); + } let changeRegFeeAction = currentContract.methods.changeTickerRegistrationFee(newRegFee); let changeRegFeeReceipt = await common.sendTransaction(changeRegFeeAction); let changeRegFeeEvent = common.getEventFromLogs(currentContract._jsonInterface, changeRegFeeReceipt.logs, 'ChangeTickerRegistrationFee'); - console.log(chalk.green(`Fee was changed successfully. New fee is ${web3.utils.fromWei(changeRegFeeEvent._newFee)} POLY\n`)); + console.log(chalk.green(`Fee was changed successfully. New fee is ${web3.utils.fromWei(changeRegFeeEvent._newFee)} ${(areFeesInPoly) ? "POLY" : "USD"}\n`)); break; case 'Change ST launch fee': - let currentLaunchFee = web3.utils.fromWei(await currentContract.methods.getSecurityTokenLaunchFee().call()); - console.log(chalk.yellow(`\nCurrent ST launch fee is ${currentLaunchFee} POLY`)); - let newLaunchFee = web3.utils.toWei(readlineSync.questionInt('Enter a new value in POLY for ST launch fee: ').toString()); + let newLaunchFee; + if (areFeesInPoly) { + console.log(chalk.yellow("\nFee is set in POLY")); + console.log(chalk.yellow(`Current ST launch fee is ${web3.utils.fromWei(stLaunchFee.polyFee)} POLY (= ${web3.utils.fromWei(stLaunchFee.usdFee)} USD)`)); + newLaunchFee = web3.utils.toWei(readlineSync.questionInt('Enter a new value in POLY for ST launch fee: ').toString()); + } else { + console.log(chalk.yellow("\nFee is set in USD")); + console.log(chalk.yellow(`Current ST launch fee is ${web3.utils.fromWei(stLaunchFee.usdFee)} USD (= ${web3.utils.fromWei(stLaunchFee.polyFee)} POLY)`)); + newLaunchFee = web3.utils.toWei(readlineSync.questionInt('Enter a new value in USD for ST launch fee: ').toString()); + } let changeLaunchFeeAction = currentContract.methods.changeSecurityLaunchFee(newLaunchFee); let changeLaunchFeeReceipt = await common.sendTransaction(changeLaunchFeeAction); let changeLaunchFeeEvent = common.getEventFromLogs(currentContract._jsonInterface, changeLaunchFeeReceipt.logs, 'ChangeSecurityLaunchFee'); - console.log(chalk.green(`Fee was changed successfully. New fee is ${web3.utils.fromWei(changeLaunchFeeEvent._newFee)} POLY\n`)); + console.log(chalk.green(`Fee was changed successfully. New fee is ${web3.utils.fromWei(changeLaunchFeeEvent._newFee)} ${(areFeesInPoly) ? "POLY" : "USD"}\n`)); + break; + case 'Change fee type': + let changeFeeType = readlineSync.keyInYNStrict(`\nFees are currently set in ${(areFeesInPoly) ? "POLY" : "USD"}. Do you want to change to ${(areFeesInPoly) ? "USD" : "POLY"}`); + if (changeFeeType) { + let newRegFee = web3.utils.toWei(readlineSync.questionInt(`Enter a new ticker registration fee in ${(areFeesInPoly) ? "USD" : "POLY"}: `).toString()); + let newLaunchFee = web3.utils.toWei(readlineSync.questionInt(`Enter a new ST launch fee in ${(areFeesInPoly) ? "USD" : "POLY"}: `).toString()); + let changeFeesAmountAndCurrencyAction = currentContract.methods.changeFeesAmountAndCurrency(newRegFee, newLaunchFee, !areFeesInPoly); + let changeFeesAmountAndCurrencyReceipt = await common.sendTransaction(changeFeesAmountAndCurrencyAction); + let changeFeeCurrencyEvent = common.getEventFromLogs(currentContract._jsonInterface, changeFeesAmountAndCurrencyReceipt.logs, 'ChangeFeeCurrency'); + let changeRegFeeEvent = common.getEventFromLogs(currentContract._jsonInterface, changeFeesAmountAndCurrencyReceipt.logs, 'ChangeTickerRegistrationFee'); + let changeLaunchFeeEvent = common.getEventFromLogs(currentContract._jsonInterface, changeFeesAmountAndCurrencyReceipt.logs, 'ChangeSecurityLaunchFee'); + console.log(chalk.green(`Fee type was changed successfully. New fee type is ${(changeFeeCurrencyEvent._isFeeInPoly) ? "POLY" : "USD"}`)); + console.log(chalk.green(`New ticker registration fee is ${web3.utils.fromWei(changeRegFeeEvent._newFee)} ${(changeFeeCurrencyEvent._isFeeInPoly) ? "POLY" : "USD"}`)); + console.log(chalk.green(`New token launch fee is ${web3.utils.fromWei(changeLaunchFeeEvent._newFee)} ${(changeFeeCurrencyEvent._isFeeInPoly) ? "POLY" : "USD"}\n`)); + } else { + console.log(chalk.yellow("\nFee type was not changed\n")); + } + break; + case 'Get all token details': + let tokenSymbols = await currentContract.methods.getTokens().call(); + let tokenDataTable = [['Symbol', 'Token Name', 'Address', 'Details', 'Deployed Date']]; + let iSecurityTokenABI = abis.iSecurityToken(); + for (let i = 0; i < tokenSymbols.length; i++) { + let securityToken = new web3.eth.Contract(iSecurityTokenABI, tokenSymbols[i]); + let tokenName = await securityToken.methods.name().call(); + let stDetails = await currentContract.methods.getSecurityTokenData(tokenSymbols[i]).call(); + tokenDataTable.push([stDetails[0], tokenName, stDetails[1], stDetails[2], moment.unix(stDetails[3]).format('MM/DD/YYYY HH:mm')]); + } + console.log(table(tokenDataTable)); break; case 'CANCEL': process.exit(0); @@ -188,7 +233,7 @@ async function strActions() { } module.exports = { - executeApp: async function() { + executeApp: async function () { return executeApp(); } -} \ No newline at end of file +} diff --git a/CLI/commands/dividends_manager.js b/CLI/commands/dividends_manager.js index 20880f7c0..28fc3a934 100644 --- a/CLI/commands/dividends_manager.js +++ b/CLI/commands/dividends_manager.js @@ -6,6 +6,8 @@ const gbl = require('./common/global'); const contracts = require('./helpers/contract_addresses'); const abis = require('./helpers/contract_abis'); const csvParse = require('./helpers/csv'); +const input = require('./IO/input'); +const output = require('./IO/output'); const BigNumber = require('bignumber.js'); const { table } = require('table') @@ -25,11 +27,11 @@ let dividendsType; async function executeApp() { console.log('\n', chalk.blue('Dividends Manager - Main Menu', '\n')); - let tmModules = await getAllModulesByType(gbl.constants.MODULES_TYPES.DIVIDENDS); + let tmModules = await common.getAllModulesByType(securityToken, gbl.constants.MODULES_TYPES.DIVIDENDS); let nonArchivedModules = tmModules.filter(m => !m.archived); if (nonArchivedModules.length > 0) { console.log(`Dividends modules attached:`); - nonArchivedModules.map(m => console.log(`- ${m.name} at ${m.address}`)) + nonArchivedModules.map(m => `${m.label}: ${m.name} (${m.version}) at ${m.address}`); } else { console.log(`There are no dividends modules attached`); } @@ -76,32 +78,30 @@ async function createCheckpointFromST() { } async function exploreAddress(currentCheckpoint) { - let address = readlineSync.question('Enter address to explore: ', { - limit: function (input) { - return web3.utils.isAddress(input); - }, - limitMessage: "Must be a valid address", - }); + let address = input.readAddress('Enter address to explore: '); let checkpoint = null; if (currentCheckpoint > 0) { checkpoint = await selectCheckpoint(false); } - let balance = web3.utils.fromWei(await securityToken.methods.balanceOf(address).call()); + let balanceUnlocked = web3.utils.fromWei(await securityToken.methods.balanceOfByPartition(web3.utils.asciiToHex('UNLOCKED'), address).call()); + let balanceTotal = web3.utils.fromWei(await securityToken.methods.balanceOf(address).call()); + output.logUnlockedBalance(address, tokenSymbol, balanceUnlocked, balanceTotal); + let totalSupply = web3.utils.fromWei(await securityToken.methods.totalSupply().call()); - console.log(`Balance of ${address} is: ${balance} ${tokenSymbol}`); - console.log(`TotalSupply is: ${totalSupply} ${tokenSymbol}`); + output.logTotalSupply(tokenSymbol, totalSupply); if (checkpoint) { - let balanceAt = web3.utils.fromWei(await securityToken.methods.balanceOfAt(address, checkpoint).call()); - let totalSupplyAt = web3.utils.fromWei(await securityToken.methods.totalSupplyAt(checkpoint).call()); - console.log(`Balance of ${address} at checkpoint ${checkpoint}: ${balanceAt} ${tokenSymbol}`); - console.log(`TotalSupply at checkpoint ${checkpoint} is: ${totalSupplyAt} ${tokenSymbol}`); + let balanceAtCheckpoint = web3.utils.fromWei(await securityToken.methods.balanceOfAt(address, checkpoint).call()); + output.logBalanceAtCheckpoint(address, tokenSymbol, checkpoint, balanceAtCheckpoint); + + let totalSupplyAtCheckpoint = web3.utils.fromWei(await securityToken.methods.totalSupplyAt(checkpoint).call()); + output.logTotalSupplyAtCheckpoint(tokenSymbol, checkpoint, totalSupplyAtCheckpoint); } } async function configExistingModules(dividendModules) { - let options = dividendModules.map(m => `${m.name} at ${m.address}`); + let options = dividendModules.map(m => `${m.label}: ${m.name} (${m.version}) at ${m.address}`); let index = readlineSync.keyInSelect(options, 'Which module do you want to config? ', { cancel: 'RETURN' }); console.log('Selected:', index != -1 ? options[index] : 'RETURN', '\n'); let moduleNameSelected = index != -1 ? dividendModules[index].name : 'RETURN'; @@ -161,6 +161,7 @@ async function dividendsManager() { break; case 'Explore checkpoint': await exploreCheckpoint(); + break; case 'Show current default exclusions': showExcluded(defaultExcluded); break; @@ -190,12 +191,7 @@ async function dividendsManager() { } async function changeWallet() { - let newWallet = readlineSync.question('Enter the new account address to receive reclaimed dividends and tax: ', { - limit: function (input) { - return web3.utils.isAddress(input); - }, - limitMessage: "Must be a valid address", - }); + let newWallet = input.readAddress('Enter the new account address to receive reclaimed dividends and tax: '); let action = currentDividendsModule.methods.changeWallet(newWallet); let receipt = await common.sendTransaction(action); let event = common.getEventFromLogs(currentDividendsModule._jsonInterface, receipt.logs, 'SetWallet'); @@ -341,19 +337,9 @@ async function manageExistingDividend(dividendIndex) { } async function taxWithholding() { - let addresses = readlineSync.question(`Enter addresses to set tax withholding to(ex - add1, add2, add3, ...) or leave empty to read from 'tax_withholding_data.csv': `, { - limit: function (input) { - return input === '' || (input.split(',').every(a => web3.utils.isAddress(a))); - }, - limitMessage: `All addresses must be valid` - }).split(','); + let addresses = input.readMultipleAddresses(`Enter addresses to set tax withholding to(ex - add1, add2, add3, ...) or leave empty to read from 'tax_withholding_data.csv': `).split(','); if (addresses[0] !== '') { - let percentage = readlineSync.question('Enter the percentage of dividends to withhold (number between 0-100): ', { - limit: function (input) { - return (parseFloat(input) >= 0 && parseFloat(input) <= 100); - }, - limitMessage: 'Must be a value between 0 and 100', - }); + let percentage = input.readPercentage('Enter the percentage of dividends to withhold'); let percentageWei = web3.utils.toWei((percentage / 100).toString()); let setWithHoldingFixedAction = currentDividendsModule.methods.setWithholdingFixed(addresses, percentageWei); let receipt = await common.sendTransaction(setWithHoldingFixedAction); @@ -391,13 +377,7 @@ async function createDividends() { let token; if (dividendsType === 'ERC20') { do { - dividendToken = readlineSync.question(`Enter the address of ERC20 token in which dividend will be denominated(POLY = ${polyToken.options.address}): `, { - limit: function (input) { - return web3.utils.isAddress(input); - }, - limitMessage: "Must be a valid ERC20 address", - defaultInput: polyToken.options.address - }); + dividendToken = input.readAddress(`Enter the address of ERC20 token in which dividend will be denominated(POLY = ${polyToken.options.address}): `, polyToken.options.address); let erc20Symbol = await getERC20TokenSymbol(dividendToken); if (erc20Symbol != null) { token = new web3.eth.Contract(abis.erc20(), dividendToken); @@ -421,7 +401,7 @@ async function createDividends() { let defaultTime = now + gbl.constants.DURATION.minutes(10); let expiryTime = readlineSync.questionInt('Enter the dividend expiry time (Unix Epoch time)\n(10 minutes from now = ' + defaultTime + ' ): ', { defaultInput: defaultTime }); - let useDefaultExcluded = !readlineSync.keyInYNStrict(`Do you want to use data from 'dividends_exclusions_data.csv' for this dividend? If not, default exclusions will apply.`); + let useDefaultExcluded = !readlineSync.keyInYNStrict(`Do you want to use data from 'exclusions_data.csv' for this dividend? If not, default exclusions will apply.`); let createDividendAction; if (dividendsType == 'ERC20') { @@ -451,14 +431,14 @@ async function createDividends() { createDividendAction = currentDividendsModule.methods.createDividendWithCheckpoint(maturityTime, expiryTime, checkpointId, web3.utils.toHex(dividendName)); } else { let excluded = getExcludedFromDataFile(); - createDividendAction = currentDividendsModule.methods.createDividendWithCheckpointAndExclusions(maturityTime, expiryTime, checkpointId, excluded, web3.utils.toHex(dividendName)); + createDividendAction = currentDividendsModule.methods.createDividendWithCheckpointAndExclusions(maturityTime, expiryTime, checkpointId, excluded[0], web3.utils.toHex(dividendName)); } } else { if (useDefaultExcluded) { createDividendAction = currentDividendsModule.methods.createDividend(maturityTime, expiryTime, web3.utils.toHex(dividendName)); } else { let excluded = getExcludedFromDataFile(); - createDividendAction = currentDividendsModule.methods.createDividendWithExclusions(maturityTime, expiryTime, excluded, web3.utils.toHex(dividendName)); + createDividendAction = currentDividendsModule.methods.createDividendWithExclusions(maturityTime, expiryTime, excluded[0], web3.utils.toHex(dividendName)); } } let receipt = await common.sendTransaction(createDividendAction, { value: dividendAmountBN }); @@ -477,19 +457,13 @@ async function reclaimFromContract() { switch (selected) { case 'ETH': let ethBalance = await web3.eth.getBalance(currentDividendsModule.options.address); - console.log(chalk.yellow(`Current ETH balance: ${web3.utils.fromWei(ethBalance)} ETH`)); + output.logBalance(currentDividendsModule.options.address, 'ETH', web3.utils.fromWei(ethBalance)); let reclaimETHAction = currentDividendsModule.methods.reclaimETH(); await common.sendTransaction(reclaimETHAction); console.log(chalk.green('ETH has been reclaimed succesfully!')); break; case 'ERC20': - let erc20Address = readlineSync.question('Enter the ERC20 token address to reclaim (POLY = ' + polyToken.options.address + '): ', { - limit: function (input) { - return web3.utils.isAddress(input); - }, - limitMessage: "Must be a valid address", - defaultInput: polyToken.options.address - }); + let erc20Address = input.readAddress('Enter the ERC20 token address to reclaim (POLY = ' + polyToken.options.address + '): ', polyToken.options.address); let reclaimERC20Action = currentDividendsModule.methods.reclaimERC20(erc20Address); await common.sendTransaction(reclaimERC20Action); console.log(chalk.green('ERC20 has been reclaimed succesfully!')); @@ -554,12 +528,7 @@ function showReport(_name, _tokenSymbol, _tokenDecimals, _amount, _witthheld, _c } async function pushDividends(dividendIndex, checkpointId) { - let accounts = readlineSync.question('Enter addresses to push dividends to (ex- add1,add2,add3,...) or leave empty to push to all addresses: ', { - limit: function (input) { - return input === '' || (input.split(',').every(a => web3.utils.isAddress(a))); - }, - limitMessage: `All addresses must be valid` - }).split(','); + let accounts = input.readMultipleAddresses('Enter addresses to push dividends to (ex- add1,add2,add3,...) or leave empty to push to all addresses: ').split(','); if (accounts[0] !== '') { let action = currentDividendsModule.methods.pushDividendPaymentToAddresses(dividendIndex, accounts); let receipt = await common.sendTransaction(action); @@ -577,12 +546,7 @@ async function pushDividends(dividendIndex, checkpointId) { } async function exploreAccount(dividendIndex, dividendTokenAddress, dividendTokenSymbol, dividendTokenDecimals) { - let account = readlineSync.question('Enter address to explore: ', { - limit: function (input) { - return web3.utils.isAddress(input); - }, - limitMessage: "Must be a valid address", - }); + let account = input.readAddress('Enter address to explore: '); let isExcluded = await currentDividendsModule.methods.isExcluded(account, dividendIndex).call(); let hasClaimed = await currentDividendsModule.methods.isClaimed(account, dividendIndex).call(); let dividendAmounts = await currentDividendsModule.methods.calculateDividend(dividendIndex, account).call(); @@ -625,36 +589,23 @@ to account ${ event._claimer} ` } async function addDividendsModule() { - let availableModules = await moduleRegistry.methods.getModulesByTypeAndToken(gbl.constants.MODULES_TYPES.DIVIDENDS, securityToken.options.address).call(); - let moduleList = await Promise.all(availableModules.map(async function (m) { - let moduleFactoryABI = abis.moduleFactory(); - let moduleFactory = new web3.eth.Contract(moduleFactoryABI, m); - let moduleName = web3.utils.hexToUtf8(await moduleFactory.methods.name().call()); - let moduleVersion = await moduleFactory.methods.version().call(); - return { name: moduleName, version: moduleVersion, factoryAddress: m }; - })); - + let moduleList = await common.getAvailableModules(moduleRegistry, gbl.constants.MODULES_TYPES.DIVIDENDS, securityToken.options.address); let options = moduleList.map(m => `${m.name} - ${m.version} (${m.factoryAddress})`); let index = readlineSync.keyInSelect(options, 'Which dividends module do you want to add? ', { cancel: 'Return' }); if (index != -1 && readlineSync.keyInYNStrict(`Are you sure you want to add ${options[index]}? `)) { - let wallet = readlineSync.question('Enter the account address to receive reclaimed dividends and tax: ', { - limit: function (input) { - return web3.utils.isAddress(input); - }, - limitMessage: "Must be a valid address", - }); - let configureFunction = abis.erc20DividendCheckpoint().find(o => o.name === 'configure' && o.type === 'function'); - let bytes = web3.eth.abi.encodeFunctionCall(configureFunction, [wallet]); - - let selectedDividendFactoryAddress = moduleList[index].factoryAddress; - let addModuleAction = securityToken.methods.addModule(selectedDividendFactoryAddress, bytes, 0, 0); - let receipt = await common.sendTransaction(addModuleAction); - let event = common.getEventFromLogs(securityToken._jsonInterface, receipt.logs, 'ModuleAdded'); - console.log(chalk.green(`Module deployed at address: ${event._module} `)); + const moduleABI = moduleList[index].name === 'ERC20DividendCheckpoint' ? abis.erc20DividendCheckpoint() : abis.etherDividendCheckpoint(); + await common.addModule(securityToken, polyToken, moduleList[index].factoryAddress, moduleABI, getDividendsInitializeData); } } +function getDividendsInitializeData(moduleABI) { + let wallet = input.readAddress('Enter the account address to receive reclaimed dividends and tax: '); + let configureFunction = moduleABI.find(o => o.name === 'configure' && o.type === 'function'); + let bytes = web3.eth.abi.encodeFunctionCall(configureFunction, [wallet]); + return bytes; +} + // Helper functions async function getBalance(address, tokenAddress) { if (tokenAddress !== gbl.constants.ADDRESS_ZERO) { @@ -821,51 +772,13 @@ function showExcluded(excluded) { console.log(); } -async function getAllModulesByType(type) { - function ModuleInfo(_moduleType, _name, _address, _factoryAddress, _archived, _paused) { - this.name = _name; - this.type = _moduleType; - this.address = _address; - this.factoryAddress = _factoryAddress; - this.archived = _archived; - this.paused = _paused; - } - - let modules = []; - - let allModules = await securityToken.methods.getModulesByType(type).call(); - - for (let i = 0; i < allModules.length; i++) { - let details = await securityToken.methods.getModule(allModules[i]).call(); - let nameTemp = web3.utils.hexToUtf8(details[0]); - let pausedTemp = null; - if (type == gbl.constants.MODULES_TYPES.STO || type == gbl.constants.MODULES_TYPES.TRANSFER) { - let abiTemp = JSON.parse(require('fs').readFileSync(`${__dirname} /../../ build / contracts / ${nameTemp}.json`).toString()).abi; - let contractTemp = new web3.eth.Contract(abiTemp, details[1]); - pausedTemp = await contractTemp.methods.paused().call(); - } - modules.push(new ModuleInfo(type, nameTemp, details[1], details[2], details[3], pausedTemp)); - } - - return modules; -} - async function initialize(_tokenSymbol) { welcome(); await setup(); - if (typeof _tokenSymbol === 'undefined') { - tokenSymbol = await selectToken(); - } else { - tokenSymbol = _tokenSymbol; - } - let securityTokenAddress = await securityTokenRegistry.methods.getSecurityTokenAddress(tokenSymbol).call(); - if (securityTokenAddress == '0x0000000000000000000000000000000000000000') { - console.log(chalk.red(`Selected Security Token ${tokenSymbol} does not exist.`)); + securityToken = await common.selectToken(securityTokenRegistry, _tokenSymbol); + if (securityToken === null) { process.exit(0); } - let securityTokenABI = abis.securityToken(); - securityToken = new web3.eth.Contract(securityTokenABI, securityTokenAddress); - securityToken.setProvider(web3.currentProvider); } function welcome() { @@ -879,8 +792,8 @@ function welcome() { async function setup() { try { let securityTokenRegistryAddress = await contracts.securityTokenRegistry(); - let securityTokenRegistryABI = abis.securityTokenRegistry(); - securityTokenRegistry = new web3.eth.Contract(securityTokenRegistryABI, securityTokenRegistryAddress); + let iSecurityTokenRegistryABI = abis.iSecurityTokenRegistry(); + securityTokenRegistry = new web3.eth.Contract(iSecurityTokenRegistryABI, securityTokenRegistryAddress); securityTokenRegistry.setProvider(web3.currentProvider); let polyTokenAddress = await contracts.polyToken(); @@ -899,36 +812,6 @@ async function setup() { } } -async function selectToken() { - let result = null; - - let userTokens = await securityTokenRegistry.methods.getTokensByOwner(Issuer.address).call(); - let tokenDataArray = await Promise.all(userTokens.map(async function (t) { - let tokenData = await securityTokenRegistry.methods.getSecurityTokenData(t).call(); - return { symbol: tokenData[0], address: t }; - })); - let options = tokenDataArray.map(function (t) { - return `${t.symbol} - Deployed at ${t.address} `; - }); - options.push('Enter token symbol manually'); - - let index = readlineSync.keyInSelect(options, 'Select a token:', { cancel: 'Exit' }); - let selected = index != -1 ? options[index] : 'Exit'; - switch (selected) { - case 'Enter token symbol manually': - result = readlineSync.question('Enter the token symbol: '); - break; - case 'Exit': - process.exit(); - break; - default: - result = tokenDataArray[index].symbol; - break; - } - - return result; -} - async function getERC20TokenSymbol(tokenAddress) { let tokenSymbol = null; try { diff --git a/CLI/commands/helpers/contract_abis.js b/CLI/commands/helpers/contract_abis.js index 2cf7b113d..217cc6ea6 100644 --- a/CLI/commands/helpers/contract_abis.js +++ b/CLI/commands/helpers/contract_abis.js @@ -1,8 +1,10 @@ let polymathRegistryABI; let securityTokenRegistryABI; +let iSecurityTokenRegistryABI; let featureRegistryABI; let moduleRegistryABI; let securityTokenABI; +let iSecurityTokenABI; let stoInterfaceABI; let cappedSTOABI; let usdTieredSTOABI; @@ -19,19 +21,23 @@ let cappedSTOFactoryABI; let usdTieredSTOFactoryABI; let erc20DividendCheckpointABI; let etherDividendCheckpointABI; +let vestingEscrowWalletABI; let moduleInterfaceABI; let ownableABI; let stoABI; let iTransferManagerABI; let moduleFactoryABI; +let moduleABI; let erc20ABI; try { polymathRegistryABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/PolymathRegistry.json`).toString()).abi; securityTokenRegistryABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/SecurityTokenRegistry.json`).toString()).abi; + iSecurityTokenRegistryABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/ISecurityTokenRegistry.json`).toString()).abi; featureRegistryABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/FeatureRegistry.json`).toString()).abi; moduleRegistryABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/ModuleRegistry.json`).toString()).abi; securityTokenABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/SecurityToken.json`).toString()).abi; + iSecurityTokenABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/ISecurityToken.json`).toString()).abi; stoInterfaceABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/ISTO.json`).toString()).abi; cappedSTOABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/CappedSTO.json`).toString()).abi; usdTieredSTOABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/USDTieredSTO.json`).toString()).abi; @@ -48,12 +54,15 @@ try { usdTieredSTOFactoryABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/USDTieredSTOFactory.json`).toString()).abi; erc20DividendCheckpointABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/ERC20DividendCheckpoint.json`).toString()).abi; etherDividendCheckpointABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/EtherDividendCheckpoint.json`).toString()).abi; + vestingEscrowWalletABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/VestingEscrowWallet.json`).toString()).abi; moduleInterfaceABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/IModule.json`).toString()).abi; ownableABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/Ownable.json`).toString()).abi; stoABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/STO.json`).toString()).abi iTransferManagerABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/ITransferManager.json`).toString()).abi moduleFactoryABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/ModuleFactory.json`).toString()).abi; - erc20ABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/DetailedERC20.json`).toString()).abi; + moduleABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/Module.json`).toString()).abi; + // Note: use ISecurity Token for ERC20 as it contains full ERC20Detailed ABI + erc20ABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/ISecurityToken.json`).toString()).abi; } catch (err) { console.log('\x1b[31m%s\x1b[0m', "Couldn't find contracts' artifacts. Make sure you ran truffle compile first"); throw err; @@ -66,6 +75,9 @@ module.exports = { securityTokenRegistry: function () { return securityTokenRegistryABI; }, + iSecurityTokenRegistry: function () { + return iSecurityTokenRegistryABI; + }, featureRegistry: function () { return featureRegistryABI; }, @@ -75,6 +87,9 @@ module.exports = { securityToken: function () { return securityTokenABI; }, + iSecurityToken: function () { + return iSecurityTokenABI; + }, stoInterface: function () { return stoInterfaceABI; }, @@ -123,6 +138,9 @@ module.exports = { etherDividendCheckpoint: function () { return etherDividendCheckpointABI; }, + vestingEscrowWallet: function () { + return vestingEscrowWalletABI; + }, moduleInterface: function () { return moduleInterfaceABI; }, @@ -152,5 +170,8 @@ module.exports = { "type": "function" }]; return alternativeErc20; + }, + moduleABI: function () { + return moduleABI; } -} \ No newline at end of file +} diff --git a/CLI/commands/investor_portal.js b/CLI/commands/investor_portal.js index 124d0d297..22c40cec8 100644 --- a/CLI/commands/investor_portal.js +++ b/CLI/commands/investor_portal.js @@ -4,6 +4,7 @@ var BigNumber = require('bignumber.js'); var chalk = require('chalk'); var common = require('./common/common_functions'); var gbl = require('./common/global'); +const input = require('./IO/input'); // Load Contract artifacts var contracts = require('./helpers/contract_addresses'); @@ -31,7 +32,7 @@ let STAddress; let STOAddress; // Global display variables -let displayCanBuy; +let displayCannotBuy; let displayValidKYC; // Start Script @@ -83,8 +84,8 @@ async function executeApp(investorAddress, investorPrivKey, symbol, currency, am async function setup() { try { let securityTokenRegistryAddress = await contracts.securityTokenRegistry(); - let securityTokenRegistryABI = abis.securityTokenRegistry(); - securityTokenRegistry = new web3.eth.Contract(securityTokenRegistryABI, securityTokenRegistryAddress); + let iSecurityTokenRegistryABI = abis.iSecurityTokenRegistry(); + securityTokenRegistry = new web3.eth.Contract(iSecurityTokenRegistryABI, securityTokenRegistryAddress); securityTokenRegistry.setProvider(web3.currentProvider); let polytokenAddress = await contracts.polyToken(); @@ -112,14 +113,14 @@ async function inputSymbol(symbol) { if (STAddress == "0x0000000000000000000000000000000000000000") { console.log(`Token symbol provided is not a registered Security Token. Please enter another symbol.`); } else { - let securityTokenABI = abis.securityToken(); - securityToken = new web3.eth.Contract(securityTokenABI, STAddress); + let iSecurityTokenABI = abis.iSecurityToken(); + securityToken = new web3.eth.Contract(iSecurityTokenABI, STAddress); await showTokenInfo(); - let gtmModule = await securityToken.methods.getModulesByName(web3.utils.toHex('GeneralTransferManager')).call(); + let gtmModules = await securityToken.methods.getModulesByName(web3.utils.toHex('GeneralTransferManager')).call(); let generalTransferManagerABI = abis.generalTransferManager(); - generalTransferManager = new web3.eth.Contract(generalTransferManagerABI, gtmModule[0]); + generalTransferManager = new web3.eth.Contract(generalTransferManagerABI, gtmModules[0]); let stoModules = await securityToken.methods.getModulesByType(gbl.constants.MODULES_TYPES.STO).call(); if (stoModules.length == 0) { @@ -191,11 +192,12 @@ async function showCappedSTOInfo() { let now = Math.floor(Date.now() / 1000); - await generalTransferManager.methods.whitelist(User.address).call({}, function (error, result) { - displayCanBuy = result.canBuyFromSTO; - displayValidKYC = parseInt(result.expiryTime) > now; + await generalTransferManager.methods.getKYCData([User.address]).call({}, function (error, result) { + displayValidKYC = parseInt(result[2]) > now; }); + displayCannotBuy = await generalTransferManager.methods.getInvestorFlag(User.address, 1).call(); + let timeTitle; let timeRemaining; if (now < displayStartTime) { @@ -224,7 +226,7 @@ async function showCappedSTOInfo() { - Investor count: ${displayInvestorCount} `); - if (!displayCanBuy) { + if (displayCannotBuy) { console.log(chalk.red(`Your address is not approved to participate in this token sale.\n`)); process.exit(0); } else if (!displayValidKYC) { @@ -295,21 +297,25 @@ async function showUserInfoForUSDTieredSTO() { } let displayInvestorInvestedUSD = web3.utils.fromWei(await currentSTO.methods.investorInvestedUSD(User.address).call()); - console.log(` - Total invested in USD: ${displayInvestorInvestedUSD} USD`); + console.log(` - Total invested in USD: ${displayInvestorInvestedUSD} USD`); + + let now = Math.floor(Date.now() / 1000); - await generalTransferManager.methods.whitelist(User.address).call({}, function (error, result) { - displayCanBuy = result.canBuyFromSTO; - displayValidKYC = parseInt(result.expiryTime) > Math.floor(Date.now() / 1000); + await generalTransferManager.methods.getKYCData([User.address]).call({}, function (error, result) { + displayValidKYC = parseInt(result[2]) > now; }); - console.log(` - Whitelisted: ${(displayCanBuy) ? 'YES' : 'NO'}`); + + displayCannotBuy = await generalTransferManager.methods.getInvestorFlag(User.address, 1).call(); + + console.log(` - Can buy from STO: ${(!displayCannotBuy) ? 'YES' : 'NO'}`); console.log(` - Valid KYC: ${(displayValidKYC) ? 'YES' : 'NO'}`); - let investorData = await currentSTO.methods.investors(User.address).call(); - let displayIsUserAccredited = investorData.accredited == 1; + displayIsUserAccredited = await generalTransferManager.methods.getInvestorFlag(User.address, 0).call(); + console.log(` - Accredited: ${(displayIsUserAccredited) ? "YES" : "NO"}`) if (!displayIsUserAccredited) { - let displayOverrideNonAccreditedLimitUSD = web3.utils.fromWei(investorData.nonAccreditedLimitUSDOverride); + let displayOverrideNonAccreditedLimitUSD = web3.utils.fromWei(await currentSTO.methods.nonAccreditedLimitUSDOverride(User.address).call()); let displayNonAccreditedLimitUSD = displayOverrideNonAccreditedLimitUSD != 0 ? displayOverrideNonAccreditedLimitUSD : web3.utils.fromWei(await currentSTO.methods.nonAccreditedLimitUSD().call()); let displayTokensRemainingAllocation = displayNonAccreditedLimitUSD - displayInvestorInvestedUSD; console.log(` - Remaining allocation: ${(displayTokensRemainingAllocation > 0 ? displayTokensRemainingAllocation : 0)} USD`); @@ -369,10 +375,13 @@ async function showUSDTieredSTOInfo() { let mintedPerTier = mintedPerTierPerRaiseType[gbl.constants.FUND_RAISE_TYPES[type]]; if ((type == STABLE) && (stableSymbols.length)) { displayMintedPerTierPerType += ` - Sold for stable coin(s): ${web3.utils.fromWei(mintedPerTier)} ${displayTokenSymbol} ${displayDiscountMinted}`; - } else { + Sold for stable coin(s): ${web3.utils.fromWei(mintedPerTier)} ${displayTokenSymbol}`; + } else if (type == POLY) { displayMintedPerTierPerType += ` Sold for ${type}:\t\t ${web3.utils.fromWei(mintedPerTier)} ${displayTokenSymbol} ${displayDiscountMinted}`; + } else if (type == ETH) { + displayMintedPerTierPerType += ` + Sold for ${type}:\t\t ${web3.utils.fromWei(mintedPerTier)} ${displayTokenSymbol}`; } } @@ -456,7 +465,7 @@ async function showUSDTieredSTOInfo() { Total USD: ${displayFundsRaisedUSD} USD `); - if (!displayCanBuy) { + if (displayCannotBuy) { console.log(chalk.red(`Your address is not approved to participate in this token sale.\n`)); process.exit(0); } else if (!displayValidKYC) { @@ -548,14 +557,14 @@ async function investUsdTieredSTO(currency, amount) { raiseTypes.push(stable.symbol) }) } - raiseType = raiseTypes[selectToken('Choose one of the allowed raise types: ')]; + raiseType = raiseTypes[selectRaiseType('Choose one of the allowed raise types: ')]; } else { if (raiseTypes[0] == STABLE) { raiseTypes.splice(raiseTypes.indexOf(STABLE), 1) stableSymbols.forEach((stable) => { raiseTypes.push(stable.symbol) }) - raiseType = raiseTypes[selectToken('Choose one of the allowed stable coin(s): ')]; + raiseType = raiseTypes[selectRaiseType('Choose one of the allowed stable coin(s): ')]; } else { raiseType = raiseTypes[0]; console.log(''); @@ -565,7 +574,7 @@ async function investUsdTieredSTO(currency, amount) { let cost; if (typeof amount === 'undefined') { - let investorInvestedUSD = web3.utils.fromWei(await currentSTO.methods.investorInvestedUSD(User.address).call()); + let investorInvestedUSD = await currentSTO.methods.investorInvestedUSD(User.address).call(); let minimumInvestmentUSD = await currentSTO.methods.minimumInvestmentUSD().call(); let minimumInvestmentRaiseType; @@ -576,12 +585,8 @@ async function investUsdTieredSTO(currency, amount) { minimumInvestmentRaiseType = await currentSTO.methods.convertFromUSD(gbl.constants.FUND_RAISE_TYPES[raiseType], minimumInvestmentUSD).call(); } - cost = readlineSync.question(chalk.yellow(`Enter the amount of ${raiseType} you would like to invest or press 'Enter' to exit: `), { - limit: function (input) { - return investorInvestedUSD != 0 || parseInt(input) > parseInt(web3.utils.fromWei(minimumInvestmentRaiseType)); - }, - limitMessage: `Amount must be greater than minimum investment (${web3.utils.fromWei(minimumInvestmentRaiseType)} ${raiseType} = ${web3.utils.fromWei(minimumInvestmentUSD)} USD)` - }); + let minimumCost = web3.utils.fromWei((new BigNumber(investorInvestedUSD)).minus(new BigNumber(minimumInvestmentRaiseType))); + cost = input.readNumberGreaterThan(minimumCost, (chalk.yellow(`Enter the amount of ${raiseType} you would like to invest or press 'Enter' to exit: `))); } else { cost = amount; } @@ -590,57 +595,72 @@ async function investUsdTieredSTO(currency, amount) { let costWei = web3.utils.toWei(cost.toString()); let tokensToBuy; + + let stableSymbolsAndBalance; + let stableInfo; + // if raiseType is different than ETH or POLY, we assume is STABLE if ((raiseType != ETH) && (raiseType != POLY)) { - tokensToBuy = await currentSTO.methods.buyTokensView(User.address, costWei, gbl.constants.FUND_RAISE_TYPES[STABLE]).call(); - } else { - tokensToBuy = await currentSTO.methods.buyTokensView(User.address, costWei, gbl.constants.FUND_RAISE_TYPES[raiseType]).call(); - } - - let minTokenToBuy = tokensToBuy.tokensMinted; - console.log(chalk.yellow(`You are going to spend ${web3.utils.fromWei(tokensToBuy.spentValue)} ${raiseType} (${web3.utils.fromWei(tokensToBuy.spentUSD)} USD) to buy ${web3.utils.fromWei(minTokenToBuy)} ${STSymbol} approx.`)); - console.log(chalk.yellow(`Due to ${raiseType} price changes and network delays, it is possible that the final amount of purchased tokens is lower.`)); - if (typeof amount !== 'undefined' || !readlineSync.keyInYNStrict(`Do you want the transaction to fail if this happens?`)) { - minTokenToBuy = 0; - } + stableSymbolsAndBalance = await processAddressWithBalance(listOfStableCoins); + stableInfo = stableSymbolsAndBalance.find(o => o.symbol === raiseType); + let stableCoin = common.connect(abis.erc20(), stableInfo.address); + if (parseInt(stableInfo.balance) >= parseInt(costWei)) { + let stableCoin = common.connect(abis.erc20(), stableInfo.address); + let allowance = await stableCoin.methods.allowance(User.address, STOAddress).call(); + if (parseInt(allowance) < parseInt(costWei)) { + let approveAction = stableCoin.methods.approve(STOAddress, costWei); + await common.sendTransaction(approveAction, { from: User }); + console.log(chalk.green(`You have approved ${cost} ${stableInfo.symbol} to be invested in this STO.\n`)); + } + tokensToBuy = await currentSTO.methods.buyWithUSD(User.address, costWei, stableInfo.address).call({ from: User.address}); + } else { + console.log(chalk.red(`Not enough balance to Buy tokens, Require ${cost} ${stableInfo.symbol} but have ${stableInfo.balance} ${stableInfo.symbol}.`)); + console.log(chalk.red(`Please purchase a smaller amount of tokens.`)); + process.exit(); + } - if (raiseType == POLY) { + } else if (raiseType == POLY) { let userBalance = await polyBalance(User.address); - if (parseInt(userBalance) >= parseInt(cost)) { + if (parseFloat(userBalance) >= parseFloat(cost)) { let allowance = await polyToken.methods.allowance(User.address, STOAddress).call(); if (parseInt(allowance) < parseInt(costWei)) { let approveAction = polyToken.methods.approve(STOAddress, costWei); await common.sendTransaction(approveAction, { from: User }); + console.log(chalk.green(`You have approved ${cost} POLY to be invested in this STO.\n`)); } - let actionBuyWithPoly = currentSTO.methods.buyWithPOLYRateLimited(User.address, costWei, minTokenToBuy); - let receipt = await common.sendTransaction(actionBuyWithPoly, { from: User, factor: 2 }); - logTokensPurchasedUSDTieredSTO(receipt); + tokensToBuy = await currentSTO.methods.buyWithPOLY(User.address, costWei).call({ from: User.address}); } else { console.log(chalk.red(`Not enough balance to Buy tokens, Require ${cost} POLY but have ${userBalance} POLY.`)); - console.log(chalk.red(`Please purchase a smaller amount of tokens or access the POLY faucet to get the POLY to complete this txn.`)); + console.log(chalk.red(`Please purchase a smaller amount of tokens.`)); process.exit(); } - } else if ((raiseType != POLY) && (raiseType != ETH)) { - - let listOfStableCoins = await currentSTO.methods.getUsdTokens().call(); - let stableSymbolsAndBalance = await processAddressWithBalance(listOfStableCoins); - let stableInfo = stableSymbolsAndBalance.find(o => o.symbol === raiseType); - if (parseInt(stableInfo.balance) >= parseInt(cost)) { - let stableCoin = common.connect(abis.erc20(), stableInfo.address); - let allowance = await stableCoin.methods.allowance(User.address, STOAddress).call(); - if (parseInt(allowance) < parseInt(costWei)) { - let approveAction = stableCoin.methods.approve(STOAddress, costWei); - await common.sendTransaction(approveAction, { from: User }); - } - let actionBuyWithUSD = currentSTO.methods.buyWithUSDRateLimited(User.address, costWei, minTokenToBuy, stableInfo.address); - let receipt = await common.sendTransaction(actionBuyWithUSD, { from: User, factor: 1.5 }); - logTokensPurchasedUSDTieredSTO(receipt); + } else { + let userBalance = web3.utils.fromWei(await web3.eth.getBalance(User.address)); + if (parseFloat(userBalance) >= parseFloat(cost)) { + tokensToBuy = await currentSTO.methods.buyWithETH(User.address).call({ from: User.address, value: costWei }); } else { - console.log(chalk.red(`Not enough balance to Buy tokens, Require ${cost} ${stableInfo.symbol} but have ${stableInfo.balance} ${stableInfo.symbol}.`)); + console.log(chalk.red(`Not enough balance to Buy tokens, Require ${cost} ETH but have ${userBalance} ETH.`)); console.log(chalk.red(`Please purchase a smaller amount of tokens.`)); process.exit(); } + } + + let minTokenToBuy = tokensToBuy[2]; + console.log(chalk.yellow(`You are going to spend ${web3.utils.fromWei(tokensToBuy[1])} ${raiseType} (${web3.utils.fromWei(tokensToBuy[0])} USD) to buy ${web3.utils.fromWei(minTokenToBuy)} ${STSymbol} approx.`)); + console.log(chalk.yellow(`Due to ${raiseType} price changes and network delays, it is possible that the final amount of purchased tokens is lower.`)); + if (typeof amount !== 'undefined' || !readlineSync.keyInYNStrict(`Do you want the transaction to fail if this happens?`)) { + minTokenToBuy = 0; + } + + if (raiseType == POLY) { + let actionBuyWithPoly = currentSTO.methods.buyWithPOLYRateLimited(User.address, costWei, minTokenToBuy); + let receipt = await common.sendTransaction(actionBuyWithPoly, { from: User, factor: 2 }); + logTokensPurchasedUSDTieredSTO(receipt); + } else if ((raiseType != POLY) && (raiseType != ETH)) { + let actionBuyWithUSD = currentSTO.methods.buyWithUSDRateLimited(User.address, costWei, minTokenToBuy, stableInfo.address); + let receipt = await common.sendTransaction(actionBuyWithUSD, { from: User, factor: 1.5 }); + logTokensPurchasedUSDTieredSTO(receipt); } else { let actionBuyWithETH = currentSTO.methods.buyWithETHRateLimited(User.address, minTokenToBuy); let receipt = await common.sendTransaction(actionBuyWithETH, { from: User, value: costWei }); @@ -651,7 +671,7 @@ async function investUsdTieredSTO(currency, amount) { await showUserInfoForUSDTieredSTO(); } -function selectToken(msg) { +function selectRaiseType(msg) { return readlineSync.keyInSelect(raiseTypes, msg, { cancel: false }); } diff --git a/CLI/commands/permission_manager.js b/CLI/commands/permission_manager.js index 71ee21198..3622018f7 100644 --- a/CLI/commands/permission_manager.js +++ b/CLI/commands/permission_manager.js @@ -4,146 +4,189 @@ var common = require('./common/common_functions'); var gbl = require('./common/global'); var contracts = require('./helpers/contract_addresses'); var abis = require('./helpers/contract_abis'); +const input = require('./IO/input'); // App flow -let tokenSymbol; let securityTokenRegistry; let securityToken; -let generalPermissionManager; -let isNewDelegate = false; +let polyToken; +let currentPermissionManager; async function executeApp() { + console.log('\n', chalk.blue('Permission Manager - Main Menu', '\n')); + + let pmModules = await common.getAllModulesByType(securityToken, gbl.constants.MODULES_TYPES.PERMISSION); + let nonArchivedModules = pmModules.filter(m => !m.archived); + if (nonArchivedModules.length > 0) { + console.log(`Permission Manager modules attached:`); + nonArchivedModules.map(m => `${m.label}: ${m.name} (${m.version}) at ${m.address}`); + } else { + console.log(`There are no Permission Manager modules attached`); + } - common.logAsciiBull(); - console.log("***********************************************"); - console.log("Welcome to the Command-Line Permission Manager."); - console.log("***********************************************"); - console.log("Issuer Account: " + Issuer.address + "\n"); - - await setup(); - - try { - await selectST(); - await addPermissionModule(); - await changePermissionStep(); - } catch (err) { - console.log(err); - return; + let options = []; + if (pmModules.length > 0) { + options.push('Config existing modules'); + } + options.push('Add new Permission Manager module'); + + let index = readlineSync.keyInSelect(options, 'What do you want to do?', { cancel: 'EXIT' }); + let optionSelected = index != -1 ? options[index] : 'EXIT'; + console.log('Selected:', optionSelected, '\n'); + switch (optionSelected) { + case 'Config existing modules': + await configExistingModules(nonArchivedModules); + break; + case 'Add new Permission Manager module': + await addPermissionModule(); + break; + case 'EXIT': + return; } + + await executeApp(); }; -async function setup() { - try { - let securityTokenRegistryAddress = await contracts.securityTokenRegistry(); - let securityTokenRegistryABI = abis.securityTokenRegistry(); - securityTokenRegistry = new web3.eth.Contract(securityTokenRegistryABI, securityTokenRegistryAddress); - securityTokenRegistry.setProvider(web3.currentProvider); - } catch (err) { - console.log(err) - console.log('\x1b[31m%s\x1b[0m', "There was a problem getting the contracts. Make sure they are deployed to the selected network."); - process.exit(0); +async function addPermissionModule() { + let moduleList = await common.getAvailableModules(moduleRegistry, gbl.constants.MODULES_TYPES.PERMISSION, securityToken.options.address); + let options = moduleList.map(m => `${m.name} - ${m.version} (${m.factoryAddress})`); + + let index = readlineSync.keyInSelect(options, 'Which permission manager module do you want to add? ', { cancel: 'Return' }); + if (index != -1 && readlineSync.keyInYNStrict(`Are you sure you want to add ${options[index]}? `)) { + const moduleABI = abis.generalPermissionManager(); + await common.addModule(securityToken, polyToken, moduleList[index].factoryAddress, moduleABI); } } -async function selectST() { - if (!tokenSymbol) - tokenSymbol = readlineSync.question('Enter the token symbol: '); +async function configExistingModules(permissionModules) { + let options = permissionModules.map(m => `${m.label}: ${m.name} (${m.version}) at ${m.address}`); + let index = readlineSync.keyInSelect(options, 'Which module do you want to config? ', { cancel: 'RETURN' }); + console.log('Selected:', index != -1 ? options[index] : 'RETURN', '\n'); + currentPermissionManager = new web3.eth.Contract(abis.generalPermissionManager(), permissionModules[index].address); + currentPermissionManager.setProvider(web3.currentProvider); - let result = await securityTokenRegistry.methods.getSecurityTokenAddress(tokenSymbol).call(); - if (result == "0x0000000000000000000000000000000000000000") { - tokenSymbol = undefined; - console.log(chalk.red(`Token symbol provided is not a registered Security Token.`)); - await selectST(); - } else { - let securityTokenABI = abis.securityToken(); - securityToken = new web3.eth.Contract(securityTokenABI, result); - } + await permissionManager(); } -async function addPermissionModule() { - let generalPermissionManagerAddress; - let result = await securityToken.methods.getModulesByName(web3.utils.toHex('GeneralPermissionManager')).call(); - if (result.length == 0) { - console.log(chalk.red(`General Permission Manager is not attached.`)); - if (readlineSync.keyInYNStrict('Do you want to add General Permission Manager Module to your Security Token?')) { - let permissionManagerFactoryAddress = await contracts.getModuleFactoryAddressByName(securityToken.options.address, gbl.constants.MODULES_TYPES.PERMISSION, 'GeneralPermissionManager'); - let addModuleAction = securityToken.methods.addModule(permissionManagerFactoryAddress, web3.utils.fromAscii('', 16), 0, 0); - let receipt = await common.sendTransaction(addModuleAction); - let event = common.getEventFromLogs(securityToken._jsonInterface, receipt.logs, 'ModuleAdded'); - console.log(`Module deployed at address: ${event._module}`); - generalPermissionManagerAddress = event._module; - } else { - process.exit(0); - } - } else { - generalPermissionManagerAddress = result[0]; +async function permissionManager() { + console.log(chalk.blue('\n', 3`Permission module at ${currentPermissionManager.options.address}`), '\n'); + + let delegates = await currentPermissionManager.methods.getAllDelegates().call(); + + console.log(`- Delegates: ${delegates.length}`); + + let options = ['Manage delegates']; + if (parseInt(delegates) > 0) { + options.push('Explore account', 'Change permission'); + } + + let index = readlineSync.keyInSelect(options, 'What do you want to do?', { cancel: 'RETURN' }); + let selected = index != -1 ? options[index] : 'RETURN'; + console.log('Selected:', selected, '\n'); + switch (selected) { + case 'Manage delegates': + await manageDelegates(delegates); + break; + case 'Explore account': + await exploreAccount(); + break; + case 'Change permission': + await changePermission(); + break + case 'RETURN': + return; } - let generalPermissionManagerABI = abis.generalPermissionManager(); - generalPermissionManager = new web3.eth.Contract(generalPermissionManagerABI, generalPermissionManagerAddress); - generalPermissionManager.setProvider(web3.currentProvider); + await permissionManager(); } -async function changePermissionStep() { - console.log('\n\x1b[34m%s\x1b[0m', "Permission Manager - Change Permission"); - let selectedDelegate = await selectDelegate(); - if (isNewDelegate) { - isNewDelegate = false; - changePermissionAction(selectedDelegate); - } else { - let selectFlow = readlineSync.keyInSelect(['Remove', 'Change permission'], 'Select an option:', { cancel: false }); - if (selectFlow == 0) { - await deleteDelegate(selectedDelegate); - console.log("Delegate successfully deleted.") - } else { - changePermissionAction(selectedDelegate); - } +async function manageDelegates(currentDelegates) { + let options = ['Add delegate']; + if (currentDelegates.length > 0) { + options.push('Delete delegate'); + } + + let index = readlineSync.keyInSelect(options, 'What do you want to do?', { cancel: 'RETURN' }); + let selected = index == -1 ? 'RETURN' : options[index]; + console.log('Selected:', selected); + switch (selected) { + case 'Add delegate': + await addNewDelegate(); + break; + case 'Delete delegate': + let delegateToDelete = await selectDelegate(); + if (readlineSync.keyInYNStrict(`Are you sure you want to delete delegate ${delegateToDelete}?`)) { + await deleteDelegate(delegateToDelete); + } + break; } } -async function changePermissionAction(selectedDelegate) { - let selectedModule = await selectModule(); - let selectedPermission = await selectPermission(selectedModule.permissions); - let isValid = isPermissionValid(); - await changePermission(selectedDelegate, selectedModule.address, selectedPermission, isValid); +async function addNewDelegate() { + let newDelegate = input.readAddress('Enter the delegate address: '); + let details = input.readStringNonEmpty('Enter the delegate details (i.e `Belongs to financial firm`): '); + + let addPermissionAction = currentPermissionManager.methods.addDelegate(newDelegate, web3.utils.asciiToHex(details)); + let receipt = await common.sendTransaction(addPermissionAction); + let event = common.getEventFromLogs(currentPermissionManager._jsonInterface, receipt.logs, 'AddDelegate'); + console.log(`Delegate added successfully: ${event._delegate} - ${web3.utils.hexToAscii(event._details)}`); } async function deleteDelegate(address) { - let deleteDelegateAction = generalPermissionManager.methods.deleteDelegate(address); + let deleteDelegateAction = currentPermissionManager.methods.deleteDelegate(address); await common.sendTransaction(deleteDelegateAction, { factor: 2 }); + console.log(`Delegate ${address} deleted successfully!`); +} + +async function exploreAccount() { + let accountToExplore = input.readAddress('Enter the account address you want to explore: '); + + let isDelegate = await currentPermissionManager.methods.checkDelegate(accountToExplore).call(); + if (!isDelegate) { + console.log(chalk.yellow(`${accountToExplore} is not a delegate.\n`)); + } else { + let delegate = {}; + delegate.address = accountToExplore; + delegate.details = web3.utils.hexToUtf8(await currentPermissionManager.methods.delegateDetails(accountToExplore).call()); + delegate.permissions = renderTable(await getPermissionsByDelegate(accountToExplore)); + + console.log(`Account: ${delegate.address} + Details: ${delegate.details} + Permisions: ${delegate.permissions}`); + } +} + +async function changePermission() { + let selectedDelegate = await selectDelegate(); + let selectedModule = await selectModule(); + let selectedPermission = await selectPermission(selectedModule.permissions); + let isValid = await isPermissionValid(); + let changePermissionAction = currentPermissionManager.methods.changePermission(selectedDelegate, selectedModule.address, web3.utils.asciiToHex(selectedPermission), isValid); + let receipt = await common.sendTransaction(changePermissionAction, { factor: 2 }); + common.getEventFromLogs(currentPermissionManager._jsonInterface, receipt.logs, 'ChangePermission'); + console.log(`Permission changed successfully!`); } // Helper functions async function selectDelegate() { - let result; - let delegates = await getDelegates(); - let permissions = await getDelegatesAndPermissions(); - - let options = ['Add new delegate']; + let delegatesAndPermissions = await getDelegatesAndPermissions(); - options = options.concat(delegates.map(function (d) { - let perm = renderTable(permissions, d.address); + options = delegatesAndPermissions.map(function (d) { + let perm = renderTable(d.permissions); return `Account: ${d.address} Details: ${d.details} Permisions: ${perm}` - })); + }); let index = readlineSync.keyInSelect(options, 'Select a delegate:', { cancel: false }); - if (index == 0) { - let newDelegate = await addNewDelegate(); - result = newDelegate; - } else { - result = delegates[index - 1].address; - } - - return result; + return delegatesAndPermissions[index].address; } async function selectModule() { let modules = await getModulesWithPermissions(); let options = modules.map(function (m) { - return m.name; + return `${m.label} - ${m.name} at ${m.address}`; }); let index = readlineSync.keyInSelect(options, 'Select a module:', { cancel: false }); return modules[index]; @@ -157,62 +200,24 @@ async function selectPermission(permissions) { return permissions[index]; } -function isPermissionValid() { +async function isPermissionValid() { let options = ['Grant permission', 'Revoke permission']; let index = readlineSync.keyInSelect(options, 'What do you want to do?', { cancel: false }); return index == 0; } -async function changePermission(delegate, moduleAddress, permission, isValid) { - let changePermissionAction = generalPermissionManager.methods.changePermission(delegate, moduleAddress, web3.utils.asciiToHex(permission), isValid); - let receipt = await common.sendTransaction(changePermissionAction, { factor: 2 }); - common.getEventFromLogs(generalPermissionManager._jsonInterface, receipt.logs, 'ChangePermission'); - console.log(`Permission changed successfully!`); -} - async function getDelegates() { let result = []; - /* - let events = await generalPermissionManager.getPastEvents('LogAddPermission', { fromBlock: 0}); - for (let event of events) { - let delegate = {}; - delegate.address = event.returnValues._delegate; - delegate.details = web3.utils.hexToAscii(event.returnValues._details); - result.push(delegate); - } - */ - let delegates = await generalPermissionManager.methods.getAllDelegates().call(); + let delegates = await currentPermissionManager.methods.getAllDelegates().call(); for (let d of delegates) { let delegate = {}; delegate.address = d; - delegate.details = web3.utils.hexToAscii(await generalPermissionManager.methods.delegateDetails(d).call()); + delegate.details = web3.utils.hexToAscii(await currentPermissionManager.methods.delegateDetails(d).call()); result.push(delegate); } return result; } -async function addNewDelegate() { - let newDelegate = readlineSync.question('Enter the delegate address: ', { - limit: function (input) { - return web3.utils.isAddress(input); - }, - limitMessage: "Must be a valid address" - }); - let details = readlineSync.question('Enter the delegate details (i.e `Belongs to financial firm`): ', { - limit: function (input) { - return input.length > 0; - }, - limitMessage: "Must be a valid string" - }); - - let addPermissionAction = generalPermissionManager.methods.addDelegate(newDelegate, web3.utils.asciiToHex(details)); - let receipt = await common.sendTransaction(addPermissionAction); - let event = common.getEventFromLogs(generalPermissionManager._jsonInterface, receipt.logs, 'AddDelegate'); - console.log(`Delegate added successfully: ${event._delegate} - ${web3.utils.hexToAscii(event._details)}`); - isNewDelegate = true; - return event._delegate; -} - async function getModulesWithPermissions() { let modules = []; let moduleABI = abis.moduleInterface(); @@ -223,8 +228,10 @@ async function getModulesWithPermissions() { let contractTemp = new web3.eth.Contract(moduleABI, m); let permissions = await contractTemp.methods.getPermissions().call(); if (permissions.length > 0) { + let moduleData = await securityToken.methods.getModule(m).call(); modules.push({ - name: web3.utils.hexToAscii((await securityToken.methods.getModule(m).call())[0]), + label: web3.utils.hexToAscii(moduleData.moduleLabel), + name: web3.utils.hexToAscii(moduleData.moduleName), address: m, permissions: permissions.map(function (p) { return web3.utils.hexToAscii(p) }) }) @@ -236,42 +243,40 @@ async function getModulesWithPermissions() { } async function getDelegatesAndPermissions() { - let moduleABI = abis.moduleInterface(); + let allDelegates = await getDelegates(); + for (const d of allDelegates) { + d.permissions = await getPermissionsByDelegate(d.address); + } + return allDelegates +} + +async function getPermissionsByDelegate(d) { let result = []; - for (const type in gbl.constants.MODULES_TYPES) { - let modulesAttached = await securityToken.methods.getModulesByType(gbl.constants.MODULES_TYPES[type]).call(); - for (const module of modulesAttached) { - let contractTemp = new web3.eth.Contract(moduleABI, module); - let permissions = await contractTemp.methods.getPermissions().call(); - if (permissions.length > 0) { - for (const permission of permissions) { - let allDelegates = await generalPermissionManager.methods.getAllDelegatesWithPerm(module, permission).call(); - let moduleName = web3.utils.hexToUtf8((await securityToken.methods.getModule(module).call())[0]); - let permissionName = web3.utils.hexToUtf8(permission); - for (delegateAddr of allDelegates) { - if (result[delegateAddr] == undefined) { - result[delegateAddr] = [] - } - if (result[delegateAddr][moduleName + '-' + module] == undefined) { - result[delegateAddr][moduleName + '-' + module] = [{ permission: permissionName }] - } else { - result[delegateAddr][moduleName + '-' + module].push({ permission: permissionName }) - } - } - } - } + let perms = await currentPermissionManager.methods.getAllModulesAndPermsFromTypes(d, Object.values(gbl.constants.MODULES_TYPES)).call(); + for (let i = 0; i < perms[0].length; i++) { + const moduleAddress = perms[0][i]; + const moduleData = await securityToken.methods.getModule(moduleAddress).call(); + const moduleName = web3.utils.hexToUtf8(moduleData.moduleName); + const moduleLabel = web3.utils.hexToUtf8(moduleData.moduleLabel); + const permissionName = web3.utils.hexToUtf8(perms[1][i]); + const moduleKey = moduleLabel + ' - (' + moduleName + ' at ' + moduleAddress + ')'; + if (result[moduleKey] == undefined) { + result[moduleKey] = [{ permission: permissionName }]; + } else { + result[moduleKey].push({ permission: permissionName }); } } - return result + + return result; } -function renderTable(permissions, address) { +function renderTable(permissions) { let result = ``; - if (permissions[address] != undefined) { - Object.keys(permissions[address]).forEach((module) => { + if (typeof permissions !== undefined) { + Object.keys(permissions).forEach((m) => { result += ` - ${module.split('-')[0]} (${module.split('-')[1]}) -> `; - (permissions[address][module]).forEach((perm) => { + ${m} -> `; + (permissions[m]).forEach((perm) => { result += `${perm.permission}, `; }) result = result.slice(0, -2); @@ -282,8 +287,49 @@ function renderTable(permissions, address) { return result } +async function initialize(_tokenSymbol) { + welcome(); + await setup(); + securityToken = await common.selectToken(securityTokenRegistry, _tokenSymbol); + if (securityToken === null) { + process.exit(0); + } +} + +function welcome() { + common.logAsciiBull(); + console.log("**********************************************"); + console.log("Welcome to the Command-Line Permission Manager"); + console.log("**********************************************"); + console.log("Issuer Account: " + Issuer.address + "\n"); +} + +async function setup() { + try { + let securityTokenRegistryAddress = await contracts.securityTokenRegistry(); + let iSecurityTokenRegistryABI = abis.iSecurityTokenRegistry(); + securityTokenRegistry = new web3.eth.Contract(iSecurityTokenRegistryABI, securityTokenRegistryAddress); + securityTokenRegistry.setProvider(web3.currentProvider); + + let polyTokenAddress = await contracts.polyToken(); + let polyTokenABI = abis.polyToken(); + polyToken = new web3.eth.Contract(polyTokenABI, polyTokenAddress); + polyToken.setProvider(web3.currentProvider); + + let moduleRegistryAddress = await contracts.moduleRegistry(); + let moduleRegistryABI = abis.moduleRegistry(); + moduleRegistry = new web3.eth.Contract(moduleRegistryABI, moduleRegistryAddress); + moduleRegistry.setProvider(web3.currentProvider); + } catch (err) { + console.log(err) + console.log('\x1b[31m%s\x1b[0m', "There was a problem getting the contracts. Make sure they are deployed to the selected network."); + process.exit(0); + } +} + module.exports = { - executeApp: async function () { + executeApp: async function (_tokenSymbol) { + await initialize(_tokenSymbol); return executeApp(); } -} \ No newline at end of file +} diff --git a/CLI/commands/sto_manager.js b/CLI/commands/sto_manager.js index 756787e2d..ec51d7c13 100644 --- a/CLI/commands/sto_manager.js +++ b/CLI/commands/sto_manager.js @@ -5,36 +5,34 @@ const abis = require('./helpers/contract_abis'); const common = require('./common/common_functions'); const gbl = require('./common/global'); const csvParse = require('./helpers/csv'); +const input = require('./IO/input'); const { table } = require('table'); const STABLE = 'STABLE'; -/////////////////// // Constants const ACCREDIT_DATA_CSV = `${__dirname}/../data/STO/USDTieredSTO/accredited_data.csv`; const NON_ACCREDIT_LIMIT_DATA_CSV = `${__dirname}/../data/STO/USDTieredSTO/nonAccreditedLimits_data.csv`; -/////////////////// // Crowdsale params let tokenSymbol; -//////////////////////// // Artifacts let securityTokenRegistry; let moduleRegistry; let polyToken; let securityToken; -async function executeApp() { +async function executeApp () { let exit = false; while (!exit) { console.log('\n', chalk.blue('STO Manager - Main Menu'), '\n'); // Show non-archived attached STO modules - let stoModules = await getAllModulesByType(gbl.constants.MODULES_TYPES.STO); + let stoModules = await common.getAllModulesByType(securityToken, gbl.constants.MODULES_TYPES.STO); let nonArchivedModules = stoModules.filter(m => !m.archived); if (nonArchivedModules.length > 0) { console.log(`STO modules attached:`); - nonArchivedModules.map(m => console.log(`- ${m.name} at ${m.address}`)) + nonArchivedModules.map(m => `${m.label}: ${m.name} (${m.version}) at ${m.address}`); } else { console.log(`There are no STO modules attached`); } @@ -46,7 +44,7 @@ async function executeApp() { options.push('Add new STO module'); let index = readlineSync.keyInSelect(options, 'What do you want to do?', { cancel: 'EXIT' }); - let optionSelected = index != -1 ? options[index] : 'EXIT'; + let optionSelected = index !== -1 ? options[index] : 'EXIT'; console.log('Selected:', optionSelected, '\n'); switch (optionSelected) { case 'Show existing STO information': @@ -67,12 +65,12 @@ async function executeApp() { } }; -function selectExistingSTO(stoModules, showPaused) { +function selectExistingSTO (stoModules, showPaused) { let filteredModules = stoModules; if (!showPaused) { filteredModules = stoModules.filter(m => !m.paused); } - let options = filteredModules.map(m => `${m.name} (${m.version}) at ${m.address}`); + let options = filteredModules.map(m => `${m.label}: ${m.name} (${m.version}) at ${m.address}`); let index = readlineSync.keyInSelect(options, 'Select a module: ', { cancel: false }); console.log('Selected:', options[index], '\n'); let selectedName = filteredModules[index].name; @@ -90,7 +88,7 @@ function selectExistingSTO(stoModules, showPaused) { return { name: selectedName, module: stoModule }; } -async function showSTO(selectedSTO, currentSTO) { +async function showSTO (selectedSTO, currentSTO) { switch (selectedSTO) { case 'CappedSTO': await cappedSTO_status(currentSTO); @@ -102,7 +100,7 @@ async function showSTO(selectedSTO, currentSTO) { } } -async function modifySTO(selectedSTO, currentSTO) { +async function modifySTO (selectedSTO, currentSTO) { switch (selectedSTO) { case 'CappedSTO': console.log(chalk.red(` @@ -116,24 +114,17 @@ async function modifySTO(selectedSTO, currentSTO) { } } -async function addSTOModule(stoConfig) { +async function addSTOModule (stoConfig) { console.log(chalk.blue('Launch STO - Configuration')); let factorySelected; let optionSelected; if (typeof stoConfig === 'undefined') { - let availableModules = await moduleRegistry.methods.getModulesByTypeAndToken(gbl.constants.MODULES_TYPES.STO, securityToken.options.address).call(); - moduleList = await Promise.all(availableModules.map(async function (m) { - let moduleFactoryABI = abis.moduleFactory(); - let moduleFactory = new web3.eth.Contract(moduleFactoryABI, m); - let moduleName = web3.utils.hexToUtf8(await moduleFactory.methods.name().call()); - let moduleVersion = await moduleFactory.methods.version().call(); - return { name: moduleName, version: moduleVersion, factoryAddress: m }; - })); + let moduleList = await common.getAvailableModules(moduleRegistry, gbl.constants.MODULES_TYPES.STO, securityToken.options.address); let options = moduleList.map(m => `${m.name} - ${m.version} (${m.factoryAddress})`); let index = readlineSync.keyInSelect(options, 'What type of STO do you want?', { cancel: 'RETURN' }); - optionSelected = index != -1 ? moduleList[index].name : 'RETURN'; + optionSelected = index !== -1 ? moduleList[index].name : 'RETURN'; factorySelected = moduleList[index].factoryAddress; } else { optionSelected = stoConfig.type; @@ -142,99 +133,78 @@ async function addSTOModule(stoConfig) { console.log('Selected:', optionSelected, '\n'); switch (optionSelected) { case 'CappedSTO': - let cappedSTO = await cappedSTO_launch(stoConfig, factorySelected); + let cappedSTO = await cappedSTO_launch(factorySelected, stoConfig); await cappedSTO_status(cappedSTO); break; case 'USDTieredSTO': - let usdTieredSTO = await usdTieredSTO_launch(stoConfig, factorySelected); + let usdTieredSTO = await usdTieredSTO_launch(factorySelected, stoConfig); await usdTieredSTO_status(usdTieredSTO); break; } } -//////////////// // Capped STO // -//////////////// -async function cappedSTO_launch(stoConfig, factoryAddress) { +async function cappedSTO_launch (factoryAddress, stoConfig) { console.log(chalk.blue('Launch STO - Capped STO in No. of Tokens')); - let cappedSTOFactoryABI = abis.cappedSTOFactory(); - let cappedSTOFactory = new web3.eth.Contract(cappedSTOFactoryABI, factoryAddress); - cappedSTOFactory.setProvider(web3.currentProvider); - let stoFee = new web3.utils.BN(await cappedSTOFactory.methods.getSetupCost().call()); - - let contractBalance = new web3.utils.BN(await polyToken.methods.balanceOf(securityToken._address).call()); - if (contractBalance.lt(stoFee)) { - let transferAmount = stoFee.sub(contractBalance); - let ownerBalance = new web3.utils.BN(await polyToken.methods.balanceOf(Issuer.address).call()); - if (ownerBalance.lt(transferAmount)) { - console.log(chalk.red(`\n**************************************************************************************************************************************************`)); - console.log(chalk.red(`Not enough balance to pay the CappedSTO fee, Requires ${web3.utils.fromWei(transferAmount)} POLY but have ${web3.utils.fromWei(ownerBalance)} POLY. Access POLY faucet to get the POLY to complete this txn`)); - console.log(chalk.red(`**************************************************************************************************************************************************\n`)); - return; - } else { - let transferAction = polyToken.methods.transfer(securityToken._address, transferAmount); - let receipt = await common.sendTransaction(transferAction, { factor: 2 }); - let event = common.getEventFromLogs(polyToken._jsonInterface, receipt.logs, 'Transfer'); - console.log(`Number of POLY sent: ${web3.utils.fromWei(new web3.utils.BN(event._value))}`) - } - } + const cappedSTOABI = abis.cappedSTO(); + const moduleAddress = await common.addModule(securityToken, polyToken, factoryAddress, cappedSTOABI, getCappedSTOIntializeData, stoConfig); + let cappedSTO = new web3.eth.Contract(cappedSTOABI, moduleAddress); + cappedSTO.setProvider(web3.currentProvider); + return cappedSTO; +} + +function getCappedSTOIntializeData (moduleABI, stoConfig) { let oneMinuteFromNow = new web3.utils.BN((Math.floor(Date.now() / 1000) + 60)); let oneMonthFromNow = new web3.utils.BN((Math.floor(Date.now() / 1000) + (30 * 24 * 60 * 60))); - let cappedSTOconfig = {}; let useConfigFile = typeof stoConfig !== 'undefined'; if (!useConfigFile) { cappedSTOconfig.cap = readlineSync.question('How many tokens do you plan to sell on the STO? (500.000): '); - if (cappedSTOconfig.cap == "") cappedSTOconfig.cap = 500000; - + if (cappedSTOconfig.cap === "") { + cappedSTOconfig.cap = 500000; + } cappedSTOconfig.raiseType = readlineSync.question('Enter' + chalk.green(` P `) + 'for POLY raise or leave empty for Ether raise (E): '); - if (cappedSTOconfig.raiseType.toUpperCase() == 'P') { + if (cappedSTOconfig.raiseType.toUpperCase() === 'P') { cappedSTOconfig.raiseType = [gbl.constants.FUND_RAISE_TYPES.POLY]; } else { cappedSTOconfig.raiseType = [gbl.constants.FUND_RAISE_TYPES.ETH]; } - - cappedSTOconfig.rate = readlineSync.question(`Enter the rate (1 ${cappedSTOconfig.raiseType == gbl.constants.FUND_RAISE_TYPES.POLY ? 'POLY' : 'ETH'} = X ${tokenSymbol}) for the STO (1000): `); - if (cappedSTOconfig.rate == "") cappedSTOconfig.rate = 1000; - + cappedSTOconfig.rate = readlineSync.question(`Enter the rate (1 ${cappedSTOconfig.raiseType === gbl.constants.FUND_RAISE_TYPES.POLY ? 'POLY' : 'ETH'} = X ${tokenSymbol}) for the STO (1000): `); + if (cappedSTOconfig.rate === "") { + cappedSTOconfig.rate = 1000; + } cappedSTOconfig.wallet = readlineSync.question('Enter the address that will receive the funds from the STO (' + Issuer.address + '): '); - if (cappedSTOconfig.wallet == "") cappedSTOconfig.wallet = Issuer.address; - + if (cappedSTOconfig.wallet === "") { + cappedSTOconfig.wallet = Issuer.address; + } cappedSTOconfig.startTime = readlineSync.question('Enter the start time for the STO (Unix Epoch time)\n(1 minutes from now = ' + oneMinuteFromNow + ' ): '); - cappedSTOconfig.endTime = readlineSync.question('Enter the end time for the STO (Unix Epoch time)\n(1 month from now = ' + oneMonthFromNow + ' ): '); } else { cappedSTOconfig = stoConfig; } - - if (cappedSTOconfig.startTime == "") cappedSTOconfig.startTime = oneMinuteFromNow; - if (cappedSTOconfig.endTime == "") cappedSTOconfig.endTime = oneMonthFromNow; - - let cappedSTOABI = abis.cappedSTO(); - let configureFunction = cappedSTOABI.find(o => o.name === 'configure' && o.type === 'function'); - let bytesSTO = web3.eth.abi.encodeFunctionCall(configureFunction, - [cappedSTOconfig.startTime, + if (cappedSTOconfig.startTime === "") { + cappedSTOconfig.startTime = oneMinuteFromNow; + } + if (cappedSTOconfig.endTime === "") { + cappedSTOconfig.endTime = oneMonthFromNow; + } + let configureFunction = moduleABI.find(o => o.name === 'configure' && o.type === 'function'); + let bytes = web3.eth.abi.encodeFunctionCall(configureFunction, + [ + cappedSTOconfig.startTime, cappedSTOconfig.endTime, web3.utils.toWei(cappedSTOconfig.cap.toString()), web3.utils.toWei(cappedSTOconfig.rate.toString()), cappedSTOconfig.raiseType, - cappedSTOconfig.wallet] - ); + cappedSTOconfig.wallet + ]); - let addModuleAction = securityToken.methods.addModule(cappedSTOFactory.options.address, bytesSTO, stoFee, 0); - let receipt = await common.sendTransaction(addModuleAction); - let event = common.getEventFromLogs(securityToken._jsonInterface, receipt.logs, 'ModuleAdded'); - console.log(`STO deployed at address: ${event._module}`); - - let cappedSTO = new web3.eth.Contract(cappedSTOABI, event._module); - cappedSTO.setProvider(web3.currentProvider); - - return cappedSTO; + return bytes; } -async function cappedSTO_status(currentSTO) { +async function cappedSTO_status (currentSTO) { let displayStartTime = await currentSTO.methods.startTime().call(); let displayEndTime = await currentSTO.methods.endTime().call(); let displayRate = new web3.utils.BN(await currentSTO.methods.rate().call()); @@ -280,10 +250,8 @@ async function cappedSTO_status(currentSTO) { `); } -//////////////////// // USD Tiered STO // -//////////////////// -function fundingConfigUSDTieredSTO() { +function fundingConfigUSDTieredSTO () { let funding = {}; let selectedFunding = readlineSync.question('Enter' + chalk.green(` P `) + 'for POLY raise,' + chalk.green(` S `) + 'for Stable Coin raise,' + chalk.green(` E `) + 'for Ether raise or any combination of them (i.e.' + chalk.green(` PSE `) + 'for all): ').toUpperCase(); @@ -298,51 +266,28 @@ function fundingConfigUSDTieredSTO() { if (selectedFunding.includes('S')) { funding.raiseType.push(gbl.constants.FUND_RAISE_TYPES.STABLE); } - if (funding.raiseType.length == 0) { + if (funding.raiseType.length === 0) { funding.raiseType = [gbl.constants.FUND_RAISE_TYPES.ETH, gbl.constants.FUND_RAISE_TYPES.POLY, gbl.constants.FUND_RAISE_TYPES.STABLE]; } return funding; } -async function addressesConfigUSDTieredSTO(usdTokenRaise) { - +async function addressesConfigUSDTieredSTO (usdTokenRaise) { let addresses, menu; - do { - addresses = {}; - addresses.wallet = readlineSync.question('Enter the address that will receive the funds from the STO (' + Issuer.address + '): ', { - limit: function (input) { - return web3.utils.isAddress(input); - }, - limitMessage: "Must be a valid address", - defaultInput: Issuer.address - }); - if (addresses.wallet == "") addresses.wallet = Issuer.address; - - addresses.reserveWallet = readlineSync.question('Enter the address that will receive remaining tokens in the case the cap is not met (' + Issuer.address + '): ', { - limit: function (input) { - return web3.utils.isAddress(input); - }, - limitMessage: "Must be a valid address", - defaultInput: Issuer.address - }); - if (addresses.reserveWallet == "") addresses.reserveWallet = Issuer.address; + addresses.wallet = input.readAddress('Enter the address that will receive the funds from the STO (' + Issuer.address + '): ', Issuer.address); + if (addresses.wallet === "") addresses.wallet = Issuer.address; + + addresses.treasuryWallet = input.readAddress('Enter the address that will receive remaining tokens in the case the cap is not met (' + Issuer.address + '): ', Issuer.address); + if (addresses.treasuryWallet === "") addresses.treasuryWallet = Issuer.address; let listOfAddress; if (usdTokenRaise) { - addresses.usdToken = readlineSync.question('Enter the address (or multiple addresses separated by commas) of the USD stable coin(s): ', { - limit: function (input) { - listOfAddress = input.split(','); - return listOfAddress.every((addr) => { - return web3.utils.isAddress(addr) - }) - }, - limitMessage: "Must be a valid address", - }); + addresses.usdToken = input.readMultipleAddresses('Enter the address (or multiple addresses separated by commas) of the USD stable coin(s): '); } else { listOfAddress = [] addresses.usdToken = []; @@ -358,13 +303,12 @@ async function addressesConfigUSDTieredSTO(usdTokenRaise) { if (typeof addresses.usdToken === 'string') { addresses.usdToken = addresses.usdToken.split(",") } - } while (menu); return addresses; } -async function checkSymbol(address) { +async function checkSymbol (address) { let stableCoin = common.connect(abis.erc20(), address); try { return await stableCoin.methods.symbol().call(); @@ -373,11 +317,11 @@ async function checkSymbol(address) { } } -async function processArray(array) { +async function processArray (array) { let result = true; for (const address of array) { let symbol = await checkSymbol(address); - if (symbol == "") { + if (symbol === "") { result = false; console.log(`${address} seems not to be a stable coin`) } @@ -385,7 +329,7 @@ async function processArray(array) { return result } -async function processAddress(array) { +async function processAddress (array) { let list = []; for (const address of array) { let symbol = await checkSymbol(address); @@ -394,17 +338,11 @@ async function processAddress(array) { return list } -function tiersConfigUSDTieredSTO(polyRaise) { +function tiersConfigUSDTieredSTO (polyRaise) { let tiers = {}; let defaultTiers = 3; - tiers.tiers = parseInt(readlineSync.question(`Enter the number of tiers for the STO? (${defaultTiers}): `, { - limit: function (input) { - return parseInt(input) > 0; - }, - limitMessage: 'Must be greater than zero', - defaultInput: defaultTiers - })); + tiers.tiers = parseInt(input.readNumberGreaterThan(0, `Enter the number of tiers for the STO? (${defaultTiers}): `, defaultTiers)); let defaultTokensPerTier = [190000000, 100000000, 200000000]; let defaultRatePerTier = ['0.05', '0.10', '0.15']; @@ -415,38 +353,13 @@ function tiersConfigUSDTieredSTO(polyRaise) { tiers.tokensPerTierDiscountPoly = []; tiers.ratePerTierDiscountPoly = []; for (let i = 0; i < tiers.tiers; i++) { - tiers.tokensPerTier[i] = readlineSync.question(`How many tokens do you plan to sell on tier No. ${i + 1}? (${defaultTokensPerTier[i]}): `, { - limit: function (input) { - return parseFloat(input) > 0; - }, - limitMessage: 'Must be greater than zero', - defaultInput: defaultTokensPerTier[i] - }); - - tiers.ratePerTier[i] = readlineSync.question(`What is the USD per token rate for tier No. ${i + 1}? (${defaultRatePerTier[i]}): `, { - limit: function (input) { - return parseFloat(input) > 0; - }, - limitMessage: 'Must be greater than zero', - defaultInput: defaultRatePerTier[i] - }); + tiers.tokensPerTier[i] = input.readNumberGreaterThan(0, `How many tokens do you plan to sell on tier No. ${i + 1}? (${defaultTokensPerTier[i]}): `, defaultTokensPerTier[i]); + + tiers.ratePerTier[i] = input.readNumberGreaterThan(0, `What is the USD per token rate for tier No. ${i + 1}? (${defaultRatePerTier[i]}): `, defaultRatePerTier[i]); if (polyRaise && readlineSync.keyInYNStrict(`Do you plan to have a discounted rate for POLY investments for tier No. ${i + 1}? `)) { - tiers.tokensPerTierDiscountPoly[i] = readlineSync.question(`How many of those tokens do you plan to sell at discounted rate on tier No. ${i + 1}? (${defaultTokensPerTierDiscountPoly[i]}): `, { - limit: function (input) { - return parseFloat(input) < parseFloat(tiers.tokensPerTier[i]); - }, - limitMessage: 'Must be less than the No. of tokens of the tier', - defaultInput: defaultTokensPerTierDiscountPoly[i] - }); - - tiers.ratePerTierDiscountPoly[i] = readlineSync.question(`What is the discounted rate for tier No. ${i + 1}? (${defaultRatePerTierDiscountPoly[i]}): `, { - limit: function (input) { - return parseFloat(input) < parseFloat(tiers.ratePerTier[i]); - }, - limitMessage: 'Must be less than the rate of the tier', - defaultInput: defaultRatePerTierDiscountPoly[i] - }); + tiers.tokensPerTierDiscountPoly[i] = input.readNumberLessThan(parseFloat(tiers.tokensPerTier[i]), `How many of those tokens do you plan to sell at discounted rate on tier No. ${i + 1}? (${defaultTokensPerTierDiscountPoly[i]}): `, defaultTokensPerTierDiscountPoly[i]); + tiers.ratePerTierDiscountPoly[i] = input.readNumberLessThan(parseFloat(tiers.ratePerTier[i]), `What is the discounted rate for tier No. ${i + 1}? (${defaultRatePerTierDiscountPoly[i]}): `, defaultRatePerTierDiscountPoly[i]); } else { tiers.tokensPerTierDiscountPoly[i] = 0; tiers.ratePerTierDiscountPoly[i] = 0; @@ -456,125 +369,78 @@ function tiersConfigUSDTieredSTO(polyRaise) { return tiers; } -function limitsConfigUSDTieredSTO() { +function limitsConfigUSDTieredSTO () { let limits = {}; let defaultMinimumInvestment = 5; - limits.minimumInvestmentUSD = readlineSync.question(`What is the minimum investment in USD? (${defaultMinimumInvestment}): `, { - limit: function (input) { - return parseFloat(input) > 0; - }, - limitMessage: "Must be greater than zero", - defaultInput: defaultMinimumInvestment - }); + limits.minimumInvestmentUSD = input.readNumberGreaterThan(0, `What is the minimum investment in USD? (${defaultMinimumInvestment}): `, defaultMinimumInvestment); let nonAccreditedLimit = 2500; - limits.nonAccreditedLimitUSD = readlineSync.question(`What is the default limit for non accredited investors in USD? (${nonAccreditedLimit}): `, { - limit: function (input) { - return parseFloat(input) >= parseFloat(limits.minimumInvestmentUSD); - }, - limitMessage: "Must be greater than minimum investment", - defaultInput: nonAccreditedLimit - }); + limits.nonAccreditedLimitUSD = input.readNumberGreaterThanOrEqual(parseFloat(limits.minimumInvestmentUSD), `What is the default limit for non accredited investors in USD? (${nonAccreditedLimit}): `, nonAccreditedLimit); return limits; } -function timesConfigUSDTieredSTO(stoConfig) { +function timesConfigUSDTieredSTO (stoConfig) { let times = {}; let oneMinuteFromNow = Math.floor(Date.now() / 1000) + 60; if (typeof stoConfig === 'undefined') { - times.startTime = parseInt(readlineSync.question('Enter the start time for the STO (Unix Epoch time)\n(1 minutes from now = ' + oneMinuteFromNow + ' ): ', { - limit: function (input) { - return parseInt(input) > Math.floor(Date.now() / 1000); - }, - limitMessage: "Must be a future time", - defaultInput: oneMinuteFromNow - })); + times.startTime = parseInt(input.readNumberGreaterThan(Math.floor(Date.now() / 1000), 'Enter the start time for the STO (Unix Epoch time)\n(1 minutes from now = ' + oneMinuteFromNow + ' ): ', oneMinuteFromNow)); } else { times.startTime = stoConfig.times.startTime; } - if (times.startTime == "") times.startTime = oneMinuteFromNow; + if (times.startTime === "") times.startTime = oneMinuteFromNow; let oneMonthFromNow = Math.floor(Date.now() / 1000) + (30 * 24 * 60 * 60); if (typeof stoConfig === 'undefined') { - times.endTime = parseInt(readlineSync.question('Enter the end time for the STO (Unix Epoch time)\n(1 month from now = ' + oneMonthFromNow + ' ): ', { - limit: function (input) { - return parseInt(input) > times.startTime; - }, - limitMessage: "Must be greater than Start Time", - defaultInput: oneMonthFromNow - })); + times.endTime = parseInt(input.readNumberGreaterThan(times.startTime, 'Enter the end time for the STO (Unix Epoch time)\n(1 month from now = ' + oneMonthFromNow + ' ): ', oneMonthFromNow)); } else { times.endTime = stoConfig.times.startTime; } - if (times.endTime == "") times.endTime = oneMonthFromNow; + if (times.endTime === "") times.endTime = oneMonthFromNow; return times; } -async function usdTieredSTO_launch(stoConfig, factoryAddress) { +async function usdTieredSTO_launch (factoryAddress, stoConfig) { console.log(chalk.blue('Launch STO - USD pegged tiered STO')); - let usdTieredSTOFactoryABI = abis.usdTieredSTOFactory(); - let usdTieredSTOFactory = new web3.eth.Contract(usdTieredSTOFactoryABI, factoryAddress); - usdTieredSTOFactory.setProvider(web3.currentProvider); - let stoFee = new web3.utils.BN(await usdTieredSTOFactory.methods.getSetupCost().call()); - - let contractBalance = new web3.utils.BN(await polyToken.methods.balanceOf(securityToken._address).call()); - if (contractBalance.lt(stoFee)) { - let transferAmount = stoFee.sub(contractBalance); - let ownerBalance = new web3.utils.BN(await polyToken.methods.balanceOf(Issuer.address).call()); - if (ownerBalance.lt(transferAmount)) { - console.log(chalk.red(`\n**************************************************************************************************************************************************`)); - console.log(chalk.red(`Not enough balance to pay the USDTieredSTO fee, Requires ${web3.utils.fromWei(transferAmount)} POLY but have ${web3.utils.fromWei(ownerBalance)} POLY. Access POLY faucet to get the POLY to complete this txn`)); - console.log(chalk.red(`**************************************************************************************************************************************************\n`)); - return; - } else { - let transferAction = polyToken.methods.transfer(securityToken._address, transferAmount); - let receipt = await common.sendTransaction(transferAction, { factor: 2 }); - let event = common.getEventFromLogs(polyToken._jsonInterface, receipt.logs, 'Transfer'); - console.log(`Number of POLY sent: ${web3.utils.fromWei(new web3.utils.BN(event._value))}`) - } - } + const usdTieredSTOABI = abis.usdTieredSTO(); + const moduleAddress = await common.addModule(securityToken, polyToken, factoryAddress, usdTieredSTOABI, getUSDTieredSTOIntializeData, stoConfig); + let usdTieredSTO = new web3.eth.Contract(usdTieredSTOABI, moduleAddress); + usdTieredSTO.setProvider(web3.currentProvider); + return usdTieredSTO; +} + +async function getUSDTieredSTOIntializeData (usdTieredSTOABI, stoConfig) { let useConfigFile = typeof stoConfig !== 'undefined'; let funding = useConfigFile ? stoConfig.funding : fundingConfigUSDTieredSTO(); let addresses = useConfigFile ? stoConfig.addresses : await addressesConfigUSDTieredSTO(funding.raiseType.includes(gbl.constants.FUND_RAISE_TYPES.STABLE)); let tiers = useConfigFile ? stoConfig.tiers : tiersConfigUSDTieredSTO(funding.raiseType.includes(gbl.constants.FUND_RAISE_TYPES.POLY)); let limits = useConfigFile ? stoConfig.limits : limitsConfigUSDTieredSTO(); let times = timesConfigUSDTieredSTO(stoConfig); - - let usdTieredSTOABI = abis.usdTieredSTO(); let configureFunction = usdTieredSTOABI.find(o => o.name === 'configure' && o.type === 'function'); let bytesSTO = web3.eth.abi.encodeFunctionCall(configureFunction, - [times.startTime, - times.endTime, - tiers.ratePerTier.map(r => web3.utils.toWei(r.toString())), - tiers.ratePerTierDiscountPoly.map(rd => web3.utils.toWei(rd.toString())), - tiers.tokensPerTier.map(t => web3.utils.toWei(t.toString())), - tiers.tokensPerTierDiscountPoly.map(td => web3.utils.toWei(td.toString())), - web3.utils.toWei(limits.nonAccreditedLimitUSD.toString()), - web3.utils.toWei(limits.minimumInvestmentUSD.toString()), - funding.raiseType, - addresses.wallet, - addresses.reserveWallet, - addresses.usdToken] - ); - - let addModuleAction = securityToken.methods.addModule(usdTieredSTOFactory.options.address, bytesSTO, stoFee, 0); - let receipt = await common.sendTransaction(addModuleAction); - let event = common.getEventFromLogs(securityToken._jsonInterface, receipt.logs, 'ModuleAdded'); - console.log(`STO deployed at address: ${event._module}`); - - let usdTieredSTO = new web3.eth.Contract(usdTieredSTOABI, event._module); - usdTieredSTO.setProvider(web3.currentProvider); - - return usdTieredSTO; -} - -async function usdTieredSTO_status(currentSTO) { + [ + times.startTime, + times.endTime, + tiers.ratePerTier.map(r => web3.utils.toWei(r.toString())), + tiers.ratePerTierDiscountPoly.map(rd => web3.utils.toWei(rd.toString())), + tiers.tokensPerTier.map(t => web3.utils.toWei(t.toString())), + tiers.tokensPerTierDiscountPoly.map(td => web3.utils.toWei(td.toString())), + web3.utils.toWei(limits.nonAccreditedLimitUSD.toString()), + web3.utils.toWei(limits.minimumInvestmentUSD.toString()), + funding.raiseType, + addresses.wallet, + addresses.treasuryWallet, + addresses.usdToken + ]); + return bytesSTO; +} + +async function usdTieredSTO_status (currentSTO) { let displayStartTime = await currentSTO.methods.startTime().call(); let displayEndTime = await currentSTO.methods.endTime().call(); let displayCurrentTier = parseInt(await currentSTO.methods.currentTier().call()) + 1; @@ -582,7 +448,7 @@ async function usdTieredSTO_status(currentSTO) { let displayNonAccreditedLimitUSD = web3.utils.fromWei(await currentSTO.methods.nonAccreditedLimitUSD().call()); let displayMinimumInvestmentUSD = web3.utils.fromWei(await currentSTO.methods.minimumInvestmentUSD().call()); let displayWallet = await currentSTO.methods.wallet().call(); - let displayReserveWallet = await currentSTO.methods.reserveWallet().call(); + let displayTreasuryWallet = await currentSTO.methods.treasuryWallet().call(); let displayTokensSold = web3.utils.fromWei(await currentSTO.methods.getTokensSold().call()); let displayInvestorCount = await currentSTO.methods.investorCount().call(); let displayIsFinalized = await currentSTO.methods.isFinalized().call() ? "YES" : "NO"; @@ -594,7 +460,7 @@ async function usdTieredSTO_status(currentSTO) { for (const fundType in gbl.constants.FUND_RAISE_TYPES) { if (await currentSTO.methods.fundRaiseTypes(gbl.constants.FUND_RAISE_TYPES[fundType]).call()) { - if (fundType == STABLE) { + if (fundType === STABLE) { stableSymbols = await processAddress(listOfStableCoins) } raiseTypes.push(fundType); @@ -626,7 +492,7 @@ async function usdTieredSTO_status(currentSTO) { } let mintedPerTier = mintedPerTierPerRaiseType[gbl.constants.FUND_RAISE_TYPES[type]]; - if ((type == STABLE) && (stableSymbols.length)) { + if ((type === STABLE) && (stableSymbols.length)) { displayMintedPerTierPerType += ` Sold for stable coin(s): ${web3.utils.fromWei(mintedPerTier)} ${displayTokenSymbol} ${displayDiscountMinted}`; } else { @@ -638,24 +504,22 @@ async function usdTieredSTO_status(currentSTO) { displayTiers += ` - Tier ${t + 1}: Tokens: ${web3.utils.fromWei(tokensPerTierTotal)} ${displayTokenSymbol} - Rate: ${web3.utils.fromWei(ratePerTier)} USD per Token` - + displayDiscountTokens; + Rate: ${web3.utils.fromWei(ratePerTier)} USD per Token` + displayDiscountTokens; displayMintedPerTier += ` - - Tokens minted in Tier ${t + 1}: ${web3.utils.fromWei(mintedPerTierTotal)} ${displayTokenSymbol}` - + displayMintedPerTierPerType; + - Tokens minted in Tier ${t + 1}: ${web3.utils.fromWei(mintedPerTierTotal)} ${displayTokenSymbol}` + displayMintedPerTierPerType; } let displayFundsRaisedUSD = web3.utils.fromWei(await currentSTO.methods.fundsRaisedUSD().call()); let displayWalletBalancePerType = ''; - let displayReserveWalletBalancePerType = ''; + let displayTreasuryWalletBalancePerType = ''; let displayFundsRaisedPerType = ''; let displayTokensSoldPerType = ''; for (const type of raiseTypes) { let balance = await getBalance(displayWallet, gbl.constants.FUND_RAISE_TYPES[type]); let walletBalance = web3.utils.fromWei(balance); - if ((type == STABLE) && (stableSymbols.length)) { + if ((type === STABLE) && (stableSymbols.length)) { stableSymbols.forEach(async (stable) => { let raised = await checkStableBalance(displayWallet, stable.address); displayWalletBalancePerType += ` @@ -667,22 +531,22 @@ async function usdTieredSTO_status(currentSTO) { Balance ${type}:\t\t ${walletBalance} ${type} (${walletBalanceUSD} USD)`; } - balance = await getBalance(displayReserveWallet, gbl.constants.FUND_RAISE_TYPES[type]); - let reserveWalletBalance = web3.utils.fromWei(balance); - let reserveWalletBalanceUSD = web3.utils.fromWei(await currentSTO.methods.convertToUSD(gbl.constants.FUND_RAISE_TYPES[type], balance).call()); - if ((type == STABLE) && (stableSymbols.length)) { + balance = await getBalance(displayTreasuryWallet, gbl.constants.FUND_RAISE_TYPES[type]); + let treasuryWalletBalance = web3.utils.fromWei(balance); + let treasuryWalletBalanceUSD = web3.utils.fromWei(await currentSTO.methods.convertToUSD(gbl.constants.FUND_RAISE_TYPES[type], balance).call()); + if ((type === STABLE) && (stableSymbols.length)) { stableSymbols.forEach(async (stable) => { - let raised = await checkStableBalance(displayReserveWallet, stable.address); - displayReserveWalletBalancePerType += ` + let raised = await checkStableBalance(displayTreasuryWallet, stable.address); + displayTreasuryWalletBalancePerType += ` Balance ${stable.symbol}:\t\t ${web3.utils.fromWei(raised)} ${stable.symbol}`; }) } else { - displayReserveWalletBalancePerType += ` - Balance ${type}:\t\t ${reserveWalletBalance} ${type} (${reserveWalletBalanceUSD} USD)`; + displayTreasuryWalletBalancePerType += ` + Balance ${type}:\t\t ${treasuryWalletBalance} ${type} (${treasuryWalletBalanceUSD} USD)`; } let fundsRaised = web3.utils.fromWei(await currentSTO.methods.fundsRaised(gbl.constants.FUND_RAISE_TYPES[type]).call()); - if ((type == STABLE) && (stableSymbols.length)) { + if ((type === STABLE) && (stableSymbols.length)) { stableSymbols.forEach(async (stable) => { let raised = await getStableCoinsRaised(currentSTO, stable.address); displayFundsRaisedPerType += ` @@ -693,10 +557,10 @@ async function usdTieredSTO_status(currentSTO) { ${type}:\t\t\t ${fundsRaised} ${type}`; } - //Only show sold for if more than one raise type are allowed + // Only show sold for if more than one raise type are allowed if (raiseTypes.length > 1) { let tokensSoldPerType = web3.utils.fromWei(await currentSTO.methods.getTokensSoldFor(gbl.constants.FUND_RAISE_TYPES[type]).call()); - if ((type == STABLE) && (stableSymbols.length)) { + if ((type === STABLE) && (stableSymbols.length)) { displayTokensSoldPerType += ` Sold for stable coin(s): ${tokensSoldPerType} ${displayTokenSymbol}`; } else { @@ -707,7 +571,7 @@ async function usdTieredSTO_status(currentSTO) { } let displayRaiseType = raiseTypes.join(' - '); - //If STO has stable coins, we list them one by one + // If STO has stable coins, we list them one by one if (stableSymbols.length) { displayRaiseType = displayRaiseType.replace(STABLE, "") + `${stableSymbols.map((obj) => { return obj.symbol }).toString().replace(`,`, ` - `)}` } @@ -731,30 +595,30 @@ async function usdTieredSTO_status(currentSTO) { - Start Time: ${new Date(displayStartTime * 1000)} - End Time: ${new Date(displayEndTime * 1000)} - Raise Type: ${displayRaiseType} - - Tiers: ${tiersLength}` - + displayTiers + ` + - Tiers: ${tiersLength}` + + displayTiers + ` - Minimum Investment: ${displayMinimumInvestmentUSD} USD - Non Accredited Limit: ${displayNonAccreditedLimitUSD} USD - - Wallet: ${displayWallet}` - + displayWalletBalancePerType + ` - - Reserve Wallet: ${displayReserveWallet}` - + displayReserveWalletBalancePerType + ` + - Wallet: ${displayWallet}` + + displayWalletBalancePerType + ` + - Treasury Wallet: ${displayTreasuryWallet}` + + displayTreasuryWalletBalancePerType + ` --------------------------------------------------------------- - ${timeTitle} ${timeRemaining} - Is Finalized: ${displayIsFinalized} - - Tokens Sold: ${displayTokensSold} ${displayTokenSymbol}` - + displayTokensSoldPerType + ` - - Current Tier: ${displayCurrentTier}` - + displayMintedPerTier + ` + - Tokens Sold: ${displayTokensSold} ${displayTokenSymbol}` + + displayTokensSoldPerType + ` + - Current Tier: ${displayCurrentTier}` + + displayMintedPerTier + ` - Investor count: ${displayInvestorCount} - - Funds Raised` - + displayFundsRaisedPerType + ` + - Funds Raised` + + displayFundsRaisedPerType + ` Total USD: ${displayFundsRaisedUSD} USD `); } -async function checkStableBalance(walletAddress, stableAddress) { +async function checkStableBalance (walletAddress, stableAddress) { let stableCoin = common.connect(abis.erc20(), stableAddress); try { return await stableCoin.methods.balanceOf(walletAddress).call(); @@ -763,11 +627,11 @@ async function checkStableBalance(walletAddress, stableAddress) { } } -async function getStableCoinsRaised(currentSTO, address) { - return await currentSTO.methods.stableCoinsRaised(address).call() +async function getStableCoinsRaised (currentSTO, address) { + return currentSTO.methods.stableCoinsRaised(address).call(); } -async function usdTieredSTO_configure(currentSTO) { +async function usdTieredSTO_configure (currentSTO) { console.log(chalk.blue('STO Configuration - USD Tiered STO')); let isFinalized = await currentSTO.methods.isFinalized().call(); @@ -777,39 +641,39 @@ async function usdTieredSTO_configure(currentSTO) { let options = []; options.push('Finalize STO', 'Change accredited account', 'Change accredited in batch', - 'Change non accredited limit for an account', 'Change non accredited limits in batch'); + 'Change non accredited limit for an account', 'Change non accredited limits in batch', + 'Modify addresses configuration'); // If STO is not started, you can modify configuration let now = Math.floor(Date.now() / 1000); let startTime = await currentSTO.methods.startTime().call.call(); if (now < startTime) { - options.push('Modify times configuration', 'Modify tiers configuration', 'Modify addresses configuration', + options.push('Modify times configuration', 'Modify tiers configuration', 'Modify limits configuration', 'Modify funding configuration'); } options.push('Reclaim ETH or ERC20 token from contract'); let index = readlineSync.keyInSelect(options, 'What do you want to do?', { cancel: 'RETURN' }); - let selected = index != -1 ? options[index] : 'Exit'; + let selected = index !== -1 ? options[index] : 'Exit'; switch (selected) { case 'Finalize STO': - let reserveWallet = await currentSTO.methods.reserveWallet().call(); - let isVerified = await securityToken.methods.verifyTransfer('0x0000000000000000000000000000000000000000', reserveWallet, 0, web3.utils.fromAscii("")).call(); + let treasuryWallet = await currentSTO.methods.treasuryWallet().call(); + let isVerified = await securityToken.methods.canTransferFrom('0x0000000000000000000000000000000000000000', treasuryWallet, 0, web3.utils.fromAscii("")).call(); if (isVerified) { if (readlineSync.keyInYNStrict()) { let finalizeAction = currentSTO.methods.finalize(); await common.sendTransaction(finalizeAction); } } else { - console.log(chalk.red(`Reserve wallet (${reserveWallet}) is not able to receive remaining tokens. Check if this address is whitelisted.`)); + console.log(chalk.red(`Treasury wallet (${treasuryWallet}) is not able to receive remaining tokens. Check if this address is whitelisted.`)); } break; case 'Change accredited account': let investor = readlineSync.question('Enter the address to change accreditation: '); let isAccredited = readlineSync.keyInYNStrict(`Is ${investor} accredited?`); - let investors = [investor]; - let accredited = [isAccredited]; - let changeAccreditedAction = currentSTO.methods.changeAccredited(investors, accredited); + let generalTransferManager = await getGeneralTransferManager(); + let changeAccreditedAction = generalTransferManager.methods.modifyInvestorFlag(investor, 0, isAccredited); // 2 GAS? await common.sendTransaction(changeAccreditedAction); break; @@ -854,7 +718,16 @@ async function usdTieredSTO_configure(currentSTO) { } } -async function showAccreditedData(currentSTO) { +async function getGeneralTransferManager () { + let gmtModules = await securityToken.methods.getModulesByName(web3.utils.toHex('GeneralTransferManager')).call(); + let generalTransferManagerAddress = gmtModules[0]; + let generalTransferManagerABI = abis.generalTransferManager(); + let generalTransferManager = new web3.eth.Contract(generalTransferManagerABI, generalTransferManagerAddress); + generalTransferManager.setProvider(web3.currentProvider); + return generalTransferManager; +} + +async function showAccreditedData (currentSTO) { let accreditedData = await currentSTO.methods.getAccreditedData().call(); let investorArray = accreditedData[0]; let accreditedArray = accreditedData[1]; @@ -878,20 +751,13 @@ async function showAccreditedData(currentSTO) { console.log(chalk.yellow(`There is no accredited data to show`)); console.log(); } - } -async function changeAccreditedInBatch(currentSTO) { +async function changeAccreditedInBatch (currentSTO) { let csvFilePath = readlineSync.question(`Enter the path for csv data file (${ACCREDIT_DATA_CSV}): `, { defaultInput: ACCREDIT_DATA_CSV }); - let batchSize = readlineSync.question(`Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, { - limit: function (input) { - return parseInt(input) > 0; - }, - limitMessage: 'Must be greater than 0', - defaultInput: gbl.constants.DEFAULT_BATCH_SIZE - }); + let batchSize = input.readNumberGreaterThan(0, `Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, gbl.constants.DEFAULT_BATCH_SIZE); let parsedData = csvParse(csvFilePath); let validData = parsedData.filter(row => web3.utils.isAddress(row[0]) && typeof row[1] === 'boolean'); let invalidRows = parsedData.filter(row => !validData.includes(row)); @@ -899,27 +765,25 @@ async function changeAccreditedInBatch(currentSTO) { console.log(chalk.red(`The following lines from csv file are not valid: ${invalidRows.map(r => parsedData.indexOf(r) + 1).join(',')}`)); } let batches = common.splitIntoBatches(validData, batchSize); + let transposedData = common.transposeBatches(batches); let [investorArray, isAccreditedArray] = common.transposeBatches(batches); + let generalTransferManager = await getGeneralTransferManager(); for (let batch = 0; batch < batches.length; batch++) { console.log(`Batch ${batch + 1} - Attempting to change accredited accounts:\n\n`, investorArray[batch], '\n'); - let action = currentSTO.methods.changeAccredited(investorArray[batch], isAccreditedArray[batch]); + // create array with correct batch length of isAccredited flag = 0 + let accreditedFlagArray = new Array(investorArray[batch].length).fill(0); + let action = generalTransferManager.methods.modifyInvestorFlagMulti(investorArray[batch], accreditedFlagArray, isAccreditedArray[batch]); let receipt = await common.sendTransaction(action); console.log(chalk.green('Change accredited transaction was successful.')); console.log(`${receipt.gasUsed} gas used. Spent: ${web3.utils.fromWei((new web3.utils.BN(receipt.gasUsed)).mul(new web3.utils.BN(defaultGasPrice)))} ETH`); } } -async function changeNonAccreditedLimitsInBatch(currentSTO) { +async function changeNonAccreditedLimitsInBatch (currentSTO) { let csvFilePath = readlineSync.question(`Enter the path for csv data file (${NON_ACCREDIT_LIMIT_DATA_CSV}): `, { defaultInput: NON_ACCREDIT_LIMIT_DATA_CSV }); - let batchSize = readlineSync.question(`Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, { - limit: function (input) { - return parseInt(input) > 0; - }, - limitMessage: 'Must be greater than 0', - defaultInput: gbl.constants.DEFAULT_BATCH_SIZE - }); + let batchSize = input.readNumberGreaterThan(0, `Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, gbl.constants.DEFAULT_BATCH_SIZE); let parsedData = csvParse(csvFilePath); let validData = parsedData.filter(row => web3.utils.isAddress(row[0]) && !isNaN(row[1])); let invalidRows = parsedData.filter(row => !validData.includes(row)); @@ -938,61 +802,59 @@ async function changeNonAccreditedLimitsInBatch(currentSTO) { } } -async function modfifyTimes(currentSTO) { +async function modfifyTimes (currentSTO) { let times = timesConfigUSDTieredSTO(); let modifyTimesAction = currentSTO.methods.modifyTimes(times.startTime, times.endTime); await common.sendTransaction(modifyTimesAction); } -async function modfifyLimits(currentSTO) { +async function modfifyLimits (currentSTO) { let limits = limitsConfigUSDTieredSTO(); - let modifyLimitsAction = currentSTO.methods.modifyLimits(limits.nonAccreditedLimitUSD, limits.minimumInvestmentUSD); + + let modifyLimitsAction = currentSTO.methods.modifyLimits( + web3.utils.toWei(limits.nonAccreditedLimitUSD.toString()), + web3.utils.toWei(limits.minimumInvestmentUSD.toString()) + ); await common.sendTransaction(modifyLimitsAction); } -async function modfifyFunding(currentSTO) { +async function modfifyFunding (currentSTO) { let funding = fundingConfigUSDTieredSTO(); let modifyFundingAction = currentSTO.methods.modifyFunding(funding.raiseType); await common.sendTransaction(modifyFundingAction); } -async function modfifyAddresses(currentSTO) { +async function modfifyAddresses (currentSTO) { let addresses = await addressesConfigUSDTieredSTO(await currentSTO.methods.fundRaiseTypes(gbl.constants.FUND_RAISE_TYPES.STABLE).call()); - let modifyAddressesAction = currentSTO.methods.modifyAddresses(addresses.wallet, addresses.reserveWallet, addresses.usdToken); + let modifyAddressesAction = currentSTO.methods.modifyAddresses(addresses.wallet, addresses.treasuryWallet, addresses.usdToken); await common.sendTransaction(modifyAddressesAction); } -async function modfifyTiers(currentSTO) { +async function modfifyTiers (currentSTO) { let tiers = tiersConfigUSDTieredSTO(await currentSTO.methods.fundRaiseTypes(gbl.constants.FUND_RAISE_TYPES.POLY).call()); let modifyTiersAction = currentSTO.methods.modifyTiers( - tiers.ratePerTier, - tiers.ratePerTierDiscountPoly, - tiers.tokensPerTier, - tiers.tokensPerTierDiscountPoly, + tiers.ratePerTier.map(r => web3.utils.toWei(r.toString())), + tiers.ratePerTierDiscountPoly.map(rd => web3.utils.toWei(rd.toString())), + tiers.tokensPerTier.map(t => web3.utils.toWei(t.toString())), + tiers.tokensPerTierDiscountPoly.map(td => web3.utils.toWei(td.toString())) ); await common.sendTransaction(modifyTiersAction); } -async function reclaimFromContract(currentSTO) { +async function reclaimFromContract (currentSTO) { let options = ['ETH', 'ERC20']; let index = readlineSync.keyInSelect(options, 'What do you want to reclaim?', { cancel: 'RETURN' }); - let selected = index != -1 ? options[index] : 'RETURN'; + let selected = index !== -1 ? options[index] : 'RETURN'; switch (selected) { case 'ETH': - let ethBalance = await this.getBalance(currentSTO.options.address, gbl.constants.FUND_RAISE_TYPES.ETH); + let ethBalance = await getBalance(currentSTO.options.address, gbl.constants.FUND_RAISE_TYPES.ETH); console.log(chalk.yellow(`Current ETH balance: ${web3.utils.fromWei(ethBalance)} ETH`)); let reclaimETHAction = currentSTO.methods.reclaimETH(); await common.sendTransaction(reclaimETHAction); console.log(chalk.green('ETH has been reclaimed succesfully!')); break; case 'ERC20': - let erc20Address = readlineSync.question('Enter the ERC20 token address to reclaim (POLY = ' + polyToken.options.address + '): ', { - limit: function (input) { - return web3.utils.isAddress(input); - }, - limitMessage: "Must be a valid address", - defaultInput: polyToken.options.address - }); + let erc20Address = input.readAddress('Enter the ERC20 token address to reclaim (POLY = ' + polyToken.options.address + '): ', polyToken.options.address); let reclaimERC20Action = currentSTO.methods.reclaimERC20(erc20Address); await common.sendTransaction(reclaimERC20Action); console.log(chalk.green('ERC20 has been reclaimed succesfully!')); @@ -1000,72 +862,28 @@ async function reclaimFromContract(currentSTO) { } } -////////////////////// // HELPER FUNCTIONS // -////////////////////// -async function getBalance(from, type) { +async function getBalance (from, type) { switch (type) { case gbl.constants.FUND_RAISE_TYPES.ETH: - return await web3.eth.getBalance(from); + return web3.eth.getBalance(from); case gbl.constants.FUND_RAISE_TYPES.POLY: - return await polyToken.methods.balanceOf(from).call(); + return polyToken.methods.balanceOf(from).call(); default: return '0'; } } -async function getAllModulesByType(type) { - function ModuleInfo(_moduleType, _name, _address, _factoryAddress, _archived, _paused, _version) { - this.name = _name; - this.type = _moduleType; - this.address = _address; - this.factoryAddress = _factoryAddress; - this.archived = _archived; - this.paused = _paused; - this.version = _version; - } - - let modules = []; - - let allModules = await securityToken.methods.getModulesByType(type).call(); - - for (let i = 0; i < allModules.length; i++) { - let details = await securityToken.methods.getModule(allModules[i]).call(); - let nameTemp = web3.utils.hexToUtf8(details[0]); - let pausedTemp = null; - if (type == gbl.constants.MODULES_TYPES.STO || type == gbl.constants.MODULES_TYPES.TRANSFER) { - let abiTemp = JSON.parse(require('fs').readFileSync(`${__dirname}/../../build/contracts/${nameTemp}.json`).toString()).abi; - let contractTemp = new web3.eth.Contract(abiTemp, details[1]); - pausedTemp = await contractTemp.methods.paused().call(); - } - let factoryAbi = abis.moduleFactory(); - let factory = new web3.eth.Contract(factoryAbi, details[2]); - let versionTemp = await factory.methods.version().call(); - modules.push(new ModuleInfo(type, nameTemp, details[1], details[2], details[3], pausedTemp, versionTemp)); - } - - return modules; -} - -async function initialize(_tokenSymbol) { +async function initialize (_tokenSymbol) { welcome(); await setup(); - if (typeof _tokenSymbol === 'undefined') { - tokenSymbol = await selectToken(); - } else { - tokenSymbol = _tokenSymbol; - } - let securityTokenAddress = await securityTokenRegistry.methods.getSecurityTokenAddress(tokenSymbol).call(); - if (securityTokenAddress == '0x0000000000000000000000000000000000000000') { - console.log(chalk.red(`Selected Security Token ${tokenSymbol} does not exist.`)); + securityToken = await common.selectToken(securityTokenRegistry, _tokenSymbol); + if (securityToken === null) { process.exit(0); } - let securityTokenABI = abis.securityToken(); - securityToken = new web3.eth.Contract(securityTokenABI, securityTokenAddress); - securityToken.setProvider(web3.currentProvider); } -function welcome() { +function welcome () { common.logAsciiBull(); console.log("****************************************"); console.log("Welcome to the Command-Line STO Manager."); @@ -1074,11 +892,11 @@ function welcome() { console.log("Issuer Account: " + Issuer.address + "\n"); } -async function setup() { +async function setup () { try { let securityTokenRegistryAddress = await contracts.securityTokenRegistry(); - let securityTokenRegistryABI = abis.securityTokenRegistry(); - securityTokenRegistry = new web3.eth.Contract(securityTokenRegistryABI, securityTokenRegistryAddress); + let iSecurityTokenRegistryABI = abis.iSecurityTokenRegistry(); + securityTokenRegistry = new web3.eth.Contract(iSecurityTokenRegistryABI, securityTokenRegistryAddress); securityTokenRegistry.setProvider(web3.currentProvider); let moduleRegistryAddress = await contracts.moduleRegistry(); @@ -1097,36 +915,6 @@ async function setup() { } } -async function selectToken() { - let result = null; - - let userTokens = await securityTokenRegistry.methods.getTokensByOwner(Issuer.address).call(); - let tokenDataArray = await Promise.all(userTokens.map(async function (t) { - let tokenData = await securityTokenRegistry.methods.getSecurityTokenData(t).call(); - return { symbol: tokenData[0], address: t }; - })); - let options = tokenDataArray.map(function (t) { - return `${t.symbol} - Deployed at ${t.address}`; - }); - options.push('Enter token symbol manually'); - - let index = readlineSync.keyInSelect(options, 'Select a token:', { cancel: 'EXIT' }); - let selected = index != -1 ? options[index] : 'EXIT'; - switch (selected) { - case 'Enter token symbol manually': - result = readlineSync.question('Enter the token symbol: '); - break; - case 'Exit': - process.exit(); - break; - default: - result = tokenDataArray[index].symbol; - break; - } - - return result; -} - module.exports = { executeApp: async function (_tokenSymbol) { await initialize(_tokenSymbol); diff --git a/CLI/commands/strMigrator.js b/CLI/commands/strMigrator.js index f27d0bfbd..f45f01002 100644 --- a/CLI/commands/strMigrator.js +++ b/CLI/commands/strMigrator.js @@ -54,7 +54,7 @@ async function step_instance_toSTR(toStrAddress) { // return web3.utils.isAddress(input); // }, // limitMessage: "Must be a valid address" - // }); + // }); _toStrAddress = "0x240f9f86b1465bf1b8eb29bc88cbf65573dfdd97"; } @@ -81,7 +81,7 @@ async function step_instance_fromTR(fromTrAddress) { // return web3.utils.isAddress(input); // }, // limitMessage: "Must be a valid address" - // }); + // }); _fromTrAddress = "0xc31714e6759a1ee26db1d06af1ed276340cd4233"; } @@ -192,7 +192,7 @@ async function step_instance_fromSTR(fromStrAddress) { // return web3.utils.isAddress(input); // }, // limitMessage: "Must be a valid address" - // }); + // }); _fromStrAddress = "0xef58491224958d978facf55d2120c55a24516b98"; } @@ -232,7 +232,7 @@ async function step_get_deployed_tokens(securityTokenRegistry, singleTicker) { let gmtAddress = (await token.methods.getModule(2, 0).call())[1]; let gtmABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../data/GeneralTransferManager1-4-0.json`).toString()).abi; let gmt = new web3.eth.Contract(gtmABI, gmtAddress); - //let gtmEvents = await gmt.getPastEvents('LogModifyWhitelist', { fromBlock: event.blockNumber}); + //let gtmEvents = await gmt.getPastEvents('LogModifyWhitelist', { fromBlock: event.blockNumber}); let gtmLogs = await getLogsFromEtherscan(gmt.options.address, 0, 'latest', 'LogModifyWhitelist(address,uint256,address,uint256,uint256,uint256,bool)'); let gtmEvents = common.getMultipleEventsFromLogs(gmt._jsonInterface, gtmLogs, 'LogModifyWhitelist'); @@ -453,4 +453,4 @@ module.exports = { executeApp: async function (toStrAddress, fromTrAddress, fromStrAddress, singleTicker, tokenAddress, onlyTickers, remoteNetwork) { return executeApp(toStrAddress, fromTrAddress, fromStrAddress, singleTicker, tokenAddress, onlyTickers, remoteNetwork); } -}; \ No newline at end of file +}; diff --git a/CLI/commands/token_manager.js b/CLI/commands/token_manager.js index 28e69e06b..adcdc49c4 100644 --- a/CLI/commands/token_manager.js +++ b/CLI/commands/token_manager.js @@ -6,6 +6,8 @@ const transferManager = require('./transfer_manager'); const common = require('./common/common_functions'); const gbl = require('./common/global'); const csvParse = require('./helpers/csv'); +const input = require('./IO/input'); +const moment = require('moment'); // Constants const MULTIMINT_DATA_CSV = `${__dirname}/../data/ST/multi_mint_data.csv`; @@ -25,8 +27,8 @@ let tokenSymbol async function setup() { try { let securityTokenRegistryAddress = await contracts.securityTokenRegistry(); - let securityTokenRegistryABI = abis.securityTokenRegistry(); - securityTokenRegistry = new web3.eth.Contract(securityTokenRegistryABI, securityTokenRegistryAddress); + let iSecurityTokenRegistryABI = abis.iSecurityTokenRegistry(); + securityTokenRegistry = new web3.eth.Contract(iSecurityTokenRegistryABI, securityTokenRegistryAddress); securityTokenRegistry.setProvider(web3.currentProvider); let polytokenAddress = await contracts.polyToken(); @@ -58,27 +60,37 @@ async function executeApp() { async function displayTokenData() { let displayTokenSymbol = await securityToken.methods.symbol().call(); + let displayTokenName = await securityToken.methods.name().call(); let displayTokenDetails = await securityToken.methods.tokenDetails().call(); let displayVersion = await securityToken.methods.getVersion().call(); + let displayGranularity = await securityToken.methods.granularity().call(); let displayTokenSupply = await securityToken.methods.totalSupply().call(); + let displayHolderCount = await securityToken.methods.holderCount().call(); let displayInvestorsCount = await securityToken.methods.getInvestorCount().call(); let displayCurrentCheckpointId = await securityToken.methods.currentCheckpointId().call(); let displayTransferFrozen = await securityToken.methods.transfersFrozen().call(); - let displayMintingFrozen = await securityToken.methods.mintingFrozen().call(); + let displayIsIssuable = await securityToken.methods.isIssuable().call(); let displayUserTokens = await securityToken.methods.balanceOf(Issuer.address).call(); + let displayTreasuryWallet = await securityToken.methods.getTreasuryWallet().call(); + let displayDocuments = await securityToken.methods.getAllDocuments().call(); console.log(` *************** Security Token Information **************** - Address: ${securityToken.options.address} - Token symbol: ${displayTokenSymbol.toUpperCase()} +- Token name: ${displayTokenName} - Token details: ${displayTokenDetails} - Token version: ${displayVersion[0]}.${displayVersion[1]}.${displayVersion[2]} +- Granularity: ${displayGranularity} - Total supply: ${web3.utils.fromWei(displayTokenSupply)} ${displayTokenSymbol.toUpperCase()} +- Holders count: ${displayHolderCount} - Investors count: ${displayInvestorsCount} - Current checkpoint: ${displayCurrentCheckpointId} - Transfer frozen: ${displayTransferFrozen ? 'YES' : 'NO'} -- Minting frozen: ${displayMintingFrozen ? 'YES' : 'NO'} -- User balance: ${web3.utils.fromWei(displayUserTokens)} ${displayTokenSymbol.toUpperCase()}`); +- Issuance allowed: ${displayIsIssuable ? 'YES' : 'NO'} +- User balance: ${web3.utils.fromWei(displayUserTokens)} ${displayTokenSymbol.toUpperCase()} +- Treasury wallet: ${displayTreasuryWallet} +- Documents attached: ${displayDocuments.length}`); } async function displayModules() { @@ -88,6 +100,7 @@ async function displayModules() { let stoModules = allModules.filter(m => m.type == gbl.constants.MODULES_TYPES.STO); let cpModules = allModules.filter(m => m.type == gbl.constants.MODULES_TYPES.DIVIDENDS); let burnModules = allModules.filter(m => m.type == gbl.constants.MODULES_TYPES.BURN); + let walletModules = allModules.filter(m => m.type == gbl.constants.MODULES_TYPES.WALLET); // Module Counts let numPM = pmModules.length; @@ -95,6 +108,7 @@ async function displayModules() { let numSTO = stoModules.length; let numCP = cpModules.length; let numBURN = burnModules.length; + let numW = walletModules.length; console.log(` ******************* Module Information ******************** @@ -103,36 +117,42 @@ async function displayModules() { - STO: ${(numSTO > 0) ? numSTO : 'None'} - Checkpoint: ${(numCP > 0) ? numCP : 'None'} - Burn: ${(numBURN > 0) ? numBURN : 'None'} +- Wallet: ${(numW > 0) ? numW : 'None'} `); if (numPM) { console.log(`Permission Manager Modules:`); - pmModules.map(m => console.log(`- ${m.name} (${m.version}) is ${(m.archived) ? chalk.yellow('archived') : 'unarchived'} at ${m.address}`)); + pmModules.map(m => console.log(`- ${m.label}: ${m.name} (${m.version}) is ${(m.archived) ? chalk.yellow('archived') : 'unarchived'} at ${m.address}`)); } if (numTM) { console.log(`Transfer Manager Modules:`); - tmModules.map(m => console.log(`- ${m.name} (${m.version}) is ${(m.archived) ? chalk.yellow('archived') : 'unarchived'} at ${m.address}`)); + tmModules.map(m => console.log(`- ${m.label}: ${m.name} (${m.version}) is ${(m.archived) ? chalk.yellow('archived') : 'unarchived'} at ${m.address}`)); } if (numSTO) { console.log(`STO Modules:`); - stoModules.map(m => console.log(`- ${m.name} (${m.version}) is ${(m.archived) ? chalk.yellow('archived') : 'unarchived'} at ${m.address}`)); + stoModules.map(m => console.log(`- ${m.label}: ${m.name} (${m.version}) is ${(m.archived) ? chalk.yellow('archived') : 'unarchived'} at ${m.address}`)); } if (numCP) { console.log(`Checkpoint Modules:`); - cpModules.map(m => console.log(`- ${m.name} (${m.version}) is ${(m.archived) ? chalk.yellow('archived') : 'unarchived'} at ${m.address}`)); + cpModules.map(m => console.log(`- ${m.label}: ${m.name} (${m.version}) is ${(m.archived) ? chalk.yellow('archived') : 'unarchived'} at ${m.address}`)); } if (numBURN) { - console.log(` Burn Modules:`); - burnModules.map(m => console.log(`- ${m.name} (${m.version}) is ${(m.archived) ? chalk.yellow('archived') : 'unarchived'} at ${m.address}`)); + console.log(`Burn Modules:`); + burnModules.map(m => console.log(`- ${m.label}: ${m.name} (${m.version}) is ${(m.archived) ? chalk.yellow('archived') : 'unarchived'} at ${m.address}`)); + } + + if (numW) { + console.log(`Wallet Modules:`); + walletModules.map(m => console.log(`- ${m.label}: ${m.name} (${m.version}) is ${(m.archived) ? chalk.yellow('archived') : 'unarchived'} at ${m.address}`)); } } async function selectAction() { - let options = ['Update token details'/*, 'Change granularity'*/]; + let options = ['Change token name', 'Update token details', 'Change treasury wallet', 'Manage documents', 'Change granularity']; let transferFrozen = await securityToken.methods.transfersFrozen().call(); if (transferFrozen) { @@ -141,11 +161,10 @@ async function selectAction() { options.push('Freeze transfers'); } - let isMintingFrozen = await securityToken.methods.mintingFrozen().call(); - if (!isMintingFrozen) { - let isFreezeMintingAllowed = await featureRegistry.methods.getFeatureStatus('freezeMintingAllowed').call(); - if (isFreezeMintingAllowed) { - options.push('Freeze minting permanently'); + let isIssuable = await securityToken.methods.isIssuable().call(); + if (isIssuable) { + if (Issuer.address == await securityToken.methods.owner().call()) { + options.push('Freeze Issuance permanently'); } } @@ -156,23 +175,40 @@ async function selectAction() { options.push('List investors at checkpoint') } - if (!isMintingFrozen) { - options.push('Mint tokens'); + if (isIssuable) { + options.push('Issue tokens'); } options.push('Manage modules', 'Withdraw tokens from contract'); + const tokenVersion = await securityToken.methods.getVersion().call(); + const latestSTVersion = await securityTokenRegistry.methods.getLatestProtocolVersion().call(); + if (tokenVersion !== latestSTVersion) { + options.push('Refresh security token'); + } + let index = readlineSync.keyInSelect(options, 'What do you want to do?', { cancel: 'Exit' }); let selected = index == -1 ? 'Exit' : options[index]; console.log('Selected:', selected); switch (selected) { + case 'Change token name': + let newTokenName = readlineSync.question('Enter new token name: '); + await changeTokenName(newTokenName); + break; case 'Update token details': let updatedDetails = readlineSync.question('Enter new off-chain details of the token (i.e. Dropbox folder url): '); await updateTokenDetails(updatedDetails); break; + case 'Change treasury wallet': + let newTreasuryWallet = input.readAddress('Enter the address of the new treasury wallet: '); + await changeTreasuryWallet(newTreasuryWallet); + break; case 'Change granularity': - //let granularity = readlineSync.questionInt('Enter ') - //await changeGranularity(); + let granularity = input.readNumberBetween(1, 18, 'Enter the granularity you want to set: '); + await changeGranularity(granularity); + break; + case 'Manage documents': + await manageDocuments(); break; case 'Freeze transfers': await freezeTransfers(); @@ -180,8 +216,8 @@ async function selectAction() { case 'Unfreeze transfers': await unfreezeTransfers(); break; - case 'Freeze minting permanently': - await freezeMinting(); + case 'Freeze Issuance permanently': + await freezeIssuance(); break; case 'Create a checkpoint': await createCheckpoint(); @@ -190,49 +226,130 @@ async function selectAction() { await listInvestors(); break; case 'List investors at checkpoint': - let checkpointId = readlineSync.question('Enter the id of the checkpoint: ', { - limit: function (input) { - return parseInt(input) > 0 && parseInt(input) <= parseInt(currentCheckpointId); - }, - limitMessage: `Must be greater than 0 and less than ${currentCheckpointId}` - }); + let checkpointId = input.readNumberBetween(1, parseInt(currentCheckpointId), 'Enter the id of the checkpoint: '); await listInvestorsAtCheckpoint(checkpointId); break; - case 'Mint tokens': - await mintTokens(); + case 'Issue tokens': + await issueTokens(); break; case 'Manage modules': await listModuleOptions(); break; case 'Withdraw tokens from contract': - let tokenAddress = readlineSync.question(`Enter the ERC20 token address (POLY ${polyToken.options.address}): `, { - limit: function (input) { - return web3.utils.isAddress(input); - }, - limitMessage: "Must be a valid address", - defaultInput: polyToken.options.address - }); - let value = readlineSync.questionFloat('Enter the value to withdraw: ', { - limit: function (input) { - return input > 0; - }, - limitMessage: "Must be a greater than 0" - }); + let tokenAddress = input.readAddress(`Enter the ERC20 token address (POLY ${polyToken.options.address}): `, polyToken.options.address); + let value = parseFloat(input.readNumberGreaterThan(0, 'Enter the value to withdraw: ')); await withdrawFromContract(tokenAddress, web3.utils.toWei(new web3.utils.BN(value))); break; + case 'Refresh security token': + console.log(chalk.yellow.bgRed.bold(`---- WARNING: THIS ACTION WILL LAUNCH A NEW SECURITY TOKEN INSTANCE! ----`)); + const confirmation = readlineSync.keyInYNStrict(`Are you sure you want to refresh your ST to version ${tokenVersion[0]}.${tokenVersion[1]}.${tokenVersion[2]}?`); + if (confirmation) { + let transferFrozen = await securityToken.methods.transfersFrozen().call(); + if (!transferFrozen) { + if (readlineSync.keyInYNStrict(`Transfers must be frozen to refresh your ST version. Do you want to freeze transfer now?`)) { + await freezeTransfers(); + } + transferFrozen = true; + } + if (transferFrozen) { + const name = await securityToken.methods.name().call(); + const symbol = await securityToken.methods.symbol().call(); + const tokenDetails = await securityToken.methods.tokenDetails().call(); + const divisible = (await securityToken.methods.granularity().call()) === '1'; + const treasuryWallet = input.readAddress('Enter the treasury address for the token (' + Issuer.address + '): ', Issuer.address); + const refreshAction = securityTokenRegistry.methods.refreshSecurityToken( + name, + symbol, + tokenDetails, + divisible, + treasuryWallet + ); + const refreshReceipt = await common.sendTransaction(refreshAction); + const refreshEvent = common.getEventFromLogs(securityTokenRegistry._jsonInterface, refreshReceipt.logs, 'SecurityTokenRefreshed'); + console.log(chalk.green(`Security Token has been refreshed successfully at ${refreshEvent._securityTokenAddress}!`)); + securityToken = new web3.eth.Contract(abis.iSecurityToken(), refreshEvent._securityTokenAddress); + securityToken.setProvider(web3.currentProvider); + } + } + break; case 'Exit': process.exit(); - break; } } // Token actions +async function changeTokenName(newTokenName) { + let changeTokenNameAction = securityToken.methods.changeName(newTokenName); + await common.sendTransaction(changeTokenNameAction); + console.log(chalk.green(`Token details have been updated successfully!`)); +} + async function updateTokenDetails(updatedDetails) { let updateTokenDetailsAction = securityToken.methods.updateTokenDetails(updatedDetails); await common.sendTransaction(updateTokenDetailsAction); console.log(chalk.green(`Token details have been updated successfully!`)); } +async function changeTreasuryWallet(newTreasuryWallet) { + let changeTreasuryWalletAction = securityToken.methods.changeTreasuryWallet(newTreasuryWallet); + await common.sendTransaction(changeTreasuryWalletAction); + console.log(chalk.green(`Treasury wallet has been updated successfully!`)); +} + +async function changeGranularity(granularity) { + let changeGranularityAction = securityToken.methods.changeGranularity(granularity); + await common.sendTransaction(changeGranularityAction); + console.log(chalk.green(`Granularity has been updated successfully!`)); +} + +async function manageDocuments() { + let options = ['Add document']; + + const allDocuments = await securityToken.methods.getAllDocuments().call(); + if (allDocuments.length > 0) { + options.push('Get document', 'Remove document'); + console.log(`Attached documents:`) + allDocuments.map(d => console.log(`- ${web3.utils.hexToUtf8(d)}`)); + } else { + console.log(`No documents attached:`); + } + + let index = readlineSync.keyInSelect(options, 'What do you want to do?', { cancel: 'Return' }); + let selected = index == -1 ? 'Return' : options[index]; + console.log('Selected:', selected); + switch (selected) { + case 'Add document': + const documentName = readlineSync.question('Enter the document name: '); + const documentUri = readlineSync.question('Enter the document uri: '); + const documentHash = readlineSync.question('Enter the document hash: '); + const setDocumentAction = securityToken.methods.setDocument(web3.utils.toHex(documentName), documentUri, web3.utils.toHex(documentHash)); + await common.sendTransaction(setDocumentAction); + console.log(chalk.green(`Document has been added successfully!`)); + break; + case 'Get document': + const documentoToGet = selectDocument(allDocuments); + const document = await securityToken.methods.getDocument(documentoToGet).call(); + console.log(web3.utils.hexToUtf8(documentoToGet)); + console.log(`Uri: ${document[0]}`); + console.log(`Hash: ${web3.utils.hexToUtf8(document[1])}`); + console.log(`Last modified: ${moment.unix(document[2]).format('MM/DD/YYYY HH:mm')}`); + break; + case 'Remove document': + const documentoToRemove = selectDocument(allDocuments); + const removeDocumentAction = securityToken.methods.removeDocument(documentoToRemove); + await common.sendTransaction(removeDocumentAction); + console.log(chalk.green(`Document has been removed successfully!`)); + break; + } +} + +function selectDocument(allDocuments) { + const options = allDocuments.map(d => `${web3.utils.hexToUtf8(d)}`); + let index = readlineSync.keyInSelect(options, 'Select a document:', { cancel: 'Return' }); + let selected = index == -1 ? 'Return' : allDocuments[index]; + return selected; +} + async function freezeTransfers() { let freezeTransfersAction = securityToken.methods.freezeTransfers(); await common.sendTransaction(freezeTransfersAction); @@ -245,10 +362,17 @@ async function unfreezeTransfers() { console.log(chalk.green(`Transfers have been unfrozen successfully!`)); } -async function freezeMinting() { - let freezeMintingAction = securityToken.methods.freezeMinting(); - await common.sendTransaction(freezeMintingAction); - console.log(chalk.green(`Minting has been frozen successfully!.`)); +async function freezeIssuance() { + console.log(chalk.yellow.bgRed.bold(`---- WARNING: THIS ACTION WILL PERMANENTLY DISABLE TOKEN ISSUANCE! ----`)); + let confirmation = readlineSync.question(`To confirm type "I acknowledge that freezing Issuance is a permanent and irrevocable change": `); + if (confirmation == "I acknowledge that freezing Issuance is a permanent and irrevocable change") { + let signature = await getFreezeIssuanceAck(securityToken.options.address, Issuer.address); + let freezeIssuanceAction = securityToken.methods.freezeIssuance(signature); + await common.sendTransaction(freezeIssuanceAction); + console.log(chalk.green(`Issuance has been frozen successfully!.`)); + } else { + console.log(chalk.yellow(`Invalid confirmation. Action Canceled`)); + } } async function createCheckpoint() { @@ -281,8 +405,8 @@ async function listInvestorsAtCheckpoint(checkpointId) { } -async function mintTokens() { - let options = ['Modify whitelist', 'Mint tokens to a single address', `Mint tokens to multiple addresses from CSV`]; +async function issueTokens() { + let options = ['Modify whitelist', 'Issue tokens to a single address', `Issue tokens to multiple addresses from CSV`]; let index = readlineSync.keyInSelect(options, 'What do you want to do?', { cancel: 'Return' }); let selected = index == -1 ? 'Return' : options[index]; console.log('Selected:', selected); @@ -291,20 +415,20 @@ async function mintTokens() { let generalTransferManager = await getGeneralTransferManager(); await common.queryModifyWhiteList(generalTransferManager); break; - case 'Mint tokens to a single address': + case 'Issue tokens to a single address': console.log(chalk.yellow(`Investor should be previously whitelisted.`)); let receiver = readlineSync.question(`Enter the address to receive the tokens: `); - let amount = readlineSync.question(`Enter the amount of tokens to mint: `); - await mintToSingleAddress(receiver, amount); + let amount = readlineSync.question(`Enter the amount of tokens to issue: `); + await issueToSingleAddress(receiver, amount); break; - case `Mint tokens to multiple addresses from CSV`: + case `Issue tokens to multiple addresses from CSV`: console.log(chalk.yellow(`Investors should be previously whitelisted.`)); - await multiMint(); + await multiIssue(); break; } } -/// Mint actions +/// Issue actions async function getGeneralTransferManager() { let gmtModules = await securityToken.methods.getModulesByName(web3.utils.toHex('GeneralTransferManager')).call(); let generalTransferManagerAddress = gmtModules[0]; @@ -314,20 +438,20 @@ async function getGeneralTransferManager() { return generalTransferManager; } -async function mintToSingleAddress(_investor, _amount) { +async function issueToSingleAddress(_investor, _amount) { try { - let mintAction = securityToken.methods.mint(_investor, web3.utils.toWei(_amount)); - let receipt = await common.sendTransaction(mintAction); - let event = common.getEventFromLogs(securityToken._jsonInterface, receipt.logs, 'Minted'); - console.log(chalk.green(`${web3.utils.fromWei(event._value)} tokens have been minted to ${event._to} successfully.`)); + let issueAction = securityToken.methods.issue(_investor, web3.utils.toWei(_amount), web3.utils.fromAscii('')); + let receipt = await common.sendTransaction(issueAction); + let event = common.getEventFromLogs(securityToken._jsonInterface, receipt.logs, 'Issued'); + console.log(chalk.green(`${web3.utils.fromWei(event._value)} tokens have been issued to ${event._to} successfully.`)); } catch (e) { console.log(e); - console.log(chalk.red(`Minting was not successful - Please make sure beneficiary address has been whitelisted`)); + console.log(chalk.red(`Issuance was not successful - Please make sure beneficiary address has been whitelisted`)); } } -async function multiMint(_csvFilePath, _batchSize) { +async function multiIssue(_csvFilePath, _batchSize) { let csvFilePath; if (typeof _csvFilePath !== 'undefined') { csvFilePath = _csvFilePath; @@ -340,13 +464,7 @@ async function multiMint(_csvFilePath, _batchSize) { if (typeof _batchSize !== 'undefined') { batchSize = _batchSize; } else { - batchSize = readlineSync.question(`Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, { - limit: function (input) { - return parseInt(input) > 0; - }, - limitMessage: 'Must be greater than 0', - defaultInput: gbl.constants.DEFAULT_BATCH_SIZE - }); + batchSize = input.readNumberGreaterThan(0, `Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, gbl.constants.DEFAULT_BATCH_SIZE); } let parsedData = csvParse(csvFilePath); let tokenDivisible = await securityToken.methods.granularity().call() == 1; @@ -363,7 +481,7 @@ async function multiMint(_csvFilePath, _batchSize) { for (const row of validData) { let investorAccount = row[0]; let tokenAmount = web3.utils.toWei(row[1].toString()); - let verifiedTransaction = await securityToken.methods.verifyTransfer(gbl.constants.ADDRESS_ZERO, investorAccount, tokenAmount, web3.utils.fromAscii('')).call(); + let verifiedTransaction = await securityToken.methods.canTransfer(investorAccount, tokenAmount, web3.utils.fromAscii('')).call(); if (verifiedTransaction) { verifiedData.push(row); } else { @@ -374,11 +492,11 @@ async function multiMint(_csvFilePath, _batchSize) { let batches = common.splitIntoBatches(verifiedData, batchSize); let [investorArray, amountArray] = common.transposeBatches(batches); for (let batch = 0; batch < batches.length; batch++) { - console.log(`Batch ${batch + 1} - Attempting to mint tokens to accounts: \n\n`, investorArray[batch], '\n'); + console.log(`Batch ${batch + 1} - Attempting to issue tokens to accounts: \n\n`, investorArray[batch], '\n'); amountArray[batch] = amountArray[batch].map(a => web3.utils.toWei(a.toString())); - let action = securityToken.methods.mintMulti(investorArray[batch], amountArray[batch]); + let action = securityToken.methods.issueMulti(investorArray[batch], amountArray[batch]); let receipt = await common.sendTransaction(action); - console.log(chalk.green('Multi mint transaction was successful.')); + console.log(chalk.green('Multi issue transaction was successful.')); console.log(`${receipt.gasUsed} gas used.Spent: ${web3.utils.fromWei((new web3.utils.BN(receipt.gasUsed)).mul(new web3.utils.BN(defaultGasPrice)))} ETH`); } @@ -454,7 +572,7 @@ async function listModuleOptions() { // Modules a actions async function addModule() { - let options = ['Permission Manager', 'Transfer Manager', 'Security Token Offering', 'Dividends', 'Burn']; + let options = ['Permission Manager', 'Transfer Manager', 'Security Token Offering', 'Dividends', 'Burn', 'Wallet']; let index = readlineSync.keyInSelect(options, 'What type of module would you like to add?', { cancel: 'Return' }); switch (options[index]) { case 'Permission Manager': @@ -481,11 +599,17 @@ async function addModule() { This option is not yet available. *********************************`)); break; + case 'Wallet': + console.log(chalk.red(` + ********************************* + This option is not yet available. + *********************************`)); + break; } } async function pauseModule(modules) { - let options = modules.map(m => `${m.name} (${m.version}) at ${m.address}`); + let options = modules.map(m => `${m.label}: ${m.name} (${m.version}) at ${m.address}`); let index = readlineSync.keyInSelect(options, 'Which module would you like to pause?'); if (index != -1) { console.log("\nSelected:", options[index]); @@ -508,7 +632,7 @@ async function pauseModule(modules) { } async function unpauseModule(modules) { - let options = modules.map(m => `${m.name} (${m.version}) at ${m.address}`); + let options = modules.map(m => `${m.label}: ${m.name} (${m.version}) at ${m.address}`); let index = readlineSync.keyInSelect(options, 'Which module would you like to pause?'); if (index != -1) { console.log("\nSelected: ", options[index]); @@ -531,7 +655,7 @@ async function unpauseModule(modules) { } async function archiveModule(modules) { - let options = modules.map(m => `${m.name} (${m.version}) at ${m.address}`); + let options = modules.map(m => `${m.label}: ${m.name} (${m.version}) at ${m.address}`); let index = readlineSync.keyInSelect(options, 'Which module would you like to archive?'); if (index != -1) { console.log("\nSelected: ", options[index]); @@ -542,7 +666,7 @@ async function archiveModule(modules) { } async function unarchiveModule(modules) { - let options = modules.map(m => `${m.name} (${m.version}) at ${m.address}`); + let options = modules.map(m => `${m.label}: ${m.name} (${m.version}) at ${m.address}`); let index = readlineSync.keyInSelect(options, 'Which module would you like to unarchive?'); if (index != -1) { console.log("\nSelected: ", options[index]); @@ -553,7 +677,7 @@ async function unarchiveModule(modules) { } async function removeModule(modules) { - let options = modules.map(m => `${m.name} (${m.version}) at ${m.address}`); + let options = modules.map(m => `${m.label}: ${m.name} (${m.version}) at ${m.address}`); let index = readlineSync.keyInSelect(options, 'Which module would you like to remove?'); if (index != -1) { console.log("\nSelected: ", options[index]); @@ -564,7 +688,7 @@ async function removeModule(modules) { } async function changeBudget(modules) { - let options = modules.map(m => `${m.name} (${m.version}) at ${m.address}`); + let options = modules.map(m => `${m.label}: ${m.name} (${m.version}) at ${m.address}`); let index = readlineSync.keyInSelect(options, 'Which module would you like to change budget for?'); if (index != -1) { console.log("\nSelected: ", options[index]); @@ -587,67 +711,23 @@ async function showUserInfo(_user) { } async function getAllModules() { - function ModuleInfo(_moduleType, _name, _address, _factoryAddress, _archived, _paused, _version) { - this.name = _name; - this.type = _moduleType; - this.address = _address; - this.factoryAddress = _factoryAddress; - this.archived = _archived; - this.paused = _paused; - this.version = _version; - } - - let modules = []; - + let allModules = []; // Iterate over all module types - for (let type = 1; type <= 5; type++) { - let allModules = await securityToken.methods.getModulesByType(type).call(); - - // Iterate over all modules of each type - for (let i = 0; i < allModules.length; i++) { - try { - let details = await securityToken.methods.getModule(allModules[i]).call(); - let nameTemp = web3.utils.hexToUtf8(details[0]); - let pausedTemp = null; - let factoryAbi = abis.moduleFactory(); - let factory = new web3.eth.Contract(factoryAbi, details[2]); - let versionTemp = await factory.methods.version().call(); - if (type == gbl.constants.MODULES_TYPES.STO || type == gbl.constants.MODULES_TYPES.TRANSFER || (type == gbl.constants.MODULES_TYPES.DIVIDENDS && versionTemp === '2.1.1')) { - let abiTemp = JSON.parse(require('fs').readFileSync(`${__dirname}/../../build/contracts/${nameTemp}.json`).toString()).abi; - let contractTemp = new web3.eth.Contract(abiTemp, details[1]); - pausedTemp = await contractTemp.methods.paused().call(); - } - - modules.push(new ModuleInfo(type, nameTemp, details[1], details[2], details[3], pausedTemp, versionTemp)); - } catch (error) { - console.log(error); - console.log(chalk.red(` - ************************* - Unable to iterate over module type - unexpected error - *************************`)); - } - } + for (let type = 1; type <= 8; type++) { + let modules = await common.getAllModulesByType(securityToken, type); + modules.forEach(m => allModules.push(m)); } - return modules; + return allModules; } async function initialize(_tokenSymbol) { welcome(); await setup(); - if (typeof _tokenSymbol === 'undefined') { - tokenSymbol = await selectToken(); - } else { - tokenSymbol = _tokenSymbol; - } - let securityTokenAddress = await securityTokenRegistry.methods.getSecurityTokenAddress(tokenSymbol).call(); - if (securityTokenAddress == '0x0000000000000000000000000000000000000000') { - console.log(chalk.red(`Selected Security Token ${tokenSymbol} does not exist.`)); + securityToken = await common.selectToken(securityTokenRegistry, _tokenSymbol); + if (securityToken === null) { process.exit(0); } - let securityTokenABI = abis.securityToken(); - securityToken = new web3.eth.Contract(securityTokenABI, securityTokenAddress); - securityToken.setProvider(web3.currentProvider); } function welcome() { @@ -659,43 +739,58 @@ function welcome() { console.log("Issuer Account: " + Issuer.address + "\n"); } -async function selectToken() { - let result = null; - - let userTokens = await securityTokenRegistry.methods.getTokensByOwner(Issuer.address).call(); - let tokenDataArray = await Promise.all(userTokens.map(async function (t) { - let tokenData = await securityTokenRegistry.methods.getSecurityTokenData(t).call(); - return { symbol: tokenData[0], address: t }; - })); - let options = tokenDataArray.map(function (t) { - return `${t.symbol} - Deployed at ${t.address}`; - }); - options.push('Enter token symbol manually'); - - let index = readlineSync.keyInSelect(options, 'Select a token:', { cancel: 'Exit' }); - let selected = index != -1 ? options[index] : 'Exit'; - switch (selected) { - case 'Enter token symbol manually': - result = readlineSync.question('Enter the token symbol: '); - break; - case 'Exit': - process.exit(); - break; - default: - result = tokenDataArray[index].symbol; - break; - } - - return result; -} - module.exports = { executeApp: async function (_tokenSymbol) { await initialize(_tokenSymbol); return executeApp(); }, - multiMint: async function (_tokenSymbol, _csvPath, _batchSize) { + multiIssue: async function (_tokenSymbol, _csvPath, _batchSize) { await initialize(_tokenSymbol); - return multiMint(_csvPath, _batchSize); + return multiIssue(_csvPath, _batchSize); } } + +async function getFreezeIssuanceAck(stAddress, from) { + const typedData = { + types: { + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' } + ], + Acknowledgment: [ + { name: 'text', type: 'string' } + ], + }, + primaryType: 'Acknowledgment', + domain: { + name: 'Polymath', + chainId: 1, + verifyingContract: stAddress + }, + message: { + text: 'I acknowledge that freezing Issuance is a permanent and irrevocable change', + }, + }; + const result = await new Promise((resolve, reject) => { + web3.currentProvider.send( + { + method: 'eth_signTypedData', + params: [from, typedData] + }, + (err, result) => { + if (err) { + return reject(err); + } + resolve(result.result); + } + ); + }); + // console.log('signed by', from); + // const recovered = sigUtil.recoverTypedSignature({ + // data: typedData, + // sig: result + // }) + // console.log('recovered address', recovered); + return result; +} diff --git a/CLI/commands/transfer.js b/CLI/commands/transfer.js index 5bd6d1859..b566cf99d 100644 --- a/CLI/commands/transfer.js +++ b/CLI/commands/transfer.js @@ -20,8 +20,8 @@ async function startScript(tokenSymbol, transferTo, transferAmount) { try { let securityTokenRegistryAddress = await contracts.securityTokenRegistry(); - let securityTokenRegistryABI = abis.securityTokenRegistry(); - securityTokenRegistry = new web3.eth.Contract(securityTokenRegistryABI, securityTokenRegistryAddress); + let iSecurityTokenRegistryABI = abis.iSecurityTokenRegistry(); + securityTokenRegistry = new web3.eth.Contract(iSecurityTokenRegistryABI, securityTokenRegistryAddress); securityTokenRegistry.setProvider(web3.currentProvider); transfer(); } catch (err) { @@ -45,14 +45,14 @@ async function transfer() { let transferAction = securityToken.methods.transfer(_transferTo,web3.utils.toWei(_transferAmount,"ether")); let receipt = await common.sendTransaction(transferAction); let event = common.getEventFromLogs(securityToken._jsonInterface, receipt.logs, 'Transfer'); - console.log(` + console.log('\x1b[32m%s\x1b[0m', ` Account ${event.from} transferred ${web3.utils.fromWei(event.value,"ether")} tokens to account ${event.to}` ); } catch (err){ console.log(err); - console.log("There was an error processing the transfer transaction. \n The most probable cause for this error is one of the involved accounts not being in the whitelist or under a lockup period.") + console.log('\x1b[31m%s\x1b[0m', "\nThere was an error processing the transfer transaction. \nThe most probable cause for this error is one of the involved accounts not being in the whitelist or under a lockup period."); return; } }; diff --git a/CLI/commands/transfer_manager.js b/CLI/commands/transfer_manager.js index 2c7bc46d2..586ab2e8a 100644 --- a/CLI/commands/transfer_manager.js +++ b/CLI/commands/transfer_manager.js @@ -6,11 +6,13 @@ const contracts = require('./helpers/contract_addresses'); const abis = require('./helpers/contract_abis'); const gbl = require('./common/global'); const csvParse = require('./helpers/csv'); +const input = require('./IO/input'); +const output = require('./IO/output'); const { table } = require('table'); -/////////////////// // Constants const WHITELIST_DATA_CSV = `${__dirname}/../data/Transfer/GTM/whitelist_data.csv`; +const FLAG_DATA_CSV = `${__dirname}/../data/Transfer/GTM/flag_data.csv`; const ADD_BLACKLIST_DATA_CSV = `${__dirname}/../data/Transfer/BlacklistTM/add_blacklist_data.csv`; const MODIFY_BLACKLIST_DATA_CSV = `${__dirname}/../data/Transfer/BlacklistTM/modify_blacklist_data.csv`; const DELETE_BLACKLIST_DATA_CSV = `${__dirname}/../data/Transfer/BlacklistTM/delete_blacklist_data.csv`; @@ -34,6 +36,7 @@ const REMOVE_LOCKUP_INVESTOR_DATA_CSV = `${__dirname}/../data/Transfer/LockupTM/ const RESTRICTION_TYPES = ['Fixed', 'Percentage']; +const MATM_MENU_VERIFY = 'Verify transfer'; const MATM_MENU_ADD = 'Add new manual approval'; const MATM_MENU_MANAGE = 'Manage existing approvals'; const MATM_MENU_EXPLORE = 'Explore account'; @@ -49,6 +52,7 @@ const MATM_MENU_OPERATE_REVOKE = 'Revoke multiple approvals in batch'; // App flow let tokenSymbol; let securityToken; +let polyToken; let securityTokenRegistry; let moduleRegistry; let currentTransferManager; @@ -56,20 +60,21 @@ let currentTransferManager; async function executeApp() { console.log('\n', chalk.blue('Transfer Manager - Main Menu', '\n')); - let tmModules = await getAllModulesByType(gbl.constants.MODULES_TYPES.TRANSFER); + let tmModules = await common.getAllModulesByType(securityToken, gbl.constants.MODULES_TYPES.TRANSFER); let nonArchivedModules = tmModules.filter(m => !m.archived); if (nonArchivedModules.length > 0) { console.log(`Transfer Manager modules attached:`); - nonArchivedModules.map(m => console.log(`- ${m.name} at ${m.address}`)) + nonArchivedModules.map(m => `${m.label}: ${m.name} (${m.version}) at ${m.address}`); } else { console.log(`There are no Transfer Manager modules attached`); } - let options = ['Verify transfer', 'Transfer']; + let options = ['Verify transfer', 'Transfer', 'Operator transfer']; let forcedTransferDisabled = await securityToken.methods.controllerDisabled().call(); if (!forcedTransferDisabled) { - options.push('Forced transfers'); + options.push('Controller transfers'); } + options.push('Manage operators'); if (nonArchivedModules.length > 0) { options.push('Config existing modules'); } @@ -80,45 +85,17 @@ async function executeApp() { console.log('Selected:', optionSelected, '\n'); switch (optionSelected) { case 'Verify transfer': - let verifyTotalSupply = web3.utils.fromWei(await securityToken.methods.totalSupply().call()); - await logTotalInvestors(); - let verifyTransferFrom = readlineSync.question(`Enter the sender account (${Issuer.address}): `, { - limit: function (input) { - return web3.utils.isAddress(input); - }, - limitMessage: "Must be a valid address", - defaultInput: Issuer.address - }); - await logBalance(verifyTransferFrom, verifyTotalSupply); - let verifyTransferTo = readlineSync.question('Enter the receiver account: ', { - limit: function (input) { - return web3.utils.isAddress(input); - }, - limitMessage: "Must be a valid address", - }); - await logBalance(verifyTransferTo, verifyTotalSupply); - let verifyTransferAmount = readlineSync.question('Enter amount of tokens to verify: '); - let isVerified = await securityToken.methods.verifyTransfer(verifyTransferFrom, verifyTransferTo, web3.utils.toWei(verifyTransferAmount), web3.utils.fromAscii("")).call(); - if (isVerified) { - console.log(chalk.green(`\n${verifyTransferAmount} ${tokenSymbol} can be transferred from ${verifyTransferFrom} to ${verifyTransferTo}!`)); - } else { - console.log(chalk.red(`\n${verifyTransferAmount} ${tokenSymbol} can't be transferred from ${verifyTransferFrom} to ${verifyTransferTo}!`)); - } + await canTransfer(); break; case 'Transfer': let totalSupply = web3.utils.fromWei(await securityToken.methods.totalSupply().call()); await logTotalInvestors(); await logBalance(Issuer.address, totalSupply); - let transferTo = readlineSync.question('Enter beneficiary of tranfer: ', { - limit: function (input) { - return web3.utils.isAddress(input); - }, - limitMessage: "Must be a valid address" - }); + let transferTo = input.readAddress('Enter beneficiary of tranfer: '); await logBalance(transferTo, totalSupply); let transferAmount = readlineSync.question('Enter amount of tokens to transfer: '); - let isTranferVerified = await securityToken.methods.verifyTransfer(Issuer.address, transferTo, web3.utils.toWei(transferAmount), web3.utils.fromAscii("")).call(); - if (isTranferVerified) { + let isTranferVerified = await securityToken.methods.canTransferFrom(Issuer.address, transferTo, web3.utils.toWei(transferAmount), web3.utils.fromAscii("")).call(); + if (isTranferVerified[0] !== gbl.constants.TRASFER_RESULT.INVALID) { let transferAction = securityToken.methods.transfer(transferTo, web3.utils.toWei(transferAmount)); let receipt = await common.sendTransaction(transferAction); let event = common.getEventFromLogs(securityToken._jsonInterface, receipt.logs, 'Transfer'); @@ -130,9 +107,15 @@ async function executeApp() { console.log(chalk.red(`Transfer failed at verification. Please review the transfer restrictions.`)); } break; - case 'Forced transfers': + case 'Operator transfer': + await operatorTransfer(); + break; + case 'Controller transfers': await forcedTransfers(); break; + case 'Manage operators': + await manageOperators(); + break; case 'Config existing modules': await configExistingModules(nonArchivedModules); break; @@ -146,71 +129,170 @@ async function executeApp() { await executeApp(); } +async function verifyTransfer(askAmount, askTo) { + let verifyTotalSupply = web3.utils.fromWei(await securityToken.methods.totalSupply().call()); + await logTotalInvestors(); + + let verifyTransferFrom = input.readAddress(`Enter the sender account (${Issuer.address}): `, Issuer.address); + await logBalance(verifyTransferFrom, verifyTotalSupply); + + let verifyTransferTo = gbl.constants.ADDRESS_ZERO; + if (askTo) { + verifyTransferTo = input.readAddress('Enter the receiver account: '); + await logBalance(verifyTransferTo, verifyTotalSupply); + } + + let verifyTransferAmount = askAmount ? input.readNumberGreaterThan(0, 'Enter amount of tokens to verify: ') : '0'; + + let verifyResult = await currentTransferManager.methods.verifyTransfer(verifyTransferFrom, verifyTransferTo, web3.utils.toWei(verifyTransferAmount), web3.utils.fromAscii("")).call(); + switch (verifyResult[0]) { + case gbl.constants.TRASFER_RESULT.INVALID: + console.log(chalk.red(`\nThis transfer is not valid for this module!`)); + break; + default: + console.log(chalk.green(`\nThis transfer is valid for this module!`)); + break; + } +} + +async function canTransfer() { + let verifyTotalSupply = web3.utils.fromWei(await securityToken.methods.totalSupply().call()); + await logTotalInvestors(); + let verifyTransferFrom = input.readAddress(`Enter the sender account (${Issuer.address}): `, Issuer.address); + await logBalance(verifyTransferFrom, verifyTotalSupply); + let verifyTransferTo = input.readAddress('Enter the receiver account: '); + await logBalance(verifyTransferTo, verifyTotalSupply); + let verifyTransferAmount = readlineSync.question('Enter amount of tokens to verify: '); + let isVerified; + if (verifyTransferFrom == Issuer.address) { + isVerified = await securityToken.methods.canTransfer(verifyTransferTo, web3.utils.toWei(verifyTransferAmount), web3.utils.fromAscii("")).call(); + } else { + isVerified = await securityToken.methods.canTransferFrom(verifyTransferFrom, verifyTransferTo, web3.utils.toWei(verifyTransferAmount), web3.utils.fromAscii("")).call(); + } + switch (isVerified.statusCode) { + case gbl.constants.TRANSFER_STATUS_CODES.TransferFailure: + console.log(chalk.red(`\n${verifyTransferAmount} ${tokenSymbol} can't be transferred from ${verifyTransferFrom} to ${verifyTransferTo}!`)); + if (web3.utils.hexToAscii(isVerified.reasonCode) !== '') { + const moduleData = await securityToken.methods.getModule(isVerified.reasonCode.substring(0, 42)).call(); + console.log(chalk.red(`The module ${web3.utils.hexToUtf8(moduleData.moduleLabel)} - ${web3.utils.hexToUtf8(moduleData.moduleName)} at ${moduleData.moduleAddress} didn't allow the transfer!`)); + } else { + console.log(chalk.red(`The transfer wasn't considered explicitly valid by any TMs!`)); + } + break; + case gbl.constants.TRANSFER_STATUS_CODES.TransferSuccess: + console.log(chalk.green(`\n${verifyTransferAmount} ${tokenSymbol} can be transferred from ${verifyTransferFrom} to ${verifyTransferTo}!`)); + break; + case gbl.constants.TRANSFER_STATUS_CODES.InsufficientBalance: + console.log(chalk.red(`\n${verifyTransferAmount} ${tokenSymbol} can't be transferred from ${verifyTransferFrom} to ${verifyTransferTo}!`)); + console.log(chalk.red(`Insufficient balance!`)); + break; + case gbl.constants.TRANSFER_STATUS_CODES.InsufficientAllowance: + console.log(chalk.red(`\nAddress ${Issuer.address} can't transfer ${verifyTransferAmount} ${tokenSymbol} on behalf of ${verifyTransferFrom}!`)); + console.log(chalk.red(`Insufficient allowance!`)); + break + case gbl.constants.TRANSFER_STATUS_CODES.TransfersHalted: + console.log(chalk.red(`\n${verifyTransferAmount} ${tokenSymbol} can't be transferred from ${verifyTransferFrom} to ${verifyTransferTo}!`)); + console.log(chalk.red(`Transfers are halted!`)); + break; + case gbl.constants.TRANSFER_STATUS_CODES.InvalidReceiver: + console.log(chalk.red(`\n${verifyTransferAmount} ${tokenSymbol} can't be transferred from ${verifyTransferFrom} to ${verifyTransferTo}!`)); + console.log(chalk.red(`Invalid receiver!`)); + break; + default: + console.log(chalk.red(`\n${verifyTransferAmount} ${tokenSymbol} can't be transferred from ${verifyTransferFrom} to ${verifyTransferTo}!`)); + break; + } +} + +async function operatorTransfer() { + const partition = 'UNLOCKED'; + const from = input.readAddress('Enter the address from which to take tokens: '); + const isOperator = await securityToken.methods.isOperator(Issuer.address, from).call(); + if (!isOperator) { + console.log(chalk.red(`You are not an authorized operator for ${from}`)); + } else { + await logBalance(from); + const to = input.readAddress('Enter address where to send tokens: '); + await logBalance(to); + const amount = input.readNumberLessThanOrEqual(parseFloat(fromBalance), 'Enter amount of tokens to transfer: '); + let isTranferVerified = await securityToken.methods.canTransferByPartition(from, to, web3.utils.asciiToHex(partition), web3.utils.toWei(amount), web3.utils.fromAscii("")).call(); + if (!isTranferVerified) { + console.log(chalk.red(`Transfer failed at verification. Please review the transfer restrictions.`)); + } else { + const data = ''; + const operatorData = readlineSync.question('Enter a message to attach to the transfer: '); + const action = securityToken.methods.operatorTransferByPartition( + web3.utils.asciiToHex(partition), + from, + to, + web3.utils.toWei(amount), + web3.utils.fromAscii(data), + web3.utils.fromAscii(operatorData) + ) + let receipt = await common.sendTransaction(action, { factor: 1.5 }); + let event = common.getEventFromLogs(securityToken._jsonInterface, receipt.logs, 'TransferByPartition'); + console.log(chalk.green(` ${event._operator} has successfully transferred ${web3.utils.fromWei(event._value)} ${tokenSymbol} +from ${event._from} to ${event._to} +Data: ${web3.utils.hexToAscii(event._data)} +Operator data: ${web3.utils.hexToAscii(event._operatorData)} + `)); + await logBalance(from); + await logBalance(to); + } + } +} + async function forcedTransfers() { let options = ['Disable controller', 'Set controller']; let controller = await securityToken.methods.controller().call(); if (controller == Issuer.address) { - options.push('Force Transfer'); + options.push('Controller Transfer'); } let index = readlineSync.keyInSelect(options, 'What do you want to do?', { cancel: 'RETURN' }); let optionSelected = index !== -1 ? options[index] : 'RETURN'; console.log('Selected:', optionSelected, '\n'); switch (optionSelected) { case 'Disable controller': - if (readlineSync.keyInYNStrict()) { - let disableControllerAction = securityToken.methods.disableController(); - await common.sendTransaction(disableControllerAction); - console.log(chalk.green(`Forced transfers have been disabled permanently`)); - } + console.log(chalk.yellow.bgRed.bold(`---- WARNING: THIS ACTION WILL PERMANENTLY DISABLE CONTROLLED TRANSFERS! ----`)); + let confirmation = readlineSync.question(`To confirm type "I acknowledge that disabling controller is a permanent and irrevocable change": `); + if (confirmation == "I acknowledge that disabling controller is a permanent and irrevocable change") { + let signature = await getDisableControllerAckSigner(securityToken.options.address, Issuer.address); + let disableControllerAction = securityToken.methods.disableController(signature); + await common.sendTransaction(disableControllerAction); + console.log(chalk.green(`Controller transfers have been disabled permanently`)); + return; + } break; case 'Set controller': - let controllerAddress = readlineSync.question(`Enter the address for the controller (${Issuer.address}): `, { - limit: function (input) { - return web3.utils.isAddress(input); - }, - limitMessage: "Must be a valid address", - defaultInput: Issuer.address - }); + let controller = await securityToken.methods.controller().call(); + if (controller == gbl.constants.ADDRESS_ZERO) { + console.log(`A controller address is not set`); + } else { + console.log(`Controller address: ${await securityToken.methods.controller().call()}`); + } + let controllerAddress = input.readAddress(`Enter the address for the controller (${Issuer.address}): `, Issuer.address); let setControllerAction = securityToken.methods.setController(controllerAddress); let setControllerReceipt = await common.sendTransaction(setControllerAction); let setControllerEvent = common.getEventFromLogs(securityToken._jsonInterface, setControllerReceipt.logs, 'SetController'); console.log(chalk.green(`New controller is ${setControllerEvent._newController}`)); break; - case 'Force Transfer': - let from = readlineSync.question('Enter the address from which to take tokens: ', { - limit: function (input) { - return web3.utils.isAddress(input); - }, - limitMessage: "Must be a valid address", - }); - let fromBalance = web3.utils.fromWei(await securityToken.methods.balanceOf(from).call()); - console.log(chalk.yellow(`Balance of ${from}: ${fromBalance} ${tokenSymbol}`)); - let to = readlineSync.question('Enter address where to send tokens: ', { - limit: function (input) { - return web3.utils.isAddress(input); - }, - limitMessage: "Must be a valid address", - }); - let toBalance = web3.utils.fromWei(await securityToken.methods.balanceOf(to).call()); - console.log(chalk.yellow(`Balance of ${to}: ${toBalance} ${tokenSymbol}`)); - let amount = readlineSync.question('Enter amount of tokens to transfer: ', { - limit: function (input) { - return parseInt(input) <= parseInt(fromBalance); - }, - limitMessage: `Amount must be less or equal than ${fromBalance} ${tokenSymbol}`, - }); - let data = '';//readlineSync.question('Enter the data to indicate validation: '); + case 'Controller Transfer': + let from = input.readAddress('Enter the address from which to take tokens: '); + await logBalance(from); + let to = input.readAddress('Enter address where to send tokens: '); + await logBalance(to); + let amount = input.readNumberLessThanOrEqual(parseFloat(fromBalance), 'Enter amount of tokens to transfer: '); + let data = ''; // readlineSync.question('Enter the data to indicate validation: '); let log = readlineSync.question('Enter a message to attach to the transfer (i.e. "Private key lost"): '); - let forceTransferAction = securityToken.methods.forceTransfer(from, to, web3.utils.toWei(amount), web3.utils.asciiToHex(data), web3.utils.asciiToHex(log)); + let forceTransferAction = securityToken.methods.controllerTransfer(from, to, web3.utils.toWei(amount), web3.utils.asciiToHex(data), web3.utils.asciiToHex(log)); let forceTransferReceipt = await common.sendTransaction(forceTransferAction, { factor: 1.5 }); - let forceTransferEvent = common.getEventFromLogs(securityToken._jsonInterface, forceTransferReceipt.logs, 'ForceTransfer'); - console.log(chalk.green(` ${forceTransferEvent._controller} has successfully forced a transfer of ${web3.utils.fromWei(forceTransferEvent._value)} ${tokenSymbol} - from ${forceTransferEvent._from} to ${forceTransferEvent._to} - Verified transfer: ${forceTransferEvent._verifyTransfer} - Data: ${web3.utils.hexToAscii(forceTransferEvent._data)} + let forceTransferEvent = common.getEventFromLogs(securityToken._jsonInterface, forceTransferReceipt.logs, 'ControllerTransfer'); + console.log(chalk.green(` ${forceTransferEvent._controller} has successfully forced a transfer of ${web3.utils.fromWei(forceTransferEvent._value)} ${tokenSymbol} + from ${forceTransferEvent._from} to ${forceTransferEvent._to} + Data: ${web3.utils.hexToAscii(forceTransferEvent._operatorData)} `)); - console.log(`Balance of ${from} after transfer: ${web3.utils.fromWei(await securityToken.methods.balanceOf(from).call())} ${tokenSymbol}`); - console.log(`Balance of ${to} after transfer: ${web3.utils.fromWei(await securityToken.methods.balanceOf(to).call())} ${tokenSymbol}`); + await logBalance(from); + await logBalance(to); break; case 'RETURN': return; @@ -219,8 +301,29 @@ async function forcedTransfers() { await forcedTransfers(); } +async function manageOperators() { + const options = ['Authorize operator', 'Revoke operator']; + const index = readlineSync.keyInSelect(options, 'What do you want to do? ', { cancel: 'RETURN' }); + const selected = index !== -1 ? options[index] : 'RETURN'; + console.log('Selected:', selected, '\n'); + switch (selected) { + case 'Authorize operator': + const operatorToAuth = input.readAddress(`Enter the address of the operator you want to authorize: `); + const authAction = securityToken.methods.authorizeOperator(operatorToAuth); + await common.sendTransaction(authAction); + console.log(chalk.green(`${operatorToAuth} has been authorized as operator successfully!`)); + break; + case 'Revoke operator': + const operatorToRevoke = input.readAddress(`Enter the address of the operator you want to revoke: `); + const revokeAction = securityToken.methods.revokeOperator(operatorToRevoke); + await common.sendTransaction(revokeAction); + console.log(chalk.green(`${operatorToRevoke} has been revoked as operator successfully!`)); + break; + } +} + async function configExistingModules(tmModules) { - let options = tmModules.map(m => `${m.name} at ${m.address}`); + let options = tmModules.map(m => `${m.label}: ${m.name} (${m.version}) at ${m.address}`); let index = readlineSync.keyInSelect(options, 'Which module do you want to config? ', { cancel: 'RETURN' }); console.log('Selected:', index !== -1 ? options[index] : 'RETURN', '\n'); let moduleNameSelected = index !== -1 ? tmModules[index].name : 'RETURN'; @@ -265,42 +368,42 @@ async function configExistingModules(tmModules) { } async function addTransferManagerModule() { - let availableModules = await moduleRegistry.methods.getModulesByTypeAndToken(gbl.constants.MODULES_TYPES.TRANSFER, securityToken.options.address).call(); - let options = await Promise.all(availableModules.map(async function (m) { - let moduleFactoryABI = abis.moduleFactory(); - let moduleFactory = new web3.eth.Contract(moduleFactoryABI, m); - return web3.utils.hexToUtf8(await moduleFactory.methods.name().call()); - })); + let moduleList = await common.getAvailableModules(moduleRegistry, gbl.constants.MODULES_TYPES.TRANSFER, securityToken.options.address); + let options = moduleList.map(m => `${m.name} - ${m.version} (${m.factoryAddress})`); let index = readlineSync.keyInSelect(options, 'Which Transfer Manager module do you want to add? ', { cancel: 'RETURN' }); - if (index != -1 && readlineSync.keyInYNStrict(`Are you sure you want to add ${options[index]} module?`)) { - let bytes = web3.utils.fromAscii('', 16); - switch (options[index]) { + if (index != -1 && readlineSync.keyInYNStrict(`Are you sure you want to add ${moduleList[index].name} module?`)) { + let getInitializeData; + let moduleAbi; + switch (moduleList[index].name) { case 'CountTransferManager': - let maxHolderCount = readlineSync.question('Enter the maximum no. of holders the SecurityToken is allowed to have: '); - let configureCountTM = abis.countTransferManager().find(o => o.name === 'configure' && o.type === 'function'); - bytes = web3.eth.abi.encodeFunctionCall(configureCountTM, [maxHolderCount]); + moduleAbi = abis.countTransferManager(); + getInitializeData = getCountTMInitializeData; break; case 'PercentageTransferManager': - let maxHolderPercentage = toWeiPercentage(readlineSync.question('Enter the maximum amount of tokens in percentage that an investor can hold: ', { - limit: function (input) { - return (parseInt(input) > 0 && parseInt(input) <= 100); - }, - limitMessage: "Must be greater than 0 and less than 100" - })); - let allowPercentagePrimaryIssuance = readlineSync.keyInYNStrict(`Do you want to ignore transactions which are part of the primary issuance? `); - let configurePercentageTM = abis.percentageTransferManager().find(o => o.name === 'configure' && o.type === 'function'); - bytes = web3.eth.abi.encodeFunctionCall(configurePercentageTM, [maxHolderPercentage, allowPercentagePrimaryIssuance]); + moduleAbi = abis.percentageTransferManager(); + getInitializeData = getPercentageTMInitializeData; break; } - let selectedTMFactoryAddress = await contracts.getModuleFactoryAddressByName(securityToken.options.address, gbl.constants.MODULES_TYPES.TRANSFER, options[index]); - let addModuleAction = securityToken.methods.addModule(selectedTMFactoryAddress, bytes, 0, 0); - let receipt = await common.sendTransaction(addModuleAction); - let event = common.getEventFromLogs(securityToken._jsonInterface, receipt.logs, 'ModuleAdded'); - console.log(chalk.green(`Module deployed at address: ${event._module}`)); + await common.addModule(securityToken, polyToken, moduleList[index].factoryAddress, moduleAbi, getInitializeData); } } +function getPercentageTMInitializeData(moduleAbi) { + const maxHolderPercentage = toWeiPercentage(input.readPercentage('Enter the maximum amount of tokens in percentage that an investor can hold')); + const allowPercentagePrimaryIssuance = readlineSync.keyInYNStrict(`Do you want to ignore transactions which are part of the primary issuance? `); + const configurePercentageTM = moduleAbi.find(o => o.name === 'configure' && o.type === 'function'); + const bytes = web3.eth.abi.encodeFunctionCall(configurePercentageTM, [maxHolderPercentage, allowPercentagePrimaryIssuance]); + return bytes +} + +function getCountTMInitializeData(moduleABI) { + const maxHolderCount = readlineSync.question('Enter the maximum no. of holders the SecurityToken is allowed to have: '); + const configureCountTM = moduleABI.find(o => o.name === 'configure' && o.type === 'function'); + const bytes = web3.eth.abi.encodeFunctionCall(configureCountTM, [maxHolderCount]); + return bytes; +} + async function generalTransferManager() { console.log('\n', chalk.blue(`General Transfer Manager at ${currentTransferManager.options.address}`), '\n'); @@ -311,30 +414,20 @@ async function generalTransferManager() { // Show current data let displayIssuanceAddress = await currentTransferManager.methods.issuanceAddress().call(); - let displaySigningAddress = await currentTransferManager.methods.signingAddress().call(); - let displayAllowAllTransfers = await currentTransferManager.methods.allowAllTransfers().call(); - let displayAllowAllWhitelistTransfers = await currentTransferManager.methods.allowAllWhitelistTransfers().call(); - let displayAllowAllWhitelistIssuances = await currentTransferManager.methods.allowAllWhitelistIssuances().call(); - let displayAllowAllBurnTransfers = await currentTransferManager.methods.allowAllBurnTransfers().call(); let displayDefaults; let displayInvestors; if (moduleVersion != '1.0.0') { displayDefaults = await currentTransferManager.methods.defaults().call(); - displayInvestors = await currentTransferManager.methods.getInvestors().call(); - } - console.log(`- Issuance address: ${displayIssuanceAddress}`); - console.log(`- Signing address: ${displaySigningAddress}`); - console.log(`- Allow all transfers: ${displayAllowAllTransfers ? `YES` : `NO`}`); - console.log(`- Allow all whitelist transfers: ${displayAllowAllWhitelistTransfers ? `YES` : `NO`}`); - console.log(`- Allow all whitelist issuances: ${displayAllowAllWhitelistIssuances ? `YES` : `NO`}`); - console.log(`- Allow all burn transfers: ${displayAllowAllBurnTransfers ? `YES` : `NO`}`); + displayInvestors = await currentTransferManager.methods.getAllInvestors().call(); + } + console.log(`- Issuance address: ${displayIssuanceAddress}`); if (displayDefaults) { console.log(`- Default times:`); - console.log(` - From time: ${displayDefaults.fromTime} (${moment.unix(displayDefaults.fromTime).format('MMMM Do YYYY, HH:mm:ss')})`); - console.log(` - To time: ${displayDefaults.toTime} (${moment.unix(displayDefaults.toTime).format('MMMM Do YYYY, HH:mm:ss')})`); + console.log(` - Can transfer after: ${displayDefaults.canSendAfter} (${moment.unix(displayDefaults.canSendAfter).format('MMMM Do YYYY, HH:mm:ss')})`); + console.log(` - Can receive after: ${displayDefaults.canReceiveAfter} (${moment.unix(displayDefaults.canReceiveAfter).format('MMMM Do YYYY, HH:mm:ss')})`); } if (displayInvestors) { - console.log(`- Investors: ${displayInvestors.length}`); + console.log(`- Number of investors: ${displayInvestors.length}`); } // ------------------ @@ -342,33 +435,19 @@ async function generalTransferManager() { if (displayInvestors && displayInvestors.length > 0) { options.push(`Show investors`, `Show whitelist data`); } - options.push('Modify whitelist', 'Modify whitelist from CSV') /*'Modify Whitelist Signed',*/ + options.push( + 'Verify transfer', + 'Modify whitelist', + 'Modify whitelist from CSV', + 'Show investor flags', + 'Show all investors flags', + 'Modify investor flag', + 'Modify investor flags from CSV' + ); /*'Modify Whitelist Signed',*/ if (displayDefaults) { options.push('Change the default times used when they are zero'); } - options.push(`Change issuance address`, 'Change signing address'); - - if (displayAllowAllTransfers) { - options.push('Disallow all transfers'); - } else { - options.push('Allow all transfers'); - } - if (displayAllowAllWhitelistTransfers) { - options.push('Disallow all whitelist transfers'); - } else { - options.push('Allow all whitelist transfers'); - } - if (displayAllowAllWhitelistIssuances) { - options.push('Disallow all whitelist issuances'); - } else { - options.push('Allow all whitelist issuances'); - } - if (displayAllowAllBurnTransfers) { - options.push('Disallow all burn transfers'); - } else { - options.push('Allow all burn transfers'); - } - + options.push(`Change issuance address`, `Display/Modify Transfer Requirements`); let index = readlineSync.keyInSelect(options, 'What do you want to do?', { cancel: 'RETURN' }); let optionSelected = index !== -1 ? options[index] : 'RETURN'; console.log('Selected:', optionSelected, '\n'); @@ -378,21 +457,19 @@ async function generalTransferManager() { displayInvestors.map(i => console.log(i)); break; case `Show whitelist data`: - let investorsToShow = readlineSync.question(`Enter the addresses of the investors you want to show (i.e: addr1,addr2,addr3) or leave empty to show them all: `, { - limit: function (input) { - return input === '' || input.split(",").every(a => web3.utils.isAddress(a)); - }, - limitMessage: `All addresses must be valid` - }); + let investorsToShow = input.readMultipleAddresses(`Enter the addresses of the investors you want to show (i.e: addr1,addr2,addr3) or leave empty to show them all: `); if (investorsToShow === '') { - let whitelistData = await currentTransferManager.methods.getAllInvestorsData().call(); - showWhitelistTable(whitelistData[0], whitelistData[1], whitelistData[2], whitelistData[3], whitelistData[4]); + let whitelistData = await currentTransferManager.methods.getAllKYCData().call(); + showWhitelistTable(whitelistData[0], whitelistData[1], whitelistData[2], whitelistData[3]); } else { let investorsArray = investorsToShow.split(','); - let whitelistData = await currentTransferManager.methods.getInvestorsData(investorsArray).call(); - showWhitelistTable(investorsArray, whitelistData[0], whitelistData[1], whitelistData[2], whitelistData[3]); + let whitelistData = await currentTransferManager.methods.getKYCData(investorsArray).call(); + showWhitelistTable(investorsArray, whitelistData[0], whitelistData[1], whitelistData[2]); } break; + case 'Verify transfer': + await verifyTransfer(false, true); + break; case 'Change the default times used when they are zero': let fromTimeDefault = readlineSync.questionInt(`Enter the default time (Unix Epoch time) used when fromTime is zero: `); let toTimeDefault = readlineSync.questionInt(`Enter the default time (Unix Epoch time) used when toTime is zero: `); @@ -407,14 +484,52 @@ async function generalTransferManager() { case 'Modify whitelist from CSV': await modifyWhitelistInBatch(); break; + case 'Show investor flags': + await showInvestorFlags(); + break; + case 'Show all investors flags': + await showAllInvestorFlags(); + break; + + case 'Modify investor flag': + let investorAddress = input.readAddress("Enter the investor's address: "); + let options = []; + options.push( + "Is Accredited", + "Cannot Buy From STO's", + "Is Volume Restricted", + "Custom Flag" + ); + let index = readlineSync.keyInSelect(options, 'Select the flag you wish to set: ', { cancel: false }); + let optionSelected = index !== -1 ? options[index] : 'RETURN'; + console.log('Selected:', optionSelected, '\n'); + let flag + switch (optionSelected) { + case "Is Accredited": + flag = 0; + break; + case "Cannot Buy From STO's": + flag = 1; + break; + case "Is Volume Restricted": + flag = 2; + break; + case "Custom Flag": + flag = parseInt(input.readNumberBetween(3, 255, "Enter the number of the flag you wish to change: ")); + break; + case "RETURN": + await generalTransferManager(); + }; + let value = readlineSync.keyInYNStrict("Should the flag be set (y) or cleared (n): "); + let modifyInvestorFlagAction = currentTransferManager.methods.modifyInvestorFlag(investorAddress, flag, value); + let modifyInvestorFlagReceipt = await common.sendTransaction(modifyInvestorFlagAction); + break; + case 'Modify investor flags from CSV': + await modifyFlagsInBatch(); + break; /* case 'Modify Whitelist Signed': - let investorSigned = readlineSync.question('Enter the address to whitelist: ', { - limit: function(input) { - return web3.utils.isAddress(input); - }, - limitMessage: "Must be a valid address" - }); + let investorSigned = input.readAddress('Enter the address to whitelist: '); let fromTimeSigned = readlineSync.questionInt('Enter the time (Unix Epoch time) when the sale lockup period ends and the investor can freely sell his tokens: '); let toTimeSigned = readlineSync.questionInt('Enter the time (Unix Epoch time) when the purchase lockup period ends and the investor can freely purchase tokens from others: '); let expiryTimeSigned = readlineSync.questionInt('Enter the time till investors KYC will be validated (after that investor need to do re-KYC): '); @@ -429,89 +544,192 @@ async function generalTransferManager() { break; */ case 'Change issuance address': - let issuanceAddress = readlineSync.question('Enter the new issuance address: ', { - limit: function (input) { - return web3.utils.isAddress(input); - }, - limitMessage: "Must be a valid address" - }); + let issuanceAddress = input.readAddress('Enter the new issuance address: '); let changeIssuanceAddressAction = currentTransferManager.methods.changeIssuanceAddress(issuanceAddress); let changeIssuanceAddressReceipt = await common.sendTransaction(changeIssuanceAddressAction); let changeIssuanceAddressEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, changeIssuanceAddressReceipt.logs, 'ChangeIssuanceAddress'); console.log(chalk.green(`${changeIssuanceAddressEvent._issuanceAddress} is the new address for the issuance!`)); break; - case 'Change signing address': - let signingAddress = readlineSync.question('Enter the new signing address: ', { - limit: function (input) { - return web3.utils.isAddress(input); - }, - limitMessage: "Must be a valid address" - }); - let changeSigningAddressAction = currentTransferManager.methods.changeSigningAddress(signingAddress); - let changeSigningAddressReceipt = await common.sendTransaction(changeSigningAddressAction); - let changeSigningAddressEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, changeSigningAddressReceipt.logs, 'ChangeSigningAddress'); - console.log(chalk.green(`${changeSigningAddressEvent._signingAddress} is the new address for the signing!`)); - break; - case 'Allow all transfers': - case 'Disallow all transfers': - let changeAllowAllTransfersAction = currentTransferManager.methods.changeAllowAllTransfers(!displayAllowAllTransfers); - let changeAllowAllTransfersReceipt = await common.sendTransaction(changeAllowAllTransfersAction); - let changeAllowAllTransfersEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, changeAllowAllTransfersReceipt.logs, 'AllowAllTransfers'); - if (changeAllowAllTransfersEvent._allowAllTransfers) { - console.log(chalk.green(`All transfers are allowed!`)); - } else { - console.log(chalk.green(`Transfers are restricted!`)); - } + case 'Display/Modify Transfer Requirements': + await transferRequirements(); break; - case 'Allow all whitelist transfers': - case 'Disallow all whitelist transfers': - let changeAllowAllWhitelistTransfersAction = currentTransferManager.methods.changeAllowAllWhitelistTransfers(!displayAllowAllWhitelistTransfers); - let changeAllowAllWhitelistTransfersReceipt = await common.sendTransaction(changeAllowAllWhitelistTransfersAction); - let changeAllowAllWhitelistTransfersEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, changeAllowAllWhitelistTransfersReceipt.logs, 'AllowAllWhitelistTransfers'); - if (changeAllowAllWhitelistTransfersEvent._allowAllWhitelistTransfers) { - console.log(chalk.green(`Time locks from whitelist are ignored for transfers!`)); - } else { - console.log(chalk.green(`Transfers are restricted by time locks from whitelist!`)); - } - break; - case 'Allow all whitelist issuances': - case 'Disallow all whitelist issuances': - let changeAllowAllWhitelistIssuancesAction = currentTransferManager.methods.changeAllowAllWhitelistIssuances(!displayAllowAllWhitelistIssuances); - let changeAllowAllWhitelistIssuancesReceipt = await common.sendTransaction(changeAllowAllWhitelistIssuancesAction); - let changeAllowAllWhitelistIssuancesEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, changeAllowAllWhitelistIssuancesReceipt.logs, 'AllowAllWhitelistIssuances'); - if (changeAllowAllWhitelistIssuancesEvent._allowAllWhitelistIssuances) { - console.log(chalk.green(`Time locks from whitelist are ignored for issuances!`)); - } else { - console.log(chalk.green(`Issuances are restricted by time locks from whitelist!`)); - } - break; - case 'Allow all burn transfers': - case 'Disallow all burn transfers': - let changeAllowAllBurnTransfersAction = currentTransferManager.methods.changeAllowAllBurnTransfers(!displayAllowAllBurnTransfers); - let changeAllowAllBurnTransfersReceipt = await common.sendTransaction(changeAllowAllBurnTransfersAction); - let changeAllowAllBurnTransfersEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, changeAllowAllBurnTransfersReceipt.logs, 'AllowAllBurnTransfers'); - if (changeAllowAllBurnTransfersEvent._allowAllWhitelistTransfers) { - console.log(chalk.green(`To burn tokens is allowed!`)); - } else { - console.log(chalk.green(`The burning mechanism is deactivated!`)); - } + case 'RETURN': + return; + } + await generalTransferManager(); +} + +async function showInvestorFlags() { + let investor = input.readAddress("Enter the investor's address: "); + let investorFlags = new web3.utils.BN(await currentTransferManager.methods.getInvestorFlags(investor).call()); + console.log(chalk.green(`\nList of flags set for address ${investor}:`)); + let flagNames = + [ + "Is Accredited", + "Cannot Buy From STO's", + "Is Volume Restricted" + ]; + let flag; + for (let i = 0; i < 256; i++) { + // Test individual bits (flags) with BN utils + let value = investorFlags.testn(i); + let name = i < flagNames.length ? flagNames[i] : "Undefined"; + if (value) console.log(" Flag " + i + " is set - " + name); + } +} + +async function showAllInvestorFlags() { + // Rough draft WIP + let allInvestorFlagData = await currentTransferManager.methods.getAllInvestorFlags().call(); + let investorsArray = allInvestorFlagData.investors; + let flagsArray = allInvestorFlagData.flags; + let flagDataTable = [['Investor Address', 'Active Flags']]; + for (let i = 0; i < investorsArray.length; i++) { + let flags = new web3.utils.BN(flagsArray[i]); + let flagNumbers = []; + // Test flags and add set flags to array + for (let j = 0; j < 256; j++) { + if (flags.testn(j)) flagNumbers.push(j); + } + flagDataTable.push([ + investorsArray[i], + flagNumbers + ]); + } + console.log(table(flagDataTable)); +} + +async function transferRequirements() { + let displayGeneralTransRequirements = await currentTransferManager.methods.transferRequirements(0).call(); + let displayIssuanceTransRequirements = await currentTransferManager.methods.transferRequirements(1).call(); + let displayRedemptionTransRequirements = await currentTransferManager.methods.transferRequirements(2).call(); + let txReqTable = [['Transfer Type', 'Valid Sender KYC', 'Valid Receiver KYC', 'Transfer Date Restriction', 'Receive Date Restriction']]; + txReqTable.push([ + "General ", + displayGeneralTransRequirements[0], + displayGeneralTransRequirements[1], + displayGeneralTransRequirements[2], + displayGeneralTransRequirements[3] + ]); + txReqTable.push([ + "Issuance", + displayIssuanceTransRequirements[0], + displayIssuanceTransRequirements[1], + displayIssuanceTransRequirements[2], + displayIssuanceTransRequirements[3] + ]); + txReqTable.push([ + "Redemption", + displayRedemptionTransRequirements[0], + displayRedemptionTransRequirements[1], + displayRedemptionTransRequirements[2], + displayRedemptionTransRequirements[3] + ]); + console.log("Transfer Requirements:"); + let tableConfig = {columnDefault: {width: 13, wrapWord: true}}; + console.log(table(txReqTable, tableConfig)); + let options = []; + options.push( + `Modify General Transfer Requirements`, + `Modify Issuance Transfer Requirements`, + `Modify Redemption Transfer Requirements`, + `Modify All Transfer Requirements`, + `Reset All Transfer Requirements to Defaults` + ); + let index = readlineSync.keyInSelect(options, 'What do you want to do?', { cancel: 'RETURN' }); + let optionSelected = index !== -1 ? options[index] : 'RETURN'; + console.log('Selected:', optionSelected, '\n'); + let transferTypesArray = [], fromValidKYCArray = [], toValidKYCArray = [], fromRestrictedArray = [], toRestrictedArray = []; + switch (optionSelected) { + case `Modify General Transfer Requirements`: + await modifyTransferRequirements(0) + break; + case `Modify Issuance Transfer Requirements`: + await modifyTransferRequirements(1) + break; + case `Modify Redemption Transfer Requirements`: + await modifyTransferRequirements(2) + break; + case `Modify All Transfer Requirements`: + transferTypesArray = [0, 1, 2]; + console.log("Set General Transfer Requirements:"); + fromValidKYCArray[0] = readlineSync.keyInYNStrict('Should the sender require valid KYC?'); + toValidKYCArray[0] = readlineSync.keyInYNStrict('Should the recipient require valid KYC?'); + fromRestrictedArray[0] = readlineSync.keyInYNStrict('Should the sender be restricted by a can transfer date?'); + toRestrictedArray[0] = readlineSync.keyInYNStrict('Should the recipient be restricted by a can receive date?'); + console.log("\nSet Issuance Transfer Requirements:"); + fromValidKYCArray[1] = readlineSync.keyInYNStrict('Should the issuance address require valid KYC?'); + toValidKYCArray[1] = readlineSync.keyInYNStrict('Should the recipient require valid KYC?'); + fromRestrictedArray[1] = readlineSync.keyInYNStrict('Should the issuance address be restricted by a can transfer date?'); + toRestrictedArray[1] = readlineSync.keyInYNStrict('Should the recipient be restricted by a can receive date?'); + console.log("\nSet Redemption Transfer Requirements:"); + fromValidKYCArray[2] = readlineSync.keyInYNStrict('Should the sender require valid KYC?'); + toValidKYCArray[2] = readlineSync.keyInYNStrict('Should the redemption address require valid KYC?'); + fromRestrictedArray[2] = readlineSync.keyInYNStrict('Should the sender be restricted by a can transfer date?'); + toRestrictedArray[2] = readlineSync.keyInYNStrict('Should the redemption address be restricted by a can receive date?'); + await modifyAllTransferRequirements(transferTypesArray, fromValidKYCArray, toValidKYCArray, fromRestrictedArray, toRestrictedArray) + break; + case `Reset All Transfer Requirements to Defaults`: + transferTypesArray = [0, 1, 2]; + fromValidKYCArray = [true, false, true]; + toValidKYCArray = [true, true, false]; + fromRestrictedArray = [true, false, false]; + toRestrictedArray = [true, false, false]; + await modifyAllTransferRequirements(transferTypesArray, fromValidKYCArray, toValidKYCArray, fromRestrictedArray, toRestrictedArray) break; case 'RETURN': return; } + await transferRequirements(); +} - await generalTransferManager(); +async function modifyTransferRequirements(transferType) { + let fromValidKYC = readlineSync.keyInYNStrict('Should the sender require valid KYC?'); + let toValidKYC = readlineSync.keyInYNStrict('Should the recipient require valid KYC?'); + let fromRestricted = readlineSync.keyInYNStrict('Should the sender be restricted by a can transfer date?'); + let toRestricted = readlineSync.keyInYNStrict('Should the recipient be restricted by a can receive date?'); + let modifyTransferRequirementsAction = currentTransferManager.methods.modifyTransferRequirements(transferType, fromValidKYC, toValidKYC, fromRestricted, toRestricted); + let receipt = await common.sendTransaction(modifyTransferRequirementsAction); + console.log(chalk.green(" Transfer Requirements sucessfully modified")); } -function showWhitelistTable(investorsArray, fromTimeArray, toTimeArray, expiryTimeArray, canBuyFromSTOArray) { - let dataTable = [['Investor', 'From time', 'To time', 'KYC expiry date', 'Restricted']]; +async function modifyAllTransferRequirements(transferType, fromValidKYC, toValidKYC, fromRestricted, toRestricted) { + let modifyAllTransferRequirementsAction = currentTransferManager.methods.modifyTransferRequirementsMulti(transferType, fromValidKYC, toValidKYC, fromRestricted, toRestricted); + let receipt = await common.sendTransaction(modifyAllTransferRequirementsAction); + console.log(chalk.green(" Transfer Requirements sucessfully modified")); +} + +function showWhitelistTable(investorsArray, canSendAfterArray, canReceiveAfterArray, expiryTimeArray) { + let dataTable = [['Investor Address', 'Can Transfer After', 'Can Receive After', 'KYC Expiry Date']]; + let canSendAfter; + let canReceiveAfter; + let expiryTime; for (let i = 0; i < investorsArray.length; i++) { + if (canSendAfterArray[i] == 0) { + canSendAfter = chalk.yellow.bold(" DEFAULT"); + } else { + canSendAfter = canSendAfterArray[i] >= Date.now() / 1000 + ? chalk.red.bold(moment.unix(canSendAfterArray[i]).format('MM/DD/YYYY HH:mm')) + : moment.unix(canSendAfterArray[i]).format('MM/DD/YYYY HH:mm'); + } + + if (canReceiveAfterArray[i] == 0) { + canReceiveAfter = chalk.yellow.bold(" DEFAULT"); + } else { + canReceiveAfter = (canReceiveAfterArray[i] >= Date.now() / 1000) + ? chalk.red.bold(moment.unix(canReceiveAfterArray[i]).format('MM/DD/YYYY HH:mm')) + : moment.unix(canReceiveAfterArray[i]).format('MM/DD/YYYY HH:mm'); + } + + expiryTime = (expiryTimeArray[i] <= Date.now() / 1000 + ? chalk.red.bold(moment.unix(expiryTimeArray[i]).format('MM/DD/YYYY HH:mm')) + : moment.unix(expiryTimeArray[i]).format('MM/DD/YYYY HH:mm')); + dataTable.push([ investorsArray[i], - moment.unix(fromTimeArray[i]).format('MM/DD/YYYY HH:mm'), - moment.unix(toTimeArray[i]).format('MM/DD/YYYY HH:mm'), - moment.unix(expiryTimeArray[i]).format('MM/DD/YYYY HH:mm'), - canBuyFromSTOArray[i] ? 'YES' : 'NO' + canSendAfter, + canReceiveAfter, + expiryTime ]); } console.log(); @@ -529,13 +747,7 @@ async function modifyWhitelistInBatch(_csvFilePath, _batchSize) { } let batchSize; if (typeof _batchSize === 'undefined') { - batchSize = readlineSync.question(`Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, { - limit: function (input) { - return parseInt(input) > 0; - }, - limitMessage: 'Must be greater than 0', - defaultInput: gbl.constants.DEFAULT_BATCH_SIZE - }); + batchSize = input.readNumberGreaterThan(0, `Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, gbl.constants.DEFAULT_BATCH_SIZE); } else { batchSize = _batchSize; } @@ -544,24 +756,64 @@ async function modifyWhitelistInBatch(_csvFilePath, _batchSize) { web3.utils.isAddress(row[0]) && moment.unix(row[1]).isValid() && moment.unix(row[2]).isValid() && - moment.unix(row[3]).isValid() && - typeof row[4] === 'boolean' + moment.unix(row[3]).isValid() ); let invalidRows = parsedData.filter(row => !validData.includes(row)); if (invalidRows.length > 0) { console.log(chalk.red(`The following lines from csv file are not valid: ${invalidRows.map(r => parsedData.indexOf(r) + 1).join(',')} `)); } let batches = common.splitIntoBatches(validData, batchSize); - let [investorArray, fromTimesArray, toTimesArray, expiryTimeArray, canBuyFromSTOArray] = common.transposeBatches(batches); + let [investorArray, canSendAfterArray, canReceiveAfterArray, expiryTimeArray] = common.transposeBatches(batches); for (let batch = 0; batch < batches.length; batch++) { console.log(`Batch ${batch + 1} - Attempting to modify whitelist to accounts: \n\n`, investorArray[batch], '\n'); - let action = currentTransferManager.methods.modifyWhitelistMulti(investorArray[batch], fromTimesArray[batch], toTimesArray[batch], expiryTimeArray[batch], canBuyFromSTOArray[batch]); + let action = currentTransferManager.methods.modifyKYCDataMulti(investorArray[batch], canSendAfterArray[batch], canReceiveAfterArray[batch], expiryTimeArray[batch]); let receipt = await common.sendTransaction(action); console.log(chalk.green('Modify whitelist transaction was successful.')); console.log(`${receipt.gasUsed} gas used.Spent: ${web3.utils.fromWei((new web3.utils.BN(receipt.gasUsed)).mul(new web3.utils.BN(defaultGasPrice)))} ETH`); } } +async function modifyFlagsInBatch(_csvFilePath, _batchSize) { + let csvFilePath; + if (typeof _csvFilePath === 'undefined') { + csvFilePath = readlineSync.question(`Enter the path for csv data file (${FLAG_DATA_CSV}): `, { + defaultInput: FLAG_DATA_CSV + }); + } else { + csvFilePath = _csvFilePath; + } + let batchSize; + if (typeof _batchSize === 'undefined') { + batchSize = input.readNumberGreaterThan(0, `Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, gbl.constants.DEFAULT_BATCH_SIZE); + } else { + batchSize = _batchSize; + } + let parsedData = csvParse(csvFilePath); + let validData = parsedData.filter(row => + web3.utils.isAddress(row[0]) && + typeof row[1] === "number" && + Number.isInteger(row[1]) && + row[1] >= 0 && + row[1] < 256 && + typeof row[2] === "boolean" + ); + let invalidRows = parsedData.filter(row => !validData.includes(row)); + if (invalidRows.length > 0) { + console.log(chalk.red(`The following lines from csv file are not valid: ${invalidRows.map(r => parsedData.indexOf(r) + 1).join(',')} `)); + let processValid = readlineSync.keyInYNStrict(chalk.yellow("Do you want to process valid rows?")); + if (!processValid) return; + } + let batches = common.splitIntoBatches(validData, batchSize); + let [investorArray, flagArray, flagValueArray] = common.transposeBatches(batches); + for (let batch = 0; batch < batches.length; batch++) { + console.log(`Batch ${batch + 1} - Attempting to modify flags to accounts: \n\n`, investorArray[batch], '\n'); + let action = currentTransferManager.methods.modifyInvestorFlagMulti(investorArray[batch], flagArray[batch], flagValueArray[batch]); + let receipt = await common.sendTransaction(action); + console.log(chalk.green('Modify investor flags transaction was successful.')); + console.log(`${receipt.gasUsed} gas used.Spent: ${web3.utils.fromWei((new web3.utils.BN(receipt.gasUsed)).mul(new web3.utils.BN(defaultGasPrice)))} ETH`); + } +} + async function manualApprovalTransferManager() { console.log('\n', chalk.blue(`Manual Approval Transfer Manager at ${currentTransferManager.options.address} `), '\n'); @@ -569,6 +821,7 @@ async function manualApprovalTransferManager() { console.log(`- Current active approvals: ${totalApprovals}`); let matmOptions = [ + MATM_MENU_VERIFY, MATM_MENU_ADD, MATM_MENU_MANAGE, MATM_MENU_EXPLORE, @@ -582,6 +835,9 @@ async function manualApprovalTransferManager() { console.log('Selected:', optionSelected, '\n'); switch (optionSelected) { + case MATM_MENU_VERIFY: + await verifyTransfer(true, true); + break; case MATM_MENU_ADD: await matmAdd(); break; @@ -602,25 +858,10 @@ async function manualApprovalTransferManager() { } async function matmAdd() { - let from = readlineSync.question('Enter the address from which transfers will be approved: ', { - limit: function (input) { - return web3.utils.isAddress(input); - }, - limitMessage: "Must be a valid address" - }); - let to = readlineSync.question('Enter the address to which transfers will be approved: ', { - limit: function (input) { - return web3.utils.isAddress(input); - }, - limitMessage: "Must be a valid address" - }); + let from = input.readAddress('Enter the address from which transfers will be approved: '); + let to = input.readAddress('Enter the address to which transfers will be approved: '); if (!await getManualApproval(from, to)) { - let description = readlineSync.question('Enter the description for the manual approval: ', { - limit: function (input) { - return input != "" && getBinarySize(input) < 33 - }, - limitMessage: "Description is required" - }); + let description = input.readStringNonEmptyWithMaxBinarySize(33, 'Enter the description for the manual approval: '); let allowance = readlineSync.question('Enter the amount of tokens which will be approved: '); let oneHourFromNow = Math.floor(Date.now() / 1000 + 3600); let expiryTime = readlineSync.questionInt(`Enter the time (Unix Epoch time) until which the transfer is allowed (1 hour from now = ${oneHourFromNow}): `, { defaultInput: oneHourFromNow }); @@ -721,12 +962,7 @@ async function matmOperate() { } async function matmManageIncrese(selectedApproval) { - let allowance = readlineSync.question(`Enter a value to increase allowance (current allowance = ${web3.utils.fromWei(selectedApproval.allowance)}): `, { - limit: function (input) { - return parseFloat(input) > 0 - }, - limitMessage: "Amount must be bigger than 0" - }); + let allowance = input.readNumberGreaterThan(0, `Enter a value to increase allowance (current allowance = ${web3.utils.fromWei(selectedApproval.allowance)}): `); if (readlineSync.keyInYNStrict(`Do you want to modify expiry time or description?`)) { let { expiryTime, description } = readExpiryTimeAndDescription(selectedApproval); @@ -740,12 +976,7 @@ async function matmManageIncrese(selectedApproval) { } async function matmManageDecrease(selectedApproval) { - let allowance = readlineSync.question(`Enter a value to decrease allowance (current allowance = ${web3.utils.fromWei(selectedApproval.allowance)}): `, { - limit: function (input) { - return parseFloat(input) > 0 - }, - limitMessage: "Amount must be bigger than 0" - }); + let allowance = input.readNumberGreaterThan(0, `Enter a value to decrease allowance (current allowance = ${web3.utils.fromWei(selectedApproval.allowance)}): `); if (readlineSync.keyInYNStrict(`Do you want to modify expiry time or description?`)) { let { expiryTime, description } = readExpiryTimeAndDescription(selectedApproval); @@ -767,19 +998,8 @@ async function matmManageTimeOrDescription(selectedApproval) { } function readExpiryTimeAndDescription(selectedApproval) { - let expiryTime = readlineSync.questionInt(`Enter the new expiry time (Unix Epoch time) until which the transfer is allowed or leave empty to keep the current (${selectedApproval.expiryTime}): `, { - limit: function (input) { - return parseFloat(input) > 0; - }, - limitMessage: "Enter Unix Epoch time", - defaultInput: selectedApproval.expiryTime - }); - let description = readlineSync.question(`Enter the new description for the manual approval or leave empty to keep the current (${web3.utils.toAscii(selectedApproval.description)}): `, { - limit: function (input) { - return input != "" && getBinarySize(input) < 33; - }, - limitMessage: "Description is required" - }); + let expiryTime = parseInt(input.readNumberGreaterThan(0, `Enter the new expiry time (Unix Epoch time) until which the transfer is allowed or leave empty to keep the current (${selectedApproval.expiryTime}): `, selectedApproval.expiryTime)); + let description = readlineSync.readStringNonEmptyWithMaxBinarySize(33, `Enter the new description for the manual approval or leave empty to keep the current (${web3.utils.toAscii(selectedApproval.description)}): `); return { expiryTime, description }; } @@ -790,15 +1010,9 @@ async function matmManageRevoke(selectedApproval) { } async function getApprovalsArray() { - let address = readlineSync.question('Enter an address to filter or leave empty to get all the approvals: ', { - limit: function (input) { - return web3.utils.isAddress(input); - }, - limitMessage: "Must be a valid address", - defaultInput: gbl.constants.ADDRESS_ZERO - }); + let address = input.readAddress('Enter an address to filter or leave empty to get all the approvals: ', gbl.constants.ADDRESS_ZERO); if (address == gbl.constants.ADDRESS_ZERO) { - return await getApprovals(); + return getApprovals(); } else { let approvals = await getApprovalsToAnAddress(address); if (!approvals.length) { @@ -860,13 +1074,7 @@ async function matmGenericCsv(path, f) { let csvFilePath = readlineSync.question(`Enter the path for csv data file (${path}): `, { defaultInput: path }); - let batchSize = readlineSync.question(`Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, { - limit: function (input) { - return parseInt(input) > 0; - }, - limitMessage: 'Must be greater than 0', - defaultInput: gbl.constants.DEFAULT_BATCH_SIZE - }); + let batchSize = input.readNumberGreaterThan(0, `Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, gbl.constants.DEFAULT_BATCH_SIZE); let parsedData = csvParse(csvFilePath); let validData = parsedData.filter(row => f(row)); let invalidRows = parsedData.filter(row => !validData.includes(row)); @@ -877,7 +1085,6 @@ async function matmGenericCsv(path, f) { } async function addManualApproveInBatch() { - var f = (row) => { return (web3.utils.isAddress(row[0]) && web3.utils.isAddress(row[1]) && @@ -902,7 +1109,6 @@ async function addManualApproveInBatch() { } async function revokeManualApproveInBatch() { - var f = (row) => { return (web3.utils.isAddress(row[0]) && web3.utils.isAddress(row[1])) @@ -921,7 +1127,6 @@ async function revokeManualApproveInBatch() { } async function modifyManualApproveInBatch() { - var f = (row) => { return (web3.utils.isAddress(row[0]) && web3.utils.isAddress(row[1]) && @@ -959,13 +1164,16 @@ async function countTransferManager() { console.log(`- Max holder count: ${displayMaxHolderCount}`); - let options = ['Change max holder count'] + let options = ['Verify transfer', 'Change max holder count'] let index = readlineSync.keyInSelect(options, 'What do you want to do?', { cancel: 'RETURN' }); let optionSelected = index !== -1 ? options[index] : 'RETURN'; console.log('Selected:', optionSelected, '\n'); switch (optionSelected) { + case 'Verify transfer': + await verifyTransfer(true, true); + break; case 'Change max holder count': - let maxHolderCount = readlineSync.question('Enter the maximum no. of holders the SecurityToken is allowed to have: '); + let maxHolderCount = input.readNumberGreaterThan(0, 'Enter the maximum no. of holders the SecurityToken is allowed to have: '); let changeHolderCountAction = currentTransferManager.methods.changeHolderCount(maxHolderCount); let changeHolderCountReceipt = await common.sendTransaction(changeHolderCountAction); let changeHolderCountEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, changeHolderCountReceipt.logs, 'ModifyHolderCount'); @@ -988,7 +1196,7 @@ async function percentageTransferManager() { console.log(`- Max holder percentage: ${fromWeiPercentage(displayMaxHolderPercentage)}%`); console.log(`- Allow primary issuance: ${displayAllowPrimaryIssuance ? `YES` : `NO`}`); - let options = ['Change max holder percentage', 'Check if investor is whitelisted', 'Modify whitelist', 'Modify whitelist from CSV']; + let options = ['Verify transfer', 'Change max holder percentage', 'Check if investor is whitelisted', 'Modify whitelist', 'Modify whitelist from CSV']; if (displayAllowPrimaryIssuance) { options.push('Disallow primary issuance'); } else { @@ -998,25 +1206,18 @@ async function percentageTransferManager() { let optionSelected = index !== -1 ? options[index] : 'RETURN'; console.log('Selected:', optionSelected, '\n'); switch (optionSelected) { + case 'Verify transfer': + await verifyTransfer(false, true); + break; case 'Change max holder percentage': - let maxHolderPercentage = toWeiPercentage(readlineSync.question('Enter the maximum amount of tokens in percentage that an investor can hold: ', { - limit: function (input) { - return (parseInt(input) > 0 && parseInt(input) <= 100); - }, - limitMessage: "Must be greater than 0 and less than 100" - })); + let maxHolderPercentage = toWeiPercentage(input.readPercentage('Enter the maximum amount of tokens in percentage that an investor can hold')); let changeHolderPercentageAction = currentTransferManager.methods.changeHolderPercentage(maxHolderPercentage); let changeHolderPercentageReceipt = await common.sendTransaction(changeHolderPercentageAction); let changeHolderPercentageEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, changeHolderPercentageReceipt.logs, 'ModifyHolderPercentage'); console.log(chalk.green(`Max holder percentage has been set to ${fromWeiPercentage(changeHolderPercentageEvent._newHolderPercentage)} successfully!`)); break; case 'Check if investor is whitelisted': - let investorToCheck = readlineSync.question('Enter the address of the investor: ', { - limit: function (input) { - return web3.utils.isAddress(input); - }, - limitMessage: "Must be a valid address" - }); + let investorToCheck = input.readAddress('Enter the address of the investor: '); let isWhitelisted = await currentTransferManager.methods.whitelist(investorToCheck).call(); if (isWhitelisted) { console.log(chalk.green(`${investorToCheck} is whitelisted!`)); @@ -1026,12 +1227,7 @@ async function percentageTransferManager() { break; case 'Modify whitelist': let valid = !!readlineSync.keyInSelect(['Remove investor from whitelist', 'Add investor to whitelist'], 'How do you want to do? ', { cancel: false }); - let investorToWhitelist = readlineSync.question('Enter the address of the investor: ', { - limit: function (input) { - return web3.utils.isAddress(input); - }, - limitMessage: "Must be a valid address" - }); + let investorToWhitelist = input.readAddress('Enter the address of the investor: '); let modifyWhitelistAction = currentTransferManager.methods.modifyWhitelist(investorToWhitelist, valid); let modifyWhitelistReceipt = await common.sendTransaction(modifyWhitelistAction); let modifyWhitelistEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, modifyWhitelistReceipt.logs, 'ModifyWhitelist'); @@ -1045,13 +1241,7 @@ async function percentageTransferManager() { let csvFilePath = readlineSync.question(`Enter the path for csv data file (${PERCENTAGE_WHITELIST_DATA_CSV}): `, { defaultInput: PERCENTAGE_WHITELIST_DATA_CSV }); - let batchSize = readlineSync.question(`Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, { - limit: function (input) { - return parseInt(input) > 0; - }, - limitMessage: 'Must be greater than 0', - defaultInput: gbl.constants.DEFAULT_BATCH_SIZE - }); + let batchSize = input.readNumberGreaterThan(0, `Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, gbl.constants.DEFAULT_BATCH_SIZE); let parsedData = csvParse(csvFilePath); let validData = parsedData.filter(row => web3.utils.isAddress(row[0]) && typeof row[1] === 'boolean'); let invalidRows = parsedData.filter(row => !validData.includes(row)); @@ -1079,6 +1269,8 @@ async function percentageTransferManager() { console.log(chalk.green(`Transactions which are part of the primary issuance will NOT be ignored!`)); } break; + case 'RETURN': + return; } await percentageTransferManager(); @@ -1090,7 +1282,7 @@ async function blacklistTransferManager() { let currentBlacklists = await currentTransferManager.methods.getAllBlacklists().call(); console.log(`- Blacklists: ${currentBlacklists.length}`); - let options = ['Add new blacklist']; + let options = ['Verify transfer', 'Add new blacklist']; if (currentBlacklists.length > 0) { options.push('Manage existing blacklist', 'Explore account'); } @@ -1100,25 +1292,18 @@ async function blacklistTransferManager() { let optionSelected = index !== -1 ? options[index] : 'RETURN'; console.log('Selected:', optionSelected, '\n'); switch (optionSelected) { + case 'Verify transfer': + await verifyTransfer(false, false); + break; case 'Add new blacklist': - let name = readlineSync.question(`Enter the name of the blacklist type: `, { - limit: function (input) { - return input !== ""; - }, - limitMessage: `Invalid blacklist name` - }); + let name = input.readStringNonEmpty(`Enter the name of the blacklist type: `); let minuteFromNow = Math.floor(Date.now() / 1000) + 60; let startTime = readlineSync.questionInt(`Enter the start date (Unix Epoch time) of the blacklist type (a minute from now = ${minuteFromNow}): `, { defaultInput: minuteFromNow }); let oneDayFromStartTime = startTime + 24 * 60 * 60; let endTime = readlineSync.questionInt(`Enter the end date (Unix Epoch time) of the blacklist type (1 day from start time = ${oneDayFromStartTime}): `, { defaultInput: oneDayFromStartTime }); let repeatPeriodTime = readlineSync.questionInt(`Enter the repeat period (days) of the blacklist type, 0 to disable (90 days): `, { defaultInput: 90 }); if (readlineSync.keyInYNStrict(`Do you want to add an investor to this blacklist type? `)) { - let investor = readlineSync.question(`Enter the address of the investor: `, { - limit: function (input) { - return web3.utils.isAddress(input); - }, - limitMessage: `Must be a valid address` - }); + let investor = input.readAddress(`Enter the address of the investor: `); let addInvestorToNewBlacklistAction = currentTransferManager.methods.addInvestorToNewBlacklist( startTime, endTime, @@ -1148,12 +1333,7 @@ async function blacklistTransferManager() { } break; case 'Explore account': - let account = readlineSync.question(`Enter the address of the investor: `, { - limit: function (input) { - return web3.utils.isAddress(input); - }, - limitMessage: `Must be a valid address` - }); + let account = input.readAddress(`Enter the address of the investor: `); let blacklistNamesToUser = await currentTransferManager.methods.getBlacklistNamesToUser(account).call(); if (blacklistNamesToUser.length > 0) { console.log(); @@ -1165,12 +1345,7 @@ async function blacklistTransferManager() { console.log(); break; case 'Delete investors from all blacklists': - let investorsToRemove = readlineSync.question(`Enter the addresses of the investors separated by comma (i.e. addr1,addr2,addr3): `, { - limit: function (input) { - return (input !== '' && input.split(",").every(a => web3.utils.isAddress(a))); - }, - limitMessage: `All addresses must be valid` - }).split(','); + let investorsToRemove = input.readMultipleAddresses(`Enter the addresses of the investors separated by comma (i.e. addr1,addr2,addr3): `).split(','); let deleteInvestorFromAllBlacklistAction; if (investorsToRemove.length === 1) { deleteInvestorFromAllBlacklistAction = currentTransferManager.methods.deleteInvestorFromAllBlacklist(investorsToRemove[0]); @@ -1202,7 +1377,7 @@ async function manageExistingBlacklist(blacklistName) { console.log(`- End time: ${moment.unix(currentBlacklist.endTime).format('MMMM Do YYYY, HH:mm:ss')}`); console.log(`- Span: ${(currentBlacklist.endTime - currentBlacklist.startTime) / 60 / 60 / 24} days`); console.log(`- Repeat period time: ${currentBlacklist.repeatPeriodTime} days`); - console.log(`- Investors: ${investors.length}`); + console.log(`- Number of investors: ${investors.length}`); // ------------------ let options = [ @@ -1242,12 +1417,7 @@ async function manageExistingBlacklist(blacklistName) { } break; case 'Add investors': - let investorsToAdd = readlineSync.question(`Enter the addresses of the investors separated by comma (i.e. addr1,addr2,addr3): `, { - limit: function (input) { - return (input !== '' && input.split(",").every(a => web3.utils.isAddress(a))); - }, - limitMessage: `All addresses must be valid` - }).split(","); + let investorsToAdd = input.readMultipleAddresses(`Enter the addresses of the investors separated by comma (i.e. addr1,addr2,addr3): `).split(","); let addInvestorToBlacklistAction; if (investorsToAdd.length === 1) { addInvestorToBlacklistAction = currentTransferManager.methods.addInvestorToBlacklist(investorsToAdd[0], blacklistName); @@ -1259,12 +1429,7 @@ async function manageExistingBlacklist(blacklistName) { addInvestorToBlacklistEvents.map(e => console.log(chalk.green(`${e._investor} has been added to ${web3.utils.hexToUtf8(e._blacklistName)} successfully!`))); break; case "Remove investor": - let investorsToRemove = readlineSync.question(`Enter the address of the investor: `, { - limit: function (input) { - return web3.utils.isAddress(input); - }, - limitMessage: `Must be a valid address` - }); + let investorsToRemove = input.readAddress(`Enter the address of the investor: `); let deleteInvestorFromBlacklistAction = currentTransferManager.methods.deleteInvestorFromBlacklist(investorsToRemove, blacklistName); let deleteInvestorFromBlacklistReceipt = await common.sendTransaction(deleteInvestorFromBlacklistAction); let deleteInvestorFromBlacklistEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, deleteInvestorFromBlacklistReceipt.logs, 'DeleteInvestorFromBlacklist'); @@ -1339,13 +1504,7 @@ async function addBlacklistsInBatch() { let csvFilePath = readlineSync.question(`Enter the path for csv data file (${ADD_BLACKLIST_DATA_CSV}): `, { defaultInput: ADD_BLACKLIST_DATA_CSV }); - let batchSize = readlineSync.question(`Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, { - limit: function (input) { - return parseInt(input) > 0; - }, - limitMessage: 'Must be greater than 0', - defaultInput: gbl.constants.DEFAULT_BATCH_SIZE - }); + let batchSize = input.readNumberGreaterThan(0, `Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, gbl.constants.DEFAULT_BATCH_SIZE); let parsedData = csvParse(csvFilePath); let validData = parsedData.filter( row => moment.unix(row[0]).isValid() && @@ -1372,13 +1531,7 @@ async function modifyBlacklistsInBatch() { let csvFilePath = readlineSync.question(`Enter the path for csv data file (${MODIFY_BLACKLIST_DATA_CSV}): `, { defaultInput: MODIFY_BLACKLIST_DATA_CSV }); - let batchSize = readlineSync.question(`Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, { - limit: function (input) { - return parseInt(input) > 0; - }, - limitMessage: 'Must be greater than 0', - defaultInput: gbl.constants.DEFAULT_BATCH_SIZE - }); + let batchSize = input.readNumberGreaterThan(`Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, gbl.constants.DEFAULT_BATCH_SIZE); let parsedData = csvParse(csvFilePath); let validData = parsedData.filter( row => moment.unix(row[0]).isValid() && @@ -1405,13 +1558,7 @@ async function deleteBlacklistsInBatch() { let csvFilePath = readlineSync.question(`Enter the path for csv data file (${DELETE_BLACKLIST_DATA_CSV}): `, { defaultInput: DELETE_BLACKLIST_DATA_CSV }); - let batchSize = readlineSync.question(`Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, { - limit: function (input) { - return parseInt(input) > 0; - }, - limitMessage: 'Must be greater than 0', - defaultInput: gbl.constants.DEFAULT_BATCH_SIZE - }); + let batchSize = input.readNumberGreaterThan(`Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, gbl.constants.DEFAULT_BATCH_SIZE); let parsedData = csvParse(csvFilePath); let validData = parsedData.filter(row => typeof row[0] === 'string'); let invalidRows = parsedData.filter(row => !validData.includes(row)); @@ -1454,13 +1601,7 @@ async function addInvestorsToBlacklistsInBatch() { let csvFilePath = readlineSync.question(`Enter the path for csv data file (${ADD_INVESTOR_BLACKLIST_DATA_CSV}): `, { defaultInput: ADD_INVESTOR_BLACKLIST_DATA_CSV }); - let batchSize = readlineSync.question(`Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, { - limit: function (input) { - return parseInt(input) > 0; - }, - limitMessage: 'Must be greater than 0', - defaultInput: gbl.constants.DEFAULT_BATCH_SIZE - }); + let batchSize = input.readNumberGreaterThan(`Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, gbl.constants.DEFAULT_BATCH_SIZE); let parsedData = csvParse(csvFilePath); let validData = parsedData.filter( row => web3.utils.isAddress(row[0]) && @@ -1502,13 +1643,7 @@ async function removeInvestorsFromBlacklistsInBatch() { let csvFilePath = readlineSync.question(`Enter the path for csv data file (${REMOVE_INVESTOR_BLACKLIST_DATA_CSV}): `, { defaultInput: REMOVE_INVESTOR_BLACKLIST_DATA_CSV }); - let batchSize = readlineSync.question(`Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, { - limit: function (input) { - return parseInt(input) > 0; - }, - limitMessage: 'Must be greater than 0', - defaultInput: gbl.constants.DEFAULT_BATCH_SIZE - }); + let batchSize = input.readNumberGreaterThan(0, `Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, gbl.constants.DEFAULT_BATCH_SIZE); let parsedData = csvParse(csvFilePath); let validData = parsedData.filter( row => web3.utils.isAddress(row[0]) && @@ -1532,29 +1667,29 @@ async function removeInvestorsFromBlacklistsInBatch() { async function volumeRestrictionTM() { console.log('\n', chalk.blue(`Volume Restriction Transfer Manager at ${currentTransferManager.options.address}`, '\n')); - let globalDailyRestriction = await currentTransferManager.methods.defaultDailyRestriction().call(); - let hasGlobalDailyRestriction = parseInt(globalDailyRestriction.startTime) !== 0; - let globalCustomRestriction = await currentTransferManager.methods.defaultRestriction().call(); - let hasGlobalCustomRestriction = parseInt(globalCustomRestriction.startTime) !== 0; + let globalDailyRestriction = await currentTransferManager.methods.getDefaultDailyRestriction().call(); + let hasGlobalDailyRestriction = parseInt(globalDailyRestriction[1]) !== 0; //startTime + let globalCustomRestriction = await currentTransferManager.methods.getDefaultRestriction().call(); + let hasGlobalCustomRestriction = parseInt(globalCustomRestriction[1]) !== 0; //startime console.log(`- Default daily restriction: ${hasGlobalDailyRestriction ? '' : 'None'}`); if (hasGlobalDailyRestriction) { - console.log(` Type: ${RESTRICTION_TYPES[globalDailyRestriction.typeOfRestriction]}`); - console.log(` Allowed tokens: ${globalDailyRestriction.typeOfRestriction === "0" ? `${web3.utils.fromWei(globalDailyRestriction.allowedTokens)} ${tokenSymbol}` : `${fromWeiPercentage(globalDailyRestriction.allowedTokens)}%`}`); - console.log(` Start time: ${moment.unix(globalDailyRestriction.startTime).format('MMMM Do YYYY, HH:mm:ss')}`); - console.log(` Rolling period: ${globalDailyRestriction.rollingPeriodInDays} days`); - console.log(` End time: ${moment.unix(globalDailyRestriction.endTime).format('MMMM Do YYYY, HH:mm:ss')} `); + console.log(` Type: ${RESTRICTION_TYPES[globalDailyRestriction[4]]}`); + console.log(` Allowed tokens: ${globalDailyRestriction[4] === "0" ? `${web3.utils.fromWei(globalDailyRestriction[0])} ${tokenSymbol}` : `${fromWeiPercentage(globalDailyRestriction[0])}%`}`); + console.log(` Start time: ${moment.unix(globalDailyRestriction[1]).format('MMMM Do YYYY, HH:mm:ss')}`); + console.log(` Rolling period: ${globalDailyRestriction[2]} days`); + console.log(` End time: ${moment.unix(globalDailyRestriction[3]).format('MMMM Do YYYY, HH:mm:ss')} `); } console.log(`- Default custom restriction: ${hasGlobalCustomRestriction ? '' : 'None'}`); if (hasGlobalCustomRestriction) { - console.log(` Type: ${RESTRICTION_TYPES[globalCustomRestriction.typeOfRestriction]}`); - console.log(` Allowed tokens: ${globalCustomRestriction.typeOfRestriction === "0" ? `${web3.utils.fromWei(globalCustomRestriction.allowedTokens)} ${tokenSymbol}` : `${fromWeiPercentage(globalCustomRestriction.allowedTokens)}%`}`); - console.log(` Start time: ${moment.unix(globalCustomRestriction.startTime).format('MMMM Do YYYY, HH:mm:ss')}`); - console.log(` Rolling period: ${globalCustomRestriction.rollingPeriodInDays} days`); - console.log(` End time: ${moment.unix(globalCustomRestriction.endTime).format('MMMM Do YYYY, HH:mm:ss')} `); + console.log(` Type: ${RESTRICTION_TYPES[globalCustomRestriction[4]]}`); + console.log(` Allowed tokens: ${globalCustomRestriction[4] === "0" ? `${web3.utils.fromWei(globalCustomRestriction[0])} ${tokenSymbol}` : `${fromWeiPercentage(globalCustomRestriction[0])}%`}`); + console.log(` Start time: ${moment.unix(globalCustomRestriction[1]).format('MMMM Do YYYY, HH:mm:ss')}`); + console.log(` Rolling period: ${globalCustomRestriction[2]} days`); + console.log(` End time: ${moment.unix(globalCustomRestriction[3]).format('MMMM Do YYYY, HH:mm:ss')} `); } - let addressesAndRestrictions = await currentTransferManager.methods.getRestrictedData().call(); + let addressesAndRestrictions = await currentTransferManager.methods.getRestrictionData().call(); console.log(`- Individual restrictions: ${addressesAndRestrictions.allAddresses.length}`); let exemptedAddresses = await currentTransferManager.methods.getExemptAddress().call(); console.log(`- Exempted addresses: ${exemptedAddresses.length}`); @@ -1567,6 +1702,7 @@ async function volumeRestrictionTM() { options.push('Show exempted addresses'); } options.push( + 'Verify transfer', 'Change exempt wallet', 'Change default restrictions', 'Change individual restrictions', @@ -1585,12 +1721,15 @@ async function volumeRestrictionTM() { addressesAndRestrictions.typeOfRestriction, addressesAndRestrictions.rollingPeriodInDays, addressesAndRestrictions.startTime, - addressesAndRestrictions.endTime, + addressesAndRestrictions.endTime ); break; case 'Show exempted addresses': showExemptedAddresses(exemptedAddresses); break; + case 'Verify transfer': + await verifyTransfer(true, false); + break; case 'Change exempt wallet': await changeExemptWallet(); break; @@ -1620,8 +1759,8 @@ function showRestrictionTable(investorArray, amountArray, typeArray, rollingPeri investorArray[i], typeArray[i] === "0" ? `${web3.utils.fromWei(amountArray[i])} ${tokenSymbol}` : `${fromWeiPercentage(amountArray[i])}%`, rollingPeriodArray[i], - moment.unix(startTimeArray[i]).format('MM/DD/YYYY HH:mm'), - moment.unix(endTimeTimeArray[i]).format('MM/DD/YYYY HH:mm') + moment.unix(startTimeArray[i]).format('MMM Do YYYY HH:mm'), + moment.unix(endTimeTimeArray[i]).format('MMM Do YYYY HH:mm') ]); } console.log(); @@ -1654,12 +1793,7 @@ async function changeExemptWallet() { return; } - let wallet = readlineSync.question('Enter the wallet to change: ', { - limit: function (input) { - return web3.utils.isAddress(input); - }, - limitMessage: "Must be a valid address" - }); + let wallet = input.readAddress('Enter the wallet to change: '); let changeExemptWalletAction = currentTransferManager.methods.changeExemptWalletList(wallet, change); let changeExemptWalletReceipt = await common.sendTransaction(changeExemptWalletAction); let changeExemptWalletEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, changeExemptWalletReceipt.logs, 'ChangedExemptWalletList'); @@ -1750,35 +1884,30 @@ async function changeDefaultRestrictions(hasGlobalDailyRestriction, hasGlobalCus } async function changeIndividualRestrictions() { - let holder = readlineSync.question('Enter the address of the token holder, whom restriction will be implied: ', { - limit: function (input) { - return web3.utils.isAddress(input); - }, - limitMessage: "Must be a valid address" - }); + let holder = input.readAddress('Enter the address of the token holder, whom restriction will be implied: '); - let currentDailyRestriction = await currentTransferManager.methods.individualDailyRestriction(holder).call(); - let hasDailyRestriction = parseInt(currentDailyRestriction.startTime) !== 0; - let currentCustomRestriction = await currentTransferManager.methods.individualRestriction(holder).call(); - let hasCustomRestriction = parseInt(currentCustomRestriction.startTime) !== 0; + let currentDailyRestriction = await currentTransferManager.methods.getIndividualDailyRestriction(holder).call(); + let hasDailyRestriction = parseInt(currentDailyRestriction[1]) !== 0; + let currentCustomRestriction = await currentTransferManager.methods.getIndividualRestriction(holder).call(); + let hasCustomRestriction = parseInt(currentCustomRestriction[1]) !== 0; console.log(`*** Current individual restrictions for ${holder} ***`, '\n'); console.log(`- Daily restriction: ${hasDailyRestriction ? '' : 'None'}`); if (hasDailyRestriction) { - console.log(` Type: ${RESTRICTION_TYPES[currentDailyRestriction.typeOfRestriction]}`); - console.log(` Allowed tokens: ${currentDailyRestriction.typeOfRestriction === "0" ? `${web3.utils.fromWei(currentDailyRestriction.allowedTokens)} ${tokenSymbol}` : `${fromWeiPercentage(currentDailyRestriction.allowedTokens)}%`}`); - console.log(` Start time: ${moment.unix(currentDailyRestriction.startTime).format('MMMM Do YYYY, HH:mm:ss')}`); - console.log(` Rolling period: ${currentDailyRestriction.rollingPeriodInDays} days`); - console.log(` End time: ${moment.unix(currentDailyRestriction.endTime).format('MMMM Do YYYY, HH:mm:ss')} `); + console.log(` Type: ${RESTRICTION_TYPES[currentDailyRestriction[4]]}`); + console.log(` Allowed tokens: ${currentDailyRestriction[4] === "0" ? `${web3.utils.fromWei(currentDailyRestriction[0])} ${tokenSymbol}` : `${fromWeiPercentage(currentDailyRestriction[0])}%`}`); + console.log(` Start time: ${moment.unix(currentDailyRestriction[1]).format('MMMM Do YYYY, HH:mm:ss')}`); + console.log(` Rolling period: ${currentDailyRestriction[2]} days`); + console.log(` End time: ${moment.unix(currentDailyRestriction[3]).format('MMMM Do YYYY, HH:mm:ss')} `); } console.log(`- Custom restriction: ${hasCustomRestriction ? '' : 'None'} `); if (hasCustomRestriction) { - console.log(` Type: ${RESTRICTION_TYPES[currentCustomRestriction.typeOfRestriction]}`); - console.log(` Allowed tokens: ${currentCustomRestriction.typeOfRestriction === "0" ? `${web3.utils.fromWei(currentCustomRestriction.allowedTokens)} ${tokenSymbol}` : `${fromWeiPercentage(currentCustomRestriction.allowedTokens)}%`}`); - console.log(` Start time: ${moment.unix(currentCustomRestriction.startTime).format('MMMM Do YYYY, HH:mm:ss')}`); - console.log(` Rolling period: ${currentCustomRestriction.rollingPeriodInDays} days`); - console.log(` End time: ${moment.unix(currentCustomRestriction.endTime).format('MMMM Do YYYY, HH:mm:ss')} `); + console.log(` Type: ${RESTRICTION_TYPES[currentCustomRestriction[4]]}`); + console.log(` Allowed tokens: ${currentCustomRestriction[4] === "0" ? `${web3.utils.fromWei(currentDailyRestriction[0])} ${tokenSymbol}` : `${fromWeiPercentage(currentDailyRestriction[0])}%`}`); + console.log(` Start time: ${moment.unix(currentCustomRestriction[1]).format('MMMM Do YYYY, HH:mm:ss')}`); + console.log(` Rolling period: ${currentCustomRestriction[2]} days`); + console.log(` End time: ${moment.unix(currentCustomRestriction[3]).format('MMMM Do YYYY, HH:mm:ss')} `); } let options = []; @@ -1870,36 +1999,32 @@ async function changeIndividualRestrictions() { } async function exploreAccount() { - let account = readlineSync.question('Enter the account to explore: ', { - limit: function (input) { - return web3.utils.isAddress(input); - }, - limitMessage: "Must be a valid address" - }); + let account = input.readAddress('Enter the account to explore: '); - let appliyngDailyRestriction = null; + let applyingDailyRestriction = null; let applyingCustomRestriction = null; let hasIndividualRestrictions = false; - let isExempted = await currentTransferManager.methods.exemptList(account).call(); + let exempted = await currentTransferManager.methods.getExemptAddress().call(); + let isExempted = exempted.includes(account); if (!isExempted) { - let individuallDailyRestriction = await currentTransferManager.methods.individualDailyRestriction(account).call(); - if (parseInt(individuallDailyRestriction.endTime) !== 0) { - appliyngDailyRestriction = individuallDailyRestriction; + let individuallDailyRestriction = await currentTransferManager.methods.getIndividualDailyRestriction(account).call(); + if (parseInt(individuallDailyRestriction[3]) !== 0) { + applyingDailyRestriction = individuallDailyRestriction; } - let customRestriction = await currentTransferManager.methods.individualRestriction(account).call(); - if (parseInt(customRestriction.endTime) !== 0) { + let customRestriction = await currentTransferManager.methods.getIndividualRestriction(account).call(); + if (parseInt(customRestriction[3]) !== 0) { applyingCustomRestriction = customRestriction; } - hasIndividualRestrictions = applyingCustomRestriction || appliyngDailyRestriction; + hasIndividualRestrictions = applyingCustomRestriction || applyingDailyRestriction; if (!hasIndividualRestrictions) { - let globalDailyRestriction = await currentTransferManager.methods.defaultDailyRestriction().call(); - if (parseInt(globalDailyRestriction.endTime) !== 0) { - appliyngDailyRestriction = globalDailyRestriction; + let globalDailyRestriction = await currentTransferManager.methods.getDefaultDailyRestriction().call(); + if (parseInt(globalDailyRestriction[3]) !== 0) { + applyingDailyRestriction = globalDailyRestriction; } - let globalCustomRestriction = await currentTransferManager.methods.defaultRestriction().call(); - if (parseInt(globalCustomRestriction.endTime) === 0) { + let globalCustomRestriction = await currentTransferManager.methods.getDefaultRestriction().call(); + if (parseInt(globalCustomRestriction[3]) === 0) { applyingCustomRestriction = globalCustomRestriction; } } @@ -1907,24 +2032,24 @@ async function exploreAccount() { console.log(`*** Applying restrictions for ${account} ***`, '\n'); - console.log(`- Daily restriction: ${appliyngDailyRestriction ? (!hasIndividualRestrictions ? 'global' : '') : 'None'}`); - if (appliyngDailyRestriction) { - console.log(` Type: ${RESTRICTION_TYPES[appliyngDailyRestriction.typeOfRestriction]}`); - console.log(` Allowed tokens: ${appliyngDailyRestriction.typeOfRestriction === "0" ? `${web3.utils.fromWei(appliyngDailyRestriction.allowedTokens)} ${tokenSymbol}` : `${fromWeiPercentage(appliyngDailyRestriction.allowedTokens)}%`}`); - console.log(` Start time: ${moment.unix(appliyngDailyRestriction.startTime).format('MMMM Do YYYY, HH:mm:ss')}`); - console.log(` Rolling period: ${appliyngDailyRestriction.rollingPeriodInDays} days`); - console.log(` End time: ${moment.unix(appliyngDailyRestriction.endTime).format('MMMM Do YYYY, HH:mm:ss')} `); + console.log(`- Daily restriction: ${applyingDailyRestriction ? (!hasIndividualRestrictions ? 'global' : '') : 'None'}`); + if (applyingDailyRestriction) { + console.log(` Type: ${RESTRICTION_TYPES[applyingDailyRestriction[4]]}`); + console.log(` Allowed tokens: ${applyingDailyRestriction[4] === "0" ? `${web3.utils.fromWei(applyingDailyRestriction[0])} ${tokenSymbol}` : `${fromWeiPercentage(applyingDailyRestriction[0])}%`}`); + console.log(` Start time: ${moment.unix(applyingDailyRestriction[1]).format('MMMM Do YYYY, HH:mm:ss')}`); + console.log(` Rolling period: ${applyingDailyRestriction[2]} days`); + console.log(` End time: ${moment.unix(applyingDailyRestriction[3]).format('MMMM Do YYYY, HH:mm:ss')} `); } console.log(`- Other restriction: ${applyingCustomRestriction ? (!hasIndividualRestrictions ? 'global' : '') : 'None'} `); if (applyingCustomRestriction) { - console.log(` Type: ${RESTRICTION_TYPES[applyingCustomRestriction.typeOfRestriction]}`); - console.log(` Allowed tokens: ${applyingCustomRestriction.typeOfRestriction === "0" ? `${web3.utils.fromWei(applyingCustomRestriction.allowedTokens)} ${tokenSymbol}` : `${fromWeiPercentage(applyingCustomRestriction.allowedTokens)}%`}`); - console.log(` Start time: ${moment.unix(applyingCustomRestriction.startTime).format('MMMM Do YYYY, HH:mm:ss')}`); - console.log(` Rolling period: ${applyingCustomRestriction.rollingPeriodInDays} days`); - console.log(` End time: ${moment.unix(applyingCustomRestriction.endTime).format('MMMM Do YYYY, HH:mm:ss')} `); + console.log(` Type: ${RESTRICTION_TYPES[applyingCustomRestriction[4]]}`); + console.log(` Allowed tokens: ${applyingCustomRestriction[4] === "0" ? `${web3.utils.fromWei(applyingCustomRestriction[0])} ${tokenSymbol}` : `${fromWeiPercentage(applyingCustomRestriction[0])}%`}`); + console.log(` Start time: ${moment.unix(applyingCustomRestriction[1]).format('MMMM Do YYYY, HH:mm:ss')}`); + console.log(` Rolling period: ${applyingCustomRestriction[2]} days`); + console.log(` End time: ${moment.unix(applyingCustomRestriction[3]).format('MMMM Do YYYY, HH:mm:ss')} `); } - if (applyingCustomRestriction || appliyngDailyRestriction) { + if (applyingCustomRestriction || applyingDailyRestriction) { let bucketDetails; if (hasIndividualRestrictions) { bucketDetails = await currentTransferManager.methods.getIndividualBucketDetailsToUser(account).call(); @@ -1982,13 +2107,7 @@ async function addDailyRestrictionsInBatch() { let csvFilePath = readlineSync.question(`Enter the path for csv data file (${ADD_DAILY_RESTRICTIONS_DATA_CSV}): `, { defaultInput: ADD_DAILY_RESTRICTIONS_DATA_CSV }); - let batchSize = readlineSync.question(`Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, { - limit: function (input) { - return parseInt(input) > 0; - }, - limitMessage: 'Must be greater than 0', - defaultInput: gbl.constants.DEFAULT_BATCH_SIZE - }); + let batchSize = input.readNumberGreaterThan(0, `Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, gbl.constants.DEFAULT_BATCH_SIZE); let parsedData = csvParse(csvFilePath); let validData = parsedData.filter( row => web3.utils.isAddress(row[0]) && @@ -2017,13 +2136,7 @@ async function modifyDailyRestrictionsInBatch() { let csvFilePath = readlineSync.question(`Enter the path for csv data file (${MODIFY_DAILY_RESTRICTIONS_DATA_CSV}): `, { defaultInput: MODIFY_DAILY_RESTRICTIONS_DATA_CSV }); - let batchSize = readlineSync.question(`Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, { - limit: function (input) { - return parseInt(input) > 0; - }, - limitMessage: 'Must be greater than 0', - defaultInput: gbl.constants.DEFAULT_BATCH_SIZE - }); + let batchSize = input.readNumberGreaterThan(0, `Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, gbl.constants.DEFAULT_BATCH_SIZE); let parsedData = csvParse(csvFilePath); let validData = parsedData.filter( row => web3.utils.isAddress(row[0]) && @@ -2052,13 +2165,7 @@ async function removeDailyRestrictionsInBatch() { let csvFilePath = readlineSync.question(`Enter the path for csv data file (${REMOVE_DAILY_RESTRICTIONS_DATA_CSV}): `, { defaultInput: REMOVE_DAILY_RESTRICTIONS_DATA_CSV }); - let batchSize = readlineSync.question(`Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, { - limit: function (input) { - return parseInt(input) > 0; - }, - limitMessage: 'Must be greater than 0', - defaultInput: gbl.constants.DEFAULT_BATCH_SIZE - }); + let batchSize = input.readNumberGreaterThan(0, `Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, gbl.constants.DEFAULT_BATCH_SIZE); let parsedData = csvParse(csvFilePath); let validData = parsedData.filter(row => web3.utils.isAddress(row[0])); let invalidRows = parsedData.filter(row => !validData.includes(row)); @@ -2080,13 +2187,7 @@ async function addCustomRestrictionsInBatch() { let csvFilePath = readlineSync.question(`Enter the path for csv data file (${ADD_CUSTOM_RESTRICTIONS_DATA_CSV}): `, { defaultInput: ADD_CUSTOM_RESTRICTIONS_DATA_CSV }); - let batchSize = readlineSync.question(`Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, { - limit: function (input) { - return parseInt(input) > 0; - }, - limitMessage: 'Must be greater than 0', - defaultInput: gbl.constants.DEFAULT_BATCH_SIZE - }); + let batchSize = input.readNumberGreaterThan(0, `Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, gbl.constants.DEFAULT_BATCH_SIZE); let parsedData = csvParse(csvFilePath); let validData = parsedData.filter( row => web3.utils.isAddress(row[0]) && @@ -2116,13 +2217,7 @@ async function modifyCustomRestrictionsInBatch() { let csvFilePath = readlineSync.question(`Enter the path for csv data file (${MODIFY_CUSTOM_RESTRICTIONS_DATA_CSV}): `, { defaultInput: MODIFY_CUSTOM_RESTRICTIONS_DATA_CSV }); - let batchSize = readlineSync.question(`Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, { - limit: function (input) { - return parseInt(input) > 0; - }, - limitMessage: 'Must be greater than 0', - defaultInput: gbl.constants.DEFAULT_BATCH_SIZE - }); + let batchSize = input.readNumberGreaterThan(0, `Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, gbl.constants.DEFAULT_BATCH_SIZE); let parsedData = csvParse(csvFilePath); let validData = parsedData.filter( row => web3.utils.isAddress(row[0]) && @@ -2152,13 +2247,7 @@ async function removeCustomRestrictionsInBatch() { let csvFilePath = readlineSync.question(`Enter the path for csv data file (${REMOVE_CUSTOM_RESTRICTIONS_DATA_CSV}): `, { defaultInput: REMOVE_CUSTOM_RESTRICTIONS_DATA_CSV }); - let batchSize = readlineSync.question(`Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, { - limit: function (input) { - return parseInt(input) > 0; - }, - limitMessage: 'Must be greater than 0', - defaultInput: gbl.constants.DEFAULT_BATCH_SIZE - }); + let batchSize = input.readNumberGreaterThan(0, `Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, gbl.constants.DEFAULT_BATCH_SIZE); let parsedData = csvParse(csvFilePath); let validData = parsedData.filter(row => web3.utils.isAddress(row[0])); let invalidRows = parsedData.filter(row => !validData.includes(row)); @@ -2191,13 +2280,8 @@ function inputRestrictionData(isDaily) { } restriction.startTime = readlineSync.questionInt(`Enter the time (Unix Epoch time) at which restriction get into effect (now = 0): `, { defaultInput: 0 }); let oneMonthFromNow = Math.floor(Date.now() / 1000) + gbl.constants.DURATION.days(30); - restriction.endTime = readlineSync.question(`Enter the time (Unix Epoch time) when the purchase lockup period ends and the investor can freely purchase tokens from others (1 month from now = ${oneMonthFromNow}): `, { - limit: function (input) { - return input > restriction.startTime + gbl.constants.DURATION.days(restriction.rollingPeriodInDays); - }, - limitMessage: 'Must be greater than startTime + rolling period', - defaultInput: oneMonthFromNow - }); + let minValue = parseInt(restriction.startTime) + gbl.constants.DURATION.days(restriction.rollingPeriodInDays); + restriction.endTime = input.readNumberGreaterThan(minValue, `Enter the time (Unix Epoch time) when the purchase lockup period ends and the investor can freely purchase tokens from others (1 month from now = ${oneMonthFromNow}): `, oneMonthFromNow); return restriction; } @@ -2207,7 +2291,7 @@ async function lockUpTransferManager() { let currentLockups = await currentTransferManager.methods.getAllLockups().call(); console.log(`- Lockups: ${currentLockups.length}`); - let options = ['Add new lockup']; + let options = ['Verify transfer', 'Add new lockup']; if (currentLockups.length > 0) { options.push('Show all existing lockups', 'Manage existing lockups', 'Explore investor'); } @@ -2217,25 +2301,18 @@ async function lockUpTransferManager() { let optionSelected = index !== -1 ? options[index] : 'RETURN'; console.log('Selected:', optionSelected, '\n'); switch (optionSelected) { + case 'Verify transfer': + await verifyTransfer(true, false); + break; case 'Add new lockup': - let name = readlineSync.question(`Enter the name of the lockup type: `, { - limit: function (input) { - return input !== ""; - }, - limitMessage: `Invalid lockup name` - }); + let name = input.readStringNonEmpty(`Enter the name of the lockup type: `); let lockupAmount = readlineSync.questionInt(`Enter the amount of tokens that will be locked: `); let minuteFromNow = Math.floor(Date.now() / 1000) + 60; let startTime = readlineSync.questionInt(`Enter the start time (Unix Epoch time) of the lockup type (a minute from now = ${minuteFromNow}): `, { defaultInput: minuteFromNow }); let lockUpPeriodSeconds = readlineSync.questionInt(`Enter the total period (seconds) of the lockup type (ten minutes = 600): `, { defaultInput: 600 }); let releaseFrequencySeconds = readlineSync.questionInt(`Enter how often to release a tranche of tokens in seconds (one minute = 60): `, { defaultInput: 60 }); if (readlineSync.keyInYNStrict(`Do you want to add an investor to this lockup type? `)) { - let investor = readlineSync.question(`Enter the address of the investor: `, { - limit: function (input) { - return web3.utils.isAddress(input); - }, - limitMessage: `Must be a valid address` - }); + let investor = input.readAddress(`Enter the address of the investor: `); let addNewLockUpToUserAction = currentTransferManager.methods.addNewLockUpToUser( investor, web3.utils.toWei(lockupAmount.toString()), @@ -2276,12 +2353,7 @@ async function lockUpTransferManager() { } break; case 'Explore investor': - let investorToExplore = readlineSync.question('Enter the address you want to explore: ', { - limit: function (input) { - return web3.utils.isAddress(input); - }, - limitMessage: "Must be a valid address" - }); + let investorToExplore = input.readAddress('Enter the address you want to explore: '); let lockupsToInvestor = await currentTransferManager.methods.getLockupsNamesToUser(investorToExplore).call(); if (lockupsToInvestor.length > 0) { let lockedTokenToInvestor = await currentTransferManager.methods.getLockedTokenToUser(investorToExplore).call(); @@ -2329,7 +2401,7 @@ async function manageExistingLockups(lockupName) { console.log(`- Start time: ${moment.unix(currentLockup.startTime).format('MMMM Do YYYY, HH:mm:ss')}`); console.log(`- Lockup period: ${currentLockup.lockUpPeriodSeconds} seconds`); console.log(`- End time: ${moment.unix(currentLockup.startTime).add(parseInt(currentLockup.lockUpPeriodSeconds), 'seconds').format('MMMM Do YYYY, HH:mm:ss')}`); console.log(`- Release frequency: ${currentLockup.releaseFrequencySeconds} seconds`); - console.log(`- Investors: ${investors.length}`); + console.log(`- Number of investors: ${investors.length}`); // ------------------ let options = [ @@ -2364,12 +2436,7 @@ async function manageExistingLockups(lockupName) { } break; case 'Add this lockup to investors': - let investorsToAdd = readlineSync.question(`Enter the addresses of the investors separated by comma (i.e.addr1, addr2, addr3): `, { - limit: function (input) { - return (input !== '' && input.split(",").every(a => web3.utils.isAddress(a))); - }, - limitMessage: `All addresses must be valid` - }).split(","); + let investorsToAdd = input.readMultipleAddresses(`Enter the addresses of the investors separated by comma (i.e.addr1, addr2, addr3): `).split(","); let addInvestorToLockupAction; if (investorsToAdd.length === 1) { addInvestorToLockupAction = currentTransferManager.methods.addLockUpByName(investorsToAdd[0], lockupName); @@ -2381,12 +2448,7 @@ async function manageExistingLockups(lockupName) { addInvestorToLockupEvents.map(e => console.log(chalk.green(`${e._userAddress} has been added to ${web3.utils.hexToUtf8(e._lockupName)} successfully!`))); break; case 'Remove this lockup from investors': - let investorsToRemove = readlineSync.question(`Enter the addresses of the investors separated by comma (i.e.addr1, addr2, addr3): `, { - limit: function (input) { - return (input !== '' && input.split(",").every(a => web3.utils.isAddress(a))); - }, - limitMessage: `All addresses must be valid` - }).split(","); + let investorsToRemove = input.readMultipleAddresses(`Enter the addresses of the investors separated by comma (i.e.addr1, addr2, addr3): `).split(","); let removeLockupFromInvestorAction; if (investorsToRemove.length === 1) { removeLockupFromInvestorAction = currentTransferManager.methods.removeLockUpFromUser(investorsToRemove[0], lockupName); @@ -2466,13 +2528,7 @@ async function addLockupsInBatch() { let csvFilePath = readlineSync.question(`Enter the path for csv data file (${ADD_LOCKUP_DATA_CSV}): `, { defaultInput: ADD_LOCKUP_DATA_CSV }); - let batchSize = readlineSync.question(`Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, { - limit: function (input) { - return parseInt(input) > 0; - }, - limitMessage: 'Must be greater than 0', - defaultInput: gbl.constants.DEFAULT_BATCH_SIZE - }); + let batchSize = input.readNumberGreaterThan(0, `Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, gbl.constants.DEFAULT_BATCH_SIZE); let parsedData = csvParse(csvFilePath); let validData = parsedData.filter( row => !isNaN(row[0]) && @@ -2501,13 +2557,7 @@ async function modifyLockupsInBatch() { let csvFilePath = readlineSync.question(`Enter the path for csv data file (${MODIFY_LOCKUP_DATA_CSV}): `, { defaultInput: MODIFY_LOCKUP_DATA_CSV }); - let batchSize = readlineSync.question(`Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, { - limit: function (input) { - return parseInt(input) > 0; - }, - limitMessage: 'Must be greater than 0', - defaultInput: gbl.constants.DEFAULT_BATCH_SIZE - }); + let batchSize = input.readNumberGreaterThan(0, `Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, gbl.constants.DEFAULT_BATCH_SIZE); let parsedData = csvParse(csvFilePath); let validData = parsedData.filter( row => !isNaN(row[0]) && @@ -2536,13 +2586,7 @@ async function deleteLockupsInBatch() { let csvFilePath = readlineSync.question(`Enter the path for csv data file (${DELETE_LOCKUP_DATA_CSV}): `, { defaultInput: DELETE_LOCKUP_DATA_CSV }); - let batchSize = readlineSync.question(`Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, { - limit: function (input) { - return parseInt(input) > 0; - }, - limitMessage: 'Must be greater than 0', - defaultInput: gbl.constants.DEFAULT_BATCH_SIZE - }); + let batchSize = input.readNumberGreaterThan(0, `Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, gbl.constants.DEFAULT_BATCH_SIZE); let parsedData = csvParse(csvFilePath); let validData = parsedData.filter(row => typeof row[0] === 'string'); let invalidRows = parsedData.filter(row => !validData.includes(row)); @@ -2565,13 +2609,7 @@ async function addLockupsToInvestorsInBatch() { let csvFilePath = readlineSync.question(`Enter the path for csv data file (${ADD_LOCKUP_INVESTOR_DATA_CSV}): `, { defaultInput: ADD_LOCKUP_INVESTOR_DATA_CSV }); - let batchSize = readlineSync.question(`Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, { - limit: function (input) { - return parseInt(input) > 0; - }, - limitMessage: 'Must be greater than 0', - defaultInput: gbl.constants.DEFAULT_BATCH_SIZE - }); + let batchSize = input.readNumberGreaterThan(0, `Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, gbl.constants.DEFAULT_BATCH_SIZE); let parsedData = csvParse(csvFilePath); let validData = parsedData.filter( row => web3.utils.isAddress(row[0]) && @@ -2596,13 +2634,7 @@ async function removeLockupsFromInvestorsInBatch() { let csvFilePath = readlineSync.question(`Enter the path for csv data file (${REMOVE_LOCKUP_INVESTOR_DATA_CSV}): `, { defaultInput: REMOVE_LOCKUP_INVESTOR_DATA_CSV }); - let batchSize = readlineSync.question(`Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, { - limit: function (input) { - return parseInt(input) > 0; - }, - limitMessage: 'Must be greater than 0', - defaultInput: gbl.constants.DEFAULT_BATCH_SIZE - }); + let batchSize = input.readNumberGreaterThan(0, `Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, gbl.constants.DEFAULT_BATCH_SIZE); let parsedData = csvParse(csvFilePath); let validData = parsedData.filter( row => web3.utils.isAddress(row[0]) && @@ -2647,51 +2679,15 @@ function fromWeiPercentage(number) { return web3.utils.fromWei(new web3.utils.BN(number).muln(100)).toString(); } -async function getAllModulesByType(type) { - function ModuleInfo(_moduleType, _name, _address, _factoryAddress, _archived, _paused) { - this.name = _name; - this.type = _moduleType; - this.address = _address; - this.factoryAddress = _factoryAddress; - this.archived = _archived; - this.paused = _paused; - } - - let modules = []; - - let allModules = await securityToken.methods.getModulesByType(type).call(); - - for (let i = 0; i < allModules.length; i++) { - let details = await securityToken.methods.getModule(allModules[i]).call(); - let nameTemp = web3.utils.hexToUtf8(details[0]); - let pausedTemp = null; - if (type == gbl.constants.MODULES_TYPES.STO || type == gbl.constants.MODULES_TYPES.TRANSFER) { - let abiTemp = JSON.parse(require('fs').readFileSync(`${__dirname}/../../build/contracts/${nameTemp}.json`).toString()).abi; - let contractTemp = new web3.eth.Contract(abiTemp, details[1]); - pausedTemp = await contractTemp.methods.paused().call(); - } - modules.push(new ModuleInfo(type, nameTemp, details[1], details[2], details[3], pausedTemp)); - } - - return modules; -} - async function initialize(_tokenSymbol) { welcome(); await setup(); - if (typeof _tokenSymbol === 'undefined') { - tokenSymbol = await selectToken(); - } else { - tokenSymbol = _tokenSymbol; - } - let securityTokenAddress = await securityTokenRegistry.methods.getSecurityTokenAddress(tokenSymbol).call(); - if (securityTokenAddress == '0x0000000000000000000000000000000000000000') { - console.log(chalk.red(`Selected Security Token ${tokenSymbol} does not exist.`)); + securityToken = await common.selectToken(securityTokenRegistry, _tokenSymbol); + if (securityToken === null) { process.exit(0); + } else { + tokenSymbol = await securityToken.methods.symbol().call(); } - let securityTokenABI = abis.securityToken(); - securityToken = new web3.eth.Contract(securityTokenABI, securityTokenAddress); - securityToken.setProvider(web3.currentProvider); } function welcome() { @@ -2705,14 +2701,19 @@ function welcome() { async function setup() { try { let securityTokenRegistryAddress = await contracts.securityTokenRegistry(); - let securityTokenRegistryABI = abis.securityTokenRegistry(); - securityTokenRegistry = new web3.eth.Contract(securityTokenRegistryABI, securityTokenRegistryAddress); + let iSecurityTokenRegistryABI = abis.iSecurityTokenRegistry(); + securityTokenRegistry = new web3.eth.Contract(iSecurityTokenRegistryABI, securityTokenRegistryAddress); securityTokenRegistry.setProvider(web3.currentProvider); let moduleRegistryAddress = await contracts.moduleRegistry(); let moduleRegistryABI = abis.moduleRegistry(); moduleRegistry = new web3.eth.Contract(moduleRegistryABI, moduleRegistryAddress); moduleRegistry.setProvider(web3.currentProvider); + + let polyTokenAddress = await contracts.polyToken(); + let polyTokenABI = abis.polyToken(); + polyToken = new web3.eth.Contract(polyTokenABI, polyTokenAddress); + polyToken.setProvider(web3.currentProvider); } catch (err) { console.log(err) console.log('\x1b[31m%s\x1b[0m', "There was a problem getting the contracts. Make sure they are deployed to the selected network."); @@ -2720,45 +2721,15 @@ async function setup() { } } -async function selectToken() { - let result = null; - - let userTokens = await securityTokenRegistry.methods.getTokensByOwner(Issuer.address).call(); - let tokenDataArray = await Promise.all(userTokens.map(async function (t) { - let tokenData = await securityTokenRegistry.methods.getSecurityTokenData(t).call(); - return { symbol: tokenData[0], address: t }; - })); - let options = tokenDataArray.map(function (t) { - return `${t.symbol} - Deployed at ${t.address} `; - }); - options.push('Enter token symbol manually'); - - let index = readlineSync.keyInSelect(options, 'Select a token:', { cancel: 'EXIT' }); - let selected = index != -1 ? options[index] : 'EXIT'; - switch (selected) { - case 'Enter token symbol manually': - result = readlineSync.question('Enter the token symbol: '); - break; - case 'EXIT': - process.exit(); - break; - default: - result = tokenDataArray[index].symbol; - break; - } - - return result; -} - async function logTotalInvestors() { - let investorsCount = await securityToken.methods.getInvestorCount().call(); - console.log(chalk.yellow(`Total investors at the moment: ${investorsCount} `)); + let holdersCount = await securityToken.methods.holderCount().call(); + console.log(chalk.yellow(`Total holders at the moment: ${holdersCount} `)); } async function logBalance(from, totalSupply) { let fromBalance = web3.utils.fromWei(await securityToken.methods.balanceOf(from).call()); - let percentage = totalSupply != '0' ? ` - ${parseFloat(fromBalance) / parseFloat(totalSupply) * 100}% of total supply` : ''; - console.log(chalk.yellow(`Balance of ${from}: ${fromBalance} ${tokenSymbol} ${percentage} `)); + let fromBalanceUnlocked = web3.utils.fromWei(await securityToken.methods.balanceOfByPartition(web3.utils.asciiToHex('UNLOCKED'), from).call()); + output.logUnlockedBalanceWithPercentage(from, tokenSymbol, fromBalanceUnlocked, fromBalance, totalSupply); } module.exports = { @@ -2779,3 +2750,48 @@ module.exports = { return modifyWhitelistInBatch(_csvFilePath, _batchSize); } } + +async function getDisableControllerAckSigner(stAddress, from) { + const typedData = { + types: { + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' } + ], + Acknowledgment: [ + { name: 'text', type: 'string' } + ], + }, + primaryType: 'Acknowledgment', + domain: { + name: 'Polymath', + chainId: 1, + verifyingContract: stAddress + }, + message: { + text: 'I acknowledge that disabling controller is a permanent and irrevocable change', + }, + }; + const result = await new Promise((resolve, reject) => { + web3.currentProvider.send( + { + method: 'eth_signTypedData', + params: [from, typedData] + }, + (err, result) => { + if (err) { + return reject(err); + } + resolve(result.result); + } + ); + }); + // console.log('signed by', from); + // const recovered = sigUtil.recoverTypedSignature({ + // data: typedData, + // sig: result + // }) + // console.log('recovered address', recovered); + return result; +} diff --git a/CLI/commands/wallet_manager.js b/CLI/commands/wallet_manager.js new file mode 100644 index 000000000..f389b404d --- /dev/null +++ b/CLI/commands/wallet_manager.js @@ -0,0 +1,657 @@ +const readlineSync = require('readline-sync'); +const chalk = require('chalk'); +const moment = require('moment'); +const BigNumber = require('bignumber.js'); +const common = require('./common/common_functions'); +const gbl = require('./common/global'); +const contracts = require('./helpers/contract_addresses'); +const abis = require('./helpers/contract_abis'); +const csvParse = require('./helpers/csv'); +const input = require('./IO/input'); + +// App flow +let securityTokenRegistry; +let securityToken; +let polyToken; +let tokenSymbol; +let currentWalletModule; + +const ADD_SCHEDULE_CSV = `${__dirname}/../data/Wallet/VEW/add_schedule_data.csv`; +const ADD_SCHEDULE_FROM_TEMPLATE_CSV = `${__dirname}/../data/Wallet/VEW/add_schedule_from_template_data.csv`; +const MODIFY_SCHEDULE_CSV = `${__dirname}/../data/Wallet/VEW/modify_schedule_data.csv`; +const REVOKE_SCHEDULE_CSV = `${__dirname}/../data/Wallet/VEW/revoke_schedule_data.csv`; + +async function executeApp() { + console.log('\n', chalk.blue('Wallet - Main Menu', '\n')); + + let wModules = await common.getAllModulesByType(securityToken, gbl.constants.MODULES_TYPES.WALLET); + let nonArchivedModules = wModules.filter(m => !m.archived); + if (nonArchivedModules.length > 0) { + console.log(`Wallet modules attached:`); + nonArchivedModules.map(m => `${m.label}: ${m.name} (${m.version}) at ${m.address}`); + } else { + console.log(`There are no Wallet modules attached`); + } + + let options = []; + if (wModules.length > 0) { + options.push('Config existing modules'); + } + options.push('Add new Wallet module'); + + let index = readlineSync.keyInSelect(options, 'What do you want to do?', { cancel: 'EXIT' }); + let optionSelected = index != -1 ? options[index] : 'EXIT'; + console.log('Selected:', optionSelected, '\n'); + switch (optionSelected) { + case 'Config existing modules': + await configExistingModules(nonArchivedModules); + break; + case 'Add new Wallet module': + await addWalletModule(); + break; + case 'EXIT': + return; + } + + await executeApp(); +}; + +async function addWalletModule() { + let moduleList = await common.getAvailableModules(moduleRegistry, gbl.constants.MODULES_TYPES.WALLET, securityToken.options.address); + let options = moduleList.map(m => `${m.name} - ${m.version} (${m.factoryAddress})`); + + let index = readlineSync.keyInSelect(options, 'Which wallet module do you want to add? ', { cancel: 'RETURN' }); + if (index != -1 && readlineSync.keyInYNStrict(`Are you sure you want to add ${options[index]}? `)) { + const moduleABI = abis.vestingEscrowWallet(); + await common.addModule(securityToken, polyToken, moduleList[index].factoryAddress, moduleABI, getVestingEscrowWalletInitializeData); + } +} + +function getVestingEscrowWalletInitializeData(moduleABI) { + const treasuryWallet = input.readAddress('Enter the Ethereum address of the treasury wallet (or leave empty to use treasury wallet from ST): ', gbl.constants.ADDRESS_ZERO); + const configureFunction = moduleABI.find(o => o.name === 'configure' && o.type === 'function'); + const bytes = web3.eth.abi.encodeFunctionCall(configureFunction, [treasuryWallet]); + return bytes; +} + +async function configExistingModules(walletModules) { + let options = walletModules.map(m => `${m.label}: ${m.name} (${m.version}) at ${m.address}`); + let index = readlineSync.keyInSelect(options, 'Which module do you want to config? ', { cancel: 'RETURN' }); + console.log('Selected:', index != -1 ? options[index] : 'RETURN', '\n'); + currentWalletModule = new web3.eth.Contract(abis.vestingEscrowWallet(), walletModules[index].address); + currentWalletModule.setProvider(web3.currentProvider); + + await walletManager(); +} + +async function walletManager() { + console.log(chalk.blue('\n', `Wallet module at ${currentWalletModule.options.address}`), '\n'); + + const treasuryWallet = await currentWalletModule.methods.getTreasuryWallet().call(); + const unassignedTokens = await currentWalletModule.methods.unassignedTokens().call(); + const templates = await currentWalletModule.methods.getAllTemplateNames().call(); + const schedulesForCurrentUser = await currentWalletModule.methods.getScheduleCount(Issuer.address).call(); + + console.log(`- Treasury wallet: ${treasuryWallet}`); + console.log(`- Unassigned Tokens: ${web3.utils.fromWei(unassignedTokens)}`); + console.log(`- Templates: ${templates.length}`); + + let options = ['Change treasury wallet', 'Manage templates', 'Manage schedules', 'Manage multiple schedules in batch', 'Explore account', 'Deposit tokens']; + if (parseInt(unassignedTokens) > 0) { + options.push('Send unassigned tokens to treasury'); + } + if (parseInt(schedulesForCurrentUser) > 0) { + options.push('Pull available tokens'); + } + let index = readlineSync.keyInSelect(options, 'What do you want to do?', { cancel: 'RETURN' }); + let selected = index != -1 ? options[index] : 'RETURN'; + console.log('Selected:', selected, '\n'); + switch (selected) { + case 'Change treasury wallet': + await changeTreasuryWallet(); + break; + case 'Manage templates': + await manageTemplates(templates); + break; + case 'Manage schedules': + const beneficiary = input.readAddress('Enter the beneficiary account from which you want to manage schedules: '); + await manageSchedules(beneficiary, templates); + break; + case 'Manage multiple schedules in batch': + await multipleSchedules(); + break; + case 'Explore account': + const account = input.readAddress('Enter the account you want to explore: '); + await exploreAccount(account); + break; + case 'Deposit tokens': + await depositTokens(); + break; + case 'Send unassigned tokens to treasury': + await sendToTreasury(unassignedTokens); + break + case 'Pull available tokens': + await pullAvailableTokens(); + break + case 'RETURN': + return; + } + + await walletManager(); +} + +async function changeTreasuryWallet() { + let newTreasuryWallet = input.readAddress('Enter the new account address for treasury wallet: '); + let action = currentWalletModule.methods.changeTreasuryWallet(newTreasuryWallet); + let receipt = await common.sendTransaction(action); + let event = common.getEventFromLogs(currentDividendsModule._jsonInterface, receipt.logs, 'TreasuryWalletChanged'); + console.log(chalk.green(`The treasury wallet has been changed successfully to ${event._newWallet}!`)); +} + +async function manageTemplates(templateNames) { + console.log('\n', chalk.blue('Wallet - Template manager', '\n')); + + const allTemplates = await getTemplates(templateNames); + + allTemplates.map(t => console.log(formatTemplateAsString(t), '\n')); + + const options = ['Add template']; + if (templateNames.length > 0) { + options.push('Remove template'); + } + const index = readlineSync.keyInSelect(options, 'What do you want to do?', { cancel: 'RETURN' }); + const selected = index != -1 ? options[index] : 'RETURN'; + console.log('Selected:', selected, '\n'); + switch (selected) { + case 'Add template': + await addTemplate(); + break; + case 'Remove template': + const templateToDelete = (selectTemplate(allTemplates)).name; + await removeTemplate(templateToDelete); + break; + } +} + +async function manageSchedules(beneficiary, allTemplateNames) { + console.log('\n', chalk.blue('Wallet - Schedules manager', '\n')); + + const templateNames = await currentWalletModule.methods.getTemplateNames(beneficiary).call(); + let schedules = []; + if (templateNames.length > 0) { + schedules = await getSchedules(beneficiary, templateNames); + console.log(`Current vesting schedules for ${beneficiary}: ${schedules.length}`); + schedules.map(t => console.log('-', formatScheduleAsString(t), `\n`)); + } else { + console.log(`Current vesting schedules for ${beneficiary}: None`); + } + + const options = ['Add vesting schedule']; + if (schedules.length > 0) { + options.push('Modify vesting schedule', 'Revoke vesting schedule', 'Revoke all vesting schedules', 'Push available tokens'); + } + const index = readlineSync.keyInSelect(options, 'What do you want to do?', { cancel: 'RETURN' }); + const selected = index != -1 ? options[index] : 'RETURN'; + console.log('Selected:', selected, '\n'); + switch (selected) { + case 'Add vesting schedule': + await addSchedule(beneficiary, allTemplateNames); + break; + case 'Modify vesting schedule': + const scheduleToModify = selectSchedule(schedules, true); + if (scheduleToModify) { + await modifySchedule(beneficiary, scheduleToModify); + } else { + console.log(chalk.yellow(`There are no valid schedules to modify`)); + } + break; + case 'Revoke vesting schedule': + const scheduleToRevoke = selectSchedule(schedules, false); + await revokeSchedule(beneficiary, scheduleToRevoke); + break; + case 'Revoke all vesting schedules': + await revokeAllSchedules(beneficiary); + break; + case 'Push available tokens': + await pushAvailableTokens(beneficiary); + break; + case 'RETURN': + return; + } + + await manageSchedules(beneficiary, allTemplateNames); +} + +function formatTemplateAsString(template) { + return `- ${template.name} + Amount: ${template.amount} ${tokenSymbol} + Duration: ${template.duration} seconds + Frequency: ${template.frequency} seconds`; +} + +function formatScheduleAsString(schedule) { + return `Template: ${schedule.templateName} + Amount: ${schedule.amount} ${tokenSymbol} + Duration: ${schedule.duration} seconds + Frequency: ${schedule.frequency} seconds + Start time: ${moment.unix(schedule.startTime).format('MMMM Do YYYY, HH:mm:ss')} + Claimed: ${schedule.claimedTokens} ${tokenSymbol} + State: ${schedule.state === '0' ? 'Created' : schedule.state === '1' ? 'Started' : 'Completed'}`; +} + +function selectTemplate(allTemplates) { + const options = allTemplates.map(t => t.name); + const index = readlineSync.keyInSelect(options, 'Select a template', { cancel: false }); + return allTemplates[index]; +} + +async function addTemplate() { + const templateData = inputTemplateData(); + + const action = currentWalletModule.methods.addTemplate(web3.utils.toHex(templateData.name), web3.utils.toWei(templateData.amount), templateData.duration, templateData.frequency); + const receipt = await common.sendTransaction(action); + const event = common.getEventFromLogs(currentWalletModule._jsonInterface, receipt.logs, 'AddTemplate'); + console.log(chalk.green(`The template has been added successfully!`)); +} + +function inputTemplateData() { + const name = input.readStringNonEmpty('Enter a name for the template: '); + console.log(chalk.yellow(`Note: Values must meet this two requirements to be valid`)); + console.log(chalk.yellow(`1. Frequency must be a factor of duration.`)); + console.log(chalk.yellow(`2. Period count (Duration / Frequency) must be a factor of number of tokens.`)); + const amount = input.readNumberGreaterThan(0, 'Enter the number of tokens that should be assigned to schedule: '); + const duration = input.readNumberGreaterThan(0, 'Enter the duration of the vesting schedule in seconds: '); + const frequency = input.readNumberGreaterThan(0, 'Enter the frequency in seconds at which tokens will be released: '); + return { + name: name, + amount: amount, + duration: duration, + frequency: frequency + }; +} + +async function removeTemplate(templateName) { + const action = currentWalletModule.methods.removeTemplate(web3.utils.toHex(templateName)); + const receipt = await common.sendTransaction(action); + const event = common.getEventFromLogs(currentWalletModule._jsonInterface, receipt.logs, 'RemoveTemplate'); + console.log(chalk.green(`The template has been removed successfully!`)); +} + +async function getTemplates(templateNames) { + // const templateList = await Promise.all(templateNames.map(async function (t) { + // const templateName = web3.utils.hexToUtf8(t); + // const templateData = await currentWalletModule.methods.templates(t).call(); + // return { + // name: templateName, + // amount: web3.utils.fromWei(templateData.numberOfTokens), + // duration: templateData.duration, + // frequency: templateData.frequency + // }; + // })); + + const templateEvents = await currentWalletModule.getPastEvents('AddTemplate', { fromBlock: 0}); + const templateList = templateEvents.map(function (t) { + const templateName = web3.utils.hexToUtf8(t.returnValues._name); + return { + name: templateName, + amount: web3.utils.fromWei(t.returnValues._numberOfTokens), + duration: t.returnValues._duration, + frequency: t.returnValues._frequency + }; + }); + + return templateList; +} + +async function getSchedules(beneficiary, templateNames) { + const scheduleList = await Promise.all(templateNames.map(async function (t) { + const templateName = web3.utils.hexToUtf8(t); + const scheduleData = await currentWalletModule.methods.getSchedule(beneficiary, t).call(); + return { + templateName: templateName, + amount: web3.utils.fromWei(scheduleData[0]), + duration: scheduleData[1], + frequency: scheduleData[2], + startTime: scheduleData[3], + claimedTokens: web3.utils.fromWei(scheduleData[4]), + state: scheduleData[5] + }; + })); + + return scheduleList; +} + +async function addSchedule(beneficiary, allTemplateNames) { + const minuteFromNow = Math.floor(Date.now() / 1000) + 60; + const startTime = input.readDateInTheFuture(`Enter the start date (Unix Epoch time) of the vesting schedule (a minute from now = ${minuteFromNow}): `, minuteFromNow); + + const currentBalance = await securityToken.methods.balanceOf(Issuer.address).call(); + console.log(chalk.yellow(`Your current balance is ${web3.utils.fromWei(currentBalance)} ${tokenSymbol}`)); + + const useTemplate = readlineSync.keyInYNStrict(`Do you want to use an existing template?`); + let action; + let templateData; + if (useTemplate) { + const allTemplates = await getTemplates(allTemplateNames); + templateData = selectTemplate(allTemplates); + action = currentWalletModule.methods.addScheduleFromTemplate(beneficiary, web3.utils.toHex(templateData.name), startTime); + } else { + templateData = inputTemplateData(); + action = currentWalletModule.methods.addSchedule( + beneficiary, + web3.utils.toHex(templateData.name), + web3.utils.toWei(templateData.amount), + templateData.duration, + templateData.frequency, + startTime + ); + } + + const unassignedTokens = await currentWalletModule.methods.unassignedTokens().call(); + const availableTokens = new BigNumber(unassignedTokens).plus(new BigNumber(currentBalance)); + if (availableTokens.isGreaterThanOrEqualTo(new BigNumber(web3.utils.toWei(templateData.amount)))) { + await approveTokens(templateData.amount); + const receipt = await common.sendTransaction(action); + const event = common.getEventFromLogs(currentWalletModule._jsonInterface, receipt.logs, 'AddSchedule'); + console.log(chalk.green('Schedule has been added successfully!')); + } else { + console.log(chalk.red(`\n`, 'You have no enough balance!!!')); + } +} + +function selectSchedule(schedules, onlyCreated) { + let schedulesToShow; + if (onlyCreated) { + schedulesToShow = schedules.filter(s => s.state === '0'); + } else { + schedulesToShow = schedules; + } + + if (schedulesToShow.length > 0) { + const options = schedulesToShow.map(s => `${s.templateName} + Start time: ${moment.unix(parseInt(s.startTime)).format('MMMM Do YYYY, HH:mm:ss')}` + ); + const index = readlineSync.keyInSelect(options, 'Select a schedule', { cancel: false }); + return schedules[index].templateName; + } else { + return null; + } +} + +async function modifySchedule(beneficiary, templateName) { + const minuteFromNow = Math.floor(Date.now() / 1000) + 60; + const startTime = input.readDateInTheFuture(`Enter the new start date (Unix Epoch time) of the vesting schedule (a minute from now = ${minuteFromNow}): `, minuteFromNow); + const action = currentWalletModule.methods.modifySchedule(beneficiary, web3.utils.toHex(templateName), startTime); + const receipt = await common.sendTransaction(action); + const event = common.getEventFromLogs(currentWalletModule._jsonInterface, receipt.logs, 'ModifySchedule'); + console.log(chalk.green('The schedule has been modified successfully!')); +} + +async function revokeSchedule(beneficiary, templateName) { + if (readlineSync.keyInYNStrict(`Are you sure you want to revoke this schedule?`)) { + const action = currentWalletModule.methods.revokeSchedule(beneficiary, web3.utils.toHex(templateName)); + const receipt = await common.sendTransaction(action); + const event = common.getEventFromLogs(currentWalletModule._jsonInterface, receipt.logs, 'RevokeSchedule'); + console.log(chalk.green('The schedule has been revoked successfully!')); + } +} + +async function revokeAllSchedules(beneficiary) { + if (readlineSync.keyInYNStrict(`Are you sure you want to revoke ALL the schedules for this beneficiary?`)) { + const action = currentWalletModule.methods.revokeAllSchedules(beneficiary); + const receipt = await common.sendTransaction(action); + const event = common.getEventFromLogs(currentWalletModule._jsonInterface, receipt.logs, 'RevokeAllSchedules'); + console.log(chalk.green(`All schedules for ${beneficiary} has been revoked successfully!`)); + } +} + +async function approveTokens(amount) { + const action = securityToken.methods.approve(currentWalletModule._address, web3.utils.toWei(amount)); + const receipt = await common.sendTransaction(action); + const event = common.getEventFromLogs(securityToken._jsonInterface, receipt.logs, 'Approval'); + console.log(chalk.green(`${amount} ${tokenSymbol} has been approved successfully!`)); +} + +async function depositTokens() { + const currentBalance = await securityToken.methods.balanceOf(Issuer.address).call(); + console.log(chalk.yellow(`Your current balance is ${web3.utils.fromWei(currentBalance)} ${tokenSymbol}`)); + const amount = input.readNumberGreaterThan(0, `Enter the amount of tokens you want to deposit: `); + if ((new BigNumber(currentBalance)).isGreaterThanOrEqualTo(new BigNumber(web3.utils.toWei(amount)))) { + await approveTokens(amount); + const action = currentWalletModule.methods.depositTokens(web3.utils.toWei(amount)); + const receipt = await common.sendTransaction(action); + const event = common.getEventFromLogs(currentWalletModule._jsonInterface, receipt.logs, 'DepositTokens'); + console.log(chalk.green('Tokens have been deposited successfully!')); + } else { + console.log(chalk.red('You have no enough balance!')); + } +} + +async function sendToTreasury(unassignedTokens) { + const amount = input.readNumberBetween(0, parseFloat(web3.utils.fromWei(unassignedTokens)), `Enter the amount of tokens you want to send to treasury: `); + const action = currentWalletModule.methods.sendToTreasury(web3.utils.toWei(amount)); + const receipt = await common.sendTransaction(action); + const event = common.getEventFromLogs(currentWalletModule._jsonInterface, receipt.logs, 'SendToTreasury'); + console.log(chalk.green('Tokens have been sent to treasury successfully!')); +} + +async function pushAvailableTokens(beneficiary) { + const action = currentWalletModule.methods.pushAvailableTokens(beneficiary); + const receipt = await common.sendTransaction(action); + const event = common.getMultipleEventsFromLogs(currentWalletModule._jsonInterface, receipt.logs, 'SendTokens'); + console.log(chalk.green(`Tokens have been sent to ${beneficiary} successfully!`)); +} + +async function pullAvailableTokens() { + const action = currentWalletModule.methods.pullAvailableTokens(); + const receipt = await common.sendTransaction(action); + const event = common.getMultipleEventsFromLogs(currentWalletModule._jsonInterface, receipt.logs, 'SendTokens'); + console.log(chalk.green(`Tokens have been sent to you successfully!`)); + const currentBalance = await securityToken.methods.balanceOf(Issuer.address).call(); + console.log(chalk.yellow(`Your current balance is ${web3.utils.fromWei(currentBalance)} ${tokenSymbol}`)); +} + +async function exploreAccount(account) { + console.log('\n', chalk.blue(`Wallet - Account explorer for ${account}`, '\n')); + + const currentBalance = await securityToken.methods.balanceOf(account).call(); + console.log(`Current balance: ${web3.utils.fromWei(currentBalance)} ${tokenSymbol}`); + const templateNames = await currentWalletModule.methods.getTemplateNames(account).call(); + let schedules = []; + if (templateNames.length > 0) { + schedules = await getSchedules(account, templateNames); + console.log(`Current vesting schedules: ${schedules.length}`); + schedules.map(t => console.log('-', formatScheduleAsString(t), `\n`)); + } else { + console.log(`Current vesting schedules: None`); + } +} + +async function multipleSchedules() { + console.log('\n', chalk.blue('Wallet - Schedules in batch', '\n')); + + const options = ['Add multiple schedules', 'Add multiple schedules from template', 'Modify multiple schedules', 'Revoke multiple schedules']; + const index = readlineSync.keyInSelect(options, 'What do you want to do?', { cancel: 'RETURN' }); + const selected = index != -1 ? options[index] : 'RETURN'; + console.log('Selected:', selected, '\n'); + switch (selected) { + case 'Add multiple schedules': + await addSchedulesInBatch(); + break; + case 'Add multiple schedules from template': + await addSchedulesFromTemplateInBatch(); + break; + case 'Modify multiple schedules': + await modifySchedulesInBatch(); + break; + case 'Revoke multiple schedules': + await revokeSchedulesInBatch(); + break; + case 'RETURN': + return; + } + + await multipleSchedules(); +} + +async function addSchedulesInBatch() { + let csvFilePath = readlineSync.question(`Enter the path for csv data file (${ADD_SCHEDULE_CSV}): `, { + defaultInput: ADD_SCHEDULE_CSV + }); + let batchSize = input.readNumberGreaterThan(0, `Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, gbl.constants.DEFAULT_BATCH_SIZE); + let parsedData = csvParse(csvFilePath); + let validData = parsedData.filter( + row => web3.utils.isAddress(row[0]) && + typeof row[1] === 'string' && + (!isNaN(row[2])) && + (!isNaN(row[3] && (parseFloat(row[3]) % 1 === 0))) && + (!isNaN(row[4] && (parseFloat(row[3]) % 1 === 0))) && + moment.unix(row[5]).isValid() + ); + + let invalidRows = parsedData.filter(row => !validData.includes(row)); + if (invalidRows.length > 0) { + console.log(chalk.red(`The following lines from csv file are not valid: ${invalidRows.map(r => parsedData.indexOf(r) + 1).join(',')} `)); + } + let batches = common.splitIntoBatches(validData, batchSize); + let [beneficiaryArray, templateNameArray, amountArray, durationArray, frequencyArray, startTimeArray] = common.transposeBatches(batches); + for (let batch = 0; batch < batches.length; batch++) { + console.log(`Batch ${batch + 1} - Attempting to add schedules to the following beneficiaries:\n\n`, beneficiaryArray[batch], '\n'); + templateNameArray[batch] = templateNameArray[batch].map(n => web3.utils.toHex(n)); + amountArray[batch] = amountArray[batch].map(n => web3.utils.toWei(n.toString())); + let action = currentWalletModule.methods.addScheduleMulti(beneficiaryArray[batch], templateNameArray[batch], amountArray[batch], durationArray[batch], frequencyArray[batch], startTimeArray[batch]); + let receipt = await common.sendTransaction(action); + console.log(chalk.green('Add multiple schedules transaction was successful.')); + console.log(`${receipt.gasUsed} gas used.Spent: ${web3.utils.fromWei((new web3.utils.BN(receipt.gasUsed)).mul(new web3.utils.BN(defaultGasPrice)))} ETH`); + } +} + +async function addSchedulesFromTemplateInBatch() { + let csvFilePath = readlineSync.question(`Enter the path for csv data file (${ADD_SCHEDULE_FROM_TEMPLATE_CSV}): `, { + defaultInput: ADD_SCHEDULE_FROM_TEMPLATE_CSV + }); + let batchSize = input.readNumberGreaterThan(0, `Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, gbl.constants.DEFAULT_BATCH_SIZE); + let parsedData = csvParse(csvFilePath); + let validData = parsedData.filter( + row => web3.utils.isAddress(row[0]) && + typeof row[1] === 'string' && + moment.unix(row[2]).isValid() + ); + + let invalidRows = parsedData.filter(row => !validData.includes(row)); + if (invalidRows.length > 0) { + console.log(chalk.red(`The following lines from csv file are not valid: ${invalidRows.map(r => parsedData.indexOf(r) + 1).join(',')} `)); + } + let batches = common.splitIntoBatches(validData, batchSize); + let [beneficiaryArray, templateNameArray, startTimeArray] = common.transposeBatches(batches); + for (let batch = 0; batch < batches.length; batch++) { + console.log(`Batch ${batch + 1} - Attempting to add schedules from template to the following beneficiaries:\n\n`, beneficiaryArray[batch], '\n'); + templateNameArray[batch] = templateNameArray[batch].map(n => web3.utils.toHex(n)); + let action = currentWalletModule.methods.addScheduleFromTemplateMulti(beneficiaryArray[batch], templateNameArray[batch], startTimeArray[batch]); + let receipt = await common.sendTransaction(action); + console.log(chalk.green('Add multiple schedules from template transaction was successful.')); + console.log(`${receipt.gasUsed} gas used.Spent: ${web3.utils.fromWei((new web3.utils.BN(receipt.gasUsed)).mul(new web3.utils.BN(defaultGasPrice)))} ETH`); + } +} + +async function modifySchedulesInBatch() { + let csvFilePath = readlineSync.question(`Enter the path for csv data file (${MODIFY_SCHEDULE_CSV}): `, { + defaultInput: MODIFY_SCHEDULE_CSV + }); + let batchSize = input.readNumberGreaterThan(0, `Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, gbl.constants.DEFAULT_BATCH_SIZE); + let parsedData = csvParse(csvFilePath); + let validData = parsedData.filter( + row => web3.utils.isAddress(row[0]) && + typeof row[1] === 'string' && + moment.unix(row[2]).isValid() + ); + + let invalidRows = parsedData.filter(row => !validData.includes(row)); + if (invalidRows.length > 0) { + console.log(chalk.red(`The following lines from csv file are not valid: ${invalidRows.map(r => parsedData.indexOf(r) + 1).join(',')} `)); + } + let batches = common.splitIntoBatches(validData, batchSize); + let [beneficiaryArray, templateNameArray, startTimeArray] = common.transposeBatches(batches); + for (let batch = 0; batch < batches.length; batch++) { + console.log(`Batch ${batch + 1} - Attempting to modify schedules to the following beneficiaries:\n\n`, beneficiaryArray[batch], '\n'); + templateNameArray[batch] = templateNameArray[batch].map(n => web3.utils.toHex(n)); + let action = currentWalletModule.methods.modifyScheduleMulti(beneficiaryArray[batch], templateNameArray[batch], startTimeArray[batch]); + let receipt = await common.sendTransaction(action); + console.log(chalk.green('Modify multiple schedules transaction was successful.')); + console.log(`${receipt.gasUsed} gas used.Spent: ${web3.utils.fromWei((new web3.utils.BN(receipt.gasUsed)).mul(new web3.utils.BN(defaultGasPrice)))} ETH`); + } +} + +async function revokeSchedulesInBatch() { + let csvFilePath = readlineSync.question(`Enter the path for csv data file (${REVOKE_SCHEDULE_CSV}): `, { + defaultInput: REVOKE_SCHEDULE_CSV + }); + let batchSize = input.readNumberGreaterThan(0, `Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, gbl.constants.DEFAULT_BATCH_SIZE); + let parsedData = csvParse(csvFilePath); + let validData = parsedData.filter( + row => web3.utils.isAddress(row[0]) + ); + + let invalidRows = parsedData.filter(row => !validData.includes(row)); + if (invalidRows.length > 0) { + console.log(chalk.red(`The following lines from csv file are not valid: ${invalidRows.map(r => parsedData.indexOf(r) + 1).join(',')} `)); + } + let batches = common.splitIntoBatches(validData, batchSize); + let [beneficiaryArray] = common.transposeBatches(batches); + for (let batch = 0; batch < batches.length; batch++) { + console.log(`Batch ${batch + 1} - Attempting to revoke schedules to the following beneficiaries:\n\n`, beneficiaryArray[batch], '\n'); + let action = currentWalletModule.methods.revokeSchedulesMulti(beneficiaryArray[batch]); + let receipt = await common.sendTransaction(action); + console.log(chalk.green('Revoke multiple schedules transaction was successful.')); + console.log(`${receipt.gasUsed} gas used.Spent: ${web3.utils.fromWei((new web3.utils.BN(receipt.gasUsed)).mul(new web3.utils.BN(defaultGasPrice)))} ETH`); + } +} + +async function initialize(_tokenSymbol) { + welcome(); + await setup(); + securityToken = await common.selectToken(securityTokenRegistry, _tokenSymbol); + if (securityToken === null) { + process.exit(0); + } else { + tokenSymbol = await securityToken.methods.symbol().call(); + } +} + +function welcome() { + common.logAsciiBull(); + console.log("******************************************"); + console.log("Welcome to the Command-Line Wallet Manager"); + console.log("******************************************"); + console.log("Issuer Account: " + Issuer.address + "\n"); +} + +async function setup() { + try { + let securityTokenRegistryAddress = await contracts.securityTokenRegistry(); + let iSecurityTokenRegistryABI = abis.iSecurityTokenRegistry(); + securityTokenRegistry = new web3.eth.Contract(iSecurityTokenRegistryABI, securityTokenRegistryAddress); + securityTokenRegistry.setProvider(web3.currentProvider); + + let polyTokenAddress = await contracts.polyToken(); + let polyTokenABI = abis.polyToken(); + polyToken = new web3.eth.Contract(polyTokenABI, polyTokenAddress); + polyToken.setProvider(web3.currentProvider); + + let moduleRegistryAddress = await contracts.moduleRegistry(); + let moduleRegistryABI = abis.moduleRegistry(); + moduleRegistry = new web3.eth.Contract(moduleRegistryABI, moduleRegistryAddress); + moduleRegistry.setProvider(web3.currentProvider); + } catch (err) { + console.log(err) + console.log('\x1b[31m%s\x1b[0m', "There was a problem getting the contracts. Make sure they are deployed to the selected network."); + process.exit(0); + } +} + +module.exports = { + executeApp: async function (_tokenSymbol) { + await initialize(_tokenSymbol); + return executeApp(); + } +} diff --git a/CLI/data/Transfer/GTM/flag_data.csv b/CLI/data/Transfer/GTM/flag_data.csv new file mode 100644 index 000000000..0488a2301 --- /dev/null +++ b/CLI/data/Transfer/GTM/flag_data.csv @@ -0,0 +1,10 @@ +0xee7ae74d964f2be7d72c1b187b38e2ed3615d4d1,0,true +0xee7ae74d964f2be7d72c1b187b38e2ed3615d4d1,2,true +0x2f0fd672bf222413cc69dc1f4f1d7e93ad1763a1,0,true +0x2f0fd672bf222413cc69dc1f4f1d7e93ad1763a1,1,true +0x2f0fd672bf222413cc69dc1f4f1d7e93ad1763a1,2,true +0xac297053173b02b02a737d47f7b4a718e5b170ef,1,true +0x49fc0b78238dab644698a90fa351b4c749e123d2,1,true +0x10223927009b8add0960359dd90d1449415b7ca9,2,true +0x3c65cfe3de848cf38e9d76e9c3e57a2f1140b399,1,true +0x3c65cfe3de848cf38e9d76e9c3e57a2f1140b399,5,true diff --git a/CLI/data/Transfer/VRTM/add_custom_restriction_data.csv b/CLI/data/Transfer/VRTM/add_custom_restriction_data.csv index 1807e90e6..dfc50ff47 100644 --- a/CLI/data/Transfer/VRTM/add_custom_restriction_data.csv +++ b/CLI/data/Transfer/VRTM/add_custom_restriction_data.csv @@ -1,8 +1,8 @@ -0xee7ae74d964f2be7d72c1b187b38e2ed3615d4d1,1000,8/1/2019,90,10/10/2019,"Fixed" +0xee7ae74d964f2be7d72c1b187b38e2ed3615d4d1,1000,8/1/2019,90,12/10/2019,"Fixed" 0x2f0fd672bf222413cc69dc1f4f1d7e93ad1763a1,2000,8/2/2019,30,10/12/2019,"Fixed" 0xac297053173b02b02a737d47f7b4a718e5b170ef,500,8/1/2019,15,10/1/2019,"Fixed" -0x49fc0b78238dab644698a90fa351b4c749e123d2,0.15,8/5/2019,90,10/10/2019,"Percentage" +0x49fc0b78238dab644698a90fa351b4c749e123d2,0.15,8/5/2019,90,12/10/2019,"Percentage" 0x10223927009b8add0960359dd90d1449415b7ca9,0.25,8/3/2019,30,10/15/2019,"Percentage" 0x3c65cfe3de848cf38e9d76e9c3e57a2f1140b399,0.1,8/10/2019,15,10/10/2019,"Percentage" 0xabf60de3265b3017db7a1be66fc8b364ec1dbb98,1234,8/20/2019,10,10/22/2019,"Fixed" -0xb841fe5a89da1bbef2d0805fbd7ffcbbb2fca5e3,5678,8/1/2019,2,10/10/2019,"Fixed" \ No newline at end of file +0xb841fe5a89da1bbef2d0805fbd7ffcbbb2fca5e3,5678,8/1/2019,2,10/10/2019,"Fixed" diff --git a/CLI/data/Transfer/VRTM/modify_custom_restriction_data.csv b/CLI/data/Transfer/VRTM/modify_custom_restriction_data.csv index b69f43361..162f8a1b0 100644 --- a/CLI/data/Transfer/VRTM/modify_custom_restriction_data.csv +++ b/CLI/data/Transfer/VRTM/modify_custom_restriction_data.csv @@ -1,5 +1,5 @@ -0xee7ae74d964f2be7d72c1b187b38e2ed3615d4d1,2000,8/1/2019,90,10/10/2019,"Fixed" +0xee7ae74d964f2be7d72c1b187b38e2ed3615d4d1,2000,8/1/2019,90,12/10/2019,"Fixed" 0x2f0fd672bf222413cc69dc1f4f1d7e93ad1763a1,2000,8/2/2019,30,10/10/2019,"Fixed" 0xac297053173b02b02a737d47f7b4a718e5b170ef,500,8/1/2019,20,10/10/2019,"Fixed" -0x49fc0b78238dab644698a90fa351b4c749e123d2,0.15,8/1/2019,90,20/10/2019,"Percentage" -0x10223927009b8add0960359dd90d1449415b7ca9,25,8/1/2019,30,10/10/2019,"Fixed" \ No newline at end of file +0x49fc0b78238dab644698a90fa351b4c749e123d2,0.15,8/1/2019,90,12/10/2019,"Percentage" +0x10223927009b8add0960359dd90d1449415b7ca9,25,8/1/2019,30,10/10/2019,"Fixed" diff --git a/CLI/data/Wallet/VEW/add_schedule_data.csv b/CLI/data/Wallet/VEW/add_schedule_data.csv new file mode 100644 index 000000000..c7ec7a926 --- /dev/null +++ b/CLI/data/Wallet/VEW/add_schedule_data.csv @@ -0,0 +1,3 @@ +0xee7ae74d964f2be7d72c1b187b38e2ed3615d4d1,First Example,1000,100,10,1570794204 +0x2f0fd672bf222413cc69dc1f4f1d7e93ad1763a1,Second Example,5000,2500,50,1570794204 +0xac297053173b02b02a737d47f7b4a718e5b170ef,Third Example,3000,3000,150,1573991004 \ No newline at end of file diff --git a/CLI/data/Wallet/VEW/add_schedule_from_template_data.csv b/CLI/data/Wallet/VEW/add_schedule_from_template_data.csv new file mode 100644 index 000000000..1abfc6164 --- /dev/null +++ b/CLI/data/Wallet/VEW/add_schedule_from_template_data.csv @@ -0,0 +1,3 @@ +0x49fc0b78238dab644698a90fa351b4c749e123d2,First Example,1570794204 +0x10223927009b8add0960359dd90d1449415b7ca9,Second Example,1570794204 +0x3c65cfe3de848cf38e9d76e9c3e57a2f1140b399,Third Example,1573991004 \ No newline at end of file diff --git a/CLI/data/Wallet/VEW/modify_schedule_data.csv b/CLI/data/Wallet/VEW/modify_schedule_data.csv new file mode 100644 index 000000000..25a04645f --- /dev/null +++ b/CLI/data/Wallet/VEW/modify_schedule_data.csv @@ -0,0 +1,3 @@ +0x49fc0b78238dab644698a90fa351b4c749e123d2,First Example,1573991004 +0x10223927009b8add0960359dd90d1449415b7ca9,Second Example,1573991004 +0x3c65cfe3de848cf38e9d76e9c3e57a2f1140b399,Third Example,1570794204 \ No newline at end of file diff --git a/CLI/data/Wallet/VEW/revoke_schedule_data.csv b/CLI/data/Wallet/VEW/revoke_schedule_data.csv new file mode 100644 index 000000000..ac0d50cd4 --- /dev/null +++ b/CLI/data/Wallet/VEW/revoke_schedule_data.csv @@ -0,0 +1,3 @@ +0xee7ae74d964f2be7d72c1b187b38e2ed3615d4d1 +0x2f0fd672bf222413cc69dc1f4f1d7e93ad1763a1 +0xac297053173b02b02a737d47f7b4a718e5b170ef \ No newline at end of file diff --git a/CLI/package.json b/CLI/package.json index 354ecc878..a37a57ffe 100644 --- a/CLI/package.json +++ b/CLI/package.json @@ -7,7 +7,7 @@ "stable_coin": "scripts/stable_coin.sh" }, "author": "Polymath Inc", - "license": "MIT", + "license": "Apache-2.0", "dependencies": { "bignumber.js": "^8.1.1", "chalk": "^2.4.1", diff --git a/CLI/polymath-cli.js b/CLI/polymath-cli.js index 66b8badf8..d7462e879 100644 --- a/CLI/polymath-cli.js +++ b/CLI/polymath-cli.js @@ -9,12 +9,13 @@ const transfer = require('./commands/transfer'); const transfer_ownership = require('./commands/transfer_ownership'); const dividends_manager = require('./commands/dividends_manager'); const transfer_manager = require('./commands/transfer_manager'); +const wallet_manager = require('./commands/wallet_manager'); const contract_manager = require('./commands/contract_manager'); const strMigrator = require('./commands/strMigrator'); const permission_manager = require('./commands/permission_manager'); const time = require('./commands/helpers/time'); const gbl = require('./commands/common/global'); -const program = require('commander'); +const program = require('commander'); const moment = require('moment'); const yaml = require('js-yaml'); const fs = require('fs'); @@ -133,6 +134,16 @@ program } }); + program + .command('wallet_manager') + .alias('w') + .option('-t, --securityToken ', 'Selects a ST to manage transfer modules') + .description('Runs wallet_manager') + .action(async function (cmd) { + await gbl.initialize(program.remoteNode); + await wallet_manager.executeApp(cmd.securityToken); + }); + program .command('contract_manager') .alias('cm') @@ -162,26 +173,26 @@ program await permission_manager.executeApp(); }); - program - .command('time_travel') - .alias('tt') - .option('-p, --period ', 'Period of time in seconds to increase') - .option('-d, --toDate ', 'Human readable date ("MM/DD/YY [HH:mm:ss]") to travel to') - .option('-e, --toEpochTime ', 'Unix Epoch time to travel to') - .description('Increases time on EVM according to given value.') - .action(async function (cmd) { - await gbl.initialize(program.remoteNode); - if (cmd.period) { - await time.increaseTimeByDuration(parseInt(cmd.period)); - } else if (cmd.toDate) { - await time.increaseTimeToDate(cmd.toDate); - } else if (cmd.toEpochTime) { - await time.increaseTimeToEpochDate(cmd.toEpochTime); - } + program + .command('time_travel') + .alias('tt') + .option('-p, --period ', 'Period of time in seconds to increase') + .option('-d, --toDate ', 'Human readable date ("MM/DD/YY [HH:mm:ss]") to travel to') + .option('-e, --toEpochTime ', 'Unix Epoch time to travel to') + .description('Increases time on EVM according to given value.') + .action(async function (cmd) { + await gbl.initialize(program.remoteNode); + if (cmd.period) { + await time.increaseTimeByDuration(parseInt(cmd.period)); + } else if (cmd.toDate) { + await time.increaseTimeToDate(cmd.toDate); + } else if (cmd.toEpochTime) { + await time.increaseTimeToEpochDate(cmd.toEpochTime); + } }); program.parse(process.argv); if (typeof program.commands.length == 0) { console.error('No command given!'); process.exit(1); -} \ No newline at end of file +} diff --git a/CLI/yarn.lock b/CLI/yarn.lock index 147edb6a6..2cb4b3482 100644 --- a/CLI/yarn.lock +++ b/CLI/yarn.lock @@ -870,10 +870,10 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= -fstream@^1.0.12, fstream@^1.0.8: - version "1.0.12" - resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.12.tgz#4e8ba8ee2d48be4f7d0de505455548eae5932045" - integrity sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg== +fstream@^1.0.2, fstream@^1.0.8: + version "1.0.11" + resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.11.tgz#5c1fb1f117477114f0632a0eb4b71b3cb0fd3171" + integrity sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE= dependencies: graceful-fs "^4.1.2" inherits "~2.0.0" @@ -900,10 +900,10 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" -glob@^7.1.3: - version "7.1.4" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255" - integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A== +glob@^7.0.5: + version "7.1.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" + integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" @@ -941,9 +941,9 @@ got@7.1.0, got@^7.1.0: url-to-options "^1.0.1" graceful-fs@^4.1.10, graceful-fs@^4.1.2, graceful-fs@^4.1.6: - version "4.2.0" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.0.tgz#8d8fdc73977cb04104721cb53666c1ca64cd328b" - integrity sha512-jpSvDPV4Cq/bgtpndIWbI5hmYxhQGHPC4d4cqBPb4DLniCfhJokdXhwhaDuLBGLQdvvRum/UiX6ECVIPvDXqdg== + version "4.1.15" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00" + integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA== "graceful-readlink@>= 1.0.0": version "1.0.1" @@ -1057,12 +1057,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -inherits@2.0.3: +inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= @@ -1201,9 +1196,9 @@ keccakjs@^0.2.1: sha3 "^1.1.0" lodash@^4.13.1, lodash@^4.17.11: - version "4.17.15" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" - integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== + version "4.17.11" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" + integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== lowercase-keys@^1.0.0: version "1.0.1" @@ -1657,11 +1652,11 @@ request@^2.79.0, request@^2.88.0: uuid "^3.3.2" rimraf@2: - version "2.6.3" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" - integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== + version "2.6.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" + integrity sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w== dependencies: - glob "^7.1.3" + glob "^7.0.5" ripemd160@^2.0.0, ripemd160@^2.0.1: version "2.0.2" @@ -1940,12 +1935,12 @@ tar.gz@^1.0.5: tar "^2.1.1" tar@^2.1.1: - version "2.2.2" - resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.2.tgz#0ca8848562c7299b8b446ff6a4d60cdbb23edc40" - integrity sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA== + version "2.2.1" + resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1" + integrity sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE= dependencies: block-stream "*" - fstream "^1.0.12" + fstream "^1.0.2" inherits "2" thenify-all@^1.0.0, thenify-all@^1.6.0: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fc1a6d701..3dadf0761 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,28 +1,12 @@ -# Contributing to Polymath +# Contributing Guidelines -🎉 First off, thanks for taking the time to contribute! 🎉 - -The following is a set of guidelines for contributing to Polymath-Core. Use your best judgment, and feel free to propose changes to this document in a pull request. - -## Table of Contents - -1. Do you have a question about Polymath's codebase? -2. Please Report bugs! -3. Contributing -4. Code Styleguide -5. Code of Conduct -6. Scope - - -## 1. Do you have a question about Polymath's codebase? +## Do you have a question? [Check out our Gitter](https://gitter.im/PolymathNetwork/Lobby) -You can also check out our frequently asked questions tagged with Polymath on Stack Exchange and Reddit. - -For questions about the dApp, please visit our [Zendesk](https://polymath.zendesk.com/hc/en-us) +See also frequently asked questions tagged with Polymath on Stack Exchange and Reddit. -# 2. Please Report bugs! +# Please Report bugs! 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. @@ -31,28 +15,32 @@ Otherwise, just create a new issue in our repository and follow the template bel [Issue template to make things easier for you](https://github.com/PolymathNetwork/polymath-core/blob/master/.github/ISSUE_TEMPLATE/feature_request.md) -# 3. Contributing Process +# Contribute! -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. The process to contribute to Polymath-core is the following: - -1. To get started, please fork the most recent development branch [here](https://github.com/PolymathNetwork/polymath-core/tree/dev-2.1.0) -2. Fix bugs or implement features. Please make sure to take extra steps to highlight the changes you are implementing by putting comments in your code. -3. Propose a [pull request](https://github.com/PolymathNetwork/polymath-core/blob/master/PULL_REQUEST_TEMPLATE.md) +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. -Please note we have a code of conduct, please follow it. +# 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) -# 4. Code Styleguide +# Code Styleguide The polymath-core repo follows the [Solidity style guide](https://solidity.readthedocs.io/en/v0.4.24/style-guide.html) -# 5. Code of Conduct +# 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. -# 6. Scope +# Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. diff --git a/LICENSE b/LICENSE index 3d6b0c7d6..f13e3c7df 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,201 @@ -MIT License - -Copyright (c) 2018 Polymath - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2017, Polymath Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index 5e7d97328..bacf84f64 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ contract IST20 { # The Polymath Core Architecture The diagram below depicts a high-level view of the various modules, registries, and contracts implemented within Polymath Core 2.0.0: -![Polymath Core architecture](https://github.com/PolymathNetwork/polymath-core/blob/master/docs/images/Core%20Architecture%202.0.0%20%20Diagram.png) +![Polymath Core architecture](https://github.com/PolymathNetwork/polymath-core/blob/dev-3.0.0/docs/images/Polymath%20Core%20v3.png) ## Components ### Polymath Registries @@ -117,7 +117,30 @@ You can easily navigate through it with the sidebar directory in order to run th ## Mainnet -### v2.1.0 +### v3.0.0 + +| Contract | Address | +| ---------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | +| SecurityTokenRegistry (Data Store): | [0x240f9f86b1465bf1b8eb29bc88cbf65573dfdd97](https://etherscan.io/address/0x240f9f86b1465bf1b8eb29bc88cbf65573dfdd97) | +| SecurityTokenRegistry (Logic): | [0x92274793a65a0de42bb4bf19b393930863877630](https://etherscan.io/address/0x92274793a65a0de42bb4bf19b393930863877630) | +| ModuleRegistry (Data Store): | [0x4566d68ea96fc2213f2446f0dd0f482146cee96d](https://etherscan.io/address/0x4566d68ea96fc2213f2446f0dd0f482146cee96d) | +| ModuleRegistry (Logic): | [0x7550fe3308ba534b44e94c83cd08b7e3c5b96db5](https://etherscan.io/address/0x7550fe3308ba534b44e94c83cd08b7e3c5b96db5) | +| Polymath Registry: | [0xdfabf3e4793cd30affb47ab6fa4cf4eef26bbc27](https://etherscan.io/address/0xdfabf3e4793cd30affb47ab6fa4cf4eef26bbc27) | +| Feature Registry: | [0xa3eacb03622bf1513880892b7270d965f693ffb5](https://etherscan.io/address/0xa3eacb03622bf1513880892b7270d965f693ffb5) | +| ETHOracle: | [0x60055e9a93aae267da5a052e95846fa9469c0e7a](https://etherscan.io/address/0x60055e9a93aae267da5a052e95846fa9469c0e7a) | +| POLYOracle: | [0x52cb4616E191Ff664B0bff247469ce7b74579D1B](https://etherscan.io/address/0x52cb4616E191Ff664B0bff247469ce7b74579D1B) | +| General Transfer Manager Factory: | [0x5fafcfc0afd80d2f95133170172b045024ca8fd1](https://etherscan.io/address/0x5fafcfc0afd80d2f95133170172b045024ca8fd1) | +| General Permission Manager Factory: | [0xeb4c8c9d71cbe60ca0e688e4e70c5ab22abb72a4](https://etherscan.io/address/0xeb4c8c9d71cbe60ca0e688e4e70c5ab22abb72a4) | +| CappedSTOFactory: | [0x7c64e9cfc397db2da3213a172d783f1b9c30d7ef](https://etherscan.io/address/0x7c64e9cfc397db2da3213a172d783f1b9c30d7ef) | +| USDTieredSTO Factory: | [0x80ae6e1b6dc661d21ee1680bd5ff919f0400f17d](https://etherscan.io/address/0x80ae6e1b6dc661d21ee1680bd5ff919f0400f17d) | +| ERC20 Dividends Checkpoint Factory: | [0x550fc7d520f596bfdf75dca4d9f5f3c0c6020212](https://etherscan.io/address/0x550fc7d520f596bfdf75dca4d9f5f3c0c6020212) | +| Count Transfer Manager Factory: | [0xA8e0a4E7f0cdECF43AFbA0360B6f64412Df2e6B0](https://etherscan.io/address/0xA8e0a4E7f0cdECF43AFbA0360B6f64412Df2e6B0) | +| Percentage Transfer Manager Factory: | [0x5732ee7ef44dc5ab7b7cbac8ada5268c96895ca5](https://etherscan.io/address/0x5732ee7ef44dc5ab7b7cbac8ada5268c96895ca5) | +| Manual Approval Transfer Manager Factory: | [0x156389b30ae9e5ca8ec9e55ff529738480e42214](https://etherscan.io/address/0x156389b30ae9e5ca8ec9e55ff529738480e42214) | + + + +### v2.0.0 | Contract | Address | | ---------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | @@ -127,15 +150,15 @@ You can easily navigate through it with the sidebar directory in order to run th | Feature Registry: | [0xa3eacb03622bf1513880892b7270d965f693ffb5](https://etherscan.io/address/0xa3eacb03622bf1513880892b7270d965f693ffb5) | | ETHOracle: | [0x60055e9a93aae267da5a052e95846fa9469c0e7a](https://etherscan.io/address/0x60055e9a93aae267da5a052e95846fa9469c0e7a) | | POLYOracle: | [0x52cb4616E191Ff664B0bff247469ce7b74579D1B](https://etherscan.io/address/0x52cb4616E191Ff664B0bff247469ce7b74579D1B) | -| General Transfer Manager Factory (2.0.0): | [0xdc95598ef2bbfdb66d02d5f3eea98ea39fbc8b26](https://etherscan.io/address/0xdc95598ef2bbfdb66d02d5f3eea98ea39fbc8b26) | -| General Transfer Manager Factory (2.1.0): | [0xa8b60c9b7054782f46931e35e7012037a574ecee](https://etherscan.io/address/0xa8b60c9b7054782f46931e35e7012037a574ecee) | +| General Transfer Manager Factory: | [0xdc95598ef2bbfdb66d02d5f3eea98ea39fbc8b26](https://etherscan.io/address/0xdc95598ef2bbfdb66d02d5f3eea98ea39fbc8b26) | | General Permission Manager Factory: | [0xf0aa1856360277c60052d6095c5b787b01388cdd](https://etherscan.io/address/0xf0aa1856360277c60052d6095c5b787b01388cdd) | -| CappedSTOFactory (2.1.0): | [0x26bcd18a748ade7fafb250bb014c7121a412870c](https://etherscan.io/address/0x26bcd18a748ade7fafb250bb014c7121a412870c) | -| USDTieredSTO Factory (2.1.0): | [0x0148ed61ffa8e0da7908a62aa2d1f266688656c4](https://etherscan.io/address/0x0148ed61ffa8e0da7908a62aa2d1f266688656c4) | -| ERC20 Dividends Checkpoint Factory (2.1.0): | [0x855152c9b98babadc996a0dd71c8b3682b8f4786](https://etherscan.io/address/0x855152c9b98babadc996a0dd71c8b3682b8f4786) | -| Count Transfer Manager Factory (2.1.0): | [0x575ed30ec5700f369115b079be8059001e79eb7b](https://etherscan.io/address/0x575ed30ec5700f369115b079be8059001e79eb7b) | +| CappedSTOFactory: | [0x77d89663e8819023a87bfe2bc9baaa6922c0e57c](https://etherscan.io/address/0x77d89663e8819023a87bfe2bc9baaa6922c0e57c) | +| USDTieredSTO Factory: | [0x5a3a30bddae1f857a19b1aed93b5cdb3c3da809a](https://etherscan.io/address/0x5a3a30bddae1f857a19b1aed93b5cdb3c3da809a) | +| EthDividendsCheckpointFactory: | [0x968c74c52f15b2de323eca8c677f6c9266bfefd6](https://etherscan.io/address/0x968c74c52f15b2de323eca8c677f6c9266bfefd6) | +| ERC20 Dividends Checkpoint Factory: | [0x82f9f1ab41bacb1433c79492e54bf13bccd7f9ae](https://etherscan.io/address/0x82f9f1ab41bacb1433c79492e54bf13bccd7f9ae) | +| Count Transfer Manager Factory: | [0xd9fd7e34d6e2c47a69e02131cf8554d52c3445d5](https://etherscan.io/address/0xd9fd7e34d6e2c47a69e02131cf8554d52c3445d5) | | Percentage Transfer Manager Factory: | [0xe6267a9c0a227d21c95b782b1bd32bb41fc3b43b](https://etherscan.io/address/0xe6267a9c0a227d21c95b782b1bd32bb41fc3b43b) | -| Manual Approval Transfer Manager Factory (2.1.0): | [0xe5b21cf83f5e49aba4601e8d8cf182f889208cfd](https://etherscan.io/address/0xe5b21cf83f5e49aba4601e8d8cf182f889208cfd) | +| Manual Approval Transfer Manager Factory (2.0.1): | [0x6af2afad53cb334e62b90ddbdcf3a086f654c298](https://etherscan.io/address/0x6af2afad53cb334e62b90ddbdcf3a086f654c298) | New SecurityTokenRegistry (2.0.1): 0x538136ed73011a766bf0a126a27300c3a7a2e6a6 @@ -144,28 +167,60 @@ New SecurityTokenRegistry (2.0.1): 0x538136ed73011a766bf0a126a27300c3a7a2e6a6 New ModuleRegistry (2.0.1): 0xbc18f144ccf87f2d98e6fa0661799fcdc3170119 (fixed bug with missing transferOwnership function) +New ManualApprovalTransferManager 0x6af2afad53cb334e62b90ddbdcf3a086f654c298 +(Fixed 0x0 from bug) + ## KOVAN -### v2.1.0 +### v3.0.0 + +| Contract | Address | +| ---------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | +| SecurityTokenRegistry (Data Store): | [0xbefb81114d532bddddc724af20c3516fa75f0afb](https://kovan.etherscan.io/address/0xbefb81114d532bddddc724af20c3516fa75f0afb) | +| SecurityTokenRegistry (Logic): | [0x71A4F01F6Dee751eEDc6E16FD25AC45b46e1b0d9](https://kovan.etherscan.io/address/0x71A4F01F6Dee751eEDc6E16FD25AC45b46e1b0d9) | +| ModuleRegistry (Data Store): | [0x0fac8d8cce224eead73c1187df96570aa80a568b](https://kovan.etherscan.io/address/0x0fac8d8cce224eead73c1187df96570aa80a568b) | +| ModuleRegistry (Logic): | [0xC5203791C9d46161B378deaDa89A1D5B67Ba23e3](https://kovan.etherscan.io/address/0xC5203791C9d46161B378deaDa89A1D5B67Ba23e3) | +| Polymath Registry: | [0x9903e7b5acfe5fa9713771a8d861eb1df8cd7046](https://kovan.etherscan.io/address/0x9903e7b5acfe5fa9713771a8d861eb1df8cd7046) | +| Feature Registry: | [0xa8f85006fdacb3d59ffae564c05433f0c949e911](https://kovan.etherscan.io/address/0xa8f85006fdacb3d59ffae564c05433f0c949e911) | +| ETHOracle: | [0xCE5551FC9d43E9D2CC255139169FC889352405C8](https://kovan.etherscan.io/address/0xCE5551FC9d43E9D2CC255139169FC889352405C8) | +| POLYOracle: | [0x461d98EF2A0c7Ac1416EF065840fF5d4C946206C](https://kovan.etherscan.io/address/0x461d98EF2A0c7Ac1416EF065840fF5d4C946206C) | +| General Transfer Manager Factory: | [0x5D92B852c31C0dd3409285339051c7594eaE198e](https://kovan.etherscan.io/address/0x5D92B852c31C0dd3409285339051c7594eaE198e) | +| General Permission Manager Factory: | [0x559a15fa038c3FB84e993BE06235E7D9A0D1cB7d](https://kovan.etherscan.io/address/0x559a15fa038c3FB84e993BE06235E7D9A0D1cB7d) | +| CappedSTOFactory: | [0x7CEa4A1Eced1a035A6BD5e673454f6Bc8c98b20E](https://kovan.etherscan.io/address/0x7CEa4A1Eced1a035A6BD5e673454f6Bc8c98b20E) | +| USDTieredSTO Factory: | [0x0C260C11B46827E9d96F9a5C7DDbb66907e2b0F3](https://kovan.etherscan.io/address/0x0C260C11B46827E9d96F9a5C7DDbb66907e2b0F3) | +| ERC20 Dividends Checkpoint Factory: | [0xE74A013FbE7B6EF5F3b4B45Ce4745dCBA3197856](https://kovan.etherscan.io/address/0xE74A013FbE7B6EF5F3b4B45Ce4745dCBA3197856) | +| Count Transfer Manager Factory: | [0xbA6893CfdDdEc76dB8a4d8f833a81F456fB64e2c](https://kovan.etherscan.io/address/0xbA6893CfdDdEc76dB8a4d8f833a81F456fB64e2c) | +| Percentage Transfer Manager Factory: | [0x127dcA5040f5B943100D4c4154fA4F7744e9482D](https://kovan.etherscan.io/address/0x127dcA5040f5B943100D4c4154fA4F7744e9482D) | +| Manual Approval Transfer Manager Factory: | [0xFcd05Ab2B494577AbE0a4549b2FBec6e1bce32C9](https://kovan.etherscan.io/address/0xFcd05Ab2B494577AbE0a4549b2FBec6e1bce32C9) | + + + +### v2.0.0 New Kovan PolyTokenFaucet: 0xb347b9f5b56b431b2cf4e1d90a5995f7519ca792 | Contract | Address | | ---------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | -| SecurityTokenRegistry (Proxy): | [0x91110c2f67e2881a8540417be9eadf5bc9f2f248](https://kovan.etherscan.io/address/0x91110c2f67e2881a8540417be9eadf5bc9f2f248) | -| ModuleRegistry (Proxy): | [0xde6d19d7a68d453244227b6ccc5d8e6c2314627a](https://kovan.etherscan.io/address/0xde6d19d7a68d453244227b6ccc5d8e6c2314627a) | -| Polymath Registry: | [0x5b215a7d39ee305ad28da29bf2f0425c6c2a00b3](https://kovan.etherscan.io/address/0x5b215a7d39ee305ad28da29bf2f0425c6c2a00b3) | -| Feature Registry: | [0x8967a7cfc4b455398be2356cd05cd43b7a39697e](https://kovan.etherscan.io/address/0x8967a7cfc4b455398be2356cd05cd43b7a39697e) | +| SecurityTokenRegistry (Proxy): | [0xbefb81114d532bddddc724af20c3516fa75f0afb](https://kovan.etherscan.io/address/0xbefb81114d532bddddc724af20c3516fa75f0afb) | +| ModuleRegistry (Proxy): | [0x0fac8d8cce224eead73c1187df96570aa80a568b](https://kovan.etherscan.io/address/0x0fac8d8cce224eead73c1187df96570aa80a568b) | +| Polymath Registry: | [0x9903e7b5acfe5fa9713771a8d861eb1df8cd7046](https://kovan.etherscan.io/address/0x9903e7b5acfe5fa9713771a8d861eb1df8cd7046) | +| Feature Registry: | [0xa8f85006fdacb3d59ffae564c05433f0c949e911](https://kovan.etherscan.io/address/0xa8f85006fdacb3d59ffae564c05433f0c949e911) | | ETHOracle: | [0xCE5551FC9d43E9D2CC255139169FC889352405C8](https://kovan.etherscan.io/address/0xCE5551FC9d43E9D2CC255139169FC889352405C8) | | POLYOracle: | [0x461d98EF2A0c7Ac1416EF065840fF5d4C946206C](https://kovan.etherscan.io/address/0x461d98EF2A0c7Ac1416EF065840fF5d4C946206C) | -| General Transfer Manager Factory: | [0x650e9507e983077d6f822472a7dcc37626d55c7f](https://kovan.etherscan.io/address/0x650e9507e983077d6f822472a7dcc37626d55c7f) | -| General Permission Manager Factory: | [0xbf0bd6305b523ce055baa6dfaa9676d6b9e6090b](https://kovan.etherscan.io/address/0xbf0bd6305b523ce055baa6dfaa9676d6b9e6090b) | -| CappedSTOFactory: | [0x01510b2c03296473f883c12d0723f0a46aa67f13](https://kovan.etherscan.io/address/0x01510b2c03296473f883c12d0723f0a46aa67f13) | -| USDTieredSTO Factory: | [0x8b9743e6129f7b7cca04e3611b5c8cd9b1d11e90](https://kovan.etherscan.io/address/0x8b9743e6129f7b7cca04e3611b5c8cd9b1d11e90) | -| ERC20 Dividends Checkpoint Factory: | [0x4369751df5bcb2f12f1790f525ef212a622b9c60](https://kovan.etherscan.io/address/0x4369751df5bcb2f12f1790f525ef212a622b9c60) | -| Count Transfer Manager Factory: | [0xc7cf0c1ddc85c18672951f9bfeb7163ecc8f0e2f](https://kovan.etherscan.io/address/0xc7cf0c1ddc85c18672951f9bfeb7163ecc8f0e2f) | -| Percentage Transfer Manager Factory: | [0xfea5fcb254bcb4ada0f86903ff822d6372325cb1](https://kovan.etherscan.io/address/0xfea5fcb254bcb4ada0f86903ff822d6372325cb1) | +| General Transfer Manager Factory: | [0xfe7e2bb6c200d5222c82d0f8fecca5f8fe4ab8ce](https://kovan.etherscan.io/address/0xfe7e2bb6c200d5222c82d0f8fecca5f8fe4ab8ce) | +| General Permission Manager Factory: | [0xde5eaa8d73f43fc5e7badb203f03ecae2b29bd92](https://kovan.etherscan.io/address/0xde5eaa8d73f43fc5e7badb203f03ecae2b29bd92) | +| CappedSTOFactory: | [0xe14d7dd044cc6cfe37548b6791416c59f19bfc0d](https://kovan.etherscan.io/address/0xe14d7dd044cc6cfe37548b6791416c59f19bfc0d) | +| USDTieredSTO Factory: | [0xf9f0bb9f868d411dd9a9511a79d172449e3c15f5](https://kovan.etherscan.io/address/0xf9f0bb9f868d411dd9a9511a79d172449e3c15f5) | +| EthDividendsCheckpointFactory: | [0x2861425ba5abbf50089c473b28f6c40a8ea5262a](https://kovan.etherscan.io/address/0x2861425ba5abbf50089c473b28f6c40a8ea5262a) | +| ERC20 Dividends Checkpoint Factory: | [0xbf9495550417feaacc43f86d2244581b6d688431](https://kovan.etherscan.io/address/0xbf9495550417feaacc43f86d2244581b6d688431) | +| Count Transfer Manager Factory: | [0x3c3c1f40ae2bdca82b90541b2cfbd41caa941c0e](https://kovan.etherscan.io/address/0x3c3c1f40ae2bdca82b90541b2cfbd41caa941c0e) | +| Percentage Transfer Manager Factory: | [0x8cd00c3914b2967a8b79815037f51c76874236b8](https://kovan.etherscan.io/address/0x8cd00c3914b2967a8b79815037f51c76874236b8) | | Manual Approval Transfer Manager Factory: | [0x9faa79e2ccf0eb49aa6ebde1795ad2e951ce78f8](https://kovan.etherscan.io/address/0x9faa79e2ccf0eb49aa6ebde1795ad2e951ce78f8) | + +New ManualApprovalTransferManager 0x9faa79e2ccf0eb49aa6ebde1795ad2e951ce78f8 +(Fixed 0x0 from bug) + + ## Package version requirements for your machine: - node v8.x.x or v9.x.x diff --git a/audit reports/Polymath DividendCheckpoint Audit Report.pdf b/audit reports/Polymath DividendCheckpoint Audit Report.pdf deleted file mode 100644 index ecbdfcec18ad631717fa82980b348ff785e0ec2a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 175032 zcmc$_WpHK7t|n-9nb|J0%goHo>}_UdW@g4RGcz;0%ur@#W@cs_pLcI}zwXyD(|=}W zMeNAT6p;N3_$?O!o$9wD7zmiY zf8T15urV@r`+mVnz{K&dv574KBlGt%6bR@PY;Bzg*#Fg6{MIM|^S}7H{7X#?|Ep@~ zB!mexxfvM@SqwPzSs0C2nT?p)8912O3>ftp*!2yJjTso27eO^qE1zWv}ofc=+g3F$lOTiKfaCu8UMPhb3R?M#t?2{V&oJrf>FMj~ z!9&KmIKv9U$^n@$1qx%Tp*t1f>FLEDYk8xO^MiQFjrWvJ5)AiD6E9%B`!zZ4!a=}v zLc+o_1}L(zUAHAd(&KJ}o`M3sh$9#NSCqbe_J7DEWNYJOY~$oez`*b?v1Gn2U0=}l zAAA0Xj`iE%**F;gG3#&7_@?~t@+%lS+B!QJerx0(XAyC85>s^g_NMOWmJ0>*zkh$uS&<3IZQ=Pd`p_a5?3_a7E= z4z`Ah#^1}-{O${je(y5IZcg8xXZ;O{;J;g;f43636Z&?4D;G5My z+!UQ0oDH4+C6D9x9CZJ}@*jNv6OjL*k^hGM{}#!AW5~$F#`-^@c%i)(z0Zv3ol`T+ zG;5;Wr70-B*2%>kC>r2ugN#hD{Y7$K*lswr@6;YI9gmsbT5CvFEmb~RLfM>wh1AU} z_kIvy<@36?a}_#oAmIY{^?rN(S9Kz%uF1K${qgR;3l{t{mhC`V(cN6BUGQGDNUR^8~v$0MT)k8Wg#78)$z!yNl(i{`h&oiAu(_g#?2FDrGiOTt|4_ z(Jhs_;z}QE(#g)4cp@;9#2;8E_W8IyXxY6B|BbmLHATtvFs zmw|pHxGo&<^E5F~UO{p_|4ry!j^^2X2!^!*M5b{oW%_|e`|g~W~m z`ZcV?-_usvc|S|mWuPm_$b)%-W(S}f{U{2%>3w*l_3~VS=jg%aU&ARdHO0iiD!Go; z$Oi(be_iC2{GB>jQI0GplN6nd#kviOL|BGs2Hr0qx9aF((a)Rk;g43Pjmiej+7O`k z_Sb_Czzr+42vB2?K{@u59afbH0x~P-!7>1U!D2x%)Ou!f=&WWY zW@J4cA^UQLcp|aaRaWbO@PcNr>?aFgl$T4e97AeU5D1zSr5A_*F}v4oc*7mGX3m7%EloB;y)~QxG3ZQ-gbhBZ)}gDI{x9Y zPoA}Jz`lool41Ml8lZ~%?p3jCtdnKNuB}a7A(jS){Y6BRM~u>8*Vi1uE)r`^g?0ng z!yT;7;Pyf7&T+s1Mg50a?@ihnqjsoGJi%*}2lBpwOUq;`Q(1ZHaN?0Yw;+Jk2s*wr zEwOxb<)>DD2dw2Oie&EZ0d(h{Fu0r-z0{cZYbpQobcz6J5*Q zz=qR;J1m8f=}5;#+T?GnL2FF?C8?CHQsjO4UB2dXST%UW3 z@vpN{k@L3AY|0g4p^AXo*?o=W;>4;XZrQ0e-H$}c1#RxkYMK zDGQmBCjT>cm5uwY4HO=4*;=CubD1z)elczD1~=SBJzmB<;SY0$nO`?;Z2G}L>&({? zlF2Z-Y9t)(FS8UKJbAlKwi`YWnYMM7^T!ZsZC+C_7OWLxGe;-Ai73o0Ws46FRli&& zHSK(z)0{LuFSNUnxsJWk8WW=GNuz42Vze?`@JJ%?N{OIKva5!C-~>aQh>X<&74C^o zF0c}%3-|l65;JWQwM2?*(MhB%&8Ny2j3Uql?mkX9_nle@JdO4Pkv=2ux-tcA1jlJJ zo_YNrfmZPa-i~fuoWU-_@R>hT7#FLnWH6v&>w*C}Ltnw}x5~0=*?;Qlmz-QK!~2a(VtLHC2b2&u&O z@Kbf5??)bVVycfs;abX4C`mLiqvm6lkDGPM*{&75pJty9ih`a6JVf8=k1S~1U%lvN z`rj6zL@fGKnxjZv0?iC;MNtqc7q2^>fGY2RIxg`BHdRnP>1KLI7Qt9fvDZW)y9J!C zpN{Qf;H88i{qGx;aQ(x>VPns-gc)f5hCSI}byuqK`K%nVWO=@~4b^x?KZ@=y!CY;y z`dG`wHMLOH6Zi2gw*~!kjpow@m zHc@Rd<(kiau(&yMxxifB`kLU|3ftJ?Wglqdbt*aUa(kqzVRcPe7sPuDl%K2^p9(ER zrJ9^Lxe7c+P~fQImkq6|bO)en#as$kc$PTOn|sI%TA-Jd4D^w;XzJT@@;ZfgqI>kI zXR>(up7!%+1d&-Ot4Ee?ST#&8{d9~u+`Ho0%kGhDacDmz@cj&@T;B+SfCY1XtAARs zZq~^14g2xtr($Kq((Y<{@uYLzgLvs8`v_`ai@=X9Yym%7#hDe_T9As*WRew{VLLk^ zE*>|Pc3xne8iQ$zy_v_Bs|dfGVYBkfyN*a<>(-tjTH2^)?I%ZW=f;klYi}M)j*FtZ z&1mJ{!ZUiLATKY(NLQlUR)Z^$EW5;&Y*rbm2*TD&A|wdKbmRSvA?N+>_NhC!P4c$}f#tQ3VSIjo@@Z;0Tru!|{1mflEWjvq44iU?N3!W$ zLvD_0p-OrS&NYb4xEdN9_Wa&vgo1Ycd5ua#H{Lsntvc@pZ32TFTO+3{}H(U-vnd~1PmN3tlweXzvDB8 z?}zPw3eYCJAib0ZHXp}1+A}6>MvK`50rkYd3amUq$ubB*@z78#){4Z$zk(@22m=Jb zgpp8zTf-uLWXQpa^imCkpxt{|3->#KgV#8q4Ig?Qj^jmmY+ZePbhUW3yHDO_rg1QR zy?mJ-j;qHYO8%Hh7!W3AP-db^=leW58Tx_bLmKp{#L_PF%6Qhq5EO)llJ8PuUY#Yu z_NPN8>{L?_$J+w?aDkh*yEiGe)Fmd%3BAFarWogJ1n4Szdlj<>kMMfau26 z#8?w3j7Z(HGD%Mg!_NsJhe>a7!$};)p>Gw20fhj5D70|G%Rl)ij}UK@yQg2ze=S-c z)J23<()}epwaf*MIOaHp;}A>xBAt;P%b<f+bftBst0b$*qEn4NK0O`+C?N*xpGL9XAmDDpEkz4pR_T53v}NJpp!A^2k>UJeK+ z1$BKAJI*^-eI>g{BLwb}p9;Pd!dR3v-(mV$f~Di&0)r8`1W^Km0eWmzcl)5wZPM#b zI;wfN7u>}gxN4Q~ye>Fz++NPSXYP~Um1F0cjTSfC*E)2vi%oAU#gPNAt_gnDOpjTv zTD0^5{=02%c1u=7sV@SD@MB!dPS%DIjK?Rld+53>=l1U?BXDjv2*VfZ4jU5LU$<++ zj{D$@DeuAD@KcwFOkjQG`=mYpN-$m?+bXp*&B==!RDc4BjciCBI23I zbK1V^T{ks@!A6d&zsh*YLb`7Pm^%v@kJaV44VE^#*zKTiNh_P2nrh`PXtC(Ass z?W8b3;aE9ska5IAV$smJ3OYeH(;gWolB$q;t4(@Da;%8j^6|Vay7E?feyME9_$L!} zTdDYL*__a6UL|PMM3&RGI3@Tt6JD6 zI3$y%ic5wB383;fjO0tJFePYP*b1t=3EHUoBm>D%MbH7Yii3@gjt#6y-Y8|u*F^PQ z6K!>3KH-k{3mvNOa7v|tn!B+w?jE#MC<9Z%Th*U=i^(8*)#bPJ1x!Q1mk0-^{Os1e z+<6N|R!yF$lMWzcV)Ve*e1#X}H)EA4SSpH|dR=1{V`Ikl!pi7dX&ZuyF*P}l*#4)i zGwE26$NE;4!Sh-VQvz~*b=;x_mc`s#Ka0pItTJYTWeeKCx6#O=WJYw7f?m#JoOGCE z&BPkPEgw}4igAxi#PTG`V4%845f~^1R`|jDond+cty_xUX0LW-i4cfIKQ-!7^iHEa z>#5UPFx|w8 zEZY4aS3lPQ{x5x-^wrb#c88Se2IMBRvtUX@WOd}Pk}C0ZNmw+4j6{R69NmczQ7;HXyyt0XMwzwxgv>(YyG*73o?jktV zCPY1hh5POWVp#MGC{^I?WqUft2WL&jtLb+yQ#RyhHd_AF7tC2*noUyHV2_^CN0(pH zqbI>5#L7@nYyYIrVL1TYn(5ekC=sJ)-vfxW<)_VysUk>|@R;?4{^b8@l*$zpb@xy| z0@_c)ORTLQD+7@-4nG$*Age4E%rrZ=1%{jmqE2>Hb_np&S&#|OELNT+FFQqdOBawp zi7nnWH{po#M*OwK`HW#{ovbur;OMHZE#{7yq27;e-;dvcamTO;s8ky`= zv6(tKtElclDZ5nb1Q`i^Dzk&rOyHb|T_>*3oD>f09F6YZ zK|WRY4?sk5>DI;+}vtv$J*tfew|L%6h2&h;q!+x~v;>0nyZ z{U2-M@)WXAJrr0}djJQ!fCFVwBjqHi>*fe0H2uuGU?q$mMQSw!WqowaHXu(@{S9QH zhe^%~LuSf;{JB>%R{VuLcw^&rw->9{PM}p*wTD{rb5_+=IzQJ~mWY*r?gRQHH1A+# z#c3JnD95@=`htUWoLFmTcwA-wq#WHEu?YJxhq`1ML=R8gA@_37vsRRM+7NWtCmzF0 z46ijZ%Gu#dSS!7Fzrdf%g_V0xP>Ul-@^PzMC7fd;xVRceeBFL;QfFQX>iLPmnh>m3 z@dTHS_eBLoHUe^B%3yUct0g}&2tCm3wUL%kZ!!pdl>56;?Uj+b$#+df-W8GfD0U+V zX{mO_MBa15q$2*5hFziEAc%OCgxR9qL=kqW^n;6d6@}eJJS!maM6j2H(V^b767nkc zmqa`ZBl)WK4@Ep@5b`SYheohhgfT@t^CMkR?ed6pRD`*r-ZT>esCH>YI?BS>P;c4@ zbrkz4L^757p+s<0`zs=zD+qPu`x!)Vl>5gb+$501Q0>)_LQ(DIkTxRRvWOc zQ0r8Yv?Gp{f|QXwQEvhWT_|?lMb4G_9V6USkfz8DLJ6}~`dvh<6#Eq;mdnFvBHR>^ zGEi@V2=P?=MMbRS`VAx0YQn_OZo&u`DR%WmDwX@OL@HJKg(B37!r(=?bHmUg)T+bY z(CVa-o+u2;2$RVT;s`J0`vpbTtHQt|QcJ>GBT@^(RwB$4g)0a(P-hCmL`BAxgaZjp z$aiT)=Be@lNKFx?@`-m`Q;YdR1h_KBuVU2n_zB2UWIAlS)!ajw}jnD>H`@;SpLzG%bMEPBvLha-j zQk*9skZ38p&GG0y1Fg;||FrcI5{4f$UsXfB*9| zitpIOLo)*tp0;Or0oPhyiB8M~zp&@T+@17sm%7XGK|97Zi1Mhn}P4V^I2SJ_!NvND{# zut3M&u8G+G8VaVr)D!Lk4iAQuW`<9OU7`bEW7obq#Q<&_JYfTLJG^3)RAl<>+&ilJ zLEXy&`0U&IP8dt@IX-$$kV}4Nb{*X#0JiL3ljqnnyAGZF=TtK%9bfCme1DXN&T#;A z?OrqIOfz{8?x9M$9bZ%Dw%EP@uI!x?C(I=+?42Vg zaDW;|kEl5c02xQtwo~R@Mds(|2^wIC9rxg#u_T2ZcgHDp?iT=(!`bzv*k5Pg$#2dF z@Wy^|Fh5Y1kdQQo2WaQ$>@mkEIpVn3zsD&t<>2{i4p~ydVSRLu0a#<_88D|Rp~+-* z^oXA$1<2ZYWX_2Ku-SPA&3Q`1IC%QZaZ5rusty`M<}3gR_8J2x0stxwXQ!8aKfLY6 zfH?=ilcUDq2`@m3V`I!5s$?J&-M%e!P7vVgsL^B2TjHL1=#c)`oVCOSV4bPK(PWoC zP?n~MQX&dq;$X2$?>(UfJY=4-FF2I%80{!cltn0#lt2RxGK(G42hAx`<$xB&$mQM-v_6rqZ&CDeelOh)!1 zN69^-!^BZ@;u56H5J#**bMg{YfKXcprNBsLJUfYlzV4|^Uv|4>M`CH+lJgmwnKWxdxpHm zy{5T_yJoQieuid8@PfJrI`X6QoARIXll7PN>juRK^#$bx;RTy)Lxso!<^|e>+yvbO z-vr)-0D#&8+k)5v)qvIjm-r?0Ky9;MQ(miXqxE#`{EQa!Pw`LjBkIxLMzljbhR^|n z2YU1O?$ODGId-astq1Y++vtJmq1|>rde6<;KJqj5H}%8qnd@=crt4wdrqH9R1z(2c zhOz?525JjNJ5)K^&K=514`2ns_8anh=qczS>fzkh*Q2S0P=n(xy!xR5&i&)(yLrQx zfp6Z5ZC#Gut;_@N6&1uws+WRyKMk*^7g&QVznn|Mk-?NJ&y;&Py61Ei#Oj8tRn0F( zQfw<$tN_9K4DAzn)wWgR-(z?f_Az7f7(1n7T(r7>!c0=)PF8lMls;wWgJxL@*$Hdb z@@nX($|rKHil{2{M9$4i{ zs$9bT8h>FST3cTrS?6Y`BUU}iVslw0$g1xwh5`C~YGlt)gt@hT0D=D~EB5yL#=RPJ z$#2U~*WVXd)nC^y$KMx(4_MW2tS6=ip@(Ff@S5iuVVi4Pa~pfxavS6t%?|Gw`51T- zSQjV1 zHkcib8`?9~HMAWpJ9GzZ2N(`8JZPw2AV1qRp&igN!a3+8h&Df&9{3{=9Zu0Fs9qcFAA}wvdTkJDkV{~65Z98rJrF%SdW=-iZTo~~;3|HYX)tCGCVvP$ z2s;cpgn8g5emFhga(~KPkpFLp038!CKX>Ym@6mMIs+g}%d zH`fW_;SB08p01eC;<08WIfp&!12c;>uwtHDMz0tyNNZx*hKyMGm>)d&3g;}0%89Q%h1Z-MYz4w?5DTluu!uDF_*akv&RF=LyT|@fSA+S@7iTc`PKiBYPa-8$^Ai_1m)uDbi;e? ziKRSWqEX?K_dfJWB=5fa`b~94lNzInGSDKx!>>(YBlt=rimnTVd5yW-!pB+MZu!J+ zW+%V7``!8l|IOy}>Wh<;BOQ0?xUp@fZNejqSK}*0vCsEWN~tgrF$v}poY0;?fH+y2 zLp;*SP&KiF>Fw$#MBw7qjrPgqqzTT!Y9AoCT zmzM&(w6weer+$_g^T11UN=Zv>Fk7NYg~7I6b9bWX=5Y*B}ACu5T>ICm58mppFm zCaYnmHSyDnd1D>6HG6#To3I$9z?@7D3m4aM5Z!_Wq=!Jpq2H^mitj*AE4u(`8t2ch9I&c00n|I*?rLo1D?wvY)P=h*@x*zM9+0-S_zb@yMQGuL__iNZI}1=0|Nd@2ZcM&-r~`F4Wl9)=$7ykz zY_L?I;n;D~l(s1Kf8ev3&95UeNiM?VE{?%qesFGn?@1&)OepTc?}s@hj~B1!`b!@f zgMxqz-tPPn-v=+RioEGMe!hR0h0Xh%L5|>T$0>N(DSLH`(Bz%^cX<3e}% zc!sy@u(J1$CZYz17RU#2Te{mqx_bvJx|vjl8$3r;;3}#-2~j~ z3z^_+;c&aWFK^ey9NB0Y$`&Rm;%8!2IWw|fo|1tRM~BOdW20x#=a;%$4R$8SVRim? zsc4a%2rg0g&R53{8R|O6HeMag0I-!051Nie z^eyYD$bdExlWp00zay$ChhEt}@Me;6mfe{dRq*mM06B{m+7*v` zm>iSYziaCzkUf`E-f>hU8^_0Ay*p{Axge2EfxwV1kIZ364Aj5b4=;>?!?Kw2P^kK*d0Z9sfi_4O|5jL{el`~+uT;&-{ zKhEntto5N+ z?{uaaJd|3bRSIDe-*>A~xMn$Ehm}t@r?%etr((vN53+0ck~jh8?17}$vM4ryfHZUc|8=#8XSk8yNR9+#&YW>|R>t|Q80hZF7fZ41maXX6+~?cqaL7j~WKFsiH@_=x33`cR zr`d+yTsZ#ir}Pc+utoI?&k4Cwx@k1pY&{vOKYS`_isjn%B45EUi-Q7&yyZ z?NMDodAH&^|XJdfKH&2UCzpwrQP(&0%f@Z9Nu-dZ`YkV zS!hLwATQ_&Cpp(8baEx0d#r+Nlu&I6&YTgQamOviaja2HtD8EahoYC80!|)djYGXAL?o=EkLNjfKy`El&qsYqC*xOjiV8-_lxH4& z0Rd_On(RJ{9nD}dnZ$*XWxII_M~_I2s{_F%K!^6U!b7!cQRc`#>xb$_gfqC> zt6m4Tm2CJ>fF-;6^R;y*j_DH=w6BNzit1hewy=<0CW10Jx7-8IMe~8ns!l$plja7` z5?$#eq)t=yg$%lmZhK($LsJtYlQ821tH=SAE6fmh=4S;LLytgM@x z3C)+&^7cwP(h_rCb-JN$X=sz2fybVdL~STSq-<^=7I7of@m%qZQrDL!u814+hI(vz zHy<~oenKEYG{&@byIFE2B;wFeit(-obe5KQOl$y8uKGo?sp)iWW=BAttYLAv!m4ZxJTO4>i?I z+Az`A3nY?4Lqo)<#E_KK049J8R*R9Om!E0(pWIq@0kWXflBn@iE)EKAOOMggLAMfM zBnDn{I0oLho4^d&SaLMeRRY@H=ZS3= z8kpTi`G8RT2Swu{{lqvuK*Pc?GqoR0$Cjm|BCr`@jo`Fe#u^_FKtC zR^P2?mB)D(r&u1r5hLj%h)hoTIi`7dqy3~pcMSsSvs1SsWn)=*t8swRGyiFt^ic!S zSohD0cV2?Gz+{BTEq`~_U5o3-V|96Tb=PI1%Z0W5AkMu&Cb%uSDSp1Gz02(lT9~YV zSC8GsubfCzY>s8yn1+t&!ME$_whi>_Dp_`6bizNfRx8?^@>p@atR^jP_L_o=dMz03 zo~BSzCR4k>DNFej-h8D_W@~{#3BY2@aLB(Ed@JucH~71^wC{)NN?v zA4d+1bF3(}UpKTHu(ER!%I%hN8JL&`Sq{Juu;yY%Qdd`XwZk`t>w+(8{8Dh*ZBIwb ziji2x#0-!6Qa8G-S$0{L4yYe7lx!`Ha>!Q5nA%OF83H*2WnO+&8~bex}35S ztRr4OO)bCdF!)BFUMQ>1|Na5=e$Zb8%^JA<#NpuvXRAtfLuJ=ir2;k@d(Qf0lb4lh&wbFl2~@R>yR=S;g_Oa|y51U%99@WNw) z@IucgNZ21ga|1&kubk&dFcIVhA!=#L+I27et}ATf;1zfeD^@Gl^0x{w?rFO@$=AX) zx#pPuP#Nq~BJkT~cd*GASeiXo6+Ir(f?_Xx=DWG|4EL2K6{jD;NV&?Yu`dx-`#6|b z+Dkt-Ryl@NVXFS2rKqA_S)wW`uC@A1)jQ2r%S7MU8rX2G$niC?;1_=02AX<4H$UuM zKvBqqlXdfO!;%38jthi%c`Y$Yx$P=8$hqfy+~g&$oAVPJz*0PNjBZVd}JrtB0G>#P$O&?YaXgF89rQ0NHrHi}qLuF4ah(6;PI-A+tq<8=u;xb}NbPiK&N%3Z#YTR~OMwKmd85ed_T^`ps0 zoX^*#i689TwFm2j{lG*zxWZ>4>}SusvEv-)^9W)H$_e@@Cj8d$Ax^PKgwekO>M4tY zxp9Ksr{fiW^ikfK-S+dG2c)(Lq3C1Wrt4RBm;Q4(k&j&!^t*s{9iN2Z`>x&}qis;m z&1sY;(=!z{_6s^j*&qW;fr0}_;CqVvuyDf%a39D;0U1{Wr!9ctqk?g{_4uisw3S7Yiz!27eE>Br0C+SUl}a#+_b7cf4*A@hflx z2FBPv1z~=lhIqV^$^6u5BrNi}r}|A+T`h&FR-5}AU#o{QL6@nijD^p{z+-h^yJ$a$ zOo>l9rrBX8(#v+MwHfVvMzowFOp4pzd8pseKruPQ+%sf!i%GyqIBPlKZxcOY-&L|6 z|MV<}pVK>%rwRe4i>Foeu`+YQ_?zi#L3|UXVF=Ki?X$e+;)t^1XnE27U^d~4n&2AR z!WVAv6@zwOa1J?DiRJ1#I~=_}np6a8NIE&@eP5@Krk?lv3gAo6XLP>IFxWQXO46h< zq#6}dR6)wkN+naWQRvE8EQ_6-n1fo&X=Wy&;N9B&1wn>j8d|#B>OO&;-^k$3@+U<^ zZzD_@lHhWZc<%hT(0k4>IHz;<1#Hzq-41i~QFQx`>lGn=RaXQrPYq~~Pya#Pda{br)=j@~0SYv4vxvk2;vO-@Fin2HVO~NK$3&Ee<^OOSqXSE|u$pXywa4*U@!pq@a&VI0e;xgzw!* zPcJ@abuYrx_`EEmA$#Q|%>jn#!)LN$cdv7Q?d&S&D9IW|BI8ZV`fv1M-sj&6d~d^* zc*r%^rl|RHg&f)%k4DN%{_WwD6aBqg#y7a(vN7qjY%~Cy?CN@UT+{1Sj0*nBjC@ju zzW79Xy+EG4kmZsRY|Zcw=bMD}lnyxz%yiAM_pRt@pHLrj@$29D{1h&wR@qmIs?b*D zLu1{AxU&9`D}vTW?V>mBo3gS{6rDr^tP34yW6F2U`res;v%MKymrqj#R-;1`V{IK{kkQjd$Gv3{oh+{0mV(hFOXGc|QSe$r7c zYeARcQ-uCvnBX}G(u9ugDwcXQ^}us)E`0oXxeQE75*kc#R_2XIvh3T4Tw8IY=Lvx! zzk(T&!gO?1ntGEm`0I)9kDH6&$025Nlrz-t)liify!**M^$D1_5`a|3!to_nJuRQ3 zMTD)}VdH+kxjLx1%}o64aVk`UCTYu#c3>*y4`=3d$vN`6wwM6)~IF zNnFps@(LwyB`pm!V2;rNQq*|h*+b0u#`Mx+hLGOOvNWjW+$-xNILV@(?3^!MKykZpUam%x8))*VaENYo#M?( z)paVfWq-KODYEF|K>4gY! zR!^NP39hJZVIYC6#WY>jR|GQXaNanQeCp@+?%1HRH}Q9&71_ntC}=RL3k@OaY#0i7Tg|sP3gLZqYnTOv86$YhP-{$AbXmuRb{%RAymW@SRKNQM2mNgXB zxrPj3Fq}(NbI_j41+^CHWnJ8+OvXKVi1I`as4%JGkGnC32)KLmT-bqDuXc$c_5*#uzO>pSuN`}gfN;7IS zd3KsvBI(*Q(>n6uk%!5}CQ?#s++}5=SZnjk^Q<+PMW!nAi)@SaFw!zK3f`I(^Q)Wd z>vFc{=26L`>8qNBk18e4UMG>p$zm1EF>SU@_)72Sd1CWr*OKq(!%WAA_8-(*`58cm~!`jvei_&Bk9$0Gs zfDgoiNKr@CE)lYsJ&86ZZt0crqgPv0lv=bev|?$g&alg-FW8t{SJ+xuiO@8m(JWN{ zMVU#k(wgQ}Dz~(>#IsK)2tFK~H+9Z)q z>60Oc-k=MG@JEq66h<`0lFmZU$Fde0^Ebtsyr}sndX$PHM`be4`qcRS*6kcQjlVn=#}qN znye&(iPwKP zV7;b1T{rG5NVPDFE{XY$LEgC@ci z4)%DPS1TLZV@3Hn2b!FR<%4?dD&bS!CNFv`P1CJaX&2^B_+zTkn&!5n z-$Xg$QknPs#3BR6f6WE(^~5cf zoa4Q4MrH!RR=rU_NeqN*4Cqhx=8A~W7!{_B*Z zl{V|$2Df}J7o8A^Bp+J7StxNRtQlJn^H({~)v#+nt<>ft-3P-sgmJ*Udc-EX7^)(T zY^-$3;=C~mQCb}F8geU{1JoLdJMk_5et1aXIFAJ7sPq6Ml?)r5sZamg;lV^}t&^Ae z`eTQw_4X&za_?mI%O?nIR<84ZRsTPAss9I6Z4BS_d5mn|DcAoki<*s{mHt19djES( z>AAOShqg%5*Gfy4=POMsTPsDOtDuK?;CJRU;`ZaF=heq1fegPzX#g`+SXg*OVktS| zNdW2zVSWs0zz+mrIk}gKbEd7#EAyx{WPjTLyq!rK6bQa6SN_ESLOmFSvv4h)I zfc-#Q^1SoB6J7e}(UG=_d-GWNhs=iF7xPj@^;Vm~y;!xEPul#PrN=yGi zgt?^!zWI2%>efL7*FHddb6rmHOJDPW=TyDB#zF;iu`9n}{tgn_gvIq9p~R}qw`|O~ z%0=B6Wwrg(LW`<}t=-^qt!R_Xbsp9=r7A2dcNFbUvGbx6v)8oGkellIyhX~`orkNf z&=XN7)uzM1x?@wh;>B#^(2y2=m&HO$h2YuO`Rxg8%9;Of4G8Dg-$#!=*C%5+UHh-| z50($UDO8ijG_06&PpW(a zA;!=k(8$uKtg+R4S?(jHu6O&*SZYKViiuA z0q!BL2{PEzI}A<(QNVS@+{FBQnFR#Oy{P3W~(kIO_omEae^(jvO6oN0y$VM^m;s`!}g~QD(MvNJhbiZ(jXxje@Fps3FKjf(% zZbM)%Z0j;U^vaA+;ed{wwFC&n1++p(E7c129!`m?rr-bAMcF4`M4O4T*;O4zn~Aco z9!?K=w`_}tg1nc{q{^4gWpnoiDAtPi3jTGiYDiA46GUi(^I@A1z?n(4;(uZY?AePr zu8Pfralkr@IPOqW#HbHsLAHGr;EDWu6>&^-kARDq?=kIfh9btE*T7tuUWae9`ki+{ zMg5lknRMu#)yvbn9cd5LNoz8jO7`=W42jOqFiIjNO-U%ke}oj@SukWU7W2xY9Gmg} zgaOxtf39tvTb9At182E=;~v&s$6)`~4Lp=`*=BQzQms-?u2=M~(%3gDS^v%_wXA(* z-%>rtR6)DBe;PQ0U}ZY@Dbz^0Yju0Y%+5xxcC@la20B6}OBrIz1AI(|C(8m8z>z9G+GbGa~D~yVfeaivg!O$04ICz;SaBA{+w*{oNRQecNGNaeO=N^W1gIsa1wb` zKmK_PQsQ+iSa|^k>SKW+MDaKQCX`d1VzRgh%>;1_Te;Z@^Y_M>-4dW~5^ql|cz{T3vDJ zEmR1glHuHRc@Kesd)5Qt&pnO>ZkqY=fDX&he1>(lJ3@}>ZE;%L-Naj^Z@o9j1jZx& zF#1^c8rn@D0Rn>(-QqCf^8sm^-UgDL$bDp@`675-2pKQsQ!C-wii5~vc?g(tig~E_ zuurnJZK;Q&Hj<~X{Qe!KMrV$ zo`eOc_WIL*ul|nI2~#wWpd#F(zAKHXGy3Vl;Ip4+E75(}q!a2f*)-8GIb1%0@Zb~H zj4SQULl)Xb*tJ4U^xGr)zHJ~qNcWKGea_OWEL$-VJ58MJjw|W8PG_t(`In+?v%m6S z$n>K~NZtjxs*!aeHqKt;N4|5!Voun@IetyH?qAh?Iwr@@DgFSkzhc`?Tnm^58#uc+ zpN}TEll|-IEUXJ?akkIgIZ5>~0i6j4n=;R)6l#(mmTT6(qK3@1fKzhU(BdAZK*QO@ zy)Atznf+4n6vl?r46aUKky z;J3TY#x7d%Vr-K@ziMO5zX&DHEL4}j<~WV!d#DX#{zT#pImTv_nCZ|c#9rG%JauC> z%DSemxT60|XF2w^sB;Ir1|UA`=Gsy}6Lv(rvGsI&etsqZsn0X6Uqtj{v9AGWzH< zqceub^IPxx{`Wr5A9t;L?!D`*d+#}S@3Z&*d_I?MlZ9Y5C3b$%Jm;Y@(^o01Bec_d zQS;UQ+BMMEA3*z>3#Y2!7}&DNGFoAZg$`XmcKhNNDIY&Q`i3n1XUbOU7J`gj_{c50 zkE7ww2?gZoS&|20eCsFWCycsSh`eJMnKOr?kR{gBbXe>74my1P!05{f-|iXxan%&b zFrv=+w=>stk>(w7P+CJ=mLE{xguWJ7%;~R2pz_C#fW~D!E8o>S`^K|OHp>Wi(GVDA z77JE&M{30e4Q(?HyvRwz4Y1nCOw20XbzQLRP{C~OxX;f7{ujmK6GtV^8qUbivW|dd zZiSv&&fNN3#Q2N>#dp)PT*t1h!BSDe|v7P%4g29PMS z=iQ#lYjIjT#KJWmB8WwepP4R8k*$22)fX}-*~h#T6ISZ7fG!i%#qM`&jh;$%VUt47 zBNwG3hhIhRgLz#OI=#s@U!A38UK$lCbO?$i(nWpv;o_zx)->u8v+0_^mKCXD%Q>#m zI#V?Kc-$rPogKU3Thx!-W-{^pM-*T6r6NlL=gGqDBG|4)B3Vm{Tzh-2C(JWHZ+!mo zQ)FSSFzfsuidp0QHF?P`|7=~(4ciD(c{rVzWKx`X*Umd$*KeK4mTub_y3hM6%}xBx z&ffZ%6~QUF8c*3c{mtv=&8#A94j|)5p~Fg$y>TwWW$~4n3`5XYFyh~cTQf@<(Xk>r zXxbsdJ07((;xooe&Oz@ay6$efU(DezE^HVnks=r3mWpH#~_+qwQPw3g5;SaVy zJ|^l`8C8|Yrq1pJ*|II#!#k(0E5o?Q3>z}QBN9Z3aJ&5FR3VGc7bZ?BYZBHs+feN0 zY@hz?snvM03wr-g*`(!EgS2NkmNy*~bC6WLqA4w}ZqpJ?a@qWpTghYiHc5igI-`X@ z63R@6I3jQ=^9@Jau?0EIbe`WIjuuDNa}G*g{;Lxfu^BHhMR}HW{&Y64K-UEq+5}{7 z{puVkpL2e7q+~ZNQRgHnCqFrI06i$V%7ITIXyB-j;QI!b!O*Nvz z2mPS{?D)3(ux7TT&kN0_IxyNWYnler5-u_hM_lWF1;N$`}OB#XSd=GkoN`;vd zE3RVWWKX$9@FXaq7&xzQ9ULQu9icE6Seg0-bwAo|%s+iKnUcgWEYEk>6_U>|Orl&A zT&{Iu27_Pz^SxW2N^%uY5e-DjdI7wQYL05>U0$C0U6yE!I3pdHd|=wi>l5d(q5C+s`ENkjv#u83AFh(etg3rW+#AwP zuX3wi3-}|JMGoN<0ZxKfVlTc$IU)O`lMnuS?u$377n`(sqCMaus+x`({c7dUFz#Yz zBTSk*G3N>R0e2X*^l%lqq?^CVvGd_@@eYJt*0ZNqQ{KE6el!u5AUi*Z@=fFm(5%S;AGtZ z&xo!C8Na9_ec2*|Qz%egIsXY*HR2beM85tK>0}^9DJtxK6kcEEk|W`6|B|%>Gc6eN ztYJykBQmt!3*v4hzgBW>VUceH3iv1DjYosnoQxWNEqw9h#OF+r&nY-bb|M+IgY%en zXfCLB0LQ#c3W55s6Tpn!Qm{xRRZ9G@mY*NJt>LJMP&~Eg7uXVKobxSQk~23>^y>!A z6~9*i&FIpjMR&Sqqp`W%lfU@?89bhds`?^EwyE5!8KQ&J&E>a@FGuxg%MZ4jD@#G4 zih)HY$(}xItL4vzV!O$TKX5E%TFn-ijM}ZG(3?5vKRproi~IbIC8qdR#pZD=eg{*c zUzc5-Yb9MNu}-V*AI*X{szE!7Eg!0Yl`)lNftFIs+4%apoBe9l!$W6p{ zhp)T&=RUJtmQ+=wu>5FjLYAh;F+_hYSFv>Yl@8r0*sVxAc*OO0ir~ekAVmwF7@58=1N=cW^5Nhm8D|ssuJHr^5SR;twwXaVU?9Ke-_DgZcNlFgeSwym40N|7eMH^g2kmbxXNEI%i>yfVf^eddOUtqv*Awmr4=Po;9 zd6rGQ;>Ea>L1ZFD^Ory>ZaUDx&0<4~GN+nD{tJ0sjdOp4=rHdzAQk?MVDauB)ucFb zRXQzK9n0BudK;MkVjKyv4i(S4VQg8kqgh-TU!F=D(&!?@^T&{ZRzGFV| z?33+N-AG@nF9<(>3i9SermWh)@iOVhWM85j-sanF@=bR3>DQT)SI7lT%RXCed6%z% zlbVr9Vgv~IYefvN9uJAq1jc-~Np}~;^6UY&<4&n!!omIc4Z=|MJ!h-+tf4|JfP&sddpJN6MZcqvQ11qb0%47@FRC9)2oOF%f=M8D07VyY&iQSs zm9!rx;sRD26q}i#`?#;j8(Sch7Yd?PD&;6RS2CGzePc46Gj{)d&13vel2p>FBaIJG zl=6qfQ|&aS25hK8YHrG={6cKni(S}=oyX$2T*PX5`IDE7LC-O*FO$g6?m!g&zC*9% z*rYm}oFHCSv7@~NjQ5$QLWce{&|W^xUbf_nj_L~TGER}WU8PZCVl-9FNhksI=harH zi!p|ig*d`l+D}(GA9lr_v5dKDzS^5k+;D#x%x64$7(H#<5j*`PI`yUNQn3t(s>jaW{1gr>#fVrTbBE&Yyl`n%RX%m~%)t)M99E&|y=RaF1^K1=_C{!vixsq#xy9~mF{ z$y?wx2%yHnW%snL*y_b5;ivJ*dJbt1eY@0R>|`@%*P5r{AfHo25=({Z&+(t0-&9P} zG}0&%97WRLRiQ}N%ON4j8B zPeZdnGP}Zxt}t%?z}qtl&P;t*TSf34YO@l=I0*Q%g;V{_7s5@ia%D zY!)Lm!8uNpdyB=u)w3xTVp7KzufHWapkmWoaP{L7fFM}2rao&$LJWXQhbr^|FQ({aFmzY;OiVMo9BAMKRr1ROUpjgiaPeo_A z*Q5kcR2Pg!$!LE(eBj7Uki0I8vSC8Xb`bufhxN%>hTtNbA4Ex8b=DDt9~M67;22gW zd|aVIX0Aj2izF>i{Pw&3Qsd>EG~`tJ-4Z5{=k+m6Vw$lhw+BYCThb;8s_O6~|JdWN zFdRF{zeKvhxx?ji2kA(bZ{hH(_NoUPQjkavUY6qBOg<^8doqJc!P7QdcsfOduBy;M z7`j^=${$b@<~bW}ZW}QXxNG_yUC)1@r&y> ztX`h;KR`i#m=-@w`$O%czP2SF^C(Cq3Jcs(yfC~5b6s@LTAB$37caYS>YfZC5F{4T z&?z4KkieYHk*axJDR?Z{+ho)W2Z3=t@^*MUaU63+{Emb_*m>WZo8GTfTnBm-+*YNQ zHKf|4J#5#z$s8cJmN3P+V{#Je=iDn{%I#Id71>aZREjcJi}qdFHOFYwJ@zLjMs|x; zah1y#r3a!zdec1xPxTspPER2pd{V1!r!}L?dT>HACz{8NX10T$RrC@~%+;!cE!9A| zcO=(db+%1mFbbr@#axRXwW#Lu5O(RseP~i?tVM9=9oJV=mwWZd;Us1ntFGy z3AqwF2nxtNbvTKc*;j6hS=Gu=NMkJuEC(l@JgH)&ygErj%6wqRL>e}>3CZ0!{wvwz z0SR8r;VixMl2x-E{7=xQpg-o3yUV9NpbDO=$z&I9zK#2iTz+dgj{s%5>NM!dWZjVnn<1rkLQGs4XR5Gz3r3avI9idW`m)7(+ z?Sj2-PX>SY)7uv*rxFgqeihLQU1iqKhGC%R& zjv{-eejCWFFF?dTU2vO91vs_ZO1&8e7Ca|6hD?TvQ@&PE8s&ZJW<`GhAr~eb%Tb;o zAZfjLUR5HSAP7=Xq@5FU8=}-QrXtU-TJRFiEdBMq=3=0Cg42XyNoO9@GFb(inaa|J3;#vH=wE{)2{hVu|yx_;{ zR6d+LuZ#VG0>(u%r=J3DNu4M7c906^GNIKAFTSA%#V7|$1TkW|+{eOmwLy3$K$^*w z{Qa}x1HR)pR4lM74JK8@b6)7>WE~iHY?Q}G+*;Tz=zgVBnWClQc(}6UT^k`KNU5>;} zll{Iyjx^n3559Rhl$34=(3-1D1aiedj{VydF6hj&`!g@%)E!>d;ls4rA${Lkt6y#a zVDDZ-?8tKehc#??xbGWT-V3Ih6IKrorjy%DM0R`hN*I4wqR^aFA^j8KablxG%{OPQ zqY6`?ZdQXm4$5L?1iL0l+`H5`(aYHt%<*svKOYLNu4f8p>ObhhQf6Oes`yND4U( zNQl7X8pxT72`+1(?IYjAgG}=c*{M~+lAJ!9D#i5!?(x4DX-MqPOW0Y)vsthTdXq{- z0`MfLv4cPtL>>oT4!HHL`0li=M@@=fRhRL)v`uh!F!>W_^%&@J@i>6<&RLGrw-A3+l@8N)pvmuKa%NTb)#GLI^0fCp%Pvcc-dqLN+zkZ4G@qskTUlN0gX z1stJ1H^WM0f7hNJW0*{2^x?NZG-ArqpZbd>SPGEx&E=PSo^d2BQG2oAKrWVywSnK& z1M?KFa-KcRaqCK3dhlS^{p3}_{t5=ROS={KHkeZ))Z$>-s=}u(q6`h za%}b_-;NOKfwzKVd0Sdj73?TGrBVfc^s|XOab1qLYQ21?nft2sCFtuwNzP3GL5Q_hE9%9HPZ zKKm^^eLBK)#=-a~l>9XQE;Z{4g!)TV;OwpKM8f+!>n?x#;4EnG5VPUn;4u6WAOu3m za3)Ctp&~i@357jOzE}ZDGdy`U>x==!X1)q1*$N0G%`ev`1o)*IdY@z`GaO*cSha-ptVWM8*5xe!tiiWg~xc z4Uv4JU1yiAd8+4!`kPBSvn<_%7RO61QMqhNH4NFrN1|HV{&8OgMK%1ww?7ABd#0s< zq#U{UenZkZ>FUPmwB=?iS*o`60?>z)L93;fI z@pa)%j8FnPVBwS2Uh2rny^oT$wP>@5d4bZ8kTcP_MXUg`T)ii z1O%;gNCUJD=}#GK!Vv>{8CQPdqDEMEUroDnp00SB!%-clj?6wS=4Ass}r;duIb zsp-jpJd{gRW1bu7lLHZMs9(z=86@rLw4;@4cvw%a34~g<>MbIvZ(k;vO>;cb9&T`s z#uXZlO1O(oqz6%758ro|1`OjlP8vm~a6`H(2+mqN0*82B70k( zPH`THT@J$Q{s1Nv<7UbsGDsL%C%?ia{?usHlWr!8zEehj|Fjn)MjmfQZe`V`(8Dp0 z&wo+%V$*Z;+D`;{1H)iLkDeHg!kYZHSxGqNwH%`R$@RM#dV**IxQ@WLtI~1(L@GGV z2~(O;Hl|t~6tC%PS+ICKQX4OeyNbJ#+P`hW;8D6YP@TU%%%8h{PfMja+fg$a)Q1Pe7o%pHx@cX8N>nGIi-SPgnKFP@g!mktAkAU_nH* z(IK16by}D3MMs8Fr4jc#wiUm8Wb9RYiK$83)Te#O5uh&rm}%}XItBb&p^7|+GEM7> z>qm+y2`iSHbVspy1d{DSf*?>RO8ya29=z?WQ@#lfuekl=4)<{^q{Q))#i2Cg=7y}8 z`8IO{i?v|H1sUVCZ)AHHy5gHm#xXxyKH2w0$IiX?%g9g*K?@~qZpRT;&ji%YW zOJK9%Wc-APIi|xxFK*^=vQqDjtSo#?W(4h4V<~d#v>Ij+o%Oh=uf+)ZPMt??NjQ-j zqyc02uGY+b_S&q3AkTn`&UzP|>kXKz#J$K{?Qnz}tTGe*I1h}{Ao&W-b~s>{C>>|Y zt?L58o8+ws73aYN$sK`aOzqyCdx1(W$6~|!?m-Vv^%C3%q*L+ii()s{FQDz_%Z5v0 z8W^%Ugwh-G<4y3nVXz%ByUV@KJhoFS0goPXvoEe@_;w4Dx%6>r1uLeJU(xiPfRO9; zVwqi3tyw$_cl8WJDW~OOM$z*urJ+`utOsYp)E#6#UNR|^9P|yX zxQvkJ3-*jSjt483Cj>hpeL3@(&s?=GC>B3%i@o)JS#?Qxo|_fI@`8&0Ce5h-jof@w zqG!lAOd9xYm!#TNr({U#4{G(hh%B@t=e_e;08bvZm!~POA~?Gr9d3zUCa}*-_U8nt z_SgmGMNA>u#yeq3BYmEGti*hcq=S^-LA^g;Y%B4(IZ?>H353smd%Z45E2!(F@E*L> zj6wTUzX%g^1*43|{$4035;yI-0cVTSa|}(wFOI+02}s}X!He}~avFrpdY0dJq<~`Q}Zdyl)?J9d`RO96x}XQb7_Z0eJQlqNQ*sBLf>5S- z!#06@tHcqF`Sd!_dBP7}IhGUo9qJ`(3yp1_P3w{VAfa7QHqUtp*X3PAFO`Dd0*p9- zVHhd)7=i(8&);x0oW3kpmn~*jC97Z0c;Lqvd<*jB#b3~>XUM||s%x<8dV9391%D&y z^Kee`^ftSvtKz$5p%M-XFhul?!lW|z8@UVPMCYt^&a-tg&nGCa>8tQ^yzBls@D+0C z`0&>AfjdoJ+_0N9KlDGpg0IhX*~(~*N9C*+LR$_R31dO2QfKO6Q+Y*V3#5pt*iGZD zVzIB~;}2!eh>xnY(wNy)lRZc;3Lt$ayH7mgzn${+xsD8paBU#61x|#m7xGw!s73@p zl5LYS=tCWj{qR-iF+Q958uiMGJL{e~Pql?p-3~4eW9$|iMfUI1(=ut+z00?oQlD@53serg?k-bLW6Vp(*YW=IuLMD(sU zFP%rZARjyoj$!bgF^DiXeACt?V>=5=mmZ>WdJo{(M4mr{D!hl+aP|>A#-iQ?$0x2n z9pP(BTQ_S6c?(nUq!ZO%JIs+->Xa8(s9$z%_M@iSVDfyR>b(xU1%El@u77ko`XQnD z%jLm>lR~N(4x8`Z69=;tgao4;RnT9(WS5Sl*YIXqbur8~LH?Ak^7+Eklpfc1?TqGX zfk^PK-C;{;`F}1tF@R<)4$QRpQ#a){)h=Jv63@PDf9sED7}qTPJeUq^MUOL_=C#Yp z1=*SoHz>?~-{qs^bgOCahYKGxRcd|r%>M7a)9cH-JXGZ z-lI(p@UC_(&R{9Ud!17nAHGN903xWgK0JOwSh?c6ZQd^vpH38MBo~@Vz=Ryj%oVjo zdC)z3xLqw+UB!Hl*xx?#WcJD8P@req+XLB$7oUJ{Eiy4_r&yS^@@!}g+!X4xTH_Q^ zZf_1#@0CjvZk{ZlbCcf%4a!OfJ7p1m2NyvfJPrrOW=? z*CczAWL;Xeqh8=oTW+7Z;f@m{M}0k_UdwO?GP2*$3fztB1d@S4Ii+(NH|77o zjw(oLDf50jZKVLVy*Kr?B+B}NFGQL7zY2OLn%&{iv-fP2@R?D1?sTB6hQK{Z3RWHTIj z@Fb_#8{hO)14^;0!&YW{{w;ueNB@KXMp&_} z)^k_qpK>=t-)E(rilB={*9059BI_;IDM8!Yj-+*o!Pj%x{^!l1)Zu}_50>BVE1KE zb&(I(SOv~Gz>bG~Nw*;hTmpPZ!ntuqdJFaR_%K+h!CwE0!oi$6%+qRsX$uTl*{H8p z6`M`Y`7F}Kuc75B1s42)6vfBe+)tp$FL*xbrEh0BZl2G*Qwk-p-iE zH5=C_?fqUM5s)izwRdhe#6!5ZDB>nFK_Bgl`>!%Iq=N_f48ytC8El>oiF^e9gOQ!4 zB4Y^jyRFa87q`otv_ z95l+SEq55Sdf!IVYar-6oB!eLlIG(R`+1wm5ZBfv-_5g){#*U3nUQ_^YgytY-$jKW zp0;GiVxt8eUFiCEEP;4Qy%DO}f;%q7*d3|$!q}j(-*kcfuCs2kLnyK+U{g>FOEZBI z!h8$3FBX2#$$B{?kGp#m7NuENq)T-FBAnTu;i@hcoEYNZwvs%4qxX=_{EV@!W8w2v zYfLY#HV_T9gNwCa22|YJW(Cr&bmF9^ebxd7#^5%DByY8jSYVmf)5|tYjqv6Z2!u5* zcy7J+vG1=LBpE`97A?Hdn(Aah9@WOtUxJS9rRpIw58L?oSvZgk7lCc88D95f7#WgN zWAV$woLx>ks?4n>1twr%6+9q~?2F&RC*jTUfo||Q);r*l^}hUyadW{y|6+g99^TV# zRNQD7E9ZdKOv+W7ZhU;_mZqS+GS|CsA!MLrcBh!|OylXe7y)HJaKd%5o3S-<#$2k5Bo}hUpMDwHLxm2vw9b@LaM^?xV$QdeG;Tr4fWdTflFZFy)|p?}tM! zqzAIrpb5g`y|s2gxd0d&y|pT)YaR~M_mU%ZkeOJQk6jgSbJe(ayf%xh{l0u}l&VT2 zS2&TRh}1D<&pJ_Ac9Xn^o1MI|&T(0`rKJ74Y&>nS1=j_M_e?QThDHbI2Yu~)Hk=B@ z;VxD(?hvw{9e$9V2iqpWtQLK7gD~Yx)Um>jQv_+ib7T~lG5It>%3_v-Gw~=U=%w%F z9Dj_bpiEBBfVZK67*`Uv184xNwC#Jzkcq;B6*^>l{+v8IdC$Lkle|U6zlxR_akvyj z@bsXANx$ku@#q9hCX<$83Pl@!oiRKcs65h!8N8E+DxS@tHS#3d(x2*>GE?OZQ`r8bf z;4`hU%ARvtt6$H;<)a%`f155o!5j~Yuogwx&^j^^gbvn%I)WJHZvAFtUkX>!C+e@6 zzd{LLK2M;Bl|PY)@Dy%lv`i#H6ryN3Y&mZM;dx(JID=j z&3(#a3zp1urk^a#upsHLR#mb)Fzz&TPmi0TK~ce(B_?8f@0@7ERbzcJBnRWN77H1| z6Qd-Qw4tZ$-T(PeWSv`dDpO>w0azg>997^<#Zx$5F$1;1`2+Bj+>f2-8ql44 z25n?e(neAQsaklcZwBcgll>R-^>*@_P}dUJF34OP8Yt{g#%si6_SN&_Aj)r?0Z2X^ z%hdSCxHIz>97dVLE-Ghfkp$*nrx8uaU_r)#u^^KPJC#Z1ZJrwx2AOk5-w9PvK&G|I zj;QcP-;$ughu?W7O0dj1Jxc6jigp^fT~FY57Pw@(Qb|wq8>PxHdYZ|s( z`i7OZnQ;9Fa>fluzSC&}hR(B`zm`5IYo&Dl9B5$U6wqjWqFy?XmT|$p@uS(S{8LF- z7YpP@U)XGprD^aiQIyE#9C7?bvp(Kczgn*|BurGGjhpeuIoc}1TW0p(4~*5gZSBv) zXNQpZWuDm`_h1#JnjeWtVX3xU6fB&!V^1kcZpok=x3%>1=6Km}KfpbUes#R!;Jn@9 zUQ`*R_3|u?7X>r(OY;l##6gK6U9 zzh}JEa0)uO^{=ilAEFyadCqGCg#Y5NCI45IuD@Ga_1Zb~{FB2DS-zrm>*n(|=kwfh z%eKva=AyLEeak*#uzF|k%I!_B^p)|{=k2D;lQvTV@Wl#0zruXHN2+;&+r83;;!-%x zL_isPA|Rmbvsh`rW`n~CR?B-PPoDA7)N>Woww8rHmXvU=8Nm5y$|1J61s-`5z zpma9JYBgRD6r&5K-j?opmA7e8wKyffK3P_ypUS%2Zi4T`T-4;h8*iC3H^@BU+|#zP zk$@gG&Bq2c65H$$iy6x@|BZsb42{Q>%2}f(XtFRO&(R13F?~Xclo*NCtX2)pjf)#J zC81$S*rRE=w~67PuR8q=q_7XPQb6b7UT)%Z$~JaQkIT>ZZv{BP35+4DAA^znE#BAb z&xtidtvI>cHw>bW4*5oWOe3B@F>1yBJ!lw_gz^g%RbKxw#W=+Wqajp7Mho3nurbeYz9J}7!kJ)wTQ1b-n=>v05|Dv4F;P~39 zJaek$f1u)=(%ts~kSh`0cH85YQHS<-YR*4&I2~4w28q}o2$7Q?|d<5OjB!8ee79n{o^tOW_z6{pKq zdgxJWE-Ss-N1}0x%ZEM53~7_XO|;WaOP+C;c?5coy#e!lgG9=FV*F)Zo`zDzSJ_d$ z^1P|F9j`e0M}Na#PdVuozc19I;nOh-{uxG5su{J7KrW4sd~SUA@%#~o*Nk?h)aBTJP3O@M2$r zw1gy&y;|N5B=ajMb-EW}&-KqtIuBD_qQ?VEq}}GWyc^`<8uG|#!qs}|<@@>~iQgZw zVwhP%x_P2fArz!%6uYC78vJ#SRj>%}B6&HjDY0Wr{^Mk(2=XG;S>nlYoPLj1eSrqGKZ;ZbD{y1zhlfp#mCQH^l#)rRG|D}eK&a{y=^5;m(p%blfsnWv+*Lt_`Yl>x2;SNI;) zF%(h4Mc%xEmA7fz>71URv)nE?T4YlEK)LYS*8Ah2bbkdDrG{3~EbCnsKlN?hJ0F)` zq4tT@{U`E}exchxT1tu%FwoDz;V{=%*{)dGpj!Q|pua8wyCMyA*-lgS|1Lb{9IYGi z1_(gC>zoKrA!z=303HzMmkm*|@1AB9|=lQO7(Q5CQ z1;(;simqE19}3~B(mh`JXyGoQb)0Ql)^qIrYO?komrZPB)r?4PvjqK=z;wy>#P1{V zT60wGlzx28lwdu>i+VUHwY>nnoQ_OP=1o#a{WPahJndfm{@1FvscrU*>zO*w#4e8% zbkSC2<<Smp5MSc5r{ReYM>}Ac5s^M~yrRMG zxcyjR*G_y_IK`oDEahFL8|NQ{!a{{L(}hE0h26ppZV=(Bd)Vl#5a@E8o&V6(XXfhi z3-RDVXQ=pMd{@O$jXBi8SfNb(@1_H^BwVD`OfFKC<4+*Y&vy>wHg|&6ls%r^(wvLT zDD1gmvd)R_#OH8Vfn6f50jp%ps1Z+(-{47a+h!qZLh${}_d2QH370i0W0&jlUL+KEb#RVENU zi`cu)O_tZ-n1r0Q&9fMPz^j*W>ZFYFR0>_zBW+{uS6_m~dRw)wpW9n#5S(UzHucLc&}F^G z8MeSA2Q_fnc2eaEqLiP1Q+M46mz7VX$k)tJSbi|*^NX>8!;R^(_8iK%49{eE=g1H` zfMY$}ihX|?-Ee&w5X#j>@#V0p@z2R$FN3vs0rSn}YV*a?e(~44*$$M|c3T1ZCANuU zAx&4eIQ;*>r6!b}?h!1Tg0!Z&8`Mp0gRd@`pA?-R*3K&P94Ryhdl zUmZMjzk%e`jFOB?ZH&iWm&tOw_7za&FtCV?cIAd*f#CXUmi2$*q{R}Rj?=l+ znLNJ80;%jL3W}~~xXcboc;CO$3`dzE;v2P>E|4U1o-x={FZ+>m6}OoeD2I%g47@EGesC zRi95l{Ixn|eR3`E@&!HNvZaqWFxcJZ!_o`jl*#rva|9MoJY3N|gH%egFf3eY9$$XT zVRdUS-JJ6D{PnH4{qtZ8RB{i0ws&>%1}&@K)Kk`AIpWoBwGnHma=Q-E)gW z%eK$VZZ~&zSro>oXTe<}4Vg=D7pz7$Yt=8++?5{UP=l8T3 z^mUELGH4_MPAj(0Kl6}lm0c+45O-?{H+tcz*Y|Mp+S7FGbqP~4+`g2$gBM7y=B+oX z%U3z5*sE5}OOC`X{ii}~I-Z!ubgNhkvqJ~)8e!Cq zF1K$p8od*w#gd0*Eg$(LJ>RM;Afw+mI%WQU zujQDtmby6^%zEQ$Zu;@$YBNBhK%u`qapnu10p3*DiJK$7|6jwwLJw`?r&CXC7%SF# z&Q-daV(@GsAg6{Kpx*?qz$rMOf6_MP>bWJBo@RvUP8@pEc+J_d%;#SoP~CgjuDm>n z3;WC+8A5-PRabSMw`|fnzEQA*suupZk9Zg8vuH+R{!zvR{THlMSXJt4c9Ne@jVt(b zT^`EEg3Vr1sb?hJhvCokf<^#SfzPq`kFUbmuyGs1Ncrva`>&{`_~D;rywRs6mnI)C z!Zgwil=4f{3vhnB0p4isEnnxrgPGH9A>HGB7%F9z1uC~Kzu97P>>H88{n4w*?6j=? zJgO9pbTUr)cP5?#{A&S~zfnE&?K}$19l1IOkG;|^WY#l53=Q zIXn^!wcg^n-_aYP8!5I8kk%g}{~F>PXv# zj=_D#x<#m^DWm09#=nsW2(WUXF;hnMhYa5XQ&sldRYDv)hCz&BK{I$<8(Ol;k-DP} zozR~KPM|~n`6M-f@h^-aj=DRq&z_wTJk9_z4;TK8AGXY{ZT)YWqVF?2Jt~Cde2&Ao zYDKotMPNnXbI|aU>@&@_eY*sfbDx)x`;c1SQkL@oLE4I5bXu2hL9uQigxp{5Yd}IL zIg}9%vaxWhWH|?jT#oYmV_21+k6rZLFubnCqCSH=PSrwp_{sgR)XJlG9WoP&>(XZ1 z>>>~r#-Af>-c*hc&~YW9uAx(Vh!!7|-0P~Mj#TCEw&=Jmv+9c}}##WXK+7lCg>80G*XuMos>RtA~D7xyfCjYPf^Q)kMfTDmjNOyNhiF9{& zcej#K(u_tLMoBkF!{``DjqZ*S+kp3Z|J-(6&$WHxzVCDHvvU>}pTT)iH3R4Fzg-G! zE|mfvae`jMP%Z~`>k9=S1ZacgpwfG}_)#uYU1O&0*&amY(u);od(nbJfyRLx+Es$%!|E@~69RLfg@N5a+?nfD%-N3Kj`N+zPH>-nqPp!^io@`U zDA4JZy&$^w&DdOX?B9N{&S`hflQXCq$u&L5W<_s-`TPl)S1Rr7PkpWwB6oP@V1YeBzxV#yC!;_ip{P%&eEZo*h&<%^ zzq)`Y;V&m1FE02$?W}+8U3ctV$K320``**^zaFfRhA{^O8g_Y955*6l9_4C1_DVyX zifIl`>C+DL1!Dz%65swSeD-0>CF+Oq1HYMT&AZ>tD1N*9bChe$(|z~rxxXO|gDUrz zcgq|)BX=>ump<%UjN*Z{le;l)gUc(M0%`t6H4PeuZZ?lnPVb-Wz_p)x6lDJ@*DE)( z)txm3J3THTc=k#&qAbYc{kZn{IbDW^2I&Uqh7cA~bssFs1}A?Ry$i5CN&bglCp)k( zfHUxGDDPu{@`+#F;vg{XAz>_rIYF5F?5Q<>P?Y#Cs#SdW5qe#=5qZf@UcZ^hjq@-! z-)$GABzscaxbg85p=QuJ@er2wgg# zl*{D~R$g3fZOd!(*eB<|3vX-lz}nZIbjk`iT+QBxiH7W=fPRkI&!fq93OFY21Ic6Q zf$cYQR@opeR^&r{2n&Vb)9h79)la>oKU>rIQPsA_GwoYj_yIbrZCiQxxYfRHyYp#j z%*Wc^HJxs|O3W0x50z;YRWT+_K~R}WHUqCgFg;GKXy@a_kNzofR9?S{AnGnE#Pxn% zr$d|FxXT_s1l_H4&<^ReMrB1iv@dwqoEjie%Pk9Ni%N$4ers`FWXF*UxlLy%Fa9cI zZ#!~9wE3)OF=}B-FJv82_UoJ2RwDILLhIGfeDcfjq)S<6fqKNq5OsC}>M9B3%&)U@#vgpVH^Bpa{R~mMNENiwL-YOya9-Zfev!6Uuk>wj|a9L5`c@NpH zMnUWAzKLx9o`#2>78+-w9;>T#B7>{uR41ZUuJ+wR))uF>fH7`^4=w1;ldyKbV_t)h zMqv883mKV^|J)3=tclOf#0qNVsO9l7qaKIF{X&u91>^VQspE0u6`6C~Hdd3PhUslg9!h(N@#@TBOL9HwcxpN2c@XAUI&l8?@t-(;zCV~A z`QV`p5v1K8&rxu%8EK>2AIdr3Low_R3nJag$NgCLqjSnU^2Q#lCSBZ(NByYxt8;wZ z#U>wQoy}|5`7?y6eB{sKR+rka+;PW@@;bzP9L}0oKe#uz$9g!plXw_-ta!Y3mv!$+ z4Uj)0YTMt*%SSe?R(kmC1~cDvw6*NOasyP(sM`{D#nODeAXAg;ay7T%AFXcCa8n+#S)RyFgg1U&ts@K%7mSH$?sE2Br59dkxyFg_xv- zSUwG*SRYod8?HRAu&yev9IlVAg|7pyqON*UL+qb29|-qN(nGwS;1B%!>**mOPt*?y z`_Ej@X{hzxp0sD@r~E7N@#ogROcx(%@QVpqSac2lbed}QW`$B?@!>l%|1)-#lu(v1 zmT;4>l2DQelCbDKMB67YRCTrMO+#BGSXI5~3Oy%W)xQV}4MMy7D581c+FOK{Kq#Vd zF&4T`(5Zc4-7AY0!g}90ZIGcZP{UE=s=kTplfqj3NUqje;%cy27qJxa5MdRO76Ffl zk06UMis*_6i|CIiiYWV-r{!zBi56kP+F9?avk8w_XG0lpo<@kXbT(X^^&v4S7y|1r z4*Nhr3BY|+pAY7<=DphHa6|}u3^V4(h#2+Q*;u-dN{srdjm5SyGe?D9G92osb{l$n&-lrhu(o#|Knh7>ya|KK4v;b}_>biWHHG;E zYdSX#g|e}E4F25*yM>D}Fb4mIg9MppqZ1}&2Aq#&_%? z*}Lm}RW9#hT$sBX?$5Sf$AoC3zFlq)_AmvP+{3r{W7pZ9e%}{u^?(LaX;NRwC(F+& zl%|fe=^59zTS|?lCtE0B%0uLjXnoRGjoej6A8B#ZQJp(=$u{!AT30K{>mXFY&RX)Ff*8Yt!YF--ww+-` z8INw3zEFXu`Qgs2T}guiVJaJwk$OAWGw85%)2jDLf0qU`@DDi*E5QYc=_6ui_Z334 z5XZA11rydsqeFv|^VBDw!>E${)Tgd-rt8#)u_2IEsuEMSI$x2a_flxRjFkc;J*}F- zK&!d^WTalnDtoLsoy;WNK-*ATYe9EGeL;IcV?l>cmCuk*-`jpk#%eLu$iUlTsbwtK z(A#E7$_f|sSa_0IPdH}G7|?KXT7L%-V?in`4O``>M?o~khCwPU`PyQYfyxi4t9}S5 zt(IwBGuZQib9XpxKXpHiFck#iPi;xPOT|qOsX$d-c|fL^dsH&%sB>1E zYY(o*Jm6z7r)yW0>uR`34@SmJ;16q(nk4BZ>8PoysjC^Q8K{}e=+CIl7|rN#>T>G4 zxep%V+i2zJxS0*=FtgRR7H)xK9GJYd3{Bjm23wfAH7>hi&UIGx-2w&^n9sniwOa)- zQOxAEzU^BPF@(&)8kg3C6il#+dyhdAX0eKU*^)}G=2;`6a>WY8+8j;65_kRG6Nq0L z7Bfhbt=7=U&vJJNQk}NKlu#|w&DFqFR^M9RSr4x7u5YVvsPDL%hs-hesQWqZqNRa! zjP?Ayc55KV%r0P*^Db*zixy0+t@f%L@|u~VIVn{cx4_AjT;Nje ziCTUArgnP`=mOXR+^l1zy{oyalcuSq3Dx1%^3ufCjMvUp^9qL2YUyYObsZtVBw*)? zWdko+sF4<~rl=uY7s{>0uK8Gclm=b|KWW0vpeEoaW4K*Y$Ff1Lrf7-(S9sr%3^cY9 zq|IJytk>1-?*Yevwn1f}B~W}QHk1r13VjKcg3{~c>vc6C9H2T{=U|x8W6@DZ<$3j! zAN&JUMO)n9(H?#b?WqkeL!9&0@xrn$c~cy$=f^7r8?EgoHhCQ!_~uEqQuI=EG*mV8 zHH>m#p@?LwFFo$}f+90LwRd25B>ljgyRGA^}OIpa8@`O+z9>%4}%LI^=LltBDjyHz&*uJm2e%Xi9v|bV?2Dl z=Gpl1?%1WWrzqIuu?vnnFRuCQ`=|k*T6nI1(t*3o_h>y2BkqpH!Oz9L_CH-N_`->Y zc#HWhc@&*8pd4kJ(~*PNW5j$sj!pv2R4oO?1?>ej1r-Hd1sw$~X1~o^%^Eiqux)s9 z=3I^YCnIA?x|dyL`gySzc>^mqk=OwwXSS`CW+we%*tq;6%NMbcWyE=Qz6G0ukvhaM z$9s!@BzB1Ny+KhWXU)vYCwoN~!)0IJZNyL!NrKC`{kYSKvq;_L{Lmc9l=JG6uiG|n zd zd0w^T9poG2_sa@YDk!~FB2t>mvgVm^5UR4Xn~N!hDmi5H@i^HFH8Yk~{HZ9esHliNP%jis=vv~*5iWvA;H7`r@3 zjdie6G^3h!Qx^U$+-rw8l=D`51s)eK=v8AG;5r!1w%2$`9lKR$R<~70RMS>lRclo@ zRl}>5s-3EXs{5;ps>_=5V2HGZ27@W5wIzSG}7Hs zRE^s#wu-PlPN|-1dTNHJEiCE-;gR<7N_B|+V?cJt#A5m^U$>L`%1yJL)Acfrlfi0p zm6Op*Wb=g6%5rmyRkKygZVP^MT4P$1RwHjyQ;Sy%cH^n7aKq_p^Yu#ig16R6O*6^z zZS!N}>1H$ia-JR1Y9-dG$0?xg6xz(c408@>Iqhl|Zwi{fU2Ilau~^<-={ntB9&WK| zRI$%r7OM<0Mi!h#HPt$sECy>L-A=;^vcPgjr$vg;JLS$y_M7kPck-qf@FJKIV*xa9&Y>fllZxnLW@4+OKnZumE1iItqtIYnufN9>W0dOT8}>-vipp=WnOvf zerEf$xgam&89$}{n#p6~o~4KCNzq(>9~kVRe=^?_27UOl{~=e!TWsY)a^FR0{WP%W zI&8AXJ9zfNPDfG4P)8Ef!aSX}ng-)7a8O@ttjRR;XeaPuJ=cdewL1hYM%E}98FbXQ zUv=1b9CZ+PWOcN6AUYyDzIG@d33VM;)#Mt1yo0KbAvMSC&PU5@UJ8p&M&bwUMaPph z9UW)K{>8^jH74F&|KRS64n{8CU2AaT#r$@$6GZDV#hP|Y$I2qv0ge~FPnnm>LEgM* zb%6C<=b2Oo$dmopcqYjAZutzpChP5TDAq09AY9h*yQ8(EzN4U{q@$~&Bj8U!E3)`Z zqhsnstSrFhZg5S<(*zpibcemx;<*lcOgYQZ9|sp8OU~B4dlnwO?*i5+ ze4e|8=eEx=;Bm*9|G3-s6Y~y5J^Tr7Sx)t!4ed5TQ>A%MqQiHVw>$8U@YLJXJI!0o zyZPJ3o5nlO+qj#+iv83)ZvV+6!Lt3%JS=~zaGsIq;@#@}b#C5@fAFe@%G9GM z?w!c;weJ+Wf8P0n)>Li4Q{{eqo{#_2>h)q?54_!9wCe|~7&Z@Ugk5%}_?O)%pXNe^ zYkkztlVMg}mHzy9qM;s!Po;B)@ig$oy3McSTkcl8_uf$t(BqKIvZV-s(Q;ZtuSBhIRkw{(w|I zMar+Y1qmbBPLTTR(4Y=P&3Q+ii^{qW()oC0<*vV57;$!lv|H~A!o3xpx$|822x7l^ zEIr@t)@(@rAGrq3!GvxL^8xU zL?T2j#498y#301v+4NcNvFtXa?fK-%1Id2YQ~!MQTo(g> z8(y-%?c}={`kVdI1vil6;_~l0`ZoH@u!7$C-kaXI-iNSQbkN%xQbTcHl2K$V()Ab^`cMRQycvbZNv3KQlh=}jvMt3g~oq{wl z@S>vE20fo5(ClI{j2-tW_@cVE<@GvIV8}&SFYfDSq5GEJkhgkLZc$f1<>~u^ zQK`8|>$ej`S>lbpwx`ocQY2Boh?n^qgBTnbe9C>x1Izu&gUSQSLu@?{QVZsyym5?p zJeQ`^q&gyNC=h$oEs@^1Yh0cy)3}jmpO*PO*QceVaB)TXZsw;Oq^58m{SSH~VW0dR z4!R0(BcHf#S~fE_!_B5#T?MQJoCGX_oVU7#L~TJk^wf91R>iOWjeMY1i5KC&8cQ;X@8rB1PP+U0#C6r8wrG1|>&oND zrwv+cK?vt6+rw!J&pI5;@SEV7Bu6Gk-6 z7!|F5S`$U&&YUN}_z+=05AnNrF;2wIK|>LHe76u{c;@b__@_tLo0J(9pMjH~D@m>W zE!2(FR>Q4iM8b5Z8O;`Zzeg|~HvG4SCi-jT0)^lQ35BRrkph}Fx7a3j|ScdqT+ zX0p>mEdSZfC`0}&xm52or{Y@@>b{eH(!K-EuW|U>e+4+bGEy>PGng|-GO{x|GLRVr z;~wJ< zZr|RD++yD1G@~IuA^$^SBGHjAkVHr_4D|@ET%G-bQ<0=nnW$>l#d;#agMa)l_3I1#Jc!X?1Ho zY5mYD&`Q#B(~8!z)6&y=(2~)j)xy`hXL84{lvk&%oZSFzfwmVnTQ*uYr7kw_KL!3L zhLs=7J^piDeQc{zE*Xc9?%-YMXtl zc7xAvG$_MZNJ|6b?59)&>Zz^{RFfwCy&Bl#m4;xELK#41_RSxQ+7`f~b8`YOv`mnD{EuBg!x zKg)3z`>Jy>=BCO(sPU6y_zJfPu=mIc$veUnelYD{>Q^egE|PN_J%IG`9* z%wOzNj9aWyTw6?0EMCm#PT?+o_2$a=%JRzdO8$!Y%KwVsitkFZifbnZgw0X&w)Rfe@REN~#RBH$sL==Jraex>= zY#=%iTzO__>~iLE?sC*}f>5GRs!*s$ethW^p!()`~XcRxFk*R#n!H zR`*R_3^(l{M#eCha&EU6P!2eWqdQcSZ{^fU>d~{&atNxXntl^@c?z9W7x% zi&~hCm|3@Jw|Tc&w?(jduvM_llX-J}r>>EnsBV{TkS?`uk=~rHhwim5mhPCYgC2=) zwyv;lca^I)3v+bZ5X5x1K1SDyg(dcHVM7KBo5HyIpW8Z57Vfd3P(3z+KA|C@iS~i^ zQU7s&h<~sD6k-4|ikLu*-CFzD+*#mS;+o;ws9364sMt)IP1&%Uvs=`f)>_wExmcZ> zBJ0AI3QlajNrj_xA(N*2f3TJYPcZqCl&rA+;WdXEjp>c*4KMv&n!wlycP$j)%&}Bv zqL0!TVB9qo)FQ=;n%s1Im1F-ar)cwJpL8cf@Lkz2j~^Zp9`PQzbK`!0{UCmmexrU9 ze!~yr4?_>5cKhC2-n+MZI2$;dIQzf$O5M0y+gAT`=4^?J-A``SWD6VHa&MD##@3l! z8x#F2Iu3PzQ4{U3z1+q@xJkZCzMr?l=gRJ0>sssP;(qRa?m^*B;g;`a;=b+^nxCsn6e=cot;OcC}U3=>8S`wFwp6K4-6>{Zyax^VZ6=rlKG#2qWTIuPMY z7^~c5^dp7^!`|mb*lY{>lEc(s7{)Kp@y;=8lYGK_67CZ3!tavrBJc9TgI6oB3PRsVR8h($QjHLP#AHGf zc%k+8{Tnpi|JuGV3Q!8LOk>{s7xkA>fJuN#fZFz54x!;kLjoSmtXC~)xM&GzQ?J<3 zTwa~OqCm?>6Aw3*bTQ*M!!bSuJNuZ z(<+}>;_yn}%!Oz6k(x!nqo%<+lx$9N^7g-9;B8_cxrhX&C|5AwzK=w_|y5b`m+N# z^BKW>ix&{v@v|d3;3qQXbxc%@Y_v`6el#drCz?OjC;IN^%fZbpTem6!(;$C%N)yO z%Ph-w%Tmi`%RI|E%XrHo%LdDY0TVn1jP!7YEt-mCMY(1gon&@K&;~6JhCv_b4XypC zakTM&az`}uG&Zy#+W2JaQK``nqa>p?qqw6K6`)2?V~^)=&!3(do;jW|p6#Bco=Kh+ zo)MmDp4FZWo@MhY#0>H2L-Hvldb5UhKN@voQt(Q67&Rd2?j`?KNP?5k(#R9Z)9p)s z&r^9Ryg%eT#5lA%F}?SajKHG$EA)qzEUQL8%S47f?MBSpKH?rnyQwP_TI6zLAT zzgO9O#*TM}cj$NEfN4qhAoXDJfb|gYVDrHL!0y*kSXr20{>MDSJk>nMJk&hKyxlzA zyvF>8d6IdXd9``0d3K-9`|>wB(v0RG-D0F=Vm>PrXdU2NM`&%TQ^tIVnTzq43EOJk z$}5z)l)RLEha4`t<3qIQ2u%LFUx>@hN@zM?f6)z)S%k&ke%#naSJOPno{PNfG7pLhrfpbR=7NJtc* z4?qxp2s^|DvJTNs13?S`*FXs{CKP#w2YS6_!@&*>~$#X_wzs;DTQ?$vJ7QK)sIUjv9`xkC>0L zkA#m1;2+>va9}u7d)!B2q{C2cNzWCK zn)f=t%URD1=`?t5c5r@;7+R#K00U^e4Foyqva$rpb$z=c7i#JR{+w zvTE3P;!uYpn4R%lq!P*Q+-O<03PnDVSU?~uP%@}52rp1R$WknwDAq>RhBW&!pnDPZ zaIG`3&SBEMvE7G(^^yROz}RS~=}klV?sK7p`4c-ajhWuUvn=*3s)y&zMPm@^&p~d z$#r{^l5+`@>e?4jO1AawZg}^85c>n{qAS5Z(WiSo@cABoL2|@D>k_wa*dr5s{K$uJ zg0Jj?c0ik;Q_!)_j?R|sjm5njYCafUyeJnr3ZDa?Bc2pgvFl+WvCqyxe(2rvn`hf+foJLGkI#zFj=<;UnqluyqvhzE zh%prJlYhwH^&MPf2kID=fXu%UKMt9EE`0WSHh8*2Q9SfqKkqz`^|1bB{%!EZMKkQJ z1n*CkzvJJRUR?i2@QMXXm+;-Ecaipqt=|W}?|k>v3`>1ws){X%X^KAos{G|Gy6wyH z7ia&Y*|7W~^*8Oi!waPsE&rXNH@*_V^2NX4+pzfJ@LR{U_HXcymulEtwA1vi&Ksz| zv_G={Ye74EbA$C3C+@8x=J=a&oN;UxtO2yGmvTRDz9|3ZP<^jU;!CMYXp0&CU&8A= z%+61~v=?7D*8aBqpm@0w+Kvv#Jfh$cu(h4uq0Im7C!r9U{@Mux^9?3e40a5zJf=Jb zIxyW1o@;(Rk_}984b;G)V17)L%Ht2SU7#Yl9^Q@h_z59;qhsq}8=!ml z=gI74e`I9*8xnjzV7~Of!ar7Km3<50PH$K6nzu1@p@E~j;jWtssF%+qSkq;auzs=Zw zo$y)=6T)ulRdx{p5f%|X5h@Wuk<)y1M%>{@odV);;2&g!Q3`!pn^FBuo2IR_t3<&( zDCzs9i|b9o{@TcOtPtWSCX~`W@`7Ts=W{1@``4Se0}4(CbLoMBH~Bwl-#dKflW~l{ zh<_9NGx}Tfw^)WahJ?S;@2GX(7yir&A>R2iko1PKkNAabE3hI=g<^$NTj~SQpDXY0 zKIchy{_>@`WZZHXxce#o{xm5UNRZt|lYSB;g;H*Rrk6!XPe$huw$ZqI?I1=bo(m-K zs5L%wQ#VkLeP*Vv6yo{GppM6wFz;#m4ht-?nnbUPJ^Rj&>Pp^{8!Vy!IbT{VHZa0I z(Iu&e+MuYj&|ezhxG%s`15EkxkOc4V$;q3JRISYD>)QV5d`lHmR3;8}L?W2V1QRwf}f@1rQIzyD3u z!NZI~AxPFG8b;p5?GK#f&xIC6bA=5>u|>> zkpxP)X6`?H6HoMxo5tF4{60gDbM6U*zVxYq*V-n|B`zciJvvtI*XYmDU!zjI-((cE z-f~8g_s6^+`1xd)xQv5seqScB@u^rU%~B>ErXU*x~Bx)zK4!S1-A4@k~ig_FBhXScBJufs}l z+4wC2&RVN`txj&j+up+u!)SvMLo$Pd!zDvj1G|H1gJ#TuAHFlmRcPy8Yk(W8o65HFP*5zA=&nO5>?WS#qqtZwJ8YYJ%VKML zYi{dW6G9?&sPOCdOvpw+O8cmN$A0UW_gl9A@36~j4{^m5HxX$ zpFkTa9NcEXeOux97?mIRXdJ)te)E-7lr)!=m(-WkmURA)u7K%B-e36*x;J<<7~t^b z@GuNnRqQQk-jU02M0ja<07e2{9&M+*Z>)V{Ek@6WCpvHCle~ZQZeO;_2kfG`@%HK1 z6bpQ1OTlcMXQFQet652FRih>JfbrH&}>&@l<~`~=wSR$6CkbYL!; z@Ph%K34ON`D)pYfLPPzo_(eH7Q4){XXqi+Pi=2K^BbA-Q?62a##ea&+i&KlMibIRH ziXk+Cc#P3`19Drm(aGQxOSvo>2fWlBIk19Io+ncU)uH=rZZUyoSf@N9XO*?h#(jO~ zX*4W3>T!-zBe{%zN;wbIDd1;yMXVWiPxqQ8ie8p(_%RBdQJzhS4LBq>mci~y^(=b1 zo$L=^>iL!u9hIBuI+8_4>62CH^OeLvfxHi2uO@bcM_ET}lh^4&zCC53gxh#a6yy_9 zpBTe5B1_`F5#@3*TIoaLQrj4@)CtNQ<(Jay>1hRM>1a7=d1*wU(`wkC zzg5U3l`?49Yv2@rQmQQWF?WG=fVSOZ>~c z6;4xxIJ^{(3g_`Fj@4Vr5T)qFktGBr+9k;vhZQH95@4F9+kAA+QhoITO&c&h+}1wm zXmB2QURy4L1MYV;HqT#<{*Z%s30%ZE|1!7|Nh%+gljm}Aqn#c196>Rwyg^nomy^v#;nG!a&*oTzu8}9yv0V!9htWbr*_L8 zz}l-I({AajS-Uh4-*0*k`)7wsRTS^!@Sc|_Mu)S=3(NpX+zbNUl=KXZ}C(4V*@q7U4q0Go9i>i{dQt((g3f(UcPIvqW zfCSr;Xg%_2jxwd8tbo{3?G|#U>(;fnXeWX^u#9oir?R^n+ z#Yy&H*|(fL%()f>d1Sde@2*byWnFXa2i52}gI3STh%GJ8{;d+VH3;#HT&*^>WxKcR zqwhrSech$oE8Vr;-Pfb^z<0 z|Jms(vdu*m`-=Kb#o57_UNh`w6oe9mXsBYn+|K>%Srfd0kRrq*KBvKGf&hu-wkqek#|- zdtIXe`FFJE2C&-Bo(JOxqw4@*;rp*;cM~S^bHrIgyq`L8uM8R zi8G2*i!+P!_t5l+P-Lr&m+f(@VXLndNW6V$4ixC2dKLjo)aPD38<)_n>ravk-Yum^aL0iWRCO|RTGN0U15L?cRV{TiJkAI1W_-Y_0~7;E59`^1(A6U>m0euQ3(N5=D& ztp}HMm)YN-_a4|HVqin#Uvq1-kCB@-dK@H!?e)>A(^L zGT_fjGf#z5GRvPWlA5H#d}Ds$=duWC%&X1=&qvCz%BaNT;de3l>)ccvILsszE^JK> ziUJD(_Eo=3-8vj(j#fjC;m?}O!^}|K^ejRZ{l!OMYd9chS-O}S#zzr(tA}- zT}O8B0^;I=d-Kb*tz!W9G*G|q-7c@U?gmX_dkCKa4%|-`qkX9m2>^-V=uWa9t%3qy znxhQwH)%O{xvz4@a&{PX;re|nn`t8onzTrTx3qhBt*;mbP2B5>>vpV1*Kn|}x_5L& z*OQxP&q^K@UZ>%(lBo$1Ln*NoK%g3SY!+-rY+2rKl6LNP`gYz?=oKuqsrA6VYcR1x zLBt9L>aW2P%BgvbPG5Mxc^sb3c9h`C=X=^FCIHB+hbU{T96?FE8(%*RJ8PnbeZ(QbgT4T5PrH=s@WKW)d`oGo>yj}qDFJi ziRn@jml2VwHFE=~4I%=dC9h`KDqDam@DBj3rImViEAg?X)BunV>$x7REYS4foo1LK zL%zNkZ~$Y07`gPyX}4o6XzV4VXl#2d1u{3rJ>~%j@o9+OZjQTITdSdd4TBEooLO8o zxR{iIKW$}fZI>Q0m1@G)qaR#`GJBZ2+9xC4@p!nqGTE)hUsJ{A`fYAG*cwxT&&{-_ z!&0NGSJ&28`?>rx9+c7!U<4T%EwqwQEvl+_IwMS}_(&!ko$vU~m^Ly-gNHE<_&Q8A zTB~1pM;>Ez@F|#swNVu};G3EgW5GY}-*zt`Zsq8i8f~radO*qQS{hZ_`?Q%f;Jr~` zjoqyLXls(yM!_F7?U`^jkC=YQ|C!actM$|(uNKkrtFEuO^4{%-*rZVaDAG`m4^SgV z7sn&#OHL^cYK|h#ZH^R94bE5qsD#D|#@Q4(z!J%vL>Z2XM5VxW=g|yv<%E`_zu=&I z8}4Sl3YZdez-1g-{mMI{F|ME)7WkF-Ct))Q;NiZKdX z3ttOei(ZR|_tOEeL1OUJ{vAvD&k`@&cVgP%)kE@i@>S>o?;QRT*I8G+JnqXI$Q#NV zH16XwKR2VW$bX)rG^qnx<*H$C#UIBWCuD!mj?9iW2sQX&5WfG_6dXOr=Z(OeIW3Os}DoyIL7c=J?ey z8nl(_zlkkrgFgh51oMlLQbZ*5({FlQJlx|25|KyIZ&vscbQ81pGG8#E#876P1TH7j zl*%HZ;*sJJ;&TNOY}+K4{1-l#xR)vyQy1))E*IyQ`IkL!y53kkDTJ|nuA+2Auw&x^ z@ykvD@he$MJcd`mElYZT`yL0+^VNr5$?tEJ`xrID3~6}qfPpz5^xvppoWBu|h7HsV zbPt#fr~yZ1wLoGIpiDG3UN*9XWbNyuboWMZ0@Xksa z&5hknTrJlA9rKKw=ZE8;Hb1ck6o4)M45JsL>Za_b3#NIZ@uG)QVSmP^R-#d&pP*$0 zBs3kMGn4=OR#0rKPRf{+Di5LAO~m{*Sgej08BU!f=ZTjxK#L)VmmF722TjIFG@T`y z)tXhBb((!SdkjEK#7*u^?7aN#=R0a#>I6C!8WsAf&$V>5^e!|m)aR7vRP^4qiCkQf zar7x8AGuP-X=6vYu7a=3G{fc#h<8XIK0k0h2!SQek|>h%lT4Du6MK?-Ql3?X)PywJ z)%+jXvAMSM(e;$|l$Ioyq%eMmt8Xz7rGCatmn>FQ#r}LWO$q2^TQzL+f-fKGcX>+4 zp_DDpc8PZYMk(2q`D=6ltM@-I4le<(k6xr+pS|e3$i1k%u)z||8bK9Q^S1W>0Py|Z zD*woo&r~X3A#cy>B@U;=FBB?&1Ahf)fs?}Vnxg$<{Sy$0h*Sgs0qYe#%UXRm3Iv0czy$`W2jIyj~!YbRu0sbbJM@Garmo;t5crj<2qL{|GCWk|2 zRE7}XZ*o>E@_#TRx8UP);W+0w=gjBG2Ld6JjP*?M%<6IPbpA?Vumqy+Yr{~zHNb@L z9cUdy9Xzl(;8#)$&^+5p^HM^hJ@bL-cCq?R^M z{^4LJ4^CPTHYhCV@?}n#$NqC~j%kQ%h-~=L5V1Xmu?2vr07j0cvQXJijXp+M-ZyFe zJ8uL&|B5TxIoT!MA>Gs!#F^xrq}2@LadY<22VmJ?^7W+7B<`e(GrhBovyZdM8RZ$r znXNMye^~b5#6E$WLYpsJlEUFMB_P>tflSH|hb?^oF@ZyF)4JaVYFljOZ*2ic@533^ znZw!ZGZW1)&rSFST$Xo47sdlvl!36%fJNyKvxe~*bFhaJ4vuYDU0(qqc`r3!P|_ug zeYmg`;dP-nf1@(oaCsuaf6j0&aQ^XJq^bF6G5D4f2)v=@ zXkZDPpSZEu<_(|lItJ(m$_8`>7Y8S{0=8tg$^clSa;`qpTYrv;VXE$)ioou9UX-4XapYR-MsU9ysoScJZ3D&~JA|!~>K~-*VNZ{Wbk1 z{pJ0&{EhrI{B@Q;2QdZFKhiw1I)S{B;mPnMY$@K6S4);lEC4BREio-wFKI6+>wRa@ zPG?X^vVvS$%5YUHvU5WyGo|*@$x?DtjS|<=L{mjmx{|w6fk-+} zvXL^t0NsZWj_8amjnv2wlu(l zmNMvIiT_1tyhmWPep_rSEUwD&La(;`@U85?ifC<^YBOAKS$DkV}Xhqyi!c zL4#O9@FCccQ-~4de^!6E>ZQVp8RpV5A(FJla@t&Jh9%M!s^GM!G;7-8@HFyWHLI?x z`m4zLHmNWX^={Q}?QWf5jVIM+rWGKh#MKIczJW?ZNuhX9OeirF9ZCoIfU5Z57-`;b zS^qDOXu$(j+l7$lr*@ZhE3m$!tCGW5^EaFCoWfrmDcFwYnZ5+=&WmttBR|M1G^^- zw}Gp8IYgl!RucUt0uxmL1yT+d#O;yQn|9$@#PRAy3#9Y5yx)gP-RzYCp%A(kxtG3I zvsbYfu~)rUxtCDdwb0QV;MB96)U@qT2A!{9hLNw6 zufS?gHCFzQwD0h0qWRuOQBgtiDk{@qnjpX0I_Eu@q&r5xOj&fe)f;;UprzfabX+w^5>+t`@Z#^_$dmiYpv zv$L}(`jGEs^xw|y&cAMqQh^q~9ShRvY7xb>(Rv@bG?{Sl5L(LL@*H`W{NHLq_rtU@ z@rJ)Jr>UiuBq1x$I`zZtIz5RbPpiMSpPH%`hjSt`r5YsvNWHSXZd+WJ{ARxU{QCLb z^Mns`><^{-RD!qE5!k&zGE8hBV88|UA(IAFI-dFsbHLG6$8y{7bHwsed#(1t%8dcV zObptOFmW5&h@qEym9)IDwFTg*D;@L?bPD}3ool5F?+>JHbDZk;qzFK3(o+xz3lzP4 zt^C4#Xu7L<^)@~ye%25Re5%%_auxZS7V%OnOekM)BPa?{yv>XjH10r)H30tyw0PqS zvo@K|XqbjKuGEZrln|fS)_a;vhc(dQ)D;50q}0;9Hp??$`U?}F#!e*>_VETqX;w;D zPpj$Vh9S5pRU3b$jPI3Fm8bV~Yr_c~O+}8bzA&82$Q%*+HN`;f++M|KOV6XO-~OO`=1c72?Rs6#FAhwb^GDxrS9jBmnv?Ve-wX25~Y2v@8);BDa(}-vnM?Axv<;O@tW*ap4ccYZ4r&Q z+ue4KEW<*S@SR5uqSK#Cb;W-7j;y=TkLX8RN4rOxfs8;}AU$v^a3^rHiPp5$wA-}Z zw5iTer>WD`x72slx2q8AsCDEz!V~2g;E7BH8f5TgP&2e~FL5_;TDSn5icu)JIDZD# z3Ht*ZhW&!|nULQgg=702=*hr)u|uo~4#8#}@(_{}#Tmef+(qpoU4gu@lcMw&D1=~X z_?mq{vS33v={xdY!HV$3+lWoU;{0)w<@=k9sBQK73~{48vG(}&r}R6Bzv@3TZW-l> z{o*2R(cNk8TkgB=+wMEXjN+~0-Qpc_hWM8Lw*F=mBZ?M9kJ^f2!cC$!IT#!?4m!s! z$2P|f$L5az>6=r(Q{PkXU>Fl2;S=oL;@<*m@dNvTeQN838ieXL8aEoe8olb%rlZ%9 z3?9Z)e++TKuhH5wpU;~*ZNa$8uxH$6JZ79>fNF;&{ClUK*Z#I%DD{h+7O5TVeyK5#VHO7bpmihDh3nV-E7XQCb1|^4hU{tMbzT4W zYEuFvPI!1-=+qfcaBUfrh$0CUj%q|@qY_ZLs1K-mR0^sP6@mJR!lE)zVW?wIZwT|b~6EF7#I{5@Dcm_u$LsmKN7 z5^^57jwB&hUEctGr+ub<6&h2mqD$RMi%NS-^-5u-7fhR}Rn#2ndukmunOaaZu=J+h znlq{237%s8O>bkkKBkVdk+Yt&fwOM6akpMxvRZHHdEH_uZ_@Z(-%D13+_(nkFQhRu(eC+3)4>zhZ5#K!8__itgn`On-)C$ zePi{q&-sQU>qhHFE21^g%F-&$I?amDnriK4Rb<`E#HB`Bvs=-uX;v`nO6y81Ija$C z6RQPl2diu=Q7a8=R%^6%v(>cqw3W3L2GfE8V`ed#7%z+zMk*bbZj>I2iA`r`}}?vztnVX@>Q~Z{$!!g1I*=o zy!pa))$@iK@zh1-3B$zrl^o?B!!KOqV&7NFy_mZLuc2|3ttlIQFp_d5#(Y2@8YVZL zvZDXyo(dOcYd{$qB{#{l6y>X}+>Wsycn19_H_o%n;eDWn#x!Eo(uo*ix+TUkJq^Q$ z;Y)YJxTP0iiqaoq9;WMI^wJ|S>=+sbhB3jIq{m~pFkI=P>FrF{<%QpLAqqTP4audGC8+Zu> z$PC5(g3gU>-ru{!bg#cGTF&R!=6CCzNBMchHf=Kibxw5CevhR|XohX$gPZ-8;;@(H z*Am;W)mnOi?vo$dJ{D?iT{)hY0m+t*KcMD+JW>$BtG^BT4w(h#*+sL^yR>QaPbLW%=V?rI~b(PO@hHA|JqkN0=9jkrtIQI3_n&%D6ld`9WNPE=1wY{Z1%HH}OX>Vn36+uQ& z5etYV1O>5d=m3alm_)P^&`I7{a__+nR`N;zG7_JzH7*)2v zo61(4`K*4*{EFeDXOuZOn>DD)f0b927ssiC%3nD@Xplau-idJyAHVJ6Ex^y#+vYte zAkQ}X*!LfPWwuUZN`d!RwLkn?F;8@`qGNFj-F}WLviy{o7doFrd*bFk`u|pWEg%!) ztn*%U>gwN%zUP#W1cEqiyF9!6y0n=K+Q=@|F5WKJuK&6&clmV5c0KQU-6hiXq)WYv zzstQ#zl)rD4d$4uu;J0UM2sE~`0y^yC6M95XhUC2?$ZUegEwBfk{*>K%>&8YEzH~C$$;Kl*_ z@w=cWv){@F#g;{P1d*)G?`fcpN>bu>8Q6 zr{SFSd&x)S@33dAzBhk_4?Gh4zA$Kf@vZu2u6MWD9K(gXFTDMH^{g;3C$wtt>M~0e zV3K|GiL37U`?q=Se7N)M%8M(Pgh9VQDF4I$`U%HJPUG8`HA6Q4;r#W)>Dm1kXK!d| zJ-hvaWl1C8ndDbiCykV67rt`lKJIyT?JG-%#+zs2*RNLg1^=qrtf-|r5-cD`N3lFHs626-a)jq({Fe=T;DGZ zK693qzqkURhaw*Xzk5D2EnvsK^>+43xMrXHjO!X*nU_6ExGZ8bn;{LJU z0XAWmHVvtMtC)SIoW~uRHc&HLGnV(W(BnbcuY1)miJFSv4eS;s>J($5Jsc*I6~m%E zvnO>Gn@awKRoZ*pviwjo^$PN^`o`q8M?j60WsC$d(O$1Ick;P=PYuGdSfcZ$Q)HFa z_!E!YHFlPtC0c8UHPbcyH4`;GHA6LRH8V8>HH4bUn&Fzh8hp)QO{?OpVwYmC;)r59 z$srAr_Bzcu&5qBF&x6m2&z{ed55nh8b)&ja?WwM`ni@-DaR~uTuq6l)ya-YR905S^ zC)5&@2xA0OLK1NX26paakY zXbc4Dprl{>lrQlD&3c)JD^7ZN;B@c?G9#p=v?0EG(!O6*j z=T$=jB-i5pt9LiNN-F*qn?3Bba49KIG}kNEB0O=!D&SH*4Jz|2;)^2)=iJg22)qu_ zHQE-U#q)$mMP=|LcsM)_UIdSXXTw9`7RdR{W=Ujd+E4d0%B;b>Gjv8vPpmD*f`P z%BY`FRZ-;}wH%ck)f_b(6&zJNl{?itKX+<&Dtv31j@;G0KM|HqeI+C?X_jP55+ZGo zyhvrFSQ3NePf{Y8lDJ7?q;L|7)JRezL16LqLxH1#J%Mdgb|43k2gnKJsolp}Iqfbd zBqO9KB)y@uA-AF6*5_I|B`!shJC32ZRIX0%bg1APa$!@KB-Kk+r`M} zq>A>WS^G{PwlqQ7JsOf`L3=>^k9L{nLwinpO%tJ=rCHJVX}+`< zwCgkk?H0|Mc9G^ylcqhR{X?^(3Da0$LNGvO?L;r$(^c+R7OjLLk4Y3 z+oYwhbDRln6P9M#VMDh8=On$w>I9eoo(oFv_*&_6s@@WHm#r?9vLuD}w&u=V^OmW* zXmwGNg&az2t)JufR#awBKV!&-ZoNO}=zUH3YC1~{Ti08MHsd*I?|+rg@pEvVI}Am) z&Da6T)yA)^UPgvuqDnejI&C|pI*mF3owc2&ok^YCo#akvXMU$xXIJOV1&syZg2#e{ zL;TMn#3-TzF@fko3?bSOGl&5*FLe)fNUbF__m+ot8s0nD9EXW1= zGmF9_1>v*rLs;fmtwLDK&)j^(=FD3mAAd7g&nFk?0-#Gp~|0(3jHo z&+A_J5~3-5^W!;@5N~0Qi|3z&JZj}0XI;JEcSh~4K&ztN!*U)oKrt)ftXs&-Rv$Z_ z@^fbA#LwMip}h@gwX)-s=1V@`$CAeiYJFjMS(+#LTofy_fkTX2FWMwip4cc=Fs z?_uvL?>=w*oJ`#n$1V+}hFYYwvr z?fj-#!0}(`FJZYCe3}>DzICwUNxqQ!_O&q2#|!Vycoi@`!pt;szyEsX`LEnp-v2nX z!k%bgZ{t3@bt^jZ$Z}A^GpS7-5Vu2n;-9(0|N-$e&QXaL?tuRq-1w-(;tpgpZH{c_rA`tG#W^!e$4X%*|x_2N<+e{FvY|Ns1- z`&;?z`@b;F*YFj@SVG4ht40nK%dK#zHXG)vWvLYt%ZT~JqM}h3Usue3`S|<3J{Wzm z(Wkznsy_{LxR&=+Gh&Jsmd91|VzSzoAHZ)k_*kbfCjzn_5^hi~!_PMaT4zX_Cgtf3 z-KGA8|7^Hr{Z;Zg_jmTO4=cONipvBFVTG{#it=jZ)p8;=k&?K4bLHkTiAq|AtOWXU z`8vQIe6!)%zM^nZ-*$MrZzw#}_ZW_bH~RwN%&88zwJ!!fqblXmSYtr^aa?qqeVljv z16_vBM`xo;(BIK{=&a`a=IrJY55ml#&6uKVnTwR0lnY-~eszA8ShZMHSCvjRlUtfl z2zOqy&~+P0t-dEwpx}DN0`sb9)cK`FF7rdpEq9XFqxx0x4cB^$n8Xq%mlTVz#L`@s z`xX_61s%>0tCuEaJtAt%EOYwDNG`Bym&q3%pA?^_eTyFJbakjY4R2-yc{P`wp0KH@Xbp={SGU>-s-RoQ9oGhjTo`*l(nN6aFS_ z(s90kG4+@U zfw6(bf!_n?)yQkZD2l`8S5kFJ@N7lRS>e_MfSS;Rmj&g>N*W$sTIq zYfVmF^>Q_G4Gt4B8&RvtejIAeY6qBgOetnSU^>Q+{0+lO^s2J;Xc<0%9xmj9r_p4IVr3INaLxnrv|e6M}M%X}jn(6p>N2;d)wi z9KVZU@(_VNPs`D$!X!^L)Z#x-g3 zgC7>-1|?G0UZ_nNRxPYm`&Bhh##FVhJwxRRxJ%WdJ^WEWnLnx?M%C795xX60?33hL zoJ-~?s1mJ>GCkbceY(cQBw32}6q*2K9S`izuhmx1P6p{64%Sox3Cx8M4;U&3h*zv; zk}vo{8?#G1gr-0RXgk#DZqOQpHYpXmx zr|}Ix=^X@ai%hSzs>u>a(a?|D-KqyAK5qU@GGDSqrUyEYIe%_a5?c!*Fj=caO8~W{ z9@Zlb4e*!LQw!57&|pIo+?+a0IJDewYp$hd5E?C$s%n_s{bz#LlEnl8`EdHb>Mtdl z4+$1-T9$FI>TbA7)Si_H?j5sm4Y150ymtGpFqZ1kUv%={yh1uv}g&F6j8AR?DuCq6~MJdBgoruWW@?F41gB3Dd)hG5G768$+N`hWT zd0W&@hiDzXSb0HDbv&YctGAPtxsC<)ni{LCWuhCP(T#lm3hVAuLwj!)4x={|m{4* zht(L9|8BBVA-af9$ThJ1&C7*TUQFGpUA_K!ICVuh^e z1nu&lQy1lruGqGZcq`rHrA60wx;x~_Oeee6@y16-baq5X@^ULmI;YPn=fCk=DDF&& zwvHa*%U3Y;NXILezHwhz?rh}MQH*hJs(gcB((twOdb#jVddn9_4~5ms4Bv9)P(0x4 z4EsJbvh3-rsLI>Wc{`ewk4=8pYn3>j|Gluq{M7&0maeo?HfCGYViBBs^88qc-ZExe za9VN_c${)DLI;m!{s5Z?XPtyIiI-yWLO;M}!QX;{kGbiYV;e=M#X# z4wL+R<=`d#AvI}Ks)U=GMh^s<&K5waRJuOU+K9kKN1&?(3(N4Ka9ecYdPyTSl0I>u zk4ECQGjf45zzpCzFcNihw{Ih5LarJ58rOl6oP@Pyz!>Sxuc{z$ zk5Qacu-1%Y#%e7z6PP-gRz2ogMzAS7uH5xm>zD+)1{VQs58c-4Co8L=u0~@g&ktd1 zM%7|&M);FwhaPKNlVxtPqk<(sD^&Sz@8oec%FTZ)=y_x1Zu=y_b+%~QrmnEYEV`HF~hmLDp z)v@@}B6OXf$J)wdxU1=CX|c8O;iI+fNvCQ(w~eyqvfYfyMzTdox?|PD2Mk-;B(XMjUl++@>H)~mw=c{S1WWs@Kn1xqKKEay$1CFn% z4lOD(wJpIuKZeqmR`f<&US<}7EYW!fzI3C}{2xZIm~n`{9~`bQNh?cFejGT{<5t>7 z!+!u?1)HP4S(L7q&OAHTr;m(Mi!uv>nhxSt;G+Q3vJxAU<_4q|(=R1=l<`8U(DS9< z=o5HlYFVkN+~^AhW9uSq=4X~SJeRjv(v+3ykYH_HEct@`LvEC^@?!KQrr5@+xd0hd zT{&qy`m%ad;|H55;D?|Y@LNOhQvaXe2dAY+Mcdj-^gnJ1rVqNh@3vI`r!oQhz{vFE`95r^=a10Cma?PLZN!pNg6;o&X`ctwZ9iLT`@@=mdI&C> zvaT=M9{Gbw5HiJ@);IfDE~zDWnR=E4Hv7Pq^8Q38l)b=O?LS|NOptm3vUM@FNLc$X z`+3=FA^ySWf4R>~S4)|!u_=G(2}UnL=KF{xhXm>ap;9Z$`n>J$f7l+HzC0=Oaa#KH zXDOlgL1@C;|AIT7QA(B{0uw?Xvc2LgST&u)nJl#@(EX+D$fe+~rxrn_ zNACMZOW?#{+n}Z+>UPo@C$6qQRFtRmysC!3fp+Junp*~P4apeTziDsE?M$Q zXfZ$0-Y0FF_S)VL{&}>&ZCG#ZnNZu?zn$Sz-PCxLvQ1wyN(399nC*)$asBB^h?NjB z2{PR;*p6Eg{S%%vYYZwrO5FbL1ZBpkOw{R3+|~`KbAJFxj9$W4V5dyhCH)iB{)B%8 z{JUX017!sJwfsD+o-SJta$@v0+D;z-T@PLjvS-j5Gl|Em>-qkt0gOyPaP?utbny6b z>-wSp>1zhCF}UV%z0y;e>HxJ4AZS5P=zZXffRPki;D~Pny-LTZGW~#PB6PaZB9#48 zsCeRV^8a%?IBj>XKU#9XHmPNYL-;-)0pFc4BUB$Xm4;Dt!?>A4aR3JMKjHc(S*uvhD zp7d*_Mk;WnK8@Cs%S|ElIYSg+sR8}(AjS&>guG4vm(Khxx3j5E2A`oz^l@OtR~zyv z)OzK#HPiynqN8CysWhB zzO#KQA8w=Nl@;JgexlaIfHiYXcN}{8H0xC1{OeB6PU8+Ye9+gA6B)HI^krcGq1vJ` zgP6(ihXJ2e;c)eW;*;SU<3{1*AJBWH#|>JCM_;JIynl~Ft#tmO&XTW|I&|N68{d%L zc{KyuIG&N5Br~jLBVy)`h3W()Cdrpd#lD#!PVbC;a~8^gx2R7p9?MxtU0tK`k1-;4 z_aBZu92{*5&Y)I{RQn%VhwpBlWLohpg9v4#&b#?Rhn1!1*rmx2_X3P0b~B&gJ#DdG z!MpyarwhkA^$(7d8MQvgmk#c4)Q_EjHC$(H=l%q(or-Uqw!J*opSDFSRTEDYW*Bbr z;hU8jHeZjass=f`Z|26bj>8mqdkdH4Cro>Dm;alwtlKv4jjZU(rvBS?_~tPVS+`o9 zfUeHKp_In%4+R}%z^!w3hky8DRts<3D^c3Ypik@x61SSlV3@~Avuz(GCUe*5y^ zaT(tJw$%lD{J!*hfHcm3oT14^iwbX!nVCC&%=-lJ)1QqtrL!3>8nXa|ga6)n@6QSP zM3wXfZtTy9f+D-4L(7JoW8M#mj-r%RKW!+l=$kEKb=ta(&&*W5)oCLdOQSDl0xxI8 zzQzIS^)>oL2(a#GtJ!G5G3Tn)vBphW*F@I1>jfo;gCeM@ zx`$4sZ{iY<)eja#Bo5&_V_FvLs2zVTi`|W7p^sHcu;U0_6#JNMWR>lRa}3W7m;ECd zX@3rd;pa&XPej&g>+-ZL0@3<`J2ofHCUb%4c@9^K;g%gqCVWSw-)0_^|u4Bubr69B8mf`2*h1vVVUua!I4~Idm zbl}oV&DGz^5v}8?+1Cz zF4DxdRU`yews_(aXWl$d(mhlw)ew=G!pJjg)G@4xtSUau`KqgDq*D0r@UV$t!)5em z#!hQINPPGt#mywgsm1UhOjmDKg`y)OfqOIDEF!TVG|Vm{0bE}6j7t>s-LBKs+s!-@ zjZ1V_-9HnTh)d7ncIg>35f_mNQok!c{AD9Q(2&_o`GK}Mxk>5U-*ok`7-Wd<;pMRr zO^buR!d?-HV-o&lT;gPU!@c1zqWM9Oa*nyjj6PLKznOfe8<}ZD{Kusyazs(Qo{K*jIil868fdB8j?nSOF9E=HdS57F)F`$<)giwPtu z+#imcZ+s#m0QgCD6A?&hrlLdyVB@9W2;cGwYCv4fHuE zNwH%TpNSjjgKOr5MpUkf2<*cNQMx*i%%vNYWeoI&rYgNLiJ7;z5_E4^Ml2m3sjD-h zPW6l9)W#v7<%qW9KxR42UTGK?BZ#Om$;oa>N9XBwsp2FfeH{-bX0K6Z+UWP0*91P~ zVP2(5z9rLaOr>6q+7~y?oNOqjQYR;SD4q5)E@lSd8Lq1XR#g%ojx!*iFf*QF#-xU? zLdclA!);RmTq4sERxOglLt?lV;~cdVYNnPdlDuCQ>Dy<^y(%?4U&PMIMd@y1$37hX z8OCmOjgrhKb@W}gOVAootJ?*$+GDn`dhoohW&=zf6m=n1+tWo)_s&blLu-MlmJu7p@xx@U>`b)dc@9317_5`~A zzo%cMxVS(0_9<|!?0-#4@w%6%*#LO6`#zuI|qy6)E1NErX+KT;bGpv0_g^WZ1=;4S z$GiIf1`i$HW4;rH^6zEbpfCx&(9TbsFb8VL&U43)b%Ilb8Dc#UfncSGF|iwBi${0r z6)%MgEUw*Y(*N>OV9(3md0+~WrM8CPw@W#V*Fj`p!y-68HB75eL<|{tv;{eH;Qiiri2zx6Qg8ru42c zn;U%ICFymDLgQvNJEy8yWu$6-_jgXh>ZeH6ZeMQky5w2axe={?E52=TWnDphB>&2L z2h*qc9`Si(SwTF|XCB2J==-ZKlEbI%sTeJk8k%K@@Uxa!$q}nhK4a}Ya)~G@+o2WY z`}d6L`M4{X)iNr3mLc+cq?LEuGcj|@J5lZ_-!Dm}UY}Gc{*vQw+6?l4u=31{@TnHQ zr52COl4B@Ivg4*xUeIr|S@mUL@SPpSl-C<-h0&4qgIYIzp998%H8J>5WrBX2x!6hi zYoq+RVy;+2883sMPz>JThYmi%KM$#e!9y+!=8xwaHmN#s!T*ll4=;#M)t^7TzgrkT zso%^bb3^AP-_A=GP%WD~Ffk6@w|p5A5~Lq^YN)E@oI(S>&=i)94mLY^D43o zS5y+X;9pKn9Djd(9m{L<=4M9L23$7Ou-ORT6X3q`+*k~rk#(wnpHl^(KSayx4l}H9 zTrv4*GxF3U|4){otD06>YSx(V---Lxf*pJ*;18$y$bg^#9v- z9}eFB5ntKYxaym zvKsvmo3{*R;w!Ch`CKOCb%#iHoQHnDFI%ej=6eRKk^hio-X`sO6nseZ-y{gHt)EPp zAwhO%_I?ibXV$3a=QWM!=XP=tl#4F_|6RY1oDlZP7$57g} ztatKVnv{+u_h-lQ=AC(i8a4;2uGJH?Y*Rs|_z$*u6w{Huf35re9XT}_6F0qExALyi zB{`MUN6alnKzJbycFw$+`tjD*M**t$SKej2D5r)cNYY`XY8Rv~x4J=#DPAkXU?JW` znevyBH}9A_Gn^a5y`JAwAi0+scIe{sL+@A;2uV#}`K2?Gel@?RNHRG!uS~z)AnCx` z$1OEzxFb-`;50cbQ!+U%Y=e@9z>)JQLL-aMxsQEP^;5$p{&#%HWjmjJJ+CLzWsmZ% z(Pca(j{%>LbUB!jclVQ-)o<^qdd+enKa&eyh-+(=R~hLb=NF!G1j4scc*awWIX;`q zNlUZpSnnk9R3O5bfkX*WkMSLhf!0BefZ+~xIgP}=f`$<_NWmdTlkb~Y9$AMl3;z)* zXv|gk2s>oXAq?pcYvFMPX`mZ>e(?PA3~TUfCveouXn=fSu{HrrT?`95_+JIe8Q+8C9Cel4GH!z*-Q}LuSfb{%D|(ixhQ<*f%gQxf96;hUuC-8_ z_+P>s%*rj-_h@?O9nVXL?Al zIP@gQp_m%?QHlFLiOm}jVCCwe_~B%KDr92BylM7IZKX+;CuD0v#aL-Vg)(DP4s^T;YM_i(uJ(!t`o@zhrDY@Y8+-8P z2Yt3Dcon5DK6Y(G;EmEFuI5ZHx+nAFLRo^hvlc`#^+!VqfMG4{9zrQLalIyZYS= z_k)~$y7Mg791>+t!U_f|9~v10=*ehnyoM)Ec4WZ#P;f;~=a`%~&b6Kj0)I zn*>9YcJ;oTX7!@))%`#{V+aO<^4A})p*DZ<(=)TznHG9#<25pH{i5o4)>GI{h>G%Z z8w2Lp!9P2s5lzOE?Z1Vq;z~XtyFaM70>c@oWLt8<+LS_*7=yfdJf(vuJBe}6Y(E6z z135GM<{5(9fibE$;|0M1UYO${zry-FgVepJLP`=$PHr^Dw@<11R~Cfh`p_D94X@=O?> z7&5GKYr~ikj;R+5Fe3B;C05$fVBRuaDOKS?GK|y$w#9*zmYal zJWg=7uN|-6v^)xuk?L!Y%s5Ux{ithnnjW4IP@z$_8x;T(EZyldqwRbSs2hxLq`~86 zekH&t>I!4=02&@8*txUVt{xzZoBC#o;xM5(ezIBH^4CJDs|GjfmhGnXVZtSjuWkpk zn-0g1(0(LJ=X5pFTCP)G3cT>>)&d4$p!br++;&_yGH+0}7Mp&s!3X8^2jU}Wjxia? zz9Qao2J%P+z|^&-@#JLg8U|y|Qx~ zNG6`APxU1uo0`65yp++#SwF+&)Mf3mXWpc2ZJdpg__wp*^d0iv_J6TS+}%az59%|l z%=fu1p(XKHr#(2u7> zSBK?d#XobiPwC*SD!yUa2X^}Iv8Q9WuX~l(eABd{^c0>)PM?Jp2$F7Q+@#EV&Ib>- z&;DW;0Umq36=zY4X3_!=`*W+vQS3eACLmWkk)yLbTFomP`dKyyvwzDAxMQV$;YH+( zqy%mGx#+{(+?1}!oq3yatH7cHZf)+_+beguxVMOZjhjpvZyU9mMvK-pND@AZD=!%; zTKJ;%(Ak@mh~_W`E#mcm-(oJ#ynupu<<-7qe*P~xe(nAuz$d{vR#z9oq+H6yw5lA2d0 z`32s*cr1d3U%iLg`ule}%f{z_S>`KD*Vv|{hSgA8%Od)c!%4_di}^QkSbPzIQB`1C z0+8fRX+~}>iHJ-7vXu&`*!xtFr1CnB!|HvT4Mc~5&9Wh^=5^K0S0+smnc=KD#BAX4 z!JcMRCy4h5cxur`3id9o57w?Hb@(?7phPaS38aSUDDjN-2Gte*Oq<-T2vQ2HFEo-0 z@;BjAQlL0VP3^o=N~c!{dAiQMvYIzmvZ|+iZ{C|UkZkN(upwRY*Tna#&qIu+SIwp5 z3i)iNcDihKe_avt?6`!QgJ6B+^ca61D@D~&BAa(EC>{QVbgnqrPPt0WeM?fo8+g{= zVBLAmfNEWUm@ux0gicmgbhl27THoMV7V;$BQ34Ks+x?;sYyWh{ws92u+2KOdD&&Gs zd&J{4s*=`)BP#YrTh!yVtu+x=)Y{40Zt2zpb(mZcAAV-`zU&}u%6k!l<3LQH%eo(( zzG;t56PGuu|KjgyCi_fGNxXgn9LfyEP0GvUP0pY0+wi<4E3MNwIumDq!Ic6D^FIw` z{K$}s;MkE=Y98$uclg%1uB3S(a3wDjIWETvqL=Fk?sh&}1BQR(Sd0wk>1f|?f8<3g z4+7LXwWbLYDE8Y24%4EG7$xuvXTG zbL@P6dLenc`~rY{mepoAU0k*`B|L~aU(eCUDmcE=nsde$C3%gfTPjx1F$9cD?{4KF zZ++z0T;7W0Kum?60=?aYH`G_;j;0>X1pF0YrGNz~;DF_0!Gos9K<+-|X2aXQts^tX zxfFH(PaKY=Y@WdM`y7Y?))v7ETnIyb#}Tl~i>D1&0(YEPXJ|+NIkuuQWdhg_8r~-mY#p3CkA1TcYeHL%e`}5rQ3Zun6+3 zNPE)aX~Ia+_=&7()7ndQIne-0zfH1Yxe>E(e{f(uTvCYOSWWUbbF|amO=b3)jl1Ax zznRKdoBZxp8|3T*S#Qwxux;}vsO&G)`|d~Dh}dg9BRS~h=JO$y5 z7jbXJw|+I5${2YxK|gWeJ5e>=kC+c{n(3BNNmKbohvfxiLK07FueXD+8#A4|!IS*G zX>+}U>q5n3BwJIt*EhqN_uZS8rQRDK-mvm4_D68w`x==&b++kl+*@9x)K#8>TyDSS z#k#jDee7j6d$Q7~0Q=q|*%S`OD=l>2wvFpX?g0A7EBt8hes1gx>1DNJ>|wY4hQAhi z@ssJAD0N*F-0UB5pge}KA7PJ2r{8XN#VooCk$I&8ktsrl-TG3M*-3)aI*Q0qUc1s* zEcvcU^Ue~ey33n<*XR&0I1=8pNzfr6Y;VwIyaH^w=|h0#|2x%Pd$-Q=Z@^(e+qIN> zO~|ecRHBV|t%)9Itf!F$axMRjOziFf`9LpW6O;K4yD_gZlk%m3ZGT(`Y;%xP5L-!oZjKK(O0t7S z*v$N&yi~@RZ@b&n{9fm9bKu&l_IS574ScsgIn;CI1 z=S<)#SiL>_h@k2G zpd_2-oLlX-deYUJdGz`0+~CNAy>u@jW1*&l{wAP zZPJC=ArVozgpG4?#=wR9BBHnzH3P@r3n0=ZA&}gcUeNeXTaIxF>pk>ha-726Jxy)( zMbVK!X%|NzRweE#cz;Sn6uT|KRCgETGoWLu^o4>g?=B!*$poxx`ql#C;tuw^AwsZ_IGd#f_ql~8$DAOnlBV`cjSd+ytEh^m)v>9zD<#|k ze8k)H6dZM6Kcee#yh-i?YeS^Lef#$Px|m}`7r^a*N%#G&xHxe-(_Yv0E}bA{+2^Ph z6UUsBliH6_eHJIo+b2B_)!uCX5En&{pAD*$9u_@ztP{E!wfIA5cCWDHD(EObGHP=^ zKPbr6X>p1SHg$XtINcW!ouK9$1}%vqwv7I#C}?F~j2!K~kS`dtV)vd8%o=YS5RRon zvyWDrulgPBNVIHoPOR8+@${I26*fmAqr4Yvxq0S0aQv%X0PXFuvR&Q%cPYPv6S}+g z|C%2D3c6+g+sJk+o~}*e0%vSKl=|Frf{Tb+EDo;izNm4M+O4~LK>eQ)rfzng(!Y{2 z%?EKbUi;qQ`0j3wg%&Nx_fp{FcAWWuuH@nTBu?G_j@NOYeNczu(f-DDo}QO27Oa&Y z;;iWH)tQI4dY*&D4}082`}Q-h(I4h@yMbqi0G2^}beh1iBPNrF0BAh^q(Hh1uAc?K zf-1m|Vs_<}&g})VoE-ENuvBVTXcLnJ08qXnb{M1j!^nG6Iem6k>Zj2iXr`)6n8+|zfv z&-1?bo^x*vUHn|^Rlh|_IJbU(f7?(N=~iIitKDsnXLLOl zujZOd_9>O_ZzmMpQX;7iR$b)eYriw~mG{}*hGYVQ@eY3V7UCzi5CAuu!Ujw53T%V< z^IDOS|JteLNAYKovkpSFxg*F(E4%nCH-eRFL==LjjhgrBci1^Mk!l}VRb2uNKYwhZ zdg>6FS6%YkPDOhP8~C6^82Q~Rccjz};`z!N#y*z2b@wj*$}QhIzTx(<9j=*L=dRKs zr1L@l#4YHTT}GPBs!JG2&a4?-+dA}s8>`w2>E~`$-92+=7s_3BeBI#L(0d>y}1lnj|LDO?_c!j)u=#MD0scgI3h%yk% zs~Nd;jC@QxUIP??F+d zb1k<5W`8&ZIF}wDeU+5$m$q;{{cjsG4}f zFEf=XKM?xDGv`2$bE%gQd>70x3(i;PeC!MYyri=QOR1i(!l}oSNO6Xlkql2-~fHoC6zb;Y!tB)zc#c(i%5_Lv$k~ku};lg|x<-7#1ud z1`a%vpWsuWyVj6-+!fEj!(#Ygn35Q?Odbd|6pi4jiHk#8DQ~^z~9=oBFPZ<8gDr_r^$_o#QU*d*=AM zT;jSv^+k{#jD6EZs-0Omk6FL2S9|3MG`2YVr`Fk`zG(BhT`j=*p7F==Fw&8@9IXky z@VcIP%7~``-ug4Gn8s#gciukd8m8k;uqeJ#?a;-|Q0B3FUG=`8H>t$NJ7$_62^IlJ zHHcdUA4hBpa}b?-j~c{XD>Lm;6B#r5QWLPkP8&w}SQ1t3Q z_E-0#>eVIxNQTH$Z=`pzV2>TlRFgpm#}Tw*)~pi|P)#G6!#|}a6MR>~BoBdu)P?a2 zK#qZo(qN)l*%xm;0eZyV#~KqoPrP$j4y;h)ud>DlMT-E}@dX!eB!<6w}G&{!eOo5r-uD*gwT* zAR#jm*pE?i4j74T?+0cjam1H_7+a}_HHkt-Nd|CeAw6Of>U^S%i7Y_BdaedTo;c4^ zPf8f#OL1XZLfgF8uh1l9%c5FBhoHKXA|>fw;+qeNFn5=iG*NmmkWSxoHVdvF!~I?v zn|VS6I6MQb86P|HSu)HCq|xVfRmg7$dz_DzQ zrXN)zoW$#jb3A0Sq@5vRm_S56qED3`fo_nzjCY>VT%^nPb}k;&9E(K&`|uWwa|{;P zUM_c+b=n&}7*Zns=hbGt5Clx!@7(Q|SV)zf;C$$lxWFbf+9#<@wDL1JD@`NWjg2l_ zmk8YB$V)h*|B|z_F9#61LhH1%=vl?$d)l4$S-G&XE~Cn*eL34ow+N7Oe$Qjy_)c&_EKEgwMewD*S63 zfH|%X3z=AX&`eUif~z(8~qbVh>EpK>-h_C zNM1saE5nK8LT#rPkCkWcB7y4@=olpMI2HDsq=8yWUNcgR1C#>7#M6iF1F+n{V1AsU z=C!7iEYix&C~@4IKiM1tzEev-hS0#Ll85VTA~aw|v5~sxG8%QhL5?I18fzmW(5??w zgHg4Z$l7G{0N0#_?hj`JQMHm^C~Y-heIhNZA|44qrB8ZvwsAGUVsGYRi!GFf(xM1; zy~9A*JuEo@$-<(K4wmD`wOT#R#bWDZB&5Bd0m zUJ+TV3RUXWIP>)@b2hdis#?iL%Dese6>}ns4PF0@NUMX}P5j8|&o7?&*I(zEXlgzD zlHy0Meo7?ABMdD^cFdc-^IPkigKH(F!bL0(LvyY;Dup;d|BnyAGu zn4kFMcUi@yn8VqIJZeu9^ou|hF%ESq!W=_pwR9=*2ullR=bTQiAxEMY-y-Rx zTk5WLWm0u4M+VGK!q|B0-^38Dqy}4*u+psl`N^u;4~H{rTR6MsJD~K6wMITVyRU3U zBAvGZ%8z7HgUc{sCJ;Y1lx7gp`I15K0{I+*vE!-;)`4cznxU$7WiVV!g2PDsrn47Y zYf5~L<6G*KH@5v}!g^8ZrnSzUjLN1m24ZEBEP|-`1CzG|t(sqwD#^vd9F-DUTN5Vv zoI@KE_DCY1;0AK-mn4^_vn0`JvkPI59N{piK}PZyt^PAXJsvHoAxYVcA+7!?IlVTm zkveBDu8Ib2<`R6VaJV+&Jbp`KVpOw#N`bPVo_Zx%){{^gQVm#~d#m4$c=47KXW^v%6YEB+k6 zue0Qm)@dD6LVsqpdgLAcEVjF?Qsu5HKfXg&2{rUU{*I{kHugE>4zIT!Jd~v5w)=1*fC()?1q(5BW57?nG0Pw$v-z>`B9;z3J!;_o4AitM+~*6OEcyN75e$ zn@o^9*y<;0=kHB#DtqG*^x^ICQPWEvnpff~|2Kd971u&aG+QY|NvtQ9bniDA}w{n%1+(Aw9+MJ8hhG2AwZE7*BZi*~-<3qm=hYx2%)`+v{_-%fad^ z@-9j3Kdvw|`674OwUh$KGLO_YPauxx+NF^pCX4!xWqr3mlXQ3KsVkV%xwl!Ep(;^&tde`+YS*{9!JqM?6u3k@q3eWJBoBY+c_|Q7$-U{uB&cM4NW78pX~#Sj>Ppu%#Yy!OKZ2 ziw<=%mW{bTN|v%6sIEd3rgsTJR=Z|7LDF3hNQ3EcJp2)C$jm=KbrOq#%Nq5rV))!4%Cs^5>$K9xNxb5a?7s*6n(o_79y}TS z1&BF`vF>4KjDhdUZct%MIl@{%1qhkUSlP8NRF*a?Q7{)g?KmIK#w>GSyN8-wW`m@m zfSXTfYEgf-^lS}x2(Vj>n89I$|LSfU!XQ~XZxAtpfteNt!tDI1^!`-0 z5XeU{*oieFUQLo9oJ$>r&=`_%8-<5L@mefnITU9GnFTDz!7z_I7Gf~{=x)R-OR5N& z+btB3p*}3bK9ev-OrjV>un@<;QTSidm!2e^Etwxa0?zK#CHyIt$oI>2^O$ zP}F5f&nZg~Z|Jk=z*wz6WVwuMAL5jZGHWS1pP8JN>Ig0UM0Rr;G2)&s+ab;x(ds;4 zoNx4l*6RIkHJGk{J>TIJ@?XDQZchVCcs8FIBYaG~O@J6-cT?sn#hG5un>eV%YcdA> z6RL=ZwelPL-<$~@iO^yR&n-_795J9e zazAv^d}f1lc*8D55yR4%#|5z7WYo<@M{m68C#v9wiZn0v3m6D7XYX1vtP?L+ZWcU$ znHA^oM#&D=TcSIw_X8z{&lR8+WG0@_)BUX57Z-PB*k}LdL2DyQcg#k2kTa~Xki~j- zRp7Vah#6H_ksZ;1s_tj~9|dCg(zRrC9}n1BjDnTrL{ZmWi0Dz^V~AjLJJ*`GJ2!}&L9e^GR(N{_Pg@pvNd(npBZ1$ObY~*`E?_-N0J0O@?rZr=7#7V zfeYmi+dWX(5uOZ)t)R1GPdoaI0RWuFH*D-?51Q%+BMdc-KO}zCSyML$9V2fE9(6* zg*hf$`!h*Yk!qGriQV(QKP~gkE~}Z3jZ;v<6qf^9)gDP@;;KN`n`|Ro-1Vdw(^n3d z3@Y-8x|}I1O_lbAyN&i3(o9iI%8G+rGJ3h?OM-*e{%@g5g9drXnRJMBdA^Bh_^EK0 zR7E$dg*IZ( znX*=faaM3yV{Rz<@}P-=epbl+OZjgerqACZ{n8H+Ov=)68`|dv$;F61ws6F?Wu|4(iKw)!SRSTC zJsm533U6QeE)!n0JpT;}rkQ+7D9Pa|s#L9To=%pZT>Fost?o>U#_?%0I+=HpB8yqI zaxPQm(gw<3K2joch5Z&-MFkS=tXdRn{t%R@`gFGWP0iM*mPP0bj@%z>8RqqOSti#q zm>sKBfz8cr_+yZ0%cFNZx$*L~HllSE&;%Q(chyLhOxLKMc-0O_lt<4mrEXX2i&!({ zVfnJO;Y(`-@CVG2P?9{Sr^zgXaX*&ilEy%P3DJS4pqINz>~v!xH|gqq)g*-w-;jBG}-4IE&C#t^|QB8ND= z^+;Ljyx9p>Zw$7i*grR*C*(F2nbm6%YNNeJf8i|X0r8Sh2)07(NH-R~b`>>%seqf` z7_+y!PkY5>q}qT3i*o{X)``=L$N|brOL|q@xILq{-U?$6km)DX!K~ITvT{IdoECLbcWF$5#i7(u zd18}ew#lgwzi+Zykm^5I{$U90Y5aa$Rsn)lOD^ES5yXY3sn z3HOuSi}qW3|0HKL#iJtnGxhqe-iFLbGTbX3{{M9qmRu4gD=? z)DD)YVXQBHKj_#4-qw^Klz=83-D&JJVws;h-UD&kHS`}TdiZkAeU5yKu8fTQgeE=R z8BX%S+}Xij1~q@LR0(cIYa4wm5oxa3=WCxC5#v;`1{X|^06873O8|4YVFFRE`|iTb z2oF(7w{Y_^BFH@}$o(D>Gs%5Jink$qa!1n7^`CXZY0%+Zyx+_LP7&e5uR0SY(matp?Hp2c zl#zB^cVh=5()L;B(AXeINYPwRJa)!8oEtkgWpnULvBRf-V+X}JH}$22iO6xhp(%Ib zalE}5CMRz4LE-%D`3-w|lZJCa_@zFKf2Mh#d2Vxf zPG3t7BCgNlvHU~(nFqqY1=gHaH|8MLoE0(WP5kLSyr3rrf{C<)xjw^uPm*+rnNjj9U%FW$#b+%l_sE&0(s%n?U6x>;FRnL9=wYWHK5>`$l{Bm9azQeAcRTPd=pmx}Hu0TwW6>O}|3UL4tDLsh z%kUN{q2|?+wD!c^?c%k4(z;(;temhNQiRWj!59Cx==26T^eW9+d+kO4H7RkKCiCez zCq;NzE*dWQ_H@`s$fx@Cv=c!`+wF~)|C(-ZaIV$Z|4EpG-?i9-oocJYhE+MAe^H~I z8~H`!{+UK@)Tq*LP3z_1%NT3lyYYK)pBVO#n8HTKo@2@^`#(n_Sw&%f;g4ZpOL=r6 zO>^(Z;(JE&`PqALztzwz?i=6Ve9f!d-|iQW z%yJ#+q<6MSPp&V?S6R6&2EHT;VS6pop?zaMd8?Q_SM5gW#lQI!0ncF@sfGMiff$OH zhtk7^nPu2tH9G0t#bqVG5c>g=1u>QnkG#?&3(dmcBy>E@Dykcu@D9YAKa?FYnc-sh z23rOIvx?@|s=d?gTC=5~R^RWdjC}lEi5{cQkHK<<`Bc$DPvnQY=WEo`d2-DRe((q4 z@?D2o%`$!mfgT>CBc8y(DFKZL*Xh=5*^WP;hpi(Mt^vuq0bt@4*HQce!(1h0?X%FE zH7Due$k`glf1{v>)^E)&)N|)YDu2%qC;0xUVOEjdw?87ylQDtB7r=Bcy-Li&1{M_Z z?+2DIk!ZEHL%HBnUpr}^mKaD-Xdf$h1xe!!Y{ z`rNGA`dTpn%Ea&GID3~YBrj%?tED3bGg{N-M8}0c`@N_|8yap@kKlUQV@CTd&<~A@ zc;l)ZQv62^g+2NmVih#Le*}BZAbbdU(hj0lNA_bMSaB171MGUGzNqSb`fm!+|% z&?O1ulfUN=$pXE2Xpii;@i$7;>mzbx?1%*70o8Rr{v()mF?C>%%e8veU6v!(6fT%$ zF}oD@r`pTE8=AC-A~&kgC0S$lzs^5PKVt5LtluI#a^SHzTKf>Ax?z@xd7deK9*ri( zxd;yE@h1*6ZZDl4@&cevir5F436=?j-?AXGkT=6eA(wlvj+J=E0$Bp9_h-cdGC4!6 zzOND)XwNYHz7 z2HjENFc-!t#AS>rR7bM|A5 zQb1oB5)Oo}pEp(-e*X2OQ>w-1hqpZ0-^KMgE(R!Way=2&b3YJQ2jol6lV-GN;ve*a6bA_6u;ID2!dVL!3Rc zRphkO{eHB|Ek89-mgw@zzLt(RmS1+wwvIGV=I8SLp)rIT5^7x(I32#SO?XHT<3efltL-oFIe0p=avuVr+upiJ!sV?T|0Jd&5&am-;e82+!=_4>D6+F{1YlD|@cfmb27} zx+CsqM%>$087j1YPi*xckP7|!=!yzIEOk=*=!8i@KBLJaE;w;$wycjHDHU$P*Fsqs zJe=P^g?V~!Ovn}I=#)W;ad*YX_I>h9?Gnutym#VGT}-w1_;qN;1TU37QtIRr0Fc%j z^4j3OFwgAj9VI3O)(oj(rX}Q~kEs)!l)IL%AF#*lsXNG!V&kGcQx%!?kW%Y6cRU8~ zoJ*5#+-sHC?Pc>}MJL|z=I$?v%Sax=c`P#`r3jk0Us7UxT%S95kvLRs zc$<#Z$t^YVzq5CqsW!}0@0g{h>Yf*PQa7-4gI(6fnG|f9aJ7t%Oq>4qLJPBPSa4pi zH-c7{`|g6kL1Ie60V^AKLd;%?Red;e1%V^)_gKA_g_?gKDZI1u{Kq~e{!5=Dyus@_ z6`h2l!X_2soBNJH;EkC+DstY32T{v{S=kOx&Z^KOcPL?|uUi!loL=)-z0w8qJh;?Z zgQ<8tnP9RmVN_)~WraG|qUV!g+1b$AS;2rlizdr80n22iV0wolc$)%Wz{(s5!!mLU zeAR&FNLFgdhN3)Kp<8iU#jWT-DB>tZ$&9|O2;-p@-gY@cS?8yqXesS2Rc|` zD`9X_%Lq~nk4ICCY8{8DY{YJ@+yj9PShp$$br+tB%s<8$2jsab@h?b@1w8H`rEuQNN0 z37Wy=x1wBpe*qK_kO3-|=vF$Wp8<5xPw10xBy|{V2CZLh5N)HW5U$F;)yISE5 z=$hh`qY#q=<1r>)253n43O@)O(MEouHP{*IkT0>~3Whoiv}-}8M;`nq;Fyvea%mlV z3Ti3L_7Dpoy)V)>Qn*qJ0wc^~H;^lc4e>qEfRz~rk1U2=#jTDC4NarwYLZdGK}i+y z<^DG=Y`l3+pUc@Qjo%u8!0hIbl)}0e5|kjJmJyqWGfxUk%$CjDY!|#-hJCN#*EAyd zZ3$;(2N7jr{R%N06|0&?C&(8_^vI>HqTw3MPY=Jz!1WMXo8#0ma6Uhxk|WLl9cgE1 zan(J**xB{b2@XnMSVb=qeVH3&IC7%ME!uTUYSAY`n9ftfH33zrIO|* zbQf|}gg88ggtK-8ArXbtBE6nT?9U5}tqFymc0aR=Eu+8tuOI*5nNXOsjYcsnuvHBS zDu^z}`Hyi3cK(MXORZImV*UfVv2fnnLHZNCl2rbSF7t=f;_pr&)(sg0x*y3x=gbnU>!c*FC6`)<0~K;K`-k_C<5G zv3bzJr}t99BTbBL(WQR1P_zxbA$G>&AwD0yCpOCirfNs8{vU$dmqJsdPZYR(dyiOl zIIB+mdiPnZRg7*HR}gLDZ=+8A_5aX`4?h*R-R6PqD*X1T*^e9bKlJz3lA-0brJqpf ziqel@J^HHe?q~!y%op--f2rl`orfz3GGe+g};AA4~r>qfgRh?OJeTh zM0#|uSY$9)aT_u1;Kep4zm!z2gsOUM;?PKw*cgwkq`XnO?;d?xcB}0+7B;I;=$l3S zm+UD74zYy$W;w5V?%cGVE;u*a|3sX2SD?*rQ3wEI2biOOG1 z8gPu7Oz>t)pIZ19pgWX21Dd$O<5HKu{Z(71HFZ@;EMYpB0JXva-&rP}nCy`4v0wqT z0bQWhCj-)S0e?WssOr{OW7Y}NtdKQ6MX@movWcfsd|c%xDfm;+0z7kG0$BmFdDq{P za^^y;D5=^{nod!Y$8Loow?OZTG+MMm!|990wl3@>F#ae|x20xP;FM36b!(#0M!rO{ zJSAa~Rczq_M(T)Z!jj(p`&LkwZobCO;KNwxI&opLEHA;9UV*wgAHQ0!J|M!xhVN20 zOtyVoJL1S%ka9?Rs4x-*A|U!lQ}72M!n?@}c*6S|`$-Uo2(k!zVsU94ecf)9;#rfJ zzFu9hLPm16U(9mw0i^iO^+t>EGAmM{%Jq@NgBAP0UDV@6abz5n`!3#$SUJ?cnBvH^ zUi}@UT$5}}g_5IRY^GH_|mkK5x!RERu#Z&&D=?@>qq(zY6tGkod zfs9?y!_Py#+B8-42Ex)6#r}XXCjEk)wdJzf-*Z`Fp@&awSl07t^w!>M?rxxMYs4@U zetM#PTtW7Iy!skrBi_2hd2EV8@4OD?^jj{jq@WcPt8TEj^0D5TiqiH;p zzo8=GUc;E!wK<5?K?xZfm4lO!khSf@>f!Yo#`Zk!Nnk%!NRT@e87tKd7;Fwc)Z1(v zaOgVI+47D))#XBo!-I;qN(95vDX6!&_Y4t}qk;czg+=FuN?bj{ZC_5x>tpyFyijc3@R>-!kYsJu6b>Mm>L|fO95bh}N*JnZJP~62}fI zmN;b2VMsLVqDa{Yq^tNXZ+h`}lw`l*xn{{3vs)W zFJ7*g$(`_GuMa&)2(ZPiWc_9Ctc%a`t9t4 zWt0kYCm~R&4bzgCx1WQ!JxcdH-SX`sKODh2>e-n}20uuET-#Ls>$PNgq?2p>?pZT9- z?@3($r#H7ARnEmSa2b7`5&E1qLHWM~~BXeS+^DfNCz3$nE z9;E5%;iQ5sa1)dS{Fb*FC-aNFvX3HXu(&y_&Yvd^0%y2=%1CQ9l!{lS`r?Q8#L5CQ zwLIn*TV?Jdo-KVXNg0ItP6wQcbj~jl-iXd49X+k_ zo9SRTweg$v;Cm4k<5d~+AKp(;avX0(5CH){K_{}=98DTAW#?vyGYQLiy;Qa!%HONg zWw~kle^WxD_z#qXLz6t_Pv)gF7uEfg^B-^qD`hOAXV2mg<2tD#XP)=E&S#_U)tis! zX1uF=-#{5cELeE5xypRF>xyMAvXoh$ywG-K4vo%Gwj8&)=X*|6LhPJYeEDlVXP-)? z3rq-!`%S2BrL_5wY0G;QcyF=cJuh$A>V{V_kz5lJ0L9v^yG9-1_d@WiWhd()>G@SU z7n`Uf4+@oS?`y8!;p>f&9VMH?iLmvF2_-1LX+LDWJ|uibM{I00y)jSo#q0+m;jky6G!|!DyAptA=Cr9u# zrNFX`^RVLIm@+M1WuI&DULD6`lnyQ{J46PWVN47)S?cWF-voF35VT#7S_a^lJ3q&;Vk3tOCsBz z=PYd?p1@~h-#r?bYXh9fhmnrD0+v zDg~A2_XgJ!-CpBe78Sv>iSfRwd#H3lmF`FO7YG_w74Vtg3OfALtM1E9pJn5Fsy4Qd zjHx__Gw8%G7*SS;#dwMSWDS+jvZWzb2N*qv+{z%}$*JsX4 z7g%dNR6}fF7PbQ!SMWuU^fZWPcetOWMChz$XD}wZ8UQ8b?*AT^%K{&1%+Mc-GnZ$X}4k*T*y$2ML43Y&EdzM3;1mr{7b(?s^;{ z;iW8nA-N8Gf4KcFQ9FBBf96Y6nREo&5hMDXjxFl2r&PI-q=~Pm< z?0u~+-g@KC$b;#2*WcLTO8s$i!{FM31q=7n1-E!8tR4HQe@l(Tuz zJL+*dmSY=h*ITOA~DTUDn>ePc6+!>!sVAv9}aIB73(Bpu-*?f~jtQveU{iFDAQ7 z;Mn}AS3HIB0pByRY3U{*RmD_D5Scp^wORBJu}thoA!YV&1!l z`?qZyck8qk*(*YzdVe9}V51dvn>1$IkNmb$SEwfWe|ODgtSTJt!o!c9fju_0o{f5o zv^eVj26bvZr5+VMIlzd#qi&oQ39WueHg> zT$#&Q|5TjX8Dy=6yS#c=197xG7`UZkR^k3(6VfC7EJv}%D`LB~49j6dy#$smzVSM9 zdKL6@GfNiF>10$bwK@g=NA=&nEZ%yxL%Qdk*XHM;K#x~JQ@Fl&(`?=~adEd=UeKgX zf6BG8{7!%8EMSiTNaTV$8SYS)e6$xMsh*o}iMu6jLlg0H++=AV>L5yz-|r1~rXel~ z5x8B;vS6~vA^Q5MXG?`1Cmaq|{dZ%g{Ag}!m&4TDzi@Pw$G?mYUG!{-KfPD8!(mfX zn)N=t_m4X6MLK3i_EHkvqFygR67sQOIYIW2S3p}fG>nFq`iH)kArO7}AOE5(p1r)- z8#5&6!%frI4URW>lb4*sIB-ZT z?&vfoUp5=XW8J*uUx0LIS~*?fZHL-(Yeq9wih;4XhQGs9z^esob#hf(6&>;|ES6b) zYX9&yupD*q1Q;uqw^cDZ<@oqUTsRiL4^mHb=;C5j-wcKd##W!Yh3-L=WnZZ1eEO1V zp|Kgp?kir_58!arP8dD2O}y?hh!8i+1O96_Fce?)21Xbw`64c7X1VOr!9QF#~Yi5{=rKD8TUP*0%pz(o6< zo%dsLbANq-1D7ZRtldzCdY#|7uX;vM-ic$Ak_J+uM-3qqh?9vv-VRiFBG$V&z6^1Jj!gh&ru=b=Pm zUr=A$kkhC9-6scz;)#3j!0kYGLy0mhK&9o9AE=ooep3cOTI3`sREcTB^4dfI0+xXv#928OwQxlp+mkGF3#q*EZ?Eq=LqTf?nrNGYj*Fr$e zx}A0^V0dX{2>^+m+A0a^gv1(8O*}2Ki^EkYcGMho0zmRXE;dyX@%d)kik0|bKt$!o z)#{+cgsGs;M6<+hN1d3U6{bGvZ9i7^waT$w3y&d+W$cg9EQ?6@E!+=^lt zO?Hf8q_vKpbHIN$5x}~m_oo;i79rjs_ZCHES%b70lyK~bj{QDwSKLARmj%@e+R66q z(2_If{9=b;kDW8`d>&$Qc045vcx$7K$tQ<*qQJ31vuCdkRfN}`_nP&i+fzSfZ&_h1u~Ul58tKF*is*o36c$l=gKVG25nX{VIV z5j5dVufg#X@R%>Yk~*;m^Q4Nt?mH^4xc4Hu>|OD%Rv?yoNHf<;8s% zn(tI`lBdrc1Y&Cn)+gg6cRWq?4eK?$YILt;CLHsFWViYay#!P9Ob3*$i>b##E6+oL>Roan>AuKia-N@>D8phy(A4vz zXTWGj*?nfv)QclAb&`EAY)8#(z6>4e3i#-y10DFYA*m*xfe>ksbl(=Lj=03+_lbE^6f-Mv~J*kBdSH~87gFmZ2tUR`+!4+uziNpiYp;Gy{=Jk46N6*fD+jse`x5xq zI<&%V2O#R*ww|cCMQvOKOOto$9=XG``ixk;2f>R*#z#7^4dICNY)V_0-xT{mynl1=&PI4cL=#|;aelKtZ2?L6YDr}h z{{A~=qLQWT?9s)!`rCOw)PIQq^>Tq41%wpjAGtu!f_Ev~-bbAi+9f7Rr4iWxtx@0Y z4{0i81eGcCg3x5s3*Q&>#IcByi&$YBKQmX~?fc&X{|nPi49JjcozhM)>HPYK83}(` z(7iAOF8lp8O{cAT8-%8HU$)yUK7e!<5%I^d+Zi>h{GA$9t6Uj^_;+_+chm zr)|QKY5y5fDW2u$4j~ipm%WM@&6M8T);0v$Jnvt-hSNaxw`5sz@1B))R6AT)#tMgd zmd(g1ra?T~Z`2bZwV$?HeetY6!w7AFilO;MW@Kp8A|nzbvd2qlLQvzp@hK2i{i(%niz3c#azW{Pe?r3i_M?hpV-Py z9(p#$oVXCF>G;2F*jwE0y|jv{2h`)|zCi7|hiC8vw&6`x?qbe|o>D_f;}cSYEv{$k zWEuY?V}>?$xw#)I$!ha%zaOT(k{#MzJg)?lxj)4JqV-Rv-$_rWYOzgyw>|; zT4GiKakTF@Q@^Uk*u7BSR2v7V@BkW{Y?&zKSKo+b#Lq*1Aj`e$$U* zi~Q7gZHXf~7@@U~2occs{n%-dXi^|5Pa)+M_}2m$uVOL2M(My_W@L%0W9}4*cd+=! z;xe_KydeE0wX}%2i#J?2zM+0eO%!gN>XFGz86MXglPqXuJdXQ}^GE z=$R|^UL8Y$`L<;ION$V3J1thJPc5M}fYmwo91c6T8>!G;k>mDn*RE%OL>9Wfr81O} zT2Aze1hJLPeC$?FB(`DuR@aqmJY4<#XF)7mYsWXGmJg>X*B1K>L~lf(4sIn`_T=zsOOjTPc$I)S*Xfk)&0~ z)2&x$ndDzcuy>2J798@&e<2tF)7>qT_C=afCl@VizMHSVQHTi3nYXK89XJ>;-}cK1 zQ7|fTs93kCX;=7Scf}7(7q?WE7&z!Pch5F=VX5+H*oMNk_;~c(iK@SlK%LwA6(VG44Bs zh)3{9aN&NCt$JbT=F3=x*2O(ae=n5&_17sEE}9@ZDu0=&KTInGl{J+9ztyR*Q*I*T z_JYK~u`(}U<@eXo-}8eFt8n$x2z9%Ld%iCY_7pgFuPXsn;1{aG0|&|C*MBOmS3=Wx zSyt*AE=+*wMqg2)16#cI?!W$lp*mmfy&ClWQp*;_BbsVu73Az0AjP#jt)6e-0uxCCp97k4K}fl}OydyxXcDH=R@AOwOl`9GO?Z{CNQS!+JN zu-4gkpMAFd&Q7wpC-t;~tf+Av9Hbm7cD;W{zCNNZb<;VZ zPG4enmd+1Gb&2^M3E-iR@5P|B?HrF?B}=>;pV%<&Oc45wG`gO{dawOZEtoNvp_oR; zKx&UGmg?;InsJ4Z16Rr*qVAqic&bo(eZ;K&55@t)1ZU)lt9@m78v3|K%+7P!0l&5!P8uT$f@ zrob?Yr<>&^8)Ni|A1Mz6!cpn+Pf-QAV^{*3@$yQ%@?TflelOL8rM(#3U%;1p81$ic zYBs_FLnYp+j91N$uhHJ6=7Fl#Kn^4H__?6j<2qsMxJP%5UJ3EcXa`BN3V|v1;~L?_ z=HGWuFD7ZP4J~-_+pJ=I&55ftR`DG!4ruq^tZF!*Mude@^JFS^{oJ9Cggbt>RfmHkYm7M)0G&c8b2rO_A4x$ z?Z<%dcDJxSPd<#*+W}Sj$k&85+~Gpy>r&+{%Xre$i_ryxkVmi&*Ddi~-m`;qw6~o3 zLCTf09S%$Jn{n9eqD-30G*S|Ms8EUy6 zc^Ob2{~Zr4gL&|!H^5_@<6gC$^rlIxonyD|RtsNMvi#nTWk~uOLY9b{zk`Q-UqMra zvRxylqOMT+Y@QR!W){1SRzYE z7&+KVTe~yl5)FMn!%2?k*nFua4OQ0bf(b*|fYkI)5-*NE!<(>ZH522yr!YD7e$-Ua z9l(af-w?-+L}AybvVnrX$M==s**bSc=dz_?qohBIs3-ox0yB+oBu?M13bLr)lKObj ztO8Y$l3mxhs7AI+`aCq&)f>zpX~63#)xAKMx_CAlY$55c#l&gkgB;Qw*CBp)g$-3XY%0PG8UneqX<^sreqX`cw9fO4Eg>Dh z{G81;wD|FZ3bI`4FInQI+vj`Y9S7B`gVh|lL}`g%W-9B#!I-%^9eh1tcgR#@>47xqL~Tm}poQR2EW z&qM!ii!UH0LaiKkz5T{|I`O%oes>n}Y5XA!1m>>F#{I zFlABHqVLdB$&XqKmitCy`y;5(Q;vanV(5vM`h-mQ#rhK&_53WBOd4HLuB+82P`ZMw zXGEQx-%(^Rx^+J;4Pxui8(HFevDb(EoZIbEwgFG;E(V@gaz*P?vH&oGaD4Ys; zHbylYj<{0EUnX0i%jbEi8Z$l@^RnJY5jqWEH5 zWzolW0YUYmJp+^F79Mr+)_m0Y17zar`<@evN+WA_vqIbicJ?j`RkSC@8A|BF)c9Ek zg1Hik}c==vxQwA);1musn~H;1Y?eMBmLoHyY04##SkG+KmjOj+%_|l zVeJF!m3(G~MTm&9PtX+wecWTaJ&%xBWm^G_n3(bX$2rCG@Er>|BLsz0?D)sbJ88K% zR{xh3=qq0qjoX-U*yH7*`TNHVvEyb&0}payb~^2@kP&wha%OgW?kIT{4UO14QZt2`wg8SdZcRqfl_GeF;vmv{=*#0F0GIoxu|Www}f zYO9bJo_&rlKA#)fZ<(bK_`s32AHl2PRAmz?B=AAmV&BLDHRWlbLOyV2^VrVb?^c&Z zLw#;H26O){6X<(uD3?iHWybn_w{9OwHrW@z0R6xLvCDOuGkgAhs9PNPVJ@?eXLe#k z{M4Kl^1f41WrKVl1w9jW85?Pjg;Y*SO!*94Ny%kq*xgFAjMhzwiyIHMfgbAR8J z-}l+P5`T*bDyrMcw`#*1l}!>x$El?#&=B_W`x2PmmoNJIaTiUQu@{&uvD zwiev6i#Gj@YKxXO<+B|BLtRCyw@-7+csflD8D1sbpdz3Fqb~a8aEW6|PX$&|COR#~ zXaRLL$Ke8JlGY;7ypogsP$F^KPMyJ3_l?Dn=(}YGIozC>iLndb>1-@DtaL4g0RDI_IO|B}t&0`vS`>$i{sA{5?tZZJ(_pp#$p^26rCXOBZuepL38;^1{ z>Qi^7$UEcBm&s8==GMcmL{ebgCxWPB{LIW(YF2+?j}RHQyqFVD zMmEWX67y%-ZU3=?+QF9wOc;HFkxN?3jR8=wA)e6k714HQxv{$G1=S5Fx30#Q;#WIhUzF1yi0%-TH8Aw|{~D(?uAh39wZw1f`I6~L?ezL1 z4fSzj%vn6Ef5?T>W(2JDvsN1nt*3?%j}7hJdjR{7crAD5lH%|U_q+-`Ka29c@y$e!tz4?4V|*5|NBRpUI$IZP=%2*67!E>=k&TBe zQ83UGH1T@Q3mLkRb2=ZBR7s~NQ^f0ORwDDvrgV}vmO}%$(v4qKYNye-v`er33~vH9 z+*s6dMaEZfJ5q`QMaNfhYld_?>H4g&JBJcWVhjijku}|z9lFf^Ov;O<14Yt>Yd3A= z5#*i2>tu9I!LxL?uJQ=I{g`YgNoLETdEAa#+1R>iB<`(cu!H3g?6U0wvUnC-$UMLzk281p4eGu8TFx>MveZ-=swPe{iKMhnQ z%qk%;v@X=J&equ8csaMg)|54^OsU~0tudG%sp0l3vS~9tMr3G%y`8J1ij2~2`hfkc zT2<2>Lpy3!8QpiR$tyqwC)ph$r+?U`IFzfFz7IG~esEg?C3VzChHs(*b`-NNRAS*> z8rGv4SL&LuBuP-ges%|k?~1}7AgVr6UnB#|&1Y%*?O=YX2C7CCGMHa6>irmqY8aw# zRON;L7#+n6p?6reBw19D0PcAt`8trBL2HgQBmo$s3CgW+0drA;yz;kVbed#cx$Kq!=_zS zM`B@M)7U?tPzJUr24d(ej$CZ!vN-c*dPdXWRO&2tJ2=X;uITJD)EOs)Ub?lvZWPlA z#b{d-YHa>EsR)VRn71H|aZE^}5Afap#G-7mB**b#hjl=`$ulLsH?Aq-EOi#Yd8uHf zsb15EWd3>VhY@a|TeBj4)3|c1(K0wRzRG+(!4znqFwc;}T!jCWx)&q0Dk`k&dYRHH6k~dks#wo8x~dTmH(C18*;(A@mFLa;$n_P%KQn^pHz9XQPe_aP%x-69tbW zM_vq~)LEc_Nt7c?Tw}4%?_}{Y_X@56?{AvO9mj8)#TS9O4jR6=xdkz89gGXl=c!|;KCBB^gcj19go81)R6zJBjpDbcP!SlGY?PA>>= z>k#>eNVfZ2ygYrQ?2HvyXyJIOSbZ!|V{C-nBr-)Q}rDlGAteij2iGinTxyc6y(EFPir zZRhilR4nmrr>qxIwD8>nU&Y6l7-&qup0#hC8I3PuNa+a|IIEyZ(jECMKtrbAH+nc|&F{=R^}#wgm-q&6ip}}T`D0&6@U;(% zRtyNjxp{KnRNpdMp3R}Th)Q>=kl%BoV{qOu@cmRjx>OURgzU4XYj*q7i=knhD_l z@pqjvR?Nsw5Wv3Zbx@`b2f#lML)$ett2^c;LLUYSZCtvC{U)@v2@@UJF~JcMvLMM> zBgOF$l1Brs-pL|@fgVcy+^ZA->m-_hk}gJTpPp!&m4^XAN=%uUagvVbhWTB98KPxq zx)kBaDF6&T!2DPqLUU`ag!yfz)NQDC7oPQwOBe|uX7PcN0ChF*9s2O@6~=mnd^^ZP zY)ZV3&}M`eAerB_BqBcmu%3HpUA;j@fV#~|3cD2BL^*a8{P$9qG5X|RYPjV8& zAk-Ah@$5L9VLO9C=v`hKVu|%x1dQaYUY>}c&o%rOu^Ni=si{}mJKcfcVwWVasrQb+ zXOF~vBzA0B8CVNp$dRzVWN>Th&DQPu;8iU5<=4?FT%x?`wZHe^8i@nf_>?zFJpBd1 z$Y+U7cf26}Cx{%<&tnXxZSqH6gN9iAE9TaJCz@GjB_K}-`uyNYa%)v$>sAszeqZPh zR`n!CQgN)mG6e7<4e}6(9f#eeHGsqjfrN44>ruoGY;_Ii#u5+umqio@SuG#vb+OdV ztP{ny#m=Un|Ca|z}$K5l#fB^LR+V6fWeArJa%MfukX*Ck!bd*X_Z);k^G zHG$&0^8B&C?2S*-a5H%MHHwdsg)i<2Y1+!Yijm8%YupmcubuM*nPGvPIqfTV;XXZvG{hmJ8a^M3?N|g!EwqX^%+_ zyBl}qW%~g;=JT?z#q^1XeF+DYiH3pSB?rtbv^8~nHGQIGfiGf%9JMtKeKk?E$$C1u zNx2x+He|McqAZY*{uFoXrQ3~Du-h-V#W3#HwAdKYu%T}5Eku;l2BQLm4}@Xp-v+ByElWnBCV39keym zHq?lQcU{{sSqR6z2zI-o@3hn&`@A7aJWS(ulOYRa-SWNh#oa1*SEozvHDU~EmIYeG zGGROyNNRl?)%xh{Dv}H@=f{{WlC&|i(jH6RkjLF}WT@<+uc7) z7D(!HlP3%8{C%>IyLHooG>(682terMmKcB39`ggPIB1W7TyAPGR`hZS)o{1yF(T=S zylgZNRFR50`ILD8O`Cj*wFSKJ#@%{Chp58zWzoYdFltK<}IIWgHN&U7=_em6HRN{sKMioSVKa!WN<$8m`DG`mCaLpePfR-em&Czt5oPBj+G7y+n}2NQ zOZvYnq(9%xb=Dp;b8G9%%kEsi@WoVDXH-eEkpWCM*Jgw+`IKR++0s#a%sT}>xq*qF zE*A2N6YfVJgweWxoBJnlATJxi15J?*b_?t}F~PXi_N{!HeA+a8T8^0s+tW-q26IgH zoMYx%mI~UjoR=M>oeR%)(jJrHhPp#AQzyd(J=GND3=fdxx6meOK(@_c{F2A=V2r)> zHFb%R1zwC3ntsw251f;p$)iWQ99GdJ$8I$QebnYN^*J=7P2ShZr832EHNI=^M8i_1 zzNYjdoajffQo(K~`Be9jvdupCY8V%2%UFa6XBs22R+KKe6|#Y`qZZ^hkEzsxs(l$} z?ISz-&X3ySP<{?HzjE)91@LY>ZwZhLH((UvM5}LI&e+f;tHG{k<}i9M%}zvu-JrU; z24Td*FIKAjOJ$qkF8(pH%@?CQAk28<*iHxSqa?Aw`8;F)+Z31}XPUUrj3VyVI(gOA zyI{9SSXswM?IS(0z~#IpZ2F*2+DDz;!4#eG>OfrKDa8l*02!XUKAsb}DZW%bv(y+XxJmeBmIM{6$zsY|f z&v@NqzX5kkNo>Vg+c|k-j%YZB3%bw$ic@s#L>vRq^;{Kga@OXBY_OXieH?AFnv8!T zG|$-M_IE4MaKGQm1;u>0Z*+$}-nC9gu+W}L0&faN^naMm+-38*BWsoOvt(VOe; zEG>%TA6IL`J~p4%7};i?~fGauVCVCkbVJcZ9Y#g zM6GP|&e2MOZ1d(?#}x5!(Gj9`FmDO&>YtBM&5-vDb~DS9?6<-o=DmxJyM?DJqG`)*N2!2-OXso z#Q17D$GrbLc4OuIHHIk%lL(VoJ3`>SGZ1fD}kG;F26cR@7V9FF&arcfb>APlr4)o2+^ zACyt~?Qsvb0X@*AR|7Bh2bG!kT7pfsw|9P-|810PnB4DAK^Jk zgS!^PJFxC?R{{raEd44Y+!gG+x>jfgzZvSr=5FGr5_bzc?io)Y{EC2~H+lGiWa6mO zI`4lG`T*~^+Ol0fr;+n-ZhbF7x*-+YB)9F_4uZt_OXxSZrh%rKr%1P!xU_*QUtid~RJlyY)XZclAw9Z+U@pV<DY3n;hzrb8n`$dxS{98}klhw|2b4%0QGE9fJz+479MaCE=#dNq1rCT|x)~v+8 zH4o;3PN?}-nhu|$baSiHkPq9F9SpzeLNQzVyB&JA<+>hvk|HFGWC~i~85=B!_`&fh z3~Jg>>)(`q2L>JEK;f0p022N=8Jxc6n z9?}mE)ux&X*t*Hs)Abx$xun>0^PFF~tebwfH{5<|Q|2%|gBhX+eFvSF=+tk2Ed|Tw z$=$uLx;Gdrgv`fkckl;k|jJbgyx1Byx+XTvS znGU%<{O4nF0n690Ey}3=Pc`pf{%3OkmF`Z)`sBV#g7-fQ{+s((q;6!sDx3JAO7`~m zWXd+CRrk1*`5qs%T6V%iHZ;X@W#VMItN7-tVr7ml$k93Vh>L)`5yQx7kqpH7-aAaWps*Ra15GO9Dvu)`ckdE52Tyo8LR6?*i1OzT}| z3nI!7UcwMm!qMm**wS*^=be5Ec)u8>CGLR07{2}f0X-(Qcae0oC?TVXqs4ug!xS{) zedp_BLtod38OXrVo^^UVa1d}W;0FsDSwS<^q5lh89&|6TkC9$sq<6Oi|BIDrxl3E| zftxg~1TKDgD@N;AjQsg0S*zdB_U+a*)|vR3y{G8r4B^a-|GEF!%awNZ<;ouJAGy6Y zRT~En2ITihJPTVQ5gn<8cXwz0*BLi^4!>iuG7cQ>jojV8*w?TQ0If>3S7L9|in`s*@uaFOt6DmX zc6RuIJk1MS(;}yww!eAbfjWH&ZM;vNO%*Q?(x+zW(4fn?s^x2^zuE1PpgY*z(S+n4 z)bk~1yY=Z@b@O)C;8>K#h)2D%pu_aFzsCSN< z;AYx{ZnXdfT%8)YcVhd&e25|Y3`^EtZgT>i?E~eCW#=-rNy>HpRn32- z+B(8EVytn*;kB`?qk#%k_je}k3;rCnO&uRDH~H!k{Vt9e*u!cElshk>u!MF=+5zQ4 zZk}0SJDM5?=05Ud_Zy^t%d=dqwZ-K9_{3g)tcT0=E@Ac!Z zw?Mb6m~J5K0SrIR${%PaT7 z(+LAbkf@EQa#b9dTI`PM#e+psD`!i04>v1wCj!hB=#xExfH3nb=6^0EC7Jn^>>NF; z+%VM9+`~%2$^v9*#muj6jiN-*}(WY1|{lpKuDdL3M)#? zh~wMS=$RAE%b6qsx!R=)_r6lkV5)aG%red5%0}kiA{_%9ELE2A<#gugWsi~Nr#<|d zJJ0DAOoF?=aI}}X=#YOMS?CZpZgO3&JDodezzq>}uPK^XS*1@_(sfqQVYryqZa*o- z5kIX8Tq+M7m-+VVx=3Yt3!oM`tXI5VuT!U!#Ic+s1h~B=d(W?sEzHjuu6|P9J3tal zI`jsO>r3k~_Fq90`M(2AfLTC9RP5FN#85==)qe)@&?62R8`s2);-_Mh@Z_* z7U$J?jIa8JQ+scdI`Jt%818dA#~8`mqX3XnVZWoD+k(5~#(n$!EgF{Q2cyprdd{5} zxL!XvSD~zXFL>csFvL&RoLxkJ_@=f;b=bJ=Kv!LL{jD`0&?@sA#af8w+PfNP) zg)}B|3+-P6p(ULBB|j(&*c)T`rXEiBMcKBLQSS#QLwSvQnMx?)6LG%sK0Bbq+b*}fao@vIuFDA~KYe4CRL4)^bRBW^G`6<-%o z-2Cttxl;M|AIp-4BnluLeb%>Sjhg?g3 zA|zQ>^zeWEP$Ki`(N*%txngJUhVRZCw3AL^f#gSXYU3+fjLfvA${qz~lTx?UbD(|C z!!YMQC<5nB{8(*QF0IJT@bLU|BOb6^>W*Ze`i{ndVWo&cuLnU)cNcOucNZS4&r2oeZOBUqg2z-fuT{Xa z#1QCM%^5Q(jg;frv68+-o9eY9k{RM#kQliZdQjGjw$oxG{97dN-ay`qvp_h|I=|Q} ze=vUhVIhArU>B$-B_SAWe<$dE+I^}oHBlauAUfN3Y4TsVRp?b-myGYF~X&-g_y_=9%>xEJvO$F`x-kv_v(6aJhn?ePt!7(NZ?Nu2o8k`{TXGA=?l^?uBIyC}m*;K#kCf7n#$ z>#ym>>`zq|d|Vpjie)^TzAjh4kPQ#=F1g#=ynEpU`^Hiy=RVDduQof1T|shG*6f#Wu8ulbJfG9K z8ST2^NY6G)d=uGQ3c!aSb#bhaJH|Hzn67&*{~g_R2R1*Hav85g z`}vqjesez^tU6iv)!k774MD9yR^W%XOF3#{0KK{gcqT8VSHTh~ZtWW0Elt{VtD2Fm zmA$7}*BKhch+}2D(gJ9ng!?wPX+z~EiREVFYv+z&IGn!Z)6(fPV`bBGsVkCAI;^?Z zF#9En*Y|cLfidS!_@x{*l`elXFB z+4YNXeZgPIztN%e@LHf^cxIPq4q;2Ug1_h{@nqm^bH>On_|c9*SK9d5!K3Gu zp~Zw$=b3m3c1Ti|!^L&!v&A>sKH?V5r%L{+LqhvP7 z!b+1E}R3lFj9v z$Y7?p6d(sQbKYV)Rc(+P?+4FpW9*yN;y)=w{mGv3cyRpO8ew`UKyXrlqFoKRQTj!5 zJ<9a=c}s%m_=6+#`la)nUOmA+Y0FXGz=ODl1)s4$+dSZZ(1GLg8mVTub6b;T=hhg6 zbH&;mzUv$K`DG~~n#$OF!a>0 zk@iT+gHD-(zvwPB8`Tv%EH>mUFrKrX3rY+^((8Q_*VY*0N(KRQb$xX`di!?A%T>hm&4*3pK3p5%Lenk^ErUT1^68=bt?v^1XBbP;+?2jxQwkHbWe6e9%O-0u!umw znJD35{mu4>!N#xcwyX8Be1jF=tyF_l`j*4hoSg`-L&u$LtY5~2>w8@aOb3m22ZEkX zO;wppoy#vM(|<(Y5SflZEpzFft+|lP%$6qIPz(4PF z;N^wanzY>C(bF;9@f!-~xx~4V^Af$OvH{ih9`K%vT!YU2&Pn{zZod%MvL0~y#kE6Y zYc?DsUd+Fe=+@2t^_vF1)W0kl4o6B#N6sPrO}nJp;!d@1?!Z%iY@mYEdN`T;dhg%t5-Q z&asRA8A@B8D_36Tqs~pnb?wB&gj;n6c(Tyv^&cL7znYLtwKZ`3BxEBjCNbcmznDEk zqgkO+{yl~8k7AC&d+02e+gJInmRCN-=GomlY<{ zhG$Y=zOAo<$51uzzg(?MU0rnu38P za`*|rt_MuZae+%pW@eay+A6-Cnf`)T7Nl2p`|?NOrq0hw<8kX6uVy#qc4R59TaiqA zA{hvsI;1<(AaM_V*smWH{{DUJL0M4UG3kEKdflRgKbG%Y3>L_#BBv=;zpRB)rQxX0 zs0pO=;^NHfk@>KYqkRK+JDEeKcf;s|ukpgKvOc6luv%-B)%;k7s~DzuHcayCh>n{& zNN{@kiZ%p7QooZNe&Bx7(HfGfn}zDV@*w^u*y@}&1G7tMOuLwX*(o=rVqt-Y_tAqvAeHPhbiQ~w2=o#LVGs9{?1cMu%w6zRn#b$aqwAe+sWT6 z)~CrDY50j)utqFsEE>IQ$l1G+l1=xSgTjANmTfjrzmfn$6aUw|$KD2r<>(24zI4bs_OBg##`+jydk8<0S+P>56 zY?_c+b#gX|6dLTU7Z({4u0RQ;aq@alq;0nhzo{b1)&ZZrvnsOAEB8QXIw9UItb9)0 z$TivX8s*Cw(d6GMFRxFuOK>qPEqP%3zU<|A@p%2@N^b2W;9FXqeCkcZWw6@jQ|t_42x(IuhPW#@OGl@5NhgPx;$t83}vipGoRTEWrG zmekqC@b|r_YeR+QVW5+@2v%!PPh7Pp%N_wlts_Bl&n!F(gby&2(?dvQr=$8 zyHoLI=!#hB&nMRgYWChnYp%kbYJZvCj()KXYZN}kOYc|6xQ|PV;-ItXG7u9h$#{FX zTKSSultMQ=7;ZUnx}*2Z>sYn^N#`pkNyyRn020&JMMlBFen0g0-Hv;*ry9cP{RMf4 z19b0Z28Hpp?v|Puj4_*XWie!9fxKNb8+=*tysb`t*@0IINph1Ep@o;eGM*TNyoevc za0iN{O!~IZd?T>kRmO#s`;cHNur<}R_=Tmt%mxy#Z(T7eHR`OGpEfpBVxxeaJ|?6P zyhIev-S_jOw!&!TYf1J3rICnp>bq}IX9~s$5m7pm$UH_9CX&z%iRWRq2eYjs+G+1O z>2cRw9EA%6ILZHT>%W{Fs1XhGr^q_1eEt0U0QM&hG0Io{OO!)84obvklJq$Sd?H?E z`O1p!U5_tg>b(H$sQACs0&?G40rcNCo>H7VNDjX#*6Q=u0X#MP`{@z6NhHApb($ap zc;uMySKSNCtDe9@;|u1}to(G_NOqzkp6m#`(Rdc(3&q#r@#Y~Ru!sy3md9#5k?Abe zc~QJE!ybyvjB#(@>7SpU>w8EDWO_wVbhvnLg})S4?7a)&XLL~drU4-hB*={D^%1r&`=Af?+Kr`7G2gn)9J1^P$(L4W)Vs_L-gMk7eWq z2~Bi*cSPbJPTBDwf=&D@dl-Hgi``uD2N=r} z>OIV-xH};xwI0>UuWbc`cm*&*((MsUw;NDaz!1h19jQ$v@+>;#Z+rxLjVmnrQQohqz8|3qb@+6Vnl2xqis+!(od|R5 z=r~Y=>JN%wpX+}l6r%F!Of){-aqR7tK}TymBqq*hic1e z`J10wt;LdXuKS#luPMqVBA$qaHZ?HAoT%#6SHaRQ=`U$6Bw09e=UD+NQM=z?Flz0{ z>J4hh9ywY|IuH9>w~eob(eL>RZOU8~sN6c`W{$;O7QmZc;2OSH1>IkLj<^3sGORtgRSRf?F`(i`sXO z?Zc9>Gy;tWuAG0q+#Q~Zd1Jw;=3RmmJNl?=V7yx^byjxu*!H-zT)DdC$t12|f;LU5 zcJ@hh4$1p(rQ(k{BtE#L81)QG>^4JdO3TVsN-1r%K6>_5RexjoAr`IvNY~-DVSFkP zMU*z!;wOh(?3Tw%{H-V#Hjgb03;k(|pHb`8hKeZ{=RZL>f}$;$&1Sjve>`sKz8_E( zEgeJmszLU)tV^P-8Z8x4GW@4wzrFgo#}LK~NjfC{;Yj$r3~&0QCR+#DTf-Bu@f;VJ zfphfNuxbl6!~1XqSkVcKmkCBW6Cf~y)3~Mi{x76H`SGmzIE81gY zA`h-ps$7Me80<3-IK{K=L$f(0>IB6Sc_72%D(o^N1K(U3gjQGqfKaJl!wd@HqTM4Q zzu!7C4HPVW_&N-Ik9h`Tdb|>$GMBywJn$jY0|(@JiC-^;qrYiCGg;f+`+m4rjrqi? z(ZUT_vU``iHnx(ZWPjY9LHIMLZ{+P{tfhjQij<-tk07@Msk^JvsNU^!iCuyjDps5Q z!9d`8){=>w{TTUArDAu%d9p>3I=GU)(s_!BfS)O=W=YX?kfUgz_Q-Ca{o7_5GebkH z(%Uzl&V7P4-_ZoE!G(JVLzTM8zdK~Udk#$JazlM&=0Oy093ra|3a75fJC*&ql4TeB zRVLvIyMPTICmV;&5ENGqQ-}O_o*!{Tw|!H$vJ&|tSN(hQ+Bu9K+rJyfb7n-}t*S$O zcgclQfw1qn;DNH5)H?4y6c@AK8-we`i;T!0TyqS&j5C1ZIs>-soblIY_~FjVR7_iI z1Hh?P^D4n!L*VcGZ$xp?t_-tR(?$?5$B6_j`JTO)p54GNS3rBe`Q;ofkD8AKZz_zUywFdaS^&u?o$5wAeyZ`Da#apX|3cNJhturZdK5Y^*)E ze6QMBrGhMMv~u->v}q=C%CC}>VHwBOr-4YbZ?Lx=ziVfe$Gr814hIvF63bh95^E7} z3fZCB&2J1?evW0m-##GMB&D-QUz7u~ey2-qXr-z3&U;3yr`+1VIdm2n@K7`M zjH>kbHj*WGl{5Nw-?~_4050c^H2-OP7#%mJD&`PA`3`T))q1&cJREePm767V*A(ky zR~~56uIhBp6rbB|^Iw-GME~!WC71;T0sq@tj;wv(DCAT z;h&`(TH1NevFA-y2Vq=qKL+32lfx$G?n*fzuRC3yyZ4G^Rc&NAXBlY5}sPtAbIzlpCUaD)`(po~tg2 zs{HILBVY3fy>@eUqz|dg@93HQ6l~v^-51w%uhGURiU<|j^OTY-*pzCrGzVSD#w6Ve z8ZR~c%K8a@d9)$rei?+;_OeKow8DR(61$3z@cYJM%?)8vxCE*GSD3{9159#u9`4#! zZt@@}7m%}+vj?*nGrv5@5#*-lVs3$1{Zg>}+g3n1xG2?)qJX(?$r|DRv?T@T0}W5weyto?c37L18)Fcly?eNy$YM8?0`@1(R$5dA&~-U*CEbPFb!UV{Fr5eE#G3 zKY0EZ+qifbp#T}iG4{hpIM~?OIR6?LMo9Kl;1Rj3ISz|p{+gV;4z6p>v*%wDx+jiL z?iU^qVPi5O!zOzm^B}6rlk?xR`EUB)2>dq!|GyC6SRq(hkS07E{OL-=f<=e~Y@chU z;8JY@+he$i_y^tnXx~zs#c!Vr{dm>MGJ0UAZ5}9}^>;F_=O$dVrbssQ^0hyAK-mq? zo}R|Y7=FkjY=!?{k^LX-;G@4bHO76tb$h7QM$umHPqm*?HL0|F{Dg@BVt@aD;41am zZyUm4|7X;8!$*}xm(QqyCZlRd&R1@&nQ5EaAGf@P`3Lj5hmY*d5@eGQI3On`oU6@u zAhP7Xl0Lkil7-Hmjv{GhX$t9?JBt6o-dl#XwQc*uVbc~WKyil_in}{hkW#D=+}&M5 z@U}>CO0f{E1sWhoa0}AnP9Z>Wm*O5A{`B0l_rC9Q@45HGd!FZgKm6y1mAO_{)*N$W z&LO`!=2*w9IU}lFxc4^R{jYTWn{N0Q-P_07E5IQ35Cv-C1fgnGL7@Rk>{3d%q|wf9 zuBmC>09wwS912y$Jlx3Z$W~VcKH{=~Q)&ykbtw6cE-od1?b~MM%q{#8fnnNU3N>cp zJMy~}j3bIT54e5|C{+heD^WEVTWnuk^3eFJp>_~2TibH_cLthrZvZhzWDEYl2} zc%%QpA{@m;M`6?&3DDRieyPNuaOW1ui;%rY(#jRV)QD7{utgNI*SP%9W=HpH=4etV ze0Yhh>#xn(agXG<-r*h`)Z{IrE}AkR=|g`qXJ))M^3?e?%A9`*aeL(ibzJpM0t#9C z$eV>^i)}W3EP?u0Pzka9ZrJ4@CU#PCG18&8Tsh#vOTNsVvrk>-Of`N#__@Rdb=H($ zWxH{X9HC5HL>2M!lJv2&g;Tarbame;BdN>P>rwgy++}Zy#u29yGG=E>9f$B6KoV$X zFcJzgEU`;m6;m0nftvE^imyS_<)FoV7tl_2b8~sz+m@vavI~_Rd6vSVpU~#(2T@aqSt4v8T|C{JX4TW4CtK*ZrKGsw_ z9HfTIDEm=K`@Gju?MYgNg zB|x=p2;<0xJNC7!^A#yeXa3o=I)tWlBftfOJoM(j3Te!7_as!%D*AIECzflNC@3HaML&O6MjT^)4faM{-SN3&G)ePzJ1h zTD9UGbfEL9Nok5uV1J*~MY79~7P5K+%8{KvYzJpijNq3x1pu(U6Sgvg(ig-x*?agP z4I)&w8_7<$COOSMA|_+guWBx>E@kUkAHMG2yizlmDY=~KYc^y3?=Lf(y0>EL?X&Ci zOPHYr8O$toJZ3p3x+5#j^CvOP3Y_9u zf1a@X+4n?iod51}0F?)paDX^>)-I-K{gLDj8g z^jZeuUB;u%6Rk9@6uXkwi!#_IIw3lhZIn~xNH#zj>=K=jN)z77q1YAJ(b$D-&egVR z)3>0gBid!9_p@!A8#<@WFC_6AB~=72ZU98P^Beu+aTn#%Dh)IO=}5*OPTD*o9pf!# zOT_^hla*sZ)yv^)se20SMD&V$kk&~xq(gJu3B)PB`=0 zgQDx~)sp3yL&EM#baluO#bO>1X|=;tp;v#Xkbp{;B9QZpIqYC2LYK1SvhOyt#( zbPf)3gQJ2HIMP)2G13LY!XL4g>T;L|W*on5WT*KlByLJhJ8F@ft-O&vnD3c&(Gl$3 zM^+A=57LIVIec8+&?!KeSkbO)^6Eo9vu&O{#KuJ45@MH~dOOWuhsRi3o$h$EC8?*m zu&~yEGKC6BwIbkxm#?7(Hvkcm)HX|1iNYCcqY9sZXpl!(Rn))^kaSdpfjy4^F^?kd zF7mctL#M?vnY)qkR@*m5HahpieKzYffy&^OqdFtu?NK8yIYA(?fIvUt1Cs_RM8|g6 z&qY2+6k2=$y2@M491v3&Fra$b$vGGhB8%p5tb$}TPDly?Rc5k#j$^?&gerk{$&xON z7OS2yU%!JlQL8|{Kto23#uO*(L2Dg`Ssck;m+8#c+0iP*nx|Ex6`k{?bzx);!ZHLl z#2-rp;WLvA^)~J7G^PGD!ky$X2-1qwr1M?5G$WJyTHRxIFlGk0Q05w^vVupFetw%Z zG12nL4ARelEpW`?GfLBnBJwmzKNSjVAJ7X&@7pM%jGY8`8A3;A<+bEJO6z7H)O$fo ztI!H83qv&af#POwQnEd8Ki&PwU!K(jkqOKh4&&fpyI2oC*^pwry9*51!}Lw!#~+!w zlg#-`RR{;?22fbIczx>xOuW4vp$vOL+d0X8GrOM)XrCDqTvfUc`ZXSCF%;*97WQ3Z z7Oz&D1@nDRt%LmB0uN1~no=1h1^HNT0G1C3Smv1aRqyC&Qbb>=s6nF|aZIaT9jw71 z#SZ3q!mNvA8?RRRS#`+ry%cfmCum)+|xR z=|cj!ZEfBbt=Sc_s8vXMVaEZ6g}jV%SKqkA#S|fn4sEl`ql2tP>}t_KPxI=RwVOl8 z)Z}fD4XP|r#6diD@(dBE6cuolLftq0)32ITL)vC~@9w@wIS*Eo4R~Hir;DYiAf?9* z#86x3uBt8T`KxG=}r%n+dqeRNhN33f?>1V$SQ4>EC1 zB4waa58aVD8b{|fNrAxc*ZT^GQU*QEZ1X|joU{DWTPrqIEU|4>v>+lP0%@E;w^jE3 zmIB*aAy8u@YR8v^SM)82E%9isupsM4Vb%6dmdDSp?^}Y)l=EO$M#_Oz^&F;nNdO#R z{lGX<0_AjuexY=tH$`sa>(ng%i?6Q!=Z`|MURJmrg6{`VQ#FA&5CX~K82pP)A=XLb zu0!J#Z&Y%XWYe#ne@$lcg>PN8k)OBE6;kw)sW-{&R>?(;^!07h48gyAI6{3R76a&# zr3D8$6s*_ykHC9St}feNQo_5?ByLBDVr`KzHI9mq`97H=P;;FaR10JFcQnyHK=gp= zXIZapQ$ootQhAgLe57|Byi z%VHUYQknPt!0o8`ek1s5Y8(wq(hOC~NAWKAqJy@H{(~)U{)iZvsv2Ux9W_aCQm21f zah{daIU>HlH&seYPQ(cyQM4oC6z{(pI9^DU`u zD?V>+9FRV!QTn~>&x?3=jS&Mh4JLS3as^fCHrMVY>OATkWTZwBzYZ9`TT8Fi^dQ?B z+lp9Q@*|8T$t-sK{0oSN~+{e{IDH)D{e z?%uVEn{m-nO;7N{0|}qT%&=Q;a^+8-MMXqFl=<;vyvM&gbU!$lluyD9GAg;BqC>cZ zbQ0fXFUawpQV={m4*hj7#QUyR$egXBfdjLVX>j^5dHuw^p8XKWL0MJ%;Ls>}fd4|h zcv_oi8_IYM&xgdtn#BZu%dw)SD={b;?b)7z!MW81R5VAal$AZ&T6?XY|87(1l49=) z=*)Kl6N=J(kS$3p!g`ji{)fwajeOm#pU1u{X8mAPrd$8&8FR+MVjfOu+Ka%{VYsVE z_N^&C#79q4Aim`CxhW6Hd0p%|)&^WDft-!382B+ir+VU@7TvB&?n}wpc0rxsK5BgX zDu=wzoM115LPWtXwT*oQ%Fv4B%tkGPxa?t?d!(}gJaS3jJ|ZIa@=cgImrB|Rm)41< zmf&Edr->H%=}Ffu-yC&U`m1W1Ubi~!9Mv~zhQ9K znP=AF@Xbd=n9WHI`L58}w^tLB8_;)DUjXDORuW|0^`u!ZNee?<93#(}5E)i~sS5pC zc}y1~TmNi)>X=J{VUL=f>H1M^PtkUh#KWR}J_@Qlwey0K)VN4{+4~&9qXW!`uAZAG zARgegVpb}t(=*PnJ>LDOai=d8d_|-0nsdSC5s=d(4Xv#~r(?qe-kQ@>zr-`UBw~$W zdw}i#dy%Z9-V#;Tm-21&my*km=BkmSsU~~o}dB5;U5Fi>%s(#h>e!|b zHcX&L=Jo@FrHIqTYfZ?mvzHBqqEAi@vHr$wMqLO~5JZ1t4T-1kPBkxdPIX4IOjsBvfq+W)kICrc;Y^yc+k+cYwZ`7&5 zksN3TD48Ui@AqwjwssJsTHh$&sU;Vgr(b%)7-wIwyKe*a=gTcHqDmkREu7?<+iLnb zhaiMnVz|v6E(*t13$;e2&Y;#pS>8H~vD5NBCS$HA6_T7M3{fMa^s6wNCAU-;T+W^u zk`Al*1wK-5`!e4`{Zkmbr!L)NN+w?~j5=gu_n1AuE-%>*NZR5Lz4XQ@6eyYpg{{zT zxR{wEV%g&aQu{>+Do<_UMffZ{9VnU+l)DpJnmtD?hGSl2s0>^Xj*M+HvkqD_D?I1p z9%P)P$&#+o@)5QQ?U3UuR<~bP(Y&ZU?2I1ObT%hJ%5(`BWwp!Gi+?xyF_Qr<17$EY zN>mOCsqZLhBd4q?z+CVyJ~3i>2r znOvGYXRQ6MdQCW9W))vwS{RMlG|yBWjIjSvjZuCl_Pb*Qlt7;=TR<%&h)P zR*vcXSCJaitbr=$!79&wIZd+OD4Ex)as!Ajrpu-M(SaQ4z9gADyR}P)Uyd*37~(TX zU^e0+Ug-={ikt2Z`py_zJ+g6t+XL+X4FYlymo}rnI*PGpYZ<};>8&mk(lHWL_M=$r zbLi#y%XEEaEtZVKStI9@EP@x3UAKtRx~QCr%xfeTc*N|ct^F%MvzprG44d?PBuHSa z)$s=K#JpKQZndh}%1`3VkL~JU?ohf1<9&buUaT~)*6;`6wy~!ahfQ2RZx5`Z@6Vs4 zO&A2Z_ovm(8u{Tk9|KkD3n3UFO$E*8+N{4H@yh?%VRVH+cey_QbSF+!Nwl1YmZ;y+ zG}I!BrXqJB0#=v$BjnyK9R0_TtNpg0U*7Z)tEuakEg;)^vkp)uSjS;%Mtbe*E9_7l z^HepJ^LogYgQxa)YIXpAo>xX0$Qw3wSn?rdr=psB;Akq)>!*c>*H;q{qg(MXjiQXq z5&HpDxfq>^w4Tv#)6yKsq9)%C=nvwW53(|7?%1uc;sy7fI*hDJY4rCLkA4e>jIk)9 zCcyOcCMxllhP@}?8-O}==r&medK!H&i-Xb1a@cV=bHTn~pBY5Jx~{pcE*gUJkB=tk z+xwcm8XibCL!G^xY;eF?043FHt8Z)On`H+_b6rOXPjpLdAA@zlS7& z=BA^{tW;BhYLZ>90w*~ALU#fH;1aoajRvBe&kD4XVDod68@^6+LhObUyd$pJTOLg7 zUcN*n59$9@k#B0ruAc1x>gkmw*Tbu%HgX1<;Kb%ajm{Fs3K;>ZM!%q%Cjs%WBadDG z?4Z+MZum<96(vU%yZ$u?nxde@ZQM_H-u)K1|EgfiFm)f&9MY;1u>P2BUu3_0sJWTz zz3P)KDbV!@OL|Lt;RZ0X2ttxBY;tm$kZN6yoast3mP_F_y366TA{cq>(znbS1a zi*5i`y#j**-I5hOCAFz-6ff~>Ol?$~ZU6-9Vk~N{uKv~pj@ zRXf`}28K|x5C{v;dgNuYhVOgDQx=1o{0k!|Jq(o^Pj?=eL?@?tZ=zH zJa^9jClB$Oy4OUbgKbMGlZm#qH_LpX@L|s-Afg=RPfXSN_K(Yg)$`J!(Als6XUH%+ zQf`nIvUk~Bn4_#{RKJlDd@h%cxpre^FEz6)!r8j=;Fga?1^j#o*) zfnVDS3Amaxl59IWSs;yZ;?8T>;&O`vKZ__K*K^|PFVAJR)uojZdosDhj*Bu}65|qdbIJ2phIOgvWR`Y6TU_Y58|$ zgtn^}!x3;Nc{jsAGXA3*K&DR8s|xBFH;6f+Q>eE}XC*ZmR1bsw|6gd!&tEA}`%-#d zGZmt40G*otzgF1yoLc+07j-;T%sU=zdq{0tLjynGTHAU>lTCR^77m&Aj%c0Fsp@wZ zr>L)-%Ee~RZUCY0n@v8?Xym~4f7{+qJf|wQLn|_*2lMc){zfB+OY7~$s%DbbjD$~m zG#ma?y=U0o{<-}-z~3h2-}8EE@1Op3a?YQ&WHR5TL3ZswP$K|9pBv8z;o^Cb8TtsH zgf`MjN@QchX79gBP5N6CdHmKy#v1usENWbhf!Zf#(_9AXA2~Z$vUUt(6re}Qru3@f z&RLEy;yFKja*_9p6Ggv{uT_j{6JI1gqCX&v+#Tn^C#Bs+SCbD+N zqGag2N<~HhVn_W1>&Y!G|FYaS33fK4P-(6UEh%QC45@R5?2an8yX^xtSe-!4hQ%YI-QE307X%d{(dY<4Ro->XTh`>=)FM96{4m4o2ojc%Ven5$w;$ zEk(P2SSOcgi5p~@nbgVL0Ax$|g}E2pL|jiJ*=u}9J)G;=W0;}Yo{%i+)vcOayN?vr zAhxRyJFYL*sq7%i)jS~;i-Ghn9@o13W4X_$01(`#)q~TX~&ThEUVcEegMI3(har*)`stu{PVe4MA+kcZS5LVBkcx4>DPt zr!(ARYw_m&x}02YB84&BTEr)WvnN)qlE&QmquKOU^Md`If%E579Q)!s!6JsBz8tZ!nLP;R`^lx ze#5A@YcFWagA1|h5i3d%vjePyhuprsJKGb^2Ct16wn66RZK>g0Mi%>`h5NQI)_{n9 zWd{53>6Yq;*nYwN1*sbnibj01h`Xc#q84g6X_E&|OpW3sH-KWQbv{)ucZRs^N@T}m zpK#~a01Lfuy$z|BfIQjAAXje!O?t+!wX8dAp_@swNTaDL6>ko>J-lEoHPc6X+KXxa zUOtftPP(V*Nm6Kymb$X}mQh*nOor$_{jz-YbOL{Kf(->{?|nXsXs$^Yb%bEQKuN-U zrlqnqFAtqva1L)TADE+gXJ6Q%LZ?nkfOt(kdJsE^v(HVG#){a5_+y<`Vd9`W+(ywT zBXRN(K57~4h~lgxoj}s{WCSGDBP+FLAvXZb8fXnfc@MpqM;Qgfkn=*q3kk#E3=XKY z7-C}Lox@YzA??&deGB$L&U-Au{dI5~yvih$nmf1YV!=VX+p0z9Z`N-!)yCa>5HyB1 zVqbhKZZwZhFzj&3xE!|4tog1B7rg3gym9)fXL@Y-2Km5|jG1Y|c2$U2 z>)f&mYN=$$0WEox1hec8y3{2#?kXJ}5FF;SqF)$oPW<@XNm`u0KfRxCREwm8`l0(y z69vqg#LV(k&M7IHOhG~<7^$MWWkXh^47`m=7*Q=Fo`u2~6B-eLq@^_!(VqcB|2wEd z?Zmt$>}M;`XYCNvZk=e(r?tNc8=Z1fR*k2L7gRox*kXk<_>Vy}_iX}9F*-K@gY@gk zxf_7qCQf;`Z?>G708`|1Dl27Sgk?hdOd(u{dmAFXMlDV@D5?SeM63Xr%1 zvOEk~URKs2UXEs*C%Y5%;|5S2tGDaj^d{Fc9xQ_y>Xq6#>DG*_{!E4txR{DMfnE-O z-CRh$+HSw-543xWxmDg@?&SmSu1u)5mpg~r{0y~Upq^rAif~xdGfLbtVE&~Yfa^}} zR-c~dC@|5_ce)jjhH_3bR1#1if8(`_5|}YlZc~E}7clhtes?@sQAQsZ-vGWI)zs2L zBgR%O(zR%R)j3NCt6DNX=qaP21c5Nr*r_(-X&!Q-CEWCQMRpz8sblqx-5HEG07|b^ z)zxYV9;8aoo=900s+ZQw*FKJxFIO#=h_nz?I>uUK9m%Cvqecd`3x`4NVwV-%N#G3~ zPx(Xb+Ex_ym|L(1axXGp)_>v?x8 z!Tz0IkGyb7xg`4A%+X4*-gMxiJB_mhVPswEY9cytMaMqUz9v@%h+rw(YoaTqmO29p zT{#5(GD*d4sf^;<#HyG}5-=I7xegw~wrOU!ZZ3AY!r_1PrZQsF>4fLAB{d7wy4J9j zug>qGwX?chcTSYR!f?i0_90ExuJ3=m_@Pzm$<^>`eoGa{X;0LU z+ajWEJcq#D870gNNllFr^NMht{hKE3$VEmXWscVsBs#~&cZQ{HT=QiTo%+GN_avJM7O`-OEM zaLT_*nSB_84{5**ep(dn(i9ZUA;n22Vhb@VkH z76^JGqWN1j{SSq#(3M(?a;H;0Ws%z9&RMAf?a>=fbat%uQHK6DyLVJ2VXtBnArevKE1e{5`Q>^G#NDBHpx0f9g=m}!}{ zDyt@8X}TNMD}3GLY!?Xnfd1G4`bcD}7JPc4y~9;lbW+ebPo^)hoBm^!BX|{jCDXchNU9^A*EIL* zD)qKxSo^G+d{CJY-rV=oi>QKFCmOa|YHAgYN_P~=X9X(+H|r#%qz$BYrZ@ij`0B@; z*=GCFlDz;2kit;1UsT*a?j%JF*6f~XyLS;qb~?XT&8w>TR{hV?1j0r_#=zLt#1N%P#~0J6>bM-56{A3aQLwoT1QA2Gcn1YxvgF1q1G;_ zmVu26HTUK(8>@X`B|tdpZWthN15oK-*J@~$p;l;@S0$Jrn*$MF08zOZeq;B4N_X}I`Z*a-uW9|3Z0tL zqb?WLo)|HZhrmLOy>yvgsE=!Uku^^U-K~wNw+x=DnBJ(dt65zV?a!Hm4Xp~pE0V@r ztw0->;*4-Z)WxW~LV~6;Gi$iK)lu6VGNRf2nt9vX#qf8qNDmASPhCq*T}$aByA7Fg zO{t$FwLT$_8QFw23ZaZxbh_?bVA=wsZUB$3)M9Yw*UYLv>RJxKSe_&$t<_Vxe|sOG8M~=4gra z1i-?X7nGT|0+ys7pVQp*6VzKezD{@M;-gw0ITK3_`jfV&MkJ|;e|06H-T+O*>+ z;m#5)u=tO#NaY@zj=gucAt0Kj<>~cpgM!o09lK?S>y%o9Q~soq)_9+XMB;*d414Q% zc-8O4&shDOnN)WMb4c*-5FAI0jo>kXt@{=#|2VLsS?>B<$Kw8JMG16W+=uEr@$MAi zInU**^VcFG9lkkf7H*Ad1;_p8yKX}0s1CS|ieJOl3qoc2j^{*xKY%k$n5Gtt#|T2* z_vFGJ;bn~62=XHA&Li@h!O$IfXJEXO=Swqly*?Tkr8PL$Z8SJps0c0svqcGE0tx?& z2V&hp`_Y?8NEz#F@R+zEMuXtTXpOZ<3l-6X)5hyfdSB|F7vepx5FQQj*5ch`XBop| zW05ilN|j06Bgc%&LX02q2|#U>)PMD2QW7b&LF+b_QqVY3d#Ng&&|i{}7cUr4MxT8H zXp_l(7ox%^@!yPwr&MeLY$w~{SLD}EuNeRN`RvZ`t7#72bC_2ZdSO6}%-EH;z0i*Bo9be~ zg}IE}4In(|^`8|*EZpBi(988BJaEn0TxvS7S-a(S{Pb-^Y#PSl*az(x!I>@(`?a(*HwA7nJRXLj zTe|+Y@`uXw>H){ZBb$P=Z(7q``=k!q=@lR0LHLke@1;(fh6Ktx9sao{%FGM{Ja`U- zp-8z+a=XZ1b+Vf(nuCIEO)0Ff6;gF~2s|?9O#xgeBK8Q*y)BZGx@GjMb(GY?J#u5L zflJFy(0e1>FJXQ)E5-(-vfYumGVyz{B#b6l|g~WKtyuo(1Gjm(#utc#Z>Q=GjY1+E+&GJ8*cXICi{B)>6?6R7o)_%!49z z?b!-@VZ|-XYxBcl7-F|Gkd9G#3LniOe>!YUA<b zN>w&$(`C5&NC`Q}lcla8RP9UDK>jpC`?X}1>TzP)mRQ;U+pzfees8%lzGZ9>T@%&2 zFH(BzR1}jt=?IR}iCO?sLBPPXrhAc`+xYmiR`z1zRdrE17Bj=5RX&U}Z+w_fg(U=G z5CmT#HVAPFMtaC+oyk{T+Q+g9%I0wlei$tG_jh>T?@Dp&<5q-cHw#vr7dNp`!`0h& zH>p@(BRJ{`eD)`<)!yaB#NOUzyF1gSu8?UM=-q@D+moH2!;7E=$zbC+JD_g>Y{OYG z8h6@j`BOoNTbiV!l7)kC9Js0^GxV2_4eG&SqLpVXGqOHm66C4oo9)&VY2)cC9>qE? zIzpq;ViphEWF6n#dH3&S-2Z*r|K-5{<-ot20|Yk{|8*y&*niqd`9CBN<$EH?FTgMO zR~}4$enI~Kyv-4uzSZF44;;maG+?AJ()+pv9P{~*qbJl={v>n9FV&dT*MxuFUpq_hAcyaeVr$Wd$TFb@{gw6Plhbb1{}M!I(X68ZynX)+pNKQEKk^rm;- zbC5R4<)*B{IdS^U3~2?GqgvuUuvXQ5?~fS%%QAsPw>oU~jMg*|3Q_q_$wJeQ!TpLQB!Ld7S>Kc&Y`I^FNy9@>qq-E16htkoW*)zK8~~=9+F1(Nr%N~uAkn{vJ?ac$#l(#)fD7AdOrI^< zDZ7}P{2*f^U^WdDz({lf^!J3vZIUWUwJpxXi|E6w)EH^soy9=tLav}4!&IaEuC9@v zN*l0dZ4@0F?OGgurYFfYeXk-}klKC8`JK;~VLSZS2dBkP)vq|8x%^-_(6%HVV+^em zy?$vN`|EK|#=i7nSrg#mn|UBNEJKvwe!=|(xm?utCxv2HfE}^n&CHM&NeRt0hV`0!tHaV>YoNO2DwV!&8; z@Dr5>@&v*q#$A8K`g}OtV$}YjpQ+#pd`nhfv1uCYdRO0k278#@u+w$fTcAzL_GM6r z{AYUW9DlFx7lSV(V@@u$<3BF^B=2;I7ECjGYiWyR&KB!ckg1j~0?7)#n56Ek?-06o zaq_~mC4fyKdhPJXewu6MVF1fjtIs$_vYN8({mT6ZuG@ypKJ>HnyDM?s3oscr#}?t< zPl&Zo{IZf+ucS!CqOdk^7rtq5@5hJOM%TU@525&I8w$Q3A8Uaf%fWgO$=_-Pj3-FM z+vi$-I-n9i@rx=`8zTT0ptd+!TmpaF=HqnKWwIYg#r8^_ z;h3Zre&Ik+MIsn+IXD^MfXM6M>wP@Abxr)k9)oVtW4*Tuid58CZ?l@2QmH&Ktx zd^*IZ?|M;`PGFlu_%Qo`>TIdrzX^Mg!AOHAtEOwc)?gVJRA@N+CaL1?wqIlBq9hp_ zku{~@yi^;r42qpfJ;&4xQ=jYnEN1PT#-|T`&)29LvA~_al)^WoAQ62L;QfrjD_W9A zORgF>xr>bLAu32AJbuk>28GBfz-Cw&_nG{1%^8y%TyjlAT4DCGFVjhTtAB7$WQArM z(Wl0^9Wr6gwIeb4+^UJ%Ts-`;%_q)+sq3(+lmxx-m@He`T+ICOT0}WH02Ii*C!h|d zIqMtDw&K+h4ZZ$jHx18vrGfPw(rss#)d~!A@9Awrnk5`zTb2ddIyjh$&T%y1OVc-< zY!sNj?aav##p)k_IL=I0Iqn?X68uCG(RFuM0{@C(In^vaZR`>JOd9K@<<38NrM(~ba^nrNcE4x@E)_A5dtr6y6SnU%=W;mQ^V3MpcsPw zqu<+Rvae41A;LH$XY>>Wq}2<)upA==+?B2zCsXp?_>W78Ro@$3*DeDBIx_z)USjW0Iy?jum2~DHQ%1FUq9&P5c1@z_&BLOs zC6hiiJ(gx3%#zc1!aKfK%YrTR-xiELDIH%qeqs6A_!H0i6PK;&*b4=BtTOH}+Mjw~ zk&heqq6e7-2xIiP8$uw#>tU}=rRfzU4wbPZytyF~&RoqxL7Pq2 zHIg%UgR|kjVzt}^aV?Vg+V8drYgR{hzf0n;s}%VinjCJO-F$fe>1mPH>Yd2wna?S{ z^QjSE3Pg4ChHcn7k1zQ~tnRoJ9*KPC$CvXatbEYnYKRz=yNIcMkLVXOaZ~=*6d1H| zvNCq{hr^z$}=F#^+=4xWzpHD z-}TM?dH1q=IEjfD4iKComo_K=r!I2F5f>=+j(CH@Um4Z5qcr{5tPp}tXB3& zZPyh$1Pf)&NgaB`_HNRIPl~cvO_uO0C!uiLv76$ z6C^1g8S3VFU(3aAS34Q+nffvfgLCl+-Ebdb zOxk~3#Ro;ax-rQMq7AxKV>{<;bL2HF8$a_#JY=93<ey~VB)5KK@jcK`19`r4J;mGS3E%hVQe6Vz8%L+qz zp*qdd^fZviIQ~c@dpTKOvGJSR@SOrp-`Dz2ij#$vcwS3ZP{&fV=51p{U*WUU}j%yKViSj6UURtSy4oP+9Dp9+V8@5_gH#o=JQKnE(tJ_ ziAlOWby3ys)92J-F{{@-xb(-}(g^y?5yVMvVBy@3_ZjdC?@H|2{{|dX7nB(kA5?I| zcO!oT3i=k*eDm(+(~Z&Xxy>~K`|Q2x7o#uFcsY6bd3Ox<4K8Ymxlz6S_IAa2)jm#v zPHB_XRkZegBV?lka3hTiu+#G7&-xGbS*>UWGy~yv2$e8v`qXpK=bUANAAbL z19d)Kbt^3b(Y%bC?K@T+6YSN=PUbx2_84zWF$IwcSzJ_#Mf%~J7X`~aV%}nPl0Lx- zyAg-az>-~ckCqLSZ)1Jy%DU=oK6x0pEE>}9O3fyr#cbpU#56uzq;n`zmbeZax`?6= z#n-gYMNVss*QNitaG% zy>|+ynD0dm<(;psGSb~FW_lbBTwS*(dz@J8Fx{CP^&nld4&Xc98m|x-*uH@;)UDXm zq#JozkEW{FBm>fYVo#*fTwzC#^j8H`2JZ}7W+_U|?0YV(U!r{S(y|7mXO29BQEYj{ zY6fZvK$(p5g^`*(?)3A|C*oeGBR_zM%ocUkMxTudpoAJ}roA_OyI=&!me-K?IYVeb zCf($AcSGI@d_!y$2={`sXgO#(XclVi=7aKu;bTi@#~CB?ZND@rI=oT@o6n@~UWrGh z9i-ef2r#C=zgbZD6`Q<@a7bp%l#k1o*zAAv3BR!{c_xa2vc37jOiE05SGVLM2-?Xd zh0S^8D}F3fJ#sw-EfKN03%h(ba|xY3syp0Muw3Vp>k-^Q-|(^w>V}Dgrw@PLW&sG z;`6$FE9Iyo!fYdAe|0k-JTeTO>#{!J^of;JtNujZrClE5$Rg1dWn1ymlDyyS&QEyjz8h^{tePFMVOP0ldTP8hV7kh2r<*@Aa z`T15U@yoUs>C1TT;m?T36&bphRop28Kg!H2okrG1d;}{#@QG?rnSH9zg zS7Zt#deYhDc7S#E$zN7lp5SRrZo<81*aNM1-T#n&U$IA-9s+c|CmlN2&E%eXi9HC< z>gHK~Q1>Q~@#a}`g3N2?0w(lp+WQG%;8-lCM2+|S>>3g9e5ieXvS7A2Jnx=;E;Bn& zpz5cnJyVRGWK}$KE}P01(8d>4=xZav2xg$j@y~A~O2F3wpXCCcrNK00@O}zHRjGeI zf+~^^wP$le*O|;>d37K>oFrrqdqYTDELxUYs212H#j;3^A6MU(2+3&SHj67&1IvL` zz(BBqXk`8Qd)Jqz%BO~>FKeIpvlx*3G3HTRI1%VuK~+ zbK^1-yITa>50PI!Yk#5{$M##mi;e}oBBlB8aY0BoygL8mPlI1bR21Ma&tSh=vB%2xjx?7mRi5t0;Ub^AjAlaWGQ(L!QhB!{J_m7R$om$l zmM32!{T9H9D@W9_B)ir>a;y?ld*o~3cQje!k5 z%q%E-GBF{gW=TxoGM|!XpDv%!cV+r_xs3b-{yGea1?~C3yT2P_^EMU*Bf5y|^K+4G>q&1W-`OF^CBP51 zb0okobK8Iv8=_90a%PG+4Kv&s)s2WNT&J9{Ql-MyTzufyJ9D{4&%WZR6A6AT9p!#t zkFCnSeB{OYNX(!1^wG-?&8}U0ZwHOBA4oj&ZIp|l3VVHF*7Wu1Ue&`?{v_s~#+1y@ zst5`a%_J>O?M)+WbXuX2-$wZ#adnRnoZ!*(+dh9yq0!dKYqMBO`|>_Pdd*F0dz!%{ zfiCzq-N;*YTl%GiiO9&%%%5S#*v>`A2M+3lv)C6JL7kr?V?~cNEX`A->1bjGc&HvT z9J;+GFu8A{S;UgNUv(H$LSgX1V5tcA#V+OSn>>M99(Vlo(9FmtZ2#G} zFR~G~uW``a9GXX&WmAMlqRu6DBd#fqvHYNvArhrucp}X5h6td>8;{J5;R|t_`&se3 zswCl`Y#=x`{NF!1*mv_V>c4C+O&)&~6p4#@fY(V&NjzN1Q}D@P-+fl*+4+z{(J85u zU&QWWoJh)}h@{tnXFBtW+$?B0yv?4tzuSIOxjP4ZyD|$T+GiTPEb$Bkg`~GW0(qqTZdL-FQ4KP>}a}zy+4S}ee zZEXAa+3=fe!2VOO`E!I~bvtRSGbuy$&08r3{kd;(2qnaOY{^hkCcatFx*w2Rf9}B7 zh?6wZLFW1E+%ggsl089!#+Sj_}RXWMu;!C>P?JBLE>Yz&QFHJV!m9%zKu7n zs-y9}4Jj|Lx5(10^R-s{&&EVnT%egvFTdZf0BjF?o63b^VB#cIzutLw;J%SOqMbjk zBzPe+qAYW*tzfV3PQn!Sp{pFzfYoDC$(r0)U}nx~6e43NPA#+*9#)c_oim6k%g!}_ z?Un1FKgf8&ju}F)Z;~cFP};bgCte<;Xr8|`%&sS4>V7tk zIj{L$bE%gallz||!Y9Im!+YPTynUVTFDRiKV;S)h6i1u&&GMscMQ~)&)($c%yV3x` zy6tLHsaI%QVJ$Ya*s+~0SZtQ4f2p^UZjYxY+Ta3dEXGa5@paU1KKfcOniLL>on8G*CtaL_HGK3(DfIVanuNv$9Kc& z!heL5;zrQrvL&_7kTrDoy2|2>mlHJse|eND;{7PwG5sRd1Nmt3NAb>g65}zyF&tao zQhPk>Gp5o@b>)2}(^-w%}yj&*PdPElNTG=if#Iy`$)s z!t;tFX5$R&>FYdY+}8t5j{>jX&rxPgB_vT&c9m8A=(|U`b8wY=31Hh*=@E%PAk4jd zsv-R`aV_D%Ptk{&%{%_UQiJZ<3Qt0ESEM^pQ9WB4d1b-@W3E3A8xP|Omc|atNImjC1N2`c^1Q#rzU}M>-W4{MJWA{LA{q_m<1{S9V2);l0+% z#I+pnQnYfroc>sI*L9pu)Yc74nX1uPfB>st{SJ_2>%{VG+d;&F3zB+SwH<%oxW+4` zBGD^Lstg0Eso&DCubX@cJkvoc4VXDsO%>>M?6cs3< z$cX@=Izn*{dan4xGpVrLmjm)xfH{93A?$l613O*1#xr^4#iEQywe^6oPq2;r!B1g} z5?KgDJ4iei$HuL7b8i0U5!bRTeeM{DPf6|`F<_%naLa~Disq_;nq~PsSs$2*WM=*E znHZbc*SeUUn#Q{10i>cSBrQ%m6iXnBLt8DNUdlo!YgaL&HC)MJ{%3|v#!v=L&#qn_ z6F}hC>>#>8n_@4$d{bwN;>57SY?0Q*r0OF1#Z%~Z`Q~_C&$)=H4nQ!r6jkS^j zjo`OrC<6A>**~OwqMdMre1mR;9Gxo2FVQCg-L3T%19Uj5OK$y@yxnkw9Bie30RoR)IRt-7d1{^-s5;Q-Btw1%meWRw`|0x$3SH)pa(P1xy)uK>W z(Nk9{XZ9rv1zjoRF3~IN`$D$J+vewWe96t3SM(3((~t|s0%iht6<~AB`aRVmrLM_g zpKj&&x4f0zzHdFLDydQ3+6n?wi8O5{%yT~@Zb6U9Ymlf?v16!sO}fd?HRJ^hqwR$} z8+e`+1yC+k{?~h3hcXon?Vc1&Q#t*@8O+>PG#cA*ziOxapKA6)lebQ3UqD@LKvN+FfvSK^#8krw&JkmbEgd{O(X-7DnO=O{WMJtX{ z$*2qizj)BR5#5!t!ffOT|1zjpauX3DSjig?hX%yef0RAkjuSpE!h7O^vuMZY;2Z;_ zxfg*6!nIe>JzE>%+t?0Y`PYj(S1(r@v|mt&dnEi|!q6->KT*n03~FSx&Ss=o z!;OwT!c{~z!#Hoq27~Tfq~{Do2#J8^SroEJTzYu*IZ0MQ-YORQ5@%N+tqxYl0 z_^Df(OO)-{#Q=mX1~`6kV*UufxU5jI)dL50FtagW2JfFih4H#0B?io8n4xG9G12c{ zO!qzCEU+M3`=rHL>CO+R*U;55>3rVpqjU)hM<}hfJPfe`%~d%^0Qvb}NYQrD*g*J^ zv{*MCu;f~F{-s-(EE3NQK`!Ms<2L!(+D=F_kSA3QH#8DAjco>{YfnoD#e81elP(zJ~`N;iS*eDEW| zmjxJCJRhwz1Z(Un9RGj;+`Sj;_7|Mwlx}&+NU+%|n<_PF$c$)oEAcOP_6V|A2=a zZz;`1K4%=B01VnBn3`JG*Rz4~Rc(iWR*`o$9s+NQPr=s~iT~t0k3t@4K@EO5Im5|U zK}NTjzSI4wA|zafgNT0T8*x10xnZMB^#BhDoe&@nxn9D}?eF*GMMWOFv?&%mz^OFf}l0p%p}W0zB7*cxV_xM+cW-yJj@kFH}jb*|$h@ z#5Y+aL5+ku@ytPE>WY%Z#N;ROO@|sL*YpWsqeXIKq*)CC?Ma;M#chqaR{^TCiSw` z44LCOraQHEgM#}V```d-nSW>!OY{7*5f;GnvhyM;)hhWb6`xAFJvjvf#7^gf3Is>l zG-6a3FMZA_Q-o;sZE+7gxPcH`RMKQy!l+XkMNy@pGexWQ4Ck;3jP;)wyyA;9m37Ti zvG#v_3@JZFR6p^R(LYCSd|qI5^f+$tC-!D`Pl7I{UfY6(~p>^bu))&n}mz7T~y%Yt7L;l4>003jOZd zzNQeR4X)9riw63IMU{?3?LsqU#RMMxlQHdQzs0c>1-s*;6pkdnnPsdD9FC{Ty9ED! znIuVuD)@UGZ)i~E9N`P0w1=_Kk7>X?{f*(Ha5_vE)iq|m=&w-=B1!C|AIjnHBOw+b z@E(+#EquYIG-{|@*f3WPCzRQY-sHu<9?&erPE!XwbQWzp4MQ`e^;HFC>r)6C$&cKK zQ9tu31AHu4qmg5NS0R9Sv2S8Sh~)&a#w*zC9V(ayKkFM9lWjY|YWx4K4Vgsbhe~8{ z{DgDqs9251L*W*~nuB@Ayb@O1>g*Y{;jbHbdG$cz62iVSDb>#v!lwwQiOCW;lNE>U zfCw${^TbnoLy4W$C=Ix@cWbAfS4a!K;DzlXni1a=fdvvD%pOt%tA;0<6DTR96oZxKbYNn6!6|+7HsXute_#suF~JDG{J$t!3*L1?1Vij_`63R*tDv4K=uY)IPZ6%ILKDF{7M)W-|I%~HTrc6@k{X?^tG18 z1=rt?Utsr_|KkG)ybuT`BnZ|%PNp-sc`@W@JLo?p7wn&wcME)%u=a8MS@ZbSNadR2FH9S8QfBYZ1_rMoj1|PI)SaQ1x}(#buv0~c z55qvKHz3tetlO}1IRg6&VueR&s)^typ(||Z*6oXVpvyes6=~F7Tt3s3h(Jr(Qlpw| zt;3*8A%?|VwTgWkHju{sx8SO+uO%gZdF%DL=-as{P6)7YeXtE z=f~^E76e`zGw~II9@!Y&3zxt*AeukBvVvDvx$gW(t^d0AB<1euj76syt4g}JV2^pW z8XYP(0D2krB_rCFnANuB^Xb)rkKv>c{5H`>s&;6i(H3EBQo4=!Du5Fb!j&8CT68*| z{=bMM6~^0v_4Y*m5I=+phsUZtW@9=Wa9Zabps1@|VmV+&-yiBN37K{blJ+kHn4*+Q z1s($iS-Ra4h9`O@l2HyajMyR0zIrtmUfe)PQqaR9fhJz_-p&pSWlL8iQM*MIe%+OBu$Y|N0W#|NUc_nEMlssFHSXmw_Cm-kqIPW!}7x0L-(w1oOt{VxAsF#vWNHjTH8a0vMgQ$qNBuWjMHm<5nDI}&VSvqno*O3j7 zZ(1P5NI0@=_i#gGW>iD@z!R=l2|l!ZcbpX|)zwP5MVbz~A@@k1{)ISNXd-X7tt0@s z5aG;b;><4t&aP2M-JY*zFU4Kw#b|e{pYQG5()3JE zw}t$DEUwK%*bt|6 z<3@eIcM237o&BhU2Q2STt37>Mkr%?LhZKrRg};wj1%vu9F^`BPksJ}gBa()jY8_(a z)r&11cr|8tr}_T&1k#hv_KbhMwytrrPyI)YZaN(=M~}vBSkyfFZ)J5RJ{*JdeLH^J zlG}+!=yYX;7K)eCHuuhw&1bbb@Nn$$+{s$*_`2&7lc=Ymi}L+smBsjDr&`~N-!uZE z7C1!<_6U5kdZItwnTyg1sSfR7>D5vv}{fUb-@XL|aiztcNUW!!VW&tRkZggiv7^N(EBA9G9zRP^@qS^<{u57voXQ z@(x?pEAMT_4UKk9PEu2N>P=$NAHO_1} z@GYNZ#Jq;;Qf6E4TfP?%Oe!y!QV)L4+3xWU9%=%8la4r|p(sy5 zt%FBSjwgpAi{$~Tt(b`I{`k)t(#O#806qp_6veKAypTv6dRQNXRZvz840JS2r#ux5 z7!2AV$t*3);HhVy&_(N4g1w+tn%J+=I(^mWiP2lTY!49w41!%k#5W%PWpBecs3eM- z!&lx{q#z9tu$7f|x)4RIC7FINafVsuFH$8kB9Hlb*PnVhAfwSn>waG|+pCC!(_yfE z{2U?{r?qrB8Rv6qzI1ngeh@MYS~6nK;^{CrM1jY`({v^P4PLBtk9ox9wY%OmfIZQP za@JZqXuA(h$7mtyXFOvsC(Q<^nRQJ$^nyPBA zpelRBW1{2Wo&*!_?nmh;%0BH4bzkd_(`D57mptDmH^KMRnGU8O6vzYbsPcW2{Pfq^=c#$g6QY4 zUS5PO741WU?G9R(aGLt}JV~-Z1AiVE-;V0b;qc5T|FW~RgGtoK,LF!yYL@ly9k zDs3MwCgz)1R;aHovNEJp=v*D3RZb(+fC0LgBzd5-Fu7KBlZ?k|m0THfn+YfE0nkH| z9+N`iq?quJuqcyR8_$hg*zaQN?h6+<vAM)qq3PJlYkTkTGxU>VTvFo&5|5LMzccEP_ujgp z-&QXArknUPr|*w|PIxA4fF5paeXdhu&uzx%OG}LD#jkl6ni+qkEP$>L4j`>s zp;j6$2@ZN-1>R(dn^Vsvnax!kQKh`W)y%oMr`UYLQ_e#7H*RL%JDnbr9Q#J4R`m6A z9UQCtg7SI?$+ih-OR{Z%d%s*2{q{u{6+0nftL+_|VO1y@(_U6gB`9x&QG}J80gVSh zj^gFopL<$NJ|#OFcPWsKNmSSr<$aExDyOg%dxOZEKcX%{u?+~pmS3b==5X4hsh0Vq zPEs z0@9*S#{8}$XQF&a69&6A%Cd~z!;zet*Tg3;wvkW2TdG5bzM7m?FZsFZ@7M232kW>F zBY*w((_nB5_Q*#rNFd`RqT{yr_KJ`%Oo;$h~ogBV)!D{I0v5d1}%;L9o(PDBq z`m}PH9jqS4F|n>~BQ}jG)5NvH6?NpY6^jK)vYXSQT(COZ{jIhK{AX zzQI>Z>47ScG9F}Y$!qhGH(Zn|Sp>u|aSQQgVt4ff(Sm9@S%#vmNGnH0;dXM9$s#Io z5704`*RgVIlt+JFYzzN}ZF}vGif_$-xWs6CbnW=KvERGC1zKnIlz*Vi1ZPHmcy;m` zZIJ`M7q68DiVD{{i)dZS+slMpqcF3@sP9$@9^-ulDt1u&`&3oB(Bny#VHiwCEK}u9I_a<{zw#+31#%OQI4$i>8%>)mGso)`gPh?8e};X zx&-@#UjG^ZE7rJQTx9wI%@=A}l{e5Nn~}@D@LQ}MZ=~W|%e7=V6`^0B5owY%1WgX- zX9b^_-6dhu3{do*gNQNSC*%b{r;Df}M<^%C&+brgu91b0#jr&DI?>4hVIhFe(%AM7 z`Ay^VHq4m;-$CvwpEW}KT-*@IeIHJ}89%~%5Js)w?MXp5bjw~X$}}|{sWH3AGL){! z^R`NfY$sD&_uGV>fk+k}&U1|6^-2S!<@t=W)WzMqh7pMO{$@Ax*)6bXw|*yNZlo)9 zzvh1riMGi1r23l5dZ_f)ZQ|BKeId^=p2|SdFvLtu*3ITU>E#qHl`_*-BJTR!`nc>> zEW@kHcEqhMthD&?6`8w0Z2rfBsU9+k|36Yny>$!uxoszj2b4{}kA%?kA5n9UD$kpY z(rKr;4SbiB~caymh0u~0s8mge)VX}UEl1$!R41 zbL>rKc$^&+)Ib@I-bR(k*r+*Ze%m^6Zoz_|R_=N37IQ;QUYAOBhDKr`vD`V4*X=Fr>|6JS={@s z?t^5-WXr{d0hAQ3tWT)ok>rWKCVr!yoZF)$FcEYB(Z)ME;B=0swz#p*pSWX3+y$9F zVT5{c{vy`jPwrsQ@e{#qfL~{VzxO4s7m3PiiV`0>#$41|LsO{UBYTcJPah%;m1oA} z&!6@@BB&xxlB4Tn{v&_AnK3M!zLk%4gkICYhx+**^>Sn^+{9aKmto<;!X31}C&81e2d7 z_G=E9dNe52uyHgRonaVMa;Wf+$=kZi_J}vAb%@uroS~og6a+Gw)LIl+Gy@or=qlaV zLI@s%k)5;E`XM+VQ|Fo()16CK(Nbki+!(VbM5N1xlSHya%jp(mphD3OW!1dT8UH#| zz%ZDar=&Sa%pECyYa2RV&J@1<+Ve~GcDPTDN;D!gKylJV!IaUud-GHZwPk>zOmj*K zb6>eaH%s{s7k@S%MLy6Y^KE+Bz*tS7byIVcvqmWp*+==y0&} zdYZU`FE(}_uFVydV4_N5m6^g6wDZsCRR51tRamRp1Ps%;W5pdB!Yg@PC#3&tf@>D?;qYO)PYb#MFcpdZneBhJp%GUaKs6r_0 zJbN?$=wG1pJ~$8oEJ)-q4O71HcQ$XP|Bm(j#JvM^{FIGmCOB99Tc;e3ILzc-Kj$b62nx!3`n&sj-#en?eXpErK1X7r3ZW`C~ zK^tjB#%1+`buQP{8ZDyiPV4SjQPWH%xsYg0^Wv+xxTVLs6zi8Waj?U_$P`qzCPexu%hY}PYuHn{($eJo_}t{u^8D1=^3nv; z=-dYDAX6XbAgfLWo?o(V(}X@TnXr(MoK}QQf=q3jH1!cYQ?g0Q>3q3AY)&W-URZzf z9~OJ;QozJ0G-i0;+3=>VA3@lHK5|R6?S8Y#L|M7MrPwq)P1Kp8X-oUVDU+$GF74Ez zF7djE=1_KZC$&SR*<~!4F~9pB)m$KMYTPtVPN92X z0lhwxG5y*=9^|5)mnT58Q zD8PGYi7}I_qf)Jhf0D&rb&)riqS3{T!%bGHqsc{Aauwna3o<}1h0hm@n}C@?r_Gf`ceUN2D1{ z_*~5lpZ|@wPGx3>C5awmX)0vftZ~;3%Fefd)uxoXPnoB#^v(XjhKS_pEVDu zgst=aE91m;YP0?z;7kjG!|Saf6ww+1bv6`(gFbMrat46>`Vx;1;{} z1NPVN?-z5`)h0yi&j2%Tq1fCbn2>&7Fr^&n5;6`)3~VYgmhe9%sFn>k4YG{LidOM z@-WNduGJ#T^VXb+`#X%!x4HePi*tyD7+Czv25;4^2@0bq`Jq}#4_$U@^-bXh#Rfr? z`9i*eQqBheEUq?qNZz6Qym(!BWKFer(F!N!Df`oQ~}NTJHmY$HYZ zj!6mzMDAG5;r5CPH4aWa53y+KD88*ozEPSS?Q)*S0Tr6^@^V9d9Bx(Y8PF#3Ht=3f zn4dw;^HDFs63KIV_(dqP0s4Ivz$y7=O%d7{%E^?%!|i-R3Q$lpWRZvpT(^jT15t`( zScI%;*4eN?&$19VuS+kjoR`v@v@9nvTn?u6^PH!wsJM4_Kz=Z?@K-s}6b+lOHjtCX zXOu5~T56}vH_=yZM5|F#;SWYKNwOGlI9jagxq_rZk`x+)GeWb1!m?=;w|JOA>5V_f zG8>^W`iHV3tw0R<(Ibg@yA64&VH$BUt{dqe=xKo0{w#L)tt%F$653;Al0b1IUqS?U zzI(92CZ!mD4RI4Sy}NQfmtHnK#mXtzOP=Zi1Em~LR&qS`Rk)>)hXh~E6SaN<+p99x zj9}d{{OhR{wXE!sDo%jPCM&Bf>7kH9!O=71a(-xkkqG&#VIl9xNo-LSJJz)6m>T`k zrD=Ye`=^zfaXkwaoe}{TT_ZszJg(G!VQl%P-)O9lc`v46=xYVxpS-)~mi@Ue!Q{@+ zv?!x_zQozQ4?)46?VO?qbSpPGyJRL?)I6%+<53k3Kx(9^y~{8R6QiGqDp;-DiV z5z}|iMcE$}F<){~%53wZ6|gcvwV3U^^vm{5Q&TZgHAeZw>CRWpGLhn(yc?|Az@;+#JQfQSD* ziA(nmADmu;(>#_HbCwNxbEFAVd8`;u!^+u)9xlKAT(+*p#T+NBdaO$lFR}kqpT}gc zfoI^_MPceq^kD1+dk%P!@?*gloT(U^9sKNwieuowxDV=6Blz@bF>zVeEtl zDQ;?n>1gs*ds*^&^g01@9Nn3pB*Y|pEqt35(VP?;qEX+u5N`!p1A8&We4Al=Wl_V}5vvI-PcVyvH0#f*F>*)^hJyH7-`y+52*_0TDra zZuN7s@oXa}SFxFzN6fpZK&_@IerYDyD%IIWMUC2;&+~K( z214z^oNTJLrC8?_u1ook-tOtXdU_|J#$?0-oJ$Edv3&TS#BjNR{)tMx7JWiT=awCjAjBe!#~Gvpga35(}8 zO~ba{B8eT1epN@Xnt88{w}we%F>Mm7VjN6oh4`~5QWE~lob`-)NhEteg4ls&=+lmu zQ>sLGg_kpWv_|^aPUyAZ{#-9S=yO_#*zXEtDDh1y>-1aP2<-2A-}~v+4he~nh;%K$-njwB)w%_TNy2m-+Yylw4M-<$RgTgFlXXO( z;VxsSbYSrK;eQes5wfHnxJ{d;BMN%(#F%3oAuL2mtXz>q?K`St%StEi2<9cE=!O%H z@-yD#@vW?(e6_9mi6X>svc-l3DAUPy8zartgdTq)(S*NsSX2F zM+Zl@q0C#`z%?)BTJ0xxN*d8O5$sN{8)DDsi+5KQaq;_!posl z8@0CgR|ly@K;tmR2aDYuy+96Tgxplzo#cYeS#H$4J?0RwTjI3MLBO1!3_s z9=70g_hw6*Flh}9M{Wgn%Ae44C(Jw*fF7Y0rg7F!s=Nb@Cw#A^Oga!E9%<>%hYFFcO@@mS#!VOn#Lx3 z(G7Xnf@K6Y8?N7tw2izV#Mq4Hbw}ZX&Ztco#&4{e6=Fc}07D*dRiWA-G?^An8OnHN z%@`07>jLt3R2K$}!PXSZa=?BLK`6NLptAfyzFAfb0)LU`rJ#utd}iPn8I|_-d#=3OUG3@|26E+2&l`0 zn9L=23|*vHE3*G9@^yH~HOx2}^_V_<<0IVz@9~jo-{g_f!SnHGb)mU4_U4}KltF?n z3OT7I4qE)LL}#pQ1)F$6+1hgLa}gb6E<0b9P_BhEu(op5^W|X#A#%#n^Wy6QE4y>! z?g_`ZvKEsp%T~`Mlh8+h%Sg}M#+}BM`wHa>z)Jc8;nqJ>Y4#$G40SCb*om)m@bjg9 zl=qV{w06;#EMLUSx?(LbW72Fay!mP=e+y40CdrFuH9 zQTlhv;@5Doiy6^5?2}YY;wH`NOrn27$Ekp-H7`Fk%hiz0F&CXZ+>(cPuWCs&Z*d7c z+;yZg^&}hhChY*>kvg5F>ebeGf(lnL@(Yq2O%+OjkZKjezn zwlsMXH?q4EUs7wKH+9JarH;d+my1eIA>M>&NjqxE#Wr0NY@Fel>=8mc9-QEQkBhEH z!^ae;46GH|4u`KdYm(1v90*7EJNi^*$&nO~3LyNn;OlJ6m4U_&j3i68Kg5H{hu2o( z!1Z*`s@RS!wT20)$3vJh9 z=`=S7A3hg0Pj#yH`(2213VdHEJ6wB%bBBMm|6R_nfGmdXtuTQeHkLn%2(*0PtLfO> zu32x>de@zM{lZZvc>E1-;2>Wsy4RknRodM*VLzp4+rA3CF5f*r&_9xdi$FcF_XWu> zLn>e)TV;MGd>gD)eM?dI`kl(v_aRsrId?7`KjxbORmS*}=}Krjumag?fjgPEPLA3S zc|nW`{~|4Ba@bbpyUj7zSMRdGlk)p(wPDOpq6gzW_4FaaPo}59jA_rFqCgZV$$@iq z3Dswv99WPkuUIC+1;MH%GbnV|h$mY{$@>N8MOI$SUT=?Kw*YeV`6z+MIj-FR1}FQJ>#5OO zIn@$2Ddmy+j9vF<=+5NNz`Ub2=;wM7VbEj`*U%qJ5}~s=``5H|F>~C=8Y%s&>B@n@K*`_0gaRYvhA5{}*Y|=idGo z|HrWX&+M?cp899~eMoL+tJxi^i@b*S#qOyL_qJ}d-go@{-rCTCde<_&FUzCU=#J*m zBe2YuG(?qWH5d1?jr8ds-Z-h_A9+3wu9RUN61=HLGF3AOL$JGm!RpuQ!Rf;JHCq&8M}&laB7RH^Y|`&+COA+z;FLyXq0oZ=Ii( zoF7ST*`B3_ML*Uge|M~NXytEK-mNWcW=}+%!hLxLw&BMdb?KXFR6bq4$Na zbBQ06FUud&r{RaPYi+U>CqVYtknQ9l)A?08Ig{mag5yF+QhmUcUdch3bmWL5Cmw!` z+>o&NzLWzi0#00PVXwy1u+zmWB?X?2?j zVGr%tvTx-k-jnC8`l0w@Rvr}5iAXo~+w@`+at<@{KB~d$=Qdq64sCc5Ea0++83@S`hPk5&W2qASE<4(wx z{H9trv=5el5m>Q|344{X@7X+x-jD6F=*bTvLrzd5MphQk(@E zP0Fq$JDTCTp7?DAn;La~i+({&-NoPH&}y^zt#uIXyZ)l3j8{FDz>>mw_f#;|o79aS z;{pA*>CMDjIZgoks~6EeEPF|}+f>R9{(2Mh;$7xR&zm^@>*;Y&SBu=ijt|!#uF1ig z7^Y?lKX&lC5^DUU#_@z~ev`hM1mA)O0Kl#B&bfXmyC=yxjdVawimN{d0TG~Slu1-V zf@_j1$3tP!bwD{E?HoNt8b*q17IK(Mk`0v$`y)9Ic)mN#oXUM@XLnJauYHyN(wwJ? zGJcli8WdyNAl;f9UKra?hX#ZN?EG^Mo-&C{SXOs))0|gm?s?$?T$IOq>drGZSFNNt zhMS{-0gV|J{%mt~#zJAT8r0h?B49l^?J*S-cUuUBa6I+>lGF`^3&CeF3dfS_;mnbE zCrFTZDFUBN63hoMb6cYdVS(3D@OU2Pih8-zY7#KrKpBKJeJQWzS%++6^deWOdoB{GQ-pOXl8sa(#ytsV+{k zPZHiI5xjsBBV3;BZ3ad+|C_~{fH;!O3sQoUIqQ+pphl8Q9!?i93h{)ITc5LGk009m z%w}RXsfVbeygwd`*S1_&C#txP|x?FEWZFTcVzQGFfY0k~BibSIy~d!Sm4k zY34N8m5rBVhF_{eNyD9h?u#U5yS;JZgdF2$S z6e-M`dm^06cHpG}qs_jcji>(^#(Eo65BK^zz)JR4y^VHEbd=Upz5U` z$TNQa^VcjBycZ4_Bw^&o9xI@VnMQD@VsT9BzhFn|li55Sbs8qM)Sfqya!p8C; zNgt00`oVh~6@7!JtVwB6N#7DU_#H3U|lFF5fIE(U?DG*iWaI=~TbI-T}S`P56 zZCh-c&C5_XxRj06()VE6oD+vjlt1JYnO93Br319GknW<{WeS4|zIA7DwH3HqCK_#` zpF^ePlHQF}pN?={sp7QW(*JI#nb9ln?Ef1MOUk+}P(M%Ud_1$HC3FP*VUJAK?E*(n}*wRnJ*Wm-5dXSV)n$fK>G zU3X5RYD^PGoN!4YR=efk8=&SAuo8;zix`EI{laVI(I#o8R<`bs|^VZNa zVaS;lnYa&8VE>+W5I4AOP)fPg5$(b~&&X4&!k81-;mRaYOA$p!|Ci>_zRL!^kQ=OK zHqWzIyRkpQshvNgV5VLRRkzKr7HE+v8#pJJhz9*Oh}M&il)cnFR96P8-GZ=jo)Qq# z1n3aWvfm_YFy|8p6m`^Z87GMqIXe^@SMj}v?xjPH6Rsf~+9$umS$lmOAR?)ZHMZG~ zae4IU*LMx(s8k4v5OQ14X$I(*z;T=_UGGOL3)*QK=+nIE5R_zr}wCqlhI zd41^*gXkYXgkW#Q(mc@W+*f)ct%f4XO-bXVt+u$C8lE%c za`o~v)!F^*oqc)V^Z`F!`Fc^>wNe$RB@^fyf72^#;S6L%14kH_?dPFyw~Vih|`GOzzDF<$N<4 zF=)C3=YfwkGjLa%y!*$14*oaPDBenM2@lbqyM#RZJvg)E{RiHZiveZ>2kRCT2=X~4 z&%#YC^cJX4j_KqtIRK{I^n!OhLKXZVwhR7Bk_aZ;zpJ~%bQNe*{g@?;N*yeJX?6I( zc8u}wKyeSN-!#=w>hB&5P||fxEKC6UZVC4#nA~3mqgbp2A+2mG*}5j&Qy>M(4YL>i zS-89$o(_bbxKSHv$f#h}5rKMpEaNC1t2DXzNVO2Mx-m1Yt3ZkR=vM`7vkE$?ZOF$c zWO`$s<)M~I-5GJ3=+Nq;7N^(3+zudcYz9d*2MS zs8*7GQ7OJhfm7?#8zH&g=j>Ud{2q%$li7X(j&IDd=9IJg6acXYu9}gQGjS5|@xeGb zJDM2Sz_@4WB+S?bF~AMqctqlK2#bOyRUr#mo>4ih;>TS5B4EUwhfmiIkWv#o0YU)2BGp4#T8wr$()scqY~PHo$##;I-Fwr%(J``qWBn_qs(O?KXU z?MyPsWX)tY)_N_b^sS!D*Ogg1mGLjIzeMGi|EEJ3|Nl9Jk${S+=ljHSe^wP?Wh>71Y<$<&dOzw;jtux?fy9`+VPjOn);7 zcdBgaEb8c}=xp*PA`$>00@)AgZDZ;R$%-!WjCBxEB-~AxZj`8;~h@LZGdFP&cB!nt1AP{Sg5MO{9Y&hB9 z@;6dOVV~+rif)+L5-fT!kqvCpD1E>79hi2oO(Y_j6eHp!UdZam_Mb!3f#w+;J^Z_A z!4?Dql0MX6j$jxhIt2qVA-=JWKecG@X~XYGFUl<5ElbBL5Gqj%Ss2Aht%Mgo~%N&Yx*EUD9Z@L^2coA z-QN;N5ByOb;`UjF@Ej#NKvSF7YUZM^q$?Om`!An6!;mY!P-_nk!Dfg#hg`7!;ml|v zdRl@7T0Y-GIDv=}H)MTWcy^Fzkhob8!|Vjzf&1N%ANgU6#rmXKX4R{^Km z5R7y~OF&2G@b5ZPb*N;{ZOU9gRvr^~?tq>RI-aE$&jTx=K~mwPjDqTT)FgZ|LtG;V zAcPe|#OomLx@pb~7tB(<{@+i0GRa&1<#&z)?E4?ChPYEg;vmGw2XIwg$@}EGf7I^&jEaWv~Y-uSjh|Gu?e=zfNzP9VQIS*Zs7< z`GMc6KOkuY-l$*wr^D`B(`%qEmq|bbv3k_EBW6P5iloT^&XE$$u54+nEzN{14D3KDdfG7TbbGkT08-19E8&HhyIx2v5?`--CB`|%Gk8w!;Fmuq_AahITsQk2qEtKO5subrc(1YX!R^T>-CGP)xCFn@v?V2tJ@2k6~?RX z?8S}EfCoAJRXOjM_R#WtkM7vz!1G{$&+nKZFiXC6<(s`1L;P&wJ0SCv{kL||BcpD+ z8=vGFVGH7F?nd$_H`4WAk*nmj=q?BeO|R#xGHGK@L|T7XmWfL(GD0VHkTsusPpRMiK z((1$d{c@|W6j=7l%OfP1d4q=FjbNo0i)ZCP90I%(&#*B5;iEYG&B|MP40S!#HHx@8Vc? zZHDw6`{q4I-xAt2^f(xxuZ1ZxqSvp}l2VQGbi2>${rQ8O#f(MD+NN>HdBHaN5Hz3T z`L%52$#4a~qiYZJ`NTS9f)pj_iR(4shU@`Thp=O0^y%!txBH&7-ke*5VS}*JXp4jI z(E`8s5wQjoj#YqvsAYhW1hQ_aj$6ow&BVi(Q( z#5(dZ4#{`f!&`G2L$?wVIO76U-}?wVcfr|sfenEyW1Qx{IqE<>{&xAG{T$%&=%yfd zhrdm1L%4?HOT~YdFJ0fv(3xpa$fJd&kr&h79W`^sFP52qS3;Rtr-I z>RW?t1aY9iqHG0J)4HpbbHW&k!)5Im-+14SeJw&sH3IpSr9B z4*lfz`NEC!i6gW=fq0(bgnt2Xc<}=Y3)2rV($*{Y_WRmCd)fZ==0@5lWbh3yfx`Ge zx~2!?Sr0)+CeHB1*7u6D;22VS%-eFM!f){DQt$=)ZBQANV(R}s;aOC3z@Wf4U-MlZ z^=S)$2612jT9e_4B&bD*$KRb`x`J4CoVmf2MmiklgiV4N{^U&q(=ab38Xz#dh^ilg zt->)UPPq+@bvGX@g3V*@&z(1RZJ-+wiTZR$VQzF!)ft;D{K~@S?(tn%%U{?QPq=@SEu_kSe z*pRs*)pbmJF~{;r?~>w=3n!8E9Hp#^TPr><_9|{piAoh}L0NWfpFbP%hOd(8zC9Z4kWoKQfqPn*?NlWaNhFFkhIFNQDqtrikAaK{j}^kNBJDxC4RGz@Sj1?=MB+AcHVw?F%UUZ` zlCsgTF)iU`VrJrSQ#LUz?H9>4%@7ep-x>hr;`u=I~bcQ_>gOa?T+jfjP@B> z^C}NCD5V{n)~(Q)pO_|2)(oTJpEYlL;^r`(+2#eoyVBEG`{C!E!=fX6w3hO1W(qf7 zrHy)+;cp?-ih{hugc-+lsTj*v>7QKZ!o#K<)7JDD7+fm|T8yZa$Z*^e&1#}1O?A0f z-0@^sP1~x9{(mkrjy3s*@CAoM@S0O7ZlkFp>M=NGn}iK67kSsfR~ z@1p?urzhPFc0AhUA==~p=kj&${0G)HHK}LZ61$y0kbU7D=`_TV63TWEDL*rmC-P_V zNAf3ff>*N^*pxoQvVeO7?`w$8Ft6w5t}U;kXZp@}4zp|APJk!=WhcgV^F#GTXWR4G z*4kD)P;Lr{x8urp`B%IZqV4-jAF&$#>+3$Pc#6R(W$-`9OL4mImd8SAK22ZaWlo{z8OUo-7BB zTlUo)y55WlvzK|i)4lkN4H z7v>iD=k{SoIZ_xT5Wa=w`mdX^{4^B<-*+ur#M&^I`nPvATacD|Hg6bs{KbP^S&$P<4RS^aF0l zyn$hMY*rxNcOBFrzx`cq)LvjZg6cd`yFwT3p%(gx%KA^L!r;yQNoOEsXJD&l0vS7D zyMm<|+fw^;cO0YcOLuhDp{VoVs{Lzr04tJ9iR)0L$Oa;zi8In{aoi)>2XrSvz6FGu z1t9pV5?5r;ak6QVSEQbX1i%9lSH#c#2sgwY{yY+FiD4t?cMPjic!aSD!z24?4IReZ zrzAQzC^!U{#&D~GIRx?vr4sCn@dEXU7es;sGJz3bF+IaSRK11R36zJ zLf!+7JDfUXfHeqx1VQ|P$K9_8qFItqSYrBw)E%=IFunl!h-``hLBrAU|H#fB(H(MI zJz1~l4hg#hqI)BJ^|>9gUqV}iejz#}IV5o+381;X#|J@|rnlbde%U#?Xwxf*hJBugi=&Q=qXDIhJIYGDdXa zElU8|97%>uO<{#e1CC60a&=sFLUp{VWJNKcD6>ebXuXKLsNLb%TftM^Q^Zqdjm#!B zC(c#MtJtgcsoq5BySI%aptP9HH=r9jY({+_^MG$g|(4x(@%-dsg9r@s=l%OE?-09 zWuK4d54t(Wbh;MriN-62cUo7UzCLwj`~vNX$}75aYD?d)zIlbODYtX-t8dG|&jezn z{lczAx^sBTENsQ#3HUwEXCS6Fc9r}>`3KRT^_lI{+hr`is&--Z1jjw})!St9|CJMbeCxUZ#=Qg9f}C@htMyYt3QH-1 zMJI!HGM+9@syKa)<|W5l(Cg)x#vTHvKlznlL*{OWEI~dc!KFoSLnuX%{gs(h?5j7W zmdq8*DrKywbuMI1REe`a<6phW1$I-C&Ui|L$^}DHfeOq7wN5CF)I*=9x4h?JP0rGrGDXnakOeF zJ1C}XfZ&n7w1l~O%OW}w78)+|L^X$DcE66pEmVZ$C}p|DDHbu4xM?(YHZPgkMazcM z&~fWMxeN^j4VT;8!Tq1LlreWr^~NZ$qfX)uk+J|`~RiA|n-=Qu8sEbq9e9QZRvh=!&MR`Eyf_?8MSP?j@$J~|6#YRCDVb2BJw zpz=V-*7A?i$?pz3bdF?OCO1#YId)Wzanx%@L3yMHO~K4KR}KXM%Em=h3SFgqRL{ti zrA7=@RDHK~5LlFK6yDGU)D>Gu0VW{In#I+YZ1rK3pG5uLmCK*#(BET#QyPi~F%Fm( zaNSWAb&X5fbX`q##6$a};u81K0jgtfdJb~;pad`&8PtAvR+LUn#wew6GZ3q!OG@6$ zWw%TNU(x!yH4PmP#d5X3H2X%N{PU8b}D#BWYMNdLnReh0{mlddX z(?9>LD)=uN>V7_jl(s)qYmxRR=dBlrwH4r;_QP<39uK(CmBb7E6 zl~}Vwyu-EgGDwg{mE;GT)ygUaW-AXegVRd4h)s=RBI*`P3Cc=kbCfcE>|%jjsu;z= z>6w5QcPyU7Yu5N0ine>u)EwXGu7-#YJY6&XQ{#yBaq^Tc)+}^L?}m`O&Cl6u#oZU+u7N+NQ;V*{we) z`Dp~aDrWD<$L2u15&mgm9HaEgG!v0a?U`%iem)1*D!(e}*AHl&>QfFS0?)%PL z-M6F9Ii<5#+%~m?6tAZKoc|S`FPlB=pKb1&>(CiY334830ov z5rJx2niBZtsLF58^TzHZG>ejHyD}ZcIDVZa}sUB!Ebb*R@WPXH&_C+%aqF3fiE5keP9&D<MWr*VT#k#LJod zfRHcz&~ktqDORMTV*d6|XQr#$&x>|_#u9QrI(N*SIIq%or37V_B&FWRe6RDs`9>pR zgyAR5gLB)^2w{lCAG8wM64tcCIw+T$SE!G(hfC!faJ9=Qn_D2?hdH2=9iRtv^abe?*W^63Li-BtNl!^i9#;+m z?+A$Y>P* ziBQ2WQYwZ2Bp7tL`M*vCN-8PuKY@%uPNER}PmqvFDHH>dNrn2Nk!_Rg3xl;JS``0Z z8wQG9>c^%fVNxIhA^j0y3FABwaH$^&jT;e&fK3`fM2424pvcb)CL%#iQcw^Qgc6nb z-|+;6ffy9CR9Z+d7U}=k4RH)G-;G4}5yTV@prkQ*14xAb+b3}lk^DDK;t)!;Nqsu* z4kvn7H0J+vf$_h+c>Jf(f8(G2Y(XoSks@>wIym{~1J`bXF!56OPlByuA!~k~{#+D_ zUdmmukD8=|V)uXSkYEhb9~%uBmtr>HKOrRHQYZi+{Sjda=kOjI(H-P(ABC^w>U_00 zC0yI;#%L1g4&dQmLk#=2#P zTt{Lf){cmaj*E!4 zqKBC`b^#3c{&wSW=7@DIGaZ-oAhW|3&h$y+v|!U|_LGRnX!fl`7B>`xqk$C>WymyQ z`|tAIulTI&Cx>uD?n}-rEr{EVTM>|QAzrKiqjDIuH&=2>lL1_Pe$(#3FStK`(V>Gk z?((Om;ZLn?qd3jIUF45=##7C1M*t4~D};5Gi^CVFchhCdcbyds2VRTr{v6Y8#(!F2 zi|lz@6@!S`Pt>=%H{sj^TR#)_<|1EHyQW;Tn#Sqc)b`r0dEa;+1T~Z~2}+*>NiQf@ z4lJ|Xv&P)>GjuBS)ztOPCwH%y`mlA3p_lhbxy^oBp!Ba<<(V$YYFoVhhK6>@T5d@% z?RHhKeAhtNtflhkMfmw4b72?sYz>Su<}+BXY`H=suD>8U=FQjP-06N6sDBxqiVQwR zL2XJtM!WU;#=%IrppOICMW{*!naSe}?c~u=r`0<6C!AeTK=9g4jzRbSp$gwtLTxdQ z4sL30!`itz)~RVZ8l1Wc+0yiD(sx#8_EgT3ny8c?;K>UXS8PReSTF+D) zLOcsNVY`q^)5BbM)B`wu_t}le8s}FWn01O_r`-eh&HQ3&1-yLs;dzjOjy1o-mXj2_k3|}ZEXU-YIG84aH}YN zaBJ56d#ieJPXP_gL_L*Ffn2h{wp~|mg6#PT)l*8HaF)}#$)BsUV*Vj63 z4z!4W)&I(^8o0G_%W+q+D}5Z9RoB?>s~VYo8bE1BCqY65>Wg8cqb+Z5uP&ovkU-q* zi8xznTGJuDKG#-^drpEvZB)H%&^Mc5IR^B$VSP>gj2zGX6)LERu(d)U6Yl83(Qgn% z8aO%x0Ul5ynt<(N| zH1vXq7fm}Eq$MV0=ZSrZ`5NlR>k zBM9&_&N*5Xe89%AU>ynY(geA#K-VC_P{t#S4g2`r`2-N6|AH!0ro|yG96@~}V165V z-wJ~hkf=E2JEgM-dr%Ww={L6{ZSC>?&RtH@@HxN{W+~ z0BOzMkd0BG{|x;aalW8(O?Vu2_!NJWe=B+G8isKp$wEl&m1?~F-1GX=yaVJ7@g0aC zo*qTWx>rgX6K7YJGc;$>7+*bHacea9+amjc1~5ogD|#XO)a6~(A*NM+dA+&eC;e^o zY4@IU*BM=h68v4b;0Bc;T3+VZ#RBC?()(l?<;l{zn$WBs0+6!Vp+g? znjy`i#gc2zx;DFpd}(>9_wRd-HNiX~ID=Zao1z4%1w3rFiY2LvWVn_HyarW}TLkd0K)IAu7*l6^SB(pNO zd(Pg1tQFhQ=UGwg!I+5?z6@Bq_wVi=|Ce0$L5~5)njZpl9lw$9S?J@*toSbR9m9u^ z@iu$fCdq_Ya+*o?uzNSS`x5oAOB3?45vXb0V1`d^(Dy|Wn-+794*2k{83)yMUwDRp z`a|TGrYpkxLh7aTAX;bzY|~C#MSzpTpm&U7J;r#4Gg7rP$Welf_97_*#@}7X3D0wH z-EFaLm9E1saCcl2KSIg_jJW{n0syJwE}Hi?=yg163;guwzQ@;AMK`wR4z(dMT|&h9 zZd%80U23~zw-NbqJ4>;=e>I`)31{-D8 zU{EAUSoShxdwBQ+vn83I97#3ph^vn~Ti9ZZa#|pBLW6>lsi(@IhnEfb2hEQ?Mmn*0 z-O*0_GAt+7ByU)!xZ|RF*Il7o>PjEe{&^~t_TRWQHp!nP2YTfDPxN;A2E*iGi(|r( zel*8<%Nv$tK!eQz%&&I`U{NG!Jb(_V>_fnXU=K!ek)bI+m3@rNs~Rz$G4Yq&cCOIC zj^jwnL=mS75&A}0FcCqh!+FT6|hr^2360d|H{DSpF`>D4!>9;FozN_u|?A_cxp8s~N;G*(2yP|`7waFYYP{*Y&%)E??l z0_FgFQ>Z^xX~qLv$i%p^R5v9PN4OO6GmbqjkLqTKd1omnq?G~I*M8H@krB{?x zvi}7EqZNczlZshPz^qY|I6qUh4dg<^dvZ~XWKK4${n3?obf)>V+!q5E9+varkIX^1 ze`JOAsCm#Gr4JH+5hOEeu$Qt+g=WU|?K^+Gq&5NEbx?#Rl0e*I0flXCZb^ zN<@8l+4u;g)Me^kyBZg@&P&Zv&0){4%`KI<6rCnDMzLg|V^{biCm;z4x1DBGFCa00 z4(%c_4Qh8CgdLD7$7ECRlNE#r+sk>rB7d2%2fway89DaZ$4yDZtTlxl?0ApIMdX12 z8_fI`{Al+H3N)n*yF(N|V!%fKI~y9duUxn|0*}nY#O(EOqNw3;>mWzDl8?TZb$;p* z%U^G4+uvceoU+~i(D60>E=#i8I-D@oo%hm_)ZC0LEq)iAesx$g+HOeKX0+8Op1A*9 zS-I6dP*iwP{55R+33iM=u{7?({Z79K7%!D;p@iuLMsbw*tjHc$SR%A+_ z=D7F)@wP<>h5y0nHP^ARF~~g3Pk8+&#;f`}vN@;aZ{c5#(W#tsTr~6a`@N+qdAeHA zyWH+JTz{Qt=0)K|Zh3O%=!-e3HrP$(wpeExon}>W7p}OpNaVi|*=+L6aD`@}1AZY4 zN>C$^dB8F?J985P6}OyNp;H!rTR$L3YW)-Ul42qEw5cODcSssd<7keB z-rA-%OoE>zGm59Y6T!MY|K>r40~~pujUohj0d=D_CkbS%tB3hwpplpbW$HJNB!XLy z(nwR0-_zOr`FMheVB;Hf(H+drrLtUVWH5(rCYDgH>Aye-$0lr0LECQhUimlh5|NaV z<}v5diHFmILNln}9uP63fELwD3xHUaDnZ73PowgPnITmBZ4MBjvg!=aKh{fi@6H;Y zZM3*<_aqCs4s>>75Spsas^Q?z-vM6L-9$FNzLL?S{;s^!&u&lIAMizpHSby3y_<~k zWF1L;jf86qR^hA@DHrj)P_!A+vb0dwSlcexE<&=gM z1#7DC?30dx0|z>G9rX6K1-$mvn>Y(;>+r2Sb_+w+DM;a9N*-0fIZGO5-BevGaZ)8X zz@pZsUU8l7EP%qflaXl{PqaWum^4kqokk|+cRf>hW>Oqa98-G+{yW0-rJ%buMGIj7%$ zzjf{eAKK1SK-8SAs{o=rynb z2y!DN%A2ff5;~1ID0m*a3q>cR)xt-yfscj6e~9S9wGitVuGU&IiESe`a9Wwq4oB1q zqOhYWX|z$0@M|63(Ghz}NfRl--s|sXhM1-p?5#)C`Nb)Fi$V zTAQ8S6j4ZwjO$2aR9uy5l52WRKe-Z#*yS|0HS!tz9Ohyz0hF@n%yjRO01dNh&8y>zjnsH zi_kO{Pn4(Yp!;HdUWZ1s^{pG4!KzpiA&I}%ZAyo|+uc`E_?E`;_M|4%EEPRVk7Eu; zfM2rxeyM&@{?Sd(Z}N3^6{)SDVCwv6O9AInsh|`i=KMR7bRIpI+g8q0`^g?*y0>9= z+2iCK@@0j$lcj7TP)kpts+jG=as4iQ{q%Aub%K8N%hY$-^|#Y+H$ zsfH{JU1MtICG3Ajw5z+TY!>MqH0H_PHZy18v?*bIZkjo;%Mj4`A|?(W!K5tI_C*oP zBCzAg926Ymjgbrtd0{lYz&m29_Qd#6+L1XcA+sa3l696)HKgzo><)bgY3LHahzlEI zX2M65x}8dHM78-2)$byVMqB>KC;5f)opQ%#MXhu4{S)Rfqo1Q9b$kWZ+zxWP$@oL37CYt7}~k#z|Ew)Ry(6xm{lf@S7*Mq(^(4Pw^51HqGfcl@Sv zjq5P-7=Br>H${=&EYXy$%3rD{@D8YA(^S?yvX4?CH^)l17)<0Xf%h{NM5!J7N_<^E z=3Dfu|8AY4mK4b)?cAv!JFnJN@3hR) zzEtn#-I8C%k|{eYIHz$_kx@_JVrHSCKFO?fF;L2g^w;tPfK-K_!r2-rhVGAN=NT5{0>d?+c#o^?TchvYc*Ee~I;}2a1f&^m6w{ z$06aJz!y2~KZ3KuY_h-vpdZY%$_i?j+q-mdc*!2^$;5W4TMW9W_qZN= zVPfNa`xZsou=pop)L?glAg?z8wk#|4%j1XgqPzVlS>_~X~1e^m) z$a~w8l@Az(03T4+bMujwcM99wv@Qoxr% z$Af3$X7r`QQKcjip9P|d76GvN2BG%;K*Cva*Q%jrHuEUCIVZ$68-BZW{S8ZNT-J@V zt{Y%?AsuGHn^YE=#lvN?eF z{y+_pM6o0CfSE=lxkJtBAhWq@l<#})iSQ~1>((l-$Ye(ewJm-Q zOhmphNCJ5!)%~tV@6|U(pJ#`MUHPqTG8Gv9xuwVz|&hQvKkX_01Bw#{DoJNqY+{o;SM`Vb&89S@u zrL2n-R=P0Q7|U?T=YZ30zi;-ZE#ctc;G^N!%i#5w*^XAd7@WNQ?y+dEWIN$=o;;n##Tt4 ze;cZAoqS|0e9^0Kw1xeIfACsuiGl&%#!foC<{ZhZG`8g}W_6sD|5SGs)?oNF#nk9O zXmD6Nt~U}c#kIC!cAC)5pn>ADj$_wp2mK2mi)JJMYNn9d>l36=BjCF@cuuJx zB$&b+xwK-!f>5i9#~Ef`KCMtyKU0HI^M++^Y3G#FD(RDGao5oJ#^^6Sma&x_+DTpR zQ1(4$;l_O`Gm{xz%>HymZ*~|S$f)=pSU!W@XucXq&$vl)YWJE;J%HJaCFBpSOFd0> z?~IHierTNFAvczDPrr0+6KoK4Yp>Jt^T$g-I7h(jj_aQBwqW8f^2+YY_t1S#P_$jF zTGFmc3=~BLS8t6FN@rsc2WTKIVu}|2!nI$kGag0w7(3psyhT*%0(GX}8ZWsrp^9y|M9a#I3lryu9tI<@4!@?ZrFW z_}u+*$^V|)z1Dn~L#xHbX1m(wbWa6D2{^V2rM(JXHz`0MVLk+Nte`2@k^-^%1)!bC zLtt8qejqEHmbj0ak6KufO>IT_r}-oMMymiEhd$ zj1&JtNa{bw))B6VjFi=!C=#??F0B0~>@kou-E+iWkr(E~^m*zAh3Zws{AyQpGabw{ zoCl24S7Q!bp~}IP=4@Xhr~O+&(Y3erI4U!KYR^{hH^bR`<44u!4`|giPkxePm_+ls zOB|)r;cTy}SM?ZtJalG#-%fhYN;p!e+@#sUY-c>?XCaa98@@=AVVcQPvi{4qnFqCf zzHHL1EevCTHmWK{%R~ds)8;~@|8dnJB%{FpUS>Y<4-U#rC>IYO-8#n@Bb95C(}I1~ z5COzFE}L=(BOBXNIWFG0oCTXMb{mhig`w4)i-8*XCYGz#T+9ltoHC$w)U)$4rYJ12 zgA%*ry>lx$E?5nXKL#!JY`4R^Xq<0DFD4w-#0?cWQpVWXTJZvWzN(IX;EPptKb$oK zGx9*=6^aL>SHc(jtiI$3>9K`itgf2IK|ZE83TnVl*%YlP8iyPiv7v>XxfcsLCM&oF z5D@Qi!BJRAK}fI>trHg8*jA@k0pR}ECT?P;@hUrc=1RH;@(x$OHdq|4VJ6<@FbN@= zkRYK)5={d=tBJd78^%n%fon5eAC?%~C;1|gT{z&97}>Vblt!aQ(jDJgm8LJPBq0VZ zDNjh*kAr1uh!;Wb#R>%zMe{`~Vb@1X@j7RVr_q_FKKLjti@s+hndA6c+^M~>HhA7( ztjiN$Sz_et-bdZ>J&=6S9ZK7)=F!UG3-{c4wgjPm7l?clfcJ7FsT{+&kk&scN8H`) z_m7tVVqtrUvj6eCJAc0uR@v#p-a+_y)$;@_`9i1ty%Wwm;ycJwn=w`K2h zdi&c-=sULeUJAgJbi#3Gt#KM*w|!20I?8NHE>cQukl#DFf4niMglE>ShG*HJ@GPFO(HJkXILQ43+|BLEP6v77`Y)h>R>%iHsh4)R)E(r>x!}zk&5F>5z6q@3A`m31^nhL%q75;9!8DLxdFvS zIc1rSn}X3s>8w@u8)*%_jm_EbPoXfj{QT#Tase7P7GBvY@B9kBesaU1VmR%0fk zafSM|*%xuAeIEgt10EA`4TyVW-gpWW2j7i620=c8g-2ui=U8})z#EudC>G-gHb3t9 zYp-yBx?%GB{x$Uw^rWUGP^7+v__Lu}CML#{-{Rn*52-ocM|{g8&yRKO7<>YfSp(H@4X8EIHvvR|aHnLxm>th~|HzRRwq0Y++!-h9kWX0@2 z)>4WHDbsYi+x^^gZPt!^4~xC(k$Em?m@CT+=xW``AqV&d!7VKdF+HdF5CP8bGvPf)`%cF*tmdqLKJpfkt zvnK_pL*goNQ$W*$Z!RpE_de-x=sh&QW^Tf8VqB7@$753Zh^d*uKrJUwg;;2UnIwta z%V5iUXclFgT4{j=OCEYa75bR^sQO5Jp^#eH>7+MgX=eND`(}t}#2wd(>Q$w@3!~i5 z%PACvJVu2advJNVq(4QNepbC;r1GbcJ><<%-{)lq2c)vf6XbmT4Ztw^BiZ#Tlg?D^^2b9OxMOl2f*>!SW{p9{0#BhVL$wOf%EiuIHOBpm~@LNPYw_acIbJN?z)x9&B62U z^nPoCXMPk*S@o3El{cd6&KQ?9!tXWOP*sJw&Z8%@)gBLwWWoEt2BcHj1cL=M|810P z5OxwO2H7p>(F6aEJV+LF(%-qh9Q#3-uz;Cjy?C`F7paR?uz&=S$`_k3Xih0xz|GyO zXu{mnK2lW$#l_htF9{pCCrAOzO=f_T;r)?z;eKu%zl%#{lW?T@RN+ie6w=-Q%>d;+Q*w}^6@n_ufLXW0X=^X7BzFbdpB!4rEqSbZ_wGma{Nq2DIr`+4YuG`q3x5aGxFG+##e7ZY@vFtMASUuD5m1JFEC3^10U zykOXQc=@7-O8P`88F5F`Qk6AmIM7QpVy`Sk&>}vE-}R*rf|ICe5wwwm-vQ^x7DH{L z;h+1_{y5%d0)M4U{;YW?sYkhkkI{(WY6dUBUesXaL#6CBXr3s<+TfJ|7K)&TRpKg? za7U;0BGHu;0>|(1&Iv`M`n0G9B~&c+KAv+w6I=?!`GT+)y5t_zBWY;^+r^>=tF{Va zW@t>+NsYBcygE-pjH;D`X7W|Dr3+6ot$vd>TA~-;qEG43*6Mw$GG-8oVg6OAI_U;m zKNs~#KWP`k-7LIg3-9_cLRg?j_h~LeFYim(W-`0`r=N>wkgPfI)m5>Op=w}pRiUgn zNANL6Ler)`;w0z9O)GF_45oxLmE%>z>|1iDs%O1z+ z`%^R*!?Mg6-?s3H;a;@EZKPGK-n}^frBH{|e9Y!U0#wWIYtVGHXxy;}x$eb{I^!Ou zjm@2x;l)w{FH`^Kl%uTOtUSC|Y3PhbLOhnC6f4iJJBoE#@EQ54oMGOJB2@LwEtU?J zK&0q@`kQ!ItHDj#2ZY;*4)hfFHBAy-X$G^S!+U0*U+GXsXrE)eAZmcQ@XW`|j{K2dbqDAzu`c451Mf=+_;0NWdISH9td8`dQ# zaO<(;;mZsev|8Aj)2F(d?87aBG$5J^pP#Qy8OPYY_g2&kXJx(}UG8mdaYFq$Ixoux zBV&L}BPD8nW#7}ps(iM(c-AdE+g3i`U4FW~_JT7HB10l%4fN9I16lPWC^?E;Ivm;l ze;7OG;LN&a(N885+qNdw6F;$SYhv4;*tTukww+8cv2Ek#J>PfFJ?EbL$F15`YomAf zsveOseYUS>BuqL|Kn~Y9O zf$=oTw-0Z8caB6x zZ*HANY+Yc|VK~j%vU}G_s(a(%=8arO%Hna-jmf93=wP)d{zyL1KqX93nk=eKt41n}{FKGXAqx;;KpJf_Uh5IQZPYEzbP1^TRtyHu5+&B!w4!;+?IqGTgyjO*h zT!vq%O$*9sQvh#G4;nZxb?76|46{(Bcp(TW51N0`B!BA`b9FFf#TpqbX2*$H&Nk5q z9CZ6+@xQ%3WdXFEE}We=cl#)_lwI3Vjc4Nv`T+73uNl=R-j}m**blGYC-bww-|Mfl zY0CptPp7Z==dm7m7MZhk{n_7@%H5nw*@J2Qb17PVF252(-Yn+wv)E)d6Cqw+8wuW* zIGs4Fki9hTpnPri1honL#y0e@P}unDmQ%WG@j%1j)PdZufXpBOS(x;L_u5 zjt_82pIsJLKT*X#d)s7hDfy~pj)U*^b;B%`tt6cS1Px6;@i~>bEm%s5UAuVRxy<5C zOVRLCLZ=m9!K10X+jl5}K4mT)d#6A1#5s91g@U%aBrrUtfk#EiF{i zlqHs>Z){S>9n40?{W~f;^A%E6qOC|A99X4KBT|Yt81yQ}3v?A`W-7+6Y24D1+>0nU zCz?Q~j`*T|2?sK0J+Jfcg{$*g?KM2Yqc&xgS`|t}sz$V|ODq7lgtVo$v6s~Y^un^D zy3)EbFt9XD^YXOBq_i=2u(c#=IG6+t+``&X#q-h|02>>Cg*x$mQG)DYQ$sm_p-g*m ziJG?Z9^XjaN)j&2qoyf0P13>Ljx5UeCb49KPkWq6)=RPyqP0B6)Jh|(2r?c(a}QA7 zxzk7kCek|n82~HOunU{?tT_k*D=CuG<3HUQ6ob~bdujv6Eoka$Yit#kYHT%n)}NM> zv^@me$^)gH8t?t`OZAYPHA7!i8+Z8b1?o>$#{%Ixyws&>r)q}kTaakya%d{FPmo~y z#uLZ%0l^A~{A-KLDnuSe2Kf+^YP$Qq{BH+U`$G!ABkV0bb$d$Y0QjsAwH_PM^^ z20Dv%+TwMYoPL+6nbm{RYvr-}+*qPj<<5gDI$$fIRy0FNQqt;OfajhZ;ia>XBe1;` zOPYKKGTbQMP5@yRiz&u?8@hxd8L5!O1KmmXRb@r`3v4 z;dqLJ=`^5K8B?VghtJJGUnw(8;+umBD!L412IsgT1D5e(4l}srBwjz)(D8PMW&i>B zeQ)D(O-*TCt#K=6a4>K4!rT7XO^^zm`k(=7qeW#k`gO3pFA*6rPJSNxF4hV)2CEwo z=slmngj!xYuw|AqjX@yMlu-oBw!(xTfbZBG{8~J!xac=q`>oPPg8t$uC=kDw_`87| zJYEwlcfc9R#;Ufno&G zGe)=G($VXT=7Pm}nxI}w^?kE4Rg;;56WWw?3v3MxRf7t;%6pnyidJ3e?Au%l?i98% zS+b#Cjyw%wJk`dG8^&z4!m@k6%?p(h;LjXmm_`!Z;yNQ5lN4@dU2$dPAwweysuhx< z0%JoX!=wB9@LDE=fshIsPvhtbl{(SrF|0%briF$EK$NtsZj+_D#^2w&<=7`dRZ!6i zGfJ%yYu}RzsRFqtO;e(&9jS~f!KJP#J!4rHRTfpKn5hR>O^3KGqz*3cGIiz^n%O(6 z3cKp2;&qmR)p3WR0G($7XqsD^A&u#7jO{|l#VEhmhG@y6uxR|W0kn#nh9q=TGV=9< z=q#0XK3tsOCDc*^Hn`IGs`v*9da>y(hgX{@mDlpwDbX<>T2sWz%HqZ>>L?FTRbg); zPHq4E5R6`gpI)e^!A+A$$buH+P#sDWdv{65_aq@IcD9y?s_~oi9{{+N%t({nKSHc& zk$Tq7@YKc}#o|*h(wgWFQX;?Jj~hRtPtY@Yz@dUA0fQZ{SaB}}xX9&E<+=tri%NLtzc zbE2oO3>M@0bar<3JzbPT`^uNhMkP=A1v=ef-tEGA$a6Pjm4EC6n-qTIxR9;-JtRhG zr74l8dcE#GtvhUbknn1RD@{ZS*<>E3p|gv)T*1e@MCM}ww(%{QH|B&XqAMB}$(4iiKZl?BzYM>Uo2?Ncy{x{uqNCOSfj}`bvk=mYm{~d+*?+Z`dX7dyMg}&9 zUy?F}OpO0LvUhMKWMl#Sx6d=EDrvJOg4p$_I>E?e+Ik6r_~RTUfo!9mFGPdPge*| zv2r?(o)4^Tl@~Y1<+Cj&qn)jEoV^lxm!2!moM@t*&%Du3PdT%b^1U1GXB&&M9ei}S zcft$Kn(cA5L!7J!mu59~S+h~gE62=W%Zqy8;$?`6&Vag}G-IQWX&DR?lJG$`ebr`! zfxq}ot+(CMWK|%GptjE{UU2XsmduS|U9!L>%nw?2q4#34DAfdn#u$4+p{-j~I%tXm zgD6!YJiRRdD}U{| zf0zi%AKlt((HOjJTFv_itR(9zRGsXjbaLUTZidB&4C^z}8Z*M+KHx|`E-u~=N_UyY z3YK*ZF+A?b0 zFyI{)WtGZ+JPBL*l56md_5kF4wGPoR-hqNlFdUxFZO0HS7#bNC4bRGv&SPZNybG|_f{vBt*0LlcTA>g)Q|opumv%vxm26%#pQ zEAEHQUThQ?R!pt0eUe%EHC;_^N+9+~K2j^vkn<>sFuj{|d{Hug4SmKH=2S3)smX1m zsi97w$S)AqXmpzjJOuHTi*OuNKN*~EBHi>cx{Lch=JZfX~Xv3l1w3s z_R8Z!VDqLoje0RzzkU#FW&#Z>EqMnM5v}^}Oe+g|g!E{d^{7Ntff~M}Cx8YV)s0LI zfZvLla&Go$->tuPUeK&jX`;gwKK40kZgt)%U)Tmpt^6Y=+|sS8_tUGR14>G`irkc= zplSOOo~8laxX=xpiUC!B}|?)sy&|MY33Ip7^$N`!sL0C`?m)s{YG7MWStHe-j)X5Jg6MCr4_ z+J~~>w{N}XNPv*dAJkX|Ir5c7M-vC>{wwfK(8k))$lB3?@SmARfskI>$k0qrz{d3}@!%g&AuA&jAsYu1 zp*A7CoSyxc;1`zgUxEro4mM8q21X8qKp-K#u&bk}qT`qM`;RM8W%`W^8jy)uZHdxQs-K%kzeqA%i zJGBFuY6JjI(A)iOv7YhaDTwseJ3zVljk0<*I$k`D?k&mA1hv#py9&^_Z_a4Y&dIoFP z53eU3N+WN~tWV(ElT=x6dBvO&+-iRaXxdVcS%VlHt86~mM&b5g8bbHZ)%9gF2dVK_ zfM)ghj93Muk(O>~*28h^1XvF2k;4x_LR4B{=dJN}`?gB6JIs^?y3?cHES84;N8T+& zeE98mpT~30FSl>z1HYU?!f9}p67!KNglW0?6!87*H&XEqR!8uK{^V*#g~ZO09@kJ% z;wff%uG)jN$eTmA;Wt8Wl-GpR#SwV_wRz_ssaQq1>A*<%E328ATIll+NN7I43_;^DmXQyDvs^H>4IIx7*EPcxs>P-VC_y=$A1R{PLq zF{j%(QUc^2FK-yJVcEjrN^|u%G?eDXzhYlo3!SMA8W(gkpI=VP_z~Ejk~&q`tI|AE zK*%F6Zr-yEHqFldHNNnlp0en)wnrZxY?22!!@S>)7gLlP{GBstVm)u8w~A}NowC=Y zBbC8;Il7?BX#p3bAawE{yN5CW~ z)tY2ntt%Cld1oVQQ}2Z?weS+g;&yO%?62V+i7R9-qq_^+a~r=3gDJFbNm4?%mb4z% z0j)rFtWH0m2v++h$OwMP4%){%+u1rc*istHg|u7;cb6D68gLk^O-j-U!Fo7ww|^~m z>;xz-!qIqN(W&@|!BbYLMN9SJyA+pEmvksYuyB`vCFz{Lc1trm!dHJ7i#&#n-@7*4 z#BpXlCjPS+lUcMdl}#jfB!M|ump<-lSSpu%>>+&+Z@|DpDykSp4R{AYK!O()#qn#c zga1f`9&J4Dxu1%;`!Tw4o@di%sUV{LF^y+V1tqC}3D3xd;W51BN0X7|>}(X3wj?T1 z-f5U!$J)^_Z)9oE1Zo*57o)lke5C!AP~FVsq(jxrSXTq#y4s$JAPfU=+OPVt*W-|HPSIEtCV6J?J}^y6(P{(h}~|dq-sG; zlz3_k*@6zWHYGU~FGBW~_8_~xUC^X5WX=$GF^Y2#V_{vmuOfS2b|>+!YzsPf=Xe#U&rEO!1C=QBC*`gHrabn6PXo~l$-E|$sU z)Kqrj1cChaqhI4!jEP0PvnO)%lgu{*&c02{aqgR^%Xl5o5qB&;=FZamR@&T4PYON- z$R!Fuvol-aM$tJ$N{EwzPx#-}CxP8aCVNlp+!8Pxfxdg;kZe9NGqc+YNXq#rFhWSZ z%_dGO70kz8^WSgm71^fsRYx;bOQ*F2r-aR#Yi9_u*s0(QIL{Q$&MjwnE`P1N;Rw$K zzMYfx7!}+2yf=HWU1;Fyy!i9r2k%(apFVeXth;SA2~vEzx2b>Tbbb4Y8IuM1-SdR} zPBwV-#p{o5r4M2pAB4ZonVC#~){rO`%{RCxRslMxfn$;F4%iYEBVoz!TSY$Py>U3` zB%T#FY+}}N;rq}5@By3w zr-=B%PZWLUklbRMqr>+2AhsytHsHm^U6<4Bx|PPfXZ&g*PsGP4CG$x+xUYIV1k@l( zzg`9QSTtWDX?h++&U~QP%xgTsPwzt>ci0Jb=!rI}+mpWCY=l4ASx$@cH!9|=qHmFGu>7T!2+|<(gL^t{i`MPb^biM=iA8siPOY@xVKc~>1C^R-tU zO*FmVhk>m}7d4IK$rGp-vKiiUD06!ndJ3Lz7=EGA0()F8c`S+i>;Z zv#M>TLwo#laW@}Neaui;Ou)Geh&7~$_PI#AckstM$W5EZeZMd$8I8g_>o+ZMUPxX= z{-X~h_TM!K+h_s)CH1F>MT9(Xz2UzBLeUPxxdeq!KEm1j+yzjde4ASz(^h1jD(Zrf zSByx^LjJqbBZsU3!Hv2_URG{#7Yw zuNGnkDTM!y)@O7#>XnDyj3Z*uo`?E3@7PmgWl+)Jw(&};HhFt{^`l7x>RkqrbLX|<-~g(Q$%7{nC8 zcoe@kVBm1-j?CxyahX{%>sC1%xe(i<+`Ev5KbzFkX6sq~3?U^S4ASSm5#sF@&9uk9 zY?C+mrPSt=@beA(c_{NZ*(6EdruXe4S`i=ThEAuUu8T_)RGHk>_*5wyj5?KaDnD;Kks&=%2DitdTiNBQgMd@ zLa$m6l)KOudNFH@G#DRvHnFE9*)|^w?p$ijoDFdNFda%Ssg((CAx;IJ7?#0q7U+;Y z9&{$x{+Rgtw{fBm4u6>Lo!j+LK3lf1^BE5kdEkCgDZd&dOB?s3 zuj65Oebj3r*u8+>gh-RDwG`R1z#u#DiXgDt;3Jsj@jYbr&T4^rd_rweZU<{?v=L~& zlRaAU^t&_2iNG6Xk3+1!@yb0omna&1$Ywaq^Px7u+6U&R@4WK0%TK6M8g3n;vQ_AB z_6gsK|LoUdlKg~x86&Qhtrq=!h1|vg+j0wFLMx);6Rf$kx|>Z@+Z>750}j~af?a`o z355+d*>)XDB>M`cAv{|G4q_pJiXCu|vDw=)=nlA5bRE{L{h%5-2{)Mt0Lf$u`Pmh`JTo zc2lxzbbddbL-LZ~iv9_y!5_vQ`k)Cguc+oT-R|;QYCY)I`FQbu$W8*<*m2b2hTA<2 zrh3fzXuX_2+l(#<`dmMMKGryD7jo`qhq*MHgqS|*$Jrv1` z7Uofqw1bzs*-7?9=(Wc?=tTiy+vJR6cOimPY3nffr98DKOg-T#0)(|ZU)#~O=; z7Yxox&sEO-oijFTze~8wy>pqgo+Mz=HJ6EKF&~(=Z^0#rj)~s~aEMOD!|d7Fmjd~# zVlr>A-NN5;-qhmADXAz~DLE)`ikgb5q&u1JPZr=xzy55}U(`;T7Pw1g zi@Rc8G)~e>35vXvRMJ$kSSc%cCX<#Dn(kmGoA1_UDUQXT4QEH^;!$K|fa-SkL6-^B z!@RA(r3FCKM^N^@J%h1aA)9Z3f`h|gJPBVdbSEJ_8>3x7%%jY2m%-=%=8p&_&||Le4qA^ z%D_+JaRR~jHfMS!euKL!vK8_m&-9d+_#()G?taIvDW-k)^OWpLltEtL8yc-G@x6(U zmsPQYd*a#7j(^G<9W+FL&c8$~9X>;eau@giwGjQVKwiPsf^cv0U~(6IB0C&mbQk!g zW5%cKQ|yzg_{05VrnFh6^FbW1XA_<>dla`7dix+61HbiVWSW52u>i&*#V-1e_zqY7 z-tJITlIMh{?r_&jwCeRJH`s?D17ye|T+@OUs*W?{9bHZ?X8u;SM^a`p-uK5~cxm|(-SfD! zU%4rwzYT@M3A!eB_R7JWUBzT|6TV6NZsNXkLOcX%c-%KrPJa=C*N}VI-pQ4xtJ(UE zTac^rinq|8V7(!hd)-@b@PlZ71>h@9quHW(!ff_7+EQ!5P4^SA!$0K+%k+3% zVRpdM_Y&lQSk8pE9xB)Ld%J>d{9fL2V*i#ch|&RP)2DgG4(MmI1?_;j?8(jn`+(Q& zWvTgrTZ6iqgSM)N!mfwq_#0+@1`1ydUi%8H;&ICBOcM7UPl^y5JW8@Be}nl9>W1s!n`}w8Hsbt5(IM(S;$G)}M%q3SUl1J-$55Ak zMzlR3Ml42(>l-bO8cHr3TPzM6N`^DSyr*85d`2=Idl(uz(2KG~c5R6HOycHC8naCr z8$tMQUg-yTHX?JZII{Q<*X;i$mPWN6!RZrGk|q|!vW;qf(I4QSQQR;dVq!@%BZ#qV zQF+KNdta}RJRyC2{cSNnV13Aj85g?ywLZ9?AvpN|p_TgVtlVJ4iOa+(?g5S|)Kd<9 zktHbekr(A?7v;HGW(eb?{jY^^B>GL+S>_WTp35Ut19nX|StK$rUaA%cvql z+A#i)p-L~6IvQ8D^cVaVtRhW&q^S1T5xOT-7K7IV4_W5hCuHSs+ds7UpzSEh0 zQ_3PUwZ$*irIE9WP?mc1FL34`g{84oH7n!3?%-|uVCY@WFb5S{zzo=BcioBH8h zLFj@Hf8o8S;pcD8*&NxuAh`Q|@c9&M&R%Xq+;IFICf+t{!Hd$XSVv%sV`|BgA{`%v zYZ+b_YRie%l8B16NeMDjhn=6zKH@!|zQb^j8{GCrnb=WB#2FG$|Bf?-yaH)#%mkx% zOcP4BYn{-ipbkE+*Gdx|Y5YtRZ&YiE-V3cpgwr3U#&~hAA;)_;6u0A_g>?NkZ!ey| zcnrtiOO&g(Jrpuha+IQ`sP$G8upLS=VXsg}M#f@g_)-!vQE`^B-ZA?O7HAL_Hk=fJ zqwBsAPPEF1Z)vQCY{INpkl@0U@F~5UiynV6n;Xdqh%8au9@Fi+B#Tk zXl9|=UT=^(etxXA(U|A4PnKMz(P?~cl&t$j4ei!+xo6=%Wz+Kbd*8{n?mB;I)3v{} zX5GCz)qc@svRmYFxz@4&0fFy%v@MEn6#c$^P0^v*Y_(bIupIBz{s_FkW1hbF`?!AV zVibWYQUncg5hz1tjj|IX8;wp?iF&EjxXDv=YF0G}mIV(Z%Q9?<-NaL{RA53KE@k>u z4vp&1anOW;pUDDd&PiHkv7Hc9H1-7Q4CUb07SuG2b&n}M#ZX!jD2Z|vq-5KWtxQ#g z0uOQSt#g`<9fiepUEiOr;Na6NQ!Nq3ru-`TdqcoU4p`JVn~>*!5F?tG;Wj7w@t_vo zz}G7&x$n}VY5ky=tZZO)H<>Cctq>EFD4U$Ri5q2=^v9ry@7PZD`#~OV*0DOOlbIso ze6`=>?q;#nl?CS|Vp=i{Yt{hJz*uxmZA_X*iVeM0ah0Zy9)eSfb=%uZbMp5w z5Ik7S%5ti6nXgFe?G%4g*9-^R(nwd9U6%Hhvf0uO?dK6{d3K>1$vaDu*_7%Md%z`$ zCnaT(-J8bfaqFb~&1|sxQ*-mTi-e&<;UZPjK>Q|&OwGI!t-o1BaQf`x{CeO1Z6@oi zqH+jQI9;>9Pnnd|kwG8f4oWJWFg472E76KcLcyRnFk&C!Rqd!YQkcne z5gB*iN{E@gBWU!Vd&sUMGy2h7;o5fL7PM%|eZ00hxJhjh-Ky*wV(X_%)Ng;iZh}W+ zV|Qkz7TqKAd?NWFs5~BWE(jGFW3Ai(+Bv5>f8+ogXrs&LS0LgKFLx8 zTWe*DMO9I0g{p3_Pm6y}ujUjKeA>!oUuyD5pn~=*F?Q}U1uqRFlp-L}7X;__C}3A+ zwdWL5n2PD59b{%@QH5(QDsCE_-&R`HjEG21Tf6gib{FT@ZP?86Y-ZgK#C#}iwT~_f(%E(Oqhpg($pm>2V;f)0)(h+9$t;IEHml-1$Ru9fEAl_yL zKfVN(S?E?Ty%|Ar1YuK>jbzXmw^==;r4I4ms4nra_t3 zx`tKt=-#UjKh(MQ6aYF5)=L3RLW&iEVBFSb!z0U2AJCh+arEOPMB%?vF&Qh<#of>; z*>+P$`TUyE(rk`MdiMtd4{+V;I0$n2cs4Tv3$eE2A}9|m0}3Lb9T@Sap)jwCsvzQ_ z3<#M7bT>rtJ?eb4kB*&ke;tt+tufI7WC1aDHKIH7XhiG2gjVCWso8_GbtOBr(L8OF zo=ol06Pv~#gX_f!f#w^LN-mbnkEySu1*-|O zq6D-EY}IVjBHK}EswtY2=XFQs(tUZrKbHOcFihwv77WT;H1kG`D7!FhT*x-o=0@IruE|)2I%ufl=G1ahLVSabhly|H0l!yW?2~KkSwT=_;dF> z1*cwo%k3J=T=L6YI?I{`r+E3Nlk@inC+^HnouzFXLfQ>H3N{PDU*uSR_fDPiGf@xm($)VK-%dWDo2Ym=F=H%QlI^u=Sl-W-zd}w_9+O;m9GCs* znm!jPAJTQQ`cChqs;#LBQ#(GsGe%4yl%S;iHkPmBWenf3!xY?6X5xG)LcMIcoH{+P z=LE0f;ntX}x{KV|tTbJE%@e0~I(ZmEU)!|mF^}seKWPm-=2cOz#fg4XTUa_XR^PiG zTj|I&QeIe6$v69f*Y>`fabc%JnEB!LbJtO>$VYcZTqw65lGBkZkWK|WJg0tOOz=n~ zhS#u#FC}SpLZ_zpr;;E^L@E6p~7ELF53bYsB^CDQBr4XGF zBC~CM`tV(Yy})`~kifUass1oQjg9RvMHZ5(;_NKy$HV(7M$3k2X4Z&)zlq6#?>&%f zr1sE%5ZVab7;o=;ij_JK)8yu%)I)F+J7>~&_%X#OCKFwZqKl|{Tmt##P+l$5c*obs zW+2np49k{N$*E12r9fp~OFO-Zj;pnDxK<1}_+>z8V@Sh?Y08YLB(N}4g{5v7l!}@U z*_djL3Pyol!5@tS@F3Lb0cDVq@Mr(Fc?hX{RkykQ8uWP2<>izA<`9rc^|QKIkKVF} zYYg;h16qW5Te}12^5*oq>xS%ZOZpwW4)k)7`r6os04SW5i&yjQa+bWq<`d}#^0qV3 z1 z=Q-Llqec(?wHyFKLQ?G96d2= zCoL&nsb539D5PUm#Inu&y^g!gb;E4}ZSrlZFAWw-vF5e~_F5t7z^SXsVCej*x6B)s zw4b_gSa-baFz!G^*x#UR?g{!IuTY>!o(+MNWYPtR6+J&1;(_8Dq?a?C=7o>5pso~J zza4CPO-o{oC9ID#y^jdix^sOjK5@DjHd!~lH*=Ou9Dn& zfn&UxJ&*Y8kilD*B^;n#k)&xUV$fK^b=L>FLdJ3Gh9G9-_;b>96w9$X_FS#uuA|Ng z2kJM(&oJ8rjNZ{{{4Ddouw^Czsm8OGX@bS`#%)m#bHhh6FMzJiyvsR+BmNi4jZ~kZ zb_+mPygGJzA6?IDr{emeYQ3m@c6BXDT)+cN@?0U!vBa_WG1Rg2G4-*Dbj<fjcR`E?g3URu4g<(B_6=we4qCK z?;#P(t~pT6H{&DDoSB^>QTlt_ksk`u8m3TYzvr*8TBAKh4toLaV3tW~ESGWJaZ*g$ z^L`%?b4aX-EVDmf7Syw$c|f=rkl}j*&y3WBtOt&G`Iz;nocjKrVcod=;ipSXQ-_gg z^^@3xj`*qw#g$GB)=&DU*SV*DkLZDCG=QQ2tsrxKuN zUJkTne8wj^fL>5Q5)VEGU?t6ka;oFN<=|kcMILs4LwNgyF#PZVztjHaJpGAZKn()u z7Dey^X}pL3oeM#DIBmq3=S zDVWchk6`^_GS%Ln(^su}wOfjxK?p8_NtYuJGa-GjA0&zFLIQg+Ap){f`}wA^N(BVz z1F*tEJDGuhQb~6OhY3nsg;%-}jJI=pvD7(M|1gG<_2&kVk`Ey>4aMP&H>?=I?jawM z?2ZeH8jr-f#R7nJByl1Svwr6Wa9H`z$o{rvv#%)3L5m27$_DoCxj>)|`y}4S(iKVn z$`wzWx5wysy!<8a0_`}^?nUT;aW~p&JPL|B0WbWke(KLr7JL@`$Phqc#1x5B%NA$J zuCP)q9VcrOVYjK&_6XoZ21ij%NhyW^2M48c9xZAjH_0%Qm7H^lbmLE1BF!je=j+iO zUb4GO^wj4Of>BdAJ-7Gx13~6q>ef*!8|}K1CZn_YHo+xjCdVf@!TP7eSC%4uEZA*T zrgLy*ouQ?d)62jbPSSaq?)8m;pw!ckaW@B;B2;>sG&U+bZ{?R1&C_-rv(Ffha0D-R z824~^)+7uvIb#0`YW)Z%|1481>E#p0G9OdZxPcOQZlQ9PBgf>N;1nnqDy(ZD`w zfuQK06M?|18(r`d68eid7?;+pPuRgr#-Ynu!4?&X)0W|kZ3ax_vSC9Jp`--FkFBu^ zNn-Iq@itO{4f5@G<8a5;!p*tQO!AXn2p^6i)tv_~qCp81LJ-yjh(rcaM~A*j4az94 zaiD>ap7_iQWZTbPy^e($s}AkzS&?CpXeKFgmIje(I9aH46e)$99DMiIJ%s`eH4K|1 zzg!V=w}3dCetjeD)vGIPNamo~nv;RrMZ47b*|+&9Jt0{xW1u|7m0w%_(7b5ih*PSZ zsFX$}8P+*os^3G@Y+<2#TqCM<qWxz+y$#7l_!=v%mb{_^eJio~ zr-P`DTgHUvPHQBQv#RXpVY05*{Ut+s!w!`$zek`y7XB(+C?@VZBvTlu|qu9&x+nG{PZsv~~{D;*F> z%Fe!u_#MjFpAJa#b5+V;P#2*b%Iz;^7yCL^7o`^TB?)DFTtpqYw9J~cf^YTb4_zLr z?W5y-&-+41)9Q7np&Dh(tD{##tjA9!WzeJRqLT0WM8ue3jM#EmdI?yUo=;1vtoXXu zabAN_A8hz%)8iWUy=0Ny?R9oL3d0FpOm#DosuHoQ(5&2VURv(26|~--`%B=Rhq^JR=hRee zmusB*Nw*qe7+}>%Lm_1YP)SNfUC2b3P>@0=Vv&Q4gzrSG`#g~-M-LD)p}0X6ha>l) zX6tw+<9-Gwr|$_svIV&W_ynY-#l*4l+eJ$a@}k~D9&WjPI{vN8@2h2$?}!`K9&C(& z`0)ZALdt09!kolt1Hl)&=&l@;Jrb>T* zMiw9`gK9 zk)T`xMVebg!i<=#R0I)g6F6)fM7$u~XRQlCTzD+bTVi`G%qK}@rVS{f`&Z#X`fA*|$J1kz0*66hN6aVjPCHVg?( z6lu+C%%_u^)rS5KO?fdrf%g|?)kpWL_he739)5ewW2~eJ;X{O?l48U1m*NlPHw%|9 zANr!{rVe>IjgOe{F5$f{xNSA5n4k*496E`T-po--C=QD1m?EH)jumAa) zMnpP*Y#sp*$0Cy2$O#1F|H;|V`}>?FH|R8Sz!b0-loVJ6<;AST6f^~?je7!{C?NQH zRCaTw2M(kg9L#MOMef2n0NEV=1x|CoE0RrML--4J5RPF1;N$n?4(4LLzc8@dGnm@6h_hSyDnXcW{?RDc@#zpe+rp|G@b@tIpH%G z*LgI3+l|O`Smr5mu@bSWc8mI1@=<6*N>0dqmDzpn1Yb{ zf~dVD2<{Ob{p|WL64sW-dtj>pj0xrv_68F}J<)Q_?AM-Ub+5_Hj-DDZtTI$Up+CJPxxR zG5Iz0Y$et`9Lz?O6|qUhH#f@}b>U*HgiNMnBw%8E$#)SdW*5T;Roo3XHBRTPp3$F_ z=X<&>2pGB6D1p|msbHy8Xs}8HryLFpp46Fer)lW$+2B>Z=DM^Jq#nzdC3v z2AUIg9S+0&V7<86!3-E=q7ljn@2mjSSD@}hitY+6=u_Pf)8?IQG|`=bQ_iz!zazCpo%8!ow%$0=b$I=i+vZX0V*_IEx%8idP^qy{{ukWY|2L)q*%e2#oR~ z2FI96*k@301t)%BvtP6DxSb-rcD;CgUTe~OSshR59I~ql8bv$@J-Fc{MG~>a?kp3$ zJls>f>Ux@v-m82SZ6pbrvt5vc3&{7=wZ4xu`-aNOgecsU)O34J=>#1 z_YP*-<1QqO$5+OlH2rMgWBs1zjJiqmkHu{fE&91Erc^K9syrY>y)4?ny@SrpWVJEXF@8|378|$vY4@@h zmh95;*3_V^vF2tc`4FOPgErZGp}Xv|@|B%%b@x#A655J|l{^q_FQeXR@K{N7k9T`} zJ0FBb`!i#nWw$911|ux{kv>Vq??x5>t^4obz$3Q*(pHwFiFJX zNd-H&0W+D>7W5zsY9%cQdec*7Ed(>p>M)!fiaSs=gt#mWW(Oc9b|1D9@JCvoj@-4} zIlOjQSa5INRYVVcaTV03_LdFMJfif2cMfuOM44(T=VzVxAQ@z6hvXnjh;HeQ24uLW z*r3x4VyoofJtCS@?U!z*rNSNl1@naxSB8ivFNHzD9Kr_}Gz24EP-+ph7RhQR)zq5R_htL{^eTC~g;uk~9;du_9kj!9HW)4X)oew(jV{G4 z@Wn3Zsz0i|Xs=W)`nq7MXXRUAUT9ji)qd0u%C>|5?}zlm{@}+{rZH81wcHxo9t=(6 zC#!-_y5|6|k{x7NHatXE%%)Rm-jbF8gHJkJ?*xQ!<`VWpAB`>fyuW!nfACF^UGE4p zMG@xQB64_T2RYtNeTTyy!j&?bRbhZxx1pLvJ0lcRbXPz4eImmg+y|FJ{STyZ0oCeP zD-}B#)0N+4kvy{$?4}N(d`&_g9>3n}Ccd)lJ!}bc;K{bt;uZUR;eT01&`9>4X%hgS z4TjA_z%oom?AAUIU~~M9m(~N#N@L`TsYllU>50vri`Sd4Fv@^sK{xO`A9fDUPh=oT z9W~8gA<&@t$!k9bCq~|nduE2#tzA0PQfoz(H0GI~`(x8Brc(bo&xtt7bkLqP%1i_c zsJ~#jcPL`pYwyVljkQ`k4Q~Y^uEky*p`_wO9=PF)9cTB0V49<87k|`bn_Go3UV7hX z5Z%ZyeQQK@#&50}DhmGW8uA345o5VcG_#jh#T3PWJFi>YcAG4Dl+w_)#NrsSqg9BN zC{c4_$b;Q0|0mir*Pvxxie8NpeD9|J`+435nQL=N{hG2y@mAr)xs4542X~o>rJ5qZ zfjkq!Z8cB@SBb=ih)cMGD27^AV`+w_=1gfR zYPgS}sR)RSndXKDNG^f6rMRTxlBqelymaQwd(NCW^S<|-`|W;u?x%at^ZP%-xXT@- zL*H)po)||fm9>90m?c4&`gbz?7^j&QA8~uW^o&d8!rW8E|~0x`SJ6Ksd=0^d$A^ zAjn%@m#i?V_!}6JHK{glj9j*p4DplK9*A}MIx#-VJYVk;&{)W6@V&^(vwyteFf%cz z8B+ogIoF4P{l|L3&XHRd;gtD%q$eSzt}s$3Xp>fB$h2Af{0^*?Yglj;;WWCBM?BwC zqDhd(DhF`Y-&@Q1dcT|tlUFe?xn8{spT1*lj`p>Tnkt<*R?U$V?i;A}Ynj{wiOZKlUOc$0 zZaR@auDf6bspf3|T~_wFuI0eGZkUF2pCVo{II?8p^j;hPX?wx(>=LPr`4S~tF(UOI zeg2!*gGsU7ADlM?+L=L@hz}=Q03rvb@)b*xoG410Zq^+$3tfazhJE&kg3E>OH$gek z>7?k_niW@du(vki{npg^ifsg(i3(q8VSFEhoj;y=%mjKDg4o#IVB&YU390dxV1>w^ zLiA^-obQDepq=%*%ELEIWRn$0%B3ffbTgqYCr6MQ8U2u&c=d^*KgxwFhAs+2+1W>9 z>hE-_ciu_YpPAYffDxUrNOHN@t!dn`VxI7belHa3w$!|A8~wrNRO*HuF|hTe8pmYv zX}aP|oZM$&EVjA6S0SSuGQaZe8FpvAf%j%%W99=2-X?O7vw0enWt4q(lyJ|%%PP_1 zK^+sqUQXP2Rx;(h?(22^;MBRzpbJtPKo|0QNXIHSKE|e+RBVqlX?K$}8ui_MDCglX z`C&6chdd#otu-0TVhjn?gOK8KfYnmA7LG(IT=ZF1`8bdiCCh1=>8XT^yOVC zE{`T>WWL5%$(nbEbRaYJ4XdjXoUU{IoR+$|qgMq#!t+`+^-fFYN`2e0qH|Yev>3nn zqMva;XA_m}5pzot*FkGnyv_CbsNEiYiTKgfWIbE;^Gk@TuWgbX8Z+#|SqofIqXJ7u1oaOIDw**K8kqCjxVNod!qJi)o`Z zJZZ@`jzkkn|*&%)6P2J&(e72fh1Ef8o~VA25@bv!hM zIpCB5qtQ&;p`xL{;50QIU3=!tC&`FBIN-7@fbL78q@|8WkrXH1EI<`nhQ>ct(Qm8b z3TT?|KTUDc;x7?8#0I(s+|eqS6^h?1%{LFjqpe>E1#vzy@|or^n7m@u$QP6op@}!h z|Frz5u-0mzY`GzGxFu}SZKfP#&wPmiXB>g)wueqP;rrrmBm;sezpg}5%`ox$%e{c^ zM_17}J-X~RZzQQdaq5Y1ri6)0kYkmpso}2qGTAA=o|wX6kgR3Jn&+U~!Su($*!(mY zI2B}1a(-pMq(V2(m9sbIk2%rnuJDTPxE()0>uOm#WeN^swaFhCTQKX(T73`4RWn<& zo7p0}XS-|~gR-cC+V{K^vRECKo@cB&IEjg@c4w|C#6&RbItIMOXLwHvn8w@ z&tbqq!hKlE(Dl}n4q2$s*$`^2=r{FX(pwPP8Zg1h$~6KZoN~u27=-?7`3gSq7l#Eh z)~NClD=qF#;`M4bxC6sr!^Lq}CyQq#p2Rl_pF|9*Mi=!QEoNbhSw@+e;c;!tPK3~=<*FzvC>ztaCyVG?O%M7% z?y$QP6?bRbXD>(dEO>89IvTiVk8F9vLo`DuVA=Q8%;iO7i<28q@nD@jajp4uvrcYW zf8R`%kb#(JqneSM6P?CFhHddjtL2ksJ2>uoFDxqJMz?kPGA=dRL;C_{gtH{@eQ(c&Z9 zYS&3`Onz%=@TzQ|1u)q>QAKuL_#rCaASNS~3IdteB=aC1M+%V(ei@x*ZHoRod!_=g z^V>ga-7oL>H@XJA4ktL?k3AO z?+ff3KU4ukJa>6k`{{!~CQVAJTQASBF`AqcM(`RW;KOMIYg$FrQxD5qAvjNO&zy$S zuas35?NSZ&n*76TD?L1ni*7jn7~q`L+66Q)rISdy#q7|iED8$a$RCr*pe9XkJGVFf ztP>?$?jW%Xm<0wd`7dbP1cv}fmaRHIZ)U^Z%TezRVW&aUIPFP;a4 zuI-VI5Wp&XHs%uM-OEeuZLLIa(^wA{}$GqQUA}TX3OSFKp}Z z<`!9xS*?EvDq{&B{%(T5tLFb9s3d^O@NX0to;DB^cY1HUByj`FsB>M5Jo%`C51-Go z>0aJyd>Pwg(){APTpZP`D~5Y^8vi7R`Gc(7%FbIdO)1ff zBkS3E%@J~7>+e%`Ze?+(m^*hF%bz`6DA=eqD&fVXCPrin)`o95LVxpWx+-r|a)Cc<&`q7Un4jw7wV$lj@H zvRL(*5@QkX)_UD$lIQ~;EKoL8+wT@M9q{!y6QTR`T{`|+lm{W_OHp6WVZhnczJC<> zI}HD;z<+9N*PoDn|D|X~M*mXxKXWxClpFOYIX@Q~aubEdYDn@ooWmvWLf?qca1Dtt z|IWUzOY}HU!$`6vK>TXpEu;oWLdMT)z$^`cz9v3kZxh2CAmbaxWoL0`RnHyAcI>dT-OIwsoX-TuUf6eN%VFxa3*U)3HX bool) public featureStatus; - - event ChangeFeatureStatus(string _nameKey, bool _newStatus); + mapping(bytes32 => bool) public featureStatus; /** * @notice Get the status of a feature * @param _nameKey is the key for the feature status mapping * @return bool */ - function getFeatureStatus(string _nameKey) external view returns(bool) { + function getFeatureStatus(string calldata _nameKey) external view returns(bool) { bytes32 key = keccak256(bytes(_nameKey)); return featureStatus[key]; } @@ -28,7 +25,7 @@ contract FeatureRegistry is IFeatureRegistry, ReclaimTokens { * @param _nameKey is the key for the feature status mapping * @param _newStatus is the new feature status */ - function setFeatureStatus(string _nameKey, bool _newStatus) public onlyOwner { + function setFeatureStatus(string calldata _nameKey, bool _newStatus) external onlyOwner { bytes32 key = keccak256(bytes(_nameKey)); require(featureStatus[key] != _newStatus, "Status unchanged"); emit ChangeFeatureStatus(_nameKey, _newStatus); diff --git a/contracts/Migrations.sol b/contracts/Migrations.sol index 9f2b50b60..610cba54d 100644 --- a/contracts/Migrations.sol +++ b/contracts/Migrations.sol @@ -1,8 +1,6 @@ -pragma solidity ^0.4.24; - +pragma solidity 0.5.8; contract Migrations { - address public owner; uint public lastCompletedMigration; @@ -16,11 +14,11 @@ contract Migrations { owner = msg.sender; } - function setCompleted(uint _completed)public restricted { + function setCompleted(uint _completed) public restricted { lastCompletedMigration = _completed; } - function upgrade(address _newAddress)public restricted { + function upgrade(address _newAddress) public restricted { Migrations upgraded = Migrations(_newAddress); upgraded.setCompleted(lastCompletedMigration); } diff --git a/contracts/ModuleRegistry.sol b/contracts/ModuleRegistry.sol index 966937c52..840e8bd2c 100644 --- a/contracts/ModuleRegistry.sol +++ b/contracts/ModuleRegistry.sol @@ -1,11 +1,11 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; +import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; import "./interfaces/IModuleRegistry.sol"; import "./interfaces/IModuleFactory.sol"; import "./interfaces/ISecurityTokenRegistry.sol"; import "./interfaces/IPolymathRegistry.sol"; import "./interfaces/IFeatureRegistry.sol"; -import "./interfaces/IERC20.sol"; import "./libraries/VersionUtils.sol"; import "./storage/EternalStorage.sol"; import "./libraries/Encoder.sol"; @@ -35,24 +35,14 @@ contract ModuleRegistry is IModuleRegistry, EternalStorage { */ - /////////// - // Events - ////////// - - // Emit when network becomes paused - event Pause(uint256 _timestammp); - // Emit when network becomes unpaused - event Unpause(uint256 _timestamp); - // Emit when Module is used by the SecurityToken - event ModuleUsed(address indexed _moduleFactory, address indexed _securityToken); - // Emit when the Module Factory gets registered on the ModuleRegistry contract - event ModuleRegistered(address indexed _moduleFactory, address indexed _owner); - // Emit when the module gets verified by Polymath - event ModuleVerified(address indexed _moduleFactory, bool _verified); - // Emit when a ModuleFactory is removed by Polymath - event ModuleRemoved(address indexed _moduleFactory, address indexed _decisionMaker); - // Emit when ownership gets transferred - event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + bytes32 constant INITIALIZE = 0x9ef7257c3339b099aacf96e55122ee78fb65a36bd2a6c19249882be9c98633bf; //keccak256("initialised") + bytes32 constant LOCKED = 0xab99c6d7581cbb37d2e578d3097bfdd3323e05447f1fd7670b6c3a3fb9d9ff79; //keccak256("locked") + bytes32 constant POLYTOKEN = 0xacf8fbd51bb4b83ba426cdb12f63be74db97c412515797993d2a385542e311d7; //keccak256("polyToken") + bytes32 constant PAUSED = 0xee35723ac350a69d2a92d3703f17439cbaadf2f093a21ba5bf5f1a53eb2a14d9; //keccak256("paused") + bytes32 constant OWNER = 0x02016836a56b71f0d02689e69e326f4f4c1b9057164ef592671cf0d37c8040c0; //keccak256("owner") + bytes32 constant POLYMATHREGISTRY = 0x90eeab7c36075577c7cc5ff366e389fefa8a18289b949bab3529ab4471139d4d; //keccak256("polymathRegistry") + bytes32 constant FEATURE_REGISTRY = 0xed9ca06607835ad25ecacbcb97f2bc414d4a51ecf391b5ae42f15991227ab146; //keccak256("featureRegistry") + bytes32 constant SECURITY_TOKEN_REGISTRY = 0x12ada4f7ee6c2b7b933330be61fefa007a1f497dc8df1b349b48071a958d7a81; //keccak256("securityTokenRegistry") /////////////// //// Modifiers @@ -62,7 +52,7 @@ contract ModuleRegistry is IModuleRegistry, EternalStorage { * @dev Throws if called by any account other than the owner. */ modifier onlyOwner() { - require(msg.sender == owner(),"sender must be owner"); + require(msg.sender == owner(), "sender must be owner"); _; } @@ -70,14 +60,26 @@ contract ModuleRegistry is IModuleRegistry, EternalStorage { * @notice Modifier to make a function callable only when the contract is not paused. */ modifier whenNotPausedOrOwner() { - if (msg.sender == owner()) - _; - else { - require(!isPaused(), "Already paused"); - _; + _whenNotPausedOrOwner(); + _; + } + + function _whenNotPausedOrOwner() internal view { + if (msg.sender != owner()) { + require(!isPaused(), "Paused"); } } + /** + * @notice Modifier to prevent reentrancy + */ + modifier nonReentrant() { + set(LOCKED, getUintValue(LOCKED) + 1); + uint256 localCounter = getUintValue(LOCKED); + _; + require(localCounter == getUintValue(LOCKED)); + } + /** * @notice Modifier to make a function callable only when the contract is not paused and ignore is msg.sender is owner. */ @@ -99,47 +101,76 @@ contract ModuleRegistry is IModuleRegistry, EternalStorage { ///////////////////////////// // Constructor - constructor () public - { + constructor() public { } function initialize(address _polymathRegistry, address _owner) external payable { - require(!getBoolValue(Encoder.getKey("initialised")),"already initialized"); + require(!getBoolValue(INITIALIZE), "already initialized"); require(_owner != address(0) && _polymathRegistry != address(0), "0x address is invalid"); - set(Encoder.getKey("polymathRegistry"), _polymathRegistry); - set(Encoder.getKey("owner"), _owner); - set(Encoder.getKey("paused"), false); - set(Encoder.getKey("initialised"), true); + set(POLYMATHREGISTRY, _polymathRegistry); + set(OWNER, _owner); + set(PAUSED, false); + set(INITIALIZE, true); } + function _customModules() internal view returns (bool) { + return IFeatureRegistry(getAddressValue(FEATURE_REGISTRY)).getFeatureStatus("customModulesAllowed"); + } + + /** - * @notice Called by a SecurityToken to check if the ModuleFactory is verified or appropriate custom module + * @notice Called by a SecurityToken (2.x) to check if the ModuleFactory is verified or appropriate custom module * @dev ModuleFactory reputation increases by one every time it is deployed(used) by a ST. * @dev Any module can be added during token creation without being registered if it is defined in the token proxy deployment contract * @dev The feature switch for custom modules is labelled "customModulesAllowed" * @param _moduleFactory is the address of the relevant module factory */ function useModule(address _moduleFactory) external { - // This if statement is required to be able to add modules from the token proxy contract during deployment - if (ISecurityTokenRegistry(getAddressValue(Encoder.getKey("securityTokenRegistry"))).isSecurityToken(msg.sender)) { - if (IFeatureRegistry(getAddressValue(Encoder.getKey("featureRegistry"))).getFeatureStatus("customModulesAllowed")) { - require(getBoolValue(Encoder.getKey("verified", _moduleFactory)) || IOwnable(_moduleFactory).owner() == IOwnable(msg.sender).owner(),"ModuleFactory must be verified or SecurityToken owner must be ModuleFactory owner"); - } else { - require(getBoolValue(Encoder.getKey("verified", _moduleFactory)), "ModuleFactory must be verified"); + useModule(_moduleFactory, false); + } + + /** + * @notice Called by a SecurityToken to check if the ModuleFactory is verified or appropriate custom module + * @dev ModuleFactory reputation increases by one every time it is deployed(used) by a ST. + * @dev Any module can be added during token creation without being registered if it is defined in the token proxy deployment contract + * @dev The feature switch for custom modules is labelled "customModulesAllowed" + * @param _moduleFactory is the address of the relevant module factory + * @param _isUpgrade whether or not the function is being called as a result of an upgrade + */ + function useModule(address _moduleFactory, bool _isUpgrade) public nonReentrant { + if (_customModules()) { + require( + getBoolValue(Encoder.getKey("verified", _moduleFactory)) || getAddressValue(Encoder.getKey("factoryOwner", _moduleFactory)) + == IOwnable(msg.sender).owner(), + "ModuleFactory must be verified or SecurityToken owner must be ModuleFactory owner" + ); + } else { + require(getBoolValue(Encoder.getKey("verified", _moduleFactory)), "ModuleFactory must be verified"); + } + // This if statement is required to be able to add modules from the STFactory contract during deployment + // before the token has been registered to the STR. + if (ISecurityTokenRegistry(getAddressValue(SECURITY_TOKEN_REGISTRY)).isSecurityToken(msg.sender)) { + require(isCompatibleModule(_moduleFactory, msg.sender), "Incompatible versions"); + if (!_isUpgrade) { + pushArray(Encoder.getKey("reputation", _moduleFactory), msg.sender); + emit ModuleUsed(_moduleFactory, msg.sender); } - require(_isCompatibleModule(_moduleFactory, msg.sender), "Version should within the compatible range of ST"); - pushArray(Encoder.getKey("reputation", _moduleFactory), msg.sender); - emit ModuleUsed(_moduleFactory, msg.sender); } } - function _isCompatibleModule(address _moduleFactory, address _securityToken) internal view returns(bool) { + /** + * @notice Check that a module and its factory are compatible + * @param _moduleFactory is the address of the relevant module factory + * @param _securityToken is the address of the relevant security token + * @return bool whether module and token are compatible + */ + function isCompatibleModule(address _moduleFactory, address _securityToken) public view returns(bool) { uint8[] memory _latestVersion = ISecurityToken(_securityToken).getVersion(); uint8[] memory _lowerBound = IModuleFactory(_moduleFactory).getLowerSTVersionBounds(); uint8[] memory _upperBound = IModuleFactory(_moduleFactory).getUpperSTVersionBounds(); - bool _isLowerAllowed = VersionUtils.compareLowerBound(_lowerBound, _latestVersion); - bool _isUpperAllowed = VersionUtils.compareUpperBound(_upperBound, _latestVersion); + bool _isLowerAllowed = VersionUtils.lessThanOrEqual(_lowerBound, _latestVersion); + bool _isUpperAllowed = VersionUtils.greaterThanOrEqual(_upperBound, _latestVersion); return (_isLowerAllowed && _isUpperAllowed); } @@ -147,9 +178,15 @@ contract ModuleRegistry is IModuleRegistry, EternalStorage { * @notice Called by the ModuleFactory owner to register new modules for SecurityTokens to use * @param _moduleFactory is the address of the module factory to be registered */ - function registerModule(address _moduleFactory) external whenNotPausedOrOwner { - if (IFeatureRegistry(getAddressValue(Encoder.getKey("featureRegistry"))).getFeatureStatus("customModulesAllowed")) { - require(msg.sender == IOwnable(_moduleFactory).owner() || msg.sender == owner(),"msg.sender must be the Module Factory owner or registry curator"); + function registerModule(address _moduleFactory) external whenNotPausedOrOwner nonReentrant { + address factoryOwner = IOwnable(_moduleFactory).owner(); + // This is set statically to avoid having to call back out to unverified factories to determine owner + set(Encoder.getKey("factoryOwner", _moduleFactory), factoryOwner); + if (_customModules()) { + require( + msg.sender == factoryOwner || msg.sender == owner(), + "msg.sender must be the Module Factory owner or registry curator" + ); } else { require(msg.sender == owner(), "Only owner allowed to register modules"); } @@ -166,14 +203,15 @@ contract ModuleRegistry is IModuleRegistry, EternalStorage { } require(moduleTypes.length != 0, "Factory must have type"); // NB - here we index by the first type of the module. - uint8 moduleType = moduleFactory.getTypes()[0]; + uint8 moduleType = moduleTypes[0]; + require(uint256(moduleType) != 0, "Invalid type"); set(Encoder.getKey("registry", _moduleFactory), uint256(moduleType)); set( Encoder.getKey("moduleListIndex", _moduleFactory), uint256(getArrayAddress(Encoder.getKey("moduleList", uint256(moduleType))).length) ); pushArray(Encoder.getKey("moduleList", uint256(moduleType)), _moduleFactory); - emit ModuleRegistered (_moduleFactory, IOwnable(_moduleFactory).owner()); + emit ModuleRegistered(_moduleFactory, factoryOwner); } /** @@ -185,7 +223,7 @@ contract ModuleRegistry is IModuleRegistry, EternalStorage { require(moduleType != 0, "Module factory should be registered"); require( - msg.sender == IOwnable(_moduleFactory).owner() || msg.sender == owner(), + msg.sender == owner() || msg.sender == getAddressValue(Encoder.getKey("factoryOwner", _moduleFactory)), "msg.sender must be the Module Factory owner or registry curator" ); uint256 index = getUintValue(Encoder.getKey("moduleListIndex", _moduleFactory)); @@ -208,6 +246,8 @@ contract ModuleRegistry is IModuleRegistry, EternalStorage { set(Encoder.getKey("verified", _moduleFactory), false); // delete moduleListIndex[_moduleFactory]; set(Encoder.getKey("moduleListIndex", _moduleFactory), uint256(0)); + // delete module owner + set(Encoder.getKey("factoryOwner", _moduleFactory), address(0)); emit ModuleRemoved(_moduleFactory, msg.sender); } @@ -217,12 +257,29 @@ contract ModuleRegistry is IModuleRegistry, EternalStorage { * @notice (The only exception to this is that the author of the module is the owner of the ST) * @notice -> Only if Polymath enabled the feature. * @param _moduleFactory is the address of the module factory to be verified - * @return bool */ - function verifyModule(address _moduleFactory, bool _verified) external onlyOwner { + function verifyModule(address _moduleFactory) external onlyOwner { require(getUintValue(Encoder.getKey("registry", _moduleFactory)) != uint256(0), "Module factory must be registered"); - set(Encoder.getKey("verified", _moduleFactory), _verified); - emit ModuleVerified(_moduleFactory, _verified); + set(Encoder.getKey("verified", _moduleFactory), true); + emit ModuleVerified(_moduleFactory); + } + + /** + * @notice Called by Polymath to verify Module Factories for SecurityTokens to use. + * @notice A module can not be used by an ST unless first approved/verified by Polymath + * @notice (The only exception to this is that the author of the module is the owner of the ST) + * @notice -> Only if Polymath enabled the feature. + * @param _moduleFactory is the address of the module factory to be verified + */ + function unverifyModule(address _moduleFactory) external nonReentrant { + // Can be called by the registry owner, the module factory, or the module factory owner + bool isOwner = msg.sender == owner(); + bool isFactory = msg.sender == _moduleFactory; + bool isFactoryOwner = msg.sender == getAddressValue(Encoder.getKey("factoryOwner", _moduleFactory)); + require(isOwner || isFactory || isFactoryOwner, "Not authorised"); + require(getUintValue(Encoder.getKey("registry", _moduleFactory)) != uint256(0), "Module factory must be registered"); + set(Encoder.getKey("verified", _moduleFactory), false); + emit ModuleUnverified(_moduleFactory); } /** @@ -232,7 +289,7 @@ contract ModuleRegistry is IModuleRegistry, EternalStorage { * @return list of tags * @return corresponding list of module factories */ - function getTagsByTypeAndToken(uint8 _moduleType, address _securityToken) external view returns(bytes32[], address[]) { + function getTagsByTypeAndToken(uint8 _moduleType, address _securityToken) external view returns(bytes32[] memory, address[] memory) { address[] memory modules = getModulesByTypeAndToken(_moduleType, _securityToken); return _tagsByModules(modules); } @@ -243,7 +300,7 @@ contract ModuleRegistry is IModuleRegistry, EternalStorage { * @return list of tags * @return corresponding list of module factories */ - function getTagsByType(uint8 _moduleType) external view returns(bytes32[], address[]) { + function getTagsByType(uint8 _moduleType) external view returns(bytes32[] memory, address[] memory) { address[] memory modules = getModulesByType(_moduleType); return _tagsByModules(modules); } @@ -254,7 +311,7 @@ contract ModuleRegistry is IModuleRegistry, EternalStorage { * @return list of tags * @return corresponding list of module factories */ - function _tagsByModules(address[] _modules) internal view returns(bytes32[], address[]) { + function _tagsByModules(address[] memory _modules) internal view returns(bytes32[] memory, address[] memory) { uint256 counter = 0; uint256 i; uint256 j; @@ -277,20 +334,48 @@ contract ModuleRegistry is IModuleRegistry, EternalStorage { } /** - * @notice Returns the reputation of the entered Module Factory + * @notice Returns the verified status, and reputation of the entered Module Factory * @param _factoryAddress is the address of the module factory + * @return bool indicating whether module factory is verified + * @return address of the factory owner * @return address array which contains the list of securityTokens that use that module factory */ - function getReputationByFactory(address _factoryAddress) external view returns(address[]) { - return getArrayAddress(Encoder.getKey("reputation", _factoryAddress)); + function getFactoryDetails(address _factoryAddress) external view returns(bool, address, address[] memory) { + return (getBoolValue(Encoder.getKey("verified", _factoryAddress)), getAddressValue(Encoder.getKey("factoryOwner", _factoryAddress)), getArrayAddress(Encoder.getKey("reputation", _factoryAddress))); } /** - * @notice Returns the list of addresses of Module Factory of a particular type + * @notice Returns the list of addresses of verified Module Factory of a particular type * @param _moduleType Type of Module * @return address array that contains the list of addresses of module factory contracts. */ - function getModulesByType(uint8 _moduleType) public view returns(address[]) { + function getModulesByType(uint8 _moduleType) public view returns(address[] memory) { + address[] memory _addressList = getArrayAddress(Encoder.getKey("moduleList", uint256(_moduleType))); + uint256 _len = _addressList.length; + uint256 counter = 0; + for (uint256 i = 0; i < _len; i++) { + if (getBoolValue(Encoder.getKey("verified", _addressList[i]))) { + counter++; + } + } + address[] memory _tempArray = new address[](counter); + counter = 0; + for (uint256 j = 0; j < _len; j++) { + if (getBoolValue(Encoder.getKey("verified", _addressList[j]))) { + _tempArray[counter] = _addressList[j]; + counter++; + } + } + return _tempArray; + } + + + /** + * @notice Returns the list of addresses of all Module Factory of a particular type + * @param _moduleType Type of Module + * @return address array that contains the list of addresses of module factory contracts. + */ + function getAllModulesByType(uint8 _moduleType) external view returns(address[] memory) { return getArrayAddress(Encoder.getKey("moduleList", uint256(_moduleType))); } @@ -300,37 +385,36 @@ contract ModuleRegistry is IModuleRegistry, EternalStorage { * @param _securityToken is the address of SecurityToken * @return address array that contains the list of available addresses of module factory contracts. */ - function getModulesByTypeAndToken(uint8 _moduleType, address _securityToken) public view returns (address[]) { - uint256 _len = getArrayAddress(Encoder.getKey("moduleList", uint256(_moduleType))).length; + function getModulesByTypeAndToken(uint8 _moduleType, address _securityToken) public view returns(address[] memory) { address[] memory _addressList = getArrayAddress(Encoder.getKey("moduleList", uint256(_moduleType))); - bool _isCustomModuleAllowed = IFeatureRegistry(getAddressValue(Encoder.getKey("featureRegistry"))).getFeatureStatus("customModulesAllowed"); + uint256 _len = _addressList.length; + bool _isCustomModuleAllowed = _customModules(); uint256 counter = 0; for (uint256 i = 0; i < _len; i++) { if (_isCustomModuleAllowed) { - if (IOwnable(_addressList[i]).owner() == IOwnable(_securityToken).owner() || getBoolValue(Encoder.getKey("verified", _addressList[i]))) - if(_isCompatibleModule(_addressList[i], _securityToken)) - counter++; - } - else if (getBoolValue(Encoder.getKey("verified", _addressList[i]))) { - if(_isCompatibleModule(_addressList[i], _securityToken)) - counter++; + if (getBoolValue( + Encoder.getKey("verified", _addressList[i])) || getAddressValue(Encoder.getKey("factoryOwner", _addressList[i])) == IOwnable(_securityToken).owner() + ) if (isCompatibleModule(_addressList[i], _securityToken)) counter++; + } else if (getBoolValue(Encoder.getKey("verified", _addressList[i]))) { + if (isCompatibleModule(_addressList[i], _securityToken)) counter++; } } address[] memory _tempArray = new address[](counter); counter = 0; for (uint256 j = 0; j < _len; j++) { if (_isCustomModuleAllowed) { - if (IOwnable(_addressList[j]).owner() == IOwnable(_securityToken).owner() || getBoolValue(Encoder.getKey("verified", _addressList[j]))) { - if(_isCompatibleModule(_addressList[j], _securityToken)) { + if (getAddressValue(Encoder.getKey("factoryOwner", _addressList[j])) == IOwnable(_securityToken).owner() || getBoolValue( + Encoder.getKey("verified", _addressList[j]) + )) { + if (isCompatibleModule(_addressList[j], _securityToken)) { _tempArray[counter] = _addressList[j]; - counter ++; + counter++; } } - } - else if (getBoolValue(Encoder.getKey("verified", _addressList[j]))) { - if(_isCompatibleModule(_addressList[j], _securityToken)) { + } else if (getBoolValue(Encoder.getKey("verified", _addressList[j]))) { + if (isCompatibleModule(_addressList[j], _securityToken)) { _tempArray[counter] = _addressList[j]; - counter ++; + counter++; } } } @@ -345,35 +429,35 @@ contract ModuleRegistry is IModuleRegistry, EternalStorage { require(_tokenContract != address(0), "0x address is invalid"); IERC20 token = IERC20(_tokenContract); uint256 balance = token.balanceOf(address(this)); - require(token.transfer(owner(), balance),"token transfer failed"); + require(token.transfer(owner(), balance), "token transfer failed"); } /** * @notice Called by the owner to pause, triggers stopped state */ function pause() external whenNotPaused onlyOwner { - set(Encoder.getKey("paused"), true); + set(PAUSED, true); /*solium-disable-next-line security/no-block-members*/ - emit Pause(now); + emit Pause(msg.sender); } /** * @notice Called by the owner to unpause, returns to normal state */ function unpause() external whenPaused onlyOwner { - set(Encoder.getKey("paused"), false); + set(PAUSED, false); /*solium-disable-next-line security/no-block-members*/ - emit Unpause(now); + emit Unpause(msg.sender); } /** * @notice Stores the contract addresses of other key contracts from the PolymathRegistry */ function updateFromRegistry() external onlyOwner { - address _polymathRegistry = getAddressValue(Encoder.getKey("polymathRegistry")); - set(Encoder.getKey("securityTokenRegistry"), IPolymathRegistry(_polymathRegistry).getAddress("SecurityTokenRegistry")); - set(Encoder.getKey("featureRegistry"), IPolymathRegistry(_polymathRegistry).getAddress("FeatureRegistry")); - set(Encoder.getKey("polyToken"), IPolymathRegistry(_polymathRegistry).getAddress("PolyToken")); + address _polymathRegistry = getAddressValue(POLYMATHREGISTRY); + set(SECURITY_TOKEN_REGISTRY, IPolymathRegistry(_polymathRegistry).getAddress("SecurityTokenRegistry")); + set(FEATURE_REGISTRY, IPolymathRegistry(_polymathRegistry).getAddress("FeatureRegistry")); + set(POLYTOKEN, IPolymathRegistry(_polymathRegistry).getAddress("PolyToken")); } /** @@ -383,7 +467,7 @@ contract ModuleRegistry is IModuleRegistry, EternalStorage { function transferOwnership(address _newOwner) external onlyOwner { require(_newOwner != address(0), "Invalid address"); emit OwnershipTransferred(owner(), _newOwner); - set(Encoder.getKey("owner"), _newOwner); + set(OWNER, _newOwner); } /** @@ -391,7 +475,7 @@ contract ModuleRegistry is IModuleRegistry, EternalStorage { * @return address owner */ function owner() public view returns(address) { - return getAddressValue(Encoder.getKey("owner")); + return getAddressValue(OWNER); } /** @@ -399,6 +483,6 @@ contract ModuleRegistry is IModuleRegistry, EternalStorage { * @return bool */ function isPaused() public view returns(bool) { - return getBoolValue(Encoder.getKey("paused")); + return getBoolValue(PAUSED); } } diff --git a/contracts/Pausable.sol b/contracts/Pausable.sol index fe4a3ca28..717423b02 100644 --- a/contracts/Pausable.sol +++ b/contracts/Pausable.sol @@ -1,12 +1,11 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; /** * @title Utility contract to allow pausing and unpausing of certain functions */ contract Pausable { - - event Pause(uint256 _timestammp); - event Unpause(uint256 _timestamp); + event Pause(address account); + event Unpause(address account); bool public paused = false; @@ -26,13 +25,13 @@ contract Pausable { _; } - /** + /** * @notice Called by the owner to pause, triggers stopped state */ function _pause() internal whenNotPaused { paused = true; /*solium-disable-next-line security/no-block-members*/ - emit Pause(now); + emit Pause(msg.sender); } /** @@ -41,7 +40,7 @@ contract Pausable { function _unpause() internal whenPaused { paused = false; /*solium-disable-next-line security/no-block-members*/ - emit Unpause(now); + emit Unpause(msg.sender); } } diff --git a/contracts/PolymathRegistry.sol b/contracts/PolymathRegistry.sol index 0d6a6ac92..0c0fc9220 100644 --- a/contracts/PolymathRegistry.sol +++ b/contracts/PolymathRegistry.sol @@ -1,24 +1,22 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; import "./ReclaimTokens.sol"; +import "./interfaces/IPolymathRegistry.sol"; /** * @title Core functionality for registry upgradability */ -contract PolymathRegistry is ReclaimTokens { - - mapping (bytes32 => address) public storedAddresses; - - event ChangeAddress(string _nameKey, address indexed _oldAddress, address indexed _newAddress); +contract PolymathRegistry is ReclaimTokens, IPolymathRegistry { + mapping(bytes32 => address) public storedAddresses; /** * @notice Gets the contract address * @param _nameKey is the key for the contract address mapping * @return address */ - function getAddress(string _nameKey) external view returns(address) { + function getAddress(string calldata _nameKey) external view returns(address) { bytes32 key = keccak256(bytes(_nameKey)); - require(storedAddresses[key] != address(0), "Invalid address key"); + require(storedAddresses[key] != address(0), "Invalid key"); return storedAddresses[key]; } @@ -27,11 +25,10 @@ contract PolymathRegistry is ReclaimTokens { * @param _nameKey is the key for the contract address mapping * @param _newAddress is the new contract address */ - function changeAddress(string _nameKey, address _newAddress) external onlyOwner { + function changeAddress(string calldata _nameKey, address _newAddress) external onlyOwner { bytes32 key = keccak256(bytes(_nameKey)); emit ChangeAddress(_nameKey, storedAddresses[key], _newAddress); storedAddresses[key] = _newAddress; } - } diff --git a/contracts/ReclaimTokens.sol b/contracts/ReclaimTokens.sol index 767442829..f14171027 100644 --- a/contracts/ReclaimTokens.sol +++ b/contracts/ReclaimTokens.sol @@ -1,13 +1,12 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; -import "./interfaces/IERC20.sol"; +import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; /** * @title Utility contract to allow owner to retreive any ERC20 sent to the contract */ contract ReclaimTokens is Ownable { - /** * @notice Reclaim all ERC20Basic compatible tokens * @param _tokenContract The address of the token contract @@ -16,6 +15,6 @@ contract ReclaimTokens is Ownable { require(_tokenContract != address(0), "Invalid address"); IERC20 token = IERC20(_tokenContract); uint256 balance = token.balanceOf(address(this)); - require(token.transfer(owner, balance), "Transfer failed"); + require(token.transfer(owner(), balance), "Transfer failed"); } } diff --git a/contracts/RegistryUpdater.sol b/contracts/RegistryUpdater.sol deleted file mode 100644 index ee325b1cf..000000000 --- a/contracts/RegistryUpdater.sol +++ /dev/null @@ -1,26 +0,0 @@ -pragma solidity ^0.4.24; - -import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; -import "./PolymathRegistry.sol"; - -contract RegistryUpdater is Ownable { - - address public polymathRegistry; - address public moduleRegistry; - address public securityTokenRegistry; - address public featureRegistry; - address public polyToken; - - constructor (address _polymathRegistry) public { - require(_polymathRegistry != address(0), "Invalid address"); - polymathRegistry = _polymathRegistry; - } - - function updateFromRegistry() public onlyOwner { - moduleRegistry = PolymathRegistry(polymathRegistry).getAddress("ModuleRegistry"); - securityTokenRegistry = PolymathRegistry(polymathRegistry).getAddress("SecurityTokenRegistry"); - featureRegistry = PolymathRegistry(polymathRegistry).getAddress("FeatureRegistry"); - polyToken = PolymathRegistry(polymathRegistry).getAddress("PolyToken"); - } - -} diff --git a/contracts/STRGetter.sol b/contracts/STRGetter.sol new file mode 100644 index 000000000..34dbfb262 --- /dev/null +++ b/contracts/STRGetter.sol @@ -0,0 +1,272 @@ +pragma solidity 0.5.8; + +import "./storage/EternalStorage.sol"; +import "./interfaces/ISecurityToken.sol"; +import "./libraries/Util.sol"; +import "./libraries/Encoder.sol"; +import "./interfaces/IOwnable.sol"; +import "./libraries/VersionUtils.sol"; +import "./interfaces/ISecurityToken.sol"; +import "./modules/PermissionManager/IPermissionManager.sol"; + +contract STRGetter is EternalStorage { + + bytes32 constant STLAUNCHFEE = 0xd677304bb45536bb7fdfa6b9e47a3c58fe413f9e8f01474b0a4b9c6e0275baf2; + bytes32 constant TICKERREGFEE = 0x2fcc69711628630fb5a42566c68bd1092bc4aa26826736293969fddcd11cb2d2; + bytes32 constant EXPIRYLIMIT = 0x604268e9a73dfd777dcecb8a614493dd65c638bad2f5e7d709d378bd2fb0baee; + bytes32 constant IS_FEE_IN_POLY = 0x7152e5426955da44af11ecd67fec5e2a3ba747be974678842afa9394b9a075b6; //keccak256("IS_FEE_IN_POLY") + + /** + * @notice Returns the list of tickers owned by the selected address + * @param _owner is the address which owns the list of tickers + */ + function getTickersByOwner(address _owner) external view returns(bytes32[] memory) { + uint256 count = 0; + // accessing the data structure userTotickers[_owner].length + bytes32[] memory tickers = getArrayBytes32(Encoder.getKey("userToTickers", _owner)); + uint i; + for (i = 0; i < tickers.length; i++) { + if (_ownerInTicker(tickers[i])) { + count++; + } + } + bytes32[] memory result = new bytes32[](count); + count = 0; + for (i = 0; i < tickers.length; i++) { + if (_ownerInTicker(tickers[i])) { + result[count] = tickers[i]; + count++; + } + } + return result; + } + + function _ownerInTicker(bytes32 _ticker) internal view returns (bool) { + string memory ticker = Util.bytes32ToString(_ticker); + /*solium-disable-next-line security/no-block-members*/ + if (getUintValue(Encoder.getKey("registeredTickers_expiryDate", ticker)) >= now || getBoolValue(Encoder.getKey("registeredTickers_status", ticker))) { + return true; + } + return false; + } + + /** + * @notice Returns the list of tokens owned by the selected address + * @param _owner is the address which owns the list of tickers + * @dev Intention is that this is called off-chain so block gas limit is not relevant + */ + function getTokensByOwner(address _owner) external view returns(address[] memory) { + return _getTokens(false, _owner); + } + + /** + * @notice Returns the list of all tokens + * @dev Intention is that this is called off-chain so block gas limit is not relevant + */ + function getTokens() public view returns(address[] memory) { + return _getTokens(true, address(0)); + } + /** + * @notice Returns the list of tokens owned by the selected address + * @param _allTokens if _allTokens is true returns all tokens despite on the second parameter + * @param _owner is the address which owns the list of tickers + */ + function _getTokens(bool _allTokens, address _owner) internal view returns(address[] memory) { + // Loop over all active users, then all associated tickers of those users + // This ensures we find tokens, even if their owner has been modified + address[] memory activeUsers = getArrayAddress(Encoder.getKey("activeUsers")); + bytes32[] memory tickers; + uint256 count = 0; + uint256 i = 0; + uint256 j = 0; + for (i = 0; i < activeUsers.length; i++) { + tickers = getArrayBytes32(Encoder.getKey("userToTickers", activeUsers[i])); + for (j = 0; j < tickers.length; j++) { + if (address(0) != _ownerInToken(tickers[j], _allTokens, _owner)) { + count++; + } + } + } + address[] memory result = new address[](count); + count = 0; + address token; + for (i = 0; i < activeUsers.length; i++) { + tickers = getArrayBytes32(Encoder.getKey("userToTickers", activeUsers[i])); + for (j = 0; j < tickers.length; j++) { + token = _ownerInToken(tickers[j], _allTokens, _owner); + if (address(0) != token) { + result[count] = token; + count++; + } + } + } + return result; + } + + function _ownerInToken(bytes32 _ticker, bool _allTokens, address _owner) internal view returns(address) { + address token = getAddressValue(Encoder.getKey("tickerToSecurityToken", Util.bytes32ToString(_ticker))); + if (token != address(0)) { + if (_allTokens || IOwnable(token).owner() == _owner) { + return token; + } + } + return address(0); + } + + /** + * @notice Returns the list of tokens to which the delegate has some access + * @param _delegate is the address for the delegate + * @dev Intention is that this is called off-chain so block gas limit is not relevant + */ + function getTokensByDelegate(address _delegate) external view returns(address[] memory) { + // Loop over all active users, then all associated tickers of those users + // This ensures we find tokens, even if their owner has been modified + address[] memory tokens = getTokens(); + uint256 count = 0; + uint256 i = 0; + for (i = 0; i < tokens.length; i++) { + if (_delegateInToken(tokens[i], _delegate)) { + count++; + } + } + address[] memory result = new address[](count); + count = 0; + for (i = 0; i < tokens.length; i++) { + if (_delegateInToken(tokens[i], _delegate)) { + result[count] = tokens[i]; + count++; + } + } + return result; + } + + function _delegateInToken(address _token, address _delegate) internal view returns(bool) { + uint256 j = 0; + address[] memory permissionManagers; + bool isArchived; + permissionManagers = ISecurityToken(_token).getModulesByType(1); + for (j = 0; j < permissionManagers.length; j++) { + (,,, isArchived,,) = ISecurityToken(_token).getModule(permissionManagers[j]); + if (!isArchived) { + if (IPermissionManager(permissionManagers[j]).checkDelegate(_delegate)) { + return true; + } + } + } + return false; + } + + /** + * @notice Returns the owner and timestamp for a given ticker + * @param _ticker is the ticker symbol + * @return address + * @return uint256 + * @return uint256 + * @return string + * @return bool + */ + function getTickerDetails(string calldata _ticker) external view returns (address, uint256, uint256, string memory, bool) { + string memory ticker = Util.upper(_ticker); + bool tickerStatus = getTickerStatus(ticker); + uint256 expiryDate = getUintValue(Encoder.getKey("registeredTickers_expiryDate", ticker)); + /*solium-disable-next-line security/no-block-members*/ + if ((tickerStatus == true) || (expiryDate > now)) { + address stAddress = getAddressValue(Encoder.getKey("tickerToSecurityToken", ticker)); + string memory tokenName = stAddress == address(0) ? "" : ISecurityToken(stAddress).name(); + return + ( + getTickerOwner(ticker), + getUintValue(Encoder.getKey("registeredTickers_registrationDate", ticker)), + expiryDate, + tokenName, + tickerStatus + ); + } else { + return (address(0), uint256(0), uint256(0), "", false); + } + } + + /** + * @notice Returns the security token address by ticker symbol + * @param _ticker is the ticker of the security token + * @return address + */ + function getSecurityTokenAddress(string calldata _ticker) external view returns (address) { + string memory ticker = Util.upper(_ticker); + return getAddressValue(Encoder.getKey("tickerToSecurityToken", ticker)); + } + + /** + * @notice Returns the security token data by address + * @param _securityToken is the address of the security token. + * @return string is the ticker of the security Token. + * @return address is the issuer of the security Token. + * @return string is the details of the security token. + * @return uint256 is the timestamp at which security Token was deployed. + */ + function getSecurityTokenData(address _securityToken) external view returns (string memory, address, string memory, uint256) { + return ( + getStringValue(Encoder.getKey("securityTokens_ticker", _securityToken)), + IOwnable(_securityToken).owner(), + getStringValue(Encoder.getKey("securityTokens_tokenDetails", _securityToken)), + getUintValue(Encoder.getKey("securityTokens_deployedAt", _securityToken)) + ); + } + + /** + * @notice Returns the current STFactory Address + */ + function getSTFactoryAddress() public view returns(address) { + return getAddressValue(Encoder.getKey("protocolVersionST", getUintValue(Encoder.getKey("latestVersion")))); + } + + /** + * @notice Returns the STFactory Address of a particular version + * @param _protocolVersion Packed protocol version + */ + function getSTFactoryAddressOfVersion(uint256 _protocolVersion) public view returns(address) { + return getAddressValue(Encoder.getKey("protocolVersionST", _protocolVersion)); + } + + /** + * @notice Gets Protocol version + */ + function getLatestProtocolVersion() public view returns(uint8[] memory) { + return VersionUtils.unpack(uint24(getUintValue(Encoder.getKey("latestVersion")))); + } + + /** + * @notice Gets the fee currency + * @return true = poly, false = usd + */ + function getIsFeeInPoly() public view returns(bool) { + return getBoolValue(IS_FEE_IN_POLY); + } + + /** + * @notice Gets the expiry limit + * @return Expiry limit + */ + function getExpiryLimit() public view returns(uint256) { + return getUintValue(EXPIRYLIMIT); + } + + /** + * @notice Gets the status of the ticker + * @param _ticker Ticker whose status need to determine + * @return bool + */ + function getTickerStatus(string memory _ticker) public view returns(bool) { + return getBoolValue(Encoder.getKey("registeredTickers_status", _ticker)); + } + + /** + * @notice Gets the owner of the ticker + * @param _ticker Ticker whose owner need to determine + * @return address Address of the owner + */ + function getTickerOwner(string memory _ticker) public view returns(address) { + return getAddressValue(Encoder.getKey("registeredTickers_owner", _ticker)); + } + +} diff --git a/contracts/SecurityTokenRegistry.sol b/contracts/SecurityTokenRegistry.sol index 81f74abde..222a6e053 100644 --- a/contracts/SecurityTokenRegistry.sol +++ b/contracts/SecurityTokenRegistry.sol @@ -1,19 +1,31 @@ -pragma solidity ^0.4.24; +/** + // + IMPORTANT: Developer should update the ISecurityTokenRegistry.sol (Interface) if there is any change in + function signature or addition/removal of the functions from SecurityTokenRegistry & STRGetter contract. + // + + */ + +pragma solidity ^0.5.0; import "openzeppelin-solidity/contracts/math/SafeMath.sol"; +import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; import "./interfaces/IOwnable.sol"; import "./interfaces/ISTFactory.sol"; -import "./interfaces/IERC20.sol"; -import "./interfaces/ISecurityTokenRegistry.sol"; +import "./interfaces/ISecurityToken.sol"; +import "./interfaces/IPolymathRegistry.sol"; +import "./interfaces/IOracle.sol"; import "./storage/EternalStorage.sol"; import "./libraries/Util.sol"; import "./libraries/Encoder.sol"; import "./libraries/VersionUtils.sol"; +import "./libraries/DecimalMath.sol"; +import "./proxy/Proxy.sol"; /** * @title Registry contract for issuers to register their tickers and security tokens */ -contract SecurityTokenRegistry is ISecurityTokenRegistry, EternalStorage { +contract SecurityTokenRegistry is EternalStorage, Proxy { /** * @notice state variables @@ -48,7 +60,7 @@ contract SecurityTokenRegistry is ISecurityTokenRegistry, EternalStorage { address owner; uint256 registrationDate; uint256 expiryDate; - string tokenName; + string tokenName; //Not stored since 3.0.0 bool status; } @@ -62,32 +74,54 @@ contract SecurityTokenRegistry is ISecurityTokenRegistry, EternalStorage { using SafeMath for uint256; - bytes32 constant INITIALIZE = 0x9ef7257c3339b099aacf96e55122ee78fb65a36bd2a6c19249882be9c98633bf; - bytes32 constant POLYTOKEN = 0xacf8fbd51bb4b83ba426cdb12f63be74db97c412515797993d2a385542e311d7; - bytes32 constant STLAUNCHFEE = 0xd677304bb45536bb7fdfa6b9e47a3c58fe413f9e8f01474b0a4b9c6e0275baf2; - bytes32 constant TICKERREGFEE = 0x2fcc69711628630fb5a42566c68bd1092bc4aa26826736293969fddcd11cb2d2; - bytes32 constant EXPIRYLIMIT = 0x604268e9a73dfd777dcecb8a614493dd65c638bad2f5e7d709d378bd2fb0baee; - bytes32 constant PAUSED = 0xee35723ac350a69d2a92d3703f17439cbaadf2f093a21ba5bf5f1a53eb2a14d9; - bytes32 constant OWNER = 0x02016836a56b71f0d02689e69e326f4f4c1b9057164ef592671cf0d37c8040c0; - bytes32 constant POLYMATHREGISTRY = 0x90eeab7c36075577c7cc5ff366e389fefa8a18289b949bab3529ab4471139d4d; + bytes32 constant INITIALIZE = 0x9ef7257c3339b099aacf96e55122ee78fb65a36bd2a6c19249882be9c98633bf; //keccak256("initialised") + bytes32 constant POLYTOKEN = 0xacf8fbd51bb4b83ba426cdb12f63be74db97c412515797993d2a385542e311d7; //keccak256("polyToken") + bytes32 constant STLAUNCHFEE = 0xd677304bb45536bb7fdfa6b9e47a3c58fe413f9e8f01474b0a4b9c6e0275baf2; //keccak256("stLaunchFee") + bytes32 constant TICKERREGFEE = 0x2fcc69711628630fb5a42566c68bd1092bc4aa26826736293969fddcd11cb2d2; //keccak256("tickerRegFee") + bytes32 constant EXPIRYLIMIT = 0x604268e9a73dfd777dcecb8a614493dd65c638bad2f5e7d709d378bd2fb0baee; //keccak256("expiryLimit") + bytes32 constant PAUSED = 0xee35723ac350a69d2a92d3703f17439cbaadf2f093a21ba5bf5f1a53eb2a14d9; //keccak256("paused") + bytes32 constant OWNER = 0x02016836a56b71f0d02689e69e326f4f4c1b9057164ef592671cf0d37c8040c0; //keccak256("owner") + bytes32 constant POLYMATHREGISTRY = 0x90eeab7c36075577c7cc5ff366e389fefa8a18289b949bab3529ab4471139d4d; //keccak256("polymathRegistry") + bytes32 constant STRGETTER = 0x982f24b3bd80807ec3cb227ba152e15c07d66855fa8ae6ca536e689205c0e2e9; //keccak256("STRGetter") + bytes32 constant IS_FEE_IN_POLY = 0x7152e5426955da44af11ecd67fec5e2a3ba747be974678842afa9394b9a075b6; //keccak256("IS_FEE_IN_POLY") + bytes32 constant ACTIVE_USERS = 0x425619ce6ba8e9f80f17c0ef298b6557e321d70d7aeff2e74dd157bd87177a9e; //keccak256("activeUsers") + bytes32 constant LATEST_VERSION = 0x4c63b69b9117452b9f11af62077d0cda875fb4e2dbe07ad6f31f728de6926230; //keccak256("latestVersion") + + string constant POLY_ORACLE = "StablePolyUsdOracle"; // Emit when network becomes paused - event Pause(uint256 _timestammp); - // Emit when network becomes unpaused - event Unpause(uint256 _timestamp); + event Pause(address account); + // Emit when network becomes unpaused + event Unpause(address account); // Emit when the ticker is removed from the registry - event TickerRemoved(string _ticker, uint256 _removedAt, address _removedBy); + event TickerRemoved(string _ticker, address _removedBy); // Emit when the token ticker expiry is changed event ChangeExpiryLimit(uint256 _oldExpiry, uint256 _newExpiry); - // Emit when changeSecurityLaunchFee is called + // Emit when changeSecurityLaunchFee is called event ChangeSecurityLaunchFee(uint256 _oldFee, uint256 _newFee); // Emit when changeTickerRegistrationFee is called event ChangeTickerRegistrationFee(uint256 _oldFee, uint256 _newFee); + // Emit when Fee currency is changed + event ChangeFeeCurrency(bool _isFeeInPoly); // Emit when ownership gets transferred event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); // Emit when ownership of the ticker gets changed event ChangeTickerOwnership(string _ticker, address indexed _oldOwner, address indexed _newOwner); - // Emit at the time of launching a new security token + // Emit at the time of launching a new security token of version 3.0+ + event NewSecurityToken( + string _ticker, + string _name, + address indexed _securityTokenAddress, + address indexed _owner, + uint256 _addedAt, + address _registrant, + bool _fromAdmin, + uint256 _usdFee, + uint256 _polyFee, + uint256 _protocolVersion + ); + // Emit at the time of launching a new security token v2.0. + // _registrationFee is in poly event NewSecurityToken( string _ticker, string _name, @@ -99,6 +133,16 @@ contract SecurityTokenRegistry is ISecurityTokenRegistry, EternalStorage { uint256 _registrationFee ); // Emit after ticker registration + event RegisterTicker( + address indexed _owner, + string _ticker, + uint256 indexed _registrationDate, + uint256 indexed _expiryDate, + bool _fromAdmin, + uint256 _registrationFeePoly, + uint256 _registrationFeeUsd + ); + // For backwards compatibility event RegisterTicker( address indexed _owner, string _ticker, @@ -108,7 +152,19 @@ contract SecurityTokenRegistry is ISecurityTokenRegistry, EternalStorage { bool _fromAdmin, uint256 _registrationFee ); - + // Emit at when issuer refreshes exisiting token + event SecurityTokenRefreshed( + string _ticker, + string _name, + address indexed _securityTokenAddress, + address indexed _owner, + uint256 _addedAt, + address _registrant, + uint256 _protocolVersion + ); + event ProtocolFactorySet(address indexed _STFactory, uint8 _major, uint8 _minor, uint8 _patch); + event LatestVersionSet(uint8 _major, uint8 _minor, uint8 _patch); + event ProtocolFactoryRemoved(address indexed _STFactory, uint8 _major, uint8 _minor, uint8 _patch); ///////////////////////////// // Modifiers ///////////////////////////// @@ -117,7 +173,16 @@ contract SecurityTokenRegistry is ISecurityTokenRegistry, EternalStorage { * @dev Throws if called by any account other than the owner. */ modifier onlyOwner() { - require(msg.sender == owner(),"sender must be owner"); + _onlyOwner(); + _; + } + + function _onlyOwner() internal view { + require(msg.sender == owner(), "Only owner"); + } + + modifier onlyOwnerOrSelf() { + require(msg.sender == owner() || msg.sender == address(this), "Only owner or self"); _; } @@ -125,11 +190,13 @@ contract SecurityTokenRegistry is ISecurityTokenRegistry, EternalStorage { * @notice Modifier to make a function callable only when the contract is not paused. */ modifier whenNotPausedOrOwner() { - if (msg.sender == owner()) - _; - else { - require(!isPaused(), "Already paused"); - _; + _whenNotPausedOrOwner(); + _; + } + + function _whenNotPausedOrOwner() internal view { + if (msg.sender != owner()) { + require(!isPaused(), "Paused"); } } @@ -137,59 +204,127 @@ contract SecurityTokenRegistry is ISecurityTokenRegistry, EternalStorage { * @notice Modifier to make a function callable only when the contract is not paused and ignore is msg.sender is owner. */ modifier whenNotPaused() { - require(!isPaused(), "Already paused"); + require(!isPaused(), "Paused"); _; } - /** * @notice Modifier to make a function callable only when the contract is paused. */ modifier whenPaused() { - require(isPaused(), "Should not be paused"); + require(isPaused(), "Not paused"); _; } - ///////////////////////////// // Initialization ///////////////////////////// + // Constructor + constructor() public { + set(INITIALIZE, true); + } + /** * @notice Initializes instance of STR * @param _polymathRegistry is the address of the Polymath Registry - * @param _STFactory is the address of the Proxy contract for Security Tokens - * @param _stLaunchFee is the fee in POLY required to launch a token - * @param _tickerRegFee is the fee in POLY required to register a ticker - * @param _polyToken is the address of the POLY ERC20 token - * @param _owner is the owner of the STR + * @param _stLaunchFee is the fee in USD required to launch a token + * @param _tickerRegFee is the fee in USD required to register a ticker + * @param _owner is the owner of the STR, + * @param _getterContract Contract address of the contract which consists getter functions. */ function initialize( address _polymathRegistry, - address _STFactory, uint256 _stLaunchFee, uint256 _tickerRegFee, - address _polyToken, - address _owner + address _owner, + address _getterContract ) - external - payable + public { - require(!getBoolValue(INITIALIZE),"already initialized"); + require(!getBoolValue(INITIALIZE),"Initialized"); require( - _STFactory != address(0) && _polyToken != address(0) && _owner != address(0) && _polymathRegistry != address(0), + _owner != address(0) && _polymathRegistry != address(0) && _getterContract != address(0), "Invalid address" ); - require(_stLaunchFee != 0 && _tickerRegFee != 0, "Fees should not be 0"); - set(POLYTOKEN, _polyToken); set(STLAUNCHFEE, _stLaunchFee); set(TICKERREGFEE, _tickerRegFee); set(EXPIRYLIMIT, uint256(60 * 1 days)); set(PAUSED, false); set(OWNER, _owner); set(POLYMATHREGISTRY, _polymathRegistry); - _setProtocolVersion(_STFactory, uint8(2), uint8(0), uint8(0)); set(INITIALIZE, true); + set(STRGETTER, _getterContract); + _updateFromRegistry(); + } + + /** + * @notice Used to update the polyToken contract address + */ + function updateFromRegistry() external onlyOwner { + _updateFromRegistry(); + } + + function _updateFromRegistry() internal { + address polymathRegistry = getAddressValue(POLYMATHREGISTRY); + set(POLYTOKEN, IPolymathRegistry(polymathRegistry).getAddress("PolyToken")); + } + + /** + * @notice Converts USD fees into POLY amounts + */ + function _takeFee(bytes32 _feeType) internal returns (uint256, uint256) { + (uint256 usdFee, uint256 polyFee) = getFees(_feeType); + if (polyFee > 0) + require(IERC20(getAddressValue(POLYTOKEN)).transferFrom(msg.sender, address(this), polyFee), "Insufficent allowance"); + return (usdFee, polyFee); + } + + /** + * @notice Returns the usd & poly fee for a particular feetype + * @param _feeType Key corresponding to fee type + */ + function getFees(bytes32 _feeType) public returns (uint256 usdFee, uint256 polyFee) { + bool isFeesInPoly = getBoolValue(IS_FEE_IN_POLY); + uint256 rawFee = getUintValue(_feeType); + address polymathRegistry = getAddressValue(POLYMATHREGISTRY); + uint256 polyRate = IOracle(IPolymathRegistry(polymathRegistry).getAddress(POLY_ORACLE)).getPrice(); + if (!isFeesInPoly) { //Fee is in USD and not poly + usdFee = rawFee; + polyFee = DecimalMath.div(rawFee, polyRate); + } else { + usdFee = DecimalMath.mul(rawFee, polyRate); + polyFee = rawFee; + } + } + + /** + * @notice Gets the security token launch fee + * @return Fee amount + */ + function getSecurityTokenLaunchFee() public returns(uint256 polyFee) { + (, polyFee) = getFees(STLAUNCHFEE); + } + + /** + * @notice Gets the ticker registration fee + * @return Fee amount + */ + function getTickerRegistrationFee() public returns(uint256 polyFee) { + (, polyFee) = getFees(TICKERREGFEE); + } + + /** + * @notice Set the getter contract address + * @param _getterContract Address of the contract + */ + function setGetterRegistry(address _getterContract) public onlyOwnerOrSelf { + require(_getterContract != address(0)); + set(STRGETTER, _getterContract); + } + + function _implementation() internal view returns(address) { + return getAddressValue(STRGETTER); } ///////////////////////////// @@ -202,42 +337,50 @@ contract SecurityTokenRegistry is ISecurityTokenRegistry, EternalStorage { * @notice its ownership. If the ticker expires and its issuer hasn't used it, then someone else can take it. * @param _owner is address of the owner of the token * @param _ticker is unique token ticker - * @param _tokenName is the name of the token - */ - function registerTicker(address _owner, string _ticker, string _tokenName) external whenNotPausedOrOwner { - require(_owner != address(0), "Owner should not be 0x"); - require(bytes(_ticker).length > 0 && bytes(_ticker).length <= 10, "Ticker length range (0,10]"); - // Attempt to charge the reg fee if it is > 0 POLY - uint256 tickerFee = getTickerRegistrationFee(); - if (tickerFee > 0) - require(IERC20(getAddressValue(POLYTOKEN)).transferFrom(msg.sender, address(this), tickerFee), "Insufficent allowance"); + */ + function registerNewTicker(address _owner, string memory _ticker) public whenNotPausedOrOwner { + require(_owner != address(0), "Bad address"); + require(bytes(_ticker).length > 0 && bytes(_ticker).length <= 10, "Bad ticker"); + // Attempt to charge the reg fee if it is > 0 USD + (uint256 usdFee, uint256 polyFee) = _takeFee(TICKERREGFEE); string memory ticker = Util.upper(_ticker); - require(_tickerAvailable(ticker), "Ticker is reserved"); + require(tickerAvailable(ticker), "Ticker reserved"); // Check whether ticker was previously registered (and expired) address previousOwner = _tickerOwner(ticker); if (previousOwner != address(0)) { _deleteTickerOwnership(previousOwner, ticker); } /*solium-disable-next-line security/no-block-members*/ - _addTicker(_owner, ticker, _tokenName, now, now.add(getExpiryLimit()), false, false, tickerFee); + _addTicker(_owner, ticker, now, now.add(getUintValue(EXPIRYLIMIT)), false, false, polyFee, usdFee); + } + + /** + * @dev This function is just for backwards compatibility + */ + function registerTicker(address _owner, string calldata _ticker, string calldata _tokenName) external { + registerNewTicker(_owner, _ticker); + (, uint256 polyFee) = getFees(TICKERREGFEE); + emit RegisterTicker(_owner, _ticker, _tokenName, now, now.add(getUintValue(EXPIRYLIMIT)), false, polyFee); } /** * @notice Internal - Sets the details of the ticker */ function _addTicker( - address _owner, - string _ticker, - string _tokenName, - uint256 _registrationDate, - uint256 _expiryDate, - bool _status, - bool _fromAdmin, - uint256 _fee - ) internal { + address _owner, + string memory _ticker, + uint256 _registrationDate, + uint256 _expiryDate, + bool _status, + bool _fromAdmin, + uint256 _polyFee, + uint256 _usdFee + ) + internal + { _setTickerOwnership(_owner, _ticker); - _storeTickerDetails(_ticker, _owner, _registrationDate, _expiryDate, _tokenName, _status); - emit RegisterTicker(_owner, _ticker, _tokenName, _registrationDate, _expiryDate, _fromAdmin, _fee); + _storeTickerDetails(_ticker, _owner, _registrationDate, _expiryDate, _status); + emit RegisterTicker(_owner, _ticker, _registrationDate, _expiryDate, _fromAdmin, _polyFee, _usdFee); } /** @@ -245,25 +388,43 @@ contract SecurityTokenRegistry is ISecurityTokenRegistry, EternalStorage { * @notice Only allowed to modify the tickers which are not yet deployed. * @param _owner is the owner of the token * @param _ticker is the token ticker - * @param _tokenName is the name of the token * @param _registrationDate is the date at which ticker is registered * @param _expiryDate is the expiry date for the ticker * @param _status is the token deployment status */ - function modifyTicker( + function modifyExistingTicker( address _owner, - string _ticker, - string _tokenName, + string memory _ticker, uint256 _registrationDate, uint256 _expiryDate, bool _status - ) external onlyOwner { - require(bytes(_ticker).length > 0 && bytes(_ticker).length <= 10, "Ticker length range (0,10]"); - require(_expiryDate != 0 && _registrationDate != 0, "Dates should not be 0"); - require(_registrationDate <= _expiryDate, "Registration date should < expiry date"); - require(_owner != address(0), "Invalid address"); + ) + public + onlyOwner + { + require(bytes(_ticker).length > 0 && bytes(_ticker).length <= 10, "Bad ticker"); + require(_expiryDate != 0 && _registrationDate != 0, "Bad dates"); + require(_registrationDate <= _expiryDate, "Bad dates"); + require(_owner != address(0), "Bad address"); string memory ticker = Util.upper(_ticker); - _modifyTicker(_owner, ticker, _tokenName, _registrationDate, _expiryDate, _status); + _modifyTicker(_owner, ticker, _registrationDate, _expiryDate, _status); + } + + /** + * @dev This function is just for backwards compatibility + */ + function modifyTicker( + address _owner, + string calldata _ticker, + string calldata _tokenName, + uint256 _registrationDate, + uint256 _expiryDate, + bool _status + ) + external + { + modifyExistingTicker(_owner, _ticker, _registrationDate, _expiryDate, _status); + emit RegisterTicker(_owner, _ticker, _tokenName, now, now.add(getUintValue(EXPIRYLIMIT)), false, 0); } /** @@ -271,12 +432,13 @@ contract SecurityTokenRegistry is ISecurityTokenRegistry, EternalStorage { */ function _modifyTicker( address _owner, - string _ticker, - string _tokenName, + string memory _ticker, uint256 _registrationDate, uint256 _expiryDate, bool _status - ) internal { + ) + internal + { address currentOwner = _tickerOwner(_ticker); if (currentOwner != address(0)) { _deleteTickerOwnership(currentOwner, _ticker); @@ -286,12 +448,12 @@ contract SecurityTokenRegistry is ISecurityTokenRegistry, EternalStorage { } // If status is true, there must be a security token linked to the ticker already if (_status) { - require(getAddressValue(Encoder.getKey("tickerToSecurityToken", _ticker)) != address(0), "Token not registered"); + require(getAddressValue(Encoder.getKey("tickerToSecurityToken", _ticker)) != address(0), "Not registered"); } - _addTicker(_owner, _ticker, _tokenName, _registrationDate, _expiryDate, _status, true, uint256(0)); + _addTicker(_owner, _ticker, _registrationDate, _expiryDate, _status, true, uint256(0), uint256(0)); } - function _tickerOwner(string _ticker) internal view returns(address) { + function _tickerOwner(string memory _ticker) internal view returns(address) { return getAddressValue(Encoder.getKey("registeredTickers_owner", _ticker)); } @@ -299,34 +461,33 @@ contract SecurityTokenRegistry is ISecurityTokenRegistry, EternalStorage { * @notice Removes the ticker details, associated ownership & security token mapping * @param _ticker is the token ticker */ - function removeTicker(string _ticker) external onlyOwner { + function removeTicker(string memory _ticker) public onlyOwner { string memory ticker = Util.upper(_ticker); address owner = _tickerOwner(ticker); - require(owner != address(0), "Ticker doesn't exist"); + require(owner != address(0), "Bad ticker"); _deleteTickerOwnership(owner, ticker); set(Encoder.getKey("tickerToSecurityToken", ticker), address(0)); - _storeTickerDetails(ticker, address(0), 0, 0, "", false); + _storeTickerDetails(ticker, address(0), 0, 0, false); /*solium-disable-next-line security/no-block-members*/ - emit TickerRemoved(ticker, now, msg.sender); + emit TickerRemoved(ticker, msg.sender); } /** - * @notice Internal - Checks if the entered ticker is registered and has not expired + * @notice Checks if the entered ticker is registered and has not expired * @param _ticker is the token ticker * @return bool */ - function _tickerAvailable(string _ticker) internal view returns(bool) { + function tickerAvailable(string memory _ticker) public view returns(bool) { if (_tickerOwner(_ticker) != address(0)) { /*solium-disable-next-line security/no-block-members*/ if ((now > getUintValue(Encoder.getKey("registeredTickers_expiryDate", _ticker))) && !_tickerStatus(_ticker)) { return true; - } else - return false; + } else return false; } return true; } - function _tickerStatus(string _ticker) internal view returns(bool) { + function _tickerStatus(string memory _ticker) internal view returns(bool) { return getBoolValue(Encoder.getKey("registeredTickers_status", _ticker)); } @@ -335,14 +496,14 @@ contract SecurityTokenRegistry is ISecurityTokenRegistry, EternalStorage { * @param _owner is the address of the owner of the ticker * @param _ticker is the ticker symbol */ - function _setTickerOwnership(address _owner, string _ticker) internal { + function _setTickerOwnership(address _owner, string memory _ticker) internal { bytes32 _ownerKey = Encoder.getKey("userToTickers", _owner); uint256 length = uint256(getArrayBytes32(_ownerKey).length); pushArray(_ownerKey, Util.stringToBytes32(_ticker)); set(Encoder.getKey("tickerIndex", _ticker), length); bytes32 seenKey = Encoder.getKey("seenUsers", _owner); if (!getBoolValue(seenKey)) { - pushArray(Encoder.getKey("activeUsers"), _owner); + pushArray(ACTIVE_USERS, _owner); set(seenKey, true); } } @@ -351,28 +512,22 @@ contract SecurityTokenRegistry is ISecurityTokenRegistry, EternalStorage { * @notice Internal - Stores the ticker details */ function _storeTickerDetails( - string _ticker, + string memory _ticker, address _owner, uint256 _registrationDate, uint256 _expiryDate, - string _tokenName, bool _status - ) internal { + ) + internal + { bytes32 key = Encoder.getKey("registeredTickers_owner", _ticker); - if (getAddressValue(key) != _owner) - set(key, _owner); + set(key, _owner); key = Encoder.getKey("registeredTickers_registrationDate", _ticker); - if (getUintValue(key) != _registrationDate) - set(key, _registrationDate); + set(key, _registrationDate); key = Encoder.getKey("registeredTickers_expiryDate", _ticker); - if (getUintValue(key) != _expiryDate) - set(key, _expiryDate); - key = Encoder.getKey("registeredTickers_tokenName", _ticker); - if (Encoder.getKey(getStringValue(key)) != Encoder.getKey(_tokenName)) - set(key, _tokenName); + set(key, _expiryDate); key = Encoder.getKey("registeredTickers_status", _ticker); - if (getBoolValue(key) != _status) - set(key, _status); + set(key, _status); } /** @@ -380,13 +535,15 @@ contract SecurityTokenRegistry is ISecurityTokenRegistry, EternalStorage { * @param _newOwner is the address of the new owner of the ticker * @param _ticker is the ticker symbol */ - function transferTickerOwnership(address _newOwner, string _ticker) external whenNotPausedOrOwner { + function transferTickerOwnership(address _newOwner, string memory _ticker) public whenNotPausedOrOwner { string memory ticker = Util.upper(_ticker); - require(_newOwner != address(0), "Invalid address"); + require(_newOwner != address(0), "Bad address"); bytes32 ownerKey = Encoder.getKey("registeredTickers_owner", ticker); - require(getAddressValue(ownerKey) == msg.sender, "Not authorised"); - if (_tickerStatus(ticker)) - require(IOwnable(getAddressValue(Encoder.getKey("tickerToSecurityToken", ticker))).owner() == _newOwner, "New owner does not match token owner"); + require(getAddressValue(ownerKey) == msg.sender, "Only owner"); + if (_tickerStatus(ticker)) require( + IOwnable(getAddressValue(Encoder.getKey("tickerToSecurityToken", ticker))).owner() == _newOwner, + "Owner mismatch" + ); _deleteTickerOwnership(msg.sender, ticker); _setTickerOwnership(_newOwner, ticker); set(ownerKey, _newOwner); @@ -396,7 +553,7 @@ contract SecurityTokenRegistry is ISecurityTokenRegistry, EternalStorage { /** * @notice Internal - Removes the owner of a ticker */ - function _deleteTickerOwnership(address _owner, string _ticker) internal { + function _deleteTickerOwnership(address _owner, string memory _ticker) internal { uint256 index = uint256(getUintValue(Encoder.getKey("tickerIndex", _ticker))); bytes32 ownerKey = Encoder.getKey("userToTickers", _owner); bytes32[] memory tickers = getArrayBytes32(ownerKey); @@ -413,193 +570,218 @@ contract SecurityTokenRegistry is ISecurityTokenRegistry, EternalStorage { * @notice Changes the expiry time for the token ticker. Only available to Polymath. * @param _newExpiry is the new expiry for newly generated tickers */ - function changeExpiryLimit(uint256 _newExpiry) external onlyOwner { - require(_newExpiry >= 1 days, "Expiry should >= 1 day"); + function changeExpiryLimit(uint256 _newExpiry) public onlyOwner { + require(_newExpiry >= 1 days, "Bad dates"); emit ChangeExpiryLimit(getUintValue(EXPIRYLIMIT), _newExpiry); set(EXPIRYLIMIT, _newExpiry); } + ///////////////////////////// + // Security Token Management + ///////////////////////////// + /** - * @notice Returns the list of tickers owned by the selected address - * @param _owner is the address which owns the list of tickers + * @notice Deploys an instance of a new Security Token of version 2.0 and records it to the registry + * @dev this function is for backwards compatibilty with 2.0 dApp. + * @param _name is the name of the token + * @param _ticker is the ticker symbol of the security token + * @param _tokenDetails is the off-chain details of the token + * @param _divisible is whether or not the token is divisible */ - function getTickersByOwner(address _owner) external view returns(bytes32[]) { - uint counter = 0; - // accessing the data structure userTotickers[_owner].length - bytes32[] memory tickers = getArrayBytes32(Encoder.getKey("userToTickers", _owner)); - for (uint i = 0; i < tickers.length; i++) { - string memory ticker = Util.bytes32ToString(tickers[i]); - /*solium-disable-next-line security/no-block-members*/ - if (getUintValue(Encoder.getKey("registeredTickers_expiryDate", ticker)) >= now || _tickerStatus(ticker)) { - counter ++; - } - } - bytes32[] memory tempList = new bytes32[](counter); - counter = 0; - for (i = 0; i < tickers.length; i++) { - ticker = Util.bytes32ToString(tickers[i]); - /*solium-disable-next-line security/no-block-members*/ - if (getUintValue(Encoder.getKey("registeredTickers_expiryDate", ticker)) >= now || _tickerStatus(ticker)) { - tempList[counter] = tickers[i]; - counter ++; - } - } - return tempList; - } - - /** - * @notice Returns the list of tokens owned by the selected address - * @param _owner is the address which owns the list of tickers - * @dev Intention is that this is called off-chain so block gas limit is not relevant - */ - function getTokensByOwner(address _owner) external view returns(address[]) { - // Loop over all active users, then all associated tickers of those users - // This ensures we find tokens, even if their owner has been modified - address[] memory activeUsers = getArrayAddress(Encoder.getKey("activeUsers")); - bytes32[] memory tickers; - address token; - uint256 count = 0; - uint256 i = 0; - uint256 j = 0; - for (i = 0; i < activeUsers.length; i++) { - tickers = getArrayBytes32(Encoder.getKey("userToTickers", activeUsers[i])); - for (j = 0; j < tickers.length; j++) { - token = getAddressValue(Encoder.getKey("tickerToSecurityToken", Util.bytes32ToString(tickers[j]))); - if (token != address(0)) { - if (IOwnable(token).owner() == _owner) { - count = count + 1; - } - } - } - } - uint256 index = 0; - address[] memory result = new address[](count); - for (i = 0; i < activeUsers.length; i++) { - tickers = getArrayBytes32(Encoder.getKey("userToTickers", activeUsers[i])); - for (j = 0; j < tickers.length; j++) { - token = getAddressValue(Encoder.getKey("tickerToSecurityToken", Util.bytes32ToString(tickers[j]))); - if (token != address(0)) { - if (IOwnable(token).owner() == _owner) { - result[index] = token; - index = index + 1; - } - } - } - } - return result; + function generateSecurityToken( + string calldata _name, + string calldata _ticker, + string calldata _tokenDetails, + bool _divisible + ) + external + { + generateNewSecurityToken(_name, _ticker, _tokenDetails, _divisible, msg.sender, VersionUtils.pack(2, 0, 0)); } /** - * @notice Returns the owner and timestamp for a given ticker - * @param _ticker is the ticker symbol - * @return address - * @return uint256 - * @return uint256 - * @return string - * @return bool - */ - function getTickerDetails(string _ticker) external view returns (address, uint256, uint256, string, bool) { - string memory ticker = Util.upper(_ticker); - bool tickerStatus = _tickerStatus(ticker); - uint256 expiryDate = getUintValue(Encoder.getKey("registeredTickers_expiryDate", ticker)); + * @notice Deploys an instance of a new Security Token and records it to the registry + * @param _name is the name of the token + * @param _ticker is the ticker symbol of the security token + * @param _tokenDetails is the off-chain details of the token + * @param _divisible is whether or not the token is divisible + * @param _treasuryWallet Ethereum address which will holds the STs. + * @param _protocolVersion Version of securityToken contract + * - `_protocolVersion` is the packed value of uin8[3] array (it will be calculated offchain) + * - if _protocolVersion == 0 then latest version of securityToken will be generated + */ + function generateNewSecurityToken( + string memory _name, + string memory _ticker, + string memory _tokenDetails, + bool _divisible, + address _treasuryWallet, + uint256 _protocolVersion + ) + public + whenNotPausedOrOwner + { + require(bytes(_name).length > 0 && bytes(_ticker).length > 0, "Bad ticker"); + require(_treasuryWallet != address(0), "0x0 not allowed"); + if (_protocolVersion == 0) { + _protocolVersion = getUintValue(LATEST_VERSION); + } + _ticker = Util.upper(_ticker); + bytes32 statusKey = Encoder.getKey("registeredTickers_status", _ticker); + require(!getBoolValue(statusKey), "Already deployed"); + set(statusKey, true); + address issuer = msg.sender; + require(_tickerOwner(_ticker) == issuer, "Not authorised"); /*solium-disable-next-line security/no-block-members*/ - if ((tickerStatus == true) || (expiryDate > now)) { - return - ( - _tickerOwner(ticker), - getUintValue(Encoder.getKey("registeredTickers_registrationDate", ticker)), - expiryDate, - getStringValue(Encoder.getKey("registeredTickers_tokenName", ticker)), - tickerStatus + require(getUintValue(Encoder.getKey("registeredTickers_expiryDate", _ticker)) >= now, "Ticker expired"); + (uint256 _usdFee, uint256 _polyFee) = _takeFee(STLAUNCHFEE); + address newSecurityTokenAddress = + _deployToken(_name, _ticker, _tokenDetails, issuer, _divisible, _treasuryWallet, _protocolVersion); + if (_protocolVersion == VersionUtils.pack(2, 0, 0)) { + // For backwards compatibilty. Should be removed with an update when we disable st 2.0 generation. + emit NewSecurityToken( + _ticker, _name, newSecurityTokenAddress, issuer, now, issuer, false, _polyFee + ); + } else { + emit NewSecurityToken( + _ticker, _name, newSecurityTokenAddress, issuer, now, issuer, false, _usdFee, _polyFee, _protocolVersion ); - } else - return (address(0), uint256(0), uint256(0), "", false); + } } - ///////////////////////////// - // Security Token Management - ///////////////////////////// - /** - * @notice Deploys an instance of a new Security Token and records it to the registry + * @notice Deploys an instance of a new Security Token and replaces the old one in the registry + * This can be used to upgrade from version 2.0 of ST to 3.0 or in case something goes wrong with earlier ST + * @dev This function needs to be in STR 3.0. Defined public to avoid stack overflow * @param _name is the name of the token * @param _ticker is the ticker symbol of the security token * @param _tokenDetails is the off-chain details of the token * @param _divisible is whether or not the token is divisible */ - function generateSecurityToken(string _name, string _ticker, string _tokenDetails, bool _divisible) external whenNotPausedOrOwner { - require(bytes(_name).length > 0 && bytes(_ticker).length > 0, "Ticker length > 0"); + function refreshSecurityToken( + string memory _name, + string memory _ticker, + string memory _tokenDetails, + bool _divisible, + address _treasuryWallet + ) + public whenNotPausedOrOwner returns (address) + { + require(bytes(_name).length > 0 && bytes(_ticker).length > 0, "Bad ticker"); + require(_treasuryWallet != address(0), "0x0 not allowed"); string memory ticker = Util.upper(_ticker); - bytes32 statusKey = Encoder.getKey("registeredTickers_status", ticker); - require(!getBoolValue(statusKey), "Already deployed"); - set(statusKey, true); - require(_tickerOwner(ticker) == msg.sender, "Not authorised"); - /*solium-disable-next-line security/no-block-members*/ - require(getUintValue(Encoder.getKey("registeredTickers_expiryDate", ticker)) >= now, "Ticker gets expired"); + require(_tickerStatus(ticker), "not deployed"); + address st = getAddressValue(Encoder.getKey("tickerToSecurityToken", ticker)); + address stOwner = IOwnable(st).owner(); + require(msg.sender == stOwner, "Unauthroized"); + require(ISecurityToken(st).transfersFrozen(), "Transfers not frozen"); + uint256 protocolVersion = getUintValue(LATEST_VERSION); + address newSecurityTokenAddress = + _deployToken(_name, ticker, _tokenDetails, stOwner, _divisible, _treasuryWallet, protocolVersion); + emit SecurityTokenRefreshed( + _ticker, _name, newSecurityTokenAddress, stOwner, now, stOwner, protocolVersion + ); + } - uint256 launchFee = getSecurityTokenLaunchFee(); - if (launchFee > 0) - require(IERC20(getAddressValue(POLYTOKEN)).transferFrom(msg.sender, address(this), launchFee), "Insufficient allowance"); + function _deployToken( + string memory _name, + string memory _ticker, + string memory _tokenDetails, + address _issuer, + bool _divisible, + address _wallet, + uint256 _protocolVersion + ) + internal + returns(address newSecurityTokenAddress) + { + // In v2.x of STFactory, the final argument to deployToken is the PolymathRegistry. + // In v3.x of STFactory, the final argument to deployToken is the Treasury wallet. + uint8[] memory upperLimit = new uint8[](3); + upperLimit[0] = 2; + upperLimit[1] = 99; + upperLimit[2] = 99; + if (VersionUtils.lessThanOrEqual(VersionUtils.unpack(uint24(_protocolVersion)), upperLimit)) { + _wallet = getAddressValue(POLYMATHREGISTRY); + } - address newSecurityTokenAddress = ISTFactory(getSTFactoryAddress()).deployToken( + newSecurityTokenAddress = ISTFactory(getAddressValue(Encoder.getKey("protocolVersionST", _protocolVersion))).deployToken( _name, - ticker, + _ticker, 18, _tokenDetails, - msg.sender, + _issuer, _divisible, - getAddressValue(POLYMATHREGISTRY) + _wallet ); /*solium-disable-next-line security/no-block-members*/ - _storeSecurityTokenData(newSecurityTokenAddress, ticker, _tokenDetails, now); - set(Encoder.getKey("tickerToSecurityToken", ticker), newSecurityTokenAddress); - /*solium-disable-next-line security/no-block-members*/ - emit NewSecurityToken(ticker, _name, newSecurityTokenAddress, msg.sender, now, msg.sender, false, launchFee); + _storeSecurityTokenData(newSecurityTokenAddress, _ticker, _tokenDetails, now); + set(Encoder.getKey("tickerToSecurityToken", _ticker), newSecurityTokenAddress); } /** * @notice Adds a new custom Security Token and saves it to the registry. (Token should follow the ISecurityToken interface) - * @param _name is the name of the token * @param _ticker is the ticker symbol of the security token * @param _owner is the owner of the token * @param _securityToken is the address of the securityToken * @param _tokenDetails is the off-chain details of the token * @param _deployedAt is the timestamp at which the security token is deployed */ - function modifySecurityToken( - string _name, - string _ticker, + function modifyExistingSecurityToken( + string memory _ticker, address _owner, address _securityToken, - string _tokenDetails, + string memory _tokenDetails, uint256 _deployedAt ) - external + public onlyOwner { - require(bytes(_name).length > 0 && bytes(_ticker).length > 0, "String length > 0"); - require(bytes(_ticker).length <= 10, "Ticker length range (0,10]"); - require(_deployedAt != 0 && _owner != address(0), "0 value params not allowed"); + require(bytes(_ticker).length <= 10, "Bad ticker"); + require(_deployedAt != 0 && _owner != address(0), "Bad data"); string memory ticker = Util.upper(_ticker); - require(_securityToken != address(0), "ST address is 0x"); + require(_securityToken != address(0), "Bad address"); uint256 registrationTime = getUintValue(Encoder.getKey("registeredTickers_registrationDate", ticker)); uint256 expiryTime = getUintValue(Encoder.getKey("registeredTickers_expiryDate", ticker)); if (registrationTime == 0) { /*solium-disable-next-line security/no-block-members*/ registrationTime = now; - expiryTime = registrationTime.add(getExpiryLimit()); + expiryTime = registrationTime.add(getUintValue(EXPIRYLIMIT)); } set(Encoder.getKey("tickerToSecurityToken", ticker), _securityToken); - _modifyTicker(_owner, ticker, _name, registrationTime, expiryTime, true); + _modifyTicker(_owner, ticker, registrationTime, expiryTime, true); _storeSecurityTokenData(_securityToken, ticker, _tokenDetails, _deployedAt); - emit NewSecurityToken(ticker, _name, _securityToken, _owner, _deployedAt, msg.sender, true, getSecurityTokenLaunchFee()); + emit NewSecurityToken( + ticker, ISecurityToken(_securityToken).name(), _securityToken, _owner, _deployedAt, msg.sender, true, uint256(0), uint256(0), 0 + ); + } + + /** + * @dev This function is just for backwards compatibility + */ + function modifySecurityToken( + string calldata /* */, + string calldata _ticker, + address _owner, + address _securityToken, + string calldata _tokenDetails, + uint256 _deployedAt + ) + external + { + modifyExistingSecurityToken(_ticker, _owner, _securityToken, _tokenDetails, _deployedAt); } /** * @notice Internal - Stores the security token details */ - function _storeSecurityTokenData(address _securityToken, string _ticker, string _tokenDetails, uint256 _deployedAt) internal { + function _storeSecurityTokenData( + address _securityToken, + string memory _ticker, + string memory _tokenDetails, + uint256 _deployedAt + ) internal { set(Encoder.getKey("securityTokens_ticker", _securityToken), _ticker); set(Encoder.getKey("securityTokens_tokenDetails", _securityToken), _tokenDetails); set(Encoder.getKey("securityTokens_deployedAt", _securityToken), _deployedAt); @@ -610,37 +792,10 @@ contract SecurityTokenRegistry is ISecurityTokenRegistry, EternalStorage { * @param _securityToken is the address of the security token * @return bool */ - function isSecurityToken(address _securityToken) external view returns (bool) { + function isSecurityToken(address _securityToken) external view returns(bool) { return (keccak256(bytes(getStringValue(Encoder.getKey("securityTokens_ticker", _securityToken)))) != keccak256("")); } - /** - * @notice Returns the security token address by ticker symbol - * @param _ticker is the ticker of the security token - * @return address - */ - function getSecurityTokenAddress(string _ticker) external view returns (address) { - string memory ticker = Util.upper(_ticker); - return getAddressValue(Encoder.getKey("tickerToSecurityToken", ticker)); - } - - /** - * @notice Returns the security token data by address - * @param _securityToken is the address of the security token. - * @return string is the ticker of the security Token. - * @return address is the issuer of the security Token. - * @return string is the details of the security token. - * @return uint256 is the timestamp at which security Token was deployed. - */ - function getSecurityTokenData(address _securityToken) external view returns (string, address, string, uint256) { - return ( - getStringValue(Encoder.getKey("securityTokens_ticker", _securityToken)), - IOwnable(_securityToken).owner(), - getStringValue(Encoder.getKey("securityTokens_tokenDetails", _securityToken)), - getUintValue(Encoder.getKey("securityTokens_deployedAt", _securityToken)) - ); - } - ///////////////////////////// // Ownership, lifecycle & Utility ///////////////////////////// @@ -649,8 +804,8 @@ contract SecurityTokenRegistry is ISecurityTokenRegistry, EternalStorage { * @dev Allows the current owner to transfer control of the contract to a newOwner. * @param _newOwner The address to transfer ownership to. */ - function transferOwnership(address _newOwner) external onlyOwner { - require(_newOwner != address(0), "Invalid address"); + function transferOwnership(address _newOwner) public onlyOwner { + require(_newOwner != address(0), "Bad address"); emit OwnershipTransferred(getAddressValue(OWNER), _newOwner); set(OWNER, _newOwner); } @@ -661,7 +816,7 @@ contract SecurityTokenRegistry is ISecurityTokenRegistry, EternalStorage { function pause() external whenNotPaused onlyOwner { set(PAUSED, true); /*solium-disable-next-line security/no-block-members*/ - emit Pause(now); + emit Pause(msg.sender); } /** @@ -670,44 +825,69 @@ contract SecurityTokenRegistry is ISecurityTokenRegistry, EternalStorage { function unpause() external whenPaused onlyOwner { set(PAUSED, false); /*solium-disable-next-line security/no-block-members*/ - emit Unpause(now); + emit Unpause(msg.sender); } /** - * @notice Sets the ticker registration fee in POLY tokens. Only Polymath. - * @param _tickerRegFee is the registration fee in POLY tokens (base 18 decimals) + * @notice Sets the ticker registration fee in USD tokens. Only Polymath. + * @param _tickerRegFee is the registration fee in USD tokens (base 18 decimals) */ - function changeTickerRegistrationFee(uint256 _tickerRegFee) external onlyOwner { + function changeTickerRegistrationFee(uint256 _tickerRegFee) public onlyOwner { uint256 fee = getUintValue(TICKERREGFEE); - require(fee != _tickerRegFee, "Fee not changed"); - emit ChangeTickerRegistrationFee(fee, _tickerRegFee); - set(TICKERREGFEE, _tickerRegFee); + require(fee != _tickerRegFee, "Bad fee"); + _changeTickerRegistrationFee(fee, _tickerRegFee); } - /** - * @notice Sets the ticker registration fee in POLY tokens. Only Polymath. - * @param _stLaunchFee is the registration fee in POLY tokens (base 18 decimals) + function _changeTickerRegistrationFee(uint256 _oldFee, uint256 _newFee) internal { + emit ChangeTickerRegistrationFee(_oldFee, _newFee); + set(TICKERREGFEE, _newFee); + } + + /** + * @notice Sets the ticker registration fee in USD tokens. Only Polymath. + * @param _stLaunchFee is the registration fee in USD tokens (base 18 decimals) */ - function changeSecurityLaunchFee(uint256 _stLaunchFee) external onlyOwner { + function changeSecurityLaunchFee(uint256 _stLaunchFee) public onlyOwner { uint256 fee = getUintValue(STLAUNCHFEE); - require(fee != _stLaunchFee, "Fee not changed"); - emit ChangeSecurityLaunchFee(fee, _stLaunchFee); - set(STLAUNCHFEE, _stLaunchFee); + require(fee != _stLaunchFee, "Bad fee"); + _changeSecurityLaunchFee(fee, _stLaunchFee); + } + + function _changeSecurityLaunchFee(uint256 _oldFee, uint256 _newFee) internal { + emit ChangeSecurityLaunchFee(_oldFee, _newFee); + set(STLAUNCHFEE, _newFee); + } + + /** + * @notice Sets the ticker registration and ST launch fee amount and currency + * @param _tickerRegFee is the ticker registration fee (base 18 decimals) + * @param _stLaunchFee is the st generation fee (base 18 decimals) + * @param _isFeeInPoly defines if the fee is in poly or usd + */ + function changeFeesAmountAndCurrency(uint256 _tickerRegFee, uint256 _stLaunchFee, bool _isFeeInPoly) public onlyOwner { + uint256 tickerFee = getUintValue(TICKERREGFEE); + uint256 stFee = getUintValue(STLAUNCHFEE); + bool isOldFeesInPoly = getBoolValue(IS_FEE_IN_POLY); + require(isOldFeesInPoly != _isFeeInPoly, "Currency unchanged"); + _changeTickerRegistrationFee(tickerFee, _tickerRegFee); + _changeSecurityLaunchFee(stFee, _stLaunchFee); + emit ChangeFeeCurrency(_isFeeInPoly); + set(IS_FEE_IN_POLY, _isFeeInPoly); } /** * @notice Reclaims all ERC20Basic compatible tokens * @param _tokenContract is the address of the token contract */ - function reclaimERC20(address _tokenContract) external onlyOwner { - require(_tokenContract != address(0), "Invalid address"); + function reclaimERC20(address _tokenContract) public onlyOwner { + require(_tokenContract != address(0), "Bad address"); IERC20 token = IERC20(_tokenContract); uint256 balance = token.balanceOf(address(this)); require(token.transfer(owner(), balance), "Transfer failed"); } /** - * @notice Changes the protocol version and the SecurityToken contract + * @notice Changes the SecurityToken contract for a particular factory version * @notice Used only by Polymath to upgrade the SecurityToken contract and add more functionalities to future versions * @notice Changing versions does not affect existing tokens. * @param _STFactoryAddress is the address of the proxy. @@ -715,72 +895,60 @@ contract SecurityTokenRegistry is ISecurityTokenRegistry, EternalStorage { * @param _minor Minor version of the proxy. * @param _patch Patch version of the proxy */ - function setProtocolVersion(address _STFactoryAddress, uint8 _major, uint8 _minor, uint8 _patch) external onlyOwner { - require(_STFactoryAddress != address(0), "0x address is not allowed"); - _setProtocolVersion(_STFactoryAddress, _major, _minor, _patch); + function setProtocolFactory(address _STFactoryAddress, uint8 _major, uint8 _minor, uint8 _patch) public onlyOwner { + _setProtocolFactory(_STFactoryAddress, _major, _minor, _patch); + } + + function _setProtocolFactory(address _STFactoryAddress, uint8 _major, uint8 _minor, uint8 _patch) internal { + require(_STFactoryAddress != address(0), "Bad address"); + uint24 _packedVersion = VersionUtils.pack(_major, _minor, _patch); + address stFactoryAddress = getAddressValue(Encoder.getKey("protocolVersionST", uint256(_packedVersion))); + require(stFactoryAddress == address(0), "Already exists"); + set(Encoder.getKey("protocolVersionST", uint256(_packedVersion)), _STFactoryAddress); + emit ProtocolFactorySet(_STFactoryAddress, _major, _minor, _patch); } /** - * @notice Internal - Changes the protocol version and the SecurityToken contract + * @notice Removes a STFactory + * @param _major Major version of the proxy. + * @param _minor Minor version of the proxy. + * @param _patch Patch version of the proxy */ - function _setProtocolVersion(address _STFactoryAddress, uint8 _major, uint8 _minor, uint8 _patch) internal { - uint8[] memory _version = new uint8[](3); - _version[0] = _major; - _version[1] = _minor; - _version[2] = _patch; + function removeProtocolFactory(uint8 _major, uint8 _minor, uint8 _patch) public onlyOwner { uint24 _packedVersion = VersionUtils.pack(_major, _minor, _patch); - require(VersionUtils.isValidVersion(getProtocolVersion(), _version),"In-valid version"); - set(Encoder.getKey("latestVersion"), uint256(_packedVersion)); - set(Encoder.getKey("protocolVersionST", getUintValue(Encoder.getKey("latestVersion"))), _STFactoryAddress); + require(getUintValue(LATEST_VERSION) != _packedVersion, "Cannot remove latestVersion"); + emit ProtocolFactoryRemoved(getAddressValue(Encoder.getKey("protocolVersionST", _packedVersion)), _major, _minor, _patch); + set(Encoder.getKey("protocolVersionST", uint256(_packedVersion)), address(0)); } /** - * @notice Returns the current STFactory Address - */ - function getSTFactoryAddress() public view returns(address) { - return getAddressValue(Encoder.getKey("protocolVersionST", getUintValue(Encoder.getKey("latestVersion")))); + * @notice Changes the default protocol version + * @notice Used only by Polymath to upgrade the SecurityToken contract and add more functionalities to future versions + * @notice Changing versions does not affect existing tokens. + * @param _major Major version of the proxy. + * @param _minor Minor version of the proxy. + * @param _patch Patch version of the proxy + */ + function setLatestVersion(uint8 _major, uint8 _minor, uint8 _patch) public onlyOwner { + _setLatestVersion(_major, _minor, _patch); } - /** - * @notice Gets Protocol version - */ - function getProtocolVersion() public view returns(uint8[]) { - return VersionUtils.unpack(uint24(getUintValue(Encoder.getKey("latestVersion")))); + function _setLatestVersion(uint8 _major, uint8 _minor, uint8 _patch) internal { + uint24 _packedVersion = VersionUtils.pack(_major, _minor, _patch); + require(getAddressValue(Encoder.getKey("protocolVersionST", _packedVersion)) != address(0), "No factory"); + set(LATEST_VERSION, uint256(_packedVersion)); + emit LatestVersionSet(_major, _minor, _patch); } /** * @notice Changes the PolyToken address. Only Polymath. * @param _newAddress is the address of the polytoken. */ - function updatePolyTokenAddress(address _newAddress) external onlyOwner { - require(_newAddress != address(0), "Invalid address"); + function updatePolyTokenAddress(address _newAddress) public onlyOwner { + require(_newAddress != address(0), "Bad address"); set(POLYTOKEN, _newAddress); } - /** - * @notice Gets the security token launch fee - * @return Fee amount - */ - function getSecurityTokenLaunchFee() public view returns(uint256) { - return getUintValue(STLAUNCHFEE); - } - - /** - * @notice Gets the ticker registration fee - * @return Fee amount - */ - function getTickerRegistrationFee() public view returns(uint256) { - return getUintValue(TICKERREGFEE); - } - - /** - * @notice Gets the expiry limit - * @return Expiry limit - */ - function getExpiryLimit() public view returns(uint256) { - return getUintValue(EXPIRYLIMIT); - } - /** * @notice Check whether the registry is paused or not * @return bool @@ -796,5 +964,4 @@ contract SecurityTokenRegistry is ISecurityTokenRegistry, EternalStorage { function owner() public view returns(address) { return getAddressValue(OWNER); } - } diff --git a/contracts/datastore/DataStore.sol b/contracts/datastore/DataStore.sol new file mode 100644 index 000000000..f0e7ee0fe --- /dev/null +++ b/contracts/datastore/DataStore.sol @@ -0,0 +1,423 @@ +pragma solidity 0.5.8; + +import "../interfaces/ISecurityToken.sol"; +import "../interfaces/IOwnable.sol"; +import "../interfaces/IDataStore.sol"; +import "./DataStoreStorage.sol"; + +/** + * @title Data store contract that stores data for all the modules in a central contract. + */ +contract DataStore is DataStoreStorage, IDataStore { + //NB To modify a specific element of an array, First push a new element to the array and then delete the old element. + //Whenver an element is deleted from an Array, last element of that array is moved to the index of deleted element. + //Delegate with MANAGEDATA permission can modify data. + event SecurityTokenChanged(address indexed _oldSecurityToken, address indexed _newSecurityToken); + + function _isAuthorized() internal view { + require(msg.sender == address(securityToken) || + msg.sender == IOwnable(address(securityToken)).owner() || + securityToken.checkPermission(msg.sender, address(this), MANAGEDATA) || + securityToken.isModule(msg.sender, DATA_KEY), + "Unauthorized" + ); + } + + modifier validKey(bytes32 _key) { + require(_key != bytes32(0), "bad key"); + _; + } + + modifier validArrayLength(uint256 _keyLength, uint256 _dataLength) { + require(_keyLength == _dataLength, "bad length"); + _; + } + + modifier onlyOwner() { + require(msg.sender == IOwnable(address(securityToken)).owner(), "Unauthorized"); + _; + } + + /** + * @dev Changes security token atatched to this data store + * @param _securityToken address of the security token + */ + function setSecurityToken(address _securityToken) external onlyOwner { + require(_securityToken != address(0), "Invalid address"); + emit SecurityTokenChanged(address(securityToken), _securityToken); + securityToken = ISecurityToken(_securityToken); + } + + /** + * @dev Stores a uint256 data against a key + * @param _key Unique key to identify the data + * @param _data Data to be stored against the key + */ + function setUint256(bytes32 _key, uint256 _data) external { + _isAuthorized(); + _setData(_key, _data, false); + } + + function setBytes32(bytes32 _key, bytes32 _data) external { + _isAuthorized(); + _setData(_key, _data, false); + } + + function setAddress(bytes32 _key, address _data) external { + _isAuthorized(); + _setData(_key, _data, false); + } + + function setBool(bytes32 _key, bool _data) external { + _isAuthorized(); + _setData(_key, _data, false); + } + + function setString(bytes32 _key, string calldata _data) external { + _isAuthorized(); + _setData(_key, _data); + } + + function setBytes(bytes32 _key, bytes calldata _data) external { + _isAuthorized(); + _setData(_key, _data); + } + + /** + * @dev Stores a uint256 array against a key + * @param _key Unique key to identify the array + * @param _data Array to be stored against the key + */ + function setUint256Array(bytes32 _key, uint256[] calldata _data) external { + _isAuthorized(); + _setData(_key, _data); + } + + function setBytes32Array(bytes32 _key, bytes32[] calldata _data) external { + _isAuthorized(); + _setData(_key, _data); + } + + function setAddressArray(bytes32 _key, address[] calldata _data) external { + _isAuthorized(); + _setData(_key, _data); + } + + function setBoolArray(bytes32 _key, bool[] calldata _data) external { + _isAuthorized(); + _setData(_key, _data); + } + + /** + * @dev Inserts a uint256 element to the array identified by the key + * @param _key Unique key to identify the array + * @param _data Element to push into the array + */ + function insertUint256(bytes32 _key, uint256 _data) external { + _isAuthorized(); + _setData(_key, _data, true); + } + + function insertBytes32(bytes32 _key, bytes32 _data) external { + _isAuthorized(); + _setData(_key, _data, true); + } + + function insertAddress(bytes32 _key, address _data) external { + _isAuthorized(); + _setData(_key, _data, true); + } + + function insertBool(bytes32 _key, bool _data) external { + _isAuthorized(); + _setData(_key, _data, true); + } + + /** + * @dev Deletes an element from the array identified by the key. + * When an element is deleted from an Array, last element of that array is moved to the index of deleted element. + * @param _key Unique key to identify the array + * @param _index Index of the element to delete + */ + function deleteUint256(bytes32 _key, uint256 _index) external { + _isAuthorized(); + _deleteUint(_key, _index); + } + + function deleteBytes32(bytes32 _key, uint256 _index) external { + _isAuthorized(); + _deleteBytes32(_key, _index); + } + + function deleteAddress(bytes32 _key, uint256 _index) external { + _isAuthorized(); + _deleteAddress(_key, _index); + } + + function deleteBool(bytes32 _key, uint256 _index) external { + _isAuthorized(); + _deleteBool(_key, _index); + } + + /** + * @dev Stores multiple uint256 data against respective keys + * @param _keys Array of keys to identify the data + * @param _data Array of data to be stored against the respective keys + */ + function setUint256Multi(bytes32[] memory _keys, uint256[] memory _data) public validArrayLength(_keys.length, _data.length) { + _isAuthorized(); + for (uint256 i = 0; i < _keys.length; i++) { + _setData(_keys[i], _data[i], false); + } + } + + function setBytes32Multi(bytes32[] memory _keys, bytes32[] memory _data) public validArrayLength(_keys.length, _data.length) { + _isAuthorized(); + for (uint256 i = 0; i < _keys.length; i++) { + _setData(_keys[i], _data[i], false); + } + } + + function setAddressMulti(bytes32[] memory _keys, address[] memory _data) public validArrayLength(_keys.length, _data.length) { + _isAuthorized(); + for (uint256 i = 0; i < _keys.length; i++) { + _setData(_keys[i], _data[i], false); + } + } + + function setBoolMulti(bytes32[] memory _keys, bool[] memory _data) public validArrayLength(_keys.length, _data.length) { + _isAuthorized(); + for (uint256 i = 0; i < _keys.length; i++) { + _setData(_keys[i], _data[i], false); + } + } + + /** + * @dev Inserts multiple uint256 elements to the array identified by the respective keys + * @param _keys Array of keys to identify the data + * @param _data Array of data to be inserted in arrays of the respective keys + */ + function insertUint256Multi(bytes32[] memory _keys, uint256[] memory _data) public validArrayLength(_keys.length, _data.length) { + _isAuthorized(); + for (uint256 i = 0; i < _keys.length; i++) { + _setData(_keys[i], _data[i], true); + } + } + + function insertBytes32Multi(bytes32[] memory _keys, bytes32[] memory _data) public validArrayLength(_keys.length, _data.length) { + _isAuthorized(); + for (uint256 i = 0; i < _keys.length; i++) { + _setData(_keys[i], _data[i], true); + } + } + + function insertAddressMulti(bytes32[] memory _keys, address[] memory _data) public validArrayLength(_keys.length, _data.length) { + _isAuthorized(); + for (uint256 i = 0; i < _keys.length; i++) { + _setData(_keys[i], _data[i], true); + } + } + + function insertBoolMulti(bytes32[] memory _keys, bool[] memory _data) public validArrayLength(_keys.length, _data.length) { + _isAuthorized(); + for (uint256 i = 0; i < _keys.length; i++) { + _setData(_keys[i], _data[i], true); + } + } + + function getUint256(bytes32 _key) external view returns(uint256) { + return uintData[_key]; + } + + function getBytes32(bytes32 _key) external view returns(bytes32) { + return bytes32Data[_key]; + } + + function getAddress(bytes32 _key) external view returns(address) { + return addressData[_key]; + } + + function getString(bytes32 _key) external view returns(string memory) { + return stringData[_key]; + } + + function getBytes(bytes32 _key) external view returns(bytes memory) { + return bytesData[_key]; + } + + function getBool(bytes32 _key) external view returns(bool) { + return boolData[_key]; + } + + function getUint256Array(bytes32 _key) external view returns(uint256[] memory) { + return uintArrayData[_key]; + } + + function getBytes32Array(bytes32 _key) external view returns(bytes32[] memory) { + return bytes32ArrayData[_key]; + } + + function getAddressArray(bytes32 _key) external view returns(address[] memory) { + return addressArrayData[_key]; + } + + function getBoolArray(bytes32 _key) external view returns(bool[] memory) { + return boolArrayData[_key]; + } + + function getUint256ArrayLength(bytes32 _key) external view returns(uint256) { + return uintArrayData[_key].length; + } + + function getBytes32ArrayLength(bytes32 _key) external view returns(uint256) { + return bytes32ArrayData[_key].length; + } + + function getAddressArrayLength(bytes32 _key) external view returns(uint256) { + return addressArrayData[_key].length; + } + + function getBoolArrayLength(bytes32 _key) external view returns(uint256) { + return boolArrayData[_key].length; + } + + function getUint256ArrayElement(bytes32 _key, uint256 _index) external view returns(uint256) { + return uintArrayData[_key][_index]; + } + + function getBytes32ArrayElement(bytes32 _key, uint256 _index) external view returns(bytes32) { + return bytes32ArrayData[_key][_index]; + } + + function getAddressArrayElement(bytes32 _key, uint256 _index) external view returns(address) { + return addressArrayData[_key][_index]; + } + + function getBoolArrayElement(bytes32 _key, uint256 _index) external view returns(bool) { + return boolArrayData[_key][_index]; + } + + function getUint256ArrayElements(bytes32 _key, uint256 _startIndex, uint256 _endIndex) public view returns(uint256[] memory array) { + uint256 size = uintArrayData[_key].length; + if (_endIndex >= size) { + size = size - _startIndex; + } else { + size = _endIndex - _startIndex + 1; + } + array = new uint256[](size); + for(uint256 i; i < size; i++) + array[i] = uintArrayData[_key][i + _startIndex]; + } + + function getBytes32ArrayElements(bytes32 _key, uint256 _startIndex, uint256 _endIndex) public view returns(bytes32[] memory array) { + uint256 size = bytes32ArrayData[_key].length; + if (_endIndex >= size) { + size = size - _startIndex; + } else { + size = _endIndex - _startIndex + 1; + } + array = new bytes32[](size); + for(uint256 i; i < size; i++) + array[i] = bytes32ArrayData[_key][i + _startIndex]; + } + + function getAddressArrayElements(bytes32 _key, uint256 _startIndex, uint256 _endIndex) public view returns(address[] memory array) { + uint256 size = addressArrayData[_key].length; + if (_endIndex >= size) { + size = size - _startIndex; + } else { + size = _endIndex - _startIndex + 1; + } + array = new address[](size); + for(uint256 i; i < size; i++) + array[i] = addressArrayData[_key][i + _startIndex]; + } + + function getBoolArrayElements(bytes32 _key, uint256 _startIndex, uint256 _endIndex) public view returns(bool[] memory array) { + uint256 size = boolArrayData[_key].length; + if (_endIndex >= size) { + size = size - _startIndex; + } else { + size = _endIndex - _startIndex + 1; + } + array = new bool[](size); + for(uint256 i; i < size; i++) + array[i] = boolArrayData[_key][i + _startIndex]; + } + + function _setData(bytes32 _key, uint256 _data, bool _insert) internal validKey(_key) { + if (_insert) + uintArrayData[_key].push(_data); + else + uintData[_key] = _data; + } + + function _setData(bytes32 _key, bytes32 _data, bool _insert) internal validKey(_key) { + if (_insert) + bytes32ArrayData[_key].push(_data); + else + bytes32Data[_key] = _data; + } + + function _setData(bytes32 _key, address _data, bool _insert) internal validKey(_key) { + if (_insert) + addressArrayData[_key].push(_data); + else + addressData[_key] = _data; + } + + function _setData(bytes32 _key, bool _data, bool _insert) internal validKey(_key) { + if (_insert) + boolArrayData[_key].push(_data); + else + boolData[_key] = _data; + } + + function _setData(bytes32 _key, string memory _data) internal validKey(_key) { + stringData[_key] = _data; + } + + function _setData(bytes32 _key, bytes memory _data) internal validKey(_key) { + bytesData[_key] = _data; + } + + function _setData(bytes32 _key, uint256[] memory _data) internal validKey(_key) { + uintArrayData[_key] = _data; + } + + function _setData(bytes32 _key, bytes32[] memory _data) internal validKey(_key) { + bytes32ArrayData[_key] = _data; + } + + function _setData(bytes32 _key, address[] memory _data) internal validKey(_key) { + addressArrayData[_key] = _data; + } + + function _setData(bytes32 _key, bool[] memory _data) internal validKey(_key) { + boolArrayData[_key] = _data; + } + + function _deleteUint(bytes32 _key, uint256 _index) internal validKey(_key) { + require(uintArrayData[_key].length > _index, "Invalid Index"); //Also prevents undeflow + uintArrayData[_key][_index] = uintArrayData[_key][uintArrayData[_key].length - 1]; + uintArrayData[_key].length--; + } + + function _deleteBytes32(bytes32 _key, uint256 _index) internal validKey(_key) { + require(bytes32ArrayData[_key].length > _index, "Invalid Index"); //Also prevents undeflow + bytes32ArrayData[_key][_index] = bytes32ArrayData[_key][bytes32ArrayData[_key].length - 1]; + bytes32ArrayData[_key].length--; + } + + function _deleteAddress(bytes32 _key, uint256 _index) internal validKey(_key) { + require(addressArrayData[_key].length > _index, "Invalid Index"); //Also prevents undeflow + addressArrayData[_key][_index] = addressArrayData[_key][addressArrayData[_key].length - 1]; + addressArrayData[_key].length--; + } + + function _deleteBool(bytes32 _key, uint256 _index) internal validKey(_key) { + require(boolArrayData[_key].length > _index, "Invalid Index"); //Also prevents undeflow + boolArrayData[_key][_index] = boolArrayData[_key][boolArrayData[_key].length - 1]; + boolArrayData[_key].length--; + } +} diff --git a/contracts/datastore/DataStoreFactory.sol b/contracts/datastore/DataStoreFactory.sol new file mode 100644 index 000000000..1281ac755 --- /dev/null +++ b/contracts/datastore/DataStoreFactory.sol @@ -0,0 +1,18 @@ +pragma solidity 0.5.8; + +import "./DataStoreProxy.sol"; + +contract DataStoreFactory { + + address public implementation; + + constructor(address _implementation) public { + require(_implementation != address(0), "Address should not be 0x"); + implementation = _implementation; + } + + function generateDataStore(address _securityToken) public returns (address) { + DataStoreProxy dsProxy = new DataStoreProxy(_securityToken, implementation); + return address(dsProxy); + } +} diff --git a/contracts/datastore/DataStoreProxy.sol b/contracts/datastore/DataStoreProxy.sol new file mode 100644 index 000000000..e8acae6bf --- /dev/null +++ b/contracts/datastore/DataStoreProxy.sol @@ -0,0 +1,36 @@ +pragma solidity 0.5.8; + +import "../proxy/Proxy.sol"; +import "./DataStoreStorage.sol"; + +/** + * @title DataStoreProxy Proxy + */ +contract DataStoreProxy is DataStoreStorage, Proxy { + + /** + * @notice Constructor + * @param _securityToken Address of the security token + * @param _implementation representing the address of the new implementation to be set + */ + constructor( + address _securityToken, + address _implementation + ) + public + { + require(_implementation != address(0) && _securityToken != address(0), + "Address should not be 0x" + ); + securityToken = ISecurityToken(_securityToken); + __implementation = _implementation; + } + + /** + * @notice Internal function to provide the address of the implementation contract + */ + function _implementation() internal view returns(address) { + return __implementation; + } + +} diff --git a/contracts/datastore/DataStoreStorage.sol b/contracts/datastore/DataStoreStorage.sol new file mode 100644 index 000000000..c3bc04b25 --- /dev/null +++ b/contracts/datastore/DataStoreStorage.sol @@ -0,0 +1,24 @@ +pragma solidity 0.5.8; + +import "../interfaces/ISecurityToken.sol"; + +contract DataStoreStorage { + // Address of the current implementation + address internal __implementation; + + ISecurityToken public securityToken; + + mapping (bytes32 => uint256) internal uintData; + mapping (bytes32 => bytes32) internal bytes32Data; + mapping (bytes32 => address) internal addressData; + mapping (bytes32 => string) internal stringData; + mapping (bytes32 => bytes) internal bytesData; + mapping (bytes32 => bool) internal boolData; + mapping (bytes32 => uint256[]) internal uintArrayData; + mapping (bytes32 => bytes32[]) internal bytes32ArrayData; + mapping (bytes32 => address[]) internal addressArrayData; + mapping (bytes32 => bool[]) internal boolArrayData; + + uint8 internal constant DATA_KEY = 6; + bytes32 internal constant MANAGEDATA = "MANAGEDATA"; +} diff --git a/contracts/external/IMedianizer.sol b/contracts/external/IMedianizer.sol index 755d54a0f..94541a5ed 100644 --- a/contracts/external/IMedianizer.sol +++ b/contracts/external/IMedianizer.sol @@ -1,15 +1,14 @@ /* solium-disable */ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; /** * @title Interface to MakerDAO Medianizer contract */ interface IMedianizer { + function peek() external view returns(bytes32, bool); - function peek() constant external returns (bytes32, bool); - - function read() constant external returns (bytes32); + function read() external view returns(bytes32); function set(address wat) external; @@ -27,7 +26,7 @@ interface IMedianizer { function poke(bytes32) external; - function compute() constant external returns (bytes32, bool); + function compute() external view returns(bytes32, bool); function void() external; diff --git a/contracts/external/oraclizeAPI.sol b/contracts/external/oraclizeAPI.sol index c8d7a8f18..9989144c7 100644 --- a/contracts/external/oraclizeAPI.sol +++ b/contracts/external/oraclizeAPI.sol @@ -1,15 +1,20 @@ -// /* + +ORACLIZE_API + Copyright (c) 2015-2016 Oraclize SRL Copyright (c) 2016 Oraclize LTD + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -17,44 +22,55 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +pragma solidity >= 0.5.0; // Incompatible compiler version - please select a compiler within the stated pragma range, or use a different version of the oraclizeAPI! -// This api is currently targeted at 0.4.18, please import oraclizeAPI_pre0.4.sol or oraclizeAPI_0.4 where necessary -/* solium-disable */ -pragma solidity >=0.4.18;// Incompatible compiler version... please select one stated within pragma solidity or use different oraclizeAPI version +// Dummy contract only used to emit to end-user they are using wrong solc +contract solcChecker { +/* INCOMPATIBLE SOLC: import the following instead: "github.com/oraclize/ethereum-api/oraclizeAPI_0.4.sol" */ function f(bytes calldata x) external; +} contract OraclizeI { + address public cbAddress; - function query(uint _timestamp, string _datasource, string _arg) external payable returns (bytes32 _id); - function query_withGasLimit(uint _timestamp, string _datasource, string _arg, uint _gaslimit) external payable returns (bytes32 _id); - function query2(uint _timestamp, string _datasource, string _arg1, string _arg2) public payable returns (bytes32 _id); - function query2_withGasLimit(uint _timestamp, string _datasource, string _arg1, string _arg2, uint _gaslimit) external payable returns (bytes32 _id); - function queryN(uint _timestamp, string _datasource, bytes _argN) public payable returns (bytes32 _id); - function queryN_withGasLimit(uint _timestamp, string _datasource, bytes _argN, uint _gaslimit) external payable returns (bytes32 _id); - function getPrice(string _datasource) public returns (uint _dsprice); - function getPrice(string _datasource, uint gaslimit) public returns (uint _dsprice); + function setProofType(byte _proofType) external; function setCustomGasPrice(uint _gasPrice) external; - function randomDS_getSessionPubKeyHash() external constant returns(bytes32); + function getPrice(string memory _datasource) public returns (uint _dsprice); + function randomDS_getSessionPubKeyHash() external view returns (bytes32 _sessionKeyHash); + function getPrice(string memory _datasource, uint _gasLimit) public returns (uint _dsprice); + function queryN(uint _timestamp, string memory _datasource, bytes memory _argN) public payable returns (bytes32 _id); + function query(uint _timestamp, string calldata _datasource, string calldata _arg) external payable returns (bytes32 _id); + function query2(uint _timestamp, string memory _datasource, string memory _arg1, string memory _arg2) public payable returns (bytes32 _id); + function query_withGasLimit(uint _timestamp, string calldata _datasource, string calldata _arg, uint _gasLimit) external payable returns (bytes32 _id); + function queryN_withGasLimit(uint _timestamp, string calldata _datasource, bytes calldata _argN, uint _gasLimit) external payable returns (bytes32 _id); + function query2_withGasLimit(uint _timestamp, string calldata _datasource, string calldata _arg1, string calldata _arg2, uint _gasLimit) external payable returns (bytes32 _id); } contract OraclizeAddrResolverI { - function getAddress() public returns (address _addr); + function getAddress() public returns (address _address); } - /* + Begin solidity-cborutils + https://github.com/smartcontractkit/solidity-cborutils + MIT License + Copyright (c) 2018 SmartContract ChainLink, Ltd. + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -62,760 +78,876 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ +*/ library Buffer { + struct buffer { bytes buf; uint capacity; } - function init(buffer memory buf, uint _capacity) internal pure { + function init(buffer memory _buf, uint _capacity) internal pure { uint capacity = _capacity; - if(capacity % 32 != 0) capacity += 32 - (capacity % 32); - // Allocate space for the buffer data - buf.capacity = capacity; + if (capacity % 32 != 0) { + capacity += 32 - (capacity % 32); + } + _buf.capacity = capacity; // Allocate space for the buffer data assembly { let ptr := mload(0x40) - mstore(buf, ptr) + mstore(_buf, ptr) mstore(ptr, 0) mstore(0x40, add(ptr, capacity)) } } - function resize(buffer memory buf, uint capacity) private pure { - bytes memory oldbuf = buf.buf; - init(buf, capacity); - append(buf, oldbuf); + function resize(buffer memory _buf, uint _capacity) private pure { + bytes memory oldbuf = _buf.buf; + init(_buf, _capacity); + append(_buf, oldbuf); } - function max(uint a, uint b) private pure returns(uint) { - if(a > b) { - return a; + function max(uint _a, uint _b) private pure returns (uint _max) { + if (_a > _b) { + return _a; } - return b; + return _b; } - /** - * @dev Appends a byte array to the end of the buffer. Resizes if doing so - * would exceed the capacity of the buffer. - * @param buf The buffer to append to. - * @param data The data to append. - * @return The original buffer. - */ - function append(buffer memory buf, bytes data) internal pure returns(buffer memory) { - if(data.length + buf.buf.length > buf.capacity) { - resize(buf, max(buf.capacity, data.length) * 2); + * @dev Appends a byte array to the end of the buffer. Resizes if doing so + * would exceed the capacity of the buffer. + * @param _buf The buffer to append to. + * @param _data The data to append. + * @return The original buffer. + * + */ + function append(buffer memory _buf, bytes memory _data) internal pure returns (buffer memory _buffer) { + if (_data.length + _buf.buf.length > _buf.capacity) { + resize(_buf, max(_buf.capacity, _data.length) * 2); } - uint dest; uint src; - uint len = data.length; + uint len = _data.length; assembly { - // Memory address of the buffer data - let bufptr := mload(buf) - // Length of existing buffer data - let buflen := mload(bufptr) - // Start address = buffer address + buffer length + sizeof(buffer length) - dest := add(add(bufptr, buflen), 32) - // Update buffer length - mstore(bufptr, add(buflen, mload(data))) - src := add(data, 32) - } - - // Copy word-length chunks while possible - for(; len >= 32; len -= 32) { + let bufptr := mload(_buf) // Memory address of the buffer data + let buflen := mload(bufptr) // Length of existing buffer data + dest := add(add(bufptr, buflen), 32) // Start address = buffer address + buffer length + sizeof(buffer length) + mstore(bufptr, add(buflen, mload(_data))) // Update buffer length + src := add(_data, 32) + } + for(; len >= 32; len -= 32) { // Copy word-length chunks while possible assembly { mstore(dest, mload(src)) } dest += 32; src += 32; } - - // Copy remaining bytes - uint mask = 256 ** (32 - len) - 1; + uint mask = 256 ** (32 - len) - 1; // Copy remaining bytes assembly { let srcpart := and(mload(src), not(mask)) let destpart := and(mload(dest), mask) mstore(dest, or(destpart, srcpart)) } - - return buf; + return _buf; } - /** - * @dev Appends a byte to the end of the buffer. Resizes if doing so would - * exceed the capacity of the buffer. - * @param buf The buffer to append to. - * @param data The data to append. - * @return The original buffer. - */ - function append(buffer memory buf, uint8 data) internal pure { - if(buf.buf.length + 1 > buf.capacity) { - resize(buf, buf.capacity * 2); + * + * @dev Appends a byte to the end of the buffer. Resizes if doing so would + * exceed the capacity of the buffer. + * @param _buf The buffer to append to. + * @param _data The data to append. + * @return The original buffer. + * + */ + function append(buffer memory _buf, uint8 _data) internal pure { + if (_buf.buf.length + 1 > _buf.capacity) { + resize(_buf, _buf.capacity * 2); } - assembly { - // Memory address of the buffer data - let bufptr := mload(buf) - // Length of existing buffer data - let buflen := mload(bufptr) - // Address = buffer address + buffer length + sizeof(buffer length) - let dest := add(add(bufptr, buflen), 32) - mstore8(dest, data) - // Update buffer length - mstore(bufptr, add(buflen, 1)) + let bufptr := mload(_buf) // Memory address of the buffer data + let buflen := mload(bufptr) // Length of existing buffer data + let dest := add(add(bufptr, buflen), 32) // Address = buffer address + buffer length + sizeof(buffer length) + mstore8(dest, _data) + mstore(bufptr, add(buflen, 1)) // Update buffer length } } - /** - * @dev Appends a byte to the end of the buffer. Resizes if doing so would - * exceed the capacity of the buffer. - * @param buf The buffer to append to. - * @param data The data to append. - * @return The original buffer. - */ - function appendInt(buffer memory buf, uint data, uint len) internal pure returns(buffer memory) { - if(len + buf.buf.length > buf.capacity) { - resize(buf, max(buf.capacity, len) * 2); - } - - uint mask = 256 ** len - 1; + * + * @dev Appends a byte to the end of the buffer. Resizes if doing so would + * exceed the capacity of the buffer. + * @param _buf The buffer to append to. + * @param _data The data to append. + * @return The original buffer. + * + */ + function appendInt(buffer memory _buf, uint _data, uint _len) internal pure returns (buffer memory _buffer) { + if (_len + _buf.buf.length > _buf.capacity) { + resize(_buf, max(_buf.capacity, _len) * 2); + } + uint mask = 256 ** _len - 1; assembly { - // Memory address of the buffer data - let bufptr := mload(buf) - // Length of existing buffer data - let buflen := mload(bufptr) - // Address = buffer address + buffer length + sizeof(buffer length) + len - let dest := add(add(bufptr, buflen), len) - mstore(dest, or(and(mload(dest), not(mask)), data)) - // Update buffer length - mstore(bufptr, add(buflen, len)) - } - return buf; + let bufptr := mload(_buf) // Memory address of the buffer data + let buflen := mload(bufptr) // Length of existing buffer data + let dest := add(add(bufptr, buflen), _len) // Address = buffer address + buffer length + sizeof(buffer length) + len + mstore(dest, or(and(mload(dest), not(mask)), _data)) + mstore(bufptr, add(buflen, _len)) // Update buffer length + } + return _buf; } } library CBOR { + using Buffer for Buffer.buffer; uint8 private constant MAJOR_TYPE_INT = 0; - uint8 private constant MAJOR_TYPE_NEGATIVE_INT = 1; + uint8 private constant MAJOR_TYPE_MAP = 5; uint8 private constant MAJOR_TYPE_BYTES = 2; - uint8 private constant MAJOR_TYPE_STRING = 3; uint8 private constant MAJOR_TYPE_ARRAY = 4; - uint8 private constant MAJOR_TYPE_MAP = 5; + uint8 private constant MAJOR_TYPE_STRING = 3; + uint8 private constant MAJOR_TYPE_NEGATIVE_INT = 1; uint8 private constant MAJOR_TYPE_CONTENT_FREE = 7; - function encodeType(Buffer.buffer memory buf, uint8 major, uint value) private pure { - if(value <= 23) { - buf.append(uint8((major << 5) | value)); - } else if(value <= 0xFF) { - buf.append(uint8((major << 5) | 24)); - buf.appendInt(value, 1); - } else if(value <= 0xFFFF) { - buf.append(uint8((major << 5) | 25)); - buf.appendInt(value, 2); - } else if(value <= 0xFFFFFFFF) { - buf.append(uint8((major << 5) | 26)); - buf.appendInt(value, 4); - } else if(value <= 0xFFFFFFFFFFFFFFFF) { - buf.append(uint8((major << 5) | 27)); - buf.appendInt(value, 8); + function encodeType(Buffer.buffer memory _buf, uint8 _major, uint _value) private pure { + if (_value <= 23) { + _buf.append(uint8((_major << 5) | _value)); + } else if (_value <= 0xFF) { + _buf.append(uint8((_major << 5) | 24)); + _buf.appendInt(_value, 1); + } else if (_value <= 0xFFFF) { + _buf.append(uint8((_major << 5) | 25)); + _buf.appendInt(_value, 2); + } else if (_value <= 0xFFFFFFFF) { + _buf.append(uint8((_major << 5) | 26)); + _buf.appendInt(_value, 4); + } else if (_value <= 0xFFFFFFFFFFFFFFFF) { + _buf.append(uint8((_major << 5) | 27)); + _buf.appendInt(_value, 8); } } - function encodeIndefiniteLengthType(Buffer.buffer memory buf, uint8 major) private pure { - buf.append(uint8((major << 5) | 31)); + function encodeIndefiniteLengthType(Buffer.buffer memory _buf, uint8 _major) private pure { + _buf.append(uint8((_major << 5) | 31)); } - function encodeUInt(Buffer.buffer memory buf, uint value) internal pure { - encodeType(buf, MAJOR_TYPE_INT, value); + function encodeUInt(Buffer.buffer memory _buf, uint _value) internal pure { + encodeType(_buf, MAJOR_TYPE_INT, _value); } - function encodeInt(Buffer.buffer memory buf, int value) internal pure { - if(value >= 0) { - encodeType(buf, MAJOR_TYPE_INT, uint(value)); + function encodeInt(Buffer.buffer memory _buf, int _value) internal pure { + if (_value >= 0) { + encodeType(_buf, MAJOR_TYPE_INT, uint(_value)); } else { - encodeType(buf, MAJOR_TYPE_NEGATIVE_INT, uint(-1 - value)); + encodeType(_buf, MAJOR_TYPE_NEGATIVE_INT, uint(-1 - _value)); } } - function encodeBytes(Buffer.buffer memory buf, bytes value) internal pure { - encodeType(buf, MAJOR_TYPE_BYTES, value.length); - buf.append(value); + function encodeBytes(Buffer.buffer memory _buf, bytes memory _value) internal pure { + encodeType(_buf, MAJOR_TYPE_BYTES, _value.length); + _buf.append(_value); } - function encodeString(Buffer.buffer memory buf, string value) internal pure { - encodeType(buf, MAJOR_TYPE_STRING, bytes(value).length); - buf.append(bytes(value)); + function encodeString(Buffer.buffer memory _buf, string memory _value) internal pure { + encodeType(_buf, MAJOR_TYPE_STRING, bytes(_value).length); + _buf.append(bytes(_value)); } - function startArray(Buffer.buffer memory buf) internal pure { - encodeIndefiniteLengthType(buf, MAJOR_TYPE_ARRAY); + function startArray(Buffer.buffer memory _buf) internal pure { + encodeIndefiniteLengthType(_buf, MAJOR_TYPE_ARRAY); } - function startMap(Buffer.buffer memory buf) internal pure { - encodeIndefiniteLengthType(buf, MAJOR_TYPE_MAP); + function startMap(Buffer.buffer memory _buf) internal pure { + encodeIndefiniteLengthType(_buf, MAJOR_TYPE_MAP); } - function endSequence(Buffer.buffer memory buf) internal pure { - encodeIndefiniteLengthType(buf, MAJOR_TYPE_CONTENT_FREE); + function endSequence(Buffer.buffer memory _buf) internal pure { + encodeIndefiniteLengthType(_buf, MAJOR_TYPE_CONTENT_FREE); } } - /* + End solidity-cborutils - */ +*/ contract usingOraclize { - uint constant day = 60*60*24; - uint constant week = 60*60*24*7; - uint constant month = 60*60*24*30; + + using CBOR for Buffer.buffer; + + OraclizeI oraclize; + OraclizeAddrResolverI OAR; + + uint constant day = 60 * 60 * 24; + uint constant week = 60 * 60 * 24 * 7; + uint constant month = 60 * 60 * 24 * 30; + byte constant proofType_NONE = 0x00; - byte constant proofType_TLSNotary = 0x10; byte constant proofType_Ledger = 0x30; - byte constant proofType_Android = 0x40; byte constant proofType_Native = 0xF0; byte constant proofStorage_IPFS = 0x01; + byte constant proofType_Android = 0x40; + byte constant proofType_TLSNotary = 0x10; + + string oraclize_network_name; uint8 constant networkID_auto = 0; + uint8 constant networkID_morden = 2; uint8 constant networkID_mainnet = 1; uint8 constant networkID_testnet = 2; - uint8 constant networkID_morden = 2; uint8 constant networkID_consensys = 161; - OraclizeAddrResolverI OAR; + mapping(bytes32 => bytes32) oraclize_randomDS_args; + mapping(bytes32 => bool) oraclize_randomDS_sessionKeysHashVerified; - OraclizeI oraclize; modifier oraclizeAPI { - if((address(OAR)==0)||(getCodeSize(address(OAR))==0)) + if ((address(OAR) == address(0)) || (getCodeSize(address(OAR)) == 0)) { oraclize_setNetwork(networkID_auto); - - if(address(oraclize) != OAR.getAddress()) + } + if (address(oraclize) != OAR.getAddress()) { oraclize = OraclizeI(OAR.getAddress()); - + } _; } - modifier coupon(string code){ - oraclize = OraclizeI(OAR.getAddress()); + + modifier oraclize_randomDS_proofVerify(bytes32 _queryId, string memory _result, bytes memory _proof) { + // RandomDS Proof Step 1: The prefix has to match 'LP\x01' (Ledger Proof version 1) + require((_proof[0] == "L") && (_proof[1] == "P") && (uint8(_proof[2]) == uint8(1))); + bool proofVerified = oraclize_randomDS_proofVerify__main(_proof, _queryId, bytes(_result), oraclize_getNetworkName()); + require(proofVerified); _; } - function oraclize_setNetwork(uint8 networkID) internal returns(bool){ + function oraclize_setNetwork(uint8 _networkID) internal returns (bool _networkSet) { return oraclize_setNetwork(); - networkID; // silence the warning and remain backwards compatible + _networkID; // silence the warning and remain backwards compatible } - function oraclize_setNetwork() internal returns(bool){ - if (getCodeSize(0x1d3B2638a7cC9f2CB3D298A3DA7a90B67E5506ed)>0){ //mainnet + + function oraclize_setNetworkName(string memory _network_name) internal { + oraclize_network_name = _network_name; + } + + function oraclize_getNetworkName() internal view returns (string memory _networkName) { + return oraclize_network_name; + } + + function oraclize_setNetwork() internal returns (bool _networkSet) { + if (getCodeSize(0x1d3B2638a7cC9f2CB3D298A3DA7a90B67E5506ed) > 0) { //mainnet OAR = OraclizeAddrResolverI(0x1d3B2638a7cC9f2CB3D298A3DA7a90B67E5506ed); oraclize_setNetworkName("eth_mainnet"); return true; } - if (getCodeSize(0xc03A2615D5efaf5F49F60B7BB6583eaec212fdf1)>0){ //ropsten testnet + if (getCodeSize(0xc03A2615D5efaf5F49F60B7BB6583eaec212fdf1) > 0) { //ropsten testnet OAR = OraclizeAddrResolverI(0xc03A2615D5efaf5F49F60B7BB6583eaec212fdf1); oraclize_setNetworkName("eth_ropsten3"); return true; } - if (getCodeSize(0xB7A07BcF2Ba2f2703b24C0691b5278999C59AC7e)>0){ //kovan testnet + if (getCodeSize(0xB7A07BcF2Ba2f2703b24C0691b5278999C59AC7e) > 0) { //kovan testnet OAR = OraclizeAddrResolverI(0xB7A07BcF2Ba2f2703b24C0691b5278999C59AC7e); oraclize_setNetworkName("eth_kovan"); return true; } - if (getCodeSize(0x146500cfd35B22E4A392Fe0aDc06De1a1368Ed48)>0){ //rinkeby testnet + if (getCodeSize(0x146500cfd35B22E4A392Fe0aDc06De1a1368Ed48) > 0) { //rinkeby testnet OAR = OraclizeAddrResolverI(0x146500cfd35B22E4A392Fe0aDc06De1a1368Ed48); oraclize_setNetworkName("eth_rinkeby"); return true; } - if (getCodeSize(0x6f485C8BF6fc43eA212E93BBF8ce046C7f1cb475)>0){ //ethereum-bridge + if (getCodeSize(0x6f485C8BF6fc43eA212E93BBF8ce046C7f1cb475) > 0) { //ethereum-bridge OAR = OraclizeAddrResolverI(0x6f485C8BF6fc43eA212E93BBF8ce046C7f1cb475); return true; } - if (getCodeSize(0x20e12A1F859B3FeaE5Fb2A0A32C18F5a65555bBF)>0){ //ether.camp ide + if (getCodeSize(0x20e12A1F859B3FeaE5Fb2A0A32C18F5a65555bBF) > 0) { //ether.camp ide OAR = OraclizeAddrResolverI(0x20e12A1F859B3FeaE5Fb2A0A32C18F5a65555bBF); return true; } - if (getCodeSize(0x51efaF4c8B3C9AfBD5aB9F4bbC82784Ab6ef8fAA)>0){ //browser-solidity + if (getCodeSize(0x51efaF4c8B3C9AfBD5aB9F4bbC82784Ab6ef8fAA) > 0) { //browser-solidity OAR = OraclizeAddrResolverI(0x51efaF4c8B3C9AfBD5aB9F4bbC82784Ab6ef8fAA); return true; } return false; } - function __callback(bytes32 myid, string result) public { - __callback(myid, result, new bytes(0)); + function __callback(bytes32 _myid, string memory _result) public { + __callback(_myid, _result, new bytes(0)); } - function __callback(bytes32 myid, string result, bytes proof) public { + + function __callback(bytes32 _myid, string memory _result, bytes memory _proof) public { return; - myid; result; proof; // Silence compiler warnings - } - - function oraclize_getPrice(string datasource) oraclizeAPI internal returns (uint){ - return oraclize.getPrice(datasource); - } - - function oraclize_getPrice(string datasource, uint gaslimit) oraclizeAPI internal returns (uint){ - return oraclize.getPrice(datasource, gaslimit); - } - - function oraclize_query(string datasource, string arg) oraclizeAPI internal returns (bytes32 id){ - uint price = oraclize.getPrice(datasource); - if (price > 1 ether + tx.gasprice*200000) return 0; // unexpectedly high price - return oraclize.query.value(price)(0, datasource, arg); - } - function oraclize_query(uint timestamp, string datasource, string arg) oraclizeAPI internal returns (bytes32 id){ - uint price = oraclize.getPrice(datasource); - if (price > 1 ether + tx.gasprice*200000) return 0; // unexpectedly high price - return oraclize.query.value(price)(timestamp, datasource, arg); - } - function oraclize_query(uint timestamp, string datasource, string arg, uint gaslimit) oraclizeAPI internal returns (bytes32 id){ - uint price = oraclize.getPrice(datasource, gaslimit); - if (price > 1 ether + tx.gasprice*gaslimit) return 0; // unexpectedly high price - return oraclize.query_withGasLimit.value(price)(timestamp, datasource, arg, gaslimit); - } - function oraclize_query(string datasource, string arg, uint gaslimit) oraclizeAPI internal returns (bytes32 id){ - uint price = oraclize.getPrice(datasource, gaslimit); - if (price > 1 ether + tx.gasprice*gaslimit) return 0; // unexpectedly high price - return oraclize.query_withGasLimit.value(price)(0, datasource, arg, gaslimit); - } - function oraclize_query(string datasource, string arg1, string arg2) oraclizeAPI internal returns (bytes32 id){ - uint price = oraclize.getPrice(datasource); - if (price > 1 ether + tx.gasprice*200000) return 0; // unexpectedly high price - return oraclize.query2.value(price)(0, datasource, arg1, arg2); - } - function oraclize_query(uint timestamp, string datasource, string arg1, string arg2) oraclizeAPI internal returns (bytes32 id){ - uint price = oraclize.getPrice(datasource); - if (price > 1 ether + tx.gasprice*200000) return 0; // unexpectedly high price - return oraclize.query2.value(price)(timestamp, datasource, arg1, arg2); - } - function oraclize_query(uint timestamp, string datasource, string arg1, string arg2, uint gaslimit) oraclizeAPI internal returns (bytes32 id){ - uint price = oraclize.getPrice(datasource, gaslimit); - if (price > 1 ether + tx.gasprice*gaslimit) return 0; // unexpectedly high price - return oraclize.query2_withGasLimit.value(price)(timestamp, datasource, arg1, arg2, gaslimit); - } - function oraclize_query(string datasource, string arg1, string arg2, uint gaslimit) oraclizeAPI internal returns (bytes32 id){ - uint price = oraclize.getPrice(datasource, gaslimit); - if (price > 1 ether + tx.gasprice*gaslimit) return 0; // unexpectedly high price - return oraclize.query2_withGasLimit.value(price)(0, datasource, arg1, arg2, gaslimit); - } - function oraclize_query(string datasource, string[] argN) oraclizeAPI internal returns (bytes32 id){ - uint price = oraclize.getPrice(datasource); - if (price > 1 ether + tx.gasprice*200000) return 0; // unexpectedly high price - bytes memory args = stra2cbor(argN); - return oraclize.queryN.value(price)(0, datasource, args); - } - function oraclize_query(uint timestamp, string datasource, string[] argN) oraclizeAPI internal returns (bytes32 id){ - uint price = oraclize.getPrice(datasource); - if (price > 1 ether + tx.gasprice*200000) return 0; // unexpectedly high price - bytes memory args = stra2cbor(argN); - return oraclize.queryN.value(price)(timestamp, datasource, args); - } - function oraclize_query(uint timestamp, string datasource, string[] argN, uint gaslimit) oraclizeAPI internal returns (bytes32 id){ - uint price = oraclize.getPrice(datasource, gaslimit); - if (price > 1 ether + tx.gasprice*gaslimit) return 0; // unexpectedly high price - bytes memory args = stra2cbor(argN); - return oraclize.queryN_withGasLimit.value(price)(timestamp, datasource, args, gaslimit); - } - function oraclize_query(string datasource, string[] argN, uint gaslimit) oraclizeAPI internal returns (bytes32 id){ - uint price = oraclize.getPrice(datasource, gaslimit); - if (price > 1 ether + tx.gasprice*gaslimit) return 0; // unexpectedly high price - bytes memory args = stra2cbor(argN); - return oraclize.queryN_withGasLimit.value(price)(0, datasource, args, gaslimit); - } - function oraclize_query(string datasource, string[1] args) oraclizeAPI internal returns (bytes32 id) { + _myid; _result; _proof; // Silence compiler warnings + } + + function oraclize_getPrice(string memory _datasource) oraclizeAPI internal returns (uint _queryPrice) { + return oraclize.getPrice(_datasource); + } + + function oraclize_getPrice(string memory _datasource, uint _gasLimit) oraclizeAPI internal returns (uint _queryPrice) { + return oraclize.getPrice(_datasource, _gasLimit); + } + + function oraclize_query(string memory _datasource, string memory _arg) oraclizeAPI internal returns (bytes32 _id) { + uint price = oraclize.getPrice(_datasource); + if (price > 1 ether + tx.gasprice * 200000) { + return 0; // Unexpectedly high price + } + return oraclize.query.value(price)(0, _datasource, _arg); + } + + function oraclize_query(uint _timestamp, string memory _datasource, string memory _arg) oraclizeAPI internal returns (bytes32 _id) { + uint price = oraclize.getPrice(_datasource); + if (price > 1 ether + tx.gasprice * 200000) { + return 0; // Unexpectedly high price + } + return oraclize.query.value(price)(_timestamp, _datasource, _arg); + } + + function oraclize_query(uint _timestamp, string memory _datasource, string memory _arg, uint _gasLimit) oraclizeAPI internal returns (bytes32 _id) { + uint price = oraclize.getPrice(_datasource,_gasLimit); + if (price > 1 ether + tx.gasprice * _gasLimit) { + return 0; // Unexpectedly high price + } + return oraclize.query_withGasLimit.value(price)(_timestamp, _datasource, _arg, _gasLimit); + } + + function oraclize_query(string memory _datasource, string memory _arg, uint _gasLimit) oraclizeAPI internal returns (bytes32 _id) { + uint price = oraclize.getPrice(_datasource, _gasLimit); + if (price > 1 ether + tx.gasprice * _gasLimit) { + return 0; // Unexpectedly high price + } + return oraclize.query_withGasLimit.value(price)(0, _datasource, _arg, _gasLimit); + } + + function oraclize_query(string memory _datasource, string memory _arg1, string memory _arg2) oraclizeAPI internal returns (bytes32 _id) { + uint price = oraclize.getPrice(_datasource); + if (price > 1 ether + tx.gasprice * 200000) { + return 0; // Unexpectedly high price + } + return oraclize.query2.value(price)(0, _datasource, _arg1, _arg2); + } + + function oraclize_query(uint _timestamp, string memory _datasource, string memory _arg1, string memory _arg2) oraclizeAPI internal returns (bytes32 _id) { + uint price = oraclize.getPrice(_datasource); + if (price > 1 ether + tx.gasprice * 200000) { + return 0; // Unexpectedly high price + } + return oraclize.query2.value(price)(_timestamp, _datasource, _arg1, _arg2); + } + + function oraclize_query(uint _timestamp, string memory _datasource, string memory _arg1, string memory _arg2, uint _gasLimit) oraclizeAPI internal returns (bytes32 _id) { + uint price = oraclize.getPrice(_datasource, _gasLimit); + if (price > 1 ether + tx.gasprice * _gasLimit) { + return 0; // Unexpectedly high price + } + return oraclize.query2_withGasLimit.value(price)(_timestamp, _datasource, _arg1, _arg2, _gasLimit); + } + + function oraclize_query(string memory _datasource, string memory _arg1, string memory _arg2, uint _gasLimit) oraclizeAPI internal returns (bytes32 _id) { + uint price = oraclize.getPrice(_datasource, _gasLimit); + if (price > 1 ether + tx.gasprice * _gasLimit) { + return 0; // Unexpectedly high price + } + return oraclize.query2_withGasLimit.value(price)(0, _datasource, _arg1, _arg2, _gasLimit); + } + + function oraclize_query(string memory _datasource, string[] memory _argN) oraclizeAPI internal returns (bytes32 _id) { + uint price = oraclize.getPrice(_datasource); + if (price > 1 ether + tx.gasprice * 200000) { + return 0; // Unexpectedly high price + } + bytes memory args = stra2cbor(_argN); + return oraclize.queryN.value(price)(0, _datasource, args); + } + + function oraclize_query(uint _timestamp, string memory _datasource, string[] memory _argN) oraclizeAPI internal returns (bytes32 _id) { + uint price = oraclize.getPrice(_datasource); + if (price > 1 ether + tx.gasprice * 200000) { + return 0; // Unexpectedly high price + } + bytes memory args = stra2cbor(_argN); + return oraclize.queryN.value(price)(_timestamp, _datasource, args); + } + + function oraclize_query(uint _timestamp, string memory _datasource, string[] memory _argN, uint _gasLimit) oraclizeAPI internal returns (bytes32 _id) { + uint price = oraclize.getPrice(_datasource, _gasLimit); + if (price > 1 ether + tx.gasprice * _gasLimit) { + return 0; // Unexpectedly high price + } + bytes memory args = stra2cbor(_argN); + return oraclize.queryN_withGasLimit.value(price)(_timestamp, _datasource, args, _gasLimit); + } + + function oraclize_query(string memory _datasource, string[] memory _argN, uint _gasLimit) oraclizeAPI internal returns (bytes32 _id) { + uint price = oraclize.getPrice(_datasource, _gasLimit); + if (price > 1 ether + tx.gasprice * _gasLimit) { + return 0; // Unexpectedly high price + } + bytes memory args = stra2cbor(_argN); + return oraclize.queryN_withGasLimit.value(price)(0, _datasource, args, _gasLimit); + } + + function oraclize_query(string memory _datasource, string[1] memory _args) oraclizeAPI internal returns (bytes32 _id) { string[] memory dynargs = new string[](1); - dynargs[0] = args[0]; - return oraclize_query(datasource, dynargs); + dynargs[0] = _args[0]; + return oraclize_query(_datasource, dynargs); } - function oraclize_query(uint timestamp, string datasource, string[1] args) oraclizeAPI internal returns (bytes32 id) { + + function oraclize_query(uint _timestamp, string memory _datasource, string[1] memory _args) oraclizeAPI internal returns (bytes32 _id) { string[] memory dynargs = new string[](1); - dynargs[0] = args[0]; - return oraclize_query(timestamp, datasource, dynargs); + dynargs[0] = _args[0]; + return oraclize_query(_timestamp, _datasource, dynargs); } - function oraclize_query(uint timestamp, string datasource, string[1] args, uint gaslimit) oraclizeAPI internal returns (bytes32 id) { + + function oraclize_query(uint _timestamp, string memory _datasource, string[1] memory _args, uint _gasLimit) oraclizeAPI internal returns (bytes32 _id) { string[] memory dynargs = new string[](1); - dynargs[0] = args[0]; - return oraclize_query(timestamp, datasource, dynargs, gaslimit); + dynargs[0] = _args[0]; + return oraclize_query(_timestamp, _datasource, dynargs, _gasLimit); } - function oraclize_query(string datasource, string[1] args, uint gaslimit) oraclizeAPI internal returns (bytes32 id) { + + function oraclize_query(string memory _datasource, string[1] memory _args, uint _gasLimit) oraclizeAPI internal returns (bytes32 _id) { string[] memory dynargs = new string[](1); - dynargs[0] = args[0]; - return oraclize_query(datasource, dynargs, gaslimit); + dynargs[0] = _args[0]; + return oraclize_query(_datasource, dynargs, _gasLimit); } - function oraclize_query(string datasource, string[2] args) oraclizeAPI internal returns (bytes32 id) { + function oraclize_query(string memory _datasource, string[2] memory _args) oraclizeAPI internal returns (bytes32 _id) { string[] memory dynargs = new string[](2); - dynargs[0] = args[0]; - dynargs[1] = args[1]; - return oraclize_query(datasource, dynargs); + dynargs[0] = _args[0]; + dynargs[1] = _args[1]; + return oraclize_query(_datasource, dynargs); } - function oraclize_query(uint timestamp, string datasource, string[2] args) oraclizeAPI internal returns (bytes32 id) { + + function oraclize_query(uint _timestamp, string memory _datasource, string[2] memory _args) oraclizeAPI internal returns (bytes32 _id) { string[] memory dynargs = new string[](2); - dynargs[0] = args[0]; - dynargs[1] = args[1]; - return oraclize_query(timestamp, datasource, dynargs); + dynargs[0] = _args[0]; + dynargs[1] = _args[1]; + return oraclize_query(_timestamp, _datasource, dynargs); } - function oraclize_query(uint timestamp, string datasource, string[2] args, uint gaslimit) oraclizeAPI internal returns (bytes32 id) { + + function oraclize_query(uint _timestamp, string memory _datasource, string[2] memory _args, uint _gasLimit) oraclizeAPI internal returns (bytes32 _id) { string[] memory dynargs = new string[](2); - dynargs[0] = args[0]; - dynargs[1] = args[1]; - return oraclize_query(timestamp, datasource, dynargs, gaslimit); + dynargs[0] = _args[0]; + dynargs[1] = _args[1]; + return oraclize_query(_timestamp, _datasource, dynargs, _gasLimit); } - function oraclize_query(string datasource, string[2] args, uint gaslimit) oraclizeAPI internal returns (bytes32 id) { + + function oraclize_query(string memory _datasource, string[2] memory _args, uint _gasLimit) oraclizeAPI internal returns (bytes32 _id) { string[] memory dynargs = new string[](2); - dynargs[0] = args[0]; - dynargs[1] = args[1]; - return oraclize_query(datasource, dynargs, gaslimit); + dynargs[0] = _args[0]; + dynargs[1] = _args[1]; + return oraclize_query(_datasource, dynargs, _gasLimit); } - function oraclize_query(string datasource, string[3] args) oraclizeAPI internal returns (bytes32 id) { + + function oraclize_query(string memory _datasource, string[3] memory _args) oraclizeAPI internal returns (bytes32 _id) { string[] memory dynargs = new string[](3); - dynargs[0] = args[0]; - dynargs[1] = args[1]; - dynargs[2] = args[2]; - return oraclize_query(datasource, dynargs); + dynargs[0] = _args[0]; + dynargs[1] = _args[1]; + dynargs[2] = _args[2]; + return oraclize_query(_datasource, dynargs); } - function oraclize_query(uint timestamp, string datasource, string[3] args) oraclizeAPI internal returns (bytes32 id) { + + function oraclize_query(uint _timestamp, string memory _datasource, string[3] memory _args) oraclizeAPI internal returns (bytes32 _id) { string[] memory dynargs = new string[](3); - dynargs[0] = args[0]; - dynargs[1] = args[1]; - dynargs[2] = args[2]; - return oraclize_query(timestamp, datasource, dynargs); + dynargs[0] = _args[0]; + dynargs[1] = _args[1]; + dynargs[2] = _args[2]; + return oraclize_query(_timestamp, _datasource, dynargs); } - function oraclize_query(uint timestamp, string datasource, string[3] args, uint gaslimit) oraclizeAPI internal returns (bytes32 id) { + + function oraclize_query(uint _timestamp, string memory _datasource, string[3] memory _args, uint _gasLimit) oraclizeAPI internal returns (bytes32 _id) { string[] memory dynargs = new string[](3); - dynargs[0] = args[0]; - dynargs[1] = args[1]; - dynargs[2] = args[2]; - return oraclize_query(timestamp, datasource, dynargs, gaslimit); + dynargs[0] = _args[0]; + dynargs[1] = _args[1]; + dynargs[2] = _args[2]; + return oraclize_query(_timestamp, _datasource, dynargs, _gasLimit); } - function oraclize_query(string datasource, string[3] args, uint gaslimit) oraclizeAPI internal returns (bytes32 id) { + + function oraclize_query(string memory _datasource, string[3] memory _args, uint _gasLimit) oraclizeAPI internal returns (bytes32 _id) { string[] memory dynargs = new string[](3); - dynargs[0] = args[0]; - dynargs[1] = args[1]; - dynargs[2] = args[2]; - return oraclize_query(datasource, dynargs, gaslimit); + dynargs[0] = _args[0]; + dynargs[1] = _args[1]; + dynargs[2] = _args[2]; + return oraclize_query(_datasource, dynargs, _gasLimit); } - function oraclize_query(string datasource, string[4] args) oraclizeAPI internal returns (bytes32 id) { + function oraclize_query(string memory _datasource, string[4] memory _args) oraclizeAPI internal returns (bytes32 _id) { string[] memory dynargs = new string[](4); - dynargs[0] = args[0]; - dynargs[1] = args[1]; - dynargs[2] = args[2]; - dynargs[3] = args[3]; - return oraclize_query(datasource, dynargs); + dynargs[0] = _args[0]; + dynargs[1] = _args[1]; + dynargs[2] = _args[2]; + dynargs[3] = _args[3]; + return oraclize_query(_datasource, dynargs); } - function oraclize_query(uint timestamp, string datasource, string[4] args) oraclizeAPI internal returns (bytes32 id) { + + function oraclize_query(uint _timestamp, string memory _datasource, string[4] memory _args) oraclizeAPI internal returns (bytes32 _id) { string[] memory dynargs = new string[](4); - dynargs[0] = args[0]; - dynargs[1] = args[1]; - dynargs[2] = args[2]; - dynargs[3] = args[3]; - return oraclize_query(timestamp, datasource, dynargs); + dynargs[0] = _args[0]; + dynargs[1] = _args[1]; + dynargs[2] = _args[2]; + dynargs[3] = _args[3]; + return oraclize_query(_timestamp, _datasource, dynargs); } - function oraclize_query(uint timestamp, string datasource, string[4] args, uint gaslimit) oraclizeAPI internal returns (bytes32 id) { + + function oraclize_query(uint _timestamp, string memory _datasource, string[4] memory _args, uint _gasLimit) oraclizeAPI internal returns (bytes32 _id) { string[] memory dynargs = new string[](4); - dynargs[0] = args[0]; - dynargs[1] = args[1]; - dynargs[2] = args[2]; - dynargs[3] = args[3]; - return oraclize_query(timestamp, datasource, dynargs, gaslimit); + dynargs[0] = _args[0]; + dynargs[1] = _args[1]; + dynargs[2] = _args[2]; + dynargs[3] = _args[3]; + return oraclize_query(_timestamp, _datasource, dynargs, _gasLimit); } - function oraclize_query(string datasource, string[4] args, uint gaslimit) oraclizeAPI internal returns (bytes32 id) { + + function oraclize_query(string memory _datasource, string[4] memory _args, uint _gasLimit) oraclizeAPI internal returns (bytes32 _id) { string[] memory dynargs = new string[](4); - dynargs[0] = args[0]; - dynargs[1] = args[1]; - dynargs[2] = args[2]; - dynargs[3] = args[3]; - return oraclize_query(datasource, dynargs, gaslimit); + dynargs[0] = _args[0]; + dynargs[1] = _args[1]; + dynargs[2] = _args[2]; + dynargs[3] = _args[3]; + return oraclize_query(_datasource, dynargs, _gasLimit); } - function oraclize_query(string datasource, string[5] args) oraclizeAPI internal returns (bytes32 id) { + + function oraclize_query(string memory _datasource, string[5] memory _args) oraclizeAPI internal returns (bytes32 _id) { string[] memory dynargs = new string[](5); - dynargs[0] = args[0]; - dynargs[1] = args[1]; - dynargs[2] = args[2]; - dynargs[3] = args[3]; - dynargs[4] = args[4]; - return oraclize_query(datasource, dynargs); - } - function oraclize_query(uint timestamp, string datasource, string[5] args) oraclizeAPI internal returns (bytes32 id) { + dynargs[0] = _args[0]; + dynargs[1] = _args[1]; + dynargs[2] = _args[2]; + dynargs[3] = _args[3]; + dynargs[4] = _args[4]; + return oraclize_query(_datasource, dynargs); + } + + function oraclize_query(uint _timestamp, string memory _datasource, string[5] memory _args) oraclizeAPI internal returns (bytes32 _id) { string[] memory dynargs = new string[](5); - dynargs[0] = args[0]; - dynargs[1] = args[1]; - dynargs[2] = args[2]; - dynargs[3] = args[3]; - dynargs[4] = args[4]; - return oraclize_query(timestamp, datasource, dynargs); - } - function oraclize_query(uint timestamp, string datasource, string[5] args, uint gaslimit) oraclizeAPI internal returns (bytes32 id) { + dynargs[0] = _args[0]; + dynargs[1] = _args[1]; + dynargs[2] = _args[2]; + dynargs[3] = _args[3]; + dynargs[4] = _args[4]; + return oraclize_query(_timestamp, _datasource, dynargs); + } + + function oraclize_query(uint _timestamp, string memory _datasource, string[5] memory _args, uint _gasLimit) oraclizeAPI internal returns (bytes32 _id) { string[] memory dynargs = new string[](5); - dynargs[0] = args[0]; - dynargs[1] = args[1]; - dynargs[2] = args[2]; - dynargs[3] = args[3]; - dynargs[4] = args[4]; - return oraclize_query(timestamp, datasource, dynargs, gaslimit); - } - function oraclize_query(string datasource, string[5] args, uint gaslimit) oraclizeAPI internal returns (bytes32 id) { + dynargs[0] = _args[0]; + dynargs[1] = _args[1]; + dynargs[2] = _args[2]; + dynargs[3] = _args[3]; + dynargs[4] = _args[4]; + return oraclize_query(_timestamp, _datasource, dynargs, _gasLimit); + } + + function oraclize_query(string memory _datasource, string[5] memory _args, uint _gasLimit) oraclizeAPI internal returns (bytes32 _id) { string[] memory dynargs = new string[](5); - dynargs[0] = args[0]; - dynargs[1] = args[1]; - dynargs[2] = args[2]; - dynargs[3] = args[3]; - dynargs[4] = args[4]; - return oraclize_query(datasource, dynargs, gaslimit); - } - function oraclize_query(string datasource, bytes[] argN) oraclizeAPI internal returns (bytes32 id){ - uint price = oraclize.getPrice(datasource); - if (price > 1 ether + tx.gasprice*200000) return 0; // unexpectedly high price - bytes memory args = ba2cbor(argN); - return oraclize.queryN.value(price)(0, datasource, args); - } - function oraclize_query(uint timestamp, string datasource, bytes[] argN) oraclizeAPI internal returns (bytes32 id){ - uint price = oraclize.getPrice(datasource); - if (price > 1 ether + tx.gasprice*200000) return 0; // unexpectedly high price - bytes memory args = ba2cbor(argN); - return oraclize.queryN.value(price)(timestamp, datasource, args); - } - function oraclize_query(uint timestamp, string datasource, bytes[] argN, uint gaslimit) oraclizeAPI internal returns (bytes32 id){ - uint price = oraclize.getPrice(datasource, gaslimit); - if (price > 1 ether + tx.gasprice*gaslimit) return 0; // unexpectedly high price - bytes memory args = ba2cbor(argN); - return oraclize.queryN_withGasLimit.value(price)(timestamp, datasource, args, gaslimit); - } - function oraclize_query(string datasource, bytes[] argN, uint gaslimit) oraclizeAPI internal returns (bytes32 id){ - uint price = oraclize.getPrice(datasource, gaslimit); - if (price > 1 ether + tx.gasprice*gaslimit) return 0; // unexpectedly high price - bytes memory args = ba2cbor(argN); - return oraclize.queryN_withGasLimit.value(price)(0, datasource, args, gaslimit); - } - function oraclize_query(string datasource, bytes[1] args) oraclizeAPI internal returns (bytes32 id) { + dynargs[0] = _args[0]; + dynargs[1] = _args[1]; + dynargs[2] = _args[2]; + dynargs[3] = _args[3]; + dynargs[4] = _args[4]; + return oraclize_query(_datasource, dynargs, _gasLimit); + } + + function oraclize_query(string memory _datasource, bytes[] memory _argN) oraclizeAPI internal returns (bytes32 _id) { + uint price = oraclize.getPrice(_datasource); + if (price > 1 ether + tx.gasprice * 200000) { + return 0; // Unexpectedly high price + } + bytes memory args = ba2cbor(_argN); + return oraclize.queryN.value(price)(0, _datasource, args); + } + + function oraclize_query(uint _timestamp, string memory _datasource, bytes[] memory _argN) oraclizeAPI internal returns (bytes32 _id) { + uint price = oraclize.getPrice(_datasource); + if (price > 1 ether + tx.gasprice * 200000) { + return 0; // Unexpectedly high price + } + bytes memory args = ba2cbor(_argN); + return oraclize.queryN.value(price)(_timestamp, _datasource, args); + } + + function oraclize_query(uint _timestamp, string memory _datasource, bytes[] memory _argN, uint _gasLimit) oraclizeAPI internal returns (bytes32 _id) { + uint price = oraclize.getPrice(_datasource, _gasLimit); + if (price > 1 ether + tx.gasprice * _gasLimit) { + return 0; // Unexpectedly high price + } + bytes memory args = ba2cbor(_argN); + return oraclize.queryN_withGasLimit.value(price)(_timestamp, _datasource, args, _gasLimit); + } + + function oraclize_query(string memory _datasource, bytes[] memory _argN, uint _gasLimit) oraclizeAPI internal returns (bytes32 _id) { + uint price = oraclize.getPrice(_datasource, _gasLimit); + if (price > 1 ether + tx.gasprice * _gasLimit) { + return 0; // Unexpectedly high price + } + bytes memory args = ba2cbor(_argN); + return oraclize.queryN_withGasLimit.value(price)(0, _datasource, args, _gasLimit); + } + + function oraclize_query(string memory _datasource, bytes[1] memory _args) oraclizeAPI internal returns (bytes32 _id) { bytes[] memory dynargs = new bytes[](1); - dynargs[0] = args[0]; - return oraclize_query(datasource, dynargs); + dynargs[0] = _args[0]; + return oraclize_query(_datasource, dynargs); } - function oraclize_query(uint timestamp, string datasource, bytes[1] args) oraclizeAPI internal returns (bytes32 id) { + + function oraclize_query(uint _timestamp, string memory _datasource, bytes[1] memory _args) oraclizeAPI internal returns (bytes32 _id) { bytes[] memory dynargs = new bytes[](1); - dynargs[0] = args[0]; - return oraclize_query(timestamp, datasource, dynargs); + dynargs[0] = _args[0]; + return oraclize_query(_timestamp, _datasource, dynargs); } - function oraclize_query(uint timestamp, string datasource, bytes[1] args, uint gaslimit) oraclizeAPI internal returns (bytes32 id) { + + function oraclize_query(uint _timestamp, string memory _datasource, bytes[1] memory _args, uint _gasLimit) oraclizeAPI internal returns (bytes32 _id) { bytes[] memory dynargs = new bytes[](1); - dynargs[0] = args[0]; - return oraclize_query(timestamp, datasource, dynargs, gaslimit); + dynargs[0] = _args[0]; + return oraclize_query(_timestamp, _datasource, dynargs, _gasLimit); } - function oraclize_query(string datasource, bytes[1] args, uint gaslimit) oraclizeAPI internal returns (bytes32 id) { + + function oraclize_query(string memory _datasource, bytes[1] memory _args, uint _gasLimit) oraclizeAPI internal returns (bytes32 _id) { bytes[] memory dynargs = new bytes[](1); - dynargs[0] = args[0]; - return oraclize_query(datasource, dynargs, gaslimit); + dynargs[0] = _args[0]; + return oraclize_query(_datasource, dynargs, _gasLimit); } - function oraclize_query(string datasource, bytes[2] args) oraclizeAPI internal returns (bytes32 id) { + function oraclize_query(string memory _datasource, bytes[2] memory _args) oraclizeAPI internal returns (bytes32 _id) { bytes[] memory dynargs = new bytes[](2); - dynargs[0] = args[0]; - dynargs[1] = args[1]; - return oraclize_query(datasource, dynargs); + dynargs[0] = _args[0]; + dynargs[1] = _args[1]; + return oraclize_query(_datasource, dynargs); } - function oraclize_query(uint timestamp, string datasource, bytes[2] args) oraclizeAPI internal returns (bytes32 id) { + + function oraclize_query(uint _timestamp, string memory _datasource, bytes[2] memory _args) oraclizeAPI internal returns (bytes32 _id) { bytes[] memory dynargs = new bytes[](2); - dynargs[0] = args[0]; - dynargs[1] = args[1]; - return oraclize_query(timestamp, datasource, dynargs); + dynargs[0] = _args[0]; + dynargs[1] = _args[1]; + return oraclize_query(_timestamp, _datasource, dynargs); } - function oraclize_query(uint timestamp, string datasource, bytes[2] args, uint gaslimit) oraclizeAPI internal returns (bytes32 id) { + + function oraclize_query(uint _timestamp, string memory _datasource, bytes[2] memory _args, uint _gasLimit) oraclizeAPI internal returns (bytes32 _id) { bytes[] memory dynargs = new bytes[](2); - dynargs[0] = args[0]; - dynargs[1] = args[1]; - return oraclize_query(timestamp, datasource, dynargs, gaslimit); + dynargs[0] = _args[0]; + dynargs[1] = _args[1]; + return oraclize_query(_timestamp, _datasource, dynargs, _gasLimit); } - function oraclize_query(string datasource, bytes[2] args, uint gaslimit) oraclizeAPI internal returns (bytes32 id) { + + function oraclize_query(string memory _datasource, bytes[2] memory _args, uint _gasLimit) oraclizeAPI internal returns (bytes32 _id) { bytes[] memory dynargs = new bytes[](2); - dynargs[0] = args[0]; - dynargs[1] = args[1]; - return oraclize_query(datasource, dynargs, gaslimit); + dynargs[0] = _args[0]; + dynargs[1] = _args[1]; + return oraclize_query(_datasource, dynargs, _gasLimit); } - function oraclize_query(string datasource, bytes[3] args) oraclizeAPI internal returns (bytes32 id) { + + function oraclize_query(string memory _datasource, bytes[3] memory _args) oraclizeAPI internal returns (bytes32 _id) { bytes[] memory dynargs = new bytes[](3); - dynargs[0] = args[0]; - dynargs[1] = args[1]; - dynargs[2] = args[2]; - return oraclize_query(datasource, dynargs); + dynargs[0] = _args[0]; + dynargs[1] = _args[1]; + dynargs[2] = _args[2]; + return oraclize_query(_datasource, dynargs); } - function oraclize_query(uint timestamp, string datasource, bytes[3] args) oraclizeAPI internal returns (bytes32 id) { + + function oraclize_query(uint _timestamp, string memory _datasource, bytes[3] memory _args) oraclizeAPI internal returns (bytes32 _id) { bytes[] memory dynargs = new bytes[](3); - dynargs[0] = args[0]; - dynargs[1] = args[1]; - dynargs[2] = args[2]; - return oraclize_query(timestamp, datasource, dynargs); + dynargs[0] = _args[0]; + dynargs[1] = _args[1]; + dynargs[2] = _args[2]; + return oraclize_query(_timestamp, _datasource, dynargs); } - function oraclize_query(uint timestamp, string datasource, bytes[3] args, uint gaslimit) oraclizeAPI internal returns (bytes32 id) { + + function oraclize_query(uint _timestamp, string memory _datasource, bytes[3] memory _args, uint _gasLimit) oraclizeAPI internal returns (bytes32 _id) { bytes[] memory dynargs = new bytes[](3); - dynargs[0] = args[0]; - dynargs[1] = args[1]; - dynargs[2] = args[2]; - return oraclize_query(timestamp, datasource, dynargs, gaslimit); + dynargs[0] = _args[0]; + dynargs[1] = _args[1]; + dynargs[2] = _args[2]; + return oraclize_query(_timestamp, _datasource, dynargs, _gasLimit); } - function oraclize_query(string datasource, bytes[3] args, uint gaslimit) oraclizeAPI internal returns (bytes32 id) { + + function oraclize_query(string memory _datasource, bytes[3] memory _args, uint _gasLimit) oraclizeAPI internal returns (bytes32 _id) { bytes[] memory dynargs = new bytes[](3); - dynargs[0] = args[0]; - dynargs[1] = args[1]; - dynargs[2] = args[2]; - return oraclize_query(datasource, dynargs, gaslimit); + dynargs[0] = _args[0]; + dynargs[1] = _args[1]; + dynargs[2] = _args[2]; + return oraclize_query(_datasource, dynargs, _gasLimit); } - function oraclize_query(string datasource, bytes[4] args) oraclizeAPI internal returns (bytes32 id) { + function oraclize_query(string memory _datasource, bytes[4] memory _args) oraclizeAPI internal returns (bytes32 _id) { bytes[] memory dynargs = new bytes[](4); - dynargs[0] = args[0]; - dynargs[1] = args[1]; - dynargs[2] = args[2]; - dynargs[3] = args[3]; - return oraclize_query(datasource, dynargs); + dynargs[0] = _args[0]; + dynargs[1] = _args[1]; + dynargs[2] = _args[2]; + dynargs[3] = _args[3]; + return oraclize_query(_datasource, dynargs); } - function oraclize_query(uint timestamp, string datasource, bytes[4] args) oraclizeAPI internal returns (bytes32 id) { + + function oraclize_query(uint _timestamp, string memory _datasource, bytes[4] memory _args) oraclizeAPI internal returns (bytes32 _id) { bytes[] memory dynargs = new bytes[](4); - dynargs[0] = args[0]; - dynargs[1] = args[1]; - dynargs[2] = args[2]; - dynargs[3] = args[3]; - return oraclize_query(timestamp, datasource, dynargs); + dynargs[0] = _args[0]; + dynargs[1] = _args[1]; + dynargs[2] = _args[2]; + dynargs[3] = _args[3]; + return oraclize_query(_timestamp, _datasource, dynargs); } - function oraclize_query(uint timestamp, string datasource, bytes[4] args, uint gaslimit) oraclizeAPI internal returns (bytes32 id) { + + function oraclize_query(uint _timestamp, string memory _datasource, bytes[4] memory _args, uint _gasLimit) oraclizeAPI internal returns (bytes32 _id) { bytes[] memory dynargs = new bytes[](4); - dynargs[0] = args[0]; - dynargs[1] = args[1]; - dynargs[2] = args[2]; - dynargs[3] = args[3]; - return oraclize_query(timestamp, datasource, dynargs, gaslimit); + dynargs[0] = _args[0]; + dynargs[1] = _args[1]; + dynargs[2] = _args[2]; + dynargs[3] = _args[3]; + return oraclize_query(_timestamp, _datasource, dynargs, _gasLimit); } - function oraclize_query(string datasource, bytes[4] args, uint gaslimit) oraclizeAPI internal returns (bytes32 id) { + + function oraclize_query(string memory _datasource, bytes[4] memory _args, uint _gasLimit) oraclizeAPI internal returns (bytes32 _id) { bytes[] memory dynargs = new bytes[](4); - dynargs[0] = args[0]; - dynargs[1] = args[1]; - dynargs[2] = args[2]; - dynargs[3] = args[3]; - return oraclize_query(datasource, dynargs, gaslimit); + dynargs[0] = _args[0]; + dynargs[1] = _args[1]; + dynargs[2] = _args[2]; + dynargs[3] = _args[3]; + return oraclize_query(_datasource, dynargs, _gasLimit); } - function oraclize_query(string datasource, bytes[5] args) oraclizeAPI internal returns (bytes32 id) { - bytes[] memory dynargs = new bytes[](5); - dynargs[0] = args[0]; - dynargs[1] = args[1]; - dynargs[2] = args[2]; - dynargs[3] = args[3]; - dynargs[4] = args[4]; - return oraclize_query(datasource, dynargs); - } - function oraclize_query(uint timestamp, string datasource, bytes[5] args) oraclizeAPI internal returns (bytes32 id) { - bytes[] memory dynargs = new bytes[](5); - dynargs[0] = args[0]; - dynargs[1] = args[1]; - dynargs[2] = args[2]; - dynargs[3] = args[3]; - dynargs[4] = args[4]; - return oraclize_query(timestamp, datasource, dynargs); - } - function oraclize_query(uint timestamp, string datasource, bytes[5] args, uint gaslimit) oraclizeAPI internal returns (bytes32 id) { + + function oraclize_query(string memory _datasource, bytes[5] memory _args) oraclizeAPI internal returns (bytes32 _id) { bytes[] memory dynargs = new bytes[](5); - dynargs[0] = args[0]; - dynargs[1] = args[1]; - dynargs[2] = args[2]; - dynargs[3] = args[3]; - dynargs[4] = args[4]; - return oraclize_query(timestamp, datasource, dynargs, gaslimit); - } - function oraclize_query(string datasource, bytes[5] args, uint gaslimit) oraclizeAPI internal returns (bytes32 id) { + dynargs[0] = _args[0]; + dynargs[1] = _args[1]; + dynargs[2] = _args[2]; + dynargs[3] = _args[3]; + dynargs[4] = _args[4]; + return oraclize_query(_datasource, dynargs); + } + + function oraclize_query(uint _timestamp, string memory _datasource, bytes[5] memory _args) oraclizeAPI internal returns (bytes32 _id) { bytes[] memory dynargs = new bytes[](5); - dynargs[0] = args[0]; - dynargs[1] = args[1]; - dynargs[2] = args[2]; - dynargs[3] = args[3]; - dynargs[4] = args[4]; - return oraclize_query(datasource, dynargs, gaslimit); + dynargs[0] = _args[0]; + dynargs[1] = _args[1]; + dynargs[2] = _args[2]; + dynargs[3] = _args[3]; + dynargs[4] = _args[4]; + return oraclize_query(_timestamp, _datasource, dynargs); } - function oraclize_cbAddress() oraclizeAPI internal returns (address){ - return oraclize.cbAddress(); + function oraclize_query(uint _timestamp, string memory _datasource, bytes[5] memory _args, uint _gasLimit) oraclizeAPI internal returns (bytes32 _id) { + bytes[] memory dynargs = new bytes[](5); + dynargs[0] = _args[0]; + dynargs[1] = _args[1]; + dynargs[2] = _args[2]; + dynargs[3] = _args[3]; + dynargs[4] = _args[4]; + return oraclize_query(_timestamp, _datasource, dynargs, _gasLimit); } - function oraclize_setProof(byte proofP) oraclizeAPI internal { - return oraclize.setProofType(proofP); + + function oraclize_query(string memory _datasource, bytes[5] memory _args, uint _gasLimit) oraclizeAPI internal returns (bytes32 _id) { + bytes[] memory dynargs = new bytes[](5); + dynargs[0] = _args[0]; + dynargs[1] = _args[1]; + dynargs[2] = _args[2]; + dynargs[3] = _args[3]; + dynargs[4] = _args[4]; + return oraclize_query(_datasource, dynargs, _gasLimit); } - function oraclize_setCustomGasPrice(uint gasPrice) oraclizeAPI internal { - return oraclize.setCustomGasPrice(gasPrice); + + function oraclize_setProof(byte _proofP) oraclizeAPI internal { + return oraclize.setProofType(_proofP); } - function oraclize_randomDS_getSessionPubKeyHash() oraclizeAPI internal returns (bytes32){ - return oraclize.randomDS_getSessionPubKeyHash(); + + function oraclize_cbAddress() oraclizeAPI internal returns (address _callbackAddress) { + return oraclize.cbAddress(); } - function getCodeSize(address _addr) constant internal returns(uint _size) { + function getCodeSize(address _addr) view internal returns (uint _size) { assembly { _size := extcodesize(_addr) } } - function parseAddr(string _a) internal pure returns (address){ + function oraclize_setCustomGasPrice(uint _gasPrice) oraclizeAPI internal { + return oraclize.setCustomGasPrice(_gasPrice); + } + + function oraclize_randomDS_getSessionPubKeyHash() oraclizeAPI internal returns (bytes32 _sessionKeyHash) { + return oraclize.randomDS_getSessionPubKeyHash(); + } + + function parseAddr(string memory _a) internal pure returns (address _parsedAddress) { bytes memory tmp = bytes(_a); uint160 iaddr = 0; uint160 b1; uint160 b2; - for (uint i=2; i<2+2*20; i+=2){ + for (uint i = 2; i < 2 + 2 * 20; i += 2) { iaddr *= 256; - b1 = uint160(tmp[i]); - b2 = uint160(tmp[i+1]); - if ((b1 >= 97)&&(b1 <= 102)) b1 -= 87; - else if ((b1 >= 65)&&(b1 <= 70)) b1 -= 55; - else if ((b1 >= 48)&&(b1 <= 57)) b1 -= 48; - if ((b2 >= 97)&&(b2 <= 102)) b2 -= 87; - else if ((b2 >= 65)&&(b2 <= 70)) b2 -= 55; - else if ((b2 >= 48)&&(b2 <= 57)) b2 -= 48; - iaddr += (b1*16+b2); + b1 = uint160(uint8(tmp[i])); + b2 = uint160(uint8(tmp[i + 1])); + if ((b1 >= 97) && (b1 <= 102)) { + b1 -= 87; + } else if ((b1 >= 65) && (b1 <= 70)) { + b1 -= 55; + } else if ((b1 >= 48) && (b1 <= 57)) { + b1 -= 48; + } + if ((b2 >= 97) && (b2 <= 102)) { + b2 -= 87; + } else if ((b2 >= 65) && (b2 <= 70)) { + b2 -= 55; + } else if ((b2 >= 48) && (b2 <= 57)) { + b2 -= 48; + } + iaddr += (b1 * 16 + b2); } return address(iaddr); } - function strCompare(string _a, string _b) internal pure returns (int) { + function strCompare(string memory _a, string memory _b) internal pure returns (int _returnCode) { bytes memory a = bytes(_a); bytes memory b = bytes(_b); uint minLength = a.length; - if (b.length < minLength) minLength = b.length; - for (uint i = 0; i < minLength; i ++) - if (a[i] < b[i]) + if (b.length < minLength) { + minLength = b.length; + } + for (uint i = 0; i < minLength; i ++) { + if (a[i] < b[i]) { return -1; - else if (a[i] > b[i]) + } else if (a[i] > b[i]) { return 1; - if (a.length < b.length) + } + } + if (a.length < b.length) { return -1; - else if (a.length > b.length) + } else if (a.length > b.length) { return 1; - else + } else { return 0; + } } - function indexOf(string _haystack, string _needle) internal pure returns (int) { + function indexOf(string memory _haystack, string memory _needle) internal pure returns (int _returnCode) { bytes memory h = bytes(_haystack); bytes memory n = bytes(_needle); - if(h.length < 1 || n.length < 1 || (n.length > h.length)) + if (h.length < 1 || n.length < 1 || (n.length > h.length)) { return -1; - else if(h.length > (2**128 -1)) + } else if (h.length > (2 ** 128 - 1)) { return -1; - else - { + } else { uint subindex = 0; - for (uint i = 0; i < h.length; i ++) - { - if (h[i] == n[0]) - { + for (uint i = 0; i < h.length; i++) { + if (h[i] == n[0]) { subindex = 1; - while(subindex < n.length && (i + subindex) < h.length && h[i + subindex] == n[subindex]) - { + while(subindex < n.length && (i + subindex) < h.length && h[i + subindex] == n[subindex]) { subindex++; } - if(subindex == n.length) + if (subindex == n.length) { return int(i); + } } } return -1; } } - function strConcat(string _a, string _b, string _c, string _d, string _e) internal pure returns (string) { + function strConcat(string memory _a, string memory _b) internal pure returns (string memory _concatenatedString) { + return strConcat(_a, _b, "", "", ""); + } + + function strConcat(string memory _a, string memory _b, string memory _c) internal pure returns (string memory _concatenatedString) { + return strConcat(_a, _b, _c, "", ""); + } + + function strConcat(string memory _a, string memory _b, string memory _c, string memory _d) internal pure returns (string memory _concatenatedString) { + return strConcat(_a, _b, _c, _d, ""); + } + + function strConcat(string memory _a, string memory _b, string memory _c, string memory _d, string memory _e) internal pure returns (string memory _concatenatedString) { bytes memory _ba = bytes(_a); bytes memory _bb = bytes(_b); bytes memory _bc = bytes(_c); @@ -824,115 +956,141 @@ contract usingOraclize { string memory abcde = new string(_ba.length + _bb.length + _bc.length + _bd.length + _be.length); bytes memory babcde = bytes(abcde); uint k = 0; - for (uint i = 0; i < _ba.length; i++) babcde[k++] = _ba[i]; - for (i = 0; i < _bb.length; i++) babcde[k++] = _bb[i]; - for (i = 0; i < _bc.length; i++) babcde[k++] = _bc[i]; - for (i = 0; i < _bd.length; i++) babcde[k++] = _bd[i]; - for (i = 0; i < _be.length; i++) babcde[k++] = _be[i]; + uint i = 0; + for (i = 0; i < _ba.length; i++) { + babcde[k++] = _ba[i]; + } + for (i = 0; i < _bb.length; i++) { + babcde[k++] = _bb[i]; + } + for (i = 0; i < _bc.length; i++) { + babcde[k++] = _bc[i]; + } + for (i = 0; i < _bd.length; i++) { + babcde[k++] = _bd[i]; + } + for (i = 0; i < _be.length; i++) { + babcde[k++] = _be[i]; + } return string(babcde); } - function strConcat(string _a, string _b, string _c, string _d) internal pure returns (string) { - return strConcat(_a, _b, _c, _d, ""); - } - - function strConcat(string _a, string _b, string _c) internal pure returns (string) { - return strConcat(_a, _b, _c, "", ""); + function safeParseInt(string memory _a) internal pure returns (uint _parsedInt) { + return safeParseInt(_a, 0); } - function strConcat(string _a, string _b) internal pure returns (string) { - return strConcat(_a, _b, "", "", ""); + function safeParseInt(string memory _a, uint _b) internal pure returns (uint _parsedInt) { + bytes memory bresult = bytes(_a); + uint mint = 0; + bool decimals = false; + for (uint i = 0; i < bresult.length; i++) { + if ((uint(uint8(bresult[i])) >= 48) && (uint(uint8(bresult[i])) <= 57)) { + if (decimals) { + if (_b == 0) break; + else _b--; + } + mint *= 10; + mint += uint(uint8(bresult[i])) - 48; + } else if (uint(uint8(bresult[i])) == 46) { + require(!decimals, 'More than one decimal encountered in string!'); + decimals = true; + } else { + revert("Non-numeral character encountered in string!"); + } + } + if (_b > 0) { + mint *= 10 ** _b; + } + return mint; } - // parseInt - function parseInt(string _a) internal pure returns (uint) { + function parseInt(string memory _a) internal pure returns (uint _parsedInt) { return parseInt(_a, 0); } - // parseInt(parseFloat*10^_b) - function parseInt(string _a, uint _b) internal pure returns (uint) { + function parseInt(string memory _a, uint _b) internal pure returns (uint _parsedInt) { bytes memory bresult = bytes(_a); uint mint = 0; bool decimals = false; - for (uint i=0; i= 48)&&(bresult[i] <= 57)){ - if (decimals){ - if (_b == 0) break; - else _b--; + for (uint i = 0; i < bresult.length; i++) { + if ((uint(uint8(bresult[i])) >= 48) && (uint(uint8(bresult[i])) <= 57)) { + if (decimals) { + if (_b == 0) { + break; + } else { + _b--; + } } mint *= 10; - mint += uint(bresult[i]) - 48; - } else if (bresult[i] == 46) decimals = true; + mint += uint(uint8(bresult[i])) - 48; + } else if (uint(uint8(bresult[i])) == 46) { + decimals = true; + } + } + if (_b > 0) { + mint *= 10 ** _b; } - if (_b > 0) mint *= 10**_b; return mint; } - function uint2str(uint i) internal pure returns (string){ - if (i == 0) return "0"; - uint j = i; + function uint2str(uint _i) internal pure returns (string memory _uintAsString) { + if (_i == 0) { + return "0"; + } + uint j = _i; uint len; - while (j != 0){ + while (j != 0) { len++; j /= 10; } bytes memory bstr = new bytes(len); uint k = len - 1; - while (i != 0){ - bstr[k--] = byte(48 + i % 10); - i /= 10; + while (_i != 0) { + bstr[k--] = byte(uint8(48 + _i % 10)); + _i /= 10; } return string(bstr); } - using CBOR for Buffer.buffer; - function stra2cbor(string[] arr) internal pure returns (bytes) { + function stra2cbor(string[] memory _arr) internal pure returns (bytes memory _cborEncoding) { safeMemoryCleaner(); Buffer.buffer memory buf; Buffer.init(buf, 1024); buf.startArray(); - for (uint i = 0; i < arr.length; i++) { - buf.encodeString(arr[i]); + for (uint i = 0; i < _arr.length; i++) { + buf.encodeString(_arr[i]); } buf.endSequence(); return buf.buf; } - function ba2cbor(bytes[] arr) internal pure returns (bytes) { + function ba2cbor(bytes[] memory _arr) internal pure returns (bytes memory _cborEncoding) { safeMemoryCleaner(); Buffer.buffer memory buf; Buffer.init(buf, 1024); buf.startArray(); - for (uint i = 0; i < arr.length; i++) { - buf.encodeBytes(arr[i]); + for (uint i = 0; i < _arr.length; i++) { + buf.encodeBytes(_arr[i]); } buf.endSequence(); return buf.buf; } - string oraclize_network_name; - function oraclize_setNetworkName(string _network_name) internal { - oraclize_network_name = _network_name; - } - - function oraclize_getNetworkName() internal view returns (string) { - return oraclize_network_name; - } - - function oraclize_newRandomDSQuery(uint _delay, uint _nbytes, uint _customGasLimit) internal returns (bytes32){ + function oraclize_newRandomDSQuery(uint _delay, uint _nbytes, uint _customGasLimit) internal returns (bytes32 _queryId) { require((_nbytes > 0) && (_nbytes <= 32)); - // Convert from seconds to ledger timer ticks - _delay *= 10; + _delay *= 10; // Convert from seconds to ledger timer ticks bytes memory nbytes = new bytes(1); - nbytes[0] = byte(_nbytes); + nbytes[0] = byte(uint8(_nbytes)); bytes memory unonce = new bytes(32); bytes memory sessionKeyHash = new bytes(32); bytes32 sessionKeyHash_bytes32 = oraclize_randomDS_getSessionPubKeyHash(); assembly { mstore(unonce, 0x20) - // the following variables can be relaxed - // check relaxed random contract under ethereum-examples repo - // for an idea on how to override and replace comit hash vars + /* + The following variables can be relaxed. + Check the relaxed random contract at https://github.com/oraclize/ethereum-examples + for an idea on how to override and replace commit hash variables. + */ mstore(add(unonce, 0x20), xor(blockhash(sub(number, 1)), xor(coinbase, timestamp))) mstore(sessionKeyHash, 0x20) mstore(add(sessionKeyHash, 0x20), sessionKeyHash_bytes32) @@ -941,15 +1099,11 @@ contract usingOraclize { assembly { mstore(add(delay, 0x20), _delay) } - bytes memory delay_bytes8 = new bytes(8); copyBytes(delay, 24, 8, delay_bytes8, 0); - bytes[4] memory args = [unonce, nbytes, sessionKeyHash, delay]; bytes32 queryId = oraclize_query("random", args, _customGasLimit); - bytes memory delay_bytes8_left = new bytes(8); - assembly { let x := mload(add(delay_bytes8, 0x20)) mstore8(add(delay_bytes8_left, 0x27), div(x, 0x100000000000000000000000000000000000000000000000000000000000000)) @@ -960,248 +1114,214 @@ contract usingOraclize { mstore8(add(delay_bytes8_left, 0x22), div(x, 0x10000000000000000000000000000000000000000000000000000)) mstore8(add(delay_bytes8_left, 0x21), div(x, 0x100000000000000000000000000000000000000000000000000)) mstore8(add(delay_bytes8_left, 0x20), div(x, 0x1000000000000000000000000000000000000000000000000)) - } - - oraclize_randomDS_setCommitment(queryId, keccak256(delay_bytes8_left, args[1], sha256(args[0]), args[2])); + oraclize_randomDS_setCommitment(queryId, keccak256(abi.encodePacked(delay_bytes8_left, args[1], sha256(args[0]), args[2]))); return queryId; } - function oraclize_randomDS_setCommitment(bytes32 queryId, bytes32 commitment) internal { - oraclize_randomDS_args[queryId] = commitment; + function oraclize_randomDS_setCommitment(bytes32 _queryId, bytes32 _commitment) internal { + oraclize_randomDS_args[_queryId] = _commitment; } - mapping(bytes32=>bytes32) oraclize_randomDS_args; - mapping(bytes32=>bool) oraclize_randomDS_sessionKeysHashVerified; - - function verifySig(bytes32 tosignh, bytes dersig, bytes pubkey) internal returns (bool){ + function verifySig(bytes32 _tosignh, bytes memory _dersig, bytes memory _pubkey) internal returns (bool _sigVerified) { bool sigok; address signer; - bytes32 sigr; bytes32 sigs; - bytes memory sigr_ = new bytes(32); - uint offset = 4+(uint(dersig[3]) - 0x20); - sigr_ = copyBytes(dersig, offset, 32, sigr_, 0); + uint offset = 4 + (uint(uint8(_dersig[3])) - 0x20); + sigr_ = copyBytes(_dersig, offset, 32, sigr_, 0); bytes memory sigs_ = new bytes(32); offset += 32 + 2; - sigs_ = copyBytes(dersig, offset+(uint(dersig[offset-1]) - 0x20), 32, sigs_, 0); - + sigs_ = copyBytes(_dersig, offset + (uint(uint8(_dersig[offset - 1])) - 0x20), 32, sigs_, 0); assembly { sigr := mload(add(sigr_, 32)) sigs := mload(add(sigs_, 32)) } - - - (sigok, signer) = safer_ecrecover(tosignh, 27, sigr, sigs); - if (address(keccak256(pubkey)) == signer) return true; - else { - (sigok, signer) = safer_ecrecover(tosignh, 28, sigr, sigs); - return (address(keccak256(pubkey)) == signer); + (sigok, signer) = safer_ecrecover(_tosignh, 27, sigr, sigs); + if (address(uint160(uint256(keccak256(_pubkey)))) == signer) { + return true; + } else { + (sigok, signer) = safer_ecrecover(_tosignh, 28, sigr, sigs); + return (address(uint160(uint256(keccak256(_pubkey)))) == signer); } } - function oraclize_randomDS_proofVerify__sessionKeyValidity(bytes proof, uint sig2offset) internal returns (bool) { + function oraclize_randomDS_proofVerify__sessionKeyValidity(bytes memory _proof, uint _sig2offset) internal returns (bool _proofVerified) { bool sigok; - - // Step 6: verify the attestation signature, APPKEY1 must sign the sessionKey from the correct ledger app (CODEHASH) - bytes memory sig2 = new bytes(uint(proof[sig2offset+1])+2); - copyBytes(proof, sig2offset, sig2.length, sig2, 0); - + // Random DS Proof Step 6: Verify the attestation signature, APPKEY1 must sign the sessionKey from the correct ledger app (CODEHASH) + bytes memory sig2 = new bytes(uint(uint8(_proof[_sig2offset + 1])) + 2); + copyBytes(_proof, _sig2offset, sig2.length, sig2, 0); bytes memory appkey1_pubkey = new bytes(64); - copyBytes(proof, 3+1, 64, appkey1_pubkey, 0); - - bytes memory tosign2 = new bytes(1+65+32); - tosign2[0] = byte(1); //role - copyBytes(proof, sig2offset-65, 65, tosign2, 1); + copyBytes(_proof, 3 + 1, 64, appkey1_pubkey, 0); + bytes memory tosign2 = new bytes(1 + 65 + 32); + tosign2[0] = byte(uint8(1)); //role + copyBytes(_proof, _sig2offset - 65, 65, tosign2, 1); bytes memory CODEHASH = hex"fd94fa71bc0ba10d39d464d0d8f465efeef0a2764e3887fcc9df41ded20f505c"; - copyBytes(CODEHASH, 0, 32, tosign2, 1+65); + copyBytes(CODEHASH, 0, 32, tosign2, 1 + 65); sigok = verifySig(sha256(tosign2), sig2, appkey1_pubkey); - - if (sigok == false) return false; - - - // Step 7: verify the APPKEY1 provenance (must be signed by Ledger) + if (!sigok) { + return false; + } + // Random DS Proof Step 7: Verify the APPKEY1 provenance (must be signed by Ledger) bytes memory LEDGERKEY = hex"7fb956469c5c9b89840d55b43537e66a98dd4811ea0a27224272c2e5622911e8537a2f8e86a46baec82864e98dd01e9ccc2f8bc5dfc9cbe5a91a290498dd96e4"; - - bytes memory tosign3 = new bytes(1+65); + bytes memory tosign3 = new bytes(1 + 65); tosign3[0] = 0xFE; - copyBytes(proof, 3, 65, tosign3, 1); - - bytes memory sig3 = new bytes(uint(proof[3+65+1])+2); - copyBytes(proof, 3+65, sig3.length, sig3, 0); - + copyBytes(_proof, 3, 65, tosign3, 1); + bytes memory sig3 = new bytes(uint(uint8(_proof[3 + 65 + 1])) + 2); + copyBytes(_proof, 3 + 65, sig3.length, sig3, 0); sigok = verifySig(sha256(tosign3), sig3, LEDGERKEY); - return sigok; } - modifier oraclize_randomDS_proofVerify(bytes32 _queryId, string _result, bytes _proof) { - // Step 1: the prefix has to match 'LP\x01' (Ledger Proof version 1) - require((_proof[0] == "L") && (_proof[1] == "P") && (_proof[2] == 1)); - - bool proofVerified = oraclize_randomDS_proofVerify__main(_proof, _queryId, bytes(_result), oraclize_getNetworkName()); - require(proofVerified); - - _; - } - - function oraclize_randomDS_proofVerify__returnCode(bytes32 _queryId, string _result, bytes _proof) internal returns (uint8){ - // Step 1: the prefix has to match 'LP\x01' (Ledger Proof version 1) - if ((_proof[0] != "L")||(_proof[1] != "P")||(_proof[2] != 1)) return 1; - + function oraclize_randomDS_proofVerify__returnCode(bytes32 _queryId, string memory _result, bytes memory _proof) internal returns (uint8 _returnCode) { + // Random DS Proof Step 1: The prefix has to match 'LP\x01' (Ledger Proof version 1) + if ((_proof[0] != "L") || (_proof[1] != "P") || (uint8(_proof[2]) != uint8(1))) { + return 1; + } bool proofVerified = oraclize_randomDS_proofVerify__main(_proof, _queryId, bytes(_result), oraclize_getNetworkName()); - if (proofVerified == false) return 2; - + if (!proofVerified) { + return 2; + } return 0; } - function matchBytes32Prefix(bytes32 content, bytes prefix, uint n_random_bytes) internal pure returns (bool){ + function matchBytes32Prefix(bytes32 _content, bytes memory _prefix, uint _nRandomBytes) internal pure returns (bool _matchesPrefix) { bool match_ = true; - - require(prefix.length == n_random_bytes); - - for (uint256 i=0; i< n_random_bytes; i++) { - if (content[i] != prefix[i]) match_ = false; + require(_prefix.length == _nRandomBytes); + for (uint256 i = 0; i< _nRandomBytes; i++) { + if (_content[i] != _prefix[i]) { + match_ = false; + } } - return match_; } - function oraclize_randomDS_proofVerify__main(bytes proof, bytes32 queryId, bytes result, string context_name) internal returns (bool){ - - // Step 2: the unique keyhash has to match with the sha256 of (context name + queryId) - uint ledgerProofLength = 3+65+(uint(proof[3+65+1])+2)+32; + function oraclize_randomDS_proofVerify__main(bytes memory _proof, bytes32 _queryId, bytes memory _result, string memory _contextName) internal returns (bool _proofVerified) { + // Random DS Proof Step 2: The unique keyhash has to match with the sha256 of (context name + _queryId) + uint ledgerProofLength = 3 + 65 + (uint(uint8(_proof[3 + 65 + 1])) + 2) + 32; bytes memory keyhash = new bytes(32); - copyBytes(proof, ledgerProofLength, 32, keyhash, 0); - if (!(keccak256(keyhash) == keccak256(sha256(context_name, queryId)))) return false; - - bytes memory sig1 = new bytes(uint(proof[ledgerProofLength+(32+8+1+32)+1])+2); - copyBytes(proof, ledgerProofLength+(32+8+1+32), sig1.length, sig1, 0); - - // Step 3: we assume sig1 is valid (it will be verified during step 5) and we verify if 'result' is the prefix of sha256(sig1) - if (!matchBytes32Prefix(sha256(sig1), result, uint(proof[ledgerProofLength+32+8]))) return false; - - // Step 4: commitment match verification, keccak256(delay, nbytes, unonce, sessionKeyHash) == commitment in storage. + copyBytes(_proof, ledgerProofLength, 32, keyhash, 0); + if (!(keccak256(keyhash) == keccak256(abi.encodePacked(sha256(abi.encodePacked(_contextName, _queryId)))))) { + return false; + } + bytes memory sig1 = new bytes(uint(uint8(_proof[ledgerProofLength + (32 + 8 + 1 + 32) + 1])) + 2); + copyBytes(_proof, ledgerProofLength + (32 + 8 + 1 + 32), sig1.length, sig1, 0); + // Random DS Proof Step 3: We assume sig1 is valid (it will be verified during step 5) and we verify if '_result' is the _prefix of sha256(sig1) + if (!matchBytes32Prefix(sha256(sig1), _result, uint(uint8(_proof[ledgerProofLength + 32 + 8])))) { + return false; + } + // Random DS Proof Step 4: Commitment match verification, keccak256(delay, nbytes, unonce, sessionKeyHash) == commitment in storage. // This is to verify that the computed args match with the ones specified in the query. - bytes memory commitmentSlice1 = new bytes(8+1+32); - copyBytes(proof, ledgerProofLength+32, 8+1+32, commitmentSlice1, 0); - + bytes memory commitmentSlice1 = new bytes(8 + 1 + 32); + copyBytes(_proof, ledgerProofLength + 32, 8 + 1 + 32, commitmentSlice1, 0); bytes memory sessionPubkey = new bytes(64); - uint sig2offset = ledgerProofLength+32+(8+1+32)+sig1.length+65; - copyBytes(proof, sig2offset-64, 64, sessionPubkey, 0); - + uint sig2offset = ledgerProofLength + 32 + (8 + 1 + 32) + sig1.length + 65; + copyBytes(_proof, sig2offset - 64, 64, sessionPubkey, 0); bytes32 sessionPubkeyHash = sha256(sessionPubkey); - if (oraclize_randomDS_args[queryId] == keccak256(commitmentSlice1, sessionPubkeyHash)){ //unonce, nbytes and sessionKeyHash match - delete oraclize_randomDS_args[queryId]; + if (oraclize_randomDS_args[_queryId] == keccak256(abi.encodePacked(commitmentSlice1, sessionPubkeyHash))) { //unonce, nbytes and sessionKeyHash match + delete oraclize_randomDS_args[_queryId]; } else return false; - - - // Step 5: validity verification for sig1 (keyhash and args signed with the sessionKey) - bytes memory tosign1 = new bytes(32+8+1+32); - copyBytes(proof, ledgerProofLength, 32+8+1+32, tosign1, 0); - if (!verifySig(sha256(tosign1), sig1, sessionPubkey)) return false; - - // verify if sessionPubkeyHash was verified already, if not.. let's do it! - if (oraclize_randomDS_sessionKeysHashVerified[sessionPubkeyHash] == false){ - oraclize_randomDS_sessionKeysHashVerified[sessionPubkeyHash] = oraclize_randomDS_proofVerify__sessionKeyValidity(proof, sig2offset); + // Random DS Proof Step 5: Validity verification for sig1 (keyhash and args signed with the sessionKey) + bytes memory tosign1 = new bytes(32 + 8 + 1 + 32); + copyBytes(_proof, ledgerProofLength, 32 + 8 + 1 + 32, tosign1, 0); + if (!verifySig(sha256(tosign1), sig1, sessionPubkey)) { + return false; + } + // Verify if sessionPubkeyHash was verified already, if not.. let's do it! + if (!oraclize_randomDS_sessionKeysHashVerified[sessionPubkeyHash]) { + oraclize_randomDS_sessionKeysHashVerified[sessionPubkeyHash] = oraclize_randomDS_proofVerify__sessionKeyValidity(_proof, sig2offset); } - return oraclize_randomDS_sessionKeysHashVerified[sessionPubkeyHash]; } - - // the following function has been written by Alex Beregszaszi (@axic), use it under the terms of the MIT license - function copyBytes(bytes from, uint fromOffset, uint length, bytes to, uint toOffset) internal pure returns (bytes) { - uint minLength = length + toOffset; - - // Buffer too small - require(to.length >= minLength); // Should be a better way? - - // NOTE: the offset 32 is added to skip the `size` field of both bytes variables - uint i = 32 + fromOffset; - uint j = 32 + toOffset; - - while (i < (32 + fromOffset + length)) { + /* + The following function has been written by Alex Beregszaszi (@axic), use it under the terms of the MIT license + */ + function copyBytes(bytes memory _from, uint _fromOffset, uint _length, bytes memory _to, uint _toOffset) internal pure returns (bytes memory _copiedBytes) { + uint minLength = _length + _toOffset; + require(_to.length >= minLength); // Buffer too small. Should be a better way? + uint i = 32 + _fromOffset; // NOTE: the offset 32 is added to skip the `size` field of both bytes variables + uint j = 32 + _toOffset; + while (i < (32 + _fromOffset + _length)) { assembly { - let tmp := mload(add(from, i)) - mstore(add(to, j), tmp) + let tmp := mload(add(_from, i)) + mstore(add(_to, j), tmp) } i += 32; j += 32; } - - return to; - } - - // the following function has been written by Alex Beregszaszi (@axic), use it under the terms of the MIT license - // Duplicate Solidity's ecrecover, but catching the CALL return value - function safer_ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal returns (bool, address) { - // We do our own memory management here. Solidity uses memory offset - // 0x40 to store the current end of memory. We write past it (as - // writes are memory extensions), but don't update the offset so - // Solidity will reuse it. The memory used here is only needed for - // this context. - - // FIXME: inline assembly can't access return values + return _to; + } + /* + The following function has been written by Alex Beregszaszi (@axic), use it under the terms of the MIT license + Duplicate Solidity's ecrecover, but catching the CALL return value + */ + function safer_ecrecover(bytes32 _hash, uint8 _v, bytes32 _r, bytes32 _s) internal returns (bool _success, address _recoveredAddress) { + /* + We do our own memory management here. Solidity uses memory offset + 0x40 to store the current end of memory. We write past it (as + writes are memory extensions), but don't update the offset so + Solidity will reuse it. The memory used here is only needed for + this context. + FIXME: inline assembly can't access return values + */ bool ret; address addr; - assembly { let size := mload(0x40) - mstore(size, hash) - mstore(add(size, 32), v) - mstore(add(size, 64), r) - mstore(add(size, 96), s) - - // NOTE: we can reuse the request memory because we deal with - // the return code - ret := call(3000, 1, 0, size, 128, size, 32) + mstore(size, _hash) + mstore(add(size, 32), _v) + mstore(add(size, 64), _r) + mstore(add(size, 96), _s) + ret := call(3000, 1, 0, size, 128, size, 32) // NOTE: we can reuse the request memory because we deal with the return code. addr := mload(size) } - return (ret, addr); } - - // the following function has been written by Alex Beregszaszi (@axic), use it under the terms of the MIT license - function ecrecovery(bytes32 hash, bytes sig) internal returns (bool, address) { + /* + The following function has been written by Alex Beregszaszi (@axic), use it under the terms of the MIT license + */ + function ecrecovery(bytes32 _hash, bytes memory _sig) internal returns (bool _success, address _recoveredAddress) { bytes32 r; bytes32 s; uint8 v; - - if (sig.length != 65) - return (false, 0); - - // The signature format is a compact form of: - // {bytes32 r}{bytes32 s}{uint8 v} - // Compact means, uint8 is not padded to 32 bytes. + if (_sig.length != 65) { + return (false, address(0)); + } + /* + The signature format is a compact form of: + {bytes32 r}{bytes32 s}{uint8 v} + Compact means, uint8 is not padded to 32 bytes. + */ assembly { - r := mload(add(sig, 32)) - s := mload(add(sig, 64)) - - // Here we are loading the last 32 bytes. We exploit the fact that - // 'mload' will pad with zeroes if we overread. - // There is no 'mload8' to do this, but that would be nicer. - v := byte(0, mload(add(sig, 96))) - - // Alternative solution: - // 'byte' is not working due to the Solidity parser, so lets - // use the second best option, 'and' - // v := and(mload(add(sig, 65)), 255) + r := mload(add(_sig, 32)) + s := mload(add(_sig, 64)) + /* + Here we are loading the last 32 bytes. We exploit the fact that + 'mload' will pad with zeroes if we overread. + There is no 'mload8' to do this, but that would be nicer. + */ + v := byte(0, mload(add(_sig, 96))) + /* + Alternative solution: + 'byte' is not working due to the Solidity parser, so lets + use the second best option, 'and' + v := and(mload(add(_sig, 65)), 255) + */ } - - // albeit non-transactional signatures are not specified by the YP, one would expect it - // to match the YP range of [27, 28] - // - // geth uses [0, 1] and some clients have followed. This might change, see: - // https://github.com/ethereum/go-ethereum/issues/2053 - if (v < 27) - v += 27; - - if (v != 27 && v != 28) - return (false, 0); - - return safer_ecrecover(hash, v, r, s); + /* + albeit non-transactional signatures are not specified by the YP, one would expect it + to match the YP range of [27, 28] + geth uses [0, 1] and some clients have followed. This might change, see: + https://github.com/ethereum/go-ethereum/issues/2053 + */ + if (v < 27) { + v += 27; + } + if (v != 27 && v != 28) { + return (false, address(0)); + } + return safer_ecrecover(_hash, v, r, s); } function safeMemoryCleaner() internal pure { @@ -1210,5 +1330,10 @@ contract usingOraclize { codecopy(fmem, codesize, sub(msize, fmem)) } } - } +/* + +END ORACLIZE_API + +*/ + diff --git a/contracts/helpers/PolyToken.sol b/contracts/helpers/PolyToken.sol deleted file mode 100644 index d8f3d48ec..000000000 --- a/contracts/helpers/PolyToken.sol +++ /dev/null @@ -1,198 +0,0 @@ -pragma solidity ^0.4.24; - -import "../interfaces/IERC20.sol"; - -/* -Copyright (c) 2016 Smart Contract Solutions, Inc. - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -/** - * @title SafeMath - * @dev Math operations with safety checks that throw on error - */ -library SafeMath { - function mul(uint256 a, uint256 b) internal pure returns (uint256) { - if (a == 0) { - return 0; - } - uint256 c = a * b; - assert(c / a == b); - return c; - } - - function div(uint256 a, uint256 b) internal pure returns (uint256) { - // assert(b > 0); // Solidity automatically throws when dividing by 0 - uint256 c = a / b; - // assert(a == b * c + a % b); // There is no case in which this doesn't hold - return c; - } - - function sub(uint256 a, uint256 b) internal pure returns (uint256) { - assert(b <= a); - return a - b; - } - - function add(uint256 a, uint256 b) internal pure returns (uint256) { - uint256 c = a + b; - assert(c >= a); - return c; - } -} - -/** - * @title Standard ERC20 token - * - * @dev Implementation of the basic standard token. - * @dev https://github.com/ethereum/EIPs/issues/20 - */ -contract PolyToken is IERC20 { - using SafeMath for uint256; - - // Poly Token parameters - string public name = "Polymath"; - string public symbol = "POLY"; - uint8 public constant decimals = 18; - uint256 public constant decimalFactor = 10 ** uint256(decimals); - uint256 public constant totalSupply = 1000000000 * decimalFactor; - mapping (address => uint256) balances; - mapping (address => mapping (address => uint256)) internal allowed; - - event Transfer(address indexed from, address indexed to, uint256 value); - event Approval(address indexed owner, address indexed spender, uint256 value); - - /** - * @dev Constructor for Poly creation - * @dev Assigns the totalSupply to the PolyDistribution contract - */ - constructor (address _polyDistributionContractAddress) public { - require(_polyDistributionContractAddress != address(0), "Invalid address"); - balances[_polyDistributionContractAddress] = totalSupply; - emit Transfer(address(0), _polyDistributionContractAddress, totalSupply); - } - - /** - * @dev Returns the balance of the specified address - * @param _owner The address to query the the balance of - * @return An uint256 representing the amount owned by the passed address - */ - function balanceOf(address _owner) public view returns (uint256 balance) { - return balances[_owner]; - } - - /** - * @dev Function to check the amount of tokens a spender is allowed to spend - * @param _owner address The address which owns the tokens - * @param _spender address The address which will spend the tokens - * @return A uint256 specifying the amount of tokens left available for the spender - */ - function allowance(address _owner, address _spender) public view returns (uint256) { - return allowed[_owner][_spender]; - } - - /** - * @dev Transfer token to a specified address - * @param _to The address to transfer tokens to - * @param _value The amount to be transferred - */ - function transfer(address _to, uint256 _value) public returns (bool) { - require(_to != address(0), "Invalid address"); - require(_value <= balances[msg.sender], "Insufficient tokens transferable"); - - // SafeMath.sub will throw if the balance is not enough - balances[msg.sender] = balances[msg.sender].sub(_value); - balances[_to] = balances[_to].add(_value); - emit Transfer(msg.sender, _to, _value); - return true; - } - - /** - * @dev Transfers tokens from one address to another - * @param _from address The address to transfer tokens from - * @param _to address The address to transfer tokens to - * @param _value uint256 The amount of tokens to be transferred - */ - function transferFrom(address _from, address _to, uint256 _value) public returns (bool) { - require(_to != address(0), "Invalid address"); - require(_value <= balances[_from], "Insufficient tokens transferable"); - require(_value <= allowed[_from][msg.sender], "Insufficient tokens allowable"); - - balances[_from] = balances[_from].sub(_value); - balances[_to] = balances[_to].add(_value); - allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value); - emit Transfer(_from, _to, _value); - return true; - } - - /** - * @dev Approves the passed address to spend the specified amount of tokens on behalf of msg.sender - * - * Beware that changing an allowance with this method brings the risk that someone may use both the old - * and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this - * race condition is to reduce the spender's allowance to 0 first and set the desired value afterwards: - * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 - * @param _spender The address which will spend the funds - * @param _value The amount of tokens to be spent - */ - function approve(address _spender, uint256 _value) public returns (bool) { - allowed[msg.sender][_spender] = _value; - emit Approval(msg.sender, _spender, _value); - return true; - } - - /** - * @dev Increases the amount of tokens that an owner has allowed a spender to spend - * - * approve should be called when allowed[_spender] == 0. To increment - * allowed value, it is better to use this function to avoid 2 calls (and wait until - * the first transaction is mined) - * From MonolithDAO Token.sol - * @param _spender The address which will spend the funds. - * @param _addedValue The amount of tokens to increase the allowance by. - */ - function increaseApproval(address _spender, uint _addedValue) public returns (bool) { - allowed[msg.sender][_spender] = allowed[msg.sender][_spender].add(_addedValue); - emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]); - return true; - } - - /** - * @dev Decreases the amount of tokens that an owner has allowed a spender to spend - * - * approve should be called when allowed[_spender] == 0. To decrement - * allowed value, it is better to use this function to avoid 2 calls (and wait until - * the first transaction is mined) - * From MonolithDAO Token.sol - * @param _spender The address which will spend the funds - * @param _subtractedValue The amount of tokens to decrease the allowance by - */ - function decreaseApproval(address _spender, uint _subtractedValue) public returns (bool) { - uint oldValue = allowed[msg.sender][_spender]; - if (_subtractedValue > oldValue) { - allowed[msg.sender][_spender] = 0; - } else { - allowed[msg.sender][_spender] = oldValue.sub(_subtractedValue); - } - emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]); - return true; - } - -} diff --git a/contracts/interfaces/IBoot.sol b/contracts/interfaces/IBoot.sol deleted file mode 100644 index db29dc319..000000000 --- a/contracts/interfaces/IBoot.sol +++ /dev/null @@ -1,10 +0,0 @@ -pragma solidity ^0.4.24; - -interface IBoot { - - /** - * @notice This function returns the signature of configure function - * @return bytes4 Configure function signature - */ - function getInitFunction() external pure returns(bytes4); -} \ No newline at end of file diff --git a/contracts/interfaces/ICheckPermission.sol b/contracts/interfaces/ICheckPermission.sol new file mode 100644 index 000000000..cbf3129b9 --- /dev/null +++ b/contracts/interfaces/ICheckPermission.sol @@ -0,0 +1,14 @@ +pragma solidity 0.5.8; + +interface ICheckPermission { + /** + * @notice Validate permissions with PermissionManager if it exists, If no Permission return false + * @dev Note that IModule withPerm will allow ST owner all permissions anyway + * @dev this allows individual modules to override this logic if needed (to not allow ST owner all permissions) + * @param _delegate address of delegate + * @param _module address of PermissionManager module + * @param _perm the permissions + * @return success + */ + function checkPermission(address _delegate, address _module, bytes32 _perm) external view returns(bool hasPerm); +} diff --git a/contracts/interfaces/IDataStore.sol b/contracts/interfaces/IDataStore.sol new file mode 100644 index 000000000..331e56748 --- /dev/null +++ b/contracts/interfaces/IDataStore.sol @@ -0,0 +1,136 @@ +pragma solidity 0.5.8; + +interface IDataStore { + /** + * @dev Changes security token atatched to this data store + * @param _securityToken address of the security token + */ + function setSecurityToken(address _securityToken) external; + + /** + * @dev Stores a uint256 data against a key + * @param _key Unique key to identify the data + * @param _data Data to be stored against the key + */ + function setUint256(bytes32 _key, uint256 _data) external; + + function setBytes32(bytes32 _key, bytes32 _data) external; + + function setAddress(bytes32 _key, address _data) external; + + function setString(bytes32 _key, string calldata _data) external; + + function setBytes(bytes32 _key, bytes calldata _data) external; + + function setBool(bytes32 _key, bool _data) external; + + /** + * @dev Stores a uint256 array against a key + * @param _key Unique key to identify the array + * @param _data Array to be stored against the key + */ + function setUint256Array(bytes32 _key, uint256[] calldata _data) external; + + function setBytes32Array(bytes32 _key, bytes32[] calldata _data) external ; + + function setAddressArray(bytes32 _key, address[] calldata _data) external; + + function setBoolArray(bytes32 _key, bool[] calldata _data) external; + + /** + * @dev Inserts a uint256 element to the array identified by the key + * @param _key Unique key to identify the array + * @param _data Element to push into the array + */ + function insertUint256(bytes32 _key, uint256 _data) external; + + function insertBytes32(bytes32 _key, bytes32 _data) external; + + function insertAddress(bytes32 _key, address _data) external; + + function insertBool(bytes32 _key, bool _data) external; + + /** + * @dev Deletes an element from the array identified by the key. + * When an element is deleted from an Array, last element of that array is moved to the index of deleted element. + * @param _key Unique key to identify the array + * @param _index Index of the element to delete + */ + function deleteUint256(bytes32 _key, uint256 _index) external; + + function deleteBytes32(bytes32 _key, uint256 _index) external; + + function deleteAddress(bytes32 _key, uint256 _index) external; + + function deleteBool(bytes32 _key, uint256 _index) external; + + /** + * @dev Stores multiple uint256 data against respective keys + * @param _keys Array of keys to identify the data + * @param _data Array of data to be stored against the respective keys + */ + function setUint256Multi(bytes32[] calldata _keys, uint256[] calldata _data) external; + + function setBytes32Multi(bytes32[] calldata _keys, bytes32[] calldata _data) external; + + function setAddressMulti(bytes32[] calldata _keys, address[] calldata _data) external; + + function setBoolMulti(bytes32[] calldata _keys, bool[] calldata _data) external; + + /** + * @dev Inserts multiple uint256 elements to the array identified by the respective keys + * @param _keys Array of keys to identify the data + * @param _data Array of data to be inserted in arrays of the respective keys + */ + function insertUint256Multi(bytes32[] calldata _keys, uint256[] calldata _data) external; + + function insertBytes32Multi(bytes32[] calldata _keys, bytes32[] calldata _data) external; + + function insertAddressMulti(bytes32[] calldata _keys, address[] calldata _data) external; + + function insertBoolMulti(bytes32[] calldata _keys, bool[] calldata _data) external; + + function getUint256(bytes32 _key) external view returns(uint256); + + function getBytes32(bytes32 _key) external view returns(bytes32); + + function getAddress(bytes32 _key) external view returns(address); + + function getString(bytes32 _key) external view returns(string memory); + + function getBytes(bytes32 _key) external view returns(bytes memory); + + function getBool(bytes32 _key) external view returns(bool); + + function getUint256Array(bytes32 _key) external view returns(uint256[] memory); + + function getBytes32Array(bytes32 _key) external view returns(bytes32[] memory); + + function getAddressArray(bytes32 _key) external view returns(address[] memory); + + function getBoolArray(bytes32 _key) external view returns(bool[] memory); + + function getUint256ArrayLength(bytes32 _key) external view returns(uint256); + + function getBytes32ArrayLength(bytes32 _key) external view returns(uint256); + + function getAddressArrayLength(bytes32 _key) external view returns(uint256); + + function getBoolArrayLength(bytes32 _key) external view returns(uint256); + + function getUint256ArrayElement(bytes32 _key, uint256 _index) external view returns(uint256); + + function getBytes32ArrayElement(bytes32 _key, uint256 _index) external view returns(bytes32); + + function getAddressArrayElement(bytes32 _key, uint256 _index) external view returns(address); + + function getBoolArrayElement(bytes32 _key, uint256 _index) external view returns(bool); + + function getUint256ArrayElements(bytes32 _key, uint256 _startIndex, uint256 _endIndex) external view returns(uint256[] memory); + + function getBytes32ArrayElements(bytes32 _key, uint256 _startIndex, uint256 _endIndex) external view returns(bytes32[] memory); + + function getAddressArrayElements(bytes32 _key, uint256 _startIndex, uint256 _endIndex) external view returns(address[] memory); + + function getBoolArrayElements(bytes32 _key, uint256 _startIndex, uint256 _endIndex) external view returns(bool[] memory); +} diff --git a/contracts/interfaces/IFeatureRegistry.sol b/contracts/interfaces/IFeatureRegistry.sol index 574f412a5..ee6c931a8 100644 --- a/contracts/interfaces/IFeatureRegistry.sol +++ b/contracts/interfaces/IFeatureRegistry.sol @@ -1,15 +1,25 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; /** * @title Interface for managing polymath feature switches */ interface IFeatureRegistry { + event ChangeFeatureStatus(string _nameKey, bool _newStatus); + + /** + * @notice change a feature status + * @dev feature status is set to false by default + * @param _nameKey is the key for the feature status mapping + * @param _newStatus is the new feature status + */ + function setFeatureStatus(string calldata _nameKey, bool _newStatus) external; + /** * @notice Get the status of a feature * @param _nameKey is the key for the feature status mapping * @return bool */ - function getFeatureStatus(string _nameKey) external view returns(bool); + function getFeatureStatus(string calldata _nameKey) external view returns(bool hasFeature); } diff --git a/contracts/interfaces/IModule.sol b/contracts/interfaces/IModule.sol index bc5140ae2..210e71e4e 100644 --- a/contracts/interfaces/IModule.sol +++ b/contracts/interfaces/IModule.sol @@ -1,23 +1,17 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; /** * @title Interface that every module contract should implement */ interface IModule { - /** * @notice This function returns the signature of configure function */ - function getInitFunction() external pure returns (bytes4); + function getInitFunction() external pure returns(bytes4 initFunction); /** * @notice Return the permission flags that are associated with a module */ - function getPermissions() external view returns(bytes32[]); - - /** - * @notice Used to withdraw the fee by the factory owner - */ - function takeFee(uint256 _amount) external returns(bool); + function getPermissions() external view returns(bytes32[] memory permissions); } diff --git a/contracts/interfaces/IModuleFactory.sol b/contracts/interfaces/IModuleFactory.sol index 9c2ad6bf2..858874b82 100644 --- a/contracts/interfaces/IModuleFactory.sol +++ b/contracts/interfaces/IModuleFactory.sol @@ -1,86 +1,118 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; /** * @title Interface that every module factory contract should implement */ interface IModuleFactory { - - event ChangeFactorySetupFee(uint256 _oldSetupCost, uint256 _newSetupCost, address _moduleFactory); - event ChangeFactoryUsageFee(uint256 _oldUsageCost, uint256 _newUsageCost, address _moduleFactory); - event ChangeFactorySubscriptionFee(uint256 _oldSubscriptionCost, uint256 _newMonthlySubscriptionCost, address _moduleFactory); + event ChangeSetupCost(uint256 _oldSetupCost, uint256 _newSetupCost); + event ChangeCostType(bool _isOldCostInPoly, bool _isNewCostInPoly); event GenerateModuleFromFactory( address _module, bytes32 indexed _moduleName, address indexed _moduleFactory, address _creator, uint256 _setupCost, - uint256 _timestamp + uint256 _setupCostInPoly ); event ChangeSTVersionBound(string _boundType, uint8 _major, uint8 _minor, uint8 _patch); //Should create an instance of the Module, or throw - function deploy(bytes _data) external returns(address); + function deploy(bytes calldata _data) external returns(address moduleAddress); /** - * @notice Type of the Module factory + * @notice Get the tags related to the module factory */ - function getTypes() external view returns(uint8[]); + function version() external view returns(string memory moduleVersion); /** - * @notice Get the name of the Module + * @notice Get the tags related to the module factory */ - function getName() external view returns(bytes32); + function name() external view returns(bytes32 moduleName); /** - * @notice Returns the instructions associated with the module + * @notice Returns the title associated with the module */ - function getInstructions() external view returns (string); + function title() external view returns(string memory moduleTitle); /** - * @notice Get the tags related to the module factory + * @notice Returns the description associated with the module */ - function getTags() external view returns (bytes32[]); + function description() external view returns(string memory moduleDescription); /** - * @notice Used to change the setup fee - * @param _newSetupCost New setup fee + * @notice Get the setup cost of the module in USD + */ + function setupCost() external returns(uint256 usdSetupCost); + + /** + * @notice Type of the Module factory */ - function changeFactorySetupFee(uint256 _newSetupCost) external; + function getTypes() external view returns(uint8[] memory moduleTypes); /** - * @notice Used to change the usage fee - * @param _newUsageCost New usage fee + * @notice Get the tags related to the module factory */ - function changeFactoryUsageFee(uint256 _newUsageCost) external; + function getTags() external view returns(bytes32[] memory moduleTags); /** - * @notice Used to change the subscription fee - * @param _newSubscriptionCost New subscription fee + * @notice Used to change the setup fee + * @param _newSetupCost New setup fee */ - function changeFactorySubscriptionFee(uint256 _newSubscriptionCost) external; + function changeSetupCost(uint256 _newSetupCost) external; + + /** + * @notice Used to change the currency and amount setup cost + * @param _setupCost new setup cost + * @param _isCostInPoly new setup cost currency. USD or POLY + */ + function changeCostAndType(uint256 _setupCost, bool _isCostInPoly) external; /** * @notice Function use to change the lower and upper bound of the compatible version st * @param _boundType Type of bound * @param _newVersion New version array */ - function changeSTVersionBounds(string _boundType, uint8[] _newVersion) external; + function changeSTVersionBounds(string calldata _boundType, uint8[] calldata _newVersion) external; - /** + /** * @notice Get the setup cost of the module */ - function getSetupCost() external view returns (uint256); + function setupCostInPoly() external returns (uint256 polySetupCost); /** * @notice Used to get the lower bound * @return Lower bound */ - function getLowerSTVersionBounds() external view returns(uint8[]); + function getLowerSTVersionBounds() external view returns(uint8[] memory lowerBounds); - /** + /** * @notice Used to get the upper bound * @return Upper bound */ - function getUpperSTVersionBounds() external view returns(uint8[]); + function getUpperSTVersionBounds() external view returns(uint8[] memory upperBounds); + + /** + * @notice Updates the tags of the ModuleFactory + * @param _tagsData New list of tags + */ + function changeTags(bytes32[] calldata _tagsData) external; + + /** + * @notice Updates the name of the ModuleFactory + * @param _name New name that will replace the old one. + */ + function changeName(bytes32 _name) external; + + /** + * @notice Updates the description of the ModuleFactory + * @param _description New description that will replace the old one. + */ + function changeDescription(string calldata _description) external; + + /** + * @notice Updates the title of the ModuleFactory + * @param _title New Title that will replace the old one. + */ + function changeTitle(string calldata _title) external; } diff --git a/contracts/interfaces/IModuleRegistry.sol b/contracts/interfaces/IModuleRegistry.sol index 0ca6044e4..63129a499 100644 --- a/contracts/interfaces/IModuleRegistry.sol +++ b/contracts/interfaces/IModuleRegistry.sol @@ -1,16 +1,45 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; /** * @title Interface for the Polymath Module Registry contract */ interface IModuleRegistry { + /////////// + // Events + ////////// + + // Emit when network becomes paused + event Pause(address account); + // Emit when network becomes unpaused + event Unpause(address account); + // Emit when Module is used by the SecurityToken + event ModuleUsed(address indexed _moduleFactory, address indexed _securityToken); + // Emit when the Module Factory gets registered on the ModuleRegistry contract + event ModuleRegistered(address indexed _moduleFactory, address indexed _owner); + // Emit when the module gets verified by Polymath + event ModuleVerified(address indexed _moduleFactory); + // Emit when the module gets unverified by Polymath or the factory owner + event ModuleUnverified(address indexed _moduleFactory); + // Emit when a ModuleFactory is removed by Polymath + event ModuleRemoved(address indexed _moduleFactory, address indexed _decisionMaker); + // Emit when ownership gets transferred + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + /** - * @notice Called by a security token to notify the registry it is using a module + * @notice Called by a security token (2.x) to notify the registry it is using a module * @param _moduleFactory is the address of the relevant module factory */ function useModule(address _moduleFactory) external; + /** + * @notice Called by a security token to notify the registry it is using a module + * @param _moduleFactory is the address of the relevant module factory + * @param _isUpgrade whether the use is part of an existing module upgrade + */ + function useModule(address _moduleFactory, bool _isUpgrade) external; + /** * @notice Called by the ModuleFactory owner to register new modules for SecurityToken to use * @param _moduleFactory is the address of the module factory to be registered @@ -23,20 +52,38 @@ interface IModuleRegistry { */ function removeModule(address _moduleFactory) external; + /** + * @notice Check that a module and its factory are compatible + * @param _moduleFactory is the address of the relevant module factory + * @param _securityToken is the address of the relevant security token + * @return bool whether module and token are compatible + */ + function isCompatibleModule(address _moduleFactory, address _securityToken) external view returns(bool isCompatible); + /** * @notice Called by Polymath to verify modules for SecurityToken to use. * @notice A module can not be used by an ST unless first approved/verified by Polymath * @notice (The only exception to this is that the author of the module is the owner of the ST - Only if enabled by the FeatureRegistry) * @param _moduleFactory is the address of the module factory to be registered */ - function verifyModule(address _moduleFactory, bool _verified) external; + function verifyModule(address _moduleFactory) external; /** - * @notice Used to get the reputation of a Module Factory - * @param _factoryAddress address of the Module Factory - * @return address array which has the list of securityToken's uses that module factory + * @notice Called by Polymath to unverify modules for SecurityToken to use. + * @notice A module can not be used by an ST unless first approved/verified by Polymath + * @notice (The only exception to this is that the author of the module is the owner of the ST - Only if enabled by the FeatureRegistry) + * @param _moduleFactory is the address of the module factory to be registered + */ + function unverifyModule(address _moduleFactory) external; + + /** + * @notice Returns the verified status, and reputation of the entered Module Factory + * @param _factoryAddress is the address of the module factory + * @return bool indicating whether module factory is verified + * @return address of the factory owner + * @return address array which contains the list of securityTokens that use that module factory */ - function getReputationByFactory(address _factoryAddress) external view returns(address[]); + function getFactoryDetails(address _factoryAddress) external view returns(bool isVerified, address factoryOwner, address[] memory usingTokens); /** * @notice Returns all the tags related to the a module type which are valid for the given token @@ -45,7 +92,7 @@ interface IModuleRegistry { * @return list of tags * @return corresponding list of module factories */ - function getTagsByTypeAndToken(uint8 _moduleType, address _securityToken) external view returns(bytes32[], address[]); + function getTagsByTypeAndToken(uint8 _moduleType, address _securityToken) external view returns(bytes32[] memory tags, address[] memory factories); /** * @notice Returns all the tags related to the a module type which are valid for the given token @@ -53,14 +100,20 @@ interface IModuleRegistry { * @return list of tags * @return corresponding list of module factories */ - function getTagsByType(uint8 _moduleType) external view returns(bytes32[], address[]); + function getTagsByType(uint8 _moduleType) external view returns(bytes32[] memory tags, address[] memory factories); + /** + * @notice Returns the list of addresses of all Module Factory of a particular type + * @param _moduleType Type of Module + * @return address array that contains the list of addresses of module factory contracts. + */ + function getAllModulesByType(uint8 _moduleType) external view returns(address[] memory factories); /** * @notice Returns the list of addresses of Module Factory of a particular type * @param _moduleType Type of Module * @return address array that contains the list of addresses of module factory contracts. */ - function getModulesByType(uint8 _moduleType) external view returns(address[]); + function getModulesByType(uint8 _moduleType) external view returns(address[] memory factories); /** * @notice Returns the list of available Module factory addresses of a particular type for a given token. @@ -68,7 +121,7 @@ interface IModuleRegistry { * @param _securityToken is the address of SecurityToken * @return address array that contains the list of available addresses of module factory contracts. */ - function getModulesByTypeAndToken(uint8 _moduleType, address _securityToken) external view returns (address[]); + function getModulesByTypeAndToken(uint8 _moduleType, address _securityToken) external view returns(address[] memory factories); /** * @notice Use to get the latest contract address of the regstries @@ -79,12 +132,34 @@ interface IModuleRegistry { * @notice Get the owner of the contract * @return address owner */ - function owner() external view returns(address); + function owner() external view returns(address ownerAddress); /** * @notice Check whether the contract operations is paused or not - * @return bool + * @return bool */ - function isPaused() external view returns(bool); + function isPaused() external view returns(bool paused); + + /** + * @notice Reclaims all ERC20Basic compatible tokens + * @param _tokenContract The address of the token contract + */ + function reclaimERC20(address _tokenContract) external; + + /** + * @notice Called by the owner to pause, triggers stopped state + */ + function pause() external; + + /** + * @notice Called by the owner to unpause, returns to normal state + */ + function unpause() external; + + /** + * @dev Allows the current owner to transfer control of the contract to a newOwner. + * @param _newOwner The address to transfer ownership to. + */ + function transferOwnership(address _newOwner) external; } diff --git a/contracts/interfaces/IOracle.sol b/contracts/interfaces/IOracle.sol index 483a22cd0..c590f6e48 100644 --- a/contracts/interfaces/IOracle.sol +++ b/contracts/interfaces/IOracle.sol @@ -1,25 +1,24 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; interface IOracle { - /** * @notice Returns address of oracle currency (0x0 for ETH) */ - function getCurrencyAddress() external view returns(address); + function getCurrencyAddress() external view returns(address currency); /** * @notice Returns symbol of oracle currency (0x0 for ETH) */ - function getCurrencySymbol() external view returns(bytes32); + function getCurrencySymbol() external view returns(bytes32 symbol); /** * @notice Returns denomination of price */ - function getCurrencyDenominated() external view returns(bytes32); + function getCurrencyDenominated() external view returns(bytes32 denominatedCurrency); /** * @notice Returns price - should throw if not valid */ - function getPrice() external view returns(uint256); + function getPrice() external returns(uint256 price); } diff --git a/contracts/interfaces/IOwnable.sol b/contracts/interfaces/IOwnable.sol index e4f427bd8..2eb7b59a9 100644 --- a/contracts/interfaces/IOwnable.sol +++ b/contracts/interfaces/IOwnable.sol @@ -1,5 +1,4 @@ -pragma solidity ^0.4.24; - +pragma solidity 0.5.8; /** * @title Ownable @@ -10,7 +9,7 @@ interface IOwnable { /** * @dev Returns owner */ - function owner() external view returns (address); + function owner() external view returns(address ownerAddress); /** * @dev Allows the current owner to relinquish control of the contract. diff --git a/contracts/interfaces/IERC20.sol b/contracts/interfaces/IPoly.sol similarity index 62% rename from contracts/interfaces/IERC20.sol rename to contracts/interfaces/IPoly.sol index b1d36463f..4c936d982 100644 --- a/contracts/interfaces/IERC20.sol +++ b/contracts/interfaces/IPoly.sol @@ -1,19 +1,19 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; /** * @title ERC20 interface * @dev see https://github.com/ethereum/EIPs/issues/20 */ -interface IERC20 { - function decimals() external view returns (uint8); - function totalSupply() external view returns (uint256); - function balanceOf(address _owner) external view returns (uint256); - function allowance(address _owner, address _spender) external view returns (uint256); - function transfer(address _to, uint256 _value) external returns (bool); - function transferFrom(address _from, address _to, uint256 _value) external returns (bool); - function approve(address _spender, uint256 _value) external returns (bool); - function decreaseApproval(address _spender, uint _subtractedValue) external returns (bool); - function increaseApproval(address _spender, uint _addedValue) external returns (bool); +interface IPoly { + function decimals() external view returns(uint8); + function totalSupply() external view returns(uint256); + function balanceOf(address _owner) external view returns(uint256); + function allowance(address _owner, address _spender) external view returns(uint256); + function transfer(address _to, uint256 _value) external returns(bool); + function transferFrom(address _from, address _to, uint256 _value) external returns(bool); + function approve(address _spender, uint256 _value) external returns(bool); + function decreaseApproval(address _spender, uint _subtractedValue) external returns(bool); + function increaseApproval(address _spender, uint _addedValue) external returns(bool); event Transfer(address indexed from, address indexed to, uint256 value); event Approval(address indexed owner, address indexed spender, uint256 value); } diff --git a/contracts/interfaces/IPolymathRegistry.sol b/contracts/interfaces/IPolymathRegistry.sol index 4601253fa..24401ada3 100644 --- a/contracts/interfaces/IPolymathRegistry.sol +++ b/contracts/interfaces/IPolymathRegistry.sol @@ -1,14 +1,21 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; - interface IPolymathRegistry { + event ChangeAddress(string _nameKey, address indexed _oldAddress, address indexed _newAddress); + /** * @notice Returns the contract address * @param _nameKey is the key for the contract address mapping * @return address */ - function getAddress(string _nameKey) external view returns(address); + function getAddress(string calldata _nameKey) external view returns(address registryAddress); + + /** + * @notice Changes the contract address + * @param _nameKey is the key for the contract address mapping + * @param _newAddress is the new contract address + */ + function changeAddress(string calldata _nameKey, address _newAddress) external; } - \ No newline at end of file diff --git a/contracts/interfaces/ISTFactory.sol b/contracts/interfaces/ISTFactory.sol index 7b7d6dd77..66fbb1c6b 100644 --- a/contracts/interfaces/ISTFactory.sol +++ b/contracts/interfaces/ISTFactory.sol @@ -1,10 +1,18 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; /** * @title Interface for security token proxy deployment */ interface ISTFactory { + event LogicContractSet(string _version, address _logicContract, bytes _upgradeData); + event TokenUpgraded( + address indexed _securityToken, + uint256 indexed _version + ); + event DefaultTransferManagerUpdated(address indexed _oldTransferManagerFactory, address indexed _newTransferManagerFactory); + event DefaultDataStoreUpdated(address indexed _oldDataStoreFactory, address indexed _newDataStoreFactory); + /** * @notice Deploys the token and adds default modules like permission manager and transfer manager. * Future versions of the proxy can attach different modules or pass some other paramters. @@ -14,17 +22,46 @@ interface ISTFactory { * @param _tokenDetails is the off-chain data associated with the Security Token * @param _issuer is the owner of the Security Token * @param _divisible whether the token is divisible or not - * @param _polymathRegistry is the address of the Polymath Registry contract + * @param _treasuryWallet Ethereum address which will holds the STs. */ function deployToken( - string _name, - string _symbol, + string calldata _name, + string calldata _symbol, uint8 _decimals, - string _tokenDetails, + string calldata _tokenDetails, address _issuer, bool _divisible, - address _polymathRegistry + address _treasuryWallet //In v2.x this is the Polymath Registry ) - external - returns (address); + external + returns(address tokenAddress); + + /** + * @notice Used to set a new token logic contract + * @param _version Version of upgraded module + * @param _logicContract Address of deployed module logic contract referenced from proxy + * @param _initializationData Initialization data that used to intialize value in the securityToken + * @param _upgradeData Data to be passed in call to upgradeToAndCall when a token upgrades its module + */ + function setLogicContract(string calldata _version, address _logicContract, bytes calldata _initializationData, bytes calldata _upgradeData) external; + + /** + * @notice Used to upgrade a token + * @param _maxModuleType maximum module type enumeration + */ + function upgradeToken(uint8 _maxModuleType) external; + + /** + * @notice Used to set a new default transfer manager + * @dev Setting this to address(0) means don't deploy a default TM + * @param _transferManagerFactory Address of new default transfer manager factory + */ + function updateDefaultTransferManager(address _transferManagerFactory) external; + + /** + * @notice Used to set a new default data store + * @dev Setting this to address(0) means don't deploy a default data store + * @param _dataStoreFactory Address of new default data store factory + */ + function updateDefaultDataStore(address _dataStoreFactory) external; } diff --git a/contracts/interfaces/ISTO.sol b/contracts/interfaces/ISTO.sol index dd5ce3f16..ce46ef456 100644 --- a/contracts/interfaces/ISTO.sol +++ b/contracts/interfaces/ISTO.sol @@ -1,11 +1,29 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; /** * @title Interface to be implemented by all STO modules */ interface ISTO { + + enum FundRaiseType {ETH, POLY, SC} + + // Event + event SetFundRaiseTypes(FundRaiseType[] _fundRaiseTypes); + /** * @notice Returns the total no. of tokens sold */ - function getTokensSold() external view returns (uint256); + function getTokensSold() external view returns(uint256 soldTokens); + + /** + * @notice Returns funds raised by the STO + */ + function getRaised(FundRaiseType _fundRaiseType) external view returns(uint256 raisedAmount); + + /** + * @notice Pause (overridden function) + * @dev Only securityToken owner restriction applied on the super function + */ + function pause() external; + } diff --git a/contracts/interfaces/ISecurityToken.sol b/contracts/interfaces/ISecurityToken.sol index f280d6c19..a517073ee 100644 --- a/contracts/interfaces/ISecurityToken.sol +++ b/contracts/interfaces/ISecurityToken.sol @@ -1,65 +1,298 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; /** * @title Interface for all security tokens */ interface ISecurityToken { - // Standard ERC20 interface - function decimals() external view returns (uint8); - function totalSupply() external view returns (uint256); - function balanceOf(address _owner) external view returns (uint256); - function allowance(address _owner, address _spender) external view returns (uint256); - function transfer(address _to, uint256 _value) external returns (bool); - function transferFrom(address _from, address _to, uint256 _value) external returns (bool); - function approve(address _spender, uint256 _value) external returns (bool); - function decreaseApproval(address _spender, uint _subtractedValue) external returns (bool); - function increaseApproval(address _spender, uint _addedValue) external returns (bool); + function symbol() external view returns (string memory); + function name() external view returns (string memory); + function decimals() external view returns(uint8); + function totalSupply() external view returns(uint256); + function balanceOf(address owner) external view returns(uint256); + function allowance(address owner, address spender) external view returns(uint256); + function transfer(address to, uint256 value) external returns(bool); + function transferFrom(address from, address to, uint256 value) external returns(bool); + function approve(address spender, uint256 value) external returns(bool); + function decreaseAllowance(address spender, uint256 subtractedValue) external returns (bool); + function increaseAllowance(address spender, uint256 addedValue) external returns (bool); event Transfer(address indexed from, address indexed to, uint256 value); event Approval(address indexed owner, address indexed spender, uint256 value); - //transfer, transferFrom must respect the result of verifyTransfer - function verifyTransfer(address _from, address _to, uint256 _value) external returns (bool success); + /** + * @notice Transfers of securities may fail for a number of reasons. So this function will used to understand the + * cause of failure by getting the byte value. Which will be the ESC that follows the EIP 1066. ESC can be mapped + * with a reson string to understand the failure cause, table of Ethereum status code will always reside off-chain + * @param _to address The address which you want to transfer to + * @param _value uint256 the amount of tokens to be transferred + * @param _data The `bytes _data` allows arbitrary data to be submitted alongside the transfer. + * @return byte Ethereum status code (ESC) + * @return bytes32 Application specific reason code + */ + function canTransfer(address _to, uint256 _value, bytes calldata _data) external view returns (byte statusCode, bytes32 reasonCode); + + // Emit at the time when module get added + event ModuleAdded( + uint8[] _types, + bytes32 indexed _name, + address indexed _moduleFactory, + address _module, + uint256 _moduleCost, + uint256 _budget, + bytes32 _label, + bool _archived + ); + + // Emit when the token details get updated + event UpdateTokenDetails(string _oldDetails, string _newDetails); + // Emit when the token name get updated + event UpdateTokenName(string _oldName, string _newName); + // Emit when the granularity get changed + event GranularityChanged(uint256 _oldGranularity, uint256 _newGranularity); + // Emit when is permanently frozen by the issuer + event FreezeIssuance(); + // Emit when transfers are frozen or unfrozen + event FreezeTransfers(bool _status); + // Emit when new checkpoint created + event CheckpointCreated(uint256 indexed _checkpointId, uint256 _investorLength); + // Events to log controller actions + event SetController(address indexed _oldController, address indexed _newController); + //Event emit when the global treasury wallet address get changed + event TreasuryWalletChanged(address _oldTreasuryWallet, address _newTreasuryWallet); + event DisableController(); + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + event TokenUpgraded(uint8 _major, uint8 _minor, uint8 _patch); + + // Emit when Module get archived from the securityToken + event ModuleArchived(uint8[] _types, address _module); //Event emitted by the tokenLib. + // Emit when Module get unarchived from the securityToken + event ModuleUnarchived(uint8[] _types, address _module); //Event emitted by the tokenLib. + // Emit when Module get removed from the securityToken + event ModuleRemoved(uint8[] _types, address _module); //Event emitted by the tokenLib. + // Emit when the budget allocated to a module is changed + event ModuleBudgetChanged(uint8[] _moduleTypes, address _module, uint256 _oldBudget, uint256 _budget); //Event emitted by the tokenLib. + + // Transfer Events + event TransferByPartition( + bytes32 indexed _fromPartition, + address _operator, + address indexed _from, + address indexed _to, + uint256 _value, + bytes _data, + bytes _operatorData + ); + + // Operator Events + event AuthorizedOperator(address indexed operator, address indexed tokenHolder); + event RevokedOperator(address indexed operator, address indexed tokenHolder); + event AuthorizedOperatorByPartition(bytes32 indexed partition, address indexed operator, address indexed tokenHolder); + event RevokedOperatorByPartition(bytes32 indexed partition, address indexed operator, address indexed tokenHolder); + + // Issuance / Redemption Events + event IssuedByPartition(bytes32 indexed partition, address indexed to, uint256 value, bytes data); + event RedeemedByPartition(bytes32 indexed partition, address indexed operator, address indexed from, uint256 value, bytes data, bytes operatorData); + + // Document Events + event DocumentRemoved(bytes32 indexed _name, string _uri, bytes32 _documentHash); + event DocumentUpdated(bytes32 indexed _name, string _uri, bytes32 _documentHash); + + // Controller Events + event ControllerTransfer( + address _controller, + address indexed _from, + address indexed _to, + uint256 _value, + bytes _data, + bytes _operatorData + ); + + event ControllerRedemption( + address _controller, + address indexed _tokenHolder, + uint256 _value, + bytes _data, + bytes _operatorData + ); + + // Issuance / Redemption Events + event Issued(address indexed _operator, address indexed _to, uint256 _value, bytes _data); + event Redeemed(address indexed _operator, address indexed _from, uint256 _value, bytes _data); + + /** + * @notice Initialization function + * @dev Expected to be called atomically with the proxy being created, by the owner of the token + * @dev Can only be called once + */ + function initialize(address _getterDelegate) external; + + /** + * @notice The standard provides an on-chain function to determine whether a transfer will succeed, + * and return details indicating the reason if the transfer is not valid. + * @param _from The address from whom the tokens get transferred. + * @param _to The address to which to transfer tokens to. + * @param _partition The partition from which to transfer tokens + * @param _value The amount of tokens to transfer from `_partition` + * @param _data Additional data attached to the transfer of tokens + * @return ESC (Ethereum Status Code) following the EIP-1066 standard + * @return Application specific reason codes with additional details + * @return The partition to which the transferred tokens were allocated for the _to address + */ + function canTransferByPartition( + address _from, + address _to, + bytes32 _partition, + uint256 _value, + bytes calldata _data + ) + external + view + returns (byte statusCode, bytes32 reasonCode, bytes32 partition); + + /** + * @notice Transfers of securities may fail for a number of reasons. So this function will used to understand the + * cause of failure by getting the byte value. Which will be the ESC that follows the EIP 1066. ESC can be mapped + * with a reson string to understand the failure cause, table of Ethereum status code will always reside off-chain + * @param _from address The address which you want to send tokens from + * @param _to address The address which you want to transfer to + * @param _value uint256 the amount of tokens to be transferred + * @param _data The `bytes _data` allows arbitrary data to be submitted alongside the transfer. + * @return byte Ethereum status code (ESC) + * @return bytes32 Application specific reason code + */ + function canTransferFrom(address _from, address _to, uint256 _value, bytes calldata _data) external view returns (byte statusCode, bytes32 reasonCode); + + /** + * @notice Used to attach a new document to the contract, or update the URI or hash of an existing attached document + * @dev Can only be executed by the owner of the contract. + * @param _name Name of the document. It should be unique always + * @param _uri Off-chain uri of the document from where it is accessible to investors/advisors to read. + * @param _documentHash hash (of the contents) of the document. + */ + function setDocument(bytes32 _name, string calldata _uri, bytes32 _documentHash) external; + + /** + * @notice Used to remove an existing document from the contract by giving the name of the document. + * @dev Can only be executed by the owner of the contract. + * @param _name Name of the document. It should be unique always + */ + function removeDocument(bytes32 _name) external; + + /** + * @notice Used to return the details of a document with a known name (`bytes32`). + * @param _name Name of the document + * @return string The URI associated with the document. + * @return bytes32 The hash (of the contents) of the document. + * @return uint256 the timestamp at which the document was last modified. + */ + function getDocument(bytes32 _name) external view returns (string memory documentUri, bytes32 documentHash, uint256 documentTime); + + /** + * @notice Used to retrieve a full list of documents attached to the smart contract. + * @return bytes32 List of all documents names present in the contract. + */ + function getAllDocuments() external view returns (bytes32[] memory documentNames); + + /** + * @notice In order to provide transparency over whether `controllerTransfer` / `controllerRedeem` are useable + * or not `isControllable` function will be used. + * @dev If `isControllable` returns `false` then it always return `false` and + * `controllerTransfer` / `controllerRedeem` will always revert. + * @return bool `true` when controller address is non-zero otherwise return `false`. + */ + function isControllable() external view returns (bool controlled); + + /** + * @notice Checks if an address is a module of certain type + * @param _module Address to check + * @param _type type to check against + */ + function isModule(address _module, uint8 _type) external view returns(bool isValid); + + /** + * @notice This function must be called to increase the total supply (Corresponds to mint function of ERC20). + * @dev It only be called by the token issuer or the operator defined by the issuer. ERC1594 doesn't have + * have the any logic related to operator but its superset ERC1400 have the operator logic and this function + * is allowed to call by the operator. + * @param _tokenHolder The account that will receive the created tokens (account should be whitelisted or KYCed). + * @param _value The amount of tokens need to be issued + * @param _data The `bytes _data` allows arbitrary data to be submitted alongside the transfer. + */ + function issue(address _tokenHolder, uint256 _value, bytes calldata _data) external; + + /** + * @notice issue new tokens and assigns them to the target _tokenHolder. + * @dev Can only be called by the issuer or STO attached to the token. + * @param _tokenHolders A list of addresses to whom the minted tokens will be dilivered + * @param _values A list of number of tokens get minted and transfer to corresponding address of the investor from _tokenHolders[] list + * @return success + */ + function issueMulti(address[] calldata _tokenHolders, uint256[] calldata _values) external; /** - * @notice Mints new tokens and assigns them to the target _investor. - * Can only be called by the STO attached to the token (Or by the ST owner if there's no STO attached yet) - * @param _investor Address the tokens will be minted to - * @param _value is the amount of tokens that will be minted to the investor + * @notice Increases totalSupply and the corresponding amount of the specified owners partition + * @param _partition The partition to allocate the increase in balance + * @param _tokenHolder The token holder whose balance should be increased + * @param _value The amount by which to increase the balance + * @param _data Additional data attached to the minting of tokens */ - function mint(address _investor, uint256 _value) external returns (bool success); + function issueByPartition(bytes32 _partition, address _tokenHolder, uint256 _value, bytes calldata _data) external; /** - * @notice Mints new tokens and assigns them to the target _investor. - * Can only be called by the STO attached to the token (Or by the ST owner if there's no STO attached yet) - * @param _investor Address the tokens will be minted to - * @param _value is The amount of tokens that will be minted to the investor - * @param _data Data to indicate validation + * @notice Decreases totalSupply and the corresponding amount of the specified partition of msg.sender + * @param _partition The partition to allocate the decrease in balance + * @param _value The amount by which to decrease the balance + * @param _data Additional data attached to the burning of tokens */ - function mintWithData(address _investor, uint256 _value, bytes _data) external returns (bool success); + function redeemByPartition(bytes32 _partition, uint256 _value, bytes calldata _data) external; /** - * @notice Used to burn the securityToken on behalf of someone else - * @param _from Address for whom to burn tokens - * @param _value No. of tokens to be burned - * @param _data Data to indicate validation + * @notice This function redeem an amount of the token of a msg.sender. For doing so msg.sender may incentivize + * using different ways that could be implemented with in the `redeem` function definition. But those implementations + * are out of the scope of the ERC1594. + * @param _value The amount of tokens need to be redeemed + * @param _data The `bytes _data` it can be used in the token contract to authenticate the redemption. */ - function burnFromWithData(address _from, uint256 _value, bytes _data) external; + function redeem(uint256 _value, bytes calldata _data) external; /** - * @notice Used to burn the securityToken - * @param _value No. of tokens to be burned - * @param _data Data to indicate validation + * @notice This function redeem an amount of the token of a msg.sender. For doing so msg.sender may incentivize + * using different ways that could be implemented with in the `redeem` function definition. But those implementations + * are out of the scope of the ERC1594. + * @dev It is analogy to `transferFrom` + * @param _tokenHolder The account whose tokens gets redeemed. + * @param _value The amount of tokens need to be redeemed + * @param _data The `bytes _data` it can be used in the token contract to authenticate the redemption. */ - function burnWithData(uint256 _value, bytes _data) external; + function redeemFrom(address _tokenHolder, uint256 _value, bytes calldata _data) external; - event Minted(address indexed _to, uint256 _value); - event Burnt(address indexed _burner, uint256 _value); + /** + * @notice Decreases totalSupply and the corresponding amount of the specified partition of tokenHolder + * @dev This function can only be called by the authorised operator. + * @param _partition The partition to allocate the decrease in balance. + * @param _tokenHolder The token holder whose balance should be decreased + * @param _value The amount by which to decrease the balance + * @param _data Additional data attached to the burning of tokens + * @param _operatorData Additional data attached to the transfer of tokens by the operator + */ + function operatorRedeemByPartition( + bytes32 _partition, + address _tokenHolder, + uint256 _value, + bytes calldata _data, + bytes calldata _operatorData + ) external; - // Permissions this to a Permission module, which has a key of 1 - // If no Permission return false - note that IModule withPerm will allow ST owner all permissions anyway - // this allows individual modules to override this logic if needed (to not allow ST owner all permissions) - function checkPermission(address _delegate, address _module, bytes32 _perm) external view returns (bool); + /** + * @notice Validate permissions with PermissionManager if it exists, If no Permission return false + * @dev Note that IModule withPerm will allow ST owner all permissions anyway + * @dev this allows individual modules to override this logic if needed (to not allow ST owner all permissions) + * @param _delegate address of delegate + * @param _module address of PermissionManager module + * @param _perm the permissions + * @return success + */ + function checkPermission(address _delegate, address _module, bytes32 _perm) external view returns(bool hasPermission); /** * @notice Returns module list for a module type @@ -68,51 +301,60 @@ interface ISecurityToken { * @return address Module address * @return address Module factory address * @return bool Module archived - * @return uint8 Module type - * @return uint256 Module index - * @return uint256 Name index - + * @return uint8 Array of module types + * @return bytes32 Module label */ - function getModule(address _module) external view returns(bytes32, address, address, bool, uint8, uint256, uint256); + function getModule(address _module) external view returns (bytes32 moduleName, address moduleAddress, address factoryAddress, bool isArchived, uint8[] memory moduleTypes, bytes32 moduleLabel); /** * @notice Returns module list for a module name * @param _name Name of the module * @return address[] List of modules with this name */ - function getModulesByName(bytes32 _name) external view returns (address[]); + function getModulesByName(bytes32 _name) external view returns(address[] memory modules); /** * @notice Returns module list for a module type * @param _type Type of the module * @return address[] List of modules with this type */ - function getModulesByType(uint8 _type) external view returns (address[]); + function getModulesByType(uint8 _type) external view returns(address[] memory modules); + + /** + * @notice use to return the global treasury wallet + */ + function getTreasuryWallet() external view returns(address treasuryWallet); /** * @notice Queries totalSupply at a specified checkpoint * @param _checkpointId Checkpoint ID to query as of */ - function totalSupplyAt(uint256 _checkpointId) external view returns (uint256); + function totalSupplyAt(uint256 _checkpointId) external view returns(uint256 supply); /** * @notice Queries balance at a specified checkpoint * @param _investor Investor to query balance for * @param _checkpointId Checkpoint ID to query as of */ - function balanceOfAt(address _investor, uint256 _checkpointId) external view returns (uint256); + function balanceOfAt(address _investor, uint256 _checkpointId) external view returns(uint256 balance); /** * @notice Creates a checkpoint that can be used to query historical balances / totalSuppy */ - function createCheckpoint() external returns (uint256); + function createCheckpoint() external returns(uint256 checkpointId); /** - * @notice Gets length of investors array - * NB - this length may differ from investorCount if the list has not been pruned of zero-balance investors - * @return Length + * @notice Gets list of times that checkpoints were created + * @return List of checkpoint times */ - function getInvestors() external view returns (address[]); + function getCheckpointTimes() external view returns(uint256[] memory checkpointTimes); + + /** + * @notice returns an array of investors + * NB - this length may differ from investorCount as it contains all investors that ever held tokens + * @return list of addresses + */ + function getInvestors() external view returns(address[] memory investors); /** * @notice returns an array of investors at a given checkpoint @@ -120,7 +362,16 @@ interface ISecurityToken { * @param _checkpointId Checkpoint id at which investor list is to be populated * @return list of investors */ - function getInvestorsAt(uint256 _checkpointId) external view returns(address[]); + function getInvestorsAt(uint256 _checkpointId) external view returns(address[] memory investors); + + /** + * @notice returns an array of investors with non zero balance at a given checkpoint + * @param _checkpointId Checkpoint id at which investor list is to be populated + * @param _start Position of investor to start iteration from + * @param _end Position of investor to stop iteration at + * @return list of investors + */ + function getInvestorsSubsetAt(uint256 _checkpointId, uint256 _start, uint256 _end) external view returns(address[] memory investors); /** * @notice generates subset of investors @@ -129,55 +380,90 @@ interface ISecurityToken { * @param _end Position of investor to stop iteration at * @return list of investors */ - function iterateInvestors(uint256 _start, uint256 _end) external view returns(address[]); - + function iterateInvestors(uint256 _start, uint256 _end) external view returns(address[] memory investors); + /** * @notice Gets current checkpoint ID * @return Id */ - function currentCheckpointId() external view returns (uint256); + function currentCheckpointId() external view returns(uint256 checkpointId); /** - * @notice Gets an investor at a particular index - * @param _index Index to return address from - * @return Investor address - */ - function investors(uint256 _index) external view returns (address); + * @notice Determines whether `_operator` is an operator for all partitions of `_tokenHolder` + * @param _operator The operator to check + * @param _tokenHolder The token holder to check + * @return Whether the `_operator` is an operator for all partitions of `_tokenHolder` + */ + function isOperator(address _operator, address _tokenHolder) external view returns (bool isValid); + + /** + * @notice Determines whether `_operator` is an operator for a specified partition of `_tokenHolder` + * @param _partition The partition to check + * @param _operator The operator to check + * @param _tokenHolder The token holder to check + * @return Whether the `_operator` is an operator for a specified partition of `_tokenHolder` + */ + function isOperatorForPartition(bytes32 _partition, address _operator, address _tokenHolder) external view returns (bool isValid); + + /** + * @notice Return all partitions + * @param _tokenHolder Whom balance need to queried + * @return List of partitions + */ + function partitionsOf(address _tokenHolder) external view returns (bytes32[] memory partitions); - /** - * @notice Allows the owner to withdraw unspent POLY stored by them on the ST or any ERC20 token. - * @dev Owner can transfer POLY to the ST which will be used to pay for modules that require a POLY fee. - * @param _tokenContract Address of the ERC20Basic compliance token - * @param _value Amount of POLY to withdraw + /** + * @notice Gets data store address + * @return data store address + */ + function dataStore() external view returns (address dataStoreAddress); + + /** + * @notice Allows owner to change data store + * @param _dataStore Address of the token data store */ + function changeDataStore(address _dataStore) external; + + + /** + * @notice Allows to change the treasury wallet address + * @param _wallet Ethereum address of the treasury wallet + */ + function changeTreasuryWallet(address _wallet) external; + + /** + * @notice Allows the owner to withdraw unspent POLY stored by them on the ST or any ERC20 token. + * @dev Owner can transfer POLY to the ST which will be used to pay for modules that require a POLY fee. + * @param _tokenContract Address of the ERC20Basic compliance token + * @param _value Amount of POLY to withdraw + */ function withdrawERC20(address _tokenContract, uint256 _value) external; /** - * @notice Allows owner to approve more POLY to one of the modules + * @notice Allows owner to increase/decrease POLY approval of one of the modules * @param _module Module address - * @param _budget New budget + * @param _change Change in allowance + * @param _increase True if budget has to be increased, false if decrease */ - function changeModuleBudget(address _module, uint256 _budget) external; + function changeModuleBudget(address _module, uint256 _change, bool _increase) external; /** * @notice Changes the tokenDetails * @param _newTokenDetails New token details */ - function updateTokenDetails(string _newTokenDetails) external; + function updateTokenDetails(string calldata _newTokenDetails) external; /** - * @notice Allows the owner to change token granularity - * @param _granularity Granularity level of the token + * @notice Allows owner to change token name + * @param _name new name of the token */ - function changeGranularity(uint256 _granularity) external; + function changeName(string calldata _name) external; /** - * @notice Removes addresses with zero balances from the investors list - * @param _start Index in investors list at which to start removing zero balances - * @param _iters Max number of iterations of the for loop - * NB - pruning this list will mean you may not be able to iterate over investors on-chain as of a historical checkpoint + * @notice Allows the owner to change token granularity + * @param _granularity Granularity level of the token */ - function pruneInvestors(uint256 _start, uint256 _iters) external; + function changeGranularity(uint256 _granularity) external; /** * @notice Freezes all the transfers @@ -190,18 +476,30 @@ interface ISecurityToken { function unfreezeTransfers() external; /** - * @notice Ends token minting period permanently + * @notice Permanently freeze issuance of this security token. + * @dev It MUST NOT be possible to increase `totalSuppy` after this function is called. */ - function freezeMinting() external; + function freezeIssuance(bytes calldata _signature) external; /** - * @notice Mints new tokens and assigns them to the target investors. - * Can only be called by the STO attached to the token or by the Issuer (Security Token contract owner) - * @param _investors A list of addresses to whom the minted tokens will be delivered - * @param _values A list of the amount of tokens to mint to corresponding addresses from _investor[] list - * @return Success - */ - function mintMulti(address[] _investors, uint256[] _values) external returns (bool success); + * @notice Attachs a module to the SecurityToken + * @dev E.G.: On deployment (through the STR) ST gets a TransferManager module attached to it + * @dev to control restrictions on transfers. + * @param _moduleFactory is the address of the module factory to be added + * @param _data is data packed into bytes used to further configure the module (See STO usage) + * @param _maxCost max amount of POLY willing to pay to the module. + * @param _budget max amount of ongoing POLY willing to assign to the module. + * @param _label custom module label. + * @param _archived whether to add the module as an archived module + */ + function addModuleWithLabel( + address _moduleFactory, + bytes calldata _data, + uint256 _maxCost, + uint256 _budget, + bytes32 _label, + bool _archived + ) external; /** * @notice Function used to attach a module to the security token @@ -213,13 +511,10 @@ interface ISecurityToken { * @param _moduleFactory is the address of the module factory to be added * @param _data is data packed into bytes used to further configure the module (See STO usage) * @param _maxCost max amount of POLY willing to pay to module. (WIP) + * @param _budget max amount of ongoing POLY willing to assign to the module. + * @param _archived whether to add the module as an archived module */ - function addModule( - address _moduleFactory, - bytes _data, - uint256 _maxCost, - uint256 _budget - ) external; + function addModule(address _moduleFactory, bytes calldata _data, uint256 _maxCost, uint256 _budget, bool _archived) external; /** * @notice Archives a module attached to the SecurityToken @@ -246,50 +541,65 @@ interface ISecurityToken { function setController(address _controller) external; /** - * @notice Used by a controller to execute a forced transfer - * @param _from address from which to take tokens - * @param _to address where to send tokens - * @param _value amount of tokens to transfer - * @param _data data to indicate validation - * @param _log data attached to the transfer by controller to emit in event + * @notice This function allows an authorised address to transfer tokens between any two token holders. + * The transfer must still respect the balances of the token holders (so the transfer must be for at most + * `balanceOf(_from)` tokens) and potentially also need to respect other transfer restrictions. + * @dev This function can only be executed by the `controller` address. + * @param _from Address The address which you want to send tokens from + * @param _to Address The address which you want to transfer to + * @param _value uint256 the amount of tokens to be transferred + * @param _data data to validate the transfer. (It is not used in this reference implementation + * because use of `_data` parameter is implementation specific). + * @param _operatorData data attached to the transfer by controller to emit in event. (It is more like a reason string + * for calling this function (aka force transfer) which provides the transparency on-chain). */ - function forceTransfer(address _from, address _to, uint256 _value, bytes _data, bytes _log) external; + function controllerTransfer(address _from, address _to, uint256 _value, bytes calldata _data, bytes calldata _operatorData) external; /** - * @notice Used by a controller to execute a foced burn - * @param _from address from which to take tokens - * @param _value amount of tokens to transfer - * @param _data data to indicate validation - * @param _log data attached to the transfer by controller to emit in event + * @notice This function allows an authorised address to redeem tokens for any token holder. + * The redemption must still respect the balances of the token holder (so the redemption must be for at most + * `balanceOf(_tokenHolder)` tokens) and potentially also need to respect other transfer restrictions. + * @dev This function can only be executed by the `controller` address. + * @param _tokenHolder The account whose tokens will be redeemed. + * @param _value uint256 the amount of tokens need to be redeemed. + * @param _data data to validate the transfer. (It is not used in this reference implementation + * because use of `_data` parameter is implementation specific). + * @param _operatorData data attached to the transfer by controller to emit in event. (It is more like a reason string + * for calling this function (aka force transfer) which provides the transparency on-chain). */ - function forceBurn(address _from, uint256 _value, bytes _data, bytes _log) external; + function controllerRedeem(address _tokenHolder, uint256 _value, bytes calldata _data, bytes calldata _operatorData) external; /** * @notice Used by the issuer to permanently disable controller functionality * @dev enabled via feature switch "disableControllerAllowed" */ - function disableController() external; + function disableController(bytes calldata _signature) external; - /** + /** * @notice Used to get the version of the securityToken */ - function getVersion() external view returns(uint8[]); + function getVersion() external view returns(uint8[] memory version); - /** + /** * @notice Gets the investor count */ - function getInvestorCount() external view returns(uint256); + function getInvestorCount() external view returns(uint256 investorCount); + + /** + * @notice Gets the holder count (investors with non zero balance) + */ + function holderCount() external view returns(uint256 count); - /** + /** * @notice Overloaded version of the transfer function * @param _to receiver of transfer * @param _value value of transfer * @param _data data to indicate validation * @return bool success */ - function transferWithData(address _to, uint256 _value, bytes _data) external returns (bool success); + function transferWithData(address _to, uint256 _value, bytes calldata _data) external; - /** + /** * @notice Overloaded version of the transferFrom function * @param _from sender of transfer * @param _to receiver of transfer @@ -297,11 +607,150 @@ interface ISecurityToken { * @param _data data to indicate validation * @return bool success */ - function transferFromWithData(address _from, address _to, uint256 _value, bytes _data) external returns(bool); + function transferFromWithData(address _from, address _to, uint256 _value, bytes calldata _data) external; + + /** + * @notice Transfers the ownership of tokens from a specified partition from one address to another address + * @param _partition The partition from which to transfer tokens + * @param _to The address to which to transfer tokens to + * @param _value The amount of tokens to transfer from `_partition` + * @param _data Additional data attached to the transfer of tokens + * @return The partition to which the transferred tokens were allocated for the _to address + */ + function transferByPartition(bytes32 _partition, address _to, uint256 _value, bytes calldata _data) external returns (bytes32 partition); - /** + /** + * @notice Get the balance according to the provided partitions + * @param _partition Partition which differentiate the tokens. + * @param _tokenHolder Whom balance need to queried + * @return Amount of tokens as per the given partitions + */ + function balanceOfByPartition(bytes32 _partition, address _tokenHolder) external view returns(uint256 balance); + + /** * @notice Provides the granularity of the token * @return uint256 */ - function granularity() external view returns(uint256); + function granularity() external view returns(uint256 granularityAmount); + + /** + * @notice Provides the address of the polymathRegistry + * @return address + */ + function polymathRegistry() external view returns(address registryAddress); + + /** + * @notice Upgrades a module attached to the SecurityToken + * @param _module address of module to archive + */ + function upgradeModule(address _module) external; + + /** + * @notice Upgrades security token + */ + function upgradeToken() external; + + /** + * @notice A security token issuer can specify that issuance has finished for the token + * (i.e. no new tokens can be minted or issued). + * @dev If a token returns FALSE for `isIssuable()` then it MUST always return FALSE in the future. + * If a token returns FALSE for `isIssuable()` then it MUST never allow additional tokens to be issued. + * @return bool `true` signifies the minting is allowed. While `false` denotes the end of minting + */ + function isIssuable() external view returns (bool issuable); + + /** + * @notice Authorises an operator for all partitions of `msg.sender`. + * NB - Allowing investors to authorize an investor to be an operator of all partitions + * but it doesn't mean we operator is allowed to transfer the LOCKED partition values. + * Logic for this restriction is written in `operatorTransferByPartition()` function. + * @param _operator An address which is being authorised. + */ + function authorizeOperator(address _operator) external; + + /** + * @notice Revokes authorisation of an operator previously given for all partitions of `msg.sender`. + * NB - Allowing investors to authorize an investor to be an operator of all partitions + * but it doesn't mean we operator is allowed to transfer the LOCKED partition values. + * Logic for this restriction is written in `operatorTransferByPartition()` function. + * @param _operator An address which is being de-authorised + */ + function revokeOperator(address _operator) external; + + /** + * @notice Authorises an operator for a given partition of `msg.sender` + * @param _partition The partition to which the operator is authorised + * @param _operator An address which is being authorised + */ + function authorizeOperatorByPartition(bytes32 _partition, address _operator) external; + + /** + * @notice Revokes authorisation of an operator previously given for a specified partition of `msg.sender` + * @param _partition The partition to which the operator is de-authorised + * @param _operator An address which is being de-authorised + */ + function revokeOperatorByPartition(bytes32 _partition, address _operator) external; + + /** + * @notice Transfers the ownership of tokens from a specified partition from one address to another address + * @param _partition The partition from which to transfer tokens. + * @param _from The address from which to transfer tokens from + * @param _to The address to which to transfer tokens to + * @param _value The amount of tokens to transfer from `_partition` + * @param _data Additional data attached to the transfer of tokens + * @param _operatorData Additional data attached to the transfer of tokens by the operator + * @return The partition to which the transferred tokens were allocated for the _to address + */ + function operatorTransferByPartition( + bytes32 _partition, + address _from, + address _to, + uint256 _value, + bytes calldata _data, + bytes calldata _operatorData + ) + external + returns (bytes32 partition); + + /* + * @notice Returns if transfers are currently frozen or not + */ + function transfersFrozen() external view returns (bool isFrozen); + + /** + * @dev Allows the current owner to transfer control of the contract to a newOwner. + * @param newOwner The address to transfer ownership to. + */ + function transferOwnership(address newOwner) external; + + /** + * @return true if `msg.sender` is the owner of the contract. + */ + function isOwner() external view returns (bool); + + /** + * @return the address of the owner. + */ + function owner() external view returns (address ownerAddress); + + function controller() external view returns(address controllerAddress); + + function moduleRegistry() external view returns(address moduleRegistryAddress); + + function securityTokenRegistry() external view returns(address securityTokenRegistryAddress); + + function polyToken() external view returns(address polyTokenAddress); + + function tokenFactory() external view returns(address tokenFactoryAddress); + + function getterDelegate() external view returns(address delegate); + + function controllerDisabled() external view returns(bool isDisabled); + + function initialized() external view returns(bool isInitialized); + + function tokenDetails() external view returns(string memory details); + + function updateFromRegistry() external; + } diff --git a/contracts/interfaces/ISecurityTokenRegistry.sol b/contracts/interfaces/ISecurityTokenRegistry.sol index 8db01f8ad..eae9ea019 100644 --- a/contracts/interfaces/ISecurityTokenRegistry.sol +++ b/contracts/interfaces/ISecurityTokenRegistry.sol @@ -1,18 +1,143 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; /** * @title Interface for the Polymath Security Token Registry contract */ interface ISecurityTokenRegistry { - /** - * @notice Creates a new Security Token and saves it to the registry - * @param _name Name of the token - * @param _ticker Ticker ticker of the security token - * @param _tokenDetails Off-chain details of the token - * @param _divisible Whether the token is divisible or not + // Emit when network becomes paused + event Pause(address account); + // Emit when network becomes unpaused + event Unpause(address account); + // Emit when the ticker is removed from the registry + event TickerRemoved(string _ticker, address _removedBy); + // Emit when the token ticker expiry is changed + event ChangeExpiryLimit(uint256 _oldExpiry, uint256 _newExpiry); + // Emit when changeSecurityLaunchFee is called + event ChangeSecurityLaunchFee(uint256 _oldFee, uint256 _newFee); + // Emit when changeTickerRegistrationFee is called + event ChangeTickerRegistrationFee(uint256 _oldFee, uint256 _newFee); + // Emit when Fee currency is changed + event ChangeFeeCurrency(bool _isFeeInPoly); + // Emit when ownership gets transferred + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + // Emit when ownership of the ticker gets changed + event ChangeTickerOwnership(string _ticker, address indexed _oldOwner, address indexed _newOwner); + // Emit at the time of launching a new security token of version 3.0+ + event NewSecurityToken( + string _ticker, + string _name, + address indexed _securityTokenAddress, + address indexed _owner, + uint256 _addedAt, + address _registrant, + bool _fromAdmin, + uint256 _usdFee, + uint256 _polyFee, + uint256 _protocolVersion + ); + // Emit at the time of launching a new security token v2.0. + // _registrationFee is in poly + event NewSecurityToken( + string _ticker, + string _name, + address indexed _securityTokenAddress, + address indexed _owner, + uint256 _addedAt, + address _registrant, + bool _fromAdmin, + uint256 _registrationFee + ); + // Emit when new ticker get registers + event RegisterTicker( + address indexed _owner, + string _ticker, + uint256 indexed _registrationDate, + uint256 indexed _expiryDate, + bool _fromAdmin, + uint256 _registrationFeePoly, + uint256 _registrationFeeUsd + ); + // Emit after ticker registration + // _registrationFee is in poly + // fee in usd is not being emitted to maintain backwards compatibility + event RegisterTicker( + address indexed _owner, + string _ticker, + string _name, + uint256 indexed _registrationDate, + uint256 indexed _expiryDate, + bool _fromAdmin, + uint256 _registrationFee + ); + // Emit at when issuer refreshes exisiting token + event SecurityTokenRefreshed( + string _ticker, + string _name, + address indexed _securityTokenAddress, + address indexed _owner, + uint256 _addedAt, + address _registrant, + uint256 _protocolVersion + ); + event ProtocolFactorySet(address indexed _STFactory, uint8 _major, uint8 _minor, uint8 _patch); + event LatestVersionSet(uint8 _major, uint8 _minor, uint8 _patch); + event ProtocolFactoryRemoved(address indexed _STFactory, uint8 _major, uint8 _minor, uint8 _patch); + + /** + * @notice Deploys an instance of a new Security Token of version 2.0 and records it to the registry + * @dev this function is for backwards compatibilty with 2.0 dApp. + * @param _name is the name of the token + * @param _ticker is the ticker symbol of the security token + * @param _tokenDetails is the off-chain details of the token + * @param _divisible is whether or not the token is divisible + */ + function generateSecurityToken( + string calldata _name, + string calldata _ticker, + string calldata _tokenDetails, + bool _divisible + ) + external; + + /** + * @notice Deploys an instance of a new Security Token and records it to the registry + * @param _name is the name of the token + * @param _ticker is the ticker symbol of the security token + * @param _tokenDetails is the off-chain details of the token + * @param _divisible is whether or not the token is divisible + * @param _treasuryWallet Ethereum address which will holds the STs. + * @param _protocolVersion Version of securityToken contract + * - `_protocolVersion` is the packed value of uin8[3] array (it will be calculated offchain) + * - if _protocolVersion == 0 then latest version of securityToken will be generated + */ + function generateNewSecurityToken( + string calldata _name, + string calldata _ticker, + string calldata _tokenDetails, + bool _divisible, + address _treasuryWallet, + uint256 _protocolVersion + ) + external; + + /** + * @notice Deploys an instance of a new Security Token and replaces the old one in the registry + * This can be used to upgrade from version 2.0 of ST to 3.0 or in case something goes wrong with earlier ST + * @dev This function needs to be in STR 3.0. Defined public to avoid stack overflow + * @param _name is the name of the token + * @param _ticker is the ticker symbol of the security token + * @param _tokenDetails is the off-chain details of the token + * @param _divisible is whether or not the token is divisible */ - function generateSecurityToken(string _name, string _ticker, string _tokenDetails, bool _divisible) external; + function refreshSecurityToken( + string calldata _name, + string calldata _ticker, + string calldata _tokenDetails, + bool _divisible, + address _treasuryWallet + ) + external returns (address securityToken); /** * @notice Adds a new custom Security Token and saves it to the registry. (Token should follow the ISecurityToken interface) @@ -24,15 +149,50 @@ interface ISecurityTokenRegistry { * @param _deployedAt Timestamp at which security token comes deployed on the ethereum blockchain */ function modifySecurityToken( - string _name, - string _ticker, + string calldata _name, + string calldata _ticker, + address _owner, + address _securityToken, + string calldata _tokenDetails, + uint256 _deployedAt + ) + external; + + /** + * @notice Adds a new custom Security Token and saves it to the registry. (Token should follow the ISecurityToken interface) + * @param _ticker is the ticker symbol of the security token + * @param _owner is the owner of the token + * @param _securityToken is the address of the securityToken + * @param _tokenDetails is the off-chain details of the token + * @param _deployedAt is the timestamp at which the security token is deployed + */ + function modifyExistingSecurityToken( + string calldata _ticker, address _owner, address _securityToken, - string _tokenDetails, + string calldata _tokenDetails, uint256 _deployedAt ) external; + /** + * @notice Modifies the ticker details. Only Polymath has the ability to do so. + * @notice Only allowed to modify the tickers which are not yet deployed. + * @param _owner is the owner of the token + * @param _ticker is the token ticker + * @param _registrationDate is the date at which ticker is registered + * @param _expiryDate is the expiry date for the ticker + * @param _status is the token deployment status + */ + function modifyExistingTicker( + address _owner, + string calldata _ticker, + uint256 _registrationDate, + uint256 _expiryDate, + bool _status + ) + external; + /** * @notice Registers the token ticker for its particular owner * @notice once the token ticker is registered to its owner then no other issuer can claim @@ -41,25 +201,23 @@ interface ISecurityTokenRegistry { * @param _ticker Token ticker * @param _tokenName Name of the token */ - function registerTicker(address _owner, string _ticker, string _tokenName) external; + function registerTicker(address _owner, string calldata _ticker, string calldata _tokenName) external; /** - * @notice Changes the protocol version and the SecurityToken contract - * @notice Used only by Polymath to upgrade the SecurityToken contract and add more functionalities to future versions - * @notice Changing versions does not affect existing tokens. - * @param _STFactoryAddress Address of the proxy. - * @param _major Major version of the proxy. - * @param _minor Minor version of the proxy. - * @param _patch Patch version of the proxy - */ - function setProtocolVersion(address _STFactoryAddress, uint8 _major, uint8 _minor, uint8 _patch) external; + * @notice Registers the token ticker to the selected owner + * @notice Once the token ticker is registered to its owner then no other issuer can claim + * @notice its ownership. If the ticker expires and its issuer hasn't used it, then someone else can take it. + * @param _owner is address of the owner of the token + * @param _ticker is unique token ticker + */ + function registerNewTicker(address _owner, string calldata _ticker) external; /** * @notice Check that Security Token is registered * @param _securityToken Address of the Scurity token * @return bool */ - function isSecurityToken(address _securityToken) external view returns (bool); + function isSecurityToken(address _securityToken) external view returns(bool isValid); /** * @dev Allows the current owner to transfer control of the contract to a newOwner. @@ -72,40 +230,57 @@ interface ISecurityTokenRegistry { * @param _ticker Symbol of the Scurity token * @return address */ - function getSecurityTokenAddress(string _ticker) external view returns (address); + function getSecurityTokenAddress(string calldata _ticker) external view returns(address tokenAddress); - /** - * @notice Get security token data by its address - * @param _securityToken Address of the Scurity token. - * @return string Symbol of the Security Token. - * @return address Address of the issuer of Security Token. - * @return string Details of the Token. - * @return uint256 Timestamp at which Security Token get launched on Polymath platform. - */ - function getSecurityTokenData(address _securityToken) external view returns (string, address, string, uint256); + /** + * @notice Returns the security token data by address + * @param _securityToken is the address of the security token. + * @return string is the ticker of the security Token. + * @return address is the issuer of the security Token. + * @return string is the details of the security token. + * @return uint256 is the timestamp at which security Token was deployed. + */ + function getSecurityTokenData(address _securityToken) external view returns ( + string memory tokenSymbol, + address tokenAddress, + string memory tokenDetails, + uint256 tokenTime + ); /** * @notice Get the current STFactory Address */ - function getSTFactoryAddress() external view returns(address); + function getSTFactoryAddress() external view returns(address stFactoryAddress); + + /** + * @notice Returns the STFactory Address of a particular version + * @param _protocolVersion Packed protocol version + */ + function getSTFactoryAddressOfVersion(uint256 _protocolVersion) external view returns(address stFactory); /** * @notice Get Protocol version */ - function getProtocolVersion() external view returns(uint8[]); + function getLatestProtocolVersion() external view returns(uint8[] memory protocolVersion); /** * @notice Used to get the ticker list as per the owner * @param _owner Address which owns the list of tickers */ - function getTickersByOwner(address _owner) external view returns(bytes32[]); + function getTickersByOwner(address _owner) external view returns(bytes32[] memory tickers); /** * @notice Returns the list of tokens owned by the selected address * @param _owner is the address which owns the list of tickers * @dev Intention is that this is called off-chain so block gas limit is not relevant */ - function getTokensByOwner(address _owner) external view returns(address[]); + function getTokensByOwner(address _owner) external view returns(address[] memory tokens); + + /** + * @notice Returns the list of all tokens + * @dev Intention is that this is called off-chain so block gas limit is not relevant + */ + function getTokens() external view returns(address[] memory tokens); /** * @notice Returns the owner and timestamp for a given ticker @@ -116,7 +291,7 @@ interface ISecurityTokenRegistry { * @return string * @return bool */ - function getTickerDetails(string _ticker) external view returns (address, uint256, uint256, string, bool); + function getTickerDetails(string calldata _ticker) external view returns(address tickerOwner, uint256 tickerRegistration, uint256 tickerExpiry, string memory tokenName, bool tickerStatus); /** * @notice Modifies the ticker details. Only polymath account has the ability @@ -130,26 +305,26 @@ interface ISecurityTokenRegistry { */ function modifyTicker( address _owner, - string _ticker, - string _tokenName, + string calldata _ticker, + string calldata _tokenName, uint256 _registrationDate, uint256 _expiryDate, bool _status ) - external; + external; - /** + /** * @notice Removes the ticker details and associated ownership & security token mapping * @param _ticker Token ticker */ - function removeTicker(string _ticker) external; + function removeTicker(string calldata _ticker) external; /** * @notice Transfers the ownership of the ticker * @dev _newOwner Address whom ownership to transfer * @dev _ticker Ticker */ - function transferTickerOwnership(address _newOwner, string _ticker) external; + function transferTickerOwnership(address _newOwner, string calldata _ticker) external; /** * @notice Changes the expiry time for the token ticker @@ -157,52 +332,156 @@ interface ISecurityTokenRegistry { */ function changeExpiryLimit(uint256 _newExpiry) external; + /** + * @notice Sets the ticker registration fee in USD tokens. Only Polymath. + * @param _tickerRegFee is the registration fee in USD tokens (base 18 decimals) + */ + function changeTickerRegistrationFee(uint256 _tickerRegFee) external; + /** - * @notice Sets the ticker registration fee in POLY tokens - * @param _tickerRegFee Registration fee in POLY tokens (base 18 decimals) + * @notice Sets the ticker registration fee in USD tokens. Only Polymath. + * @param _stLaunchFee is the registration fee in USD tokens (base 18 decimals) */ - function changeTickerRegistrationFee(uint256 _tickerRegFee) external; + function changeSecurityLaunchFee(uint256 _stLaunchFee) external; - /** - * @notice Sets the ticker registration fee in POLY tokens - * @param _stLaunchFee Registration fee in POLY tokens (base 18 decimals) + /** + * @notice Sets the ticker registration and ST launch fee amount and currency + * @param _tickerRegFee is the ticker registration fee (base 18 decimals) + * @param _stLaunchFee is the st generation fee (base 18 decimals) + * @param _isFeeInPoly defines if the fee is in poly or usd + */ + function changeFeesAmountAndCurrency(uint256 _tickerRegFee, uint256 _stLaunchFee, bool _isFeeInPoly) external; + + /** + * @notice Changes the SecurityToken contract for a particular factory version + * @notice Used only by Polymath to upgrade the SecurityToken contract and add more functionalities to future versions + * @notice Changing versions does not affect existing tokens. + * @param _STFactoryAddress is the address of the proxy. + * @param _major Major version of the proxy. + * @param _minor Minor version of the proxy. + * @param _patch Patch version of the proxy + */ + function setProtocolFactory(address _STFactoryAddress, uint8 _major, uint8 _minor, uint8 _patch) external; + + /** + * @notice Removes a STFactory + * @param _major Major version of the proxy. + * @param _minor Minor version of the proxy. + * @param _patch Patch version of the proxy + */ + function removeProtocolFactory(uint8 _major, uint8 _minor, uint8 _patch) external; + + /** + * @notice Changes the default protocol version + * @notice Used only by Polymath to upgrade the SecurityToken contract and add more functionalities to future versions + * @notice Changing versions does not affect existing tokens. + * @param _major Major version of the proxy. + * @param _minor Minor version of the proxy. + * @param _patch Patch version of the proxy */ - function changeSecurityLaunchFee(uint256 _stLaunchFee) external; + function setLatestVersion(uint8 _major, uint8 _minor, uint8 _patch) external; /** - * @notice Change the PolyToken address - * @param _newAddress Address of the polytoken + * @notice Changes the PolyToken address. Only Polymath. + * @param _newAddress is the address of the polytoken. */ function updatePolyTokenAddress(address _newAddress) external; + /** + * @notice Used to update the polyToken contract address + */ + function updateFromRegistry() external; + /** * @notice Gets the security token launch fee * @return Fee amount */ - function getSecurityTokenLaunchFee() external view returns(uint256); + function getSecurityTokenLaunchFee() external returns(uint256 fee); /** * @notice Gets the ticker registration fee * @return Fee amount */ - function getTickerRegistrationFee() external view returns(uint256); + function getTickerRegistrationFee() external returns(uint256 fee); + + /** + * @notice Set the getter contract address + * @param _getterContract Address of the contract + */ + function setGetterRegistry(address _getterContract) external; + + /** + * @notice Returns the usd & poly fee for a particular feetype + * @param _feeType Key corresponding to fee type + */ + function getFees(bytes32 _feeType) external returns (uint256 usdFee, uint256 polyFee); + + /** + * @notice Returns the list of tokens to which the delegate has some access + * @param _delegate is the address for the delegate + * @dev Intention is that this is called off-chain so block gas limit is not relevant + */ + function getTokensByDelegate(address _delegate) external view returns(address[] memory tokens); /** * @notice Gets the expiry limit * @return Expiry limit */ - function getExpiryLimit() external view returns(uint256); + function getExpiryLimit() external view returns(uint256 expiry); + + /** + * @notice Gets the status of the ticker + * @param _ticker Ticker whose status need to determine + * @return bool + */ + function getTickerStatus(string calldata _ticker) external view returns(bool status); + + /** + * @notice Gets the fee currency + * @return true = poly, false = usd + */ + function getIsFeeInPoly() external view returns(bool isInPoly); + + /** + * @notice Gets the owner of the ticker + * @param _ticker Ticker whose owner need to determine + * @return address Address of the owner + */ + function getTickerOwner(string calldata _ticker) external view returns(address owner); /** * @notice Checks whether the registry is paused or not * @return bool */ - function isPaused() external view returns(bool); + function isPaused() external view returns(bool paused); + + /** + * @notice Called by the owner to pause, triggers stopped state + */ + function pause() external; + + /** + * @notice Called by the owner to unpause, returns to normal state + */ + function unpause() external; + + /** + * @notice Reclaims all ERC20Basic compatible tokens + * @param _tokenContract is the address of the token contract + */ + function reclaimERC20(address _tokenContract) external; /** * @notice Gets the owner of the contract * @return address owner */ - function owner() external view returns(address); + function owner() external view returns(address ownerAddress); + + /** + * @notice Checks if the entered ticker is registered and has not expired + * @param _ticker is the token ticker + * @return bool + */ + function tickerAvailable(string calldata _ticker) external view returns(bool); } diff --git a/contracts/interfaces/ITransferManager.sol b/contracts/interfaces/ITransferManager.sol new file mode 100644 index 000000000..8ca4339b7 --- /dev/null +++ b/contracts/interfaces/ITransferManager.sol @@ -0,0 +1,29 @@ +pragma solidity 0.5.8; + +/** + * @title Interface to be implemented by all Transfer Manager modules + */ +interface ITransferManager { + // If verifyTransfer returns: + // FORCE_VALID, the transaction will always be valid, regardless of other TM results + // INVALID, then the transfer should not be allowed regardless of other TM results + // VALID, then the transfer is valid for this TM + // NA, then the result from this TM is ignored + enum Result {INVALID, NA, VALID, FORCE_VALID} + + /** + * @notice Determines if the transfer between these two accounts can happen + */ + function executeTransfer(address _from, address _to, uint256 _amount, bytes calldata _data) external returns(Result result); + + function verifyTransfer(address _from, address _to, uint256 _amount, bytes calldata _data) external view returns(Result result, bytes32 partition); + + /** + * @notice return the amount of tokens for a given user as per the partition + * @param _partition Identifier + * @param _tokenHolder Whom token amount need to query + * @param _additionalBalance It is the `_value` that transfer during transfer/transferFrom function call + */ + function getTokensByPartition(bytes32 _partition, address _tokenHolder, uint256 _additionalBalance) external view returns(uint256 amount); + +} diff --git a/contracts/interfaces/IUSDTieredSTOProxy.sol b/contracts/interfaces/IUSDTieredSTOProxy.sol deleted file mode 100644 index 3aec141a0..000000000 --- a/contracts/interfaces/IUSDTieredSTOProxy.sol +++ /dev/null @@ -1,24 +0,0 @@ -pragma solidity ^0.4.24; - -/** - * @title Interface for security token proxy deployment - */ -interface IUSDTieredSTOProxy { - - /** - * @notice Deploys the STO. - * @param _securityToken Contract address of the securityToken - * @param _polyAddress Contract address of the PolyToken - * @param _factoryAddress Contract address of the factory - * @return address Address of the deployed STO - */ - function deploySTO(address _securityToken, address _polyAddress, address _factoryAddress) external returns (address); - - /** - * @notice Used to get the init function signature - * @param _contractAddress Address of the STO contract - * @return bytes4 - */ - function getInitFunction(address _contractAddress) external returns (bytes4); - -} \ No newline at end of file diff --git a/contracts/interfaces/IUpgradableTokenFactory.sol b/contracts/interfaces/IUpgradableTokenFactory.sol new file mode 100644 index 000000000..a30039e32 --- /dev/null +++ b/contracts/interfaces/IUpgradableTokenFactory.sol @@ -0,0 +1,14 @@ +pragma solidity 0.5.8; + +/** + * @title Interface to be implemented by upgradable token factories + */ +interface IUpgradableTokenFactory { + + /** + * @notice Used to upgrade a token + * @param _maxModuleType maximum module type enumeration + */ + function upgradeToken(uint8 _maxModuleType) external; + +} diff --git a/contracts/interfaces/IVoting.sol b/contracts/interfaces/IVoting.sol new file mode 100644 index 000000000..61a8a158d --- /dev/null +++ b/contracts/interfaces/IVoting.sol @@ -0,0 +1,60 @@ +pragma solidity 0.5.8; + +interface IVoting { + + /** + * @notice Allows the token issuer to set the active stats of a ballot + * @param _ballotId The index of the target ballot + * @param _isActive The bool value of the active stats of the ballot + * @return bool success + */ + function changeBallotStatus(uint256 _ballotId, bool _isActive) external; + + /** + * @notice Queries the result of a given ballot + * @param _ballotId Id of the target ballot + * @return uint256 voteWeighting + * @return uint256 tieWith + * @return uint256 winningProposal + * @return bool isVotingSucceed + * @return uint256 totalVoters + */ + function getBallotResults(uint256 _ballotId) external view returns( + uint256[] memory voteWeighting, + uint256[] memory tieWith, + uint256 winningProposal, + bool isVotingSucceed, + uint256 totalVoters + ); + + /** + * @notice Get the voted proposal + * @param _ballotId Id of the ballot + * @param _voter Address of the voter + */ + function getSelectedProposal(uint256 _ballotId, address _voter) external view returns(uint256 proposalId); + + /** + * @notice Get the details of the ballot + * @param _ballotId The index of the target ballot + * @return uint256 quorum + * @return uint256 totalSupplyAtCheckpoint + * @return uint256 checkpointId + * @return uint256 startTime + * @return uint256 endTime + * @return uint256 totalProposals + * @return uint256 totalVoters + * @return bool isActive + */ + function getBallotDetails(uint256 _ballotId) external view returns( + uint256 quorum, + uint256 totalSupplyAtCheckpoint, + uint256 checkpointId, + uint256 startTime, + uint256 endTime, + uint256 totalProposals, + uint256 totalVoters, + bool isActive + ); + +} diff --git a/contracts/interfaces/token/IERC1410.sol b/contracts/interfaces/token/IERC1410.sol new file mode 100644 index 000000000..96a5d0bf8 --- /dev/null +++ b/contracts/interfaces/token/IERC1410.sol @@ -0,0 +1,51 @@ +pragma solidity 0.5.8; + +interface IERC1410 { + + // Token Information + function balanceOfByPartition(bytes32 _partition, address _tokenHolder) external view returns (uint256); + //function partitionsOf(address _tokenHolder) external view returns (bytes32[] memory); + + // Token Transfers + function transferByPartition(bytes32 _partition, address _to, uint256 _value, bytes calldata _data) external returns (bytes32); + function operatorTransferByPartition(bytes32 _partition, address _from, address _to, uint256 _value, bytes calldata _data, bytes calldata _operatorData) external returns (bytes32); + function canTransferByPartition(address _from, address _to, bytes32 _partition, uint256 _value, bytes calldata _data) external view returns (byte, bytes32, bytes32); + + // Operator Information + // These functions are present in the STGetter + // function isOperator(address _operator, address _tokenHolder) external view returns (bool); + // function isOperatorForPartition(bytes32 _partition, address _operator, address _tokenHolder) external view returns (bool); + + // Operator Management + function authorizeOperator(address _operator) external; + function revokeOperator(address _operator) external; + function authorizeOperatorByPartition(bytes32 _partition, address _operator) external; + function revokeOperatorByPartition(bytes32 _partition, address _operator) external; + + // Issuance / Redemption + function issueByPartition(bytes32 _partition, address _tokenHolder, uint256 _value, bytes calldata _data) external; + function redeemByPartition(bytes32 _partition, uint256 _value, bytes calldata _data) external; + function operatorRedeemByPartition(bytes32 _partition, address _tokenHolder, uint256 _value, bytes calldata _data, bytes calldata _operatorData) external; + + // Transfer Events + event TransferByPartition( + bytes32 indexed _fromPartition, + address _operator, + address indexed _from, + address indexed _to, + uint256 _value, + bytes _data, + bytes _operatorData + ); + + // Operator Events + event AuthorizedOperator(address indexed operator, address indexed tokenHolder); + event RevokedOperator(address indexed operator, address indexed tokenHolder); + event AuthorizedOperatorByPartition(bytes32 indexed partition, address indexed operator, address indexed tokenHolder); + event RevokedOperatorByPartition(bytes32 indexed partition, address indexed operator, address indexed tokenHolder); + + // Issuance / Redemption Events + event IssuedByPartition(bytes32 indexed partition, address indexed to, uint256 value, bytes data); + event RedeemedByPartition(bytes32 indexed partition, address indexed operator, address indexed from, uint256 value, bytes data, bytes operatorData); + +} diff --git a/contracts/interfaces/token/IERC1594.sol b/contracts/interfaces/token/IERC1594.sol new file mode 100644 index 000000000..d74743c80 --- /dev/null +++ b/contracts/interfaces/token/IERC1594.sol @@ -0,0 +1,29 @@ +pragma solidity 0.5.8; + +/** + * @title Standard Interface of ERC1594 + */ +interface IERC1594 { + + // Transfers + function transferWithData(address _to, uint256 _value, bytes calldata _data) external; + function transferFromWithData(address _from, address _to, uint256 _value, bytes calldata _data) external; + + // Token Issuance + // Present in the STGetter.sol + //function isIssuable() external view returns (bool); + function issue(address _tokenHolder, uint256 _value, bytes calldata _data) external; + + // Token Redemption + function redeem(uint256 _value, bytes calldata _data) external; + function redeemFrom(address _tokenHolder, uint256 _value, bytes calldata _data) external; + + // Transfer Validity + function canTransfer(address _to, uint256 _value, bytes calldata _data) external view returns (byte, bytes32); + function canTransferFrom(address _from, address _to, uint256 _value, bytes calldata _data) external view returns (byte, bytes32); + + // Issuance / Redemption Events + event Issued(address indexed _operator, address indexed _to, uint256 _value, bytes _data); + event Redeemed(address indexed _operator, address indexed _from, uint256 _value, bytes _data); + +} diff --git a/contracts/interfaces/token/IERC1643.sol b/contracts/interfaces/token/IERC1643.sol new file mode 100644 index 000000000..33ad85635 --- /dev/null +++ b/contracts/interfaces/token/IERC1643.sol @@ -0,0 +1,19 @@ +pragma solidity 0.5.8; + +// @title IERC1643 Document Management (part of the ERC1400 Security Token Standards) +/// @dev See https://github.com/SecurityTokenStandard/EIP-Spec + +interface IERC1643 { + + // Document Management + //-- Included in interface but commented because getDocuement() & getAllDocuments() body is provided in the STGetter + // function getDocument(bytes32 _name) external view returns (string memory, bytes32, uint256); + // function getAllDocuments() external view returns (bytes32[] memory); + function setDocument(bytes32 _name, string calldata _uri, bytes32 _documentHash) external; + function removeDocument(bytes32 _name) external; + + // Document Events + event DocumentRemoved(bytes32 indexed _name, string _uri, bytes32 _documentHash); + event DocumentUpdated(bytes32 indexed _name, string _uri, bytes32 _documentHash); + +} diff --git a/contracts/interfaces/token/IERC1644.sol b/contracts/interfaces/token/IERC1644.sol new file mode 100644 index 000000000..2367d3df9 --- /dev/null +++ b/contracts/interfaces/token/IERC1644.sol @@ -0,0 +1,28 @@ +pragma solidity 0.5.8; + +interface IERC1644 { + + // Controller Operation + function isControllable() external view returns (bool); + function controllerTransfer(address _from, address _to, uint256 _value, bytes calldata _data, bytes calldata _operatorData) external; + function controllerRedeem(address _tokenHolder, uint256 _value, bytes calldata _data, bytes calldata _operatorData) external; + + // Controller Events + event ControllerTransfer( + address _controller, + address indexed _from, + address indexed _to, + uint256 _value, + bytes _data, + bytes _operatorData + ); + + event ControllerRedemption( + address _controller, + address indexed _tokenHolder, + uint256 _value, + bytes _data, + bytes _operatorData + ); + +} diff --git a/contracts/libraries/BokkyPooBahsDateTimeLibrary.sol b/contracts/libraries/BokkyPooBahsDateTimeLibrary.sol index 29de4d7cd..6af25046a 100644 --- a/contracts/libraries/BokkyPooBahsDateTimeLibrary.sol +++ b/contracts/libraries/BokkyPooBahsDateTimeLibrary.sol @@ -1,7 +1,7 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; // ---------------------------------------------------------------------------- -// BokkyPooBah's DateTime Library v1.00 +// BokkyPooBah's DateTime Library v1.01 // // A gas-efficient Solidity date and time library // @@ -22,10 +22,7 @@ pragma solidity ^0.4.24; // dayOfWeek | 1 ... 7 | 1 = Monday, ..., 7 = Sunday // // -// Enjoy. (c) BokkyPooBah / Bok Consulting Pty Ltd 2018. -// -// GNU Lesser General Public License 3.0 -// https://www.gnu.org/licenses/lgpl-3.0.en.html +// Enjoy. (c) BokkyPooBah / Bok Consulting Pty Ltd 2018-2019. The MIT Licence. // ---------------------------------------------------------------------------- library BokkyPooBahsDateTimeLibrary { diff --git a/contracts/libraries/DecimalMath.sol b/contracts/libraries/DecimalMath.sol index 242daffab..4b32d8912 100644 --- a/contracts/libraries/DecimalMath.sol +++ b/contracts/libraries/DecimalMath.sol @@ -1,25 +1,26 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; import "openzeppelin-solidity/contracts/math/SafeMath.sol"; library DecimalMath { - using SafeMath for uint256; - /** + uint256 internal constant e18 = uint256(10) ** uint256(18); + + /** * @notice This function multiplies two decimals represented as (decimal * 10**DECIMALS) * @return uint256 Result of multiplication represented as (decimal * 10**DECIMALS) */ - function mul(uint256 x, uint256 y) internal pure returns (uint256 z) { - z = SafeMath.add(SafeMath.mul(x, y), (10 ** 18) / 2) / (10 ** 18); + function mul(uint256 x, uint256 y) internal pure returns(uint256 z) { + z = SafeMath.add(SafeMath.mul(x, y), (e18) / 2) / (e18); } /** * @notice This function divides two decimals represented as (decimal * 10**DECIMALS) * @return uint256 Result of division represented as (decimal * 10**DECIMALS) */ - function div(uint256 x, uint256 y) internal pure returns (uint256 z) { - z = SafeMath.add(SafeMath.mul(x, (10 ** 18)), y / 2) / y; + function div(uint256 x, uint256 y) internal pure returns(uint256 z) { + z = SafeMath.add(SafeMath.mul(x, (e18)), y / 2) / y; } -} \ No newline at end of file +} diff --git a/contracts/libraries/Encoder.sol b/contracts/libraries/Encoder.sol index 27d9a4ddf..67647264a 100644 --- a/contracts/libraries/Encoder.sol +++ b/contracts/libraries/Encoder.sol @@ -1,28 +1,27 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; library Encoder { - - function getKey(string _key) internal pure returns (bytes32) { + function getKey(string memory _key) internal pure returns(bytes32) { return bytes32(keccak256(abi.encodePacked(_key))); } - function getKey(string _key1, address _key2) internal pure returns (bytes32) { + function getKey(string memory _key1, address _key2) internal pure returns(bytes32) { return bytes32(keccak256(abi.encodePacked(_key1, _key2))); } - function getKey(string _key1, string _key2) internal pure returns (bytes32) { + function getKey(string memory _key1, string memory _key2) internal pure returns(bytes32) { return bytes32(keccak256(abi.encodePacked(_key1, _key2))); } - function getKey(string _key1, uint256 _key2) internal pure returns (bytes32) { + function getKey(string memory _key1, uint256 _key2) internal pure returns(bytes32) { return bytes32(keccak256(abi.encodePacked(_key1, _key2))); } - function getKey(string _key1, bytes32 _key2) internal pure returns (bytes32) { + function getKey(string memory _key1, bytes32 _key2) internal pure returns(bytes32) { return bytes32(keccak256(abi.encodePacked(_key1, _key2))); } - function getKey(string _key1, bool _key2) internal pure returns (bytes32) { + function getKey(string memory _key1, bool _key2) internal pure returns(bytes32) { return bytes32(keccak256(abi.encodePacked(_key1, _key2))); } diff --git a/contracts/libraries/KindMath.sol b/contracts/libraries/KindMath.sol deleted file mode 100644 index 850926088..000000000 --- a/contracts/libraries/KindMath.sol +++ /dev/null @@ -1,53 +0,0 @@ -pragma solidity ^0.4.24; - -// Copied from OpenZeppelin and modified to be friendlier - -/** - * @title KindMath - * @dev Math operations with safety checks that throw on error - */ -library KindMath { - - /** - * @dev Multiplies two numbers, throws on overflow. - */ - function mul(uint256 a, uint256 b) internal pure returns (uint256 c) { - // Gas optimization: this is cheaper than requireing 'a' not being zero, but the - // benefit is lost if 'b' is also tested. - // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522 - if (a == 0) { - return 0; - } - - c = a * b; - require(c / a == b, "mul overflow"); - return c; - } - - /** - * @dev Integer division of two numbers, truncating the quotient. - */ - function div(uint256 a, uint256 b) internal pure returns (uint256) { - // require(b > 0); // Solidity automatically throws when dividing by 0 - // uint256 c = a / b; - // require(a == b * c + a % b); // There is no case in which this doesn't hold - return a / b; - } - - /** - * @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend). - */ - function sub(uint256 a, uint256 b) internal pure returns (uint256) { - require(b <= a, "sub overflow"); - return a - b; - } - - /** - * @dev Adds two numbers, throws on overflow. - */ - function add(uint256 a, uint256 b) internal pure returns (uint256 c) { - c = a + b; - require(c >= a, "add overflow"); - return c; - } -} diff --git a/contracts/libraries/StatusCodes.sol b/contracts/libraries/StatusCodes.sol new file mode 100644 index 000000000..77323ef75 --- /dev/null +++ b/contracts/libraries/StatusCodes.sol @@ -0,0 +1,21 @@ +pragma solidity 0.5.8; + +library StatusCodes { + + // ERC1400 status code inspired from ERC1066 + enum Status { + TransferFailure, + TransferSuccess, + InsufficientBalance, + InsufficientAllowance, + TransfersHalted, + FundsLocked, + InvalidSender, + InvalidReceiver, + InvalidOperator + } + + function code(Status _status) internal pure returns (byte) { + return byte(uint8(0x50) + (uint8(_status))); + } +} diff --git a/contracts/libraries/TokenLib.sol b/contracts/libraries/TokenLib.sol index 42e5bebff..bf350edc5 100644 --- a/contracts/libraries/TokenLib.sol +++ b/contracts/libraries/TokenLib.sol @@ -1,90 +1,254 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; -import "../modules/PermissionManager/IPermissionManager.sol"; +import "../interfaces/IPoly.sol"; +import "./StatusCodes.sol"; +import "../modules/UpgradableModuleFactory.sol"; +import "../interfaces/IDataStore.sol"; +import "../tokens/SecurityTokenStorage.sol"; +import "../interfaces/ITransferManager.sol"; +import "../modules/UpgradableModuleFactory.sol"; import "openzeppelin-solidity/contracts/math/SafeMath.sol"; +import "../modules/PermissionManager/IPermissionManager.sol"; +import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; library TokenLib { using SafeMath for uint256; - // Struct for module data - struct ModuleData { - bytes32 name; - address module; - address moduleFactory; - bool isArchived; - uint8[] moduleTypes; - uint256[] moduleIndexes; - uint256 nameIndex; + struct EIP712Domain { + string name; + uint256 chainId; + address verifyingContract; } - // Structures to maintain checkpoints of balances for governance / dividends - struct Checkpoint { - uint256 checkpointId; - uint256 value; + struct Acknowledgment { + string text; } - struct InvestorDataStorage { - // List of investors who have ever held a non-zero token balance - mapping (address => bool) investorListed; - // List of token holders - address[] investors; - // Total number of non-zero token holders - uint256 investorCount; - } + bytes32 constant EIP712DOMAIN_TYPEHASH = keccak256( + "EIP712Domain(string name,uint256 chainId,address verifyingContract)" + ); + + bytes32 constant ACK_TYPEHASH = keccak256( + "Acknowledgment(string text)" + ); + + bytes32 internal constant WHITELIST = "WHITELIST"; + bytes32 internal constant INVESTORSKEY = 0xdf3a8dd24acdd05addfc6aeffef7574d2de3f844535ec91e8e0f3e45dba96731; //keccak256(abi.encodePacked("INVESTORS")) + // Emit when Module get upgraded from the securityToken + event ModuleUpgraded(uint8[] _types, address _module); // Emit when Module is archived from the SecurityToken - event ModuleArchived(uint8[] _types, address _module, uint256 _timestamp); + event ModuleArchived(uint8[] _types, address _module); // Emit when Module is unarchived from the SecurityToken - event ModuleUnarchived(uint8[] _types, address _module, uint256 _timestamp); + event ModuleUnarchived(uint8[] _types, address _module); + // Emit when Module get removed from the securityToken + event ModuleRemoved(uint8[] _types, address _module); + // Emit when the budget allocated to a module is changed + event ModuleBudgetChanged(uint8[] _moduleTypes, address _module, uint256 _oldBudget, uint256 _budget); + // Emit when document is added/removed + event DocumentUpdated(bytes32 indexed _name, string _uri, bytes32 _documentHash); + event DocumentRemoved(bytes32 indexed _name, string _uri, bytes32 _documentHash); + + function hash(EIP712Domain memory _eip712Domain) internal pure returns (bytes32) { + return keccak256( + abi.encode( + EIP712DOMAIN_TYPEHASH, + keccak256(bytes(_eip712Domain.name)), + _eip712Domain.chainId, + _eip712Domain.verifyingContract + ) + ); + } + + function hash(Acknowledgment memory _ack) internal pure returns (bytes32) { + return keccak256(abi.encode(ACK_TYPEHASH, keccak256(bytes(_ack.text)))); + } + + function recoverFreezeIssuanceAckSigner(bytes calldata _signature) external view returns (address) { + Acknowledgment memory ack = Acknowledgment("I acknowledge that freezing Issuance is a permanent and irrevocable change"); + return extractSigner(ack, _signature); + } + + function recoverDisableControllerAckSigner(bytes calldata _signature) external view returns (address) { + Acknowledgment memory ack = Acknowledgment("I acknowledge that disabling controller is a permanent and irrevocable change"); + return extractSigner(ack, _signature); + } + + function extractSigner(Acknowledgment memory _ack, bytes memory _signature) internal view returns (address) { + bytes32 r; + bytes32 s; + uint8 v; + + // Check the signature length + if (_signature.length != 65) { + return (address(0)); + } + + // Divide the signature in r, s and v variables + // ecrecover takes the signature parameters, and the only way to get them + // currently is to use assembly. + // solhint-disable-next-line no-inline-assembly + assembly { + r := mload(add(_signature, 0x20)) + s := mload(add(_signature, 0x40)) + v := byte(0, mload(add(_signature, 0x60))) + } + + // Version of signature should be 27 or 28, but 0 and 1 are also possible versions + if (v < 27) { + v += 27; + } + + // If the version is correct return the signer address + if (v != 27 && v != 28) { + return (address(0)); + } + + bytes32 DOMAIN_SEPARATOR = hash( + EIP712Domain( + { + name: "Polymath", + chainId: 1, + verifyingContract: address(this) + } + ) + ); + + // Note: we need to use `encodePacked` here instead of `encode`. + bytes32 digest = keccak256(abi.encodePacked( + "\x19\x01", + DOMAIN_SEPARATOR, + hash(_ack) + )); + return ecrecover(digest, v, r, s); + } /** * @notice Archives a module attached to the SecurityToken * @param _moduleData Storage data - * @param _module Address of module to archive */ - function archiveModule(ModuleData storage _moduleData, address _module) public { + function archiveModule(SecurityTokenStorage.ModuleData storage _moduleData) external { require(!_moduleData.isArchived, "Module archived"); require(_moduleData.module != address(0), "Module missing"); /*solium-disable-next-line security/no-block-members*/ - emit ModuleArchived(_moduleData.moduleTypes, _module, now); + emit ModuleArchived(_moduleData.moduleTypes, _moduleData.module); _moduleData.isArchived = true; } /** * @notice Unarchives a module attached to the SecurityToken * @param _moduleData Storage data - * @param _module Address of module to unarchive */ - function unarchiveModule(ModuleData storage _moduleData, address _module) public { + function unarchiveModule(IModuleRegistry _moduleRegistry, SecurityTokenStorage.ModuleData storage _moduleData) external { require(_moduleData.isArchived, "Module unarchived"); /*solium-disable-next-line security/no-block-members*/ - emit ModuleUnarchived(_moduleData.moduleTypes, _module, now); + // Check the version is still valid - can only be false if token was upgraded between unarchive / archive + _moduleRegistry.useModule(_moduleData.moduleFactory, true); + emit ModuleUnarchived(_moduleData.moduleTypes, _moduleData.module); _moduleData.isArchived = false; } /** - * @notice Validates permissions with PermissionManager if it exists. If there's no permission return false - * @dev Note that IModule withPerm will allow ST owner all permissions by default - * @dev this allows individual modules to override this logic if needed (to not allow ST owner all permissions) - * @param _modules is the modules to check permissions on - * @param _delegate is the address of the delegate - * @param _module is the address of the PermissionManager module - * @param _perm is the permissions data - * @return success - */ - function checkPermission(address[] storage _modules, address _delegate, address _module, bytes32 _perm) public view returns(bool) { - if (_modules.length == 0) { - return false; + * @notice Upgrades a module attached to the SecurityToken + * @param _moduleData Storage data + */ + function upgradeModule(IModuleRegistry _moduleRegistry, SecurityTokenStorage.ModuleData storage _moduleData) external { + require(_moduleData.module != address(0), "Module missing"); + //Check module is verified and within version bounds + _moduleRegistry.useModule(_moduleData.moduleFactory, true); + // Will revert if module isn't upgradable + UpgradableModuleFactory(_moduleData.moduleFactory).upgrade(_moduleData.module); + emit ModuleUpgraded(_moduleData.moduleTypes, _moduleData.module); + } + + /** + * @notice Removes a module attached to the SecurityToken + * @param _module address of module to unarchive + */ + function removeModule( + address _module, + mapping(uint8 => address[]) storage _modules, + mapping(address => SecurityTokenStorage.ModuleData) storage _modulesToData, + mapping(bytes32 => address[]) storage _names + ) + external + { + require(_modulesToData[_module].isArchived, "Not archived"); + require(_modulesToData[_module].module != address(0), "Module missing"); + /*solium-disable-next-line security/no-block-members*/ + emit ModuleRemoved(_modulesToData[_module].moduleTypes, _module); + // Remove from module type list + uint8[] memory moduleTypes = _modulesToData[_module].moduleTypes; + for (uint256 i = 0; i < moduleTypes.length; i++) { + _removeModuleWithIndex(moduleTypes[i], _modulesToData[_module].moduleIndexes[i], _modules, _modulesToData); + /* modulesToData[_module].moduleType[moduleTypes[i]] = false; */ + } + // Remove from module names list + uint256 index = _modulesToData[_module].nameIndex; + bytes32 name = _modulesToData[_module].name; + uint256 length = _names[name].length; + _names[name][index] = _names[name][length - 1]; + _names[name].length = length - 1; + if ((length - 1) != index) { + _modulesToData[_names[name][index]].nameIndex = index; } + // Remove from modulesToData + delete _modulesToData[_module]; + } - for (uint8 i = 0; i < _modules.length; i++) { - if (IPermissionManager(_modules[i]).checkPermission(_delegate, _module, _perm)) { - return true; + /** + * @notice Internal - Removes a module attached to the SecurityToken by index + */ + function _removeModuleWithIndex( + uint8 _type, + uint256 _index, + mapping(uint8 => address[]) storage _modules, + mapping(address => SecurityTokenStorage.ModuleData) storage _modulesToData + ) + internal + { + uint256 length = _modules[_type].length; + _modules[_type][_index] = _modules[_type][length - 1]; + _modules[_type].length = length - 1; + + if ((length - 1) != _index) { + //Need to find index of _type in moduleTypes of module we are moving + uint8[] memory newTypes = _modulesToData[_modules[_type][_index]].moduleTypes; + for (uint256 i = 0; i < newTypes.length; i++) { + if (newTypes[i] == _type) { + _modulesToData[_modules[_type][_index]].moduleIndexes[i] = _index; + } } } + } - return false; + /** + * @notice allows owner to increase/decrease POLY approval of one of the modules + * @param _module module address + * @param _change change in allowance + * @param _increase true if budget has to be increased, false if decrease + */ + function changeModuleBudget( + address _module, + uint256 _change, + bool _increase, + IERC20 _polyToken, + mapping(address => SecurityTokenStorage.ModuleData) storage _modulesToData + ) + external + { + require(_modulesToData[_module].module != address(0), "Module missing"); + uint256 currentAllowance = _polyToken.allowance(address(this), _module); + uint256 newAllowance; + if (_increase) { + require(IPoly(address(_polyToken)).increaseApproval(_module, _change), "IncreaseApproval fail"); + newAllowance = currentAllowance.add(_change); + } else { + require(IPoly(address(_polyToken)).decreaseApproval(_module, _change), "Insufficient allowance"); + newAllowance = currentAllowance.sub(_change); + } + emit ModuleBudgetChanged(_modulesToData[_module].moduleTypes, _module, currentAllowance, newAllowance); } /** @@ -94,7 +258,7 @@ library TokenLib { * @param _currentValue is the Current value of checkpoint * @return uint256 */ - function getValueAt(Checkpoint[] storage _checkpoints, uint256 _checkpointId, uint256 _currentValue) public view returns(uint256) { + function getValueAt(SecurityTokenStorage.Checkpoint[] storage _checkpoints, uint256 _checkpointId, uint256 _currentValue) external view returns(uint256) { //Checkpoint id 0 is when the token is first created - everyone has a zero balance if (_checkpointId == 0) { return 0; @@ -133,7 +297,7 @@ library TokenLib { * @param _checkpoints is the affected checkpoint object array * @param _newValue is the new value that needs to be stored */ - function adjustCheckpoints(TokenLib.Checkpoint[] storage _checkpoints, uint256 _newValue, uint256 _currentCheckpointId) public { + function adjustCheckpoints(SecurityTokenStorage.Checkpoint[] storage _checkpoints, uint256 _newValue, uint256 _currentCheckpointId) external { //No checkpoints set yet if (_currentCheckpointId == 0) { return; @@ -143,48 +307,188 @@ library TokenLib { return; } //New checkpoint, so record balance - _checkpoints.push( - TokenLib.Checkpoint({ - checkpointId: _currentCheckpointId, - value: _newValue - }) - ); + _checkpoints.push(SecurityTokenStorage.Checkpoint({checkpointId: _currentCheckpointId, value: _newValue})); } /** * @notice Keeps track of the number of non-zero token holders - * @param _investorData Date releated to investor metrics + * @param _holderCount Number of current token holders * @param _from Sender of transfer * @param _to Receiver of transfer * @param _value Value of transfer * @param _balanceTo Balance of the _to address * @param _balanceFrom Balance of the _from address + * @param _dataStore address of data store */ function adjustInvestorCount( - InvestorDataStorage storage _investorData, + uint256 _holderCount, address _from, address _to, uint256 _value, uint256 _balanceTo, - uint256 _balanceFrom - ) public { + uint256 _balanceFrom, + IDataStore _dataStore + ) + external + returns(uint256) + { + uint256 holderCount = _holderCount; if ((_value == 0) || (_from == _to)) { - return; + return holderCount; } // Check whether receiver is a new token holder if ((_balanceTo == 0) && (_to != address(0))) { - _investorData.investorCount = (_investorData.investorCount).add(1); + holderCount = holderCount.add(1); + if (!_isExistingInvestor(_to, _dataStore)) { + _dataStore.insertAddress(INVESTORSKEY, _to); + //KYC data can not be present if added is false and hence we can set packed KYC as uint256(1) to set added as true + _dataStore.setUint256(_getKey(WHITELIST, _to), uint256(1)); + } } // Check whether sender is moving all of their tokens if (_value == _balanceFrom) { - _investorData.investorCount = (_investorData.investorCount).sub(1); + holderCount = holderCount.sub(1); + } + + return holderCount; + } + + /** + * @notice Used to attach a new document to the contract, or update the URI or hash of an existing attached document + * @param name Name of the document. It should be unique always + * @param uri Off-chain uri of the document from where it is accessible to investors/advisors to read. + * @param documentHash hash (of the contents) of the document. + */ + function setDocument( + mapping(bytes32 => SecurityTokenStorage.Document) storage document, + bytes32[] storage docNames, + mapping(bytes32 => uint256) storage docIndexes, + bytes32 name, + string calldata uri, + bytes32 documentHash + ) + external + { + require(name != bytes32(0), "Bad name"); + require(bytes(uri).length > 0, "Bad uri"); + if (document[name].lastModified == uint256(0)) { + docNames.push(name); + docIndexes[name] = docNames.length; + } + document[name] = SecurityTokenStorage.Document(documentHash, now, uri); + emit DocumentUpdated(name, uri, documentHash); + } + + /** + * @notice Used to remove an existing document from the contract by giving the name of the document. + * @dev Can only be executed by the owner of the contract. + * @param name Name of the document. It should be unique always + */ + function removeDocument( + mapping(bytes32 => SecurityTokenStorage.Document) storage document, + bytes32[] storage docNames, + mapping(bytes32 => uint256) storage docIndexes, + bytes32 name + ) + external + { + require(document[name].lastModified != uint256(0), "Not existed"); + uint256 index = docIndexes[name] - 1; + if (index != docNames.length - 1) { + docNames[index] = docNames[docNames.length - 1]; + docIndexes[docNames[index]] = index + 1; } - //Also adjust investor list - if (!_investorData.investorListed[_to] && (_to != address(0))) { - _investorData.investors.push(_to); - _investorData.investorListed[_to] = true; + docNames.length--; + emit DocumentRemoved(name, document[name].uri, document[name].docHash); + delete document[name]; + } + + /** + * @notice Validate transfer with TransferManager module if it exists + * @dev TransferManager module has a key of 2 + * @param modules Array of addresses for transfer managers + * @param modulesToData Mapping of the modules details + * @param from sender of transfer + * @param to receiver of transfer + * @param value value of transfer + * @param data data to indicate validation + * @param transfersFrozen whether the transfer are frozen or not. + * @return bool + */ + function verifyTransfer( + address[] storage modules, + mapping(address => SecurityTokenStorage.ModuleData) storage modulesToData, + address from, + address to, + uint256 value, + bytes memory data, + bool transfersFrozen + ) + public //Marked public to avoid stack too deep error + view + returns(bool, bytes32) + { + if (!transfersFrozen) { + bool isInvalid = false; + bool isValid = false; + bool isForceValid = false; + // Use the local variables to avoid the stack too deep error + bytes32 appCode; + for (uint256 i = 0; i < modules.length; i++) { + if (!modulesToData[modules[i]].isArchived) { + (ITransferManager.Result valid, bytes32 reason) = ITransferManager(modules[i]).verifyTransfer(from, to, value, data); + if (valid == ITransferManager.Result.INVALID) { + isInvalid = true; + appCode = reason; + } else if (valid == ITransferManager.Result.VALID) { + isValid = true; + } else if (valid == ITransferManager.Result.FORCE_VALID) { + isForceValid = true; + } + } + } + // Use the local variables to avoid the stack too deep error + isValid = isForceValid ? true : (isInvalid ? false : isValid); + return (isValid, isValid ? bytes32(StatusCodes.code(StatusCodes.Status.TransferSuccess)): appCode); } + return (false, bytes32(StatusCodes.code(StatusCodes.Status.TransfersHalted))); + } + + function canTransfer( + bool success, + bytes32 appCode, + address to, + uint256 value, + uint256 balanceOfFrom + ) + external + pure + returns (byte, bytes32) + { + if (!success) + return (StatusCodes.code(StatusCodes.Status.TransferFailure), appCode); + + if (balanceOfFrom < value) + return (StatusCodes.code(StatusCodes.Status.InsufficientBalance), bytes32(0)); + + if (to == address(0)) + return (StatusCodes.code(StatusCodes.Status.InvalidReceiver), bytes32(0)); + + // Balance overflow can never happen due to totalsupply being a uint256 as well + // else if (!KindMath.checkAdd(balanceOf(_to), _value)) + // return (0x50, bytes32(0)); + + return (StatusCodes.code(StatusCodes.Status.TransferSuccess), bytes32(0)); + } + + function _getKey(bytes32 _key1, address _key2) internal pure returns(bytes32) { + return bytes32(keccak256(abi.encodePacked(_key1, _key2))); + } + function _isExistingInvestor(address _investor, IDataStore dataStore) internal view returns(bool) { + uint256 data = dataStore.getUint256(_getKey(WHITELIST, _investor)); + //extracts `added` from packed `whitelistData` + return uint8(data) == 0 ? false : true; } } diff --git a/contracts/libraries/Util.sol b/contracts/libraries/Util.sol index 5d4cce83e..04ed231cb 100644 --- a/contracts/libraries/Util.sol +++ b/contracts/libraries/Util.sol @@ -1,20 +1,19 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; /** * @title Utility contract for reusable code */ library Util { - - /** + /** * @notice Changes a string to upper case * @param _base String to change */ - function upper(string _base) internal pure returns (string) { + function upper(string memory _base) internal pure returns(string memory) { bytes memory _baseBytes = bytes(_base); for (uint i = 0; i < _baseBytes.length; i++) { bytes1 b1 = _baseBytes[i]; if (b1 >= 0x61 && b1 <= 0x7A) { - b1 = bytes1(uint8(b1)-32); + b1 = bytes1(uint8(b1) - 32); } _baseBytes[i] = b1; } @@ -26,7 +25,7 @@ library Util { * @param _source String that need to convert into bytes32 */ /// Notice - Maximum Length for _source will be 32 chars otherwise returned bytes32 value will have lossy value. - function stringToBytes32(string memory _source) internal pure returns (bytes32) { + function stringToBytes32(string memory _source) internal pure returns(bytes32) { return bytesToBytes32(bytes(_source), 0); } @@ -36,7 +35,7 @@ library Util { * @param _offset Offset from which to begin conversion */ /// Notice - Maximum length for _source will be 32 chars otherwise returned bytes32 value will have lossy value. - function bytesToBytes32(bytes _b, uint _offset) internal pure returns (bytes32) { + function bytesToBytes32(bytes memory _b, uint _offset) internal pure returns(bytes32) { bytes32 result; for (uint i = 0; i < _b.length; i++) { @@ -49,10 +48,11 @@ library Util { * @notice Changes the bytes32 into string * @param _source that need to convert into string */ - function bytes32ToString(bytes32 _source) internal pure returns (string result) { + function bytes32ToString(bytes32 _source) internal pure returns(string memory) { bytes memory bytesString = new bytes(32); uint charCount = 0; - for (uint j = 0; j < 32; j++) { + uint j = 0; + for (j = 0; j < 32; j++) { byte char = byte(bytes32(uint(_source) * 2 ** (8 * j))); if (char != 0) { bytesString[charCount] = char; @@ -71,12 +71,11 @@ library Util { * @param _data Passed data * @return bytes4 sig */ - function getSig(bytes _data) internal pure returns (bytes4 sig) { + function getSig(bytes memory _data) internal pure returns(bytes4 sig) { uint len = _data.length < 4 ? _data.length : 4; - for (uint i = 0; i < len; i++) { - sig = bytes4(uint(sig) + uint(_data[i]) * (2 ** (8 * (len - 1 - i)))); + for (uint256 i = 0; i < len; i++) { + sig |= bytes4(_data[i] & 0xFF) >> (i * 8); } + return sig; } - - } diff --git a/contracts/libraries/VersionUtils.sol b/contracts/libraries/VersionUtils.sol index 71376aa45..3cbc9abf4 100644 --- a/contracts/libraries/VersionUtils.sol +++ b/contracts/libraries/VersionUtils.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; /** * @title Helper library use to compare or validate the semantic versions @@ -6,108 +6,30 @@ pragma solidity ^0.4.24; library VersionUtils { - /** - * @notice This function is used to validate the version submitted - * @param _current Array holds the present version of ST - * @param _new Array holds the latest version of the ST - * @return bool - */ - function isValidVersion(uint8[] _current, uint8[] _new) internal pure returns(bool) { - bool[] memory _temp = new bool[](_current.length); - uint8 counter = 0; - for (uint8 i = 0; i < _current.length; i++) { - if (_current[i] < _new[i]) - _temp[i] = true; - else - _temp[i] = false; - } - + function lessThanOrEqual(uint8[] memory _current, uint8[] memory _new) internal pure returns(bool) { + require(_current.length == 3); + require(_new.length == 3); + uint8 i = 0; for (i = 0; i < _current.length; i++) { - if (i == 0) { - if (_current[i] <= _new[i]) - if(_temp[0]) { - counter = counter + 3; - break; - } else - counter++; - else - return false; - } else { - if (_temp[i-1]) - counter++; - else if (_current[i] <= _new[i]) - counter++; - else - return false; - } + if (_current[i] == _new[i]) continue; + if (_current[i] < _new[i]) return true; + if (_current[i] > _new[i]) return false; } - if (counter == _current.length) - return true; + return true; } - /** - * @notice Used to compare the lower bound with the latest version - * @param _version1 Array holds the lower bound of the version - * @param _version2 Array holds the latest version of the ST - * @return bool - */ - function compareLowerBound(uint8[] _version1, uint8[] _version2) internal pure returns(bool) { - require(_version1.length == _version2.length, "Input length mismatch"); - uint counter = 0; - for (uint8 j = 0; j < _version1.length; j++) { - if (_version1[j] == 0) - counter ++; - } - if (counter != _version1.length) { - counter = 0; - for (uint8 i = 0; i < _version1.length; i++) { - if (_version2[i] > _version1[i]) - return true; - else if (_version2[i] < _version1[i]) - return false; - else - counter++; - } - if (counter == _version1.length - 1) - return true; - else - return false; - } else - return true; - } - - /** - * @notice Used to compare the upper bound with the latest version - * @param _version1 Array holds the upper bound of the version - * @param _version2 Array holds the latest version of the ST - * @return bool - */ - function compareUpperBound(uint8[] _version1, uint8[] _version2) internal pure returns(bool) { - require(_version1.length == _version2.length, "Input length mismatch"); - uint counter = 0; - for (uint8 j = 0; j < _version1.length; j++) { - if (_version1[j] == 0) - counter ++; + function greaterThanOrEqual(uint8[] memory _current, uint8[] memory _new) internal pure returns(bool) { + require(_current.length == 3); + require(_new.length == 3); + uint8 i = 0; + for (i = 0; i < _current.length; i++) { + if (_current[i] == _new[i]) continue; + if (_current[i] > _new[i]) return true; + if (_current[i] < _new[i]) return false; } - if (counter != _version1.length) { - counter = 0; - for (uint8 i = 0; i < _version1.length; i++) { - if (_version1[i] > _version2[i]) - return true; - else if (_version1[i] < _version2[i]) - return false; - else - counter++; - } - if (counter == _version1.length - 1) - return true; - else - return false; - } else - return true; + return true; } - /** * @notice Used to pack the uint8[] array data into uint24 value * @param _major Major version @@ -122,7 +44,7 @@ library VersionUtils { * @notice Used to convert packed data into uint8 array * @param _packedVersion Packed data */ - function unpack(uint24 _packedVersion) internal pure returns (uint8[]) { + function unpack(uint24 _packedVersion) internal pure returns(uint8[] memory) { uint8[] memory _unpackVersion = new uint8[](3); _unpackVersion[0] = uint8(_packedVersion >> 16); _unpackVersion[1] = uint8(_packedVersion >> 8); @@ -131,4 +53,25 @@ library VersionUtils { } + /** + * @notice Used to packed the KYC data + */ + function packKYC(uint64 _a, uint64 _b, uint64 _c, uint8 _d) internal pure returns(uint256) { + // this function packs 3 uint64 and a uint8 together in a uint256 to save storage cost + // a is rotated left by 136 bits, b is rotated left by 72 bits and c is rotated left by 8 bits. + // rotation pads empty bits with zeroes so now we can safely do a bitwise OR operation to pack + // all the variables together. + return (uint256(_a) << 136) | (uint256(_b) << 72) | (uint256(_c) << 8) | uint256(_d); + } + + /** + * @notice Used to convert packed data into KYC data + * @param _packedVersion Packed data + */ + function unpackKYC(uint256 _packedVersion) internal pure returns(uint64 canSendAfter, uint64 canReceiveAfter, uint64 expiryTime, uint8 added) { + canSendAfter = uint64(_packedVersion >> 136); + canReceiveAfter = uint64(_packedVersion >> 72); + expiryTime = uint64(_packedVersion >> 8); + added = uint8(_packedVersion); + } } diff --git a/contracts/libraries/VolumeRestrictionLib.sol b/contracts/libraries/VolumeRestrictionLib.sol index b0962f231..f53fd4b08 100644 --- a/contracts/libraries/VolumeRestrictionLib.sol +++ b/contracts/libraries/VolumeRestrictionLib.sol @@ -1,17 +1,25 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; +import "../interfaces/IDataStore.sol"; import "./BokkyPooBahsDateTimeLibrary.sol"; import "openzeppelin-solidity/contracts/math/SafeMath.sol"; -import "../interfaces/ISecurityToken.sol"; -import "../storage/VolumeRestrictionTMStorage.sol"; +import "../modules/TransferManager/VRTM/VolumeRestrictionTMStorage.sol"; library VolumeRestrictionLib { using SafeMath for uint256; + uint256 internal constant ONE = uint256(1); + uint8 internal constant INDEX = uint8(2); + bytes32 internal constant INVESTORFLAGS = "INVESTORFLAGS"; + bytes32 internal constant INVESTORSKEY = 0xdf3a8dd24acdd05addfc6aeffef7574d2de3f844535ec91e8e0f3e45dba96731; //keccak256(abi.encodePacked("INVESTORS")) + bytes32 internal constant WHITELIST = "WHITELIST"; + + function deleteHolderFromList( - VolumeRestrictionTMStorage.RestrictedData storage data, + mapping(address => VolumeRestrictionTMStorage.TypeOfPeriod) storage _holderToRestrictionType, address _holder, + IDataStore _dataStore, VolumeRestrictionTMStorage.TypeOfPeriod _typeOfPeriod ) public @@ -22,50 +30,36 @@ library VolumeRestrictionLib { // if removing restriction is individual then typeOfPeriod is TypeOfPeriod.OneDay // in uint8 its value is 1. if removing restriction is daily individual then typeOfPeriod // is TypeOfPeriod.MultipleDays in uint8 its value is 0. - if (data.restrictedHolders[_holder].typeOfPeriod != VolumeRestrictionTMStorage.TypeOfPeriod.Both) { - uint128 index = data.restrictedHolders[_holder].index; - uint256 _len = data.restrictedAddresses.length; - if (index != _len) { - data.restrictedHolders[data.restrictedAddresses[_len - 1]].index = index; - data.restrictedAddresses[index - 1] = data.restrictedAddresses[_len - 1]; - } - delete data.restrictedHolders[_holder]; - data.restrictedAddresses.length--; + if (_holderToRestrictionType[_holder] != VolumeRestrictionTMStorage.TypeOfPeriod.Both) { + uint256 flags = _dataStore.getUint256(_getKey(INVESTORFLAGS, _holder)); + flags = flags & ~(ONE << INDEX); + _dataStore.setUint256(_getKey(INVESTORFLAGS, _holder), flags); } else { - data.restrictedHolders[_holder].typeOfPeriod = _typeOfPeriod; + _holderToRestrictionType[_holder] = _typeOfPeriod; } } function addRestrictionData( - VolumeRestrictionTMStorage.RestrictedData storage data, + mapping(address => VolumeRestrictionTMStorage.TypeOfPeriod) storage _holderToRestrictionType, address _holder, VolumeRestrictionTMStorage.TypeOfPeriod _callFrom, - uint256 _endTime + uint256 _endTime, + IDataStore _dataStore ) public { - uint128 index = data.restrictedHolders[_holder].index; - if (data.restrictedHolders[_holder].seen == 0) { - data.restrictedAddresses.push(_holder); - index = uint128(data.restrictedAddresses.length); + uint256 flags = _dataStore.getUint256(_getKey(INVESTORFLAGS, _holder)); + if (!_isExistingInvestor(_holder, _dataStore)) { + _dataStore.insertAddress(INVESTORSKEY, _holder); + //KYC data can not be present if added is false and hence we can set packed KYC as uint256(1) to set added as true + _dataStore.setUint256(_getKey(WHITELIST, _holder), uint256(1)); } - VolumeRestrictionTMStorage.TypeOfPeriod _type = _getTypeOfPeriod(data.restrictedHolders[_holder].typeOfPeriod, _callFrom, _endTime); - data.restrictedHolders[_holder] = VolumeRestrictionTMStorage.RestrictedHolder(uint8(1), _type, index); - } - - function _getTypeOfPeriod( - VolumeRestrictionTMStorage.TypeOfPeriod _currentTypeOfPeriod, - VolumeRestrictionTMStorage.TypeOfPeriod _callFrom, - uint256 _endTime - ) - internal - pure - returns(VolumeRestrictionTMStorage.TypeOfPeriod) - { - if (_currentTypeOfPeriod != _callFrom && _endTime != uint256(0)) - return VolumeRestrictionTMStorage.TypeOfPeriod.Both; - else - return _callFrom; + if (!_isVolRestricted(flags)) { + flags = flags | (ONE << INDEX); + _dataStore.setUint256(_getKey(INVESTORFLAGS, _holder), flags); + } + VolumeRestrictionTMStorage.TypeOfPeriod _type = _getTypeOfPeriod(_holderToRestrictionType[_holder], _callFrom, _endTime); + _holderToRestrictionType[_holder] = _type; } function isValidAmountAfterRestrictionChanges( @@ -78,9 +72,9 @@ library VolumeRestrictionLib { public view returns(bool) - { + { // if restriction is to check whether the current transaction is performed within the 24 hours - // span after the last transaction performed by the user + // span after the last transaction performed by the user if (BokkyPooBahsDateTimeLibrary.diffSeconds(_lastTradedTimestamp, now) < 86400) { (,, uint256 lastTxDay) = BokkyPooBahsDateTimeLibrary.timestampToDate(_lastTradedTimestamp); (,, uint256 currentTxDay) = BokkyPooBahsDateTimeLibrary.timestampToDate(now); @@ -96,4 +90,114 @@ library VolumeRestrictionLib { return true; } + /** + * @notice Provide the restriction details of all the restricted addresses + * @return address List of the restricted addresses + * @return uint256 List of the tokens allowed to the restricted addresses corresponds to restricted address + * @return uint256 List of the start time of the restriction corresponds to restricted address + * @return uint256 List of the rolling period in days for a restriction corresponds to restricted address. + * @return uint256 List of the end time of the restriction corresponds to restricted address. + * @return uint8 List of the type of restriction to validate the value of the `allowedTokens` + * of the restriction corresponds to restricted address + */ + function getRestrictionData( + mapping(address => VolumeRestrictionTMStorage.TypeOfPeriod) storage _holderToRestrictionType, + VolumeRestrictionTMStorage.IndividualRestrictions storage _individualRestrictions, + IDataStore _dataStore + ) + public + view + returns( + address[] memory allAddresses, + uint256[] memory allowedTokens, + uint256[] memory startTime, + uint256[] memory rollingPeriodInDays, + uint256[] memory endTime, + VolumeRestrictionTMStorage.RestrictionType[] memory typeOfRestriction + ) + { + address[] memory investors = _dataStore.getAddressArray(INVESTORSKEY); + uint256 counter; + uint256 i; + for (i = 0; i < investors.length; i++) { + if (_isVolRestricted(_dataStore.getUint256(_getKey(INVESTORFLAGS, investors[i])))) { + counter = counter + (_holderToRestrictionType[investors[i]] == VolumeRestrictionTMStorage.TypeOfPeriod.Both ? 2 : 1); + } + } + allAddresses = new address[](counter); + allowedTokens = new uint256[](counter); + startTime = new uint256[](counter); + rollingPeriodInDays = new uint256[](counter); + endTime = new uint256[](counter); + typeOfRestriction = new VolumeRestrictionTMStorage.RestrictionType[](counter); + counter = 0; + for (i = 0; i < investors.length; i++) { + if (_isVolRestricted(_dataStore.getUint256(_getKey(INVESTORFLAGS, investors[i])))) { + allAddresses[counter] = investors[i]; + if (_holderToRestrictionType[investors[i]] == VolumeRestrictionTMStorage.TypeOfPeriod.MultipleDays) { + _setValues(_individualRestrictions.individualRestriction[investors[i]], allowedTokens, startTime, rollingPeriodInDays, endTime, typeOfRestriction, counter); + } + else if (_holderToRestrictionType[investors[i]] == VolumeRestrictionTMStorage.TypeOfPeriod.OneDay) { + _setValues(_individualRestrictions.individualDailyRestriction[investors[i]], allowedTokens, startTime, rollingPeriodInDays, endTime, typeOfRestriction, counter); + } + else if (_holderToRestrictionType[investors[i]] == VolumeRestrictionTMStorage.TypeOfPeriod.Both) { + _setValues(_individualRestrictions.individualRestriction[investors[i]], allowedTokens, startTime, rollingPeriodInDays, endTime, typeOfRestriction, counter); + counter++; + allAddresses[counter] = investors[i]; + _setValues(_individualRestrictions.individualDailyRestriction[investors[i]], allowedTokens, startTime, rollingPeriodInDays, endTime, typeOfRestriction, counter); + } + counter++; + } + } + } + + function _setValues( + VolumeRestrictionTMStorage.VolumeRestriction memory _restriction, + uint256[] memory _allowedTokens, + uint256[] memory _startTime, + uint256[] memory _rollingPeriodInDays, + uint256[] memory _endTime, + VolumeRestrictionTMStorage.RestrictionType[] memory _typeOfRestriction, + uint256 _index + ) + internal + pure + { + _allowedTokens[_index] = _restriction.allowedTokens; + _startTime[_index] = _restriction.startTime; + _rollingPeriodInDays[_index] = _restriction.rollingPeriodInDays; + _endTime[_index] = _restriction.endTime; + _typeOfRestriction[_index] = _restriction.typeOfRestriction; + } + + function _isVolRestricted(uint256 _flags) internal pure returns(bool) { + uint256 volRestricted = (_flags >> INDEX) & ONE; + return (volRestricted > 0 ? true : false); + } + + function _getTypeOfPeriod( + VolumeRestrictionTMStorage.TypeOfPeriod _currentTypeOfPeriod, + VolumeRestrictionTMStorage.TypeOfPeriod _callFrom, + uint256 _endTime + ) + internal + pure + returns(VolumeRestrictionTMStorage.TypeOfPeriod) + { + if (_currentTypeOfPeriod != _callFrom && _endTime != uint256(0)) + return VolumeRestrictionTMStorage.TypeOfPeriod.Both; + else + return _callFrom; + } + + function _isExistingInvestor(address _investor, IDataStore _dataStore) internal view returns(bool) { + uint256 data = _dataStore.getUint256(_getKey(WHITELIST, _investor)); + //extracts `added` from packed `_whitelistData` + return uint8(data) == 0 ? false : true; + } + + function _getKey(bytes32 _key1, address _key2) internal pure returns(bytes32) { + return bytes32(keccak256(abi.encodePacked(_key1, _key2))); + } + } diff --git a/contracts/mocks/DummySTO.sol b/contracts/mocks/Dummy/DummySTO.sol similarity index 66% rename from contracts/mocks/DummySTO.sol rename to contracts/mocks/Dummy/DummySTO.sol index a444df49c..40a06b234 100644 --- a/contracts/mocks/DummySTO.sol +++ b/contracts/mocks/Dummy/DummySTO.sol @@ -1,32 +1,22 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; -import "../modules/STO/STO.sol"; -import "../interfaces/ISecurityToken.sol"; +import "../../modules/STO/STO.sol"; +import "../../interfaces/ISecurityToken.sol"; +import "./DummySTOStorage.sol"; /** * @title STO module for sample implementation of a different crowdsale module */ -contract DummySTO is STO { - - bytes32 public constant ADMIN = "ADMIN"; - - uint256 public investorCount; - - uint256 public cap; - string public someString; +contract DummySTO is DummySTOStorage, STO { event GenerateTokens(address _investor, uint256 _amount); - mapping (address => uint256) public investors; - /** * @notice Constructor * @param _securityToken Address of the security token - * @param _polyAddress Address of the polytoken */ - constructor (address _securityToken, address _polyAddress) public - Module(_securityToken, _polyAddress) - { + constructor(address _securityToken, address _polyToken) public Module(_securityToken, _polyToken) { + } /** @@ -36,7 +26,7 @@ contract DummySTO is STO { * @param _cap Maximum No. of tokens for sale * @param _someString Any string that contails the details */ - function configure(uint256 _startTime, uint256 _endTime, uint256 _cap, string _someString) public onlyFactory { + function configure(uint256 _startTime, uint256 _endTime, uint256 _cap, string memory _someString) public onlyFactory { startTime = _startTime; endTime = _endTime; cap = _cap; @@ -46,8 +36,8 @@ contract DummySTO is STO { /** * @notice This function returns the signature of configure function */ - function getInitFunction() public pure returns (bytes4) { - return bytes4(keccak256("configure(uint256,uint256,uint256,string)")); + function getInitFunction() public pure returns(bytes4) { + return this.configure.selector; } /** @@ -58,39 +48,40 @@ contract DummySTO is STO { function generateTokens(address _investor, uint256 _amount) public withPerm(ADMIN) { require(!paused, "Should not be paused"); require(_amount > 0, "Amount should be greater than 0"); - ISecurityToken(securityToken).mint(_investor, _amount); + require(_canBuy(_investor), "Unauthorized"); + securityToken.issue(_investor, _amount, ""); if (investors[_investor] == 0) { investorCount = investorCount + 1; } //TODO: Add SafeMath maybe investors[_investor] = investors[_investor] + _amount; - emit GenerateTokens (_investor, _amount); + emit GenerateTokens(_investor, _amount); } /** * @notice Returns the total no. of investors */ - function getNumberInvestors() public view returns (uint256) { + function getNumberInvestors() public view returns(uint256) { return investorCount; } /** * @notice Returns the total no. of investors */ - function getTokensSold() public view returns (uint256) { + function getTokensSold() external view returns(uint256) { return 0; } /** * @notice Returns the permissions flag that are associated with STO */ - function getPermissions() public view returns(bytes32[]) { + function getPermissions() public view returns(bytes32[] memory) { bytes32[] memory allPermissions = new bytes32[](1); allPermissions[0] = ADMIN; return allPermissions; } - function () payable { + function () external payable { //Payable fallback function to allow us to test leaking ETH } diff --git a/contracts/mocks/Dummy/DummySTOFactory.sol b/contracts/mocks/Dummy/DummySTOFactory.sol new file mode 100644 index 000000000..694b77b72 --- /dev/null +++ b/contracts/mocks/Dummy/DummySTOFactory.sol @@ -0,0 +1,49 @@ +pragma solidity 0.5.8; + +import "../../modules/UpgradableModuleFactory.sol"; +import "./DummySTOProxy.sol"; + +/** + * @title Factory for deploying DummySTO module + */ +contract DummySTOFactory is UpgradableModuleFactory { + + /** + * @notice Constructor + * @param _setupCost Setup cost of the module + * @param _logicContract Contract address that contains the logic related to `description` + * @param _polymathRegistry Address of the Polymath registry + * @param _isCostInPoly true = cost in Poly, false = USD + */ + constructor ( + uint256 _setupCost, + address _logicContract, + address _polymathRegistry, + bool _isCostInPoly + ) + public + UpgradableModuleFactory("3.0.0", _setupCost, _logicContract, _polymathRegistry, _isCostInPoly) + { + name = "DummySTO"; + title = "Dummy STO"; + description = "Dummy STO"; + typesData.push(3); + tagsData.push("Dummy"); + tagsData.push("ETH"); + tagsData.push("STO"); + compatibleSTVersionRange["lowerBound"] = VersionUtils.pack(uint8(3), uint8(0), uint8(0)); + compatibleSTVersionRange["upperBound"] = VersionUtils.pack(uint8(3), uint8(0), uint8(0)); + } + + /** + * @notice Used to launch the Module with the help of factory + * @param _data Data used for the intialization of the module factory variables + * @return address Contract address of the Module + */ + function deploy(bytes calldata _data) external returns(address) { + address dummySTO = address(new DummySTOProxy(logicContracts[latestUpgrade].version, msg.sender, polymathRegistry.getAddress("PolyToken"), logicContracts[latestUpgrade].logicContract)); + _initializeModule(dummySTO, _data); + return dummySTO; + } + +} diff --git a/contracts/mocks/Dummy/DummySTOProxy.sol b/contracts/mocks/Dummy/DummySTOProxy.sol new file mode 100644 index 000000000..0085484b6 --- /dev/null +++ b/contracts/mocks/Dummy/DummySTOProxy.sol @@ -0,0 +1,37 @@ +pragma solidity 0.5.8; + +import "../../proxy/OwnedUpgradeabilityProxy.sol"; +import "../../Pausable.sol"; +import "openzeppelin-solidity/contracts/utils/ReentrancyGuard.sol"; +import "../../storage/modules/STO/STOStorage.sol"; +import "../../storage/modules/ModuleStorage.sol"; +import "./DummySTOStorage.sol"; + +/** + * @title DummySTO module Proxy + */ +contract DummySTOProxy is DummySTOStorage, STOStorage, ModuleStorage, Pausable, ReentrancyGuard, OwnedUpgradeabilityProxy { + + /** + * @notice Constructor + * @param _securityToken Address of the security token + * @param _polyAddress Address of the polytoken + * @param _implementation representing the address of the new implementation to be set + */ + constructor ( + string memory _version, + address _securityToken, + address _polyAddress, + address _implementation + ) + public + ModuleStorage(_securityToken, _polyAddress) + { + require( + _implementation != address(0), + "Implementation address should not be 0x" + ); + _upgradeTo(_version, _implementation); + } + +} diff --git a/contracts/mocks/Dummy/DummySTOStorage.sol b/contracts/mocks/Dummy/DummySTOStorage.sol new file mode 100644 index 000000000..718301677 --- /dev/null +++ b/contracts/mocks/Dummy/DummySTOStorage.sol @@ -0,0 +1,15 @@ +pragma solidity 0.5.8; + +/** + * @title Contract used to store layout for the DummySTO storage + */ +contract DummySTOStorage { + + uint256 public investorCount; + + uint256 public cap; + string public someString; + + mapping (address => uint256) public investors; + +} diff --git a/contracts/mocks/DummySTOFactory.sol b/contracts/mocks/DummySTOFactory.sol deleted file mode 100644 index 03d5fb3a6..000000000 --- a/contracts/mocks/DummySTOFactory.sol +++ /dev/null @@ -1,70 +0,0 @@ -pragma solidity ^0.4.24; - -import "./DummySTO.sol"; -import "../modules/ModuleFactory.sol"; -import "../libraries/Util.sol"; - -/** - * @title Factory for deploying DummySTO module - */ -contract DummySTOFactory is ModuleFactory { - - /** - * @notice Constructor - * @param _polyAddress Address of the polytoken - */ - constructor (address _polyAddress, uint256 _setupCost, uint256 _usageCost, uint256 _subscriptionCost) public - ModuleFactory(_polyAddress, _setupCost, _usageCost, _subscriptionCost) - { - version = "1.0.0"; - name = "DummySTO"; - title = "Dummy STO"; - description = "Dummy STO"; - compatibleSTVersionRange["lowerBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0)); - compatibleSTVersionRange["upperBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0)); - } - /** - * @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), "Sufficent Allowance is not provided"); - //Check valid bytes - can only call module init function - DummySTO dummySTO = new DummySTO(msg.sender, address(polyToken)); - //Checks that _data is valid (not calling anything it shouldn't) - require(Util.getSig(_data) == dummySTO.getInitFunction(), "Invalid data"); - /*solium-disable-next-line security/no-low-level-calls*/ - require(address(dummySTO).call(_data), "Unsuccessfull call"); - /*solium-disable-next-line security/no-block-members*/ - emit GenerateModuleFromFactory(address(dummySTO), getName(), address(this), msg.sender, setupCost, now); - return address(dummySTO); - } - - /** - * @notice Type of the Module factory - */ - function getTypes() external view returns(uint8[]) { - uint8[] memory res = new uint8[](1); - res[0] = 3; - return res; - } - - /** - * @notice Returns the instructions associated with the module - */ - function getInstructions() external view returns(string) { - return "Dummy STO - you can mint tokens at will"; - } - - /** - * @notice Get the tags related to the module factory - */ - function getTags() external view returns(bytes32[]) { - bytes32[] memory availableTags = new bytes32[](4); - availableTags[0] = "Dummy"; - availableTags[1] = "Non-refundable"; - availableTags[2] = "ETH"; - return availableTags; - } -} diff --git a/contracts/mocks/FunctionSigClash1.sol b/contracts/mocks/FunctionSigClash1.sol new file mode 100644 index 000000000..651d64e38 --- /dev/null +++ b/contracts/mocks/FunctionSigClash1.sol @@ -0,0 +1,6 @@ +pragma solidity 0.5.8; + +contract functionSigClash1 { + // function clash550254402() public { + // } +} diff --git a/contracts/mocks/FunctionSigClash2.sol b/contracts/mocks/FunctionSigClash2.sol new file mode 100644 index 000000000..c41e1d61a --- /dev/null +++ b/contracts/mocks/FunctionSigClash2.sol @@ -0,0 +1,6 @@ +pragma solidity 0.5.8; + +contract functionSigClash2 { + // function proxyOwner() public { + // } +} diff --git a/contracts/mocks/MockBurnFactory.sol b/contracts/mocks/MockBurnFactory.sol index 6c0e47c3d..a28c89af5 100644 --- a/contracts/mocks/MockBurnFactory.sol +++ b/contracts/mocks/MockBurnFactory.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; import "./MockRedemptionManager.sol"; import "../modules/Experimental/Burn/TrackedRedemptionFactory.sol"; @@ -9,27 +9,35 @@ import "../modules/Experimental/Burn/TrackedRedemptionFactory.sol"; contract MockBurnFactory is TrackedRedemptionFactory { - /** - * @notice Constructor - * @param _polyAddress Address of the polytoken - */ - constructor (address _polyAddress, uint256 _setupCost, uint256 _usageCost, uint256 _subscriptionCost) public - TrackedRedemptionFactory(_polyAddress, _setupCost, _usageCost, _subscriptionCost) + /** + * @notice Constructor + * @param _setupCost Setup cost of the module + * @param _polymathRegistry Address of the Polymath Registry + */ + constructor( + uint256 _setupCost, + address _polymathRegistry, + bool _isFeeInPoly + ) + public + TrackedRedemptionFactory(_setupCost, _polymathRegistry, _isFeeInPoly) { + } /** * @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), "Unable to pay setup cost"); - //Check valid bytes - can only call module init function - MockRedemptionManager mockRedemptionManager = new MockRedemptionManager(msg.sender, address(polyToken)); - /*solium-disable-next-line security/no-block-members*/ - emit GenerateModuleFromFactory(address(mockRedemptionManager), getName(), address(this), msg.sender, setupCost, now); - return address(mockRedemptionManager); + function deploy( + bytes calldata _data + ) + external + returns(address) + { + address mockRedemptionManager = address(new MockRedemptionManager(msg.sender, polymathRegistry.getAddress("PolyToken"))); + _initializeModule(mockRedemptionManager, _data); + return mockRedemptionManager; } } diff --git a/contracts/mocks/MockCountTransferManager.sol b/contracts/mocks/MockCountTransferManager.sol new file mode 100644 index 000000000..777c72004 --- /dev/null +++ b/contracts/mocks/MockCountTransferManager.sol @@ -0,0 +1,31 @@ +pragma solidity 0.5.8; + +import "../modules/TransferManager/CTM/CountTransferManager.sol"; + +/** + * @title Transfer Manager for limiting maximum number of token holders + */ +contract MockCountTransferManager is CountTransferManager { + + event Upgrader(uint256 _someData); + uint256 public someValue; + + /** + * @notice Constructor + * @param _securityToken Address of the security token + */ + constructor(address _securityToken, address _polyToken) public CountTransferManager(_securityToken, _polyToken) { + + } + + function initialize(uint256 _someData) public { + require(msg.sender == address(this)); + someValue = _someData; + emit Upgrader(_someData); + } + + function newFunction() external { + emit Upgrader(maxHolderCount); + } + +} diff --git a/contracts/mocks/MockFactory.sol b/contracts/mocks/MockFactory.sol index 8be7754f7..de39c0c2e 100644 --- a/contracts/mocks/MockFactory.sol +++ b/contracts/mocks/MockFactory.sol @@ -1,31 +1,38 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; -import "./DummySTOFactory.sol"; +import "./Dummy/DummySTOFactory.sol"; /** * @title Mock Contract Not fit for production environment */ contract MockFactory is DummySTOFactory { + bool public typesSwitch = false; - bool public switchTypes = false; - /** + /** * @notice Constructor - * @param _polyAddress Address of the polytoken + * @param _setupCost Setup cost of the module + * @param _logicContract Contract address that contains the logic related to `description` + * @param _polymathRegistry Address of the Polymath Registry */ - constructor (address _polyAddress, uint256 _setupCost, uint256 _usageCost, uint256 _subscriptionCost) public - DummySTOFactory(_polyAddress, _setupCost, _usageCost, _subscriptionCost) + constructor( + uint256 _setupCost, + address _logicContract, + address _polymathRegistry, + bool _isFeeInPoly + ) + public + DummySTOFactory(_setupCost, _logicContract, _polymathRegistry, _isFeeInPoly) { - } /** * @notice Type of the Module factory */ - function getTypes() external view returns(uint8[]) { - if (!switchTypes) { - uint8[] memory types = new uint8[](0); - return types; + function getTypes() external view returns(uint8[] memory) { + if (!typesSwitch) { + uint8[] memory res = new uint8[](0); + return res; } else { uint8[] memory res = new uint8[](2); res[0] = 1; @@ -35,8 +42,8 @@ contract MockFactory is DummySTOFactory { } - function changeTypes() external onlyOwner { - switchTypes = !switchTypes; + function switchTypes() external onlyOwner { + typesSwitch = !typesSwitch; } } diff --git a/contracts/mocks/MockModuleRegistry.sol b/contracts/mocks/MockModuleRegistry.sol index 5711108c1..dbce5fab8 100644 --- a/contracts/mocks/MockModuleRegistry.sol +++ b/contracts/mocks/MockModuleRegistry.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; import "../ModuleRegistry.sol"; @@ -6,14 +6,12 @@ import "../ModuleRegistry.sol"; * @title Registry contract for issuers to register their security tokens */ contract MockModuleRegistry is ModuleRegistry { - /// @notice It is dummy functionality /// Alert! Alert! Do not use it for the mainnet release - function addMoreReputation(address _moduleFactory, address[] _tokens) public onlyOwner { + function addMoreReputation(address _moduleFactory, address[] memory _tokens) public onlyOwner { for (uint8 i = 0; i < _tokens.length; i++) { pushArray(Encoder.getKey("reputation", _moduleFactory), _tokens[i]); } } - } diff --git a/contracts/mocks/MockOracle.sol b/contracts/mocks/MockOracle.sol index d7cf14a2a..2ef7d5383 100644 --- a/contracts/mocks/MockOracle.sol +++ b/contracts/mocks/MockOracle.sol @@ -1,9 +1,8 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; import "../interfaces/IOracle.sol"; contract MockOracle is IOracle { - address public currency; bytes32 public currencySymbol; bytes32 public denominatedCurrency; @@ -44,7 +43,7 @@ contract MockOracle is IOracle { /** * @notice Returns price - should throw if not valid */ - function getPrice() external view returns(uint256) { + function getPrice() external returns(uint256) { return price; } diff --git a/contracts/mocks/MockPolyOracle.sol b/contracts/mocks/MockPolyOracle.sol index 681bb2681..aae3b96f6 100644 --- a/contracts/mocks/MockPolyOracle.sol +++ b/contracts/mocks/MockPolyOracle.sol @@ -1,11 +1,10 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; import "../oracles/PolyOracle.sol"; contract MockPolyOracle is PolyOracle { - - constructor() payable public { + constructor() public payable { OAR = OraclizeAddrResolverI(0x6f485C8BF6fc43eA212E93BBF8ce046C7f1cb475); } -} \ No newline at end of file +} diff --git a/contracts/mocks/MockRedemptionManager.sol b/contracts/mocks/MockRedemptionManager.sol index 02fbb2e81..bbd4af721 100644 --- a/contracts/mocks/MockRedemptionManager.sol +++ b/contracts/mocks/MockRedemptionManager.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; import "../modules/Experimental/Burn/TrackedRedemption.sol"; @@ -6,19 +6,18 @@ import "../modules/Experimental/Burn/TrackedRedemption.sol"; * @title Burn module for burning tokens and keeping track of burnt amounts */ contract MockRedemptionManager is TrackedRedemption { + mapping(address => uint256) tokenToRedeem; + mapping(address => mapping(bytes32 => uint256)) redeemedTokensByPartition; - mapping (address => uint256) tokenToRedeem; - - event RedeemedTokenByOwner(address _investor, address _byWhoom, uint256 _value, uint256 _timestamp); + event RedeemedTokenByOwner(address _investor, address _byWhoom, uint256 _value); + event RedeemedTokensByPartition(address indexed _investor, address indexed _operator, bytes32 _partition, uint256 _value, bytes _data, bytes _operatorData); /** * @notice Constructor * @param _securityToken Address of the security token - * @param _polyAddress Address of the polytoken */ - constructor (address _securityToken, address _polyAddress) public - TrackedRedemption(_securityToken, _polyAddress) - { + constructor(address _securityToken, address _polyToken) public TrackedRedemption(_securityToken, _polyToken) { + } /** @@ -26,7 +25,7 @@ contract MockRedemptionManager is TrackedRedemption { * @param _value The number of tokens to redeem */ function transferToRedeem(uint256 _value) public { - require(ISecurityToken(securityToken).transferFrom(msg.sender, address(this), _value), "Insufficient funds"); + require(securityToken.transferFrom(msg.sender, address(this), _value), "Insufficient funds"); tokenToRedeem[msg.sender] = _value; } @@ -38,9 +37,46 @@ contract MockRedemptionManager is TrackedRedemption { require(tokenToRedeem[msg.sender] >= _value, "Insufficient tokens redeemable"); tokenToRedeem[msg.sender] = tokenToRedeem[msg.sender].sub(_value); redeemedTokens[msg.sender] = redeemedTokens[msg.sender].add(_value); - ISecurityToken(securityToken).burnWithData(_value, ""); + securityToken.redeem(_value, ""); + /*solium-disable-next-line security/no-block-members*/ + emit RedeemedTokenByOwner(msg.sender, address(this), _value); + } + + /** + * @notice To redeem tokens and track redemptions + * @param _value The number of tokens to redeem + * @param _partition Partition from which balance will be deducted + * @param _data Extra data parmeter pass to do some offchain operation + */ + function redeemTokensByPartition(uint256 _value, bytes32 _partition, bytes calldata _data) external { + require(tokenToRedeem[msg.sender] >= _value, "Insufficient tokens redeemable"); + tokenToRedeem[msg.sender] = tokenToRedeem[msg.sender].sub(_value); + redeemedTokensByPartition[msg.sender][_partition] = redeemedTokensByPartition[msg.sender][_partition].add(_value); + securityToken.redeemByPartition(_partition, _value, _data); + /*solium-disable-next-line security/no-block-members*/ + emit RedeemedTokensByPartition(msg.sender, address(0), _partition, _value, _data, ""); + } + + /** + * @notice To redeem tokens and track redemptions + * @param _value The number of tokens to redeem + * @param _partition Partition from which balance will be deducted + * @param _data Extra data parmeter pass to do some offchain operation + * @param _operatorData Data to log the operator call + */ + function operatorRedeemTokensByPartition(uint256 _value, bytes32 _partition, bytes calldata _data, bytes calldata _operatorData) external { + require(tokenToRedeem[msg.sender] >= _value, "Insufficient tokens redeemable"); + tokenToRedeem[msg.sender] = tokenToRedeem[msg.sender].sub(_value); + redeemedTokensByPartition[msg.sender][_partition] = redeemedTokensByPartition[msg.sender][_partition].add(_value); + securityToken.operatorRedeemByPartition(_partition, msg.sender, _value, _data, _operatorData); /*solium-disable-next-line security/no-block-members*/ - emit RedeemedTokenByOwner(msg.sender, address(this), _value, now); + emit RedeemedTokensByPartition(msg.sender, address(this), _partition, _value, _data, _operatorData); } + function operatorTransferToRedeem(uint256 _value, bytes32 _partition, bytes calldata _data, bytes calldata _operatorData) external { + securityToken.operatorTransferByPartition(_partition, msg.sender, address(this), _value, _data, _operatorData); + tokenToRedeem[msg.sender] = _value; + } + + } diff --git a/contracts/mocks/MockSTGetter.sol b/contracts/mocks/MockSTGetter.sol new file mode 100644 index 000000000..249e38920 --- /dev/null +++ b/contracts/mocks/MockSTGetter.sol @@ -0,0 +1,24 @@ +pragma solidity 0.5.8; + +import "../tokens/STGetter.sol"; + +/** + * @title Security Token contract (mock) + * @notice SecurityToken is an ERC1400 token with added capabilities: + * @notice - Implements the ERC1400 Interface + * @notice - Transfers are restricted + * @notice - Modules can be attached to it to control its behaviour + * @notice - ST should not be deployed directly, but rather the SecurityTokenRegistry should be used + * @notice - ST does not inherit from ISecurityToken due to: + * @notice - https://github.com/ethereum/solidity/issues/4847 + */ +contract MockSTGetter is STGetter { + using SafeMath for uint256; + + event UpgradeEvent(uint256 _upgrade); + + function newGetter(uint256 _upgrade) public { + emit UpgradeEvent(_upgrade); + } + +} diff --git a/contracts/mocks/MockSTRGetter.sol b/contracts/mocks/MockSTRGetter.sol new file mode 100644 index 000000000..c206af5a1 --- /dev/null +++ b/contracts/mocks/MockSTRGetter.sol @@ -0,0 +1,16 @@ +pragma solidity 0.5.8; + +import "../STRGetter.sol"; + +/** + * @title Registry contract for issuers to register their security tokens + */ +contract MockSTRGetter is STRGetter { + /// @notice It is a dummy function + /// Alert! Alert! Do NOT use it for the mainnet release + + function newFunction() public pure returns (uint256) { + return 99; + } + +} diff --git a/contracts/mocks/MockSecurityTokenLogic.sol b/contracts/mocks/MockSecurityTokenLogic.sol new file mode 100644 index 000000000..9d1992614 --- /dev/null +++ b/contracts/mocks/MockSecurityTokenLogic.sol @@ -0,0 +1,66 @@ +pragma solidity 0.5.8; + +import "../tokens/SecurityToken.sol"; + +/** + * @title Security Token contract (mock) + * @notice SecurityToken is an ERC1400 token with added capabilities: + * @notice - Implements the ERC1400 Interface + * @notice - Transfers are restricted + * @notice - Modules can be attached to it to control its behaviour + * @notice - ST should not be deployed directly, but rather the SecurityTokenRegistry should be used + * @notice - ST does not inherit from ISecurityToken due to: + * @notice - https://github.com/ethereum/solidity/issues/4847 + */ +contract MockSecurityTokenLogic is SecurityToken { + + event UpgradeEvent(uint256 _upgrade); + uint256 public someValue; + + /** + * @notice Initialization function + * @dev Expected to be called atomically with the proxy being created, by the owner of the token + * @dev Can only be called once + */ + function upgrade(address _getterDelegate, uint256 _upgrade) external { + getterDelegate = _getterDelegate; + someValue = _upgrade; + //securityTokenVersion = SemanticVersion(3, 1, 0); + emit UpgradeEvent(_upgrade); + } + + /** + * @notice Initialization function + * @dev Expected to be called atomically with the proxy being created, by the owner of the token + * @dev Can only be called once + */ + function initialize(address _getterDelegate, uint256 _someValue) public { + //Expected to be called atomically with the proxy being created + require(!initialized, "Already initialized"); + getterDelegate = _getterDelegate; + securityTokenVersion = SemanticVersion(3, 0, 0); + updateFromRegistry(); + tokenFactory = msg.sender; + initialized = true; + someValue = _someValue; + } + + function newFunction(uint256 _upgrade) external { + emit UpgradeEvent(_upgrade); + } + + //To reduce bytecode size + function addModuleWithLabel( + address /* _moduleFactory */, + bytes memory /* _data */, + uint256 /* _maxCost */, + uint256 /* _budget */, + bytes32 /* _label */, + bool /* _archived */ + ) + public + { + emit UpgradeEvent(0); + } + +} diff --git a/contracts/mocks/MockWrongTypeFactory.sol b/contracts/mocks/MockWrongTypeFactory.sol index f04a3a2de..f8289423f 100644 --- a/contracts/mocks/MockWrongTypeFactory.sol +++ b/contracts/mocks/MockWrongTypeFactory.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; import "./MockBurnFactory.sol"; import "../modules/ModuleFactory.sol"; @@ -9,23 +9,29 @@ import "../libraries/Util.sol"; */ contract MockWrongTypeFactory is MockBurnFactory { - - /** - * @notice Constructor - * @param _polyAddress Address of the polytoken - */ - constructor (address _polyAddress, uint256 _setupCost, uint256 _usageCost, uint256 _subscriptionCost) public - MockBurnFactory(_polyAddress, _setupCost, _usageCost, _subscriptionCost) + /** + * @notice Constructor + * @param _setupCost Setup cost of the module + * @param _polymathRegistry Address of the Polymath Registry + */ + constructor( + uint256 _setupCost, + address _polymathRegistry, + bool _isFeeInPoly + ) + public + MockBurnFactory(_setupCost, _polymathRegistry, _isFeeInPoly) { + } /** * @notice Type of the Module factory */ - function getTypes() external view returns(uint8[]) { - uint8[] memory types = new uint8[](1); - types[0] = 4; - return types; + function getTypes() external view returns(uint8[] memory) { + uint8[] memory res = new uint8[](1); + res[0] = 4; + return res; } } diff --git a/contracts/mocks/PolyTokenFaucet.sol b/contracts/mocks/PolyTokenFaucet.sol index f06716c42..78f02b563 100644 --- a/contracts/mocks/PolyTokenFaucet.sol +++ b/contracts/mocks/PolyTokenFaucet.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; import "openzeppelin-solidity/contracts/math/SafeMath.sol"; @@ -8,7 +8,6 @@ import "openzeppelin-solidity/contracts/math/SafeMath.sol"; */ contract PolyTokenFaucet { - using SafeMath for uint256; uint256 totalSupply_; string public name = "Polymath Network"; @@ -23,14 +22,14 @@ contract PolyTokenFaucet { constructor() public { decimals = 18; - totalSupply_ = 1000000 * uint256(10)**decimals; + totalSupply_ = 1000000 * uint256(10) ** decimals; balances[msg.sender] = totalSupply_; emit Transfer(address(0), msg.sender, totalSupply_); } /* Token faucet - Not part of the ERC20 standard */ - function getTokens(uint256 _amount, address _recipient) public returns (bool) { - require(_amount <= 1000000 * uint256(10)**decimals, "Amount should not exceed 1 million"); + function getTokens(uint256 _amount, address _recipient) public returns(bool) { + require(_amount <= 1000000 * uint256(10) ** decimals, "Amount should not exceed 1 million"); require(_recipient != address(0), "Recipient address can not be empty"); balances[_recipient] = balances[_recipient].add(_amount); totalSupply_ = totalSupply_.add(_amount); @@ -44,7 +43,7 @@ contract PolyTokenFaucet { * @param _value The amount of token to be transferred * @return Whether the transfer was successful or not */ - function transfer(address _to, uint256 _value) public returns (bool) { + function transfer(address _to, uint256 _value) public returns(bool) { balances[msg.sender] = balances[msg.sender].sub(_value); balances[_to] = balances[_to].add(_value); emit Transfer(msg.sender, _to, _value); @@ -58,7 +57,7 @@ contract PolyTokenFaucet { * @param _value The amount of token to be transferred * @return Whether the transfer was successful or not */ - function transferFrom(address _from, address _to, uint256 _value) public returns (bool) { + function transferFrom(address _from, address _to, uint256 _value) public returns(bool) { require(_to != address(0), "Invalid address"); require(_value <= balances[_from], "Insufficient tokens transferable"); require(_value <= allowed[_from][msg.sender], "Insufficient tokens allowable"); @@ -75,7 +74,7 @@ contract PolyTokenFaucet { * @param _owner The address from which the balance will be retrieved * @return The balance */ - function balanceOf(address _owner) public view returns (uint256 balance) { + function balanceOf(address _owner) public view returns(uint256 balance) { return balances[_owner]; } @@ -85,7 +84,7 @@ contract PolyTokenFaucet { * @param _value The amount of tokens to be approved for transfer * @return Whether the approval was successful or not */ - function approve(address _spender, uint256 _value) public returns (bool) { + function approve(address _spender, uint256 _value) public returns(bool) { allowed[msg.sender][_spender] = _value; emit Approval(msg.sender, _spender, _value); return true; @@ -96,11 +95,11 @@ contract PolyTokenFaucet { * @param _spender The address of the account able to transfer the tokens * @return Amount of remaining tokens allowed to be spent */ - function allowance(address _owner, address _spender) public view returns (uint256 remaining) { + function allowance(address _owner, address _spender) public view returns(uint256 remaining) { return allowed[_owner][_spender]; } - function totalSupply() public view returns (uint256) { + function totalSupply() public view returns(uint256) { return totalSupply_; } @@ -113,15 +112,8 @@ contract PolyTokenFaucet { * @param _spender The address which will spend the funds. * @param _addedValue The amount of tokens to increase the allowance by. */ - function increaseApproval( - address _spender, - uint _addedValue - ) - public - returns (bool) - { - allowed[msg.sender][_spender] = ( - allowed[msg.sender][_spender].add(_addedValue)); + function increaseApproval(address _spender, uint _addedValue) public returns(bool) { + allowed[msg.sender][_spender] = (allowed[msg.sender][_spender].add(_addedValue)); emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]); return true; } @@ -136,13 +128,7 @@ contract PolyTokenFaucet { * @param _spender The address which will spend the funds. * @param _subtractedValue The amount of tokens to decrease the allowance by. */ - function decreaseApproval( - address _spender, - uint _subtractedValue - ) - public - returns (bool) - { + function decreaseApproval(address _spender, uint _subtractedValue) public returns(bool) { uint oldValue = allowed[msg.sender][_spender]; if (_subtractedValue > oldValue) { allowed[msg.sender][_spender] = 0; diff --git a/contracts/mocks/SecurityTokenMock.sol b/contracts/mocks/SecurityTokenMock.sol new file mode 100644 index 000000000..6fdd949e2 --- /dev/null +++ b/contracts/mocks/SecurityTokenMock.sol @@ -0,0 +1,16 @@ +pragma solidity 0.5.8; + +import "../tokens/SecurityToken.sol"; + +contract SecurityTokenMock is SecurityToken { + + /** + * @notice Initialization function + * @dev Expected to be called atomically with the proxy being created, by the owner of the token + * @dev Can only be called once + */ + function initialize(address _getterDelegate) public { + super.initialize(_getterDelegate); + securityTokenVersion = SemanticVersion(2, 2, 0); + } +} diff --git a/contracts/mocks/SecurityTokenRegistryMock.sol b/contracts/mocks/SecurityTokenRegistryMock.sol index d0a6de099..6715ed03f 100644 --- a/contracts/mocks/SecurityTokenRegistryMock.sol +++ b/contracts/mocks/SecurityTokenRegistryMock.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; import "../SecurityTokenRegistry.sol"; @@ -6,11 +6,17 @@ import "../SecurityTokenRegistry.sol"; * @title Registry contract for issuers to register their security tokens */ contract SecurityTokenRegistryMock is SecurityTokenRegistry { - /// @notice It is a dummy function - /// Alert! Alert! Do not use it for the mainnet release - function changeTheDeployedAddress(string _ticker, address _newSecurityTokenAddress) public { - set(Encoder.getKey("tickerToSecurityToken", _ticker), _newSecurityTokenAddress); - } - + /// Alert! Alert! Do NOT use it for the mainnet release + + uint256 public someValue; + + function changeTheFee(uint256 _newFee) public { + set(STLAUNCHFEE, _newFee); + } + + function configure(uint256 _someValue) public { + someValue = _someValue; + } + } diff --git a/contracts/mocks/TestSTOFactory.sol b/contracts/mocks/TestSTOFactory.sol index 120f72bbf..d678307c3 100644 --- a/contracts/mocks/TestSTOFactory.sol +++ b/contracts/mocks/TestSTOFactory.sol @@ -1,17 +1,23 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; -import "./DummySTOFactory.sol"; +import "./Dummy/DummySTOFactory.sol"; contract TestSTOFactory is DummySTOFactory { - /** * @notice Constructor - * @param _polyAddress Address of the polytoken + * @param _setupCost Setup cost of the module + * @param _logicContract Contract address that contains the logic related to `description` + * @param _polymathRegistry Address of the Polymath Registry */ - constructor (address _polyAddress, uint256 _setupCost, uint256 _usageCost, uint256 _subscriptionCost) public - DummySTOFactory(_polyAddress, _setupCost, _usageCost, _subscriptionCost) + constructor( + uint256 _setupCost, + address _logicContract, + address _polymathRegistry, + bool _isFeeInPoly + ) + public + DummySTOFactory(_setupCost, _logicContract, _polymathRegistry, _isFeeInPoly) { - version = "1.0.0"; name = "TestSTO"; title = "Test STO"; description = "Test STO"; @@ -19,17 +25,10 @@ contract TestSTOFactory is DummySTOFactory { compatibleSTVersionRange["upperBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0)); } - /** - * @notice Returns the instructions associated with the module - */ - function getInstructions() external view returns(string) { - return "Test STO - you can mint tokens at will"; - } - /** * @notice Gets the tags related to the module factory */ - function getTags() external view returns(bytes32[]) { + function getTags() external view returns(bytes32[] memory) { bytes32[] memory availableTags = new bytes32[](4); availableTags[0] = "Test"; availableTags[1] = "Non-refundable"; diff --git a/contracts/modules/Burn/IBurn.sol b/contracts/modules/Burn/IBurn.sol index 7a84a802b..d40e046d1 100644 --- a/contracts/modules/Burn/IBurn.sol +++ b/contracts/modules/Burn/IBurn.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; /** * @title Interface to be implemented by all checkpoint modules diff --git a/contracts/modules/Checkpoint/DividendCheckpoint.sol b/contracts/modules/Checkpoint/Dividend/DividendCheckpoint.sol similarity index 75% rename from contracts/modules/Checkpoint/DividendCheckpoint.sol rename to contracts/modules/Checkpoint/Dividend/DividendCheckpoint.sol index 626638924..ffa1513fd 100644 --- a/contracts/modules/Checkpoint/DividendCheckpoint.sol +++ b/contracts/modules/Checkpoint/Dividend/DividendCheckpoint.sol @@ -1,461 +1,437 @@ -/** - * DISCLAIMER: Under certain conditions, the function pushDividendPayment - * may fail due to block gas limits. - * If the total number of investors that ever held tokens is greater than ~15,000 then - * the function may fail. If this happens investors can pull their dividends, or the Issuer - * can use pushDividendPaymentToAddresses to provide an explict address list in batches - */ -pragma solidity ^0.4.24; - -import "./ICheckpoint.sol"; -import "./DividendCheckpointStorage.sol"; -import "../Module.sol"; -import "../../Pausable.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 DividendCheckpointStorage, ICheckpoint, Module, Pausable { - using SafeMath for uint256; - - event SetDefaultExcludedAddresses(address[] _excluded, uint256 _timestamp); - event SetWithholding(address[] _investors, uint256[] _withholding, uint256 _timestamp); - event SetWithholdingFixed(address[] _investors, uint256 _withholding, uint256 _timestamp); - event SetWallet(address indexed _oldWallet, address indexed _newWallet, uint256 _timestamp); - event UpdateDividendDates(uint256 indexed _dividendIndex, uint256 _maturity, uint256 _expiry); - - modifier validDividendIndex(uint256 _dividendIndex) { - _validDividendIndex(_dividendIndex); - _; - } - - function _validDividendIndex(uint256 _dividendIndex) internal view { - require(_dividendIndex < dividends.length, "Invalid dividend"); - require(!dividends[_dividendIndex].reclaimed, "Dividend reclaimed"); - /*solium-disable-next-line security/no-block-members*/ - require(now >= dividends[_dividendIndex].maturity, "Dividend maturity in future"); - /*solium-disable-next-line security/no-block-members*/ - require(now < dividends[_dividendIndex].expiry, "Dividend expiry in past"); - } - - /** - * @notice Pause (overridden function) - */ - function pause() public onlyOwner { - super._pause(); - } - - /** - * @notice Unpause (overridden function) - */ - function unpause() public onlyOwner { - super._unpause(); - } - - /** - * @notice Reclaims ERC20Basic compatible tokens - * @dev We duplicate here due to the overriden owner & onlyOwner - * @param _tokenContract The address of the token contract - */ - function reclaimERC20(address _tokenContract) external onlyOwner { - require(_tokenContract != address(0), "Invalid address"); - IERC20 token = IERC20(_tokenContract); - uint256 balance = token.balanceOf(address(this)); - require(token.transfer(msg.sender, balance), "Transfer failed"); - } - - /** - * @notice Reclaims ETH - * @dev We duplicate here due to the overriden owner & onlyOwner - */ - function reclaimETH() external onlyOwner { - msg.sender.transfer(address(this).balance); - } - - /** - * @notice Function used to intialize the contract variables - * @param _wallet Ethereum account address to receive reclaimed dividends and tax - */ - function configure( - address _wallet - ) public onlyFactory { - _setWallet(_wallet); - } - - /** - * @notice Init function i.e generalise function to maintain the structure of the module contract - * @return bytes4 - */ - function getInitFunction() public pure returns (bytes4) { - return this.configure.selector; - } - - /** - * @notice Function used to change wallet address - * @param _wallet Ethereum account address to receive reclaimed dividends and tax - */ - function changeWallet(address _wallet) external onlyOwner { - _setWallet(_wallet); - } - - function _setWallet(address _wallet) internal { - require(_wallet != address(0)); - emit SetWallet(wallet, _wallet, now); - wallet = _wallet; - } - - /** - * @notice Return the default excluded addresses - * @return List of excluded addresses - */ - function getDefaultExcluded() external view returns (address[]) { - return excluded; - } - - /** - * @notice Creates a checkpoint on the security token - * @return Checkpoint ID - */ - function createCheckpoint() public withPerm(CHECKPOINT) returns (uint256) { - return ISecurityToken(securityToken).createCheckpoint(); - } - - /** - * @notice Function to clear and set list of excluded addresses used for future dividends - * @param _excluded Addresses of investors - */ - function setDefaultExcluded(address[] _excluded) public withPerm(MANAGE) { - require(_excluded.length <= EXCLUDED_ADDRESS_LIMIT, "Too many excluded addresses"); - for (uint256 j = 0; j < _excluded.length; j++) { - require (_excluded[j] != address(0), "Invalid address"); - for (uint256 i = j + 1; i < _excluded.length; i++) { - require (_excluded[j] != _excluded[i], "Duplicate exclude address"); - } - } - excluded = _excluded; - /*solium-disable-next-line security/no-block-members*/ - emit SetDefaultExcludedAddresses(excluded, now); - } - - /** - * @notice Function to set withholding tax rates for investors - * @param _investors Addresses of investors - * @param _withholding Withholding tax for individual investors (multiplied by 10**16) - */ - function setWithholding(address[] _investors, uint256[] _withholding) public withPerm(MANAGE) { - require(_investors.length == _withholding.length, "Mismatched input lengths"); - /*solium-disable-next-line security/no-block-members*/ - emit SetWithholding(_investors, _withholding, now); - for (uint256 i = 0; i < _investors.length; i++) { - require(_withholding[i] <= 10**18, "Incorrect withholding tax"); - withholdingTax[_investors[i]] = _withholding[i]; - } - } - - /** - * @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 withPerm(MANAGE) { - require(_withholding <= 10**18, "Incorrect withholding tax"); - /*solium-disable-next-line security/no-block-members*/ - emit SetWithholdingFixed(_investors, _withholding, now); - 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.dividendExcluded[_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 checkpointId = dividend.checkpointId; - address[] memory investors = ISecurityToken(securityToken).getInvestorsAt(checkpointId); - uint256 numberInvestors = Math.min256(investors.length, _start.add(_iterations)); - for (uint256 i = _start; i < numberInvestors; i++) { - address payee = investors[i]; - if ((!dividend.claimed[payee]) && (!dividend.dividendExcluded[payee])) { - _payDividend(payee, dividend, _dividendIndex); - } - } - } - - /** - * @notice Investors can pull their own dividends - * @param _dividendIndex Dividend to pull - */ - function pullDividendPayment(uint256 _dividendIndex) public validDividendIndex(_dividendIndex) whenNotPaused - { - Dividend storage dividend = dividends[_dividendIndex]; - require(!dividend.claimed[msg.sender], "Dividend already claimed"); - require(!dividend.dividendExcluded[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 claim, withheld amounts - */ - function calculateDividend(uint256 _dividendIndex, address _payee) public view returns(uint256, uint256) { - require(_dividendIndex < dividends.length, "Invalid dividend"); - Dividend storage dividend = dividends[_dividendIndex]; - if (dividend.claimed[_payee] || dividend.dividendExcluded[_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 Allows issuer to change maturity / expiry dates for dividends - * @dev NB - setting the maturity of a currently matured dividend to a future date - * @dev will effectively refreeze claims on that dividend until the new maturity date passes - * @ dev NB - setting the expiry date to a past date will mean no more payments can be pulled - * @dev or pushed out of a dividend - * @param _dividendIndex Dividend to withdraw from - * @param _maturity updated maturity date - * @param _expiry updated expiry date - */ - function updateDividendDates(uint256 _dividendIndex, uint256 _maturity, uint256 _expiry) external onlyOwner { - require(_dividendIndex < dividends.length, "Invalid dividend"); - require(_expiry > _maturity, "Expiry before maturity"); - Dividend storage dividend = dividends[_dividendIndex]; - dividend.expiry = _expiry; - dividend.maturity = _maturity; - emit UpdateDividendDates(_dividendIndex, _maturity, _expiry); - } - - /** - * @notice Get static dividend data - * @return uint256[] timestamp of dividends creation - * @return uint256[] timestamp of dividends maturity - * @return uint256[] timestamp of dividends expiry - * @return uint256[] amount of dividends - * @return uint256[] claimed amount of dividends - * @return bytes32[] name of dividends - */ - function getDividendsData() external view returns ( - uint256[] memory createds, - uint256[] memory maturitys, - uint256[] memory expirys, - uint256[] memory amounts, - uint256[] memory claimedAmounts, - bytes32[] memory names) - { - createds = new uint256[](dividends.length); - maturitys = new uint256[](dividends.length); - expirys = new uint256[](dividends.length); - amounts = new uint256[](dividends.length); - claimedAmounts = new uint256[](dividends.length); - names = new bytes32[](dividends.length); - for (uint256 i = 0; i < dividends.length; i++) { - (createds[i], maturitys[i], expirys[i], amounts[i], claimedAmounts[i], names[i]) = getDividendData(i); - } - } - - /** - * @notice Get static dividend data - * @return uint256 timestamp of dividend creation - * @return uint256 timestamp of dividend maturity - * @return uint256 timestamp of dividend expiry - * @return uint256 amount of dividend - * @return uint256 claimed amount of dividend - * @return bytes32 name of dividend - */ - function getDividendData(uint256 _dividendIndex) public view returns ( - uint256 created, - uint256 maturity, - uint256 expiry, - uint256 amount, - uint256 claimedAmount, - bytes32 name) - { - created = dividends[_dividendIndex].created; - maturity = dividends[_dividendIndex].maturity; - expiry = dividends[_dividendIndex].expiry; - amount = dividends[_dividendIndex].amount; - claimedAmount = dividends[_dividendIndex].claimedAmount; - name = dividends[_dividendIndex].name; - } - - /** - * @notice Retrieves list of investors, their claim status and whether they are excluded - * @param _dividendIndex Dividend to withdraw from - * @return address[] list of investors - * @return bool[] whether investor has claimed - * @return bool[] whether investor is excluded - * @return uint256[] amount of withheld tax (estimate if not claimed) - * @return uint256[] amount of claim (estimate if not claimeed) - * @return uint256[] investor balance - */ - function getDividendProgress(uint256 _dividendIndex) external view returns ( - address[] memory investors, - bool[] memory resultClaimed, - bool[] memory resultExcluded, - uint256[] memory resultWithheld, - uint256[] memory resultAmount, - uint256[] memory resultBalance) - { - require(_dividendIndex < dividends.length, "Invalid dividend"); - //Get list of Investors - Dividend storage dividend = dividends[_dividendIndex]; - uint256 checkpointId = dividend.checkpointId; - investors = ISecurityToken(securityToken).getInvestorsAt(checkpointId); - resultClaimed = new bool[](investors.length); - resultExcluded = new bool[](investors.length); - resultWithheld = new uint256[](investors.length); - resultAmount = new uint256[](investors.length); - resultBalance = new uint256[](investors.length); - for (uint256 i; i < investors.length; i++) { - resultClaimed[i] = dividend.claimed[investors[i]]; - resultExcluded[i] = dividend.dividendExcluded[investors[i]]; - resultBalance[i] = ISecurityToken(securityToken).balanceOfAt(investors[i], dividend.checkpointId); - if (!resultExcluded[i]) { - if (resultClaimed[i]) { - resultWithheld[i] = dividend.withheld[investors[i]]; - resultAmount[i] = resultBalance[i].mul(dividend.amount).div(dividend.totalSupply).sub(resultWithheld[i]); - } else { - (uint256 claim, uint256 withheld) = calculateDividend(_dividendIndex, investors[i]); - resultWithheld[i] = withheld; - resultAmount[i] = claim.sub(withheld); - } - } - } - } - - /** - * @notice Retrieves list of investors, their balances, and their current withholding tax percentage - * @param _checkpointId Checkpoint Id to query for - * @return address[] list of investors - * @return uint256[] investor balances - * @return uint256[] investor withheld percentages - */ - function getCheckpointData(uint256 _checkpointId) external view returns (address[] memory investors, uint256[] memory balances, uint256[] memory withholdings) { - require(_checkpointId <= ISecurityToken(securityToken).currentCheckpointId(), "Invalid checkpoint"); - investors = ISecurityToken(securityToken).getInvestorsAt(_checkpointId); - balances = new uint256[](investors.length); - withholdings = new uint256[](investors.length); - for (uint256 i; i < investors.length; i++) { - balances[i] = ISecurityToken(securityToken).balanceOfAt(investors[i], _checkpointId); - withholdings[i] = withholdingTax[investors[i]]; - } - } - - /** - * @notice Checks whether an address is excluded from claiming a dividend - * @param _dividendIndex Dividend to withdraw from - * @return bool whether the address is excluded - */ - function isExcluded(address _investor, uint256 _dividendIndex) external view returns (bool) { - require(_dividendIndex < dividends.length, "Invalid dividend"); - return dividends[_dividendIndex].dividendExcluded[_investor]; - } - - /** - * @notice Checks whether an address has claimed a dividend - * @param _dividendIndex Dividend to withdraw from - * @return bool whether the address has claimed - */ - function isClaimed(address _investor, uint256 _dividendIndex) external view returns (bool) { - require(_dividendIndex < dividends.length, "Invalid dividend"); - return dividends[_dividendIndex].claimed[_investor]; - } - - /** - * @notice Return the permissions flag that are associated with this module - * @return bytes32 array - */ - function getPermissions() public view returns(bytes32[]) { - bytes32[] memory allPermissions = new bytes32[](2); - allPermissions[0] = DISTRIBUTE; - allPermissions[1] = MANAGE; - return allPermissions; - } - -} +/** + * DISCLAIMER: Under certain conditions, the function pushDividendPayment + * may fail due to block gas limits. + * If the total number of investors that ever held tokens is greater than ~15,000 then + * the function may fail. If this happens investors can pull their dividends, or the Issuer + * can use pushDividendPaymentToAddresses to provide an explict address list in batches + */ +pragma solidity 0.5.8; + +import ".././ICheckpoint.sol"; +import "../../../storage/modules/Checkpoint/Dividend/DividendCheckpointStorage.sol"; +import "../../Module.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 DividendCheckpointStorage, ICheckpoint, Module { + using SafeMath for uint256; + uint256 internal constant e18 = uint256(10) ** uint256(18); + + event SetDefaultExcludedAddresses(address[] _excluded); + event SetWithholding(address[] _investors, uint256[] _withholding); + event SetWithholdingFixed(address[] _investors, uint256 _withholding); + event SetWallet(address indexed _oldWallet, address indexed _newWallet); + event UpdateDividendDates(uint256 indexed _dividendIndex, uint256 _maturity, uint256 _expiry); + + function _validDividendIndex(uint256 _dividendIndex) internal view { + require(_dividendIndex < dividends.length, "Invalid dividend"); + require(!dividends[_dividendIndex].reclaimed, "Dividend reclaimed"); + /*solium-disable-next-line security/no-block-members*/ + require(now >= dividends[_dividendIndex].maturity, "Dividend maturity in future"); + /*solium-disable-next-line security/no-block-members*/ + require(now < dividends[_dividendIndex].expiry, "Dividend expiry in past"); + } + + /** + * @notice Function used to intialize the contract variables + * @param _wallet Ethereum account address to receive reclaimed dividends and tax + */ + function configure( + address payable _wallet + ) public onlyFactory { + _setWallet(_wallet); + } + + /** + * @notice Init function i.e generalise function to maintain the structure of the module contract + * @return bytes4 + */ + function getInitFunction() public pure returns(bytes4) { + return this.configure.selector; + } + + /** + * @notice Function used to change wallet address + * @param _wallet Ethereum account address to receive reclaimed dividends and tax + */ + function changeWallet(address payable _wallet) external { + _onlySecurityTokenOwner(); + _setWallet(_wallet); + } + + function _setWallet(address payable _wallet) internal { + emit SetWallet(wallet, _wallet); + wallet = _wallet; + } + + /** + * @notice Return the default excluded addresses + * @return List of excluded addresses + */ + function getDefaultExcluded() external view returns(address[] memory) { + return excluded; + } + + /** + * @notice Returns the treasury wallet address + */ + function getTreasuryWallet() public view returns(address payable) { + if (wallet == address(0)) { + address payable treasuryWallet = address(uint160(IDataStore(getDataStore()).getAddress(TREASURY))); + require(address(treasuryWallet) != address(0), "Invalid address"); + return treasuryWallet; + } + else + return wallet; + } + + /** + * @notice Creates a checkpoint on the security token + * @return Checkpoint ID + */ + function createCheckpoint() public withPerm(OPERATOR) returns(uint256) { + return securityToken.createCheckpoint(); + } + + /** + * @notice Function to clear and set list of excluded addresses used for future dividends + * @param _excluded Addresses of investors + */ + function setDefaultExcluded(address[] memory _excluded) public withPerm(ADMIN) { + require(_excluded.length <= EXCLUDED_ADDRESS_LIMIT, "Too many excluded addresses"); + for (uint256 j = 0; j < _excluded.length; j++) { + require(_excluded[j] != address(0), "Invalid address"); + for (uint256 i = j + 1; i < _excluded.length; i++) { + require(_excluded[j] != _excluded[i], "Duplicate exclude address"); + } + } + excluded = _excluded; + /*solium-disable-next-line security/no-block-members*/ + emit SetDefaultExcludedAddresses(excluded); + } + + /** + * @notice Function to set withholding tax rates for investors + * @param _investors Addresses of investors + * @param _withholding Withholding tax for individual investors (multiplied by 10**16) + */ + function setWithholding(address[] memory _investors, uint256[] memory _withholding) public withPerm(ADMIN) { + require(_investors.length == _withholding.length, "Mismatched input lengths"); + /*solium-disable-next-line security/no-block-members*/ + emit SetWithholding(_investors, _withholding); + for (uint256 i = 0; i < _investors.length; i++) { + require(_withholding[i] <= e18, "Incorrect withholding tax"); + 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[] memory _investors, uint256 _withholding) public withPerm(ADMIN) { + require(_withholding <= e18, "Incorrect withholding tax"); + /*solium-disable-next-line security/no-block-members*/ + emit SetWithholdingFixed(_investors, _withholding); + 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 payable[] memory _payees + ) + public + withPerm(OPERATOR) + { + _validDividendIndex(_dividendIndex); + Dividend storage dividend = dividends[_dividendIndex]; + for (uint256 i = 0; i < _payees.length; i++) { + if ((!dividend.claimed[_payees[i]]) && (!dividend.dividendExcluded[_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 _end Index in investor list at which to stop pushing dividends + */ + function pushDividendPayment( + uint256 _dividendIndex, + uint256 _start, + uint256 _end + ) + public + withPerm(OPERATOR) + { + //NB If possible, please use pushDividendPaymentToAddresses as it is cheaper than this function + _validDividendIndex(_dividendIndex); + Dividend storage dividend = dividends[_dividendIndex]; + uint256 checkpointId = dividend.checkpointId; + address[] memory investors = securityToken.getInvestorsSubsetAt(checkpointId, _start, _end); + // The investors list maybe smaller than _end - _start becuase it only contains addresses that had a positive balance + // the _start and _end used here are for the address list stored in the dataStore + for (uint256 i = 0; i < investors.length; i++) { + address payable payee = address(uint160(investors[i])); + if ((!dividend.claimed[payee]) && (!dividend.dividendExcluded[payee])) { + _payDividend(payee, dividend, _dividendIndex); + } + } + } + + /** + * @notice Investors can pull their own dividends + * @param _dividendIndex Dividend to pull + */ + function pullDividendPayment(uint256 _dividendIndex) public whenNotPaused { + _validDividendIndex(_dividendIndex); + Dividend storage dividend = dividends[_dividendIndex]; + require(!dividend.claimed[msg.sender], "Dividend already claimed"); + require(!dividend.dividendExcluded[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 payable _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 claim, withheld amounts + */ + function calculateDividend(uint256 _dividendIndex, address _payee) public view returns(uint256, uint256) { + require(_dividendIndex < dividends.length, "Invalid dividend"); + Dividend storage dividend = dividends[_dividendIndex]; + if (dividend.claimed[_payee] || dividend.dividendExcluded[_payee]) { + return (0, 0); + } + uint256 balance = securityToken.balanceOfAt(_payee, dividend.checkpointId); + uint256 claim = balance.mul(dividend.amount).div(dividend.totalSupply); + uint256 withheld = claim.mul(withholdingTax[_payee]).div(e18); + 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[] memory) { + 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 Allows issuer to change maturity / expiry dates for dividends + * @dev NB - setting the maturity of a currently matured dividend to a future date + * @dev will effectively refreeze claims on that dividend until the new maturity date passes + * @ dev NB - setting the expiry date to a past date will mean no more payments can be pulled + * @dev or pushed out of a dividend + * @param _dividendIndex Dividend to withdraw from + * @param _maturity updated maturity date + * @param _expiry updated expiry date + */ + function updateDividendDates(uint256 _dividendIndex, uint256 _maturity, uint256 _expiry) external withPerm(ADMIN) { + require(_dividendIndex < dividends.length, "Invalid dividend"); + require(_expiry > _maturity, "Expiry before maturity"); + Dividend storage dividend = dividends[_dividendIndex]; + require(dividend.expiry > now, "Dividend already expired"); + dividend.expiry = _expiry; + dividend.maturity = _maturity; + emit UpdateDividendDates(_dividendIndex, _maturity, _expiry); + } + + /** + * @notice Get static dividend data + * @return uint256[] timestamp of dividends creation + * @return uint256[] timestamp of dividends maturity + * @return uint256[] timestamp of dividends expiry + * @return uint256[] amount of dividends + * @return uint256[] claimed amount of dividends + * @return bytes32[] name of dividends + */ + function getDividendsData() external view returns ( + uint256[] memory createds, + uint256[] memory maturitys, + uint256[] memory expirys, + uint256[] memory amounts, + uint256[] memory claimedAmounts, + bytes32[] memory names) + { + createds = new uint256[](dividends.length); + maturitys = new uint256[](dividends.length); + expirys = new uint256[](dividends.length); + amounts = new uint256[](dividends.length); + claimedAmounts = new uint256[](dividends.length); + names = new bytes32[](dividends.length); + for (uint256 i = 0; i < dividends.length; i++) { + (createds[i], maturitys[i], expirys[i], amounts[i], claimedAmounts[i], names[i]) = getDividendData(i); + } + } + + /** + * @notice Get static dividend data + * @return uint256 timestamp of dividend creation + * @return uint256 timestamp of dividend maturity + * @return uint256 timestamp of dividend expiry + * @return uint256 amount of dividend + * @return uint256 claimed amount of dividend + * @return bytes32 name of dividend + */ + function getDividendData(uint256 _dividendIndex) public view returns ( + uint256 created, + uint256 maturity, + uint256 expiry, + uint256 amount, + uint256 claimedAmount, + bytes32 name) + { + created = dividends[_dividendIndex].created; + maturity = dividends[_dividendIndex].maturity; + expiry = dividends[_dividendIndex].expiry; + amount = dividends[_dividendIndex].amount; + claimedAmount = dividends[_dividendIndex].claimedAmount; + name = dividends[_dividendIndex].name; + } + + /** + * @notice Retrieves list of investors, their claim status and whether they are excluded + * @param _dividendIndex Dividend to withdraw from + * @return address[] list of investors + * @return bool[] whether investor has claimed + * @return bool[] whether investor is excluded + * @return uint256[] amount of withheld tax (estimate if not claimed) + * @return uint256[] amount of claim (estimate if not claimeed) + * @return uint256[] investor balance + */ + function getDividendProgress(uint256 _dividendIndex) external view returns ( + address[] memory investors, + bool[] memory resultClaimed, + bool[] memory resultExcluded, + uint256[] memory resultWithheld, + uint256[] memory resultAmount, + uint256[] memory resultBalance) + { + require(_dividendIndex < dividends.length, "Invalid dividend"); + //Get list of Investors + Dividend storage dividend = dividends[_dividendIndex]; + uint256 checkpointId = dividend.checkpointId; + investors = securityToken.getInvestorsAt(checkpointId); + resultClaimed = new bool[](investors.length); + resultExcluded = new bool[](investors.length); + resultWithheld = new uint256[](investors.length); + resultAmount = new uint256[](investors.length); + resultBalance = new uint256[](investors.length); + for (uint256 i; i < investors.length; i++) { + resultClaimed[i] = dividend.claimed[investors[i]]; + resultExcluded[i] = dividend.dividendExcluded[investors[i]]; + resultBalance[i] = securityToken.balanceOfAt(investors[i], dividend.checkpointId); + if (!resultExcluded[i]) { + if (resultClaimed[i]) { + resultWithheld[i] = dividend.withheld[investors[i]]; + resultAmount[i] = resultBalance[i].mul(dividend.amount).div(dividend.totalSupply).sub(resultWithheld[i]); + } else { + (uint256 claim, uint256 withheld) = calculateDividend(_dividendIndex, investors[i]); + resultWithheld[i] = withheld; + resultAmount[i] = claim.sub(withheld); + } + } + } + } + + /** + * @notice Retrieves list of investors, their balances, and their current withholding tax percentage + * @param _checkpointId Checkpoint Id to query for + * @return address[] list of investors + * @return uint256[] investor balances + * @return uint256[] investor withheld percentages + */ + function getCheckpointData(uint256 _checkpointId) external view returns (address[] memory investors, uint256[] memory balances, uint256[] memory withholdings) { + require(_checkpointId <= securityToken.currentCheckpointId(), "Invalid checkpoint"); + investors = securityToken.getInvestorsAt(_checkpointId); + balances = new uint256[](investors.length); + withholdings = new uint256[](investors.length); + for (uint256 i; i < investors.length; i++) { + balances[i] = securityToken.balanceOfAt(investors[i], _checkpointId); + withholdings[i] = withholdingTax[investors[i]]; + } + } + + /** + * @notice Checks whether an address is excluded from claiming a dividend + * @param _dividendIndex Dividend to withdraw from + * @return bool whether the address is excluded + */ + function isExcluded(address _investor, uint256 _dividendIndex) external view returns (bool) { + require(_dividendIndex < dividends.length, "Invalid dividend"); + return dividends[_dividendIndex].dividendExcluded[_investor]; + } + + /** + * @notice Checks whether an address has claimed a dividend + * @param _dividendIndex Dividend to withdraw from + * @return bool whether the address has claimed + */ + function isClaimed(address _investor, uint256 _dividendIndex) external view returns (bool) { + require(_dividendIndex < dividends.length, "Invalid dividend"); + return dividends[_dividendIndex].claimed[_investor]; + } + + /** + * @notice Return the permissions flag that are associated with this module + * @return bytes32 array + */ + function getPermissions() public view returns(bytes32[] memory) { + bytes32[] memory allPermissions = new bytes32[](2); + allPermissions[0] = ADMIN; + allPermissions[1] = OPERATOR; + return allPermissions; + } + +} diff --git a/contracts/modules/Checkpoint/ERC20DividendCheckpoint.sol b/contracts/modules/Checkpoint/Dividend/ERC20/ERC20DividendCheckpoint.sol similarity index 79% rename from contracts/modules/Checkpoint/ERC20DividendCheckpoint.sol rename to contracts/modules/Checkpoint/Dividend/ERC20/ERC20DividendCheckpoint.sol index d9dc93579..af122ddba 100644 --- a/contracts/modules/Checkpoint/ERC20DividendCheckpoint.sol +++ b/contracts/modules/Checkpoint/Dividend/ERC20/ERC20DividendCheckpoint.sol @@ -1,281 +1,277 @@ -pragma solidity ^0.4.24; - -import "./DividendCheckpoint.sol"; -import "./ERC20DividendCheckpointStorage.sol"; -import "../../interfaces/IOwnable.sol"; -import "../../interfaces/IERC20.sol"; - -/** - * @title Checkpoint module for issuing ERC20 dividends - */ -contract ERC20DividendCheckpoint is ERC20DividendCheckpointStorage, DividendCheckpoint { - using SafeMath for uint256; - - event ERC20DividendDeposited( - address indexed _depositor, - uint256 _checkpointId, - uint256 _created, - uint256 _maturity, - uint256 _expiry, - address indexed _token, - uint256 _amount, - uint256 _totalSupply, - uint256 _dividendIndex, - bytes32 indexed _name - ); - event ERC20DividendClaimed( - address indexed _payee, - uint256 indexed _dividendIndex, - address indexed _token, - uint256 _amount, - uint256 _withheld - ); - event ERC20DividendReclaimed( - address indexed _claimer, - uint256 indexed _dividendIndex, - address indexed _token, - uint256 _claimedAmount - ); - event ERC20DividendWithholdingWithdrawn( - address indexed _claimer, - uint256 indexed _dividendIndex, - address indexed _token, - uint256 _withheldAmount - ); - - /** - * @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 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 _name Name/Title for identification - */ - function createDividend( - uint256 _maturity, - uint256 _expiry, - address _token, - uint256 _amount, - bytes32 _name - ) - external - withPerm(MANAGE) - { - createDividendWithExclusions(_maturity, _expiry, _token, _amount, excluded, _name); - } - - /** - * @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 - * @param _name Name/Title for identification - */ - function createDividendWithCheckpoint( - uint256 _maturity, - uint256 _expiry, - address _token, - uint256 _amount, - uint256 _checkpointId, - bytes32 _name - ) - external - withPerm(MANAGE) - { - _createDividendWithCheckpointAndExclusions(_maturity, _expiry, _token, _amount, _checkpointId, excluded, _name); - } - - /** - * @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 - * @param _name Name/Title for identification - */ - function createDividendWithExclusions( - uint256 _maturity, - uint256 _expiry, - address _token, - uint256 _amount, - address[] _excluded, - bytes32 _name - ) - public - withPerm(MANAGE) - { - uint256 checkpointId = ISecurityToken(securityToken).createCheckpoint(); - _createDividendWithCheckpointAndExclusions(_maturity, _expiry, _token, _amount, checkpointId, _excluded, _name); - } - - /** - * @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 - * @param _excluded List of addresses to exclude - * @param _name Name/Title for identification - */ - function createDividendWithCheckpointAndExclusions( - uint256 _maturity, - uint256 _expiry, - address _token, - uint256 _amount, - uint256 _checkpointId, - address[] _excluded, - bytes32 _name - ) - public - withPerm(MANAGE) - { - _createDividendWithCheckpointAndExclusions(_maturity, _expiry, _token, _amount, _checkpointId, _excluded, _name); - } - - /** - * @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 - * @param _excluded List of addresses to exclude - * @param _name Name/Title for identification - */ - function _createDividendWithCheckpointAndExclusions( - uint256 _maturity, - uint256 _expiry, - address _token, - uint256 _amount, - uint256 _checkpointId, - address[] _excluded, - bytes32 _name - ) - internal - { - ISecurityToken securityTokenInstance = ISecurityToken(securityToken); - require(_excluded.length <= EXCLUDED_ADDRESS_LIMIT, "Too many addresses excluded"); - require(_expiry > _maturity, "Expiry before maturity"); - /*solium-disable-next-line security/no-block-members*/ - require(_expiry > now, "Expiry in past"); - require(_amount > 0, "No dividend sent"); - require(_token != address(0), "Invalid token"); - require(_checkpointId <= securityTokenInstance.currentCheckpointId(), "Invalid checkpoint"); - require(IERC20(_token).transferFrom(msg.sender, address(this), _amount), "insufficent allowance"); - require(_name[0] != 0); - uint256 dividendIndex = dividends.length; - uint256 currentSupply = securityTokenInstance.totalSupplyAt(_checkpointId); - uint256 excludedSupply = 0; - dividends.push( - Dividend( - _checkpointId, - now, /*solium-disable-line security/no-block-members*/ - _maturity, - _expiry, - _amount, - 0, - 0, - false, - 0, - 0, - _name - ) - ); - - for (uint256 j = 0; j < _excluded.length; j++) { - require (_excluded[j] != address(0), "Invalid address"); - require(!dividends[dividendIndex].dividendExcluded[_excluded[j]], "duped exclude address"); - excludedSupply = excludedSupply.add(securityTokenInstance.balanceOfAt(_excluded[j], _checkpointId)); - dividends[dividendIndex].dividendExcluded[_excluded[j]] = true; - } - - dividends[dividendIndex].totalSupply = currentSupply.sub(excludedSupply); - dividendTokens[dividendIndex] = _token; - _emitERC20DividendDepositedEvent(_checkpointId, _maturity, _expiry, _token, _amount, currentSupply, dividendIndex, _name); - } - - /** - * @notice Emits the ERC20DividendDeposited event. - * Seperated into a different function as a workaround for stack too deep error - */ - function _emitERC20DividendDepositedEvent( - uint256 _checkpointId, - uint256 _maturity, - uint256 _expiry, - address _token, - uint256 _amount, - uint256 currentSupply, - uint256 dividendIndex, - bytes32 _name - ) - internal - { - /*solium-disable-next-line security/no-block-members*/ - emit ERC20DividendDeposited(msg.sender, _checkpointId, now, _maturity, _expiry, _token, _amount, currentSupply, dividendIndex, _name); - } - - /** - * @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) = calculateDividend(_dividendIndex, _payee); - _dividend.claimed[_payee] = true; - _dividend.claimedAmount = claim.add(_dividend.claimedAmount); - uint256 claimAfterWithheld = claim.sub(withheld); - if (claimAfterWithheld > 0) { - require(IERC20(dividendTokens[_dividendIndex]).transfer(_payee, claimAfterWithheld), "transfer failed"); - } - if (withheld > 0) { - _dividend.totalWithheld = _dividend.totalWithheld.add(withheld); - _dividend.withheld[_payee] = withheld; - } - emit ERC20DividendClaimed(_payee, _dividendIndex, dividendTokens[_dividendIndex], claim, withheld); - } - - /** - * @notice Issuer can reclaim remaining unclaimed dividend amounts, for expired dividends - * @param _dividendIndex Dividend to reclaim - */ - function reclaimDividend(uint256 _dividendIndex) external withPerm(MANAGE) { - require(_dividendIndex < dividends.length, "Invalid dividend"); - /*solium-disable-next-line security/no-block-members*/ - require(now >= dividends[_dividendIndex].expiry, "Dividend expiry in future"); - require(!dividends[_dividendIndex].reclaimed, "already claimed"); - dividends[_dividendIndex].reclaimed = true; - Dividend storage dividend = dividends[_dividendIndex]; - uint256 remainingAmount = dividend.amount.sub(dividend.claimedAmount); - require(IERC20(dividendTokens[_dividendIndex]).transfer(wallet, remainingAmount), "transfer failed"); - emit ERC20DividendReclaimed(wallet, _dividendIndex, dividendTokens[_dividendIndex], remainingAmount); - } - - /** - * @notice Allows issuer to withdraw withheld tax - * @param _dividendIndex Dividend to withdraw from - */ - function withdrawWithholding(uint256 _dividendIndex) external withPerm(MANAGE) { - require(_dividendIndex < dividends.length, "Invalid dividend"); - Dividend storage dividend = dividends[_dividendIndex]; - uint256 remainingWithheld = dividend.totalWithheld.sub(dividend.totalWithheldWithdrawn); - dividend.totalWithheldWithdrawn = dividend.totalWithheld; - require(IERC20(dividendTokens[_dividendIndex]).transfer(wallet, remainingWithheld), "transfer failed"); - emit ERC20DividendWithholdingWithdrawn(wallet, _dividendIndex, dividendTokens[_dividendIndex], remainingWithheld); - } - -} +pragma solidity 0.5.8; + +import "../DividendCheckpoint.sol"; +import "./ERC20DividendCheckpointStorage.sol"; +import "../../../../interfaces/IOwnable.sol"; +import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; + +/** + * @title Checkpoint module for issuing ERC20 dividends + */ +contract ERC20DividendCheckpoint is ERC20DividendCheckpointStorage, DividendCheckpoint { + using SafeMath for uint256; + + event ERC20DividendDeposited( + address indexed _depositor, + uint256 _checkpointId, + uint256 _maturity, + uint256 _expiry, + address indexed _token, + uint256 _amount, + uint256 _totalSupply, + uint256 _dividendIndex, + bytes32 indexed _name + ); + event ERC20DividendClaimed(address indexed _payee, uint256 indexed _dividendIndex, address indexed _token, uint256 _amount, uint256 _withheld); + event ERC20DividendReclaimed(address indexed _claimer, uint256 indexed _dividendIndex, address indexed _token, uint256 _claimedAmount); + event ERC20DividendWithholdingWithdrawn( + address indexed _claimer, + uint256 indexed _dividendIndex, + address indexed _token, + uint256 _withheldAmount + ); + + /** + * @notice Constructor + * @param _securityToken Address of the security token + */ + constructor(address _securityToken, address _polyToken) public Module(_securityToken, _polyToken) { + + } + + /** + * @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 _name Name/Title for identification + */ + function createDividend( + uint256 _maturity, + uint256 _expiry, + address _token, + uint256 _amount, + bytes32 _name + ) + external + withPerm(ADMIN) + { + createDividendWithExclusions(_maturity, _expiry, _token, _amount, excluded, _name); + } + + /** + * @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 + * @param _name Name/Title for identification + */ + function createDividendWithCheckpoint( + uint256 _maturity, + uint256 _expiry, + address _token, + uint256 _amount, + uint256 _checkpointId, + bytes32 _name + ) + external + withPerm(ADMIN) + { + _createDividendWithCheckpointAndExclusions(_maturity, _expiry, _token, _amount, _checkpointId, excluded, _name); + } + + /** + * @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 + * @param _name Name/Title for identification + */ + function createDividendWithExclusions( + uint256 _maturity, + uint256 _expiry, + address _token, + uint256 _amount, + address[] memory _excluded, + bytes32 _name + ) + public + withPerm(ADMIN) + { + uint256 checkpointId = securityToken.createCheckpoint(); + _createDividendWithCheckpointAndExclusions(_maturity, _expiry, _token, _amount, checkpointId, _excluded, _name); + } + + /** + * @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 + * @param _excluded List of addresses to exclude + * @param _name Name/Title for identification + */ + function createDividendWithCheckpointAndExclusions( + uint256 _maturity, + uint256 _expiry, + address _token, + uint256 _amount, + uint256 _checkpointId, + address[] memory _excluded, + bytes32 _name + ) + public + withPerm(ADMIN) + { + _createDividendWithCheckpointAndExclusions(_maturity, _expiry, _token, _amount, _checkpointId, _excluded, _name); + } + + /** + * @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 + * @param _excluded List of addresses to exclude + * @param _name Name/Title for identification + */ + function _createDividendWithCheckpointAndExclusions( + uint256 _maturity, + uint256 _expiry, + address _token, + uint256 _amount, + uint256 _checkpointId, + address[] memory _excluded, + bytes32 _name + ) + internal + { + require(_excluded.length <= EXCLUDED_ADDRESS_LIMIT, "Too many addresses excluded"); + require(_expiry > _maturity, "Expiry before maturity"); + /*solium-disable-next-line security/no-block-members*/ + require(_expiry > now, "Expiry in past"); + require(_amount > 0, "No dividend sent"); + require(_token != address(0), "Invalid token"); + require(_checkpointId <= securityToken.currentCheckpointId(), "Invalid checkpoint"); + require(IERC20(_token).transferFrom(msg.sender, address(this), _amount), "insufficent allowance"); + require(_name != bytes32(0)); + uint256 dividendIndex = dividends.length; + uint256 currentSupply = securityToken.totalSupplyAt(_checkpointId); + require(currentSupply > 0, "Invalid supply"); + uint256 excludedSupply = 0; + dividends.push( + Dividend( + _checkpointId, + now, /*solium-disable-line security/no-block-members*/ + _maturity, + _expiry, + _amount, + 0, + 0, + false, + 0, + 0, + _name + ) + ); + + for (uint256 j = 0; j < _excluded.length; j++) { + require(_excluded[j] != address(0), "Invalid address"); + require(!dividends[dividendIndex].dividendExcluded[_excluded[j]], "duped exclude address"); + excludedSupply = excludedSupply.add(securityToken.balanceOfAt(_excluded[j], _checkpointId)); + dividends[dividendIndex].dividendExcluded[_excluded[j]] = true; + } + require(currentSupply > excludedSupply, "Invalid supply"); + dividends[dividendIndex].totalSupply = currentSupply - excludedSupply; + dividendTokens[dividendIndex] = _token; + _emitERC20DividendDepositedEvent(_checkpointId, _maturity, _expiry, _token, _amount, currentSupply, dividendIndex, _name); + } + + /** + * @notice Emits the ERC20DividendDeposited event. + * Seperated into a different function as a workaround for stack too deep error + */ + function _emitERC20DividendDepositedEvent( + uint256 _checkpointId, + uint256 _maturity, + uint256 _expiry, + address _token, + uint256 _amount, + uint256 currentSupply, + uint256 dividendIndex, + bytes32 _name + ) + internal + { + /*solium-disable-next-line security/no-block-members*/ + emit ERC20DividendDeposited( + msg.sender, + _checkpointId, + _maturity, + _expiry, + _token, + _amount, + currentSupply, + dividendIndex, + _name + ); + } + + /** + * @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 payable _payee, Dividend storage _dividend, uint256 _dividendIndex) internal { + (uint256 claim, uint256 withheld) = calculateDividend(_dividendIndex, _payee); + _dividend.claimed[_payee] = true; + _dividend.claimedAmount = claim.add(_dividend.claimedAmount); + uint256 claimAfterWithheld = claim.sub(withheld); + if (claimAfterWithheld > 0) { + require(IERC20(dividendTokens[_dividendIndex]).transfer(_payee, claimAfterWithheld), "transfer failed"); + } + if (withheld > 0) { + _dividend.totalWithheld = _dividend.totalWithheld.add(withheld); + _dividend.withheld[_payee] = withheld; + } + emit ERC20DividendClaimed(_payee, _dividendIndex, dividendTokens[_dividendIndex], claim, withheld); + } + + /** + * @notice Issuer can reclaim remaining unclaimed dividend amounts, for expired dividends + * @param _dividendIndex Dividend to reclaim + */ + function reclaimDividend(uint256 _dividendIndex) external withPerm(OPERATOR) { + require(_dividendIndex < dividends.length, "Invalid dividend"); + /*solium-disable-next-line security/no-block-members*/ + require(now >= dividends[_dividendIndex].expiry, "Dividend expiry in future"); + require(!dividends[_dividendIndex].reclaimed, "already claimed"); + dividends[_dividendIndex].reclaimed = true; + Dividend storage dividend = dividends[_dividendIndex]; + uint256 remainingAmount = dividend.amount.sub(dividend.claimedAmount); + require(IERC20(dividendTokens[_dividendIndex]).transfer(getTreasuryWallet(), remainingAmount), "transfer failed"); + emit ERC20DividendReclaimed(wallet, _dividendIndex, dividendTokens[_dividendIndex], remainingAmount); + } + + /** + * @notice Allows issuer to withdraw withheld tax + * @param _dividendIndex Dividend to withdraw from + */ + function withdrawWithholding(uint256 _dividendIndex) external withPerm(OPERATOR) { + require(_dividendIndex < dividends.length, "Invalid dividend"); + Dividend storage dividend = dividends[_dividendIndex]; + uint256 remainingWithheld = dividend.totalWithheld.sub(dividend.totalWithheldWithdrawn); + dividend.totalWithheldWithdrawn = dividend.totalWithheld; + require(IERC20(dividendTokens[_dividendIndex]).transfer(getTreasuryWallet(), remainingWithheld), "transfer failed"); + emit ERC20DividendWithholdingWithdrawn(wallet, _dividendIndex, dividendTokens[_dividendIndex], remainingWithheld); + } + +} diff --git a/contracts/modules/Checkpoint/Dividend/ERC20/ERC20DividendCheckpointFactory.sol b/contracts/modules/Checkpoint/Dividend/ERC20/ERC20DividendCheckpointFactory.sol new file mode 100644 index 000000000..42a424001 --- /dev/null +++ b/contracts/modules/Checkpoint/Dividend/ERC20/ERC20DividendCheckpointFactory.sol @@ -0,0 +1,48 @@ +pragma solidity 0.5.8; + +import "./ERC20DividendCheckpointProxy.sol"; +import "../../../UpgradableModuleFactory.sol"; + +/** + * @title Factory for deploying ERC20DividendCheckpoint module + */ +contract ERC20DividendCheckpointFactory is UpgradableModuleFactory { + + /** + * @notice Constructor + * @param _setupCost Setup cost of the module + * @param _logicContract Contract address that contains the logic related to `description` + * @param _polymathRegistry Address of the Polymath registry + * @param _isCostInPoly true = cost in Poly, false = USD + */ + constructor ( + uint256 _setupCost, + address _logicContract, + address _polymathRegistry, + bool _isCostInPoly + ) + public + UpgradableModuleFactory("3.0.0", _setupCost, _logicContract, _polymathRegistry, _isCostInPoly) + { + name = "ERC20DividendCheckpoint"; + title = "ERC20 Dividend Checkpoint"; + description = "Create ERC20 dividends for token holders at a specific checkpoint"; + typesData.push(4); + tagsData.push("ERC20"); + tagsData.push("Dividend"); + tagsData.push("Checkpoint"); + compatibleSTVersionRange["lowerBound"] = VersionUtils.pack(uint8(3), uint8(0), uint8(0)); + compatibleSTVersionRange["upperBound"] = VersionUtils.pack(uint8(3), uint8(0), uint8(0)); + } + + /** + * @notice Used to launch the Module with the help of factory + * @return Address Contract address of the Module + */ + function deploy(bytes calldata _data) external returns(address) { + address erc20DividendCheckpoint = address(new ERC20DividendCheckpointProxy(logicContracts[latestUpgrade].version, msg.sender, polymathRegistry.getAddress("PolyToken"), logicContracts[latestUpgrade].logicContract)); + _initializeModule(erc20DividendCheckpoint, _data); + return erc20DividendCheckpoint; + } + +} diff --git a/contracts/modules/Checkpoint/Dividend/ERC20/ERC20DividendCheckpointProxy.sol b/contracts/modules/Checkpoint/Dividend/ERC20/ERC20DividendCheckpointProxy.sol new file mode 100644 index 000000000..4b060eae1 --- /dev/null +++ b/contracts/modules/Checkpoint/Dividend/ERC20/ERC20DividendCheckpointProxy.sol @@ -0,0 +1,32 @@ +pragma solidity 0.5.8; + +import "../../../../proxy/OwnedUpgradeabilityProxy.sol"; +import "./ERC20DividendCheckpointStorage.sol"; +import "../../../../storage/modules/Checkpoint/Dividend/DividendCheckpointStorage.sol"; +import "../../../../Pausable.sol"; +import "../../../../storage/modules/ModuleStorage.sol"; + +/** + * @title Transfer Manager module for core transfer validation functionality + */ +contract ERC20DividendCheckpointProxy is ERC20DividendCheckpointStorage, DividendCheckpointStorage, ModuleStorage, Pausable, OwnedUpgradeabilityProxy { + /** + * @notice Constructor + * @param _securityToken Address of the security token + * @param _polyAddress Address of the polytoken + * @param _implementation representing the address of the new implementation to be set + */ + constructor( + string memory _version, + address _securityToken, + address _polyAddress, + address _implementation + ) + public + ModuleStorage(_securityToken, _polyAddress) + { + require(_implementation != address(0), "Implementation address should not be 0x"); + _upgradeTo(_version, _implementation); + } + +} diff --git a/contracts/modules/Checkpoint/ERC20DividendCheckpointStorage.sol b/contracts/modules/Checkpoint/Dividend/ERC20/ERC20DividendCheckpointStorage.sol similarity index 69% rename from contracts/modules/Checkpoint/ERC20DividendCheckpointStorage.sol rename to contracts/modules/Checkpoint/Dividend/ERC20/ERC20DividendCheckpointStorage.sol index 29401f8d9..d83fcde56 100644 --- a/contracts/modules/Checkpoint/ERC20DividendCheckpointStorage.sol +++ b/contracts/modules/Checkpoint/Dividend/ERC20/ERC20DividendCheckpointStorage.sol @@ -1,11 +1,10 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; /** * @title It holds the storage variables related to ERC20DividendCheckpoint module */ contract ERC20DividendCheckpointStorage { - // Mapping to token address for each dividend - mapping (uint256 => address) public dividendTokens; + mapping(uint256 => address) public dividendTokens; } diff --git a/contracts/modules/Checkpoint/EtherDividendCheckpoint.sol b/contracts/modules/Checkpoint/Dividend/Ether/EtherDividendCheckpoint.sol similarity index 80% rename from contracts/modules/Checkpoint/EtherDividendCheckpoint.sol rename to contracts/modules/Checkpoint/Dividend/Ether/EtherDividendCheckpoint.sol index 699da0b57..360f5dfe2 100644 --- a/contracts/modules/Checkpoint/EtherDividendCheckpoint.sol +++ b/contracts/modules/Checkpoint/Dividend/Ether/EtherDividendCheckpoint.sol @@ -1,218 +1,219 @@ -pragma solidity ^0.4.24; - -import "./DividendCheckpoint.sol"; -import "../../interfaces/IOwnable.sol"; - -/** - * @title Checkpoint module for issuing ether dividends - */ -contract EtherDividendCheckpoint is DividendCheckpoint { - using SafeMath for uint256; - - event EtherDividendDeposited( - address indexed _depositor, - uint256 _checkpointId, - uint256 _created, - uint256 _maturity, - uint256 _expiry, - uint256 _amount, - uint256 _totalSupply, - uint256 indexed _dividendIndex, - bytes32 indexed _name - ); - event EtherDividendClaimed(address indexed _payee, uint256 indexed _dividendIndex, uint256 _amount, uint256 _withheld); - event EtherDividendReclaimed(address indexed _claimer, uint256 indexed _dividendIndex, uint256 _claimedAmount); - event EtherDividendClaimFailed(address indexed _payee, uint256 indexed _dividendIndex, uint256 _amount, uint256 _withheld); - event EtherDividendWithholdingWithdrawn(address indexed _claimer, uint256 indexed _dividendIndex, uint256 _withheldAmount); - - /** - * @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 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 - * @param _name Name/title for identification - */ - function createDividend(uint256 _maturity, uint256 _expiry, bytes32 _name) external payable withPerm(MANAGE) { - createDividendWithExclusions(_maturity, _expiry, excluded, _name); - } - - /** - * @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 - * @param _name Name/title for identification - */ - function createDividendWithCheckpoint( - uint256 _maturity, - uint256 _expiry, - uint256 _checkpointId, - bytes32 _name - ) - external - payable - withPerm(MANAGE) - { - _createDividendWithCheckpointAndExclusions(_maturity, _expiry, _checkpointId, excluded, _name); - } - - /** - * @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 - * @param _name Name/title for identification - */ - function createDividendWithExclusions( - uint256 _maturity, - uint256 _expiry, - address[] _excluded, - bytes32 _name - ) - public - payable - withPerm(MANAGE) - { - uint256 checkpointId = ISecurityToken(securityToken).createCheckpoint(); - _createDividendWithCheckpointAndExclusions(_maturity, _expiry, checkpointId, _excluded, _name); - } - - /** - * @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 - * @param _name Name/title for identification - */ - function createDividendWithCheckpointAndExclusions( - uint256 _maturity, - uint256 _expiry, - uint256 _checkpointId, - address[] _excluded, - bytes32 _name - ) - public - payable - withPerm(MANAGE) - { - _createDividendWithCheckpointAndExclusions(_maturity, _expiry, _checkpointId, _excluded, _name); - } - - /** - * @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 - * @param _name Name/title for identification - */ - function _createDividendWithCheckpointAndExclusions( - uint256 _maturity, - uint256 _expiry, - uint256 _checkpointId, - address[] _excluded, - bytes32 _name - ) - internal - { - require(_excluded.length <= EXCLUDED_ADDRESS_LIMIT, "Too many addresses excluded"); - require(_expiry > _maturity, "Expiry is before maturity"); - /*solium-disable-next-line security/no-block-members*/ - require(_expiry > now, "Expiry is in the past"); - require(msg.value > 0, "No dividend sent"); - require(_checkpointId <= ISecurityToken(securityToken).currentCheckpointId()); - require(_name[0] != 0); - uint256 dividendIndex = dividends.length; - uint256 currentSupply = ISecurityToken(securityToken).totalSupplyAt(_checkpointId); - uint256 excludedSupply = 0; - dividends.push( - Dividend( - _checkpointId, - now, /*solium-disable-line security/no-block-members*/ - _maturity, - _expiry, - msg.value, - 0, - 0, - false, - 0, - 0, - _name - ) - ); - - for (uint256 j = 0; j < _excluded.length; j++) { - require (_excluded[j] != address(0), "Invalid address"); - require(!dividends[dividendIndex].dividendExcluded[_excluded[j]], "duped exclude address"); - excludedSupply = excludedSupply.add(ISecurityToken(securityToken).balanceOfAt(_excluded[j], _checkpointId)); - dividends[dividendIndex].dividendExcluded[_excluded[j]] = true; - } - dividends[dividendIndex].totalSupply = currentSupply.sub(excludedSupply); - /*solium-disable-next-line security/no-block-members*/ - emit EtherDividendDeposited(msg.sender, _checkpointId, now, _maturity, _expiry, msg.value, currentSupply, dividendIndex, _name); - } - - /** - * @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) = calculateDividend(_dividendIndex, _payee); - _dividend.claimed[_payee] = true; - uint256 claimAfterWithheld = claim.sub(withheld); - /*solium-disable-next-line security/no-send*/ - if (_payee.send(claimAfterWithheld)) { - _dividend.claimedAmount = _dividend.claimedAmount.add(claim); - if (withheld > 0) { - _dividend.totalWithheld = _dividend.totalWithheld.add(withheld); - _dividend.withheld[_payee] = withheld; - } - emit EtherDividendClaimed(_payee, _dividendIndex, claim, withheld); - } else { - _dividend.claimed[_payee] = false; - emit EtherDividendClaimFailed(_payee, _dividendIndex, claim, withheld); - } - } - - /** - * @notice Issuer can reclaim remaining unclaimed dividend amounts, for expired dividends - * @param _dividendIndex Dividend to reclaim - */ - function reclaimDividend(uint256 _dividendIndex) external withPerm(MANAGE) { - require(_dividendIndex < dividends.length, "Incorrect dividend index"); - /*solium-disable-next-line security/no-block-members*/ - require(now >= dividends[_dividendIndex].expiry, "Dividend expiry is in the future"); - require(!dividends[_dividendIndex].reclaimed, "Dividend is already claimed"); - Dividend storage dividend = dividends[_dividendIndex]; - dividend.reclaimed = true; - uint256 remainingAmount = dividend.amount.sub(dividend.claimedAmount); - wallet.transfer(remainingAmount); - emit EtherDividendReclaimed(wallet, _dividendIndex, remainingAmount); - } - - /** - * @notice Allows issuer to withdraw withheld tax - * @param _dividendIndex Dividend to withdraw from - */ - function withdrawWithholding(uint256 _dividendIndex) external withPerm(MANAGE) { - require(_dividendIndex < dividends.length, "Incorrect dividend index"); - Dividend storage dividend = dividends[_dividendIndex]; - uint256 remainingWithheld = dividend.totalWithheld.sub(dividend.totalWithheldWithdrawn); - dividend.totalWithheldWithdrawn = dividend.totalWithheld; - wallet.transfer(remainingWithheld); - emit EtherDividendWithholdingWithdrawn(wallet, _dividendIndex, remainingWithheld); - } - -} +pragma solidity 0.5.8; + +import "../DividendCheckpoint.sol"; +import "../../../../interfaces/IOwnable.sol"; + +/** + * @title Checkpoint module for issuing ether dividends + */ +contract EtherDividendCheckpoint is DividendCheckpoint { + using SafeMath for uint256; + + event EtherDividendDeposited( + address indexed _depositor, + uint256 _checkpointId, + uint256 _maturity, + uint256 _expiry, + uint256 _amount, + uint256 _totalSupply, + uint256 indexed _dividendIndex, + bytes32 indexed _name + ); + event EtherDividendClaimed(address indexed _payee, uint256 indexed _dividendIndex, uint256 _amount, uint256 _withheld); + event EtherDividendReclaimed(address indexed _claimer, uint256 indexed _dividendIndex, uint256 _claimedAmount); + event EtherDividendClaimFailed(address indexed _payee, uint256 indexed _dividendIndex, uint256 _amount, uint256 _withheld); + event EtherDividendWithholdingWithdrawn(address indexed _claimer, uint256 indexed _dividendIndex, uint256 _withheldAmount); + + /** + * @notice Constructor + * @param _securityToken Address of the security token + */ + constructor(address _securityToken, address _polyToken) public Module(_securityToken, _polyToken) { + + } + + /** + * @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 + * @param _name Name/title for identification + */ + function createDividend(uint256 _maturity, uint256 _expiry, bytes32 _name) external payable withPerm(ADMIN) { + createDividendWithExclusions(_maturity, _expiry, excluded, _name); + } + + /** + * @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 + * @param _name Name/title for identification + */ + function createDividendWithCheckpoint( + uint256 _maturity, + uint256 _expiry, + uint256 _checkpointId, + bytes32 _name + ) + external + payable + withPerm(ADMIN) + { + _createDividendWithCheckpointAndExclusions(_maturity, _expiry, _checkpointId, excluded, _name); + } + + /** + * @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 + * @param _name Name/title for identification + */ + function createDividendWithExclusions( + uint256 _maturity, + uint256 _expiry, + address[] memory _excluded, + bytes32 _name + ) + public + payable + withPerm(ADMIN) + { + uint256 checkpointId = securityToken.createCheckpoint(); + _createDividendWithCheckpointAndExclusions(_maturity, _expiry, checkpointId, _excluded, _name); + } + + /** + * @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 + * @param _name Name/title for identification + */ + function createDividendWithCheckpointAndExclusions( + uint256 _maturity, + uint256 _expiry, + uint256 _checkpointId, + address[] memory _excluded, + bytes32 _name + ) + public + payable + withPerm(ADMIN) + { + _createDividendWithCheckpointAndExclusions(_maturity, _expiry, _checkpointId, _excluded, _name); + } + + /** + * @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 + * @param _name Name/title for identification + */ + function _createDividendWithCheckpointAndExclusions( + uint256 _maturity, + uint256 _expiry, + uint256 _checkpointId, + address[] memory _excluded, + bytes32 _name + ) + internal + { + require(_excluded.length <= EXCLUDED_ADDRESS_LIMIT, "Too many addresses excluded"); + require(_expiry > _maturity, "Expiry is before maturity"); + /*solium-disable-next-line security/no-block-members*/ + require(_expiry > now, "Expiry is in the past"); + require(msg.value > 0, "No dividend sent"); + require(_checkpointId <= securityToken.currentCheckpointId()); + require(_name[0] != bytes32(0)); + uint256 dividendIndex = dividends.length; + uint256 currentSupply = securityToken.totalSupplyAt(_checkpointId); + require(currentSupply > 0, "Invalid supply"); + uint256 excludedSupply = 0; + dividends.push( + Dividend( + _checkpointId, + now, /*solium-disable-line security/no-block-members*/ + _maturity, + _expiry, + msg.value, + 0, + 0, + false, + 0, + 0, + _name + ) + ); + + for (uint256 j = 0; j < _excluded.length; j++) { + require(_excluded[j] != address(0), "Invalid address"); + require(!dividends[dividendIndex].dividendExcluded[_excluded[j]], "duped exclude address"); + excludedSupply = excludedSupply.add(securityToken.balanceOfAt(_excluded[j], _checkpointId)); + dividends[dividendIndex].dividendExcluded[_excluded[j]] = true; + } + require(currentSupply > excludedSupply, "Invalid supply"); + dividends[dividendIndex].totalSupply = currentSupply - excludedSupply; + /*solium-disable-next-line security/no-block-members*/ + emit EtherDividendDeposited(msg.sender, _checkpointId, _maturity, _expiry, msg.value, currentSupply, dividendIndex, _name); + } + + /** + * @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 payable _payee, Dividend storage _dividend, uint256 _dividendIndex) internal { + (uint256 claim, uint256 withheld) = calculateDividend(_dividendIndex, _payee); + _dividend.claimed[_payee] = true; + uint256 claimAfterWithheld = claim.sub(withheld); + /*solium-disable-next-line security/no-send*/ + if (_payee.send(claimAfterWithheld)) { + _dividend.claimedAmount = _dividend.claimedAmount.add(claim); + if (withheld > 0) { + _dividend.totalWithheld = _dividend.totalWithheld.add(withheld); + _dividend.withheld[_payee] = withheld; + } + emit EtherDividendClaimed(_payee, _dividendIndex, claim, withheld); + } else { + _dividend.claimed[_payee] = false; + emit EtherDividendClaimFailed(_payee, _dividendIndex, claim, withheld); + } + } + + /** + * @notice Issuer can reclaim remaining unclaimed dividend amounts, for expired dividends + * @param _dividendIndex Dividend to reclaim + */ + function reclaimDividend(uint256 _dividendIndex) external withPerm(OPERATOR) { + require(_dividendIndex < dividends.length, "Incorrect dividend index"); + /*solium-disable-next-line security/no-block-members*/ + require(now >= dividends[_dividendIndex].expiry, "Dividend expiry is in the future"); + require(!dividends[_dividendIndex].reclaimed, "Dividend is already claimed"); + Dividend storage dividend = dividends[_dividendIndex]; + dividend.reclaimed = true; + uint256 remainingAmount = dividend.amount.sub(dividend.claimedAmount); + address payable wallet = getTreasuryWallet(); + wallet.transfer(remainingAmount); + emit EtherDividendReclaimed(wallet, _dividendIndex, remainingAmount); + } + + /** + * @notice Allows issuer to withdraw withheld tax + * @param _dividendIndex Dividend to withdraw from + */ + function withdrawWithholding(uint256 _dividendIndex) external withPerm(OPERATOR) { + require(_dividendIndex < dividends.length, "Incorrect dividend index"); + Dividend storage dividend = dividends[_dividendIndex]; + uint256 remainingWithheld = dividend.totalWithheld.sub(dividend.totalWithheldWithdrawn); + dividend.totalWithheldWithdrawn = dividend.totalWithheld; + address payable wallet = getTreasuryWallet(); + wallet.transfer(remainingWithheld); + emit EtherDividendWithholdingWithdrawn(wallet, _dividendIndex, remainingWithheld); + } + +} diff --git a/contracts/modules/Checkpoint/Dividend/Ether/EtherDividendCheckpointFactory.sol b/contracts/modules/Checkpoint/Dividend/Ether/EtherDividendCheckpointFactory.sol new file mode 100644 index 000000000..1d3239009 --- /dev/null +++ b/contracts/modules/Checkpoint/Dividend/Ether/EtherDividendCheckpointFactory.sol @@ -0,0 +1,48 @@ +pragma solidity 0.5.8; + +import "./EtherDividendCheckpointProxy.sol"; +import "../../../UpgradableModuleFactory.sol"; + +/** + * @title Factory for deploying EtherDividendCheckpoint module + */ +contract EtherDividendCheckpointFactory is UpgradableModuleFactory { + + /** + * @notice Constructor + * @param _setupCost Setup cost of the module + * @param _logicContract Contract address that contains the logic related to `description` + * @param _polymathRegistry Address of the Polymath registry + * @param _isCostInPoly true = cost in Poly, false = USD + */ + constructor ( + uint256 _setupCost, + address _logicContract, + address _polymathRegistry, + bool _isCostInPoly + ) + public + UpgradableModuleFactory("3.0.0", _setupCost, _logicContract, _polymathRegistry, _isCostInPoly) + { + name = "EtherDividendCheckpoint"; + title = "Ether Dividend Checkpoint"; + description = "Create ETH dividends for token holders at a specific checkpoint"; + typesData.push(4); + tagsData.push("Ether"); + tagsData.push("Dividend"); + tagsData.push("Checkpoint"); + compatibleSTVersionRange["lowerBound"] = VersionUtils.pack(uint8(3), uint8(0), uint8(0)); + compatibleSTVersionRange["upperBound"] = VersionUtils.pack(uint8(3), uint8(0), uint8(0)); + } + + /** + * @notice Used to launch the Module with the help of factory + * @return address Contract address of the Module + */ + function deploy(bytes calldata _data) external returns(address) { + address ethDividendCheckpoint = address(new EtherDividendCheckpointProxy(logicContracts[latestUpgrade].version, msg.sender, polymathRegistry.getAddress("PolyToken"), logicContracts[latestUpgrade].logicContract)); + _initializeModule(ethDividendCheckpoint, _data); + return ethDividendCheckpoint; + } + +} diff --git a/contracts/modules/Checkpoint/Dividend/Ether/EtherDividendCheckpointProxy.sol b/contracts/modules/Checkpoint/Dividend/Ether/EtherDividendCheckpointProxy.sol new file mode 100644 index 000000000..953d6456e --- /dev/null +++ b/contracts/modules/Checkpoint/Dividend/Ether/EtherDividendCheckpointProxy.sol @@ -0,0 +1,31 @@ +pragma solidity 0.5.8; + +import "../../../../proxy/OwnedUpgradeabilityProxy.sol"; +import "../../../../storage/modules/Checkpoint/Dividend/DividendCheckpointStorage.sol"; +import "../../../../Pausable.sol"; +import "../../../../storage/modules/ModuleStorage.sol"; + +/** + * @title Transfer Manager module for core transfer validation functionality + */ +contract EtherDividendCheckpointProxy is DividendCheckpointStorage, ModuleStorage, Pausable, OwnedUpgradeabilityProxy { + /** + * @notice Constructor + * @param _securityToken Address of the security token + * @param _polyAddress Address of the polytoken + * @param _implementation representing the address of the new implementation to be set + */ + constructor ( + string memory _version, + address _securityToken, + address _polyAddress, + address _implementation + ) + public + ModuleStorage(_securityToken, _polyAddress) + { + require(_implementation != address(0), "Implementation address should not be 0x"); + _upgradeTo(_version, _implementation); + } + +} diff --git a/contracts/modules/Checkpoint/ERC20DividendCheckpointFactory.sol b/contracts/modules/Checkpoint/ERC20DividendCheckpointFactory.sol deleted file mode 100644 index e18e54e96..000000000 --- a/contracts/modules/Checkpoint/ERC20DividendCheckpointFactory.sol +++ /dev/null @@ -1,79 +0,0 @@ -pragma solidity ^0.4.24; - -import "../../proxy/ERC20DividendCheckpointProxy.sol"; -import "../../libraries/Util.sol"; -import "../../interfaces/IBoot.sol"; -import "../ModuleFactory.sol"; - -/** - * @title Factory for deploying ERC20DividendCheckpoint module - */ -contract ERC20DividendCheckpointFactory is ModuleFactory { - - address public logicContract; - - /** - * @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 - * @param _logicContract Contract address that contains the logic related to `description` - */ - constructor (address _polyAddress, uint256 _setupCost, uint256 _usageCost, uint256 _subscriptionCost, address _logicContract) public - ModuleFactory(_polyAddress, _setupCost, _usageCost, _subscriptionCost) - { - require(_logicContract != address(0), "Invalid logic contract"); - version = "2.1.1"; - name = "ERC20DividendCheckpoint"; - title = "ERC20 Dividend Checkpoint"; - description = "Create ERC20 dividends for token holders at a specific checkpoint"; - compatibleSTVersionRange["lowerBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0)); - compatibleSTVersionRange["upperBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0)); - logicContract = _logicContract; - } - - /** - * @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), "insufficent allowance"); - address erc20DividendCheckpoint = new ERC20DividendCheckpointProxy(msg.sender, address(polyToken), logicContract); - //Checks that _data is valid (not calling anything it shouldn't) - require(Util.getSig(_data) == IBoot(erc20DividendCheckpoint).getInitFunction(), "Invalid data"); - /*solium-disable-next-line security/no-low-level-calls*/ - require(erc20DividendCheckpoint.call(_data), "Unsuccessfull call"); - /*solium-disable-next-line security/no-block-members*/ - emit GenerateModuleFromFactory(erc20DividendCheckpoint, getName(), address(this), msg.sender, setupCost, now); - return erc20DividendCheckpoint; - } - - /** - * @notice Type of the Module factory - */ - function getTypes() external view returns(uint8[]) { - uint8[] memory res = new uint8[](1); - res[0] = 4; - return res; - } - - /** - * @notice Returns the instructions associated with the module - */ - function getInstructions() external view returns(string) { - return "Create ERC20 dividend to be paid out to token holders based on their balances at dividend creation time"; - } - - /** - * @notice Get the tags related to the module factory - */ - function getTags() external view returns(bytes32[]) { - bytes32[] memory availableTags = new bytes32[](3); - availableTags[0] = "ERC20"; - availableTags[1] = "Dividend"; - availableTags[2] = "Checkpoint"; - return availableTags; - } -} diff --git a/contracts/modules/Checkpoint/EtherDividendCheckpointFactory.sol b/contracts/modules/Checkpoint/EtherDividendCheckpointFactory.sol deleted file mode 100644 index 0d74febf2..000000000 --- a/contracts/modules/Checkpoint/EtherDividendCheckpointFactory.sol +++ /dev/null @@ -1,79 +0,0 @@ -pragma solidity ^0.4.24; - -import "../../proxy/EtherDividendCheckpointProxy.sol"; -import "../../libraries/Util.sol"; -import "../../interfaces/IBoot.sol"; -import "../ModuleFactory.sol"; - -/** - * @title Factory for deploying EtherDividendCheckpoint module - */ -contract EtherDividendCheckpointFactory is ModuleFactory { - - address public logicContract; - - /** - * @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 - * @param _logicContract Contract address that contains the logic related to `description` - */ - constructor (address _polyAddress, uint256 _setupCost, uint256 _usageCost, uint256 _subscriptionCost, address _logicContract) public - ModuleFactory(_polyAddress, _setupCost, _usageCost, _subscriptionCost) - { - require(_logicContract != address(0), "Invalid logic contract"); - version = "2.1.1"; - name = "EtherDividendCheckpoint"; - title = "Ether Dividend Checkpoint"; - description = "Create ETH dividends for token holders at a specific checkpoint"; - compatibleSTVersionRange["lowerBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0)); - compatibleSTVersionRange["upperBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0)); - logicContract = _logicContract; - } - - /** - * @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), "Insufficent allowance or balance"); - address ethDividendCheckpoint = new EtherDividendCheckpointProxy(msg.sender, address(polyToken), logicContract); - //Checks that _data is valid (not calling anything it shouldn't) - require(Util.getSig(_data) == IBoot(ethDividendCheckpoint).getInitFunction(), "Invalid data"); - /*solium-disable-next-line security/no-low-level-calls*/ - require(ethDividendCheckpoint.call(_data), "Unsuccessfull call"); - /*solium-disable-next-line security/no-block-members*/ - emit GenerateModuleFromFactory(ethDividendCheckpoint, getName(), address(this), msg.sender, setupCost, now); - return ethDividendCheckpoint; - } - - /** - * @notice Type of the Module factory - */ - function getTypes() external view returns(uint8[]) { - uint8[] memory res = new uint8[](1); - res[0] = 4; - return res; - } - - /** - * @notice Returns the instructions associated with the module - */ - function getInstructions() external view returns(string) { - return "Create a dividend which will be paid out to token holders proportionally according to their balances at the point the dividend is created"; - } - - /** - * @notice Get the tags related to the module factory - */ - function getTags() external view returns(bytes32[]) { - bytes32[] memory availableTags = new bytes32[](3); - availableTags[0] = "ETH"; - availableTags[1] = "Checkpoint"; - availableTags[2] = "Dividend"; - return availableTags; - } -} diff --git a/contracts/modules/Checkpoint/ICheckpoint.sol b/contracts/modules/Checkpoint/ICheckpoint.sol index f2a7417b2..4f845fa98 100644 --- a/contracts/modules/Checkpoint/ICheckpoint.sol +++ b/contracts/modules/Checkpoint/ICheckpoint.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; /** * @title Interface to be implemented by all checkpoint modules diff --git a/contracts/modules/Checkpoint/Voting/PLCR/PLCRVotingCheckpoint.sol b/contracts/modules/Checkpoint/Voting/PLCR/PLCRVotingCheckpoint.sol new file mode 100644 index 000000000..d17d9cd95 --- /dev/null +++ b/contracts/modules/Checkpoint/Voting/PLCR/PLCRVotingCheckpoint.sol @@ -0,0 +1,395 @@ +pragma solidity 0.5.8; + +import "../VotingCheckpoint.sol"; +import "./PLCRVotingCheckpointStorage.sol"; +import "openzeppelin-solidity/contracts/math/SafeMath.sol"; + +contract PLCRVotingCheckpoint is PLCRVotingCheckpointStorage, VotingCheckpoint { + + using SafeMath for uint256; + + event VoteCommit(address indexed _voter, uint256 _weight, uint256 indexed _ballotId, bytes32 _secretVote); + event VoteRevealed( + address indexed _voter, + uint256 _weight, + uint256 indexed _ballotId, + uint256 _choiceOfProposal, + uint256 _salt, + bytes32 _secretVote + ); + event BallotCreated( + uint256 indexed _ballotId, + uint256 indexed _checkpointId, + uint256 _startTime, + uint256 _commitDuration, + uint256 _revealDuration, + uint256 _noOfProposals, + uint256 _quorumPercentage + ); + event BallotStatusChanged(uint256 indexed _ballotId, bool _newStatus); + event ChangedBallotExemptedVotersList(uint256 indexed _ballotId, address indexed _voter, bool _exempt); + + constructor(address _securityToken, address _polyAddress) + public + Module(_securityToken, _polyAddress) + { + + } + + /** + * @notice Use to create the ballot + * @param _commitDuration Unix time period till the voters commit there vote + * @param _revealDuration Unix time period till the voters reveal there vote starts when commit duration ends + * @param _noOfProposals Total number of proposal used in the ballot. In general it is 2 (For & Against) + * @param _quorumPercentage Minimum number of weight vote percentage requires to win a election. + */ + function createBallot( + uint256 _commitDuration, + uint256 _revealDuration, + uint256 _noOfProposals, + uint256 _quorumPercentage + ) + external + withPerm(ADMIN) + { + uint256 startTime = now; + uint256 checkpointId = securityToken.createCheckpoint(); + _createBallotWithCheckpoint(_commitDuration, _revealDuration, _noOfProposals, _quorumPercentage, checkpointId, startTime); + } + + /** + * @notice Use to create the ballot + * @param _commitDuration Unix time period till the voters commit there vote + * @param _revealDuration Unix time period till the voters reveal there vote starts when commit duration ends + * @param _noOfProposals Total number of proposal used in the ballot. In general it is 2 (For & Against) + * @param _quorumPercentage Minimum number of weight vote percentage requires to win a election. + * @param _checkpointId Valid checkpoint Id + * @param _startTime startTime of the ballot + */ + function createCustomBallot( + uint256 _commitDuration, + uint256 _revealDuration, + uint256 _noOfProposals, + uint256 _quorumPercentage, + uint256 _checkpointId, + uint256 _startTime + ) + external + withPerm(ADMIN) + { + // validate the checkpointId, It should be less than or equal to the current checkpointId of the securityToken + require(_checkpointId <= securityToken.currentCheckpointId(), "Invalid checkpoint Id"); + _createBallotWithCheckpoint(_commitDuration, _revealDuration, _noOfProposals, _quorumPercentage, _checkpointId, _startTime); + } + + function _createBallotWithCheckpoint( + uint256 _commitDuration, + uint256 _revealDuration, + uint256 _noOfProposals, + uint256 _quorumPercentage, + uint256 _checkpointId, + uint256 _startTime + ) + internal + { + // Sanity checks + _isGreaterThanZero(_commitDuration); + _isGreaterThanZero(_revealDuration); + _isGreaterThanZero(_quorumPercentage); + require(_quorumPercentage <= 100 * 10 ** 16, "Invalid quorum percentage"); // not more than 100 % + // Overflow check + require( + uint64(_commitDuration) == _commitDuration && + uint64(_revealDuration) == _revealDuration && + uint64(_startTime) == _startTime && + uint24(_noOfProposals) == _noOfProposals, + "Parameter values get overflowed" + ); + require(_startTime >= now, "Invalid start time"); + // Valid proposal Id range should be 1 to `_noOfProposals`. + require(_noOfProposals > 1, "Invalid number of proposals"); + uint256 _ballotId = ballots.length; + ballots.push(Ballot( + _checkpointId, + _quorumPercentage, + uint64(_commitDuration), + uint64(_revealDuration), + uint64(_startTime), + uint24(_noOfProposals), + uint32(0), + true + )); + emit BallotCreated(_ballotId, _checkpointId, _startTime, _commitDuration, _revealDuration, _noOfProposals, _quorumPercentage); + } + + /** + * @notice Used to commit the vote + * @param _ballotId Given ballot Id + * @param _secretVote It is secret hash value (hashed offchain) + */ + function commitVote(uint256 _ballotId, bytes32 _secretVote) external { + // Check for the ballots array out of bound + _checkIndexOutOfBound(_ballotId); + require(_secretVote != bytes32(0), "Invalid vote"); + // Check for the valid stage. Whether that ballot is in the COMMIT state or not. + _checkValidStage(_ballotId, Stage.COMMIT); + // Check whether the msg.sender is allowed to vote for a given ballotId or not. + require(isVoterAllowed(_ballotId, msg.sender), "Invalid voter"); + // validate the storage values + Ballot storage ballot = ballots[_ballotId]; + require(ballot.investorToProposal[msg.sender].secretVote == bytes32(0), "Already voted"); + require(ballot.isActive, "Inactive ballot"); + // Get the balance of the voter (i.e `msg.sender`) at the checkpoint on which ballot was created. + uint256 weight = securityToken.balanceOfAt(msg.sender, ballot.checkpointId); + require(weight > 0, "Zero weight is not allowed"); + // Update the storage value. Assigned `0` as vote option it will be updated when voter reveals its vote. + ballot.investorToProposal[msg.sender] = Vote(0, _secretVote); + emit VoteCommit(msg.sender, weight, _ballotId, _secretVote); + } + + /** + * @notice Used to reveal the vote + * @param _ballotId Given ballot Id + * @param _choiceOfProposal Proposal chossed by the voter. It varies from (1 to totalProposals) + * @param _salt used salt for hashing (unique for each user) + */ + function revealVote(uint256 _ballotId, uint256 _choiceOfProposal, uint256 _salt) external { + // Check for the ballots array out of bound + _checkIndexOutOfBound(_ballotId); + // Check for the valid stage. Whether that ballot is in the REVEAL state or not. + _checkValidStage(_ballotId, Stage.REVEAL); + Ballot storage ballot = ballots[_ballotId]; + // validate the storage values + require(ballot.isActive, "Inactive ballot"); + require(ballot.investorToProposal[msg.sender].secretVote != bytes32(0), "Secret vote not available"); + require(ballot.totalProposals >= _choiceOfProposal && _choiceOfProposal > 0, "Invalid proposal choice"); + + // validate the secret vote + require( + bytes32(keccak256(abi.encodePacked(_choiceOfProposal, _salt))) == ballot.investorToProposal[msg.sender].secretVote, + "Invalid vote" + ); + // Get the balance of the voter (i.e `msg.sender`) at the checkpoint on which ballot was created. + uint256 weight = securityToken.balanceOfAt(msg.sender, ballot.checkpointId); + bytes32 secretVote = ballot.investorToProposal[msg.sender].secretVote; + // update the storage values + ballot.proposalToVotes[_choiceOfProposal] = ballot.proposalToVotes[_choiceOfProposal].add(weight); + ballot.totalVoters = ballot.totalVoters + 1; + ballot.investorToProposal[msg.sender] = Vote(_choiceOfProposal, bytes32(0)); + emit VoteRevealed(msg.sender, weight, _ballotId, _choiceOfProposal, _salt, secretVote); + } + + /** + * Change the given ballot exempted list + * @param _ballotId Given ballot Id + * @param _voter Address of the voter + * @param _exempt Whether it is exempted or not + */ + function changeBallotExemptedVotersList(uint256 _ballotId, address _voter, bool _exempt) external withPerm(ADMIN) { + _changeBallotExemptedVotersList(_ballotId, _voter, _exempt); + } + + /** + * Change the given ballot exempted list (Multi) + * @param _ballotId Given ballot Id + * @param _voters Address of the voter + * @param _exempts Whether it is exempted or not + */ + function changeBallotExemptedVotersListMulti(uint256 _ballotId, address[] calldata _voters, bool[] calldata _exempts) external withPerm(ADMIN) { + require(_voters.length == _exempts.length, "Array length mismatch"); + for (uint256 i = 0; i < _voters.length; i++) { + _changeBallotExemptedVotersList(_ballotId, _voters[i], _exempts[i]); + } + } + + function _changeBallotExemptedVotersList(uint256 _ballotId, address _voter, bool _exempt) internal { + // Check for the ballots array out of bound + _checkIndexOutOfBound(_ballotId); + require(_voter != address(0), "Invalid address"); + require(ballots[_ballotId].exemptedVoters[_voter] != _exempt, "No change"); + ballots[_ballotId].exemptedVoters[_voter] = _exempt; + emit ChangedBallotExemptedVotersList(_ballotId, _voter, _exempt); + } + + /** + * Use to check whether the voter is allowed to vote or not + * @param _ballotId The index of the target ballot + * @param _voter Address of the voter + * @return bool + */ + function isVoterAllowed(uint256 _ballotId, address _voter) public view returns(bool) { + bool allowed = (ballots[_ballotId].exemptedVoters[_voter] || (defaultExemptIndex[_voter] != 0)); + return !allowed; + } + + /** + * @notice Allows the token issuer to set the active stats of a ballot + * @param _ballotId The index of the target ballot + * @param _isActive The bool value of the active stats of the ballot + */ + function changeBallotStatus(uint256 _ballotId, bool _isActive) external withPerm(ADMIN) { + // Check for the ballots array out of bound + _checkIndexOutOfBound(_ballotId); + require( + now <= uint256(ballots[_ballotId].startTime) + .add(uint256(ballots[_ballotId].commitDuration) + .add(uint256(ballots[_ballotId].revealDuration))), + "Already ended" + ); + require(ballots[_ballotId].isActive != _isActive, "Active state unchanged"); + ballots[_ballotId].isActive = _isActive; + emit BallotStatusChanged(_ballotId, _isActive); + } + + /** + * @notice Used to get the current stage of the ballot + * @param _ballotId Given ballot Id + */ + function getCurrentBallotStage(uint256 _ballotId) public view returns (Stage) { + Ballot memory ballot = ballots[_ballotId]; + uint256 commitTimeEnd = uint256(ballot.startTime).add(uint256(ballot.commitDuration)); + uint256 revealTimeEnd = commitTimeEnd.add(uint256(ballot.revealDuration)); + + if (now < ballot.startTime) + return Stage.PREP; + else if (now >= ballot.startTime && now <= commitTimeEnd) + return Stage.COMMIT; + else if ( now > commitTimeEnd && now <= revealTimeEnd) + return Stage.REVEAL; + else if (now > revealTimeEnd) + return Stage.RESOLVED; + } + + /** + * @notice Queries the result of a given ballot + * @param _ballotId Id of the target ballot + * @return uint256 voteWeighting + * @return uint256 tieWith + * @return uint256 winningProposal + * @return bool isVotingSucceed + * @return uint256 totalVoters + */ + function getBallotResults(uint256 _ballotId) external view returns( + uint256[] memory voteWeighting, + uint256[] memory tieWith, + uint256 winningProposal, + bool isVotingSucceed, + uint256 totalVotes + ) { + if (_ballotId >= ballots.length) + return (new uint256[](0), new uint256[](0), 0, false, 0); + + Ballot storage ballot = ballots[_ballotId]; + uint256 i = 0; + uint256 counter = 0; + uint256 maxWeight = 0; + uint256 supplyAtCheckpoint = securityToken.totalSupplyAt(ballot.checkpointId); + uint256 quorumWeight = (supplyAtCheckpoint.mul(ballot.quorum)).div(10 ** 18); + voteWeighting = new uint256[](ballot.totalProposals); + for (i = 0; i < ballot.totalProposals; i++) { + voteWeighting[i] = ballot.proposalToVotes[i + 1]; + if (maxWeight < ballot.proposalToVotes[i + 1]) { + maxWeight = ballot.proposalToVotes[i + 1]; + if (maxWeight >= quorumWeight) + winningProposal = i + 1; + } + } + if (maxWeight >= quorumWeight) { + isVotingSucceed = true; + for (i = 0; i < ballot.totalProposals; i++) { + if (maxWeight == ballot.proposalToVotes[i + 1] && (i + 1) != winningProposal) + counter ++; + } + } + + tieWith = new uint256[](counter); + if (counter > 0) { + counter = 0; + for (i = 0; i < ballot.totalProposals; i++) { + if (maxWeight == ballot.proposalToVotes[i + 1] && (i + 1) != winningProposal) { + tieWith[counter] = i + 1; + counter ++; + } + } + } + totalVotes = uint256(ballot.totalVoters); + } + + /** + * @notice Get the voted proposal + * @param _ballotId Id of the ballot + * @param _voter Address of the voter + */ + function getSelectedProposal(uint256 _ballotId, address _voter) external view returns(uint256 proposalId) { + if (_ballotId >= ballots.length) + return 0; + return (ballots[_ballotId].investorToProposal[_voter].voteOption); + } + + /** + * @notice Get the details of the ballot + * @param _ballotId The index of the target ballot + * @return uint256 quorum + * @return uint256 totalSupplyAtCheckpoint + * @return uint256 checkpointId + * @return uint256 startTime + * @return uint256 endTime + * @return uint256 totalProposals + * @return uint256 totalVoters + * @return bool isActive + */ + function getBallotDetails(uint256 _ballotId) external view returns(uint256, uint256, uint256, uint256, uint256, uint256, uint256, bool) { + Ballot memory ballot = ballots[_ballotId]; + return ( + ballot.quorum, + securityToken.totalSupplyAt(ballot.checkpointId), + ballot.checkpointId, + ballot.startTime, + (uint256(ballot.startTime).add(uint256(ballot.commitDuration))).add(uint256(ballot.revealDuration)), + ballot.totalProposals, + ballot.totalVoters, + ballot.isActive + ); + } + + /** + * Return the commit relveal time duration of ballot + * @param _ballotId Id of a ballot + */ + function getBallotCommitRevealDuration(uint256 _ballotId) external view returns(uint256, uint256) { + Ballot memory ballot = ballots[_ballotId]; + return ( + ballot.commitDuration, + ballot.revealDuration + ); + } + + /** + * @notice This function returns the signature of configure function + */ + function getInitFunction() external pure returns(bytes4) { + return bytes4(0); + } + + /** + * @notice Return the permissions flag that are associated with CountTransferManager + */ + function getPermissions() external view returns(bytes32[] memory) { + bytes32[] memory allPermissions = new bytes32[](1); + allPermissions[0] = ADMIN; + return allPermissions; + } + + function _isGreaterThanZero(uint256 _value) internal pure { + require(_value > 0, "Invalid value"); + } + + function _checkIndexOutOfBound(uint256 _ballotId) internal view { + require(ballots.length > _ballotId, "Index out of bound"); + } + + function _checkValidStage(uint256 _ballotId, Stage _stage) internal view { + require(getCurrentBallotStage(_ballotId) == _stage, "Not in a valid stage"); + } + +} diff --git a/contracts/modules/Checkpoint/Voting/PLCR/PLCRVotingCheckpointFactory.sol b/contracts/modules/Checkpoint/Voting/PLCR/PLCRVotingCheckpointFactory.sol new file mode 100644 index 000000000..2ce4153ef --- /dev/null +++ b/contracts/modules/Checkpoint/Voting/PLCR/PLCRVotingCheckpointFactory.sol @@ -0,0 +1,49 @@ +pragma solidity 0.5.8; + +import "./PLCRVotingCheckpointProxy.sol"; +import "../../../UpgradableModuleFactory.sol"; + +/** + * @title Factory for deploying PLCRVotingCheckpoint module + */ +contract PLCRVotingCheckpointFactory is UpgradableModuleFactory { + + /** + * @notice Constructor + * @param _setupCost Setup cost of the module + * @param _logicContract Contract address that contains the logic related to `description` + * @param _polymathRegistry Address of the Polymath registry + * @param _isCostInPoly true = cost in Poly, false = USD + */ + constructor ( + uint256 _setupCost, + address _logicContract, + address _polymathRegistry, + bool _isCostInPoly + ) + public + UpgradableModuleFactory("3.0.0", _setupCost, _logicContract, _polymathRegistry, _isCostInPoly) + { + initialVersion = "3.0.0"; + name = "PLCRVotingCheckpoint"; + title = "PLCR Voting Checkpoint"; + description = "Commit & reveal technique used for voting"; + typesData.push(4); + tagsData.push("Vote"); + tagsData.push("Checkpoint"); + tagsData.push("PLCR"); + compatibleSTVersionRange["lowerBound"] = VersionUtils.pack(uint8(3), uint8(0), uint8(0)); + compatibleSTVersionRange["upperBound"] = VersionUtils.pack(uint8(3), uint8(0), uint8(0)); + + } + + /** + * @notice used to launch the Module with the help of factory + * @return address Contract address of the Module + */ + function deploy(bytes calldata _data) external returns(address) { + address plcrVotingCheckpoint = address(new PLCRVotingCheckpointProxy(logicContracts[latestUpgrade].version, msg.sender, polymathRegistry.getAddress("PolyToken"), logicContracts[latestUpgrade].logicContract)); + _initializeModule(plcrVotingCheckpoint, _data); + return plcrVotingCheckpoint; + } +} diff --git a/contracts/modules/Checkpoint/Voting/PLCR/PLCRVotingCheckpointProxy.sol b/contracts/modules/Checkpoint/Voting/PLCR/PLCRVotingCheckpointProxy.sol new file mode 100644 index 000000000..d206b51fc --- /dev/null +++ b/contracts/modules/Checkpoint/Voting/PLCR/PLCRVotingCheckpointProxy.sol @@ -0,0 +1,32 @@ +pragma solidity 0.5.8; + +import "../../../../Pausable.sol"; +import "./PLCRVotingCheckpointStorage.sol"; +import "../../../../storage/modules/ModuleStorage.sol"; +import "../../../../proxy/OwnedUpgradeabilityProxy.sol"; +import "../../../../storage/modules/Checkpoint/Voting/VotingCheckpointStorage.sol"; + +/** + * @title Voting module for governance + */ +contract PLCRVotingCheckpointProxy is PLCRVotingCheckpointStorage, VotingCheckpointStorage, ModuleStorage, Pausable, OwnedUpgradeabilityProxy { + /** + * @notice Constructor + * @param _securityToken Address of the security token + * @param _polyAddress Address of the polytoken + * @param _implementation representing the address of the new implementation to be set + */ + constructor( + string memory _version, + address _securityToken, + address _polyAddress, + address _implementation + ) + public + ModuleStorage(_securityToken, _polyAddress) + { + require(_implementation != address(0), "Implementation address should not be 0x"); + _upgradeTo(_version, _implementation); + } + +} diff --git a/contracts/modules/Checkpoint/Voting/PLCR/PLCRVotingCheckpointStorage.sol b/contracts/modules/Checkpoint/Voting/PLCR/PLCRVotingCheckpointStorage.sol new file mode 100644 index 000000000..a4e217e04 --- /dev/null +++ b/contracts/modules/Checkpoint/Voting/PLCR/PLCRVotingCheckpointStorage.sol @@ -0,0 +1,27 @@ +pragma solidity 0.5.8; + +contract PLCRVotingCheckpointStorage { + + enum Stage { PREP, COMMIT, REVEAL, RESOLVED } + + struct Ballot { + uint256 checkpointId; // Checkpoint At which ballot created + uint256 quorum; // Should be a multiple of 10 ** 16 + uint64 commitDuration; // no. of seconds the commit stage will live + uint64 revealDuration; // no. of seconds the reveal stage will live + uint64 startTime; // Timestamp at which ballot will come into effect + uint24 totalProposals; // Count of proposals allowed for a given ballot + uint32 totalVoters; // Count of voters who vote for the given ballot + bool isActive; // flag used to turn off/on the ballot + mapping(uint256 => uint256) proposalToVotes; // Mapping for proposal to total weight collected by the proposal + mapping(address => Vote) investorToProposal; // mapping for storing vote details of a voter + mapping(address => bool) exemptedVoters; // Mapping for blacklist voters + } + + struct Vote { + uint256 voteOption; + bytes32 secretVote; + } + + Ballot[] ballots; +} diff --git a/contracts/modules/Checkpoint/Voting/Transparent/WeightedVoteCheckpoint.sol b/contracts/modules/Checkpoint/Voting/Transparent/WeightedVoteCheckpoint.sol new file mode 100644 index 000000000..cfe4edaa1 --- /dev/null +++ b/contracts/modules/Checkpoint/Voting/Transparent/WeightedVoteCheckpoint.sol @@ -0,0 +1,289 @@ +pragma solidity 0.5.8; + +import "../VotingCheckpoint.sol"; +import "./WeightedVoteCheckpointStorage.sol"; +import "openzeppelin-solidity/contracts/math/SafeMath.sol"; + +/** + * @title Checkpoint module for token weighted vote + * @notice This voting system uses public votes + * @notice In this module every token holder has voting right (Should be greater than zero) + * Tally will be calculated as per the weight (balance of the token holder) + */ +contract WeightedVoteCheckpoint is WeightedVoteCheckpointStorage, VotingCheckpoint { + using SafeMath for uint256; + + event BallotCreated( + uint256 indexed _ballotId, + uint256 indexed _checkpointId, + uint256 _startTime, + uint256 _endTime, + uint256 _noOfProposals, + uint256 _quorumPercentage + ); + event VoteCast(address indexed _voter, uint256 _weight, uint256 indexed _ballotId, uint256 indexed _proposalId); + event BallotStatusChanged(uint256 indexed _ballotId, bool _isActive); + event ChangedBallotExemptedVotersList(uint256 indexed _ballotId, address indexed _voter, bool _exempt); + + /** + * @notice Constructor + * @param _securityToken Address of the security token + * @param _polyToken Address of the polytoken + */ + constructor(address _securityToken, address _polyToken) + public + Module(_securityToken, _polyToken) + { + + } + + /** + * @notice This function returns the signature of configure function + */ + function getInitFunction() external pure returns(bytes4) { + return bytes4(0); + } + + /** + * @notice Allows the token issuer to create a ballot + * @param _duration The duration of the voting period in seconds + * @param _noOfProposals Number of proposals + * @param _quorumPercentage Minimum Quorum percentage required to make a proposal won + */ + function createBallot(uint256 _duration, uint256 _noOfProposals, uint256 _quorumPercentage) external withPerm(ADMIN) { + require(_duration > 0, "Incorrect ballot duration"); + uint256 checkpointId = securityToken.createCheckpoint(); + uint256 endTime = now.add(_duration); + _createCustomBallot(checkpointId, _quorumPercentage, now, endTime, _noOfProposals); + } + + function _createCustomBallot( + uint256 _checkpointId, + uint256 _quorumPercentage, + uint256 _startTime, + uint256 _endTime, + uint256 _noOfProposals + ) + internal + { + require(_noOfProposals > 1, "Incorrect proposals no"); + require(_endTime > _startTime, "Times are not valid"); + require(_quorumPercentage <= 100 * 10 ** 16 && _quorumPercentage > 0, "Invalid quorum percentage"); // not more than 100 % + require( + uint64(_startTime) == _startTime && + uint64(_endTime) == _endTime && + uint64(_noOfProposals) == _noOfProposals, + "values get overflowed" + ); + uint256 ballotId = ballots.length; + ballots.push( + Ballot( + _checkpointId, _quorumPercentage, uint64(_startTime), uint64(_endTime), uint64(_noOfProposals), uint56(0), true + ) + ); + emit BallotCreated(ballotId, _checkpointId, _startTime, _endTime, _noOfProposals, _quorumPercentage); + } + + /** + * @notice Allows the token issuer to create a ballot with custom settings + * @param _checkpointId Index of the checkpoint to use for token balances + * @param _quorumPercentage Minimum Quorum percentage required to make a proposal won + * @param _startTime Start time of the voting period in Unix Epoch time + * @param _endTime End time of the voting period in Unix Epoch time + * @param _noOfProposals Number of proposals + */ + function createCustomBallot(uint256 _checkpointId, uint256 _quorumPercentage, uint256 _startTime, uint256 _endTime, uint256 _noOfProposals) external withPerm(ADMIN) { + require(_checkpointId <= securityToken.currentCheckpointId(), "Invalid checkpoint Id"); + require(_startTime >= now, "Invalid startTime"); + _createCustomBallot(_checkpointId, _quorumPercentage, _startTime, _endTime, _noOfProposals); + } + + /** + * @notice Allows a token holder to cast their vote on a specific ballot + * @param _ballotId The index of the target ballot + * @param _proposalId Id of the proposal which investor want to vote for proposal + */ + function castVote(uint256 _ballotId, uint256 _proposalId) external { + // Check for the ballots array out of bound + _checkIndexOutOfBound(_ballotId); + // Check whether the msg.sender is allowed to vote for a given ballotId or not. + require(isVoterAllowed(_ballotId, msg.sender), "Invalid voter"); + Ballot storage ballot = ballots[_ballotId]; + // Get the balance of the voter (i.e `msg.sender`) at the checkpoint on which ballot was created. + uint256 weight = securityToken.balanceOfAt(msg.sender, ballot.checkpointId); + require(weight > 0, "weight should be > 0"); + // Validate the storage values + require(ballot.totalProposals >= _proposalId && _proposalId > 0, "Incorrect proposals Id"); + require(now >= ballot.startTime && now <= ballot.endTime, "Voting period is not active"); + require(ballot.investorToProposal[msg.sender] == 0, "Token holder has already voted"); + require(ballot.isActive, "Ballot is not active"); + // Update the storage + ballot.investorToProposal[msg.sender] = _proposalId; + ballot.totalVoters = ballot.totalVoters + 1; + ballot.proposalToVotes[_proposalId] = ballot.proposalToVotes[_proposalId].add(weight); + emit VoteCast(msg.sender, weight, _ballotId, _proposalId); + } + + /** + * Change the given ballot exempted list + * @param _ballotId Given ballot Id + * @param _voter Address of the voter + * @param _exempt Whether it is exempted or not + */ + function changeBallotExemptedVotersList(uint256 _ballotId, address _voter, bool _exempt) external withPerm(ADMIN) { + _changeBallotExemptedVotersList(_ballotId, _voter, _exempt); + } + + /** + * Change the given ballot exempted list (Multi) + * @param _ballotId Given ballot Id + * @param _voters Address of the voter + * @param _exempts Whether it is exempted or not + */ + function changeBallotExemptedVotersListMulti(uint256 _ballotId, address[] calldata _voters, bool[] calldata _exempts) external withPerm(ADMIN) { + require(_voters.length == _exempts.length, "Array length mismatch"); + for (uint256 i = 0; i < _voters.length; i++) { + _changeBallotExemptedVotersList(_ballotId, _voters[i], _exempts[i]); + } + } + + function _changeBallotExemptedVotersList(uint256 _ballotId, address _voter, bool _exempt) internal { + // Check for the ballots array out of bound + _checkIndexOutOfBound(_ballotId); + require(_voter != address(0), "Invalid address"); + require(ballots[_ballotId].exemptedVoters[_voter] != _exempt, "No change"); + ballots[_ballotId].exemptedVoters[_voter] = _exempt; + emit ChangedBallotExemptedVotersList(_ballotId, _voter, _exempt); + } + + /** + * Use to check whether the voter is allowed to vote or not + * @param _ballotId The index of the target ballot + * @param _voter Address of the voter + * @return bool + */ + function isVoterAllowed(uint256 _ballotId, address _voter) public view returns(bool) { + bool allowed = (ballots[_ballotId].exemptedVoters[_voter] || (defaultExemptIndex[_voter] != 0)); + return !allowed; + } + + /** + * @notice Allows the token issuer to set the active stats of a ballot + * @param _ballotId The index of the target ballot + * @param _isActive The bool value of the active stats of the ballot + * @return bool success + */ + function changeBallotStatus(uint256 _ballotId, bool _isActive) external withPerm(ADMIN) { + require(uint64(now) <= ballots[_ballotId].endTime, "Already ended"); + require(ballots[_ballotId].isActive != _isActive, "Active state unchanged"); + ballots[_ballotId].isActive = _isActive; + emit BallotStatusChanged(_ballotId, _isActive); + } + + /** + * @notice Queries the result of a given ballot + * @param _ballotId Id of the target ballot + * @return uint256 voteWeighting + * @return uint256 tieWith + * @return uint256 winningProposal + * @return bool isVotingSucceed + * @return uint256 totalVotes + */ + function getBallotResults(uint256 _ballotId) external view returns ( + uint256[] memory voteWeighting, + uint256[] memory tieWith, + uint256 winningProposal, + bool isVotingSucceed, + uint256 totalVotes + ) { + if (_ballotId >= ballots.length) + return (new uint256[](0), new uint256[](0), winningProposal, isVotingSucceed, totalVotes); + + Ballot storage ballot = ballots[_ballotId]; + uint256 i; + uint256 counter = 0; + uint256 maxWeight = 0; + uint256 supplyAtCheckpoint = securityToken.totalSupplyAt(ballot.checkpointId); + uint256 quorumWeight = (supplyAtCheckpoint.mul(ballot.quorum)).div(10 ** 18); + voteWeighting = new uint256[](ballot.totalProposals); + for (i = 0; i < ballot.totalProposals; i++) { + voteWeighting[i] = ballot.proposalToVotes[i + 1]; + if (maxWeight < ballot.proposalToVotes[i + 1]) { + maxWeight = ballot.proposalToVotes[i + 1]; + if (maxWeight >= quorumWeight) + winningProposal = i + 1; + } + } + if (maxWeight >= quorumWeight) { + isVotingSucceed = true; + for (i = 0; i < ballot.totalProposals; i++) { + if (maxWeight == ballot.proposalToVotes[i + 1] && (i + 1) != winningProposal) + counter ++; + } + } + + tieWith = new uint256[](counter); + if (counter > 0) { + counter = 0; + for (i = 0; i < ballot.totalProposals; i++) { + if (maxWeight == ballot.proposalToVotes[i + 1] && (i + 1) != winningProposal) { + tieWith[counter] = i + 1; + counter ++; + } + } + } + totalVotes = uint256(ballot.totalVoters); + } + + /** + * @notice Get the voted proposal + * @param _ballotId Id of the ballot + * @param _voter Address of the voter + */ + function getSelectedProposal(uint256 _ballotId, address _voter) external view returns(uint256 proposalId) { + if (_ballotId >= ballots.length) + return 0; + return ballots[_ballotId].investorToProposal[_voter]; + } + + /** + * @notice Get the details of the ballot + * @param _ballotId The index of the target ballot + * @return uint256 quorum + * @return uint256 totalSupplyAtCheckpoint + * @return uint256 checkpointId + * @return uint256 startTime + * @return uint256 endTime + * @return uint256 totalProposals + * @return uint256 totalVoters + * @return bool isActive + */ + function getBallotDetails(uint256 _ballotId) external view returns(uint256, uint256, uint256, uint256, uint256, uint256, uint256, bool) { + Ballot memory ballot = ballots[_ballotId]; + return ( + ballot.quorum, + securityToken.totalSupplyAt(ballot.checkpointId), + ballot.checkpointId, + ballot.startTime, + ballot.endTime, + ballot.totalProposals, + ballot.totalVoters, + ballot.isActive + ); + } + + /** + * @notice Return the permissions flag that are associated with STO + * @return bytes32 array + */ + function getPermissions() public view returns(bytes32[] memory) { + bytes32[] memory allPermissions = new bytes32[](1); + allPermissions[0] = ADMIN; + return allPermissions; + } + + function _checkIndexOutOfBound(uint256 _ballotId) internal view { + require(ballots.length > _ballotId, "Index out of bound"); + } + +} diff --git a/contracts/modules/Checkpoint/Voting/Transparent/WeightedVoteCheckpointFactory.sol b/contracts/modules/Checkpoint/Voting/Transparent/WeightedVoteCheckpointFactory.sol new file mode 100644 index 000000000..54f8440f0 --- /dev/null +++ b/contracts/modules/Checkpoint/Voting/Transparent/WeightedVoteCheckpointFactory.sol @@ -0,0 +1,48 @@ +pragma solidity 0.5.8; + +import "./WeightedVoteCheckpointProxy.sol"; +import "../../../UpgradableModuleFactory.sol"; + +/** + * @title Factory for deploying WeightedVoteCheckpoint module + */ +contract WeightedVoteCheckpointFactory is UpgradableModuleFactory { + + /** + * @notice Constructor + * @param _setupCost Setup cost of the module + * @param _logicContract Contract address that contains the logic related to `description` + * @param _polymathRegistry Address of the Polymath registry + * @param _isCostInPoly true = cost in Poly, false = USD + */ + constructor ( + uint256 _setupCost, + address _logicContract, + address _polymathRegistry, + bool _isCostInPoly + ) + public + UpgradableModuleFactory("3.0.0", _setupCost, _logicContract, _polymathRegistry, _isCostInPoly) + { + initialVersion = "3.0.0"; + name = "WeightedVoteCheckpoint"; + title = "Weighted Vote Checkpoint"; + description = "Weighted votes based on token amount"; + typesData.push(4); + tagsData.push("Vote"); + tagsData.push("Transparent"); + tagsData.push("Checkpoint"); + compatibleSTVersionRange["lowerBound"] = VersionUtils.pack(uint8(3), uint8(0), uint8(0)); + compatibleSTVersionRange["upperBound"] = VersionUtils.pack(uint8(3), uint8(0), uint8(0)); + } + + /** + * @notice used to launch the Module with the help of factory + * @return address Contract address of the Module + */ + function deploy(bytes calldata _data) external returns(address) { + address weightedVoteCheckpoint = address(new WeightedVoteCheckpointProxy(logicContracts[latestUpgrade].version, msg.sender, polymathRegistry.getAddress("PolyToken"), logicContracts[latestUpgrade].logicContract)); + _initializeModule(weightedVoteCheckpoint, _data); + return weightedVoteCheckpoint; + } +} diff --git a/contracts/modules/Checkpoint/Voting/Transparent/WeightedVoteCheckpointProxy.sol b/contracts/modules/Checkpoint/Voting/Transparent/WeightedVoteCheckpointProxy.sol new file mode 100644 index 000000000..da71404ed --- /dev/null +++ b/contracts/modules/Checkpoint/Voting/Transparent/WeightedVoteCheckpointProxy.sol @@ -0,0 +1,32 @@ +pragma solidity 0.5.8; + +import "../../../../Pausable.sol"; +import "./WeightedVoteCheckpointStorage.sol"; +import "../../../../storage/modules/ModuleStorage.sol"; +import "../../../../proxy/OwnedUpgradeabilityProxy.sol"; +import "../../../../storage/modules/Checkpoint/Voting/VotingCheckpointStorage.sol"; + +/** + * @title Voting module for governance + */ +contract WeightedVoteCheckpointProxy is WeightedVoteCheckpointStorage, VotingCheckpointStorage, ModuleStorage, Pausable, OwnedUpgradeabilityProxy { + /** + * @notice Constructor + * @param _securityToken Address of the security token + * @param _polyAddress Address of the polytoken + * @param _implementation representing the address of the new implementation to be set + */ + constructor( + string memory _version, + address _securityToken, + address _polyAddress, + address _implementation + ) + public + ModuleStorage(_securityToken, _polyAddress) + { + require(_implementation != address(0), "Implementation address should not be 0x"); + _upgradeTo(_version, _implementation); + } + +} diff --git a/contracts/modules/Checkpoint/Voting/Transparent/WeightedVoteCheckpointStorage.sol b/contracts/modules/Checkpoint/Voting/Transparent/WeightedVoteCheckpointStorage.sol new file mode 100644 index 000000000..fec3cf97f --- /dev/null +++ b/contracts/modules/Checkpoint/Voting/Transparent/WeightedVoteCheckpointStorage.sol @@ -0,0 +1,19 @@ +pragma solidity 0.5.8; + +contract WeightedVoteCheckpointStorage { + + struct Ballot { + uint256 checkpointId; // Checkpoint At which ballot created + uint256 quorum; // Should be a multiple of 10 ** 16 + uint64 startTime; // Timestamp at which ballot will come into effect + uint64 endTime; // Timestamp at which ballot will no more into effect + uint64 totalProposals; // Count of proposals allowed for a given ballot + uint56 totalVoters; // Count of voters who vote for the given ballot + bool isActive; // flag used to turn off/on the ballot + mapping(uint256 => uint256) proposalToVotes; // Mapping for proposal to total weight collected by the proposal + mapping(address => uint256) investorToProposal; // mapping for storing vote details of a voter + mapping(address => bool) exemptedVoters; // Mapping for blacklist voters + } + + Ballot[] ballots; +} diff --git a/contracts/modules/Checkpoint/Voting/VotingCheckpoint.sol b/contracts/modules/Checkpoint/Voting/VotingCheckpoint.sol new file mode 100644 index 000000000..47013b4fe --- /dev/null +++ b/contracts/modules/Checkpoint/Voting/VotingCheckpoint.sol @@ -0,0 +1,56 @@ +pragma solidity 0.5.8; + +import "../../../interfaces/IVoting.sol"; +import "../../Module.sol"; +import ".././ICheckpoint.sol"; +import "../../../storage/modules/Checkpoint/Voting/VotingCheckpointStorage.sol"; + +contract VotingCheckpoint is VotingCheckpointStorage, ICheckpoint, IVoting, Module { + + event ChangedDefaultExemptedVotersList(address indexed _voter, bool _exempt); + + /** + * Change the global exempted voters list + * @param _voter Address of the voter + * @param _exempt Whether it is exempted or not + */ + function changeDefaultExemptedVotersList(address _voter, bool _exempt) external withPerm(ADMIN) { + _changeDefaultExemptedVotersList(_voter, _exempt); + } + + /** + * Change the global exempted voters list + * @param _voters Address of the voter + * @param _exempts Whether it is exempted or not + */ + function changeDefaultExemptedVotersListMulti(address[] calldata _voters, bool[] calldata _exempts) external withPerm(ADMIN) { + require(_voters.length == _exempts.length, "Array length mismatch"); + for (uint256 i = 0; i < _voters.length; i++) { + _changeDefaultExemptedVotersList(_voters[i], _exempts[i]); + } + } + + function _changeDefaultExemptedVotersList(address _voter, bool _exempt) internal { + require(_voter != address(0), "Invalid address"); + require((defaultExemptIndex[_voter] == 0) == _exempt); + if (_exempt) { + defaultExemptedVoters.push(_voter); + defaultExemptIndex[_voter] = defaultExemptedVoters.length; + } else { + if (defaultExemptedVoters.length != defaultExemptIndex[_voter]) { + defaultExemptedVoters[defaultExemptIndex[_voter] - 1] = defaultExemptedVoters[defaultExemptedVoters.length - 1]; + defaultExemptIndex[defaultExemptedVoters[defaultExemptIndex[_voter] - 1]] = defaultExemptIndex[_voter]; + } + delete defaultExemptIndex[_voter]; + defaultExemptedVoters.length --; + } + emit ChangedDefaultExemptedVotersList(_voter, _exempt); + } + + /** + * Return the default exemption list + */ + function getDefaultExemptionVotersList() external view returns(address[] memory) { + return defaultExemptedVoters; + } +} diff --git a/contracts/modules/Experimental/Burn/TrackedRedemption.sol b/contracts/modules/Experimental/Burn/TrackedRedemption.sol index bc06a99e9..8e97fd6b2 100644 --- a/contracts/modules/Experimental/Burn/TrackedRedemption.sol +++ b/contracts/modules/Experimental/Burn/TrackedRedemption.sol @@ -1,8 +1,7 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; import "../../Burn/IBurn.sol"; import "../../Module.sol"; -import "../../../interfaces/ISecurityToken.sol"; import "openzeppelin-solidity/contracts/math/SafeMath.sol"; /** @@ -11,24 +10,22 @@ import "openzeppelin-solidity/contracts/math/SafeMath.sol"; contract TrackedRedemption is IBurn, Module { using SafeMath for uint256; - mapping (address => uint256) redeemedTokens; + mapping(address => uint256) redeemedTokens; - event Redeemed(address _investor, uint256 _value, uint256 _timestamp); + event Redeemed(address _investor, uint256 _value); /** * @notice Constructor * @param _securityToken Address of the security token - * @param _polyAddress Address of the polytoken */ - constructor (address _securityToken, address _polyAddress) public - Module(_securityToken, _polyAddress) - { + constructor(address _securityToken, address _polyToken) public Module(_securityToken, _polyToken) { + } /** * @notice This function returns the signature of configure function */ - function getInitFunction() public pure returns (bytes4) { + function getInitFunction() public pure returns(bytes4) { return bytes4(0); } @@ -37,16 +34,16 @@ contract TrackedRedemption is IBurn, Module { * @param _value The number of tokens to redeem */ function redeemTokens(uint256 _value) public { - ISecurityToken(securityToken).burnFromWithData(msg.sender, _value, ""); + securityToken.redeemFrom(msg.sender, _value, ""); redeemedTokens[msg.sender] = redeemedTokens[msg.sender].add(_value); /*solium-disable-next-line security/no-block-members*/ - emit Redeemed(msg.sender, _value, now); + emit Redeemed(msg.sender, _value); } /** * @notice Returns the permissions flag that are associated with CountTransferManager */ - function getPermissions() public view returns(bytes32[]) { + function getPermissions() public view returns(bytes32[] memory) { bytes32[] memory allPermissions = new bytes32[](0); return allPermissions; } diff --git a/contracts/modules/Experimental/Burn/TrackedRedemptionFactory.sol b/contracts/modules/Experimental/Burn/TrackedRedemptionFactory.sol index d1fa010b7..c31459288 100644 --- a/contracts/modules/Experimental/Burn/TrackedRedemptionFactory.sol +++ b/contracts/modules/Experimental/Burn/TrackedRedemptionFactory.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; import "./TrackedRedemption.sol"; import "../../ModuleFactory.sol"; @@ -7,62 +7,43 @@ import "../../ModuleFactory.sol"; * @title Factory for deploying GeneralTransferManager module */ contract TrackedRedemptionFactory is ModuleFactory { - /** * @notice Constructor - * @param _polyAddress Address of the polytoken * @param _setupCost Setup cost of module - * @param _usageCost Usage cost of module - * @param _subscriptionCost Monthly cost of module + * @param _polymathRegistry Address of the Polymath registry + * @param _isCostInPoly true = cost in Poly, false = USD */ - constructor (address _polyAddress, uint256 _setupCost, uint256 _usageCost, uint256 _subscriptionCost) public - ModuleFactory(_polyAddress, _setupCost, _usageCost, _subscriptionCost) + constructor( + uint256 _setupCost, + address _polymathRegistry, + bool _isCostInPoly + ) + public ModuleFactory(_setupCost, _polymathRegistry, _isCostInPoly) { - version = "1.0.0"; + initialVersion = "3.0.0"; name = "TrackedRedemption"; title = "Tracked Redemption"; description = "Track token redemptions"; - compatibleSTVersionRange["lowerBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0)); - compatibleSTVersionRange["upperBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0)); + typesData.push(5); + tagsData.push("Tracked"); + tagsData.push("Redemption"); + compatibleSTVersionRange["lowerBound"] = VersionUtils.pack(uint8(3), uint8(0), uint8(0)); + compatibleSTVersionRange["upperBound"] = VersionUtils.pack(uint8(3), uint8(0), uint8(0)); } /** * @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), "Insufficent allowance or balance"); - address trackedRedemption = new TrackedRedemption(msg.sender, address(polyToken)); - /*solium-disable-next-line security/no-block-members*/ - emit GenerateModuleFromFactory(address(trackedRedemption), getName(), address(this), msg.sender, setupCost, now); - return address(trackedRedemption); - } - - /** - * @notice Type of the Module factory - */ - function getTypes() external view returns(uint8[]) { - uint8[] memory res = new uint8[](1); - res[0] = 5; - return res; - } - - /** - * @notice Returns the instructions associated with the module - */ - function getInstructions() external view returns(string) { - return "Allows an investor to redeem security tokens which are tracked by this module"; - } - - /** - * @notice Get the tags related to the module factory - */ - function getTags() external view returns(bytes32[]) { - bytes32[] memory availableTags = new bytes32[](2); - availableTags[0] = "Redemption"; - availableTags[1] = "Tracked"; - return availableTags; + function deploy( + bytes calldata _data + ) + external + returns(address) + { + address trackedRedemption = address(new TrackedRedemption(msg.sender, polymathRegistry.getAddress("PolyToken"))); + _initializeModule(trackedRedemption, _data); + return trackedRedemption; } } diff --git a/contracts/modules/Experimental/Mixed/ScheduledCheckpoint.sol b/contracts/modules/Experimental/Mixed/ScheduledCheckpoint.sol index 097a44f50..3eeffb49c 100644 --- a/contracts/modules/Experimental/Mixed/ScheduledCheckpoint.sol +++ b/contracts/modules/Experimental/Mixed/ScheduledCheckpoint.sol @@ -1,48 +1,50 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; import "./../../Checkpoint/ICheckpoint.sol"; -import "../../TransferManager/ITransferManager.sol"; -import "../../../interfaces/ISecurityToken.sol"; +import "../../TransferManager/TransferManager.sol"; import "openzeppelin-solidity/contracts/math/SafeMath.sol"; +import "../../../libraries/BokkyPooBahsDateTimeLibrary.sol"; /** * @title Burn module for burning tokens and keeping track of burnt amounts */ -contract ScheduledCheckpoint is ICheckpoint, ITransferManager { +contract ScheduledCheckpoint is ICheckpoint, TransferManager { using SafeMath for uint256; + enum TimeUnit {SECONDS, DAYS, WEEKS, MONTHS, YEARS} + struct Schedule { bytes32 name; uint256 startTime; uint256 nextTime; uint256 interval; + TimeUnit timeUnit; uint256 index; uint256[] checkpointIds; uint256[] timestamps; uint256[] periods; + uint256 totalPeriods; } bytes32[] public names; - mapping (bytes32 => Schedule) public schedules; + mapping(bytes32 => Schedule) public schedules; - event AddSchedule(bytes32 _name, uint256 _startTime, uint256 _interval, uint256 _timestamp); - event RemoveSchedule(bytes32 _name, uint256 _timestamp); + event AddSchedule(bytes32 _name, uint256 _startTime, uint256 _interval, TimeUnit _timeUint); + event RemoveSchedule(bytes32 _name); /** * @notice Constructor * @param _securityToken Address of the security token - * @param _polyAddress Address of the polytoken */ - constructor (address _securityToken, address _polyAddress) public - Module(_securityToken, _polyAddress) - { + constructor(address _securityToken, address _polyToken) public Module(_securityToken, _polyToken) { + } /** * @notice This function returns the signature of configure function */ - function getInitFunction() public pure returns (bytes4) { + function getInitFunction() public pure returns(bytes4) { return bytes4(0); } @@ -51,24 +53,30 @@ contract ScheduledCheckpoint is ICheckpoint, ITransferManager { * @param _name name of the new schedule (must be unused) * @param _startTime start time of the schedule (first checkpoint) * @param _interval interval at which checkpoints should be created + * @param _timeUnit unit of time at which checkpoints should be created */ - function addSchedule(bytes32 _name, uint256 _startTime, uint256 _interval) external onlyOwner { + function addSchedule(bytes32 _name, uint256 _startTime, uint256 _interval, TimeUnit _timeUnit) external { + _onlySecurityTokenOwner(); + require(_name != bytes32(""), "Empty name"); require(_startTime > now, "Start time must be in the future"); require(schedules[_name].name == bytes32(0), "Name already in use"); schedules[_name].name = _name; schedules[_name].startTime = _startTime; schedules[_name].nextTime = _startTime; schedules[_name].interval = _interval; + schedules[_name].timeUnit = _timeUnit; schedules[_name].index = names.length; names.push(_name); - emit AddSchedule(_name, _startTime, _interval, now); + emit AddSchedule(_name, _startTime, _interval, _timeUnit); } /** * @notice removes a schedule for checkpoints * @param _name name of the schedule to be removed */ - function removeSchedule(bytes32 _name) external onlyOwner { + function removeSchedule(bytes32 _name) external { + _onlySecurityTokenOwner(); + require(_name != bytes32(""), "Empty name"); require(schedules[_name].name == _name, "Name does not exist"); uint256 index = schedules[_name].index; names[index] = names[names.length - 1]; @@ -77,56 +85,99 @@ contract ScheduledCheckpoint is ICheckpoint, ITransferManager { schedules[names[index]].index = index; } delete schedules[_name]; - emit RemoveSchedule(_name, now); + emit RemoveSchedule(_name); } - /** * @notice Used to create checkpoints that correctly reflect balances - * @param _isTransfer whether or not an actual transfer is occuring * @return always returns Result.NA */ - function verifyTransfer(address /* _from */, address /* _to */, uint256 /* _amount */, bytes /* _data */, bool _isTransfer) public returns(Result) { - require(_isTransfer == false || msg.sender == securityToken, "Sender is not owner"); - if (paused || !_isTransfer) { - return Result.NA; + function executeTransfer( + address, /* _from */ + address, /* _to */ + uint256, /* _amount */ + bytes calldata /* _data */ + ) + external + onlySecurityToken + returns(Result) + { + if (!paused) { + _updateAll(); } - _updateAll(); - return Result.NA; + return (Result.NA); + } + + /** + * @notice Used to create checkpoints that correctly reflect balances + * @return always returns Result.NA + */ + function verifyTransfer( + address, /* _from */ + address, /* _to */ + uint256, /* _amount */ + bytes memory /* _data */ + ) + public + view + returns(Result, bytes32) + { + return (Result.NA, bytes32(0)); } /** * @notice gets schedule details * @param _name name of the schedule */ - function getSchedule(bytes32 _name) view external returns(bytes32, uint256, uint256, uint256, uint256[], uint256[], uint256[]) { - return ( - schedules[_name].name, - schedules[_name].startTime, - schedules[_name].nextTime, - schedules[_name].interval, - schedules[_name].checkpointIds, - schedules[_name].timestamps, - schedules[_name].periods - ); + function getSchedule(bytes32 _name) external view returns( + bytes32, + uint256, + uint256, + uint256, + TimeUnit, + uint256[] memory, + uint256[] memory, + uint256[] memory, + uint256 + ){ + Schedule storage schedule = schedules[_name]; + return (schedule.name, schedule.startTime, schedule.nextTime, schedule.interval, schedule.timeUnit, schedule.checkpointIds, schedule.timestamps, schedule.periods, schedule.totalPeriods); } /** * @notice manually triggers update outside of transfer request for named schedule (can be used to reduce user gas costs) * @param _name name of the schedule */ - function update(bytes32 _name) external onlyOwner { + function update(bytes32 _name) external { + _onlySecurityTokenOwner(); _update(_name); } function _update(bytes32 _name) internal { Schedule storage schedule = schedules[_name]; if (schedule.nextTime <= now) { - uint256 checkpointId = ISecurityToken(securityToken).createCheckpoint(); - uint256 periods = now.sub(schedule.nextTime).div(schedule.interval).add(1); - schedule.timestamps.push(schedule.nextTime); - schedule.nextTime = periods.mul(schedule.interval).add(schedule.nextTime); + uint256 checkpointId = securityToken.createCheckpoint(); schedule.checkpointIds.push(checkpointId); + schedule.timestamps.push(schedule.nextTime); + uint256 periods; + if (schedule.timeUnit == TimeUnit.SECONDS ) { + periods = now.sub(schedule.nextTime).div(schedule.interval).add(1); + schedule.nextTime = periods.mul(schedule.interval).add(schedule.nextTime); + } else if (schedule.timeUnit == TimeUnit.DAYS ) { + periods = BokkyPooBahsDateTimeLibrary.diffDays(schedule.nextTime, now).div(schedule.interval).add(1); + schedule.nextTime = BokkyPooBahsDateTimeLibrary.addDays(schedule.nextTime, periods.mul(schedule.interval)); + } else if (schedule.timeUnit == TimeUnit.WEEKS ) { + periods = BokkyPooBahsDateTimeLibrary.diffDays(schedule.nextTime, now).div(7).div(schedule.interval).add(1); + schedule.nextTime = BokkyPooBahsDateTimeLibrary.addDays(schedule.nextTime, periods.mul(schedule.interval).mul(7)); + } else if (schedule.timeUnit == TimeUnit.MONTHS ) { + periods = BokkyPooBahsDateTimeLibrary.diffMonths(schedule.nextTime, now).div(schedule.interval).add(1); + uint256 totalPeriods = schedule.totalPeriods.add(periods); + schedule.nextTime = BokkyPooBahsDateTimeLibrary.addMonths(schedule.startTime, totalPeriods.mul(schedule.interval)); + } else if (schedule.timeUnit == TimeUnit.YEARS ) { + periods = BokkyPooBahsDateTimeLibrary.diffYears(schedule.nextTime, now).div(schedule.interval).add(1); + schedule.nextTime = BokkyPooBahsDateTimeLibrary.addYears(schedule.nextTime, periods.mul(schedule.interval)); + } + schedule.totalPeriods = schedule.totalPeriods.add(periods); schedule.periods.push(periods); } } @@ -134,7 +185,8 @@ contract ScheduledCheckpoint is ICheckpoint, ITransferManager { /** * @notice manually triggers update outside of transfer request for all schedules (can be used to reduce user gas costs) */ - function updateAll() onlyOwner external { + function updateAll() external { + _onlySecurityTokenOwner(); _updateAll(); } @@ -148,7 +200,7 @@ contract ScheduledCheckpoint is ICheckpoint, ITransferManager { /** * @notice Return the permissions flag that are associated with CountTransferManager */ - function getPermissions() view external returns(bytes32[]) { + function getPermissions() external view returns(bytes32[] memory) { bytes32[] memory allPermissions = new bytes32[](0); return allPermissions; } diff --git a/contracts/modules/Experimental/Mixed/ScheduledCheckpointFactory.sol b/contracts/modules/Experimental/Mixed/ScheduledCheckpointFactory.sol index 1b5daac29..343641fba 100644 --- a/contracts/modules/Experimental/Mixed/ScheduledCheckpointFactory.sol +++ b/contracts/modules/Experimental/Mixed/ScheduledCheckpointFactory.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; import "./ScheduledCheckpoint.sol"; import "../../ModuleFactory.sol"; @@ -7,96 +7,44 @@ import "../../ModuleFactory.sol"; * @title Factory for deploying EtherDividendCheckpoint module */ contract ScheduledCheckpointFactory is ModuleFactory { - /** * @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 - ModuleFactory(_polyAddress, _setupCost, _usageCost, _subscriptionCost) + * @param _polymathRegistry Address of the Polymath registry + * @param _isCostInPoly true = cost in Poly, false = USD + */ + constructor( + uint256 _setupCost, + address _polymathRegistry, + bool _isCostInPoly + ) + public ModuleFactory(_setupCost, _polymathRegistry, _isCostInPoly) { - version = "1.0.0"; + initialVersion = "3.0.0"; name = "ScheduledCheckpoint"; title = "Schedule Checkpoints"; description = "Allows you to schedule checkpoints in the future"; - compatibleSTVersionRange["lowerBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0)); - compatibleSTVersionRange["upperBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0)); + typesData.push(4); + typesData.push(2); + tagsData.push("Scheduled"); + tagsData.push("Checkpoint"); + compatibleSTVersionRange["lowerBound"] = VersionUtils.pack(uint8(3), uint8(0), uint8(0)); + compatibleSTVersionRange["upperBound"] = VersionUtils.pack(uint8(3), uint8(0), uint8(0)); } /** * @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"); - address scheduledCheckpoint = new ScheduledCheckpoint(msg.sender, address(polyToken)); - emit GenerateModuleFromFactory(scheduledCheckpoint, getName(), address(this), msg.sender, setupCost, now); + function deploy( + bytes calldata _data + ) + external + returns(address) + { + address scheduledCheckpoint = address(new ScheduledCheckpoint(msg.sender, polymathRegistry.getAddress("PolyToken"))); + _initializeModule(scheduledCheckpoint, _data); return scheduledCheckpoint; } - /** - * @notice Type of the Module factory - */ - function getTypes() external view returns(uint8[]) { - uint8[] memory res = new uint8[](2); - res[0] = 4; - res[1] = 2; - return res; - } - - /** - * @notice Get the name of the Module - */ - function getName() public view returns(bytes32) { - return name; - } - - /** - * @notice Get the description of the Module - */ - function getDescription() external view returns(string) { - return description; - } - - /** - * @notice Get the title of the Module - */ - function getTitle() external view returns(string) { - return title; - } - - /** - * @notice Get the version of the Module - */ - function getVersion() external view returns(string) { - return version; - } - - /** - * @notice Get the setup cost of the module - */ - function getSetupCost() external view returns (uint256) { - return setupCost; - } - - /** - * @notice Get the Instructions that helped to used the module - */ - function getInstructions() external view returns(string) { - return "Schedule a series of future checkpoints by specifying a start time and interval of each checkpoint"; - } - - /** - * @notice Get the tags related to the module factory - */ - function getTags() external view returns(bytes32[]) { - bytes32[] memory availableTags = new bytes32[](2); - availableTags[0] = "Scheduled"; - availableTags[1] = "Checkpoint"; - return availableTags; - } } diff --git a/contracts/modules/Experimental/TransferManager/BlacklistTransferManagerFactory.sol b/contracts/modules/Experimental/TransferManager/BlacklistTransferManagerFactory.sol deleted file mode 100644 index 36dd4304f..000000000 --- a/contracts/modules/Experimental/TransferManager/BlacklistTransferManagerFactory.sol +++ /dev/null @@ -1,70 +0,0 @@ -pragma solidity ^0.4.24; - -import "./BlacklistTransferManager.sol"; -import "../../ModuleFactory.sol"; -import "../../../libraries/Util.sol"; - -/** - * @title Factory for deploying BlacklistManager module - */ -contract BlacklistTransferManagerFactory is ModuleFactory { - - /** - * @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 - ModuleFactory(_polyAddress, _setupCost, _usageCost, _subscriptionCost) - { - version = "2.1.0"; - name = "BlacklistTransferManager"; - title = "Blacklist Transfer Manager"; - description = "Automate blacklist to restrict selling"; - compatibleSTVersionRange["lowerBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0)); - compatibleSTVersionRange["upperBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0)); - } - - /** - * @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"); - address blacklistTransferManager = new BlacklistTransferManager(msg.sender, address(polyToken)); - /*solium-disable-next-line security/no-block-members*/ - emit GenerateModuleFromFactory(address(blacklistTransferManager), getName(), address(this), msg.sender, setupCost, now); - return address(blacklistTransferManager); - } - - /** - * @notice Type of the Module factory - */ - function getTypes() external view returns(uint8[]) { - uint8[] memory res = new uint8[](1); - res[0] = 2; - return res; - } - - /** - * @notice Get the Instructions that helped to used the module - */ - function getInstructions() public view returns(string) { - return "Allows an issuer to blacklist the addresses."; - } - - /** - * @notice Get the tags related to the module factory - */ - function getTags() public view returns(bytes32[]) { - bytes32[] memory availableTags = new bytes32[](2); - availableTags[0] = "Blacklist"; - availableTags[1] = "Restricted transfer"; - return availableTags; - } - - -} diff --git a/contracts/modules/Experimental/TransferManager/KYCTransferManager.sol b/contracts/modules/Experimental/TransferManager/KYCTransferManager.sol new file mode 100644 index 000000000..c30ad4699 --- /dev/null +++ b/contracts/modules/Experimental/TransferManager/KYCTransferManager.sol @@ -0,0 +1,102 @@ +pragma solidity 0.5.8; + +import "../../TransferManager/TransferManager.sol"; +import "../../../interfaces/ISecurityToken.sol"; +import "openzeppelin-solidity/contracts/math/SafeMath.sol"; + +/** + * @title Transfer Manager module for core transfer validation functionality + */ +contract KYCTransferManager is TransferManager { + + using SafeMath for uint256; + + bytes32 public constant KYC_NUMBER = "KYC_NUMBER"; //We will standardize what key to use for what. + + bytes32 public constant KYC_ARRAY = "KYC_ARRAY"; + + /** + * @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 This function returns the signature of configure function + */ + function getInitFunction() public pure returns (bytes4) { + return bytes4(0); + } + + function executeTransfer(address _from, address _to, uint256 _amount, bytes calldata _data) + external + returns (Result) + { + (Result success,)= verifyTransfer(_from, _to, _amount, _data); + return success; + } + + function verifyTransfer(address /*_from*/, address _to, uint256 /*_amount*/, bytes memory /* _data */) public view returns(Result, bytes32) { + if (!paused && checkKYC(_to)) { + return (Result.VALID, bytes32(uint256(address(this)) << 96)); + } + return (Result.NA, bytes32(0)); + } + + function modifyKYC( address _investor, bool _kycStatus) public withPerm(ADMIN) { + _modifyKYC(_investor, _kycStatus); + } + + function _modifyKYC(address _investor, bool _kycStatus) internal { + IDataStore dataStore = getDataStore(); + bytes32 key = _getKYCKey(_investor); + uint256 kycNumber = dataStore.getUint256(key); //index in address array + 1 + uint256 kycTotal = dataStore.getAddressArrayLength(KYC_ARRAY); + if(_kycStatus) { + require(kycNumber == 0, "KYC exists"); + dataStore.setUint256(key, kycTotal + 1); + dataStore.insertAddress(KYC_ARRAY, _investor); + } else { + require(kycNumber != 0, "KYC does not exist"); + address lastAddress = dataStore.getAddressArrayElement(KYC_ARRAY, kycTotal - 1); + dataStore.deleteAddress(KYC_ARRAY, kycNumber - 1); + + //Corrects the index of last element as delete fucntions move last element to index. + dataStore.setUint256(_getKYCKey(lastAddress), kycNumber); + } + //Alternatively, we can just emit an event and not maintain the KYC array on chain. + //I am maintaining the array to showcase how it can be done in cases where it might be needed. + } + + function getKYCAddresses() public view returns(address[] memory) { + IDataStore dataStore = getDataStore(); + return dataStore.getAddressArray(KYC_ARRAY); + } + + function checkKYC(address _investor) public view returns (bool kyc) { + bytes32 key = _getKYCKey(_investor); + IDataStore dataStore = getDataStore(); + if (dataStore.getUint256(key) > 0) + kyc = true; + } + + function _getKYCKey(address _identity) internal pure returns(bytes32) { + return bytes32(keccak256(abi.encodePacked(KYC_NUMBER, _identity))); + } + + /** + * @notice Return the permissions flag that are associated with this module + * @return bytes32 array + */ + function getPermissions() public view returns(bytes32[] memory) { + bytes32[] memory allPermissions = new bytes32[](1); + allPermissions[0] = ADMIN; + return allPermissions; + } + +} diff --git a/contracts/modules/Experimental/TransferManager/KYCTransferManagerFactory.sol b/contracts/modules/Experimental/TransferManager/KYCTransferManagerFactory.sol new file mode 100644 index 000000000..14c319a71 --- /dev/null +++ b/contracts/modules/Experimental/TransferManager/KYCTransferManagerFactory.sol @@ -0,0 +1,42 @@ +pragma solidity 0.5.8; + +import "./KYCTransferManager.sol"; +import "./../../ModuleFactory.sol"; + + +contract KYCTransferManagerFactory is ModuleFactory { + + /** + * @notice Constructor + */ + constructor( + uint256 _setupCost, + address _polymathRegistry, + bool _isCostInPoly + ) + public ModuleFactory(_setupCost, _polymathRegistry, _isCostInPoly) + { + initialVersion = "3.0.0"; + name = "KYCTransferManager"; + title = "KYC Transfer Manager"; + description = "Manages KYC"; + typesData.push(2); + typesData.push(6); + tagsData.push("KYC"); + tagsData.push("Transfer Restriction"); + compatibleSTVersionRange["lowerBound"] = VersionUtils.pack(uint8(3), uint8(0), uint8(0)); + compatibleSTVersionRange["upperBound"] = VersionUtils.pack(uint8(3), uint8(0), uint8(0)); + } + + + /** + * @notice Used to launch the Module with the help of factory + * @return address Contract address of the Module + */ + function deploy(bytes calldata _data) external returns(address) { + address kycTransferManager = address(new KYCTransferManager(msg.sender, polymathRegistry.getAddress("PolyToken"))); + _initializeModule(kycTransferManager, _data); + return kycTransferManager; + } + +} diff --git a/contracts/modules/Experimental/TransferManager/LockUpTransferManagerFactory.sol b/contracts/modules/Experimental/TransferManager/LockUpTransferManagerFactory.sol deleted file mode 100644 index 22581eba4..000000000 --- a/contracts/modules/Experimental/TransferManager/LockUpTransferManagerFactory.sol +++ /dev/null @@ -1,70 +0,0 @@ -pragma solidity ^0.4.24; - -import "../../ModuleFactory.sol"; -import "./LockUpTransferManager.sol"; - -/** - * @title Factory for deploying LockUpTransferManager module - */ -contract LockUpTransferManagerFactory is ModuleFactory { - - /** - * @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 - ModuleFactory(_polyAddress, _setupCost, _usageCost, _subscriptionCost) - { - version = "1.0.0"; - name = "LockUpTransferManager"; - title = "LockUp Transfer Manager"; - description = "Manage transfers using lock ups over time"; - compatibleSTVersionRange["lowerBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0)); - compatibleSTVersionRange["upperBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0)); - } - - /** - * @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"); - LockUpTransferManager lockUpTransferManager = new LockUpTransferManager(msg.sender, address(polyToken)); - /*solium-disable-next-line security/no-block-members*/ - emit GenerateModuleFromFactory(address(lockUpTransferManager), getName(), address(this), msg.sender, setupCost, now); - return address(lockUpTransferManager); - } - - /** - * @notice Type of the Module factory - * @return uint8 - */ - function getTypes() external view returns(uint8[]) { - uint8[] memory res = new uint8[](1); - res[0] = 2; - return res; - } - - /** - * @notice Returns the instructions associated with the module - */ - function getInstructions() external view returns(string) { - return "Allows an issuer to set lockup periods for user addresses, with funds distributed over time. Init function takes no parameters."; - } - - /** - * @notice Get the tags related to the module factory - */ - function getTags() external view returns(bytes32[]) { - bytes32[] memory availableTags = new bytes32[](2); - availableTags[0] = "LockUp"; - availableTags[1] = "Transfer Restriction"; - return availableTags; - } - - -} diff --git a/contracts/modules/Experimental/TransferManager/SignedTransferManager.sol b/contracts/modules/Experimental/TransferManager/SignedTransferManager.sol new file mode 100644 index 000000000..0e2e80b87 --- /dev/null +++ b/contracts/modules/Experimental/TransferManager/SignedTransferManager.sol @@ -0,0 +1,164 @@ +pragma solidity 0.5.8; + +import "../../TransferManager/TransferManager.sol"; +import "openzeppelin-solidity/contracts/math/SafeMath.sol"; +import "openzeppelin-solidity/contracts/cryptography/ECDSA.sol"; + +/** + * @title Transfer Manager module for verifing transations with a signed message + */ +contract SignedTransferManager is TransferManager { + using SafeMath for uint256; + using ECDSA for bytes32; + + //Keeps track of if the signature has been used or invalidated + //mapping(bytes => bool) invalidSignatures; + bytes32 constant public INVALID_SIG = "INVALIDSIG"; + + // Emit when a signature has been deemed invalid + event SignatureUsed(bytes _data); + + /** + * @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 This function returns the signature of configure function + */ + function getInitFunction() external pure returns (bytes4) { + return bytes4(0); + } + + /** + * @notice function to check if a signature is still valid + * @param _data signature + */ + function checkSignatureValidity(bytes calldata _data) external view returns(bool) { + address targetAddress; + uint256 nonce; + uint256 validFrom; + uint256 expiry; + bytes memory signature; + (targetAddress, nonce, validFrom, expiry, signature) = abi.decode(_data, (address, uint256, uint256, uint256, bytes)); + if (targetAddress != address(this) || expiry < now || validFrom > now || signature.length == 0 || _checkSignatureIsInvalid(signature)) + return false; + return true; + } + + function checkSigner(address _signer) external view returns(bool) { + return _checkSigner(_signer); + } + + /** + * @notice allow verify transfer with signature + * @param _from address transfer from + * @param _to address transfer to + * @param _amount transfer amount + * @param _data signature + * Sig needs to be valid (not used or deemed as invalid) + * Signer needs to be in the signers mapping + */ + function executeTransfer(address _from, address _to, uint256 _amount, bytes calldata _data) external onlySecurityToken returns(Result) { + (Result success, ) = verifyTransfer(_from, _to, _amount, _data); + if (success == Result.VALID && _data.length > 32) { + bytes memory signature; + (,,,,signature) = abi.decode(_data, (address, uint256, uint256, uint256, bytes)); + _invalidateSignature(signature); + } + return success; + } + + /** + * @notice allow verify transfer with signature + * @param _from address transfer from + * @param _to address transfer to + * @param _amount transfer amount + * @param _data signature + * Sig needs to be valid (not used or deemed as invalid) + * Signer needs to be in the signers mapping + */ + function verifyTransfer(address _from, address _to, uint256 _amount, bytes memory _data) public view returns(Result, bytes32) { + if (!paused) { + + if (_data.length <= 32) + return (Result.NA, bytes32(0)); + + address targetAddress; + uint256 nonce; + uint256 validFrom; + uint256 expiry; + bytes memory signature; + (targetAddress, nonce, validFrom, expiry, signature) = abi.decode(_data, (address, uint256, uint256, uint256, bytes)); + + if (address(this) != targetAddress || signature.length == 0 || _checkSignatureIsInvalid(signature) || expiry < now || validFrom > now) + return (Result.NA, bytes32(0)); + + bytes32 hash = keccak256(abi.encodePacked(targetAddress, nonce, validFrom, expiry, _from, _to, _amount)); + address signer = hash.toEthSignedMessageHash().recover(signature); + + if (!_checkSigner(signer)) + return (Result.NA, bytes32(0)); + return (Result.VALID, bytes32(uint256(address(this)) << 96)); + } + return (Result.NA, bytes32(0)); + } + + /** + * @notice allow signers to deem a signature invalid + * @param _from address transfer from + * @param _to address transfer to + * @param _amount transfer amount + * @param _data signature + * Sig needs to be valid (not used or deemed as invalid) + * Signer needs to be in the signers mapping + */ + function invalidateSignature(address _from, address _to, uint256 _amount, bytes calldata _data) external { + require(_checkSigner(msg.sender), "Unauthorized Signer"); + + address targetAddress; + uint256 nonce; + uint256 validFrom; + uint256 expiry; + bytes memory signature; + (targetAddress, nonce, validFrom, expiry, signature) = abi.decode(_data, (address, uint256, uint256, uint256, bytes)); + + require(!_checkSignatureIsInvalid(signature), "Signature already invalid"); + require(targetAddress == address(this), "Signature not for this module"); + + bytes32 hash = keccak256(abi.encodePacked(targetAddress, nonce, validFrom, expiry, _from, _to, _amount)); + require(hash.toEthSignedMessageHash().recover(signature) == msg.sender, "Incorrect Signer"); + + _invalidateSignature(signature); + } + + /** + * @notice Return the permissions flag that are associated with ManualApproval transfer manager + */ + function getPermissions() public view returns(bytes32[] memory) { + bytes32[] memory allPermissions = new bytes32[](1); + allPermissions[0] = ADMIN; + return allPermissions; + } + + function _checkSignatureIsInvalid(bytes memory _data) internal view returns(bool) { + IDataStore dataStore = getDataStore(); + return dataStore.getBool(keccak256(abi.encodePacked(INVALID_SIG, _data))); + } + + function _checkSigner(address _signer) internal view returns(bool) { + return _checkPerm(OPERATOR, _signer); + } + + function _invalidateSignature(bytes memory _data) internal { + IDataStore dataStore = getDataStore(); + dataStore.setBool(keccak256(abi.encodePacked(INVALID_SIG, _data)), true); + emit SignatureUsed(_data); + } +} diff --git a/contracts/modules/Experimental/TransferManager/SignedTransferManagerFactory.sol b/contracts/modules/Experimental/TransferManager/SignedTransferManagerFactory.sol new file mode 100644 index 000000000..eeb94baf6 --- /dev/null +++ b/contracts/modules/Experimental/TransferManager/SignedTransferManagerFactory.sol @@ -0,0 +1,44 @@ +pragma solidity 0.5.8; + +import "./SignedTransferManager.sol"; +import "../../ModuleFactory.sol"; + +/** + * @title Factory for deploying SignedTransferManager module + */ +contract SignedTransferManagerFactory is ModuleFactory { + + /** + * @notice Constructor + */ + constructor( + uint256 _setupCost, + address _polymathRegistry, + bool _isCostInPoly + ) + public ModuleFactory(_setupCost, _polymathRegistry, _isCostInPoly) + { + initialVersion = "3.0.0"; + name = "SignedTransferManager"; + title = "Signed Transfer Manager"; + description = "Manage transfers using a signature"; + typesData.push(2); + typesData.push(6); + tagsData.push("Signed"); + tagsData.push("Transfer Restriction"); + compatibleSTVersionRange["lowerBound"] = VersionUtils.pack(uint8(3), uint8(0), uint8(0)); + compatibleSTVersionRange["upperBound"] = VersionUtils.pack(uint8(3), uint8(0), uint8(0)); + } + + + /** + * @notice used to launch the Module with the help of factory + * @return address Contract address of the Module + */ + function deploy(bytes calldata _data) external returns(address) { + address signedTransferManager = address(new SignedTransferManager(msg.sender, polymathRegistry.getAddress("PolyToken"))); + _initializeModule(signedTransferManager, _data); + return signedTransferManager; + } + +} diff --git a/contracts/modules/Experimental/Wallet/IWallet.sol b/contracts/modules/Experimental/Wallet/IWallet.sol deleted file mode 100644 index 96affd472..000000000 --- a/contracts/modules/Experimental/Wallet/IWallet.sol +++ /dev/null @@ -1,19 +0,0 @@ -pragma solidity ^0.4.24; - -import "../../../Pausable.sol"; -import "../../Module.sol"; - -/** - * @title Interface to be implemented by all Wallet modules - * @dev abstract contract - */ -contract IWallet is Module, Pausable { - - function unpause() public onlyOwner { - super._unpause(); - } - - function pause() public onlyOwner { - super._pause(); - } -} diff --git a/contracts/modules/Experimental/Wallet/VestingEscrowWalletFactory.sol b/contracts/modules/Experimental/Wallet/VestingEscrowWalletFactory.sol deleted file mode 100644 index 2e35453f0..000000000 --- a/contracts/modules/Experimental/Wallet/VestingEscrowWalletFactory.sol +++ /dev/null @@ -1,76 +0,0 @@ -pragma solidity ^0.4.24; - -import "../../../proxy/VestingEscrowWalletProxy.sol"; -import "../../../interfaces/IBoot.sol"; -import "../../ModuleFactory.sol"; -import "../../../libraries/Util.sol"; - -/** - * @title Factory for deploying VestingEscrowWallet module - */ -contract VestingEscrowWalletFactory is ModuleFactory { - - address public logicContract; - /** - * @notice Constructor - * @param _polyAddress Address of the polytoken - */ - constructor (address _polyAddress, uint256 _setupCost, uint256 _usageCost, uint256 _subscriptionCost, address _logicContract) public - ModuleFactory(_polyAddress, _setupCost, _usageCost, _subscriptionCost) - { - require(_logicContract != address(0), "Invalid address"); - version = "1.0.0"; - name = "VestingEscrowWallet"; - title = "Vesting Escrow Wallet"; - description = "Manage vesting schedules to employees / affiliates"; - compatibleSTVersionRange["lowerBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0)); - compatibleSTVersionRange["upperBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0)); - logicContract = _logicContract; - } - - /** - * @notice Used to launch the Module with the help of factory - * _data Data used for the intialization of the module factory variables - * @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 due to insufficent Allowance provided"); - } - VestingEscrowWalletProxy vestingEscrowWallet = new VestingEscrowWalletProxy(msg.sender, address(polyToken), logicContract); - //Checks that _data is valid (not calling anything it shouldn't) - require(Util.getSig(_data) == IBoot(vestingEscrowWallet).getInitFunction(), "Invalid data"); - /*solium-disable-next-line security/no-low-level-calls*/ - require(address(vestingEscrowWallet).call(_data), "Unsuccessfull call"); - /*solium-disable-next-line security/no-block-members*/ - emit GenerateModuleFromFactory(address(vestingEscrowWallet), getName(), address(this), msg.sender, setupCost, now); - return address(vestingEscrowWallet); - } - - /** - * @notice Type of the Module factory - */ - function getTypes() external view returns(uint8[]) { - uint8[] memory res = new uint8[](1); - res[0] = 6; - return res; - } - - /** - * @notice Returns the instructions associated with the module - */ - function getInstructions() external view returns(string) { - /*solium-disable-next-line max-len*/ - return "Issuer can deposit tokens to the contract and create the vesting schedule for the given address (Affiliate/Employee). These address can withdraw tokens according to there vesting schedule."; - } - - /** - * @notice Get the tags related to the module factory - */ - function getTags() external view returns(bytes32[]) { - bytes32[] memory availableTags = new bytes32[](2); - availableTags[0] = "Vested"; - availableTags[1] = "Escrow Wallet"; - return availableTags; - } -} diff --git a/contracts/modules/Module.sol b/contracts/modules/Module.sol index a3698f22f..cd491d9f8 100644 --- a/contracts/modules/Module.sol +++ b/contracts/modules/Module.sol @@ -1,20 +1,23 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; import "../interfaces/IModule.sol"; +import "../Pausable.sol"; +import "../interfaces/IModuleFactory.sol"; +import "../interfaces/IDataStore.sol"; import "../interfaces/ISecurityToken.sol"; -import "./ModuleStorage.sol"; +import "../interfaces/ICheckPermission.sol"; +import "../storage/modules/ModuleStorage.sol"; import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; +import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; /** * @title Interface that any module contract should implement * @notice Contract is abstract */ -contract Module is IModule, ModuleStorage { - +contract Module is IModule, ModuleStorage, Pausable { /** * @notice Constructor * @param _securityToken Address of the security token - * @param _polyAddress Address of the polytoken */ constructor (address _securityToken, address _polyAddress) public ModuleStorage(_securityToken, _polyAddress) @@ -23,38 +26,67 @@ contract Module is IModule, ModuleStorage { //Allows owner, factory or permissioned delegate modifier withPerm(bytes32 _perm) { - bool isOwner = msg.sender == Ownable(securityToken).owner(); - bool isFactory = msg.sender == factory; - require(isOwner||isFactory||ISecurityToken(securityToken).checkPermission(msg.sender, address(this), _perm), "Permission check failed"); + require(_checkPerm(_perm, msg.sender), "Invalid permission"); _; } - modifier onlyOwner { - require(msg.sender == Ownable(securityToken).owner(), "Sender is not owner"); - _; + function _checkPerm(bytes32 _perm, address _caller) internal view returns (bool) { + bool isOwner = _caller == Ownable(address(securityToken)).owner(); + bool isFactory = _caller == factory; + return isOwner || isFactory || ICheckPermission(address(securityToken)).checkPermission(_caller, address(this), _perm); + } + + function _onlySecurityTokenOwner() internal view { + require(msg.sender == Ownable(address(securityToken)).owner(), "Sender is not owner"); } - modifier onlyFactory { + modifier onlyFactory() { require(msg.sender == factory, "Sender is not factory"); _; } - modifier onlyFactoryOwner { - require(msg.sender == Ownable(factory).owner(), "Sender is not factory owner"); - _; + /** + * @notice Pause (overridden function) + */ + function pause() public { + _onlySecurityTokenOwner(); + super._pause(); } - modifier onlyFactoryOrOwner { - require((msg.sender == Ownable(securityToken).owner()) || (msg.sender == factory), "Sender is not factory or owner"); - _; + /** + * @notice Unpause (overridden function) + */ + function unpause() public { + _onlySecurityTokenOwner(); + super._unpause(); } /** - * @notice used to withdraw the fee by the factory owner + * @notice used to return the data store address of securityToken */ - function takeFee(uint256 _amount) public withPerm(FEE_ADMIN) returns(bool) { - require(polyToken.transferFrom(securityToken, Ownable(factory).owner(), _amount), "Unable to take fee"); - return true; + function getDataStore() public view returns(IDataStore) { + return IDataStore(securityToken.dataStore()); } + /** + * @notice Reclaims ERC20Basic compatible tokens + * @dev We duplicate here due to the overriden owner & onlyOwner + * @param _tokenContract The address of the token contract + */ + function reclaimERC20(address _tokenContract) external { + _onlySecurityTokenOwner(); + require(_tokenContract != address(0), "Invalid address"); + IERC20 token = IERC20(_tokenContract); + uint256 balance = token.balanceOf(address(this)); + require(token.transfer(msg.sender, balance), "Transfer failed"); + } + + /** + * @notice Reclaims ETH + * @dev We duplicate here due to the overriden owner & onlyOwner + */ + function reclaimETH() external { + _onlySecurityTokenOwner(); + msg.sender.transfer(address(this).balance); + } } diff --git a/contracts/modules/ModuleFactory.sol b/contracts/modules/ModuleFactory.sol index 9fc6cea02..260a5d151 100644 --- a/contracts/modules/ModuleFactory.sol +++ b/contracts/modules/ModuleFactory.sol @@ -1,9 +1,14 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; -import "../interfaces/IERC20.sol"; +import "../libraries/VersionUtils.sol"; +import "../libraries/Util.sol"; +import "../interfaces/IModule.sol"; +import "../interfaces/IOracle.sol"; +import "../interfaces/IPolymathRegistry.sol"; import "../interfaces/IModuleFactory.sol"; +import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; -import "../libraries/VersionUtils.sol"; +import "../libraries/DecimalMath.sol"; /** * @title Interface that any module factory contract should implement @@ -11,96 +16,113 @@ import "../libraries/VersionUtils.sol"; */ contract ModuleFactory is IModuleFactory, Ownable { - IERC20 public polyToken; - uint256 public usageCost; - uint256 public monthlySubscriptionCost; + IPolymathRegistry public polymathRegistry; - uint256 public setupCost; - string public description; - string public version; + string initialVersion; bytes32 public name; string public title; + string public description; + + uint8[] typesData; + bytes32[] tagsData; + + bool public isCostInPoly; + uint256 public setupCost; + + string constant POLY_ORACLE = "StablePolyUsdOracle"; // @notice Allow only two variables to be stored - // 1. lowerBound + // 1. lowerBound // 2. upperBound - // @dev (0.0.0 will act as the wildcard) + // @dev (0.0.0 will act as the wildcard) // @dev uint24 consists packed value of uint8 _major, uint8 _minor, uint8 _patch mapping(string => uint24) compatibleSTVersionRange; /** * @notice Constructor - * @param _polyAddress Address of the polytoken */ - constructor (address _polyAddress, uint256 _setupCost, uint256 _usageCost, uint256 _subscriptionCost) public { - polyToken = IERC20(_polyAddress); + constructor(uint256 _setupCost, address _polymathRegistry, bool _isCostInPoly) public { setupCost = _setupCost; - usageCost = _usageCost; - monthlySubscriptionCost = _subscriptionCost; + polymathRegistry = IPolymathRegistry(_polymathRegistry); + isCostInPoly = _isCostInPoly; } /** - * @notice Used to change the fee of the setup cost - * @param _newSetupCost new setup cost + * @notice Type of the Module factory + */ + function getTypes() external view returns(uint8[] memory) { + return typesData; + } + + /** + * @notice Get the tags related to the module factory */ - function changeFactorySetupFee(uint256 _newSetupCost) public onlyOwner { - emit ChangeFactorySetupFee(setupCost, _newSetupCost, address(this)); - setupCost = _newSetupCost; + function getTags() external view returns(bytes32[] memory) { + return tagsData; } /** - * @notice Used to change the fee of the usage cost - * @param _newUsageCost new usage cost + * @notice Get the version related to the module factory */ - function changeFactoryUsageFee(uint256 _newUsageCost) public onlyOwner { - emit ChangeFactoryUsageFee(usageCost, _newUsageCost, address(this)); - usageCost = _newUsageCost; + function version() external view returns(string memory) { + return initialVersion; } /** - * @notice Used to change the fee of the subscription cost - * @param _newSubscriptionCost new subscription cost + * @notice Used to change the fee of the setup cost + * @param _setupCost new setup cost */ - function changeFactorySubscriptionFee(uint256 _newSubscriptionCost) public onlyOwner { - emit ChangeFactorySubscriptionFee(monthlySubscriptionCost, _newSubscriptionCost, address(this)); - monthlySubscriptionCost = _newSubscriptionCost; + function changeSetupCost(uint256 _setupCost) public onlyOwner { + emit ChangeSetupCost(setupCost, _setupCost); + setupCost = _setupCost; + } + /** + * @notice Used to change the currency and amount of setup cost + * @param _setupCost new setup cost + * @param _isCostInPoly new setup cost currency. USD or POLY + */ + function changeCostAndType(uint256 _setupCost, bool _isCostInPoly) public onlyOwner { + emit ChangeSetupCost(setupCost, _setupCost); + emit ChangeCostType(isCostInPoly, _isCostInPoly); + setupCost = _setupCost; + isCostInPoly = _isCostInPoly; } /** * @notice Updates the title of the ModuleFactory - * @param _newTitle New Title that will replace the old one. + * @param _title New Title that will replace the old one. */ - function changeTitle(string _newTitle) public onlyOwner { - require(bytes(_newTitle).length > 0, "Invalid title"); - title = _newTitle; + function changeTitle(string memory _title) public onlyOwner { + require(bytes(_title).length > 0, "Invalid text"); + title = _title; } /** * @notice Updates the description of the ModuleFactory - * @param _newDesc New description that will replace the old one. + * @param _description New description that will replace the old one. */ - function changeDescription(string _newDesc) public onlyOwner { - require(bytes(_newDesc).length > 0, "Invalid description"); - description = _newDesc; + function changeDescription(string memory _description) public onlyOwner { + require(bytes(_description).length > 0, "Invalid text"); + description = _description; } /** * @notice Updates the name of the ModuleFactory - * @param _newName New name that will replace the old one. + * @param _name New name that will replace the old one. */ - function changeName(bytes32 _newName) public onlyOwner { - require(_newName != bytes32(0),"Invalid name"); - name = _newName; + function changeName(bytes32 _name) public onlyOwner { + require(_name != bytes32(0), "Invalid text"); + name = _name; } /** - * @notice Updates the version of the ModuleFactory - * @param _newVersion New name that will replace the old one. + * @notice Updates the tags of the ModuleFactory + * @param _tagsData New list of tags */ - function changeVersion(string _newVersion) public onlyOwner { - require(bytes(_newVersion).length > 0, "Invalid version"); - version = _newVersion; + function changeTags(bytes32[] memory _tagsData) public onlyOwner { + require(_tagsData.length > 0, "Invalid text"); + tagsData = _tagsData; } /** @@ -108,16 +130,21 @@ contract ModuleFactory is IModuleFactory, Ownable { * @param _boundType Type of bound * @param _newVersion new version array */ - function changeSTVersionBounds(string _boundType, uint8[] _newVersion) external onlyOwner { + function changeSTVersionBounds(string calldata _boundType, uint8[] calldata _newVersion) external onlyOwner { require( - keccak256(abi.encodePacked(_boundType)) == keccak256(abi.encodePacked("lowerBound")) || - keccak256(abi.encodePacked(_boundType)) == keccak256(abi.encodePacked("upperBound")), - "Must be a valid bound type" + keccak256(abi.encodePacked(_boundType)) == keccak256(abi.encodePacked("lowerBound")) || keccak256( + abi.encodePacked(_boundType) + ) == keccak256(abi.encodePacked("upperBound")), + "Invalid bound type" ); - require(_newVersion.length == 3); - if (compatibleSTVersionRange[_boundType] != uint24(0)) { + require(_newVersion.length == 3, "Invalid version"); + if (compatibleSTVersionRange[_boundType] != uint24(0)) { uint8[] memory _currentVersion = VersionUtils.unpack(compatibleSTVersionRange[_boundType]); - require(VersionUtils.isValidVersion(_currentVersion, _newVersion), "Failed because of in-valid version"); + if (keccak256(abi.encodePacked(_boundType)) == keccak256(abi.encodePacked("lowerBound"))) { + require(VersionUtils.lessThanOrEqual(_newVersion, _currentVersion), "Invalid version"); + } else { + require(VersionUtils.greaterThanOrEqual(_newVersion, _currentVersion), "Invalid version"); + } } compatibleSTVersionRange[_boundType] = VersionUtils.pack(_newVersion[0], _newVersion[1], _newVersion[2]); emit ChangeSTVersionBound(_boundType, _newVersion[0], _newVersion[1], _newVersion[2]); @@ -127,7 +154,7 @@ contract ModuleFactory is IModuleFactory, Ownable { * @notice Used to get the lower bound * @return lower bound */ - function getLowerSTVersionBounds() external view returns(uint8[]) { + function getLowerSTVersionBounds() external view returns(uint8[] memory) { return VersionUtils.unpack(compatibleSTVersionRange["lowerBound"]); } @@ -135,22 +162,48 @@ contract ModuleFactory is IModuleFactory, Ownable { * @notice Used to get the upper bound * @return upper bound */ - function getUpperSTVersionBounds() external view returns(uint8[]) { + function getUpperSTVersionBounds() external view returns(uint8[] memory) { return VersionUtils.unpack(compatibleSTVersionRange["upperBound"]); } /** * @notice Get the setup cost of the module */ - function getSetupCost() external view returns (uint256) { - return setupCost; + function setupCostInPoly() public returns (uint256) { + if (isCostInPoly) + return setupCost; + uint256 polyRate = IOracle(polymathRegistry.getAddress(POLY_ORACLE)).getPrice(); + return DecimalMath.div(setupCost, polyRate); + } + + /** + * @notice Calculates fee in POLY + */ + function _takeFee() internal returns(uint256) { + uint256 polySetupCost = setupCostInPoly(); + address polyToken = polymathRegistry.getAddress("PolyToken"); + if (polySetupCost > 0) { + require(IERC20(polyToken).transferFrom(msg.sender, owner(), polySetupCost), "Insufficient allowance for module fee"); + } + return polySetupCost; } - /** - * @notice Get the name of the Module - */ - function getName() public view returns(bytes32) { - return name; + /** + * @notice Used to initialize the module + * @param _module Address of module + * @param _data Data used for the intialization of the module factory variables + */ + function _initializeModule(address _module, bytes memory _data) internal { + uint256 polySetupCost = _takeFee(); + bytes4 initFunction = IModule(_module).getInitFunction(); + if (initFunction != bytes4(0)) { + require(Util.getSig(_data) == initFunction, "Provided data is not valid"); + /*solium-disable-next-line security/no-low-level-calls*/ + (bool success, ) = _module.call(_data); + require(success, "Unsuccessful initialization"); + } + /*solium-disable-next-line security/no-block-members*/ + emit GenerateModuleFromFactory(_module, name, address(this), msg.sender, setupCost, polySetupCost); } } diff --git a/contracts/modules/ModuleStorage.sol b/contracts/modules/ModuleStorage.sol deleted file mode 100644 index cd52c9de2..000000000 --- a/contracts/modules/ModuleStorage.sol +++ /dev/null @@ -1,30 +0,0 @@ -pragma solidity ^0.4.24; - -import "../interfaces/IERC20.sol"; - -/** - * @title Storage for Module contract - * @notice Contract is abstract - */ -contract ModuleStorage { - - /** - * @notice Constructor - * @param _securityToken Address of the security token - * @param _polyAddress Address of the polytoken - */ - constructor (address _securityToken, address _polyAddress) public { - securityToken = _securityToken; - factory = msg.sender; - polyToken = IERC20(_polyAddress); - } - - address public factory; - - address public securityToken; - - bytes32 public constant FEE_ADMIN = "FEE_ADMIN"; - - IERC20 public polyToken; - -} diff --git a/contracts/modules/PermissionManager/GeneralPermissionManager.sol b/contracts/modules/PermissionManager/GeneralPermissionManager.sol index f85abf675..bee4a3a3c 100644 --- a/contracts/modules/PermissionManager/GeneralPermissionManager.sol +++ b/contracts/modules/PermissionManager/GeneralPermissionManager.sol @@ -1,41 +1,30 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; import "./IPermissionManager.sol"; import "../Module.sol"; +import "./GeneralPermissionManagerStorage.sol"; +import "../../interfaces/ISecurityToken.sol"; /** * @title Permission Manager module for core permissioning functionality */ -contract GeneralPermissionManager is IPermissionManager, Module { - - // Mapping used to hold the permissions on the modules provided to delegate, module add => delegate add => permission bytes32 => bool - mapping (address => mapping (address => mapping (bytes32 => bool))) public perms; - // Mapping hold the delagate details - mapping (address => bytes32) public delegateDetails; - // Array to track all delegates - address[] public allDelegates; - - - // Permission flag - bytes32 public constant CHANGE_PERMISSION = "CHANGE_PERMISSION"; +contract GeneralPermissionManager is GeneralPermissionManagerStorage, IPermissionManager, Module { /// Event emitted after any permission get changed for the delegate - event ChangePermission(address indexed _delegate, address _module, bytes32 _perm, bool _valid, uint256 _timestamp); + event ChangePermission(address indexed _delegate, address _module, bytes32 _perm, bool _valid); /// Used to notify when delegate is added in permission manager contract - event AddDelegate(address indexed _delegate, bytes32 _details, uint256 _timestamp); - + event AddDelegate(address indexed _delegate, bytes32 _details); /// @notice constructor - constructor (address _securityToken, address _polyAddress) public - Module(_securityToken, _polyAddress) - { + constructor(address _securityToken, address _polyToken) public Module(_securityToken, _polyToken) { + } /** * @notice Init function i.e generalise function to maintain the structure of the module contract * @return bytes4 */ - function getInitFunction() public pure returns (bytes4) { + function getInitFunction() public pure returns(bytes4) { return bytes4(0); } @@ -49,8 +38,7 @@ contract GeneralPermissionManager is IPermissionManager, Module { function checkPermission(address _delegate, address _module, bytes32 _perm) external view returns(bool) { if (delegateDetails[_delegate] != bytes32(0)) { return perms[_module][_delegate][_perm]; - } else - return false; + } else return false; } /** @@ -58,26 +46,28 @@ contract GeneralPermissionManager is IPermissionManager, Module { * @param _delegate Ethereum address of the delegate * @param _details Details about the delegate i.e `Belongs to financial firm` */ - function addDelegate(address _delegate, bytes32 _details) external withPerm(CHANGE_PERMISSION) { + function addDelegate(address _delegate, bytes32 _details) external withPerm(ADMIN) { require(_delegate != address(0), "Invalid address"); require(_details != bytes32(0), "0 value not allowed"); require(delegateDetails[_delegate] == bytes32(0), "Already present"); delegateDetails[_delegate] = _details; allDelegates.push(_delegate); /*solium-disable-next-line security/no-block-members*/ - emit AddDelegate(_delegate, _details, now); + emit AddDelegate(_delegate, _details); } /** * @notice Used to delete a delegate * @param _delegate Ethereum address of the delegate */ - function deleteDelegate(address _delegate) external withPerm(CHANGE_PERMISSION) { + function deleteDelegate(address _delegate) external withPerm(ADMIN) { require(delegateDetails[_delegate] != bytes32(0), "delegate does not exist"); - for (uint256 i = 0; i < allDelegates.length; i++) { + uint256 delegateLen = allDelegates.length; + for (uint256 i = 0; i < delegateLen; i++) { if (allDelegates[i] == _delegate) { - allDelegates[i] = allDelegates[allDelegates.length - 1]; - allDelegates.length = allDelegates.length - 1; + allDelegates[i] = allDelegates[delegateLen - 1]; + allDelegates.length--; + break; } } delete delegateDetails[_delegate]; @@ -93,8 +83,7 @@ contract GeneralPermissionManager is IPermissionManager, Module { if (delegateDetails[_potentialDelegate] != bytes32(0)) { return true; - } else - return false; + } else return false; } /** @@ -105,15 +94,7 @@ contract GeneralPermissionManager is IPermissionManager, Module { * @param _valid Bool flag use to switch on/off the permission * @return bool */ - function changePermission( - address _delegate, - address _module, - bytes32 _perm, - bool _valid - ) - public - withPerm(CHANGE_PERMISSION) - { + function changePermission(address _delegate, address _module, bytes32 _perm, bool _valid) public withPerm(ADMIN) { require(_delegate != address(0), "invalid address"); _changePermission(_delegate, _module, _perm, _valid); } @@ -128,18 +109,17 @@ contract GeneralPermissionManager is IPermissionManager, Module { */ function changePermissionMulti( address _delegate, - address[] _modules, - bytes32[] _perms, - bool[] _valids + address[] memory _modules, + bytes32[] memory _perms, + bool[] memory _valids ) - external - withPerm(CHANGE_PERMISSION) + public + withPerm(ADMIN) { require(_delegate != address(0), "invalid address"); require(_modules.length > 0, "0 length is not allowed"); - require(_modules.length == _perms.length, "Array length mismatch"); - require(_valids.length == _perms.length, "Array length mismatch"); - for(uint256 i = 0; i < _perms.length; i++) { + require(_modules.length == _perms.length && _valids.length == _perms.length, "Array length mismatch"); + for (uint256 i = 0; i < _perms.length; i++) { _changePermission(_delegate, _modules[i], _perms[i], _valids[i]); } } @@ -150,7 +130,7 @@ contract GeneralPermissionManager is IPermissionManager, Module { * @param _perm Permission flag * @return address[] */ - function getAllDelegatesWithPerm(address _module, bytes32 _perm) external view returns(address[]) { + function getAllDelegatesWithPerm(address _module, bytes32 _perm) external view returns(address[] memory) { uint256 counter = 0; uint256 i = 0; for (i = 0; i < allDelegates.length; i++) { @@ -161,7 +141,7 @@ contract GeneralPermissionManager is IPermissionManager, Module { address[] memory allDelegatesWithPerm = new address[](counter); counter = 0; for (i = 0; i < allDelegates.length; i++) { - if (perms[_module][allDelegates[i]][_perm]){ + if (perms[_module][allDelegates[i]][_perm]) { allDelegatesWithPerm[counter] = allDelegates[i]; counter++; } @@ -177,18 +157,21 @@ contract GeneralPermissionManager is IPermissionManager, Module { * @return address[] the address array of Modules this delegate has permission * @return bytes32[] the permission array of the corresponding Modules */ - function getAllModulesAndPermsFromTypes(address _delegate, uint8[] _types) external view returns(address[], bytes32[]) { + function getAllModulesAndPermsFromTypes(address _delegate, uint8[] calldata _types) external view returns( + address[] memory, + bytes32[] memory + ) { uint256 counter = 0; // loop through _types and get their modules from securityToken->getModulesByType for (uint256 i = 0; i < _types.length; i++) { - address[] memory _currentTypeModules = ISecurityToken(securityToken).getModulesByType(_types[i]); + address[] memory _currentTypeModules = securityToken.getModulesByType(_types[i]); // loop through each modules to get their perms from IModule->getPermissions - for (uint256 j = 0; j < _currentTypeModules.length; j++){ + for (uint256 j = 0; j < _currentTypeModules.length; j++) { bytes32[] memory _allModulePerms = IModule(_currentTypeModules[j]).getPermissions(); // loop through each perm, if it is true, push results into arrays for (uint256 k = 0; k < _allModulePerms.length; k++) { if (perms[_currentTypeModules[j]][_delegate][_allModulePerms[k]]) { - counter ++; + counter++; } } } @@ -198,11 +181,11 @@ contract GeneralPermissionManager is IPermissionManager, Module { bytes32[] memory _allPerms = new bytes32[](counter); counter = 0; - for (i = 0; i < _types.length; i++){ - _currentTypeModules = ISecurityToken(securityToken).getModulesByType(_types[i]); - for (j = 0; j < _currentTypeModules.length; j++) { - _allModulePerms = IModule(_currentTypeModules[j]).getPermissions(); - for (k = 0; k < _allModulePerms.length; k++) { + for (uint256 i = 0; i < _types.length; i++) { + address[] memory _currentTypeModules = securityToken.getModulesByType(_types[i]); + for (uint256 j = 0; j < _currentTypeModules.length; j++) { + bytes32[] memory _allModulePerms = IModule(_currentTypeModules[j]).getPermissions(); + for (uint256 k = 0; k < _allModulePerms.length; k++) { if (perms[_currentTypeModules[j]][_delegate][_allModulePerms[k]]) { _allModules[counter] = _currentTypeModules[j]; _allPerms[counter] = _allModulePerms[k]; @@ -212,7 +195,7 @@ contract GeneralPermissionManager is IPermissionManager, Module { } } - return(_allModules, _allPerms); + return (_allModules, _allPerms); } /** @@ -223,34 +206,27 @@ contract GeneralPermissionManager is IPermissionManager, Module { * @param _valid Bool flag use to switch on/off the permission * @return bool */ - function _changePermission( - address _delegate, - address _module, - bytes32 _perm, - bool _valid - ) - internal - { + function _changePermission(address _delegate, address _module, bytes32 _perm, bool _valid) internal { perms[_module][_delegate][_perm] = _valid; /*solium-disable-next-line security/no-block-members*/ - emit ChangePermission(_delegate, _module, _perm, _valid, now); + emit ChangePermission(_delegate, _module, _perm, _valid); } /** * @notice Used to get all delegates * @return address[] */ - function getAllDelegates() external view returns(address[]) { + function getAllDelegates() external view returns(address[] memory) { return allDelegates; } - + /** * @notice Returns the Permission flag related the `this` contract * @return Array of permission flags */ - function getPermissions() public view returns(bytes32[]) { + function getPermissions() public view returns(bytes32[] memory) { bytes32[] memory allPermissions = new bytes32[](1); - allPermissions[0] = CHANGE_PERMISSION; + allPermissions[0] = ADMIN; return allPermissions; } diff --git a/contracts/modules/PermissionManager/GeneralPermissionManagerFactory.sol b/contracts/modules/PermissionManager/GeneralPermissionManagerFactory.sol index bfdc73801..d070696b1 100644 --- a/contracts/modules/PermissionManager/GeneralPermissionManagerFactory.sol +++ b/contracts/modules/PermissionManager/GeneralPermissionManagerFactory.sol @@ -1,63 +1,51 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; -import "./GeneralPermissionManager.sol"; -import "../ModuleFactory.sol"; +import "../UpgradableModuleFactory.sol"; +import "./GeneralPermissionManagerProxy.sol"; /** * @title Factory for deploying GeneralPermissionManager module */ -contract GeneralPermissionManagerFactory is ModuleFactory { +contract GeneralPermissionManagerFactory is UpgradableModuleFactory { /** * @notice Constructor - * @param _polyAddress Address of the polytoken + * @param _setupCost Setup cost of the module + * @param _logicContract Contract address that contains the logic related to `description` + * @param _polymathRegistry Address of the Polymath registry + * @param _isCostInPoly true = cost in Poly, false = USD */ - constructor (address _polyAddress, uint256 _setupCost, uint256 _usageCost, uint256 _subscriptionCost) public - ModuleFactory(_polyAddress, _setupCost, _usageCost, _subscriptionCost) + constructor ( + uint256 _setupCost, + address _logicContract, + address _polymathRegistry, + bool _isCostInPoly + ) + public + UpgradableModuleFactory("3.0.0", _setupCost, _logicContract, _polymathRegistry, _isCostInPoly) { - version = "1.0.0"; name = "GeneralPermissionManager"; title = "General Permission Manager"; description = "Manage permissions within the Security Token and attached modules"; - compatibleSTVersionRange["lowerBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0)); - compatibleSTVersionRange["upperBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0)); + typesData.push(1); + tagsData.push("Permission Management"); + compatibleSTVersionRange["lowerBound"] = VersionUtils.pack(uint8(3), uint8(0), uint8(0)); + compatibleSTVersionRange["upperBound"] = VersionUtils.pack(uint8(3), uint8(0), uint8(0)); } /** * @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 due to insufficent Allowance provided"); - address permissionManager = new GeneralPermissionManager(msg.sender, address(polyToken)); - /*solium-disable-next-line security/no-block-members*/ - emit GenerateModuleFromFactory(address(permissionManager), getName(), address(this), msg.sender, setupCost, now); + function deploy( + bytes calldata _data + ) + external + returns(address) + { + address permissionManager = address(new GeneralPermissionManagerProxy(logicContracts[latestUpgrade].version, msg.sender, polymathRegistry.getAddress("PolyToken"), logicContracts[latestUpgrade].logicContract)); + _initializeModule(permissionManager, _data); return permissionManager; } - /** - * @notice Type of the Module factory - */ - function getTypes() external view returns(uint8[]) { - uint8[] memory res = new uint8[](1); - res[0] = 1; - return res; - } - - /** - * @notice Returns the instructions associated with the module - */ - function getInstructions() external view returns(string) { - /*solium-disable-next-line max-len*/ - return "Add and remove permissions for the SecurityToken and associated modules. Permission types should be encoded as bytes32 values and attached using withPerm modifier to relevant functions. No initFunction required."; - } - - /** - * @notice Get the tags related to the module factory - */ - function getTags() external view returns(bytes32[]) { - bytes32[] memory availableTags = new bytes32[](0); - return availableTags; - } } diff --git a/contracts/modules/PermissionManager/GeneralPermissionManagerProxy.sol b/contracts/modules/PermissionManager/GeneralPermissionManagerProxy.sol new file mode 100644 index 000000000..6272eb908 --- /dev/null +++ b/contracts/modules/PermissionManager/GeneralPermissionManagerProxy.sol @@ -0,0 +1,36 @@ +pragma solidity 0.5.8; + +import "../../proxy/OwnedUpgradeabilityProxy.sol"; +import "../../Pausable.sol"; +import "openzeppelin-solidity/contracts/utils/ReentrancyGuard.sol"; +import "../../storage/modules/ModuleStorage.sol"; +import "./GeneralPermissionManagerStorage.sol"; + +/** + * @title GeneralPermissionManager module Proxy + */ +contract GeneralPermissionManagerProxy is GeneralPermissionManagerStorage, ModuleStorage, Pausable, ReentrancyGuard, OwnedUpgradeabilityProxy { + + /** + * @notice Constructor + * @param _securityToken Address of the security token + * @param _polyAddress Address of the polytoken + * @param _implementation representing the address of the new implementation to be set + */ + constructor ( + string memory _version, + address _securityToken, + address _polyAddress, + address _implementation + ) + public + ModuleStorage(_securityToken, _polyAddress) + { + require( + _implementation != address(0), + "Implementation address should not be 0x" + ); + _upgradeTo(_version, _implementation); + } + +} diff --git a/contracts/modules/PermissionManager/GeneralPermissionManagerStorage.sol b/contracts/modules/PermissionManager/GeneralPermissionManagerStorage.sol new file mode 100644 index 000000000..a9559c08f --- /dev/null +++ b/contracts/modules/PermissionManager/GeneralPermissionManagerStorage.sol @@ -0,0 +1,15 @@ +pragma solidity 0.5.8; + +/** + * @title Contract used to store layout for the GeneralPermissionManager storage + */ +contract GeneralPermissionManagerStorage { + + // Mapping used to hold the permissions on the modules provided to delegate, module add => delegate add => permission bytes32 => bool + mapping (address => mapping (address => mapping (bytes32 => bool))) public perms; + // Mapping hold the delagate details + mapping (address => bytes32) public delegateDetails; + // Array to track all delegates + address[] public allDelegates; + +} diff --git a/contracts/modules/PermissionManager/IPermissionManager.sol b/contracts/modules/PermissionManager/IPermissionManager.sol index d9b78944d..9872e53d5 100644 --- a/contracts/modules/PermissionManager/IPermissionManager.sol +++ b/contracts/modules/PermissionManager/IPermissionManager.sol @@ -1,10 +1,9 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; /** * @title Interface to be implemented by all permission manager modules */ interface IPermissionManager { - /** * @notice Used to check the permission on delegate corresponds to module contract address * @param _delegate Ethereum address of the delegate @@ -42,13 +41,7 @@ interface IPermissionManager { * @param _valid Bool flag use to switch on/off the permission * @return bool */ - function changePermission( - address _delegate, - address _module, - bytes32 _perm, - bool _valid - ) - external; + function changePermission(address _delegate, address _module, bytes32 _perm, bool _valid) external; /** * @notice Used to change one or more permissions for a single delegate at once @@ -60,11 +53,10 @@ interface IPermissionManager { */ function changePermissionMulti( address _delegate, - address[] _modules, - bytes32[] _perms, - bool[] _valids - ) - external; + address[] calldata _modules, + bytes32[] calldata _perms, + bool[] calldata _valids + ) external; /** * @notice Used to return all delegates with a given permission and module @@ -72,9 +64,9 @@ interface IPermissionManager { * @param _perm Permission flag * @return address[] */ - function getAllDelegatesWithPerm(address _module, bytes32 _perm) external view returns(address[]); + function getAllDelegatesWithPerm(address _module, bytes32 _perm) external view returns(address[] memory); - /** + /** * @notice Used to return all permission of a single or multiple module * @dev possible that function get out of gas is there are lot of modules and perm related to them * @param _delegate Ethereum address of the delegate @@ -82,18 +74,21 @@ interface IPermissionManager { * @return address[] the address array of Modules this delegate has permission * @return bytes32[] the permission array of the corresponding Modules */ - function getAllModulesAndPermsFromTypes(address _delegate, uint8[] _types) external view returns(address[], bytes32[]); + function getAllModulesAndPermsFromTypes(address _delegate, uint8[] calldata _types) external view returns( + address[] memory, + bytes32[] memory + ); /** * @notice Used to get the Permission flag related the `this` contract * @return Array of permission flags */ - function getPermissions() external view returns(bytes32[]); + function getPermissions() external view returns(bytes32[] memory); /** * @notice Used to get all delegates * @return address[] */ - function getAllDelegates() external view returns(address[]); + function getAllDelegates() external view returns(address[] memory); } diff --git a/contracts/modules/STO/CappedSTO.sol b/contracts/modules/STO/Capped/CappedSTO.sol similarity index 80% rename from contracts/modules/STO/CappedSTO.sol rename to contracts/modules/STO/Capped/CappedSTO.sol index 3245aa3f5..3cf2ecc93 100644 --- a/contracts/modules/STO/CappedSTO.sol +++ b/contracts/modules/STO/Capped/CappedSTO.sol @@ -1,27 +1,16 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; -import "./STO.sol"; -import "../../interfaces/ISecurityToken.sol"; -import "openzeppelin-solidity/contracts/ReentrancyGuard.sol"; +import "../STO.sol"; +import "openzeppelin-solidity/contracts/utils/ReentrancyGuard.sol"; import "openzeppelin-solidity/contracts/math/SafeMath.sol"; +import "./CappedSTOStorage.sol"; /** * @title STO module for standard capped crowdsale */ -contract CappedSTO is STO, ReentrancyGuard { +contract CappedSTO is CappedSTOStorage, STO, ReentrancyGuard { using SafeMath for uint256; - // Determine whether users can invest on behalf of a beneficiary - bool public allowBeneficialInvestments = false; - // How many token units a buyer gets (multiplied by 10^18) per wei / base unit of POLY - // If rate is 10^18, buyer will get 1 token unit for every wei / base unit of poly. - uint256 public rate; - //How many token base units this STO will be allowed to sell to investors - // 1 full token = 10^decimals_of_token base units - uint256 public cap; - - mapping (address => uint256) public investors; - /** * Event for token purchase logging * @param purchaser who paid for the tokens @@ -33,16 +22,15 @@ contract CappedSTO is STO, ReentrancyGuard { event SetAllowBeneficialInvestments(bool _allowed); - constructor (address _securityToken, address _polyAddress) public - Module(_securityToken, _polyAddress) - { + constructor(address _securityToken, address _polyToken) public Module(_securityToken, _polyToken) { + } ////////////////////////////////// /** * @notice fallback function ***DO NOT OVERRIDE*** */ - function () external payable { + function() external payable { buyTokens(msg.sender); } @@ -60,11 +48,11 @@ contract CappedSTO is STO, ReentrancyGuard { uint256 _endTime, uint256 _cap, uint256 _rate, - FundRaiseType[] _fundRaiseTypes, - address _fundsReceiver + FundRaiseType[] memory _fundRaiseTypes, + address payable _fundsReceiver ) - public - onlyFactory + public + onlyFactory { require(endTime == 0, "Already configured"); require(_rate > 0, "Rate of token should be greater than 0"); @@ -84,15 +72,15 @@ contract CappedSTO is STO, ReentrancyGuard { /** * @notice This function returns the signature of configure function */ - function getInitFunction() public pure returns (bytes4) { - return bytes4(keccak256("configure(uint256,uint256,uint256,uint256,uint8[],address)")); + function getInitFunction() public pure returns(bytes4) { + return this.configure.selector; } /** * @notice Function to set allowBeneficialInvestments (allow beneficiary to be different to funder) * @param _allowBeneficialInvestments Boolean to allow or disallow beneficial investments */ - function changeAllowBeneficialInvestments(bool _allowBeneficialInvestments) public onlyOwner { + function changeAllowBeneficialInvestments(bool _allowBeneficialInvestments) public withPerm(OPERATOR) { require(_allowBeneficialInvestments != allowBeneficialInvestments, "Does not change value"); allowBeneficialInvestments = _allowBeneficialInvestments; emit SetAllowBeneficialInvestments(allowBeneficialInvestments); @@ -102,12 +90,11 @@ contract CappedSTO is STO, ReentrancyGuard { * @notice Low level token purchase ***DO NOT OVERRIDE*** * @param _beneficiary Address performing the token purchase */ - function buyTokens(address _beneficiary) public payable nonReentrant { + function buyTokens(address _beneficiary) public payable whenNotPaused nonReentrant { if (!allowBeneficialInvestments) { require(_beneficiary == msg.sender, "Beneficiary address does not match msg.sender"); } - require(!paused, "Should not be paused"); require(fundRaiseTypes[uint8(FundRaiseType.ETH)], "Mode of investment is not ETH"); uint256 weiAmount = msg.value; @@ -121,8 +108,7 @@ contract CappedSTO is STO, ReentrancyGuard { * @notice low level token purchase * @param _investedPOLY Amount of POLY invested */ - function buyTokensWithPoly(uint256 _investedPOLY) public nonReentrant{ - require(!paused, "Should not be paused"); + function buyTokensWithPoly(uint256 _investedPOLY) public whenNotPaused nonReentrant { require(fundRaiseTypes[uint8(FundRaiseType.POLY)], "Mode of investment is not POLY"); uint256 refund = _processTx(msg.sender, _investedPOLY); _forwardPoly(msg.sender, wallet, _investedPOLY.sub(refund)); @@ -132,22 +118,23 @@ contract CappedSTO is STO, ReentrancyGuard { * @notice Checks whether the cap has been reached. * @return bool Whether the cap was reached */ - function capReached() public view returns (bool) { + function capReached() public view returns(bool) { return totalTokensSold >= cap; } /** * @notice Return the total no. of tokens sold */ - function getTokensSold() public view returns (uint256) { + function getTokensSold() external view returns (uint256) { return totalTokensSold; } /** * @notice Return the permissions flag that are associated with STO */ - function getPermissions() public view returns(bytes32[]) { - bytes32[] memory allPermissions = new bytes32[](0); + function getPermissions() public view returns(bytes32[] memory) { + bytes32[] memory allPermissions = new bytes32[](1); + allPermissions[0] = OPERATOR; return allPermissions; } @@ -163,16 +150,9 @@ contract CappedSTO is STO, ReentrancyGuard { * @return Boolean value to justify whether the fund raise type is POLY or not, i.e true for POLY. */ function getSTODetails() public view returns(uint256, uint256, uint256, uint256, uint256, uint256, uint256, bool) { - return ( - startTime, - endTime, - cap, - rate, - (fundRaiseTypes[uint8(FundRaiseType.POLY)]) ? fundsRaised[uint8(FundRaiseType.POLY)]: fundsRaised[uint8(FundRaiseType.ETH)], - investorCount, - totalTokensSold, - (fundRaiseTypes[uint8(FundRaiseType.POLY)]) - ); + return (startTime, endTime, cap, rate, (fundRaiseTypes[uint8(FundRaiseType.POLY)]) ? fundsRaised[uint8( + FundRaiseType.POLY + )] : fundsRaised[uint8(FundRaiseType.ETH)], investorCount, totalTokensSold, (fundRaiseTypes[uint8(FundRaiseType.POLY)])); } // ----------------------------------------- @@ -184,7 +164,6 @@ contract CappedSTO is STO, ReentrancyGuard { * @param _investedAmount Value in wei involved in the purchase */ function _processTx(address _beneficiary, uint256 _investedAmount) internal returns(uint256 refund) { - _preValidatePurchase(_beneficiary, _investedAmount); // calculate token amount to be created uint256 tokens; @@ -201,6 +180,7 @@ contract CappedSTO is STO, ReentrancyGuard { _processPurchase(_beneficiary, tokens); emit TokenPurchase(msg.sender, _beneficiary, _investedAmount, tokens); + } /** @@ -212,6 +192,7 @@ contract CappedSTO is STO, ReentrancyGuard { function _preValidatePurchase(address _beneficiary, uint256 _investedAmount) internal view { require(_beneficiary != address(0), "Beneficiary address should not be 0x"); require(_investedAmount != 0, "Amount invested should not be equal to 0"); + require(_canBuy(_beneficiary), "Unauthorized"); /*solium-disable-next-line security/no-block-members*/ require(now >= startTime && now <= endTime, "Offering is closed/Not yet started"); } @@ -223,7 +204,7 @@ contract CappedSTO is STO, ReentrancyGuard { * @param _tokenAmount Number of tokens to be emitted */ function _deliverTokens(address _beneficiary, uint256 _tokenAmount) internal { - require(ISecurityToken(securityToken).mint(_beneficiary, _tokenAmount), "Error in minting the tokens"); + securityToken.issue(_beneficiary, _tokenAmount, ""); } /** @@ -246,13 +227,13 @@ contract CappedSTO is STO, ReentrancyGuard { * @return Number of tokens that can be purchased with the specified _investedAmount * @return Remaining amount that should be refunded to the investor */ - function _getTokenAmount(uint256 _investedAmount) internal view returns (uint256 tokens, uint256 refund) { + function _getTokenAmount(uint256 _investedAmount) internal view returns(uint256 tokens, uint256 refund) { tokens = _investedAmount.mul(rate); tokens = tokens.div(uint256(10) ** 18); if (totalTokensSold.add(tokens) > cap) { tokens = cap.sub(totalTokensSold); } - uint256 granularity = ISecurityToken(securityToken).granularity(); + uint256 granularity = securityToken.granularity(); tokens = tokens.div(granularity); tokens = tokens.mul(granularity); require(tokens > 0, "Cap reached"); diff --git a/contracts/modules/STO/Capped/CappedSTOFactory.sol b/contracts/modules/STO/Capped/CappedSTOFactory.sol new file mode 100644 index 000000000..abed3ffc4 --- /dev/null +++ b/contracts/modules/STO/Capped/CappedSTOFactory.sol @@ -0,0 +1,50 @@ +pragma solidity 0.5.8; + +import "../../UpgradableModuleFactory.sol"; +import "./CappedSTOProxy.sol"; + +/** + * @title Factory for deploying CappedSTO module + */ +contract CappedSTOFactory is UpgradableModuleFactory { + + /** + * @notice Constructor + * @param _setupCost Setup cost of the module + * @param _logicContract Contract address that contains the logic related to `description` + * @param _polymathRegistry Address of the Polymath registry + * @param _isCostInPoly true = cost in Poly, false = USD + */ + constructor ( + uint256 _setupCost, + address _logicContract, + address _polymathRegistry, + bool _isCostInPoly + ) + public + UpgradableModuleFactory("3.0.0", _setupCost, _logicContract, _polymathRegistry, _isCostInPoly) + { + name = "CappedSTO"; + title = "Capped STO"; + description = "This smart contract creates a maximum number of tokens (i.e. hard cap) which the total aggregate of tokens acquired by all investors cannot exceed. Security tokens are sent to the investor upon reception of the funds (ETH or POLY), and any security tokens left upon termination of the offering will not be minted."; + typesData.push(3); + tagsData.push("Capped"); + tagsData.push("ETH"); + tagsData.push("POLY"); + tagsData.push("STO"); + compatibleSTVersionRange["lowerBound"] = VersionUtils.pack(uint8(3), uint8(0), uint8(0)); + compatibleSTVersionRange["upperBound"] = VersionUtils.pack(uint8(3), uint8(0), uint8(0)); + } + + /** + * @notice Used to launch the Module with the help of factory + * @param _data Data used for the intialization of the module factory variables + * @return address Contract address of the Module + */ + function deploy(bytes calldata _data) external returns(address) { + address cappedSTO = address(new CappedSTOProxy(logicContracts[latestUpgrade].version, msg.sender, polymathRegistry.getAddress("PolyToken"), logicContracts[latestUpgrade].logicContract)); + _initializeModule(cappedSTO, _data); + return cappedSTO; + } + +} diff --git a/contracts/modules/STO/Capped/CappedSTOProxy.sol b/contracts/modules/STO/Capped/CappedSTOProxy.sol new file mode 100644 index 000000000..7abafe7f3 --- /dev/null +++ b/contracts/modules/STO/Capped/CappedSTOProxy.sol @@ -0,0 +1,37 @@ +pragma solidity 0.5.8; + +import "../../../proxy/OwnedUpgradeabilityProxy.sol"; +import "../../../Pausable.sol"; +import "openzeppelin-solidity/contracts/utils/ReentrancyGuard.sol"; +import "../../../storage/modules/STO/STOStorage.sol"; +import "../../../storage/modules/ModuleStorage.sol"; +import "./CappedSTOStorage.sol"; + +/** + * @title CappedSTO module Proxy + */ +contract CappedSTOProxy is CappedSTOStorage, STOStorage, ModuleStorage, Pausable, ReentrancyGuard, OwnedUpgradeabilityProxy { + + /** + * @notice Constructor + * @param _securityToken Address of the security token + * @param _polyAddress Address of the polytoken + * @param _implementation representing the address of the new implementation to be set + */ + constructor( + string memory _version, + address _securityToken, + address _polyAddress, + address _implementation + ) + public + ModuleStorage(_securityToken, _polyAddress) + { + require( + _implementation != address(0), + "Implementation address should not be 0x" + ); + _upgradeTo(_version, _implementation); + } + +} diff --git a/contracts/modules/STO/Capped/CappedSTOStorage.sol b/contracts/modules/STO/Capped/CappedSTOStorage.sol new file mode 100644 index 000000000..f5c0fabef --- /dev/null +++ b/contracts/modules/STO/Capped/CappedSTOStorage.sol @@ -0,0 +1,19 @@ +pragma solidity 0.5.8; + +/** + * @title Contract used to store layout for the CappedSTO storage + */ +contract CappedSTOStorage { + + // Determine whether users can invest on behalf of a beneficiary + bool public allowBeneficialInvestments = false; + // How many token units a buyer gets (multiplied by 10^18) per wei / base unit of POLY + // If rate is 10^18, buyer will get 1 token unit for every wei / base unit of poly. + uint256 public rate; + //How many token base units this STO will be allowed to sell to investors + // 1 full token = 10^decimals_of_token base units + uint256 public cap; + + mapping (address => uint256) public investors; + +} diff --git a/contracts/modules/STO/CappedSTOFactory.sol b/contracts/modules/STO/CappedSTOFactory.sol deleted file mode 100644 index 273389737..000000000 --- a/contracts/modules/STO/CappedSTOFactory.sol +++ /dev/null @@ -1,74 +0,0 @@ -pragma solidity ^0.4.24; - -import "./CappedSTO.sol"; -import "../ModuleFactory.sol"; -import "../../libraries/Util.sol"; - -/** - * @title Factory for deploying CappedSTO module - */ -contract CappedSTOFactory is ModuleFactory { - - /** - * @notice Constructor - * @param _polyAddress Address of the polytoken - */ - constructor (address _polyAddress, uint256 _setupCost, uint256 _usageCost, uint256 _subscriptionCost) public - ModuleFactory(_polyAddress, _setupCost, _usageCost, _subscriptionCost) - { - version = "2.1.0"; - name = "CappedSTO"; - title = "Capped STO"; - description = "This smart contract creates a maximum number of tokens (i.e. hard cap) which the total aggregate of tokens acquired by all investors cannot exceed. Security tokens are sent to the investor upon reception of the funds (ETH or POLY), and any security tokens left upon termination of the offering will not be minted."; - compatibleSTVersionRange["lowerBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0)); - compatibleSTVersionRange["upperBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0)); - } - - /** - * @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), "Sufficent Allowance is not provided"); - //Check valid bytes - can only call module init function - CappedSTO cappedSTO = new CappedSTO(msg.sender, address(polyToken)); - //Checks that _data is valid (not calling anything it shouldn't) - require(Util.getSig(_data) == cappedSTO.getInitFunction(), "Invalid data"); - /*solium-disable-next-line security/no-low-level-calls*/ - require(address(cappedSTO).call(_data), "Unsuccessfull call"); - /*solium-disable-next-line security/no-block-members*/ - emit GenerateModuleFromFactory(address(cappedSTO), getName(), address(this), msg.sender, setupCost, now); - return address(cappedSTO); - } - - /** - * @notice Type of the Module factory - */ - function getTypes() external view returns(uint8[]) { - uint8[] memory res = new uint8[](1); - res[0] = 3; - return res; - } - - /** - * @notice Returns the instructions associated with the module - */ - function getInstructions() external view returns(string) { - /*solium-disable-next-line max-len*/ - return "Initialises a capped STO. Init parameters are _startTime (time STO starts), _endTime (time STO ends), _cap (cap in tokens for STO), _rate (POLY/ETH to token rate), _fundRaiseType (whether you are raising in POLY or ETH), _polyToken (address of POLY token), _fundsReceiver (address which will receive funds)"; - } - - /** - * @notice Get the tags related to the module factory - */ - function getTags() external view returns(bytes32[]) { - bytes32[] memory availableTags = new bytes32[](4); - availableTags[0] = "Capped"; - availableTags[1] = "Non-refundable"; - availableTags[2] = "POLY"; - availableTags[3] = "ETH"; - return availableTags; - } - -} diff --git a/contracts/modules/STO/PreSaleSTO.sol b/contracts/modules/STO/PreSale/PreSaleSTO.sol similarity index 76% rename from contracts/modules/STO/PreSaleSTO.sol rename to contracts/modules/STO/PreSale/PreSaleSTO.sol index 4dacaa2d7..c23190f3c 100644 --- a/contracts/modules/STO/PreSaleSTO.sol +++ b/contracts/modules/STO/PreSale/PreSaleSTO.sol @@ -1,29 +1,23 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; -import "./STO.sol"; -import "../../interfaces/ISecurityToken.sol"; +import "../STO.sol"; import "openzeppelin-solidity/contracts/math/SafeMath.sol"; +import "./PreSaleSTOStorage.sol"; /** * @title STO module for private presales */ -contract PreSaleSTO is STO { +contract PreSaleSTO is PreSaleSTOStorage, STO { using SafeMath for uint256; - bytes32 public constant PRE_SALE_ADMIN = "PRE_SALE_ADMIN"; - event TokensAllocated(address _investor, uint256 _amount); - mapping (address => uint256) public investors; - /** * @notice Constructor * @param _securityToken Address of the security token - * @param _polyAddress Address of the polytoken */ - constructor (address _securityToken, address _polyAddress) public - Module(_securityToken, _polyAddress) - { + constructor(address _securityToken, address _polyToken) public Module(_securityToken, _polyToken) { + } /** @@ -38,30 +32,30 @@ contract PreSaleSTO is STO { /** * @notice This function returns the signature of the configure function */ - function getInitFunction() public pure returns (bytes4) { - return bytes4(keccak256("configure(uint256)")); + function getInitFunction() public pure returns(bytes4) { + return this.configure.selector; } /** * @notice Returns the total no. of investors */ - function getNumberInvestors() public view returns (uint256) { + function getNumberInvestors() public view returns(uint256) { return investorCount; } /** * @notice Returns the total no. of tokens sold */ - function getTokensSold() public view returns (uint256) { + function getTokensSold() external view returns(uint256) { return totalTokensSold; } /** * @notice Returns the permissions flag that are associated with STO */ - function getPermissions() public view returns(bytes32[]) { + function getPermissions() public view returns(bytes32[] memory) { bytes32[] memory allPermissions = new bytes32[](1); - allPermissions[0] = PRE_SALE_ADMIN; + allPermissions[0] = ADMIN; return allPermissions; } @@ -79,12 +73,13 @@ contract PreSaleSTO is STO { uint256 _polyContributed ) public - withPerm(PRE_SALE_ADMIN) + withPerm(ADMIN) { /*solium-disable-next-line security/no-block-members*/ require(now <= endTime, "Already passed Endtime"); require(_amount > 0, "No. of tokens provided should be greater the zero"); - ISecurityToken(securityToken).mint(_investor, _amount); + require(_canBuy(_investor), "Unauthorized"); + securityToken.issue(_investor, _amount, ""); if (investors[_investor] == uint256(0)) { investorCount = investorCount.add(1); } @@ -103,13 +98,13 @@ contract PreSaleSTO is STO { * @param _polyContributed Array of amount of POLY contributed by each investor */ function allocateTokensMulti( - address[] _investors, - uint256[] _amounts, - uint256[] _etherContributed, - uint256[] _polyContributed + address[] memory _investors, + uint256[] memory _amounts, + uint256[] memory _etherContributed, + uint256[] memory _polyContributed ) public - withPerm(PRE_SALE_ADMIN) + withPerm(ADMIN) { require(_investors.length == _amounts.length, "Mis-match in length of the arrays"); require(_etherContributed.length == _polyContributed.length, "Mis-match in length of the arrays"); diff --git a/contracts/modules/STO/PreSale/PreSaleSTOFactory.sol b/contracts/modules/STO/PreSale/PreSaleSTOFactory.sol new file mode 100644 index 000000000..de2654276 --- /dev/null +++ b/contracts/modules/STO/PreSale/PreSaleSTOFactory.sol @@ -0,0 +1,48 @@ +pragma solidity 0.5.8; + +import "../../UpgradableModuleFactory.sol"; +import "./PreSaleSTOProxy.sol"; + +/** + * @title Factory for deploying PreSaleSTO module + */ +contract PreSaleSTOFactory is UpgradableModuleFactory { + + /** + * @notice Constructor + * @param _setupCost Setup cost of the module + * @param _logicContract Contract address that contains the logic related to `description` + * @param _polymathRegistry Address of the Polymath registry + * @param _isCostInPoly true = cost in Poly, false = USD + */ + constructor ( + uint256 _setupCost, + address _logicContract, + address _polymathRegistry, + bool _isCostInPoly + ) + public + UpgradableModuleFactory("3.0.0", _setupCost, _logicContract, _polymathRegistry, _isCostInPoly) + { + name = "PreSaleSTO"; + title = "PreSale STO"; + description = "Allows Issuer to configure pre-sale token allocations"; + typesData.push(3); + tagsData.push("PreSale"); + tagsData.push("STO"); + compatibleSTVersionRange["lowerBound"] = VersionUtils.pack(uint8(3), uint8(0), uint8(0)); + compatibleSTVersionRange["upperBound"] = VersionUtils.pack(uint8(3), uint8(0), uint8(0)); + } + + /** + * @notice Used to launch the Module with the help of factory + * @param _data Data used for the intialization of the module factory variables + * @return address Contract address of the Module + */ + function deploy(bytes calldata _data) external returns(address) { + address preSaleSTO = address(new PreSaleSTOProxy(logicContracts[latestUpgrade].version, msg.sender, polymathRegistry.getAddress("PolyToken"), logicContracts[latestUpgrade].logicContract)); + _initializeModule(preSaleSTO, _data); + return preSaleSTO; + } + +} diff --git a/contracts/modules/STO/PreSale/PreSaleSTOProxy.sol b/contracts/modules/STO/PreSale/PreSaleSTOProxy.sol new file mode 100644 index 000000000..c2d3268ad --- /dev/null +++ b/contracts/modules/STO/PreSale/PreSaleSTOProxy.sol @@ -0,0 +1,32 @@ +pragma solidity 0.5.8; + +import "../../../proxy/OwnedUpgradeabilityProxy.sol"; +import "../../../Pausable.sol"; +import "openzeppelin-solidity/contracts/utils/ReentrancyGuard.sol"; +import "../../../storage/modules/STO/STOStorage.sol"; +import "../../../storage/modules/ModuleStorage.sol"; +import "./PreSaleSTOStorage.sol"; + +/** + * @title PreSaleSTO module Proxy + */ +contract PreSaleSTOProxy is PreSaleSTOStorage, STOStorage, ModuleStorage, Pausable, ReentrancyGuard, OwnedUpgradeabilityProxy { + + /** + * @notice Constructor + * @param _securityToken Address of the security token + * @param _polyAddress Address of the polytoken + * @param _implementation representing the address of the new implementation to be set + */ + constructor (string memory _version, address _securityToken, address _polyAddress, address _implementation) + public + ModuleStorage(_securityToken, _polyAddress) + { + require( + _implementation != address(0), + "Implementation address should not be 0x" + ); + _upgradeTo(_version, _implementation); + } + +} diff --git a/contracts/modules/STO/PreSale/PreSaleSTOStorage.sol b/contracts/modules/STO/PreSale/PreSaleSTOStorage.sol new file mode 100644 index 000000000..8ff56cd77 --- /dev/null +++ b/contracts/modules/STO/PreSale/PreSaleSTOStorage.sol @@ -0,0 +1,10 @@ +pragma solidity 0.5.8; + +/** + * @title Contract used to store layout for the PreSaleSTO storage + */ +contract PreSaleSTOStorage { + + mapping (address => uint256) public investors; + +} diff --git a/contracts/modules/STO/PreSaleSTOFactory.sol b/contracts/modules/STO/PreSaleSTOFactory.sol deleted file mode 100644 index 035c4bad5..000000000 --- a/contracts/modules/STO/PreSaleSTOFactory.sol +++ /dev/null @@ -1,72 +0,0 @@ -pragma solidity ^0.4.24; - -import "./PreSaleSTO.sol"; -import "../ModuleFactory.sol"; -import "../../libraries/Util.sol"; - -/** - * @title Factory for deploying PreSaleSTO module - */ -contract PreSaleSTOFactory is ModuleFactory { - - /** - * @notice Constructor - * @param _polyAddress Address of the polytoken - */ - constructor (address _polyAddress, uint256 _setupCost, uint256 _usageCost, uint256 _subscriptionCost) public - ModuleFactory(_polyAddress, _setupCost, _usageCost, _subscriptionCost) - { - version = "1.0.0"; - name = "PreSaleSTO"; - title = "PreSale STO"; - description = "Allows Issuer to configure pre-sale token allocations"; - compatibleSTVersionRange["lowerBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0)); - compatibleSTVersionRange["upperBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0)); - } - - /** - * @notice Used to launch the Module with the help of factory - * @param _data Data used for the intialization of the module factory variables - * @return address Contract address of the Module - */ - function deploy(bytes _data) external returns(address) { - if (setupCost > 0) { - require(polyToken.transferFrom(msg.sender, owner, setupCost), "Sufficent Allowance is not provided"); - } - //Check valid bytes - can only call module init function - PreSaleSTO preSaleSTO = new PreSaleSTO(msg.sender, address(polyToken)); - //Checks that _data is valid (not calling anything it shouldn't) - require(Util.getSig(_data) == preSaleSTO.getInitFunction(), "Invalid data"); - /*solium-disable-next-line security/no-low-level-calls*/ - require(address(preSaleSTO).call(_data), "Unsuccessfull call"); - /*solium-disable-next-line security/no-block-members*/ - emit GenerateModuleFromFactory(address(preSaleSTO), getName(), address(this), msg.sender, setupCost, now); - return address(preSaleSTO); - } - - /** - * @notice Type of the Module factory - */ - function getTypes() external view returns(uint8[]) { - uint8[] memory res = new uint8[](1); - res[0] = 3; - return res; - } - - /** - * @notice Returns the instructions associated with the module - */ - function getInstructions() external view returns(string) { - return "Configure and track pre-sale token allocations"; - } - - /** - * @notice Get the tags related to the module factory - */ - function getTags() external view returns(bytes32[]) { - bytes32[] memory availableTags = new bytes32[](1); - availableTags[0] = "Presale"; - return availableTags; - } - -} diff --git a/contracts/modules/STO/STO.sol b/contracts/modules/STO/STO.sol index d05de918e..af68cca4c 100644 --- a/contracts/modules/STO/STO.sol +++ b/contracts/modules/STO/STO.sol @@ -1,47 +1,40 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; -import "../../Pausable.sol"; import "../Module.sol"; -import "../../interfaces/IERC20.sol"; -import "../../interfaces/ISTO.sol"; -import "./STOStorage.sol"; +import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; +import "../../storage/modules/STO/STOStorage.sol"; import "openzeppelin-solidity/contracts/math/SafeMath.sol"; +import "../../interfaces/ISTO.sol"; /** - * @title Interface to be implemented by all STO modules + * @title Base abstract contract to be extended by all STO modules */ -contract STO is ISTO, STOStorage, Module, Pausable { +contract STO is ISTO, STOStorage, Module { using SafeMath for uint256; - enum FundRaiseType { ETH, POLY, SC } - - // Event - event SetFundRaiseTypes(FundRaiseType[] _fundRaiseTypes); - /** * @notice Returns funds raised by the STO */ - function getRaised(FundRaiseType _fundRaiseType) public view returns (uint256) { + function getRaised(FundRaiseType _fundRaiseType) public view returns(uint256) { return fundsRaised[uint8(_fundRaiseType)]; } /** - * @notice Pause (overridden function) + * @notice Returns the total no. of tokens sold */ - function pause() public onlyOwner { - /*solium-disable-next-line security/no-block-members*/ - require(now < endTime, "STO has been finalized"); - super._pause(); - } + function getTokensSold() external view returns (uint256); /** - * @notice Unpause (overridden function) + * @notice Pause (overridden function) + * @dev Only securityToken owner restriction applied on the super function */ - function unpause() public onlyOwner { - super._unpause(); + function pause() public { + /*solium-disable-next-line security/no-block-members*/ + require(now < endTime, "STO has been finalized"); + super.pause(); } - function _setFundRaiseType(FundRaiseType[] _fundRaiseTypes) internal { + function _setFundRaiseType(FundRaiseType[] memory _fundRaiseTypes) internal { // FundRaiseType[] parameter type ensures only valid values for _fundRaiseTypes require(_fundRaiseTypes.length > 0 && _fundRaiseTypes.length <= 3, "Raise type is not specified"); fundRaiseTypes[uint8(FundRaiseType.ETH)] = false; @@ -53,24 +46,13 @@ contract STO is ISTO, STOStorage, Module, Pausable { emit SetFundRaiseTypes(_fundRaiseTypes); } - /** - * @notice Reclaims ERC20Basic compatible tokens - * @dev We duplicate here due to the overriden owner & onlyOwner - * @param _tokenContract The address of the token contract - */ - function reclaimERC20(address _tokenContract) external onlyOwner { - require(_tokenContract != address(0), "Invalid address"); - IERC20 token = IERC20(_tokenContract); - uint256 balance = token.balanceOf(address(this)); - require(token.transfer(msg.sender, balance), "Transfer failed"); + function _canBuy(address _investor) internal view returns(bool) { + IDataStore dataStore = getDataStore(); + uint256 flags = dataStore.getUint256(_getKey(INVESTORFLAGS, _investor)); + return(flags & (uint256(1) << 1) == 0); } - /** - * @notice Reclaims ETH - * @dev We duplicate here due to the overriden owner & onlyOwner - */ - function reclaimETH() external onlyOwner { - msg.sender.transfer(address(this).balance); + function _getKey(bytes32 _key1, address _key2) internal pure returns(bytes32) { + return bytes32(keccak256(abi.encodePacked(_key1, _key2))); } - } diff --git a/contracts/modules/STO/USDTieredSTO.sol b/contracts/modules/STO/USDTiered/USDTieredSTO.sol similarity index 62% rename from contracts/modules/STO/USDTieredSTO.sol rename to contracts/modules/STO/USDTiered/USDTieredSTO.sol index e40e4c6b0..8283ca349 100644 --- a/contracts/modules/STO/USDTieredSTO.sol +++ b/contracts/modules/STO/USDTiered/USDTieredSTO.sol @@ -1,22 +1,20 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; -import "./STO.sol"; -import "../../interfaces/ISecurityToken.sol"; -import "../../interfaces/IOracle.sol"; -import "../../RegistryUpdater.sol"; -import "../../libraries/DecimalMath.sol"; +import "../STO.sol"; +import "../../../interfaces/IPolymathRegistry.sol"; +import "../../../interfaces/IOracle.sol"; +import "../../../libraries/DecimalMath.sol"; import "openzeppelin-solidity/contracts/math/SafeMath.sol"; -import "openzeppelin-solidity/contracts/ReentrancyGuard.sol"; -import "../../storage/USDTieredSTOStorage.sol"; +import "./USDTieredSTOStorage.sol"; /** * @title STO module for standard capped crowdsale */ -contract USDTieredSTO is USDTieredSTOStorage, STO, ReentrancyGuard { +contract USDTieredSTO is USDTieredSTOStorage, STO { using SafeMath for uint256; - string public constant POLY_ORACLE = "PolyUsdOracle"; - string public constant ETH_ORACLE = "EthUsdOracle"; + string internal constant POLY_ORACLE = "PolyUsdOracle"; + string internal constant ETH_ORACLE = "EthUsdOracle"; //////////// // Events // @@ -24,7 +22,6 @@ contract USDTieredSTO is USDTieredSTOStorage, STO, ReentrancyGuard { event SetAllowBeneficialInvestments(bool _allowed); event SetNonAccreditedLimit(address _investor, uint256 _limit); - event SetAccredited(address _investor, bool _accredited); event TokenPurchase( address indexed _purchaser, address indexed _beneficiary, @@ -43,37 +40,28 @@ contract USDTieredSTO is USDTieredSTOStorage, STO, ReentrancyGuard { uint256 _rate ); event ReserveTokenMint(address indexed _owner, address indexed _wallet, uint256 _tokens, uint256 _latestTier); - event SetAddresses( - address indexed _wallet, - address indexed _reserveWallet, - address[] _usdTokens - ); - event SetLimits( - uint256 _nonAccreditedLimitUSD, - uint256 _minimumInvestmentUSD - ); - event SetTimes( - uint256 _startTime, - uint256 _endTime - ); + event SetAddresses(address indexed _wallet, IERC20[] _usdTokens); + event SetLimits(uint256 _nonAccreditedLimitUSD, uint256 _minimumInvestmentUSD); + event SetTimes(uint256 _startTime, uint256 _endTime); event SetTiers( uint256[] _ratePerTier, uint256[] _ratePerTierDiscountPoly, uint256[] _tokensPerTierTotal, uint256[] _tokensPerTierDiscountPoly ); + event SetTreasuryWallet(address _oldWallet, address _newWallet); /////////////// // Modifiers // /////////////// - modifier validETH { + modifier validETH() { require(_getOracle(bytes32("ETH"), bytes32("USD")) != address(0), "Invalid Oracle"); require(fundRaiseTypes[uint8(FundRaiseType.ETH)], "ETH not allowed"); _; } - modifier validPOLY { + modifier validPOLY() { require(_getOracle(bytes32("POLY"), bytes32("USD")) != address(0), "Invalid Oracle"); require(fundRaiseTypes[uint8(FundRaiseType.POLY)], "POLY not allowed"); _; @@ -88,10 +76,8 @@ contract USDTieredSTO is USDTieredSTOStorage, STO, ReentrancyGuard { // STO Configuration // /////////////////////// - constructor (address _securityToken, address _polyAddress) - public - Module(_securityToken, _polyAddress) - { + constructor(address _securityToken, address _polyAddress) public Module(_securityToken, _polyAddress) { + } /** @@ -104,23 +90,26 @@ contract USDTieredSTO is USDTieredSTOStorage, STO, ReentrancyGuard { * @param _minimumInvestmentUSD Minimun investment in USD (* 10**18) * @param _fundRaiseTypes Types of currency used to collect the funds * @param _wallet Ethereum account address to hold the funds - * @param _reserveWallet Ethereum account address to receive unsold tokens - * @param _usdTokens Array of contract addressess of the stable coins + * @param _treasuryWallet Ethereum account address to receive unsold tokens + * @param _usdTokens Contract address of the stable coins */ function configure( uint256 _startTime, uint256 _endTime, - uint256[] _ratePerTier, - uint256[] _ratePerTierDiscountPoly, - uint256[] _tokensPerTierTotal, - uint256[] _tokensPerTierDiscountPoly, + uint256[] memory _ratePerTier, + uint256[] memory _ratePerTierDiscountPoly, + uint256[] memory _tokensPerTierTotal, + uint256[] memory _tokensPerTierDiscountPoly, uint256 _nonAccreditedLimitUSD, uint256 _minimumInvestmentUSD, - FundRaiseType[] _fundRaiseTypes, - address _wallet, - address _reserveWallet, - address[] _usdTokens - ) public onlyFactory { + FundRaiseType[] memory _fundRaiseTypes, + address payable _wallet, + address _treasuryWallet, + IERC20[] memory _usdTokens + ) + public + onlyFactory + { oracleKeys[bytes32("ETH")][bytes32("USD")] = ETH_ORACLE; oracleKeys[bytes32("POLY")][bytes32("USD")] = POLY_ORACLE; require(endTime == 0, "Already configured"); @@ -128,7 +117,7 @@ contract USDTieredSTO is USDTieredSTOStorage, STO, ReentrancyGuard { _modifyTiers(_ratePerTier, _ratePerTierDiscountPoly, _tokensPerTierTotal, _tokensPerTierDiscountPoly); // NB - _setFundRaiseType must come before modifyAddresses _setFundRaiseType(_fundRaiseTypes); - _modifyAddresses(_wallet, _reserveWallet, _usdTokens); + _modifyAddresses(_wallet, _treasuryWallet, _usdTokens); _modifyLimits(_nonAccreditedLimitUSD, _minimumInvestmentUSD); } @@ -136,9 +125,8 @@ contract USDTieredSTO is USDTieredSTOStorage, STO, ReentrancyGuard { * @dev Modifies fund raise types * @param _fundRaiseTypes Array of fund raise types to allow */ - function modifyFunding(FundRaiseType[] _fundRaiseTypes) external onlyOwner { - /*solium-disable-next-line security/no-block-members*/ - require(now < startTime, "STO already started"); + function modifyFunding(FundRaiseType[] calldata _fundRaiseTypes) external withPerm(OPERATOR) { + _isSTOStarted(); _setFundRaiseType(_fundRaiseTypes); } @@ -147,12 +135,8 @@ contract USDTieredSTO is USDTieredSTOStorage, STO, ReentrancyGuard { * @param _nonAccreditedLimitUSD max non accredited invets limit * @param _minimumInvestmentUSD overall minimum investment limit */ - function modifyLimits( - uint256 _nonAccreditedLimitUSD, - uint256 _minimumInvestmentUSD - ) external onlyOwner { - /*solium-disable-next-line security/no-block-members*/ - require(now < startTime, "STO already started"); + function modifyLimits(uint256 _nonAccreditedLimitUSD, uint256 _minimumInvestmentUSD) external withPerm(OPERATOR) { + _isSTOStarted(); _modifyLimits(_nonAccreditedLimitUSD, _minimumInvestmentUSD); } @@ -164,13 +148,15 @@ contract USDTieredSTO is USDTieredSTOStorage, STO, ReentrancyGuard { * @param _tokensPerTierDiscountPoly Array of discounted tokens per tier */ function modifyTiers( - uint256[] _ratePerTier, - uint256[] _ratePerTierDiscountPoly, - uint256[] _tokensPerTierTotal, - uint256[] _tokensPerTierDiscountPoly - ) external onlyOwner { - /*solium-disable-next-line security/no-block-members*/ - require(now < startTime, "STO already started"); + uint256[] calldata _ratePerTier, + uint256[] calldata _ratePerTierDiscountPoly, + uint256[] calldata _tokensPerTierTotal, + uint256[] calldata _tokensPerTierDiscountPoly + ) + external + withPerm(OPERATOR) + { + _isSTOStarted(); _modifyTiers(_ratePerTier, _ratePerTierDiscountPoly, _tokensPerTierTotal, _tokensPerTierDiscountPoly); } @@ -179,54 +165,68 @@ contract USDTieredSTO is USDTieredSTOStorage, STO, ReentrancyGuard { * @param _startTime start time of sto * @param _endTime end time of sto */ - function modifyTimes( - uint256 _startTime, - uint256 _endTime - ) external onlyOwner { - /*solium-disable-next-line security/no-block-members*/ - require(now < startTime, "STO already started"); + function modifyTimes(uint256 _startTime, uint256 _endTime) external withPerm(OPERATOR) { + _isSTOStarted(); _modifyTimes(_startTime, _endTime); } + function _isSTOStarted() internal view { + /*solium-disable-next-line security/no-block-members*/ + require(now < startTime, "Already started"); + } + /** * @dev Modifies addresses used as wallet, reserve wallet and usd token * @param _wallet Address of wallet where funds are sent - * @param _reserveWallet Address of wallet where unsold tokens are sent + * @param _treasuryWallet Address of wallet where unsold tokens are sent * @param _usdTokens Address of usd tokens */ - function modifyAddresses( - address _wallet, - address _reserveWallet, - address[] _usdTokens - ) external onlyOwner { - _modifyAddresses(_wallet, _reserveWallet, _usdTokens); + function modifyAddresses(address payable _wallet, address _treasuryWallet, IERC20[] calldata _usdTokens) external { + _onlySecurityTokenOwner(); + _modifyAddresses(_wallet, _treasuryWallet, _usdTokens); } - function _modifyLimits( - uint256 _nonAccreditedLimitUSD, - uint256 _minimumInvestmentUSD - ) internal { + /** + * @dev Modifies Oracle address. + * By default, Polymath oracles are used but issuer can overide them using this function + * Set _oracleAddress to 0x0 to fallback to using Polymath oracles + * @param _fundRaiseType Actual currency + * @param _oracleAddress address of the oracle + */ + function modifyOracle(FundRaiseType _fundRaiseType, address _oracleAddress) external { + _onlySecurityTokenOwner(); + if (_fundRaiseType == FundRaiseType.ETH) { + customOracles[bytes32("ETH")][bytes32("USD")] = _oracleAddress; + } else { + require(_fundRaiseType == FundRaiseType.POLY, "Invalid currency"); + customOracles[bytes32("POLY")][bytes32("USD")] = _oracleAddress; + } + } + + function _modifyLimits(uint256 _nonAccreditedLimitUSD, uint256 _minimumInvestmentUSD) internal { minimumInvestmentUSD = _minimumInvestmentUSD; nonAccreditedLimitUSD = _nonAccreditedLimitUSD; emit SetLimits(minimumInvestmentUSD, nonAccreditedLimitUSD); } function _modifyTiers( - uint256[] _ratePerTier, - uint256[] _ratePerTierDiscountPoly, - uint256[] _tokensPerTierTotal, - uint256[] _tokensPerTierDiscountPoly - ) internal { - require(_tokensPerTierTotal.length > 0, "No tiers provided"); - require(_ratePerTier.length == _tokensPerTierTotal.length && + uint256[] memory _ratePerTier, + uint256[] memory _ratePerTierDiscountPoly, + uint256[] memory _tokensPerTierTotal, + uint256[] memory _tokensPerTierDiscountPoly + ) + internal + { + require( + _tokensPerTierTotal.length > 0 && + _ratePerTier.length == _tokensPerTierTotal.length && _ratePerTierDiscountPoly.length == _tokensPerTierTotal.length && _tokensPerTierDiscountPoly.length == _tokensPerTierTotal.length, - "Tier data length mismatch" + "Invalid Input" ); delete tiers; for (uint256 i = 0; i < _ratePerTier.length; i++) { - require(_ratePerTier[i] > 0, "Invalid rate"); - require(_tokensPerTierTotal[i] > 0, "Invalid token amount"); + require(_ratePerTier[i] > 0 && _tokensPerTierTotal[i] > 0, "Invalid value"); require(_tokensPerTierDiscountPoly[i] <= _tokensPerTierTotal[i], "Too many discounted tokens"); require(_ratePerTierDiscountPoly[i] <= _ratePerTier[i], "Invalid discount"); tiers.push(Tier(_ratePerTier[i], _ratePerTierDiscountPoly[i], _tokensPerTierTotal[i], _tokensPerTierDiscountPoly[i], 0, 0)); @@ -234,10 +234,7 @@ contract USDTieredSTO is USDTieredSTOStorage, STO, ReentrancyGuard { emit SetTiers(_ratePerTier, _ratePerTierDiscountPoly, _tokensPerTierTotal, _tokensPerTierDiscountPoly); } - function _modifyTimes( - uint256 _startTime, - uint256 _endTime - ) internal { + function _modifyTimes(uint256 _startTime, uint256 _endTime) internal { /*solium-disable-next-line security/no-block-members*/ require((_endTime > _startTime) && (_startTime > now), "Invalid times"); startTime = _startTime; @@ -245,27 +242,25 @@ contract USDTieredSTO is USDTieredSTOStorage, STO, ReentrancyGuard { emit SetTimes(_startTime, _endTime); } - function _modifyAddresses( - address _wallet, - address _reserveWallet, - address[] _usdTokens - ) internal { - require(_wallet != address(0) && _reserveWallet != address(0), "Invalid wallet"); + function _modifyAddresses(address payable _wallet, address _treasuryWallet, IERC20[] memory _usdTokens) internal { + require(_wallet != address(0), "Invalid wallet"); wallet = _wallet; - reserveWallet = _reserveWallet; + emit SetTreasuryWallet(treasuryWallet, _treasuryWallet); + treasuryWallet = _treasuryWallet; _modifyUSDTokens(_usdTokens); } - function _modifyUSDTokens(address[] _usdTokens) internal { - for(uint256 i = 0; i < usdTokens.length; i++) { - usdTokenEnabled[usdTokens[i]] = false; + function _modifyUSDTokens(IERC20[] memory _usdTokens) internal { + uint256 i; + for(i = 0; i < usdTokens.length; i++) { + usdTokenEnabled[address(usdTokens[i])] = false; } usdTokens = _usdTokens; for(i = 0; i < _usdTokens.length; i++) { - require(_usdTokens[i] != address(0) && _usdTokens[i] != address(polyToken), "Invalid USD token"); - usdTokenEnabled[_usdTokens[i]] = true; + require(address(_usdTokens[i]) != address(0) && _usdTokens[i] != polyToken, "Invalid USD token"); + usdTokenEnabled[address(_usdTokens[i])] = true; } - emit SetAddresses(wallet, reserveWallet, _usdTokens); + emit SetAddresses(wallet, _usdTokens); } //////////////////// @@ -273,10 +268,10 @@ contract USDTieredSTO is USDTieredSTOStorage, STO, ReentrancyGuard { //////////////////// /** - * @notice Finalizes the STO and mint remaining tokens to reserve address - * @notice Reserve address must be whitelisted to successfully finalize + * @notice Finalizes the STO and mint remaining tokens to treasury address + * @notice Treasury wallet address must be whitelisted to successfully finalize */ - function finalize() external onlyOwner { + function finalize() external withPerm(ADMIN) { require(!isFinalized, "STO is finalized"); isFinalized = true; uint256 tempReturned; @@ -290,77 +285,53 @@ contract USDTieredSTO is USDTieredSTOStorage, STO, ReentrancyGuard { tiers[i].mintedTotal = tiers[i].tokenTotal; } } - uint256 granularity = ISecurityToken(securityToken).granularity(); + address walletAddress = (treasuryWallet == address(0) ? IDataStore(getDataStore()).getAddress(TREASURY) : treasuryWallet); + require(walletAddress != address(0), "Invalid address"); + uint256 granularity = securityToken.granularity(); tempReturned = tempReturned.div(granularity); tempReturned = tempReturned.mul(granularity); - require(ISecurityToken(securityToken).mint(reserveWallet, tempReturned), "Error in minting"); - emit ReserveTokenMint(msg.sender, reserveWallet, tempReturned, currentTier); + securityToken.issue(walletAddress, tempReturned, ""); + emit ReserveTokenMint(msg.sender, walletAddress, tempReturned, currentTier); finalAmountReturned = tempReturned; totalTokensSold = tempSold; } - /** - * @notice Modifies the list of accredited addresses - * @param _investors Array of investor addresses to modify - * @param _accredited Array of bools specifying accreditation status - */ - function changeAccredited(address[] _investors, bool[] _accredited) external onlyOwner { - require(_investors.length == _accredited.length, "Array mismatch"); - for (uint256 i = 0; i < _investors.length; i++) { - if (_accredited[i]) { - investors[_investors[i]].accredited = uint8(1); - } else { - investors[_investors[i]].accredited = uint8(0); - } - _addToInvestorsList(_investors[i]); - emit SetAccredited(_investors[i], _accredited[i]); - } - } - /** * @notice Modifies the list of overrides for non-accredited limits in USD * @param _investors Array of investor addresses to modify * @param _nonAccreditedLimit Array of uints specifying non-accredited limits */ - function changeNonAccreditedLimit(address[] _investors, uint256[] _nonAccreditedLimit) external onlyOwner { + function changeNonAccreditedLimit(address[] calldata _investors, uint256[] calldata _nonAccreditedLimit) external withPerm(OPERATOR) { //nonAccreditedLimitUSDOverride - require(_investors.length == _nonAccreditedLimit.length, "Array mismatch"); + require(_investors.length == _nonAccreditedLimit.length, "Length mismatch"); for (uint256 i = 0; i < _investors.length; i++) { - investors[_investors[i]].nonAccreditedLimitUSDOverride = _nonAccreditedLimit[i]; - _addToInvestorsList(_investors[i]); + nonAccreditedLimitUSDOverride[_investors[i]] = _nonAccreditedLimit[i]; emit SetNonAccreditedLimit(_investors[i], _nonAccreditedLimit[i]); } } - function _addToInvestorsList(address _investor) internal { - if (investors[_investor].seen == uint8(0)) { - investors[_investor].seen = uint8(1); - investorsList.push(_investor); - } - } - /** * @notice Returns investor accredited & non-accredited override informatiomn - * @return address[] list of all configured investors - * @return bool[] whether investor is accredited - * @return uint256[] any USD overrides for non-accredited limits for the investor + * @return investors list of all configured investors + * @return accredited whether investor is accredited + * @return override any USD overrides for non-accredited limits for the investor */ - function getAccreditedData() external view returns (address[], bool[], uint256[]) { - bool[] memory accrediteds = new bool[](investorsList.length); - uint256[] memory nonAccreditedLimitUSDOverrides = new uint256[](investorsList.length); - uint256 i; - for (i = 0; i < investorsList.length; i++) { - accrediteds[i] = (investors[investorsList[i]].accredited == uint8(0)? false: true); - nonAccreditedLimitUSDOverrides[i] = investors[investorsList[i]].nonAccreditedLimitUSDOverride; + function getAccreditedData() external view returns (address[] memory investors, bool[] memory accredited, uint256[] memory overrides) { + IDataStore dataStore = getDataStore(); + investors = dataStore.getAddressArray(INVESTORSKEY); + accredited = new bool[](investors.length); + overrides = new uint256[](investors.length); + for (uint256 i = 0; i < investors.length; i++) { + accredited[i] = _getIsAccredited(investors[i], dataStore); + overrides[i] = nonAccreditedLimitUSDOverride[investors[i]]; } - return (investorsList, accrediteds, nonAccreditedLimitUSDOverrides); } /** * @notice Function to set allowBeneficialInvestments (allow beneficiary to be different to funder) * @param _allowBeneficialInvestments Boolean to allow or disallow beneficial investments */ - function changeAllowBeneficialInvestments(bool _allowBeneficialInvestments) external onlyOwner { + function changeAllowBeneficialInvestments(bool _allowBeneficialInvestments) external withPerm(OPERATOR) { require(_allowBeneficialInvestments != allowBeneficialInvestments); allowBeneficialInvestments = _allowBeneficialInvestments; emit SetAllowBeneficialInvestments(allowBeneficialInvestments); @@ -373,21 +344,21 @@ contract USDTieredSTO is USDTieredSTOStorage, STO, ReentrancyGuard { /** * @notice fallback function - assumes ETH being invested */ - function () external payable { + function() external payable { buyWithETHRateLimited(msg.sender, 0); } // Buy functions without rate restriction - function buyWithETH(address _beneficiary) external payable { - buyWithETHRateLimited(_beneficiary, 0); + function buyWithETH(address _beneficiary) external payable returns (uint256, uint256, uint256) { + return buyWithETHRateLimited(_beneficiary, 0); } - function buyWithPOLY(address _beneficiary, uint256 _investedPOLY) external { - buyWithPOLYRateLimited(_beneficiary, _investedPOLY, 0); + function buyWithPOLY(address _beneficiary, uint256 _investedPOLY) external returns (uint256, uint256, uint256) { + return buyWithPOLYRateLimited(_beneficiary, _investedPOLY, 0); } - function buyWithUSD(address _beneficiary, uint256 _investedSC, IERC20 _usdToken) external { - buyWithUSDRateLimited(_beneficiary, _investedSC, 0, _usdToken); + function buyWithUSD(address _beneficiary, uint256 _investedSC, IERC20 _usdToken) external returns (uint256, uint256, uint256) { + return buyWithUSDRateLimited(_beneficiary, _investedSC, 0, _usdToken); } /** @@ -395,11 +366,8 @@ contract USDTieredSTO is USDTieredSTOStorage, STO, ReentrancyGuard { * @param _beneficiary Address where security tokens will be sent * @param _minTokens Minumum number of tokens to buy or else revert */ - function buyWithETHRateLimited(address _beneficiary, uint256 _minTokens) public payable validETH { - uint256 rate = getRate(FundRaiseType.ETH); - uint256 initialMinted = getTokensMinted(); - (uint256 spentUSD, uint256 spentValue) = _buyTokens(_beneficiary, msg.value, rate, FundRaiseType.ETH); - require(getTokensMinted().sub(initialMinted) >= _minTokens, "Insufficient minted"); + function buyWithETHRateLimited(address _beneficiary, uint256 _minTokens) public payable validETH returns (uint256, uint256, uint256) { + (uint256 rate, uint256 spentUSD, uint256 spentValue, uint256 initialMinted) = _getSpentvalues(_beneficiary, msg.value, FundRaiseType.ETH, _minTokens); // Modify storage investorInvested[_beneficiary][uint8(FundRaiseType.ETH)] = investorInvested[_beneficiary][uint8(FundRaiseType.ETH)].add(spentValue); fundsRaised[uint8(FundRaiseType.ETH)] = fundsRaised[uint8(FundRaiseType.ETH)].add(spentValue); @@ -408,6 +376,7 @@ contract USDTieredSTO is USDTieredSTOStorage, STO, ReentrancyGuard { // Refund excess ETH to investor wallet msg.sender.transfer(msg.value.sub(spentValue)); emit FundsReceived(msg.sender, _beneficiary, spentUSD, FundRaiseType.ETH, msg.value, spentValue, rate); + return (spentUSD, spentValue, getTokensMinted().sub(initialMinted)); } /** @@ -416,8 +385,8 @@ contract USDTieredSTO is USDTieredSTOStorage, STO, ReentrancyGuard { * @param _investedPOLY Amount of POLY invested * @param _minTokens Minumum number of tokens to buy or else revert */ - function buyWithPOLYRateLimited(address _beneficiary, uint256 _investedPOLY, uint256 _minTokens) public validPOLY { - _buyWithTokens(_beneficiary, _investedPOLY, FundRaiseType.POLY, _minTokens, polyToken); + function buyWithPOLYRateLimited(address _beneficiary, uint256 _investedPOLY, uint256 _minTokens) public validPOLY returns (uint256, uint256, uint256) { + return _buyWithTokens(_beneficiary, _investedPOLY, FundRaiseType.POLY, _minTokens, polyToken); } /** @@ -428,17 +397,13 @@ contract USDTieredSTO is USDTieredSTOStorage, STO, ReentrancyGuard { * @param _usdToken Address of USD stable coin to buy tokens with */ function buyWithUSDRateLimited(address _beneficiary, uint256 _investedSC, uint256 _minTokens, IERC20 _usdToken) - public validSC(_usdToken) + public validSC(address(_usdToken)) returns (uint256, uint256, uint256) { - _buyWithTokens(_beneficiary, _investedSC, FundRaiseType.SC, _minTokens, _usdToken); + return _buyWithTokens(_beneficiary, _investedSC, FundRaiseType.SC, _minTokens, _usdToken); } - function _buyWithTokens(address _beneficiary, uint256 _tokenAmount, FundRaiseType _fundRaiseType, uint256 _minTokens, IERC20 _token) internal { - require(_fundRaiseType == FundRaiseType.POLY || _fundRaiseType == FundRaiseType.SC, "Invalid raise"); - uint256 initialMinted = getTokensMinted(); - uint256 rate = getRate(_fundRaiseType); - (uint256 spentUSD, uint256 spentValue) = _buyTokens(_beneficiary, _tokenAmount, rate, _fundRaiseType); - require(getTokensMinted().sub(initialMinted) >= _minTokens, "Insufficient minted"); + function _buyWithTokens(address _beneficiary, uint256 _tokenAmount, FundRaiseType _fundRaiseType, uint256 _minTokens, IERC20 _token) internal returns (uint256, uint256, uint256) { + (uint256 rate, uint256 spentUSD, uint256 spentValue, uint256 initialMinted) = _getSpentvalues(_beneficiary, _tokenAmount, _fundRaiseType, _minTokens); // Modify storage investorInvested[_beneficiary][uint8(_fundRaiseType)] = investorInvested[_beneficiary][uint8(_fundRaiseType)].add(spentValue); fundsRaised[uint8(_fundRaiseType)] = fundsRaised[uint8(_fundRaiseType)].add(spentValue); @@ -447,8 +412,17 @@ contract USDTieredSTO is USDTieredSTOStorage, STO, ReentrancyGuard { // Forward coins to issuer wallet require(_token.transferFrom(msg.sender, wallet, spentValue), "Transfer failed"); emit FundsReceived(msg.sender, _beneficiary, spentUSD, _fundRaiseType, _tokenAmount, spentValue, rate); + return (spentUSD, spentValue, getTokensMinted().sub(initialMinted)); } + function _getSpentvalues(address _beneficiary, uint256 _amount, FundRaiseType _fundRaiseType, uint256 _minTokens) internal returns(uint256 rate, uint256 spentUSD, uint256 spentValue, uint256 initialMinted) { + initialMinted = getTokensMinted(); + rate = getRate(_fundRaiseType); + (spentUSD, spentValue) = _buyTokens(_beneficiary, _amount, rate, _fundRaiseType); + require(getTokensMinted().sub(initialMinted) >= _minTokens, "Insufficient minted"); + } + + /** * @notice Low level token purchase * @param _beneficiary Address where security tokens will be sent @@ -462,7 +436,6 @@ contract USDTieredSTO is USDTieredSTOStorage, STO, ReentrancyGuard { FundRaiseType _fundRaiseType ) internal - nonReentrant whenNotPaused returns(uint256 spentUSD, uint256 spentValue) { @@ -470,6 +443,8 @@ contract USDTieredSTO is USDTieredSTOStorage, STO, ReentrancyGuard { require(_beneficiary == msg.sender, "Beneficiary != funder"); } + require(_canBuy(_beneficiary), "Unauthorized"); + uint256 originalUSD = DecimalMath.mul(_rate, _investmentValue); uint256 allowedUSD = _buyTokensChecks(_beneficiary, _investmentValue, originalUSD); @@ -500,45 +475,6 @@ contract USDTieredSTO is USDTieredSTOStorage, STO, ReentrancyGuard { spentValue = DecimalMath.div(spentUSD, _rate); } - /** - * @notice Getter function for buyer to calculate how many tokens will they get - * @param _beneficiary Address where security tokens are to be sent - * @param _investmentValue Amount of POLY, ETH or Stable coins invested - * @param _fundRaiseType Fund raise type (POLY, ETH, SC) - */ - function buyTokensView( - address _beneficiary, - uint256 _investmentValue, - FundRaiseType _fundRaiseType - ) - external - view - returns(uint256 spentUSD, uint256 spentValue, uint256 tokensMinted) - { - require(_fundRaiseType == FundRaiseType.POLY || _fundRaiseType == FundRaiseType.SC || _fundRaiseType == FundRaiseType.ETH, "Invalid raise type"); - uint256 rate = getRate(_fundRaiseType); - uint256 originalUSD = DecimalMath.mul(rate, _investmentValue); - uint256 allowedUSD = _buyTokensChecks(_beneficiary, _investmentValue, originalUSD); - - // Iterate over each tier and process payment - for (uint256 i = currentTier; i < tiers.length; i++) { - bool gotoNextTier; - uint256 tempSpentUSD; - uint256 tempTokensMinted; - // If there are tokens remaining, process investment - if (tiers[i].mintedTotal < tiers[i].tokenTotal) { - (tempSpentUSD, gotoNextTier, tempTokensMinted) = _calculateTierView(i, allowedUSD.sub(spentUSD), _fundRaiseType); - spentUSD = spentUSD.add(tempSpentUSD); - tokensMinted = tokensMinted.add(tempTokensMinted); - // If all funds have been spent, exit the loop - if (!gotoNextTier) - break; - } - } - - spentValue = DecimalMath.div(spentUSD, rate); - } - function _buyTokensChecks( address _beneficiary, uint256 _investmentValue, @@ -549,15 +485,15 @@ contract USDTieredSTO is USDTieredSTOStorage, STO, ReentrancyGuard { returns(uint256 netInvestedUSD) { require(isOpen(), "STO not open"); - require(_investmentValue > 0, "No funds were sent"); + require(_investmentValue > 0, "No funds sent"); // Check for minimum investment - require(investedUSD.add(investorInvestedUSD[_beneficiary]) >= minimumInvestmentUSD, "investment < minimumInvestmentUSD"); + require(investedUSD.add(investorInvestedUSD[_beneficiary]) >= minimumInvestmentUSD, "Investment < min"); netInvestedUSD = investedUSD; // Check for non-accredited cap - if (investors[_beneficiary].accredited == uint8(0)) { - uint256 investorLimitUSD = (investors[_beneficiary].nonAccreditedLimitUSDOverride == 0) ? nonAccreditedLimitUSD : investors[_beneficiary].nonAccreditedLimitUSDOverride; - require(investorInvestedUSD[_beneficiary] < investorLimitUSD, "Over investor limit"); + if (!_isAccredited(_beneficiary)) { + uint256 investorLimitUSD = (nonAccreditedLimitUSDOverride[_beneficiary] == 0) ? nonAccreditedLimitUSD : nonAccreditedLimitUSDOverride[_beneficiary]; + require(investorInvestedUSD[_beneficiary] < investorLimitUSD, "Over Non-accredited investor limit"); if (investedUSD.add(investorInvestedUSD[_beneficiary]) > investorLimitUSD) netInvestedUSD = investorLimitUSD.sub(investorInvestedUSD[_beneficiary]); } @@ -602,40 +538,6 @@ contract USDTieredSTO is USDTieredSTOStorage, STO, ReentrancyGuard { } } - function _calculateTierView( - uint256 _tier, - uint256 _investedUSD, - FundRaiseType _fundRaiseType - ) - internal - view - returns(uint256 spentUSD, bool gotoNextTier, uint256 tokensMinted) - { - // First purchase any discounted tokens if POLY investment - uint256 tierSpentUSD; - uint256 tierPurchasedTokens; - Tier storage tierData = tiers[_tier]; - // Check whether there are any remaining discounted tokens - if ((_fundRaiseType == FundRaiseType.POLY) && (tierData.tokensDiscountPoly > tierData.mintedDiscountPoly)) { - uint256 discountRemaining = tierData.tokensDiscountPoly.sub(tierData.mintedDiscountPoly); - uint256 totalRemaining = tierData.tokenTotal.sub(tierData.mintedTotal); - if (totalRemaining < discountRemaining) - (spentUSD, tokensMinted, gotoNextTier) = _purchaseTierAmount(tierData.rateDiscountPoly, totalRemaining, _investedUSD); - else - (spentUSD, tokensMinted, gotoNextTier) = _purchaseTierAmount(tierData.rateDiscountPoly, discountRemaining, _investedUSD); - _investedUSD = _investedUSD.sub(spentUSD); - } - // Now, if there is any remaining USD to be invested, purchase at non-discounted rate - if (_investedUSD > 0 && - tierData.tokenTotal.sub(tierData.mintedTotal.add(tokensMinted)) > 0 && - (_fundRaiseType != FundRaiseType.POLY || tierData.tokensDiscountPoly <= tierData.mintedDiscountPoly) - ) { - (tierSpentUSD, tierPurchasedTokens, gotoNextTier) = _purchaseTierAmount(tierData.rate, tierData.tokenTotal.sub(tierData.mintedTotal), _investedUSD); - spentUSD = spentUSD.add(tierSpentUSD); - tokensMinted = tokensMinted.add(tierPurchasedTokens); - } - } - function _purchaseTier( address _beneficiary, uint256 _tierPrice, @@ -645,25 +547,9 @@ contract USDTieredSTO is USDTieredSTOStorage, STO, ReentrancyGuard { ) internal returns(uint256 spentUSD, uint256 purchasedTokens, bool gotoNextTier) - { - (spentUSD, purchasedTokens, gotoNextTier) = _purchaseTierAmount(_tierPrice, _tierRemaining, _investedUSD); - if (purchasedTokens > 0) { - require(ISecurityToken(securityToken).mint(_beneficiary, purchasedTokens), "Error in minting"); - emit TokenPurchase(msg.sender, _beneficiary, purchasedTokens, spentUSD, _tierPrice, _tier); - } - } - - function _purchaseTierAmount( - uint256 _tierPrice, - uint256 _tierRemaining, - uint256 _investedUSD - ) - internal - view - returns(uint256 spentUSD, uint256 purchasedTokens, bool gotoNextTier) { purchasedTokens = DecimalMath.div(_investedUSD, _tierPrice); - uint256 granularity = ISecurityToken(securityToken).granularity(); + uint256 granularity = securityToken.granularity(); if (purchasedTokens > _tierRemaining) { purchasedTokens = _tierRemaining.div(granularity); @@ -679,6 +565,22 @@ contract USDTieredSTO is USDTieredSTOStorage, STO, ReentrancyGuard { if (spentUSD > _investedUSD) { spentUSD = _investedUSD; } + + if (purchasedTokens > 0) { + securityToken.issue(_beneficiary, purchasedTokens, ""); + emit TokenPurchase(msg.sender, _beneficiary, purchasedTokens, spentUSD, _tierPrice, _tier); + } + } + + function _isAccredited(address _investor) internal view returns(bool) { + IDataStore dataStore = getDataStore(); + return _getIsAccredited(_investor, dataStore); + } + + function _getIsAccredited(address _investor, IDataStore dataStore) internal view returns(bool) { + uint256 flags = dataStore.getUint256(_getKey(INVESTORFLAGS, _investor)); + uint256 flag = flags & uint256(1); //isAccredited is flag 0 so we don't need to bit shift flags. + return flag > 0 ? true : false; } ///////////// @@ -690,15 +592,8 @@ contract USDTieredSTO is USDTieredSTOStorage, STO, ReentrancyGuard { * @return bool Whether the STO is accepting investments */ function isOpen() public view returns(bool) { - if (isFinalized) - return false; - /*solium-disable-next-line security/no-block-members*/ - if (now < startTime) - return false; /*solium-disable-next-line security/no-block-members*/ - if (now >= endTime) - return false; - if (capReached()) + if (isFinalized || now < startTime || now >= endTime || capReached()) return false; return true; } @@ -718,15 +613,13 @@ contract USDTieredSTO is USDTieredSTOStorage, STO, ReentrancyGuard { * @dev returns current conversion rate of funds * @param _fundRaiseType Fund raise type to get rate of */ - function getRate(FundRaiseType _fundRaiseType) public view returns (uint256) { + function getRate(FundRaiseType _fundRaiseType) public returns (uint256) { if (_fundRaiseType == FundRaiseType.ETH) { return IOracle(_getOracle(bytes32("ETH"), bytes32("USD"))).getPrice(); } else if (_fundRaiseType == FundRaiseType.POLY) { return IOracle(_getOracle(bytes32("POLY"), bytes32("USD"))).getPrice(); } else if (_fundRaiseType == FundRaiseType.SC) { - return 1 * 10**18; - } else { - revert("Incorrect funding"); + return 10**18; } } @@ -736,9 +629,8 @@ contract USDTieredSTO is USDTieredSTOStorage, STO, ReentrancyGuard { * @param _amount Value to convert to USD * @return uint256 Value in USD */ - function convertToUSD(FundRaiseType _fundRaiseType, uint256 _amount) external view returns(uint256) { - uint256 rate = getRate(_fundRaiseType); - return DecimalMath.mul(_amount, rate); + function convertToUSD(FundRaiseType _fundRaiseType, uint256 _amount) public returns(uint256) { + return DecimalMath.mul(_amount, getRate(_fundRaiseType)); } /** @@ -747,9 +639,8 @@ contract USDTieredSTO is USDTieredSTOStorage, STO, ReentrancyGuard { * @param _amount Value to convert from USD * @return uint256 Value in ETH or POLY */ - function convertFromUSD(FundRaiseType _fundRaiseType, uint256 _amount) external view returns(uint256) { - uint256 rate = getRate(_fundRaiseType); - return DecimalMath.div(_amount, rate); + function convertFromUSD(FundRaiseType _fundRaiseType, uint256 _amount) public returns(uint256) { + return DecimalMath.div(_amount, getRate(_fundRaiseType)); } /** @@ -759,20 +650,17 @@ contract USDTieredSTO is USDTieredSTOStorage, STO, ReentrancyGuard { function getTokensSold() public view returns (uint256) { if (isFinalized) return totalTokensSold; - else - return getTokensMinted(); + return getTokensMinted(); } /** * @notice Return the total no. of tokens minted * @return uint256 Total number of tokens minted */ - function getTokensMinted() public view returns (uint256) { - uint256 tokensMinted; + function getTokensMinted() public view returns (uint256 tokensMinted) { for (uint256 i = 0; i < tiers.length; i++) { tokensMinted = tokensMinted.add(tiers[i].mintedTotal); } - return tokensMinted; } /** @@ -780,12 +668,10 @@ contract USDTieredSTO is USDTieredSTOStorage, STO, ReentrancyGuard { * param _fundRaiseType The fund raising currency (e.g. ETH, POLY, SC) to calculate sold tokens for * @return uint256 Total number of tokens sold for ETH */ - function getTokensSoldFor(FundRaiseType _fundRaiseType) external view returns (uint256) { - uint256 tokensSold; + function getTokensSoldFor(FundRaiseType _fundRaiseType) external view returns (uint256 tokensSold) { for (uint256 i = 0; i < tiers.length; i++) { tokensSold = tokensSold.add(tiers[i].minted[uint8(_fundRaiseType)]); } - return tokensSold; } /** @@ -793,8 +679,7 @@ contract USDTieredSTO is USDTieredSTOStorage, STO, ReentrancyGuard { * param _tier The tier to return minted tokens for * @return uint256[] array of minted tokens in each fund raise type */ - function getTokensMintedByTier(uint256 _tier) external view returns (uint256[]) { - require(_tier < tiers.length, "Invalid tier"); + function getTokensMintedByTier(uint256 _tier) external view returns(uint256[] memory) { uint256[] memory tokensMinted = new uint256[](3); tokensMinted[0] = tiers[_tier].minted[uint8(FundRaiseType.ETH)]; tokensMinted[1] = tiers[_tier].minted[uint8(FundRaiseType.POLY)]; @@ -808,7 +693,6 @@ contract USDTieredSTO is USDTieredSTOStorage, STO, ReentrancyGuard { * @return uint256 Total number of tokens sold in the tier */ function getTokensSoldByTier(uint256 _tier) external view returns (uint256) { - require(_tier < tiers.length, "Incorrect tier"); uint256 tokensSold; tokensSold = tokensSold.add(tiers[_tier].minted[uint8(FundRaiseType.ETH)]); tokensSold = tokensSold.add(tiers[_tier].minted[uint8(FundRaiseType.POLY)]); @@ -828,15 +712,17 @@ contract USDTieredSTO is USDTieredSTOStorage, STO, ReentrancyGuard { * @notice Return the usd tokens accepted by the STO * @return address[] usd tokens */ - function getUsdTokens() external view returns (address[]) { + function getUsdTokens() external view returns (IERC20[] memory) { return usdTokens; } /** * @notice Return the permissions flag that are associated with STO */ - function getPermissions() public view returns(bytes32[]) { - bytes32[] memory allPermissions = new bytes32[](0); + function getPermissions() public view returns(bytes32[] memory allPermissions) { + allPermissions = new bytes32[](2); + allPermissions[0] = OPERATOR; + allPermissions[1] = ADMIN; return allPermissions; } @@ -852,7 +738,7 @@ contract USDTieredSTO is USDTieredSTOStorage, STO, ReentrancyGuard { * @return Amount of tokens sold. * @return Array of bools to show if funding is allowed in ETH, POLY, SC respectively */ - function getSTODetails() external view returns(uint256, uint256, uint256, uint256[], uint256[], uint256, uint256, uint256, bool[]) { + function getSTODetails() external view returns(uint256, uint256, uint256, uint256[] memory, uint256[] memory, uint256, uint256, uint256, bool[] memory) { uint256[] memory cap = new uint256[](tiers.length); uint256[] memory rate = new uint256[](tiers.length); for(uint256 i = 0; i < tiers.length; i++) { @@ -880,12 +766,13 @@ contract USDTieredSTO is USDTieredSTOStorage, STO, ReentrancyGuard { * @notice This function returns the signature of configure function * @return bytes4 Configure function signature */ - function getInitFunction() public pure returns (bytes4) { - return 0xeac2f9e4; + function getInitFunction() public pure returns(bytes4) { + return this.configure.selector; } - function _getOracle(bytes32 _currency, bytes32 _denominatedCurrency) internal view returns (address) { - return PolymathRegistry(RegistryUpdater(securityToken).polymathRegistry()).getAddress(oracleKeys[_currency][_denominatedCurrency]); + function _getOracle(bytes32 _currency, bytes32 _denominatedCurrency) internal view returns(address oracleAddress) { + oracleAddress = customOracles[_currency][_denominatedCurrency]; + if (oracleAddress == address(0)) + oracleAddress = IPolymathRegistry(securityToken.polymathRegistry()).getAddress(oracleKeys[_currency][_denominatedCurrency]); } - } diff --git a/contracts/modules/STO/USDTiered/USDTieredSTOFactory.sol b/contracts/modules/STO/USDTiered/USDTieredSTOFactory.sol new file mode 100644 index 000000000..7d55a2632 --- /dev/null +++ b/contracts/modules/STO/USDTiered/USDTieredSTOFactory.sol @@ -0,0 +1,51 @@ +pragma solidity 0.5.8; + +import "./USDTieredSTOProxy.sol"; +import "../../UpgradableModuleFactory.sol"; + +/** + * @title Factory for deploying CappedSTO module + */ +contract USDTieredSTOFactory is UpgradableModuleFactory { + + /** + * @notice Constructor + * @param _setupCost Setup cost of the module + * @param _logicContract Contract address that contains the logic related to `description` + * @param _polymathRegistry Address of the Polymath registry + * @param _isCostInPoly true = cost in Poly, false = USD + */ + constructor ( + uint256 _setupCost, + address _logicContract, + address _polymathRegistry, + bool _isCostInPoly + ) + public + UpgradableModuleFactory("3.0.0", _setupCost, _logicContract, _polymathRegistry, _isCostInPoly) + { + name = "USDTieredSTO"; + title = "USD Tiered STO"; + /*solium-disable-next-line max-len*/ + description = "It allows both accredited and non-accredited investors to contribute into the STO. Non-accredited investors will be capped at a maximum investment limit (as a default or specific to their jurisdiction). Tokens will be sold according to tiers sequentially & each tier has its own price and volume of tokens to sell. Upon receipt of funds (ETH, POLY or DAI), security tokens will automatically transfer to investor’s wallet address"; + typesData.push(3); + tagsData.push("Tiered"); + tagsData.push("ETH"); + tagsData.push("POLY"); + tagsData.push("USD"); + tagsData.push("STO"); + compatibleSTVersionRange["lowerBound"] = VersionUtils.pack(uint8(3), uint8(0), uint8(0)); + compatibleSTVersionRange["upperBound"] = VersionUtils.pack(uint8(3), uint8(0), uint8(0)); + } + + /** + * @notice Used to launch the Module with the help of factory + * @return address Contract address of the Module + */ + function deploy(bytes calldata _data) external returns(address) { + address usdTieredSTO = address(new USDTieredSTOProxy(logicContracts[latestUpgrade].version, msg.sender, polymathRegistry.getAddress("PolyToken"), logicContracts[latestUpgrade].logicContract)); + _initializeModule(usdTieredSTO, _data); + return usdTieredSTO; + } + +} diff --git a/contracts/modules/STO/USDTiered/USDTieredSTOProxy.sol b/contracts/modules/STO/USDTiered/USDTieredSTOProxy.sol new file mode 100644 index 000000000..4bb3caf5d --- /dev/null +++ b/contracts/modules/STO/USDTiered/USDTieredSTOProxy.sol @@ -0,0 +1,25 @@ +pragma solidity 0.5.8; + +import "../../../proxy/OwnedUpgradeabilityProxy.sol"; +import "./USDTieredSTOStorage.sol"; +import "../../../Pausable.sol"; +import "openzeppelin-solidity/contracts/utils/ReentrancyGuard.sol"; +import "../../../storage/modules/STO/STOStorage.sol"; +import "../../../storage/modules/ModuleStorage.sol"; + +/** + * @title USDTiered STO module Proxy + */ +contract USDTieredSTOProxy is USDTieredSTOStorage, STOStorage, ModuleStorage, Pausable, ReentrancyGuard, OwnedUpgradeabilityProxy { + /** + * @notice Constructor + * @param _securityToken Address of the security token + * @param _polyAddress Address of the polytoken + * @param _implementation representing the address of the new implementation to be set + */ + constructor (string memory _version, address _securityToken, address _polyAddress, address _implementation) public ModuleStorage(_securityToken, _polyAddress) { + require(_implementation != address(0), "Implementation address should not be 0x"); + _upgradeTo(_version, _implementation); + } + +} diff --git a/contracts/storage/USDTieredSTOStorage.sol b/contracts/modules/STO/USDTiered/USDTieredSTOStorage.sol similarity index 63% rename from contracts/storage/USDTieredSTOStorage.sol rename to contracts/modules/STO/USDTiered/USDTieredSTOStorage.sol index 31e9d803d..f5db99ad5 100644 --- a/contracts/storage/USDTieredSTOStorage.sol +++ b/contracts/modules/STO/USDTiered/USDTieredSTOStorage.sol @@ -1,12 +1,14 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; -import "../interfaces/IERC20.sol"; +import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; /** * @title Contract used to store layout for the USDTieredSTO storage */ contract USDTieredSTOStorage { + bytes32 internal constant INVESTORSKEY = 0xdf3a8dd24acdd05addfc6aeffef7574d2de3f844535ec91e8e0f3e45dba96731; //keccak256(abi.encodePacked("INVESTORS")) + ///////////// // Storage // ///////////// @@ -14,48 +16,35 @@ contract USDTieredSTOStorage { // NB rates mentioned below are actually price and are used like price in the logic. // How many token units a buyer gets per USD in this tier (multiplied by 10**18) uint256 rate; - // How many token units a buyer gets per USD in this tier (multiplied by 10**18) when investing in POLY up to tokensDiscountPoly uint256 rateDiscountPoly; - // How many tokens are available in this tier (relative to totalSupply) uint256 tokenTotal; - // How many token units are available in this tier (relative to totalSupply) at the ratePerTierDiscountPoly rate uint256 tokensDiscountPoly; - // How many tokens have been minted in this tier (relative to totalSupply) uint256 mintedTotal; - // How many tokens have been minted in this tier (relative to totalSupply) for each fund raise type - mapping (uint8 => uint256) minted; - + mapping(uint8 => uint256) minted; // How many tokens have been minted in this tier (relative to totalSupply) at discounted POLY rate uint256 mintedDiscountPoly; } - struct Investor { - // Whether investor is accredited (0 = non-accredited, 1 = accredited) - uint8 accredited; - // Whether we have seen the investor before (already added to investors list) - uint8 seen; - // Overrides for default limit in USD for non-accredited investors multiplied by 10**18 (0 = no override) - uint256 nonAccreditedLimitUSDOverride; - } + mapping(address => uint256) public nonAccreditedLimitUSDOverride; - mapping (bytes32 => mapping (bytes32 => string)) oracleKeys; + mapping(bytes32 => mapping(bytes32 => string)) oracleKeys; // Determine whether users can invest on behalf of a beneficiary - bool public allowBeneficialInvestments = false; + bool public allowBeneficialInvestments; // Whether or not the STO has been finalized bool public isFinalized; - // Address of issuer reserve wallet for unsold tokens - address public reserveWallet; + // Address of issuer treasury wallet for unsold tokens + address public treasuryWallet; // List of stable coin addresses - address[] public usdTokens; + IERC20[] internal usdTokens; // Current tier uint256 public currentTier; @@ -67,20 +56,13 @@ contract USDTieredSTOStorage { mapping (address => uint256) public stableCoinsRaised; // Amount in USD invested by each address - mapping (address => uint256) public investorInvestedUSD; + mapping(address => uint256) public investorInvestedUSD; // Amount in fund raise type invested by each investor - mapping (address => mapping (uint8 => uint256)) public investorInvested; - - // Accredited & non-accredited investor data - mapping (address => Investor) public investors; + mapping(address => mapping(uint8 => uint256)) public investorInvested; // List of active stable coin addresses - mapping (address => bool) public usdTokenEnabled; - - // List of all addresses that have been added as accredited or non-accredited without - // the default limit - address[] public investorsList; + mapping (address => bool) internal usdTokenEnabled; // Default limit in USD for non-accredited investors multiplied by 10**18 uint256 public nonAccreditedLimitUSD; @@ -94,4 +76,6 @@ contract USDTieredSTOStorage { // Array of Tiers Tier[] public tiers; + // Optional custom Oracles. + mapping(bytes32 => mapping(bytes32 => address)) customOracles; } diff --git a/contracts/modules/STO/USDTieredSTOFactory.sol b/contracts/modules/STO/USDTieredSTOFactory.sol deleted file mode 100644 index 1e44c7a7e..000000000 --- a/contracts/modules/STO/USDTieredSTOFactory.sol +++ /dev/null @@ -1,79 +0,0 @@ -pragma solidity ^0.4.24; - -import "../../interfaces/IBoot.sol"; -import "../../proxy/USDTieredSTOProxy.sol"; -import "../ModuleFactory.sol"; -import "../../libraries/Util.sol"; - -/** - * @title Factory for deploying CappedSTO module - */ -contract USDTieredSTOFactory is ModuleFactory { - - address public logicContract; - - /** - * @notice Constructor - * @param _polyAddress Address of the polytoken - */ - constructor (address _polyAddress, uint256 _setupCost, uint256 _usageCost, uint256 _subscriptionCost, address _logicContract) public - ModuleFactory(_polyAddress, _setupCost, _usageCost, _subscriptionCost) - { - require(_logicContract != address(0), "0x address is not allowed"); - logicContract = _logicContract; - version = "2.1.0"; - name = "USDTieredSTO"; - title = "USD Tiered STO"; - /*solium-disable-next-line max-len*/ - description = "It allows both accredited and non-accredited investors to contribute into the STO. Non-accredited investors will be capped at a maximum investment limit (as a default or specific to their jurisdiction). Tokens will be sold according to tiers sequentially & each tier has its own price and volume of tokens to sell. Upon receipt of funds (ETH, POLY or DAI), security tokens will automatically transfer to investor’s wallet address"; - compatibleSTVersionRange["lowerBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0)); - compatibleSTVersionRange["upperBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0)); - } - - /** - * @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), "Sufficent Allowance is not provided"); - //Check valid bytes - can only call module init function - address usdTieredSTO = new USDTieredSTOProxy(msg.sender, address(polyToken), logicContract); - //Checks that _data is valid (not calling anything it shouldn't) - require(Util.getSig(_data) == IBoot(usdTieredSTO).getInitFunction(), "Invalid data"); - /*solium-disable-next-line security/no-low-level-calls*/ - require(usdTieredSTO.call(_data), "Unsuccessfull call"); - /*solium-disable-next-line security/no-block-members*/ - emit GenerateModuleFromFactory(usdTieredSTO, getName(), address(this), msg.sender, setupCost, now); - return usdTieredSTO; - } - - /** - * @notice Type of the Module factory - */ - function getTypes() external view returns(uint8[]) { - uint8[] memory res = new uint8[](1); - res[0] = 3; - return res; - } - - /** - * @notice Returns the instructions associated with the module - */ - function getInstructions() external view returns(string) { - return "Initialises a USD tiered STO."; - } - - /** - * @notice Get the tags related to the module factory - */ - function getTags() external view returns(bytes32[]) { - bytes32[] memory availableTags = new bytes32[](4); - availableTags[0] = "USD"; - availableTags[1] = "Tiered"; - availableTags[2] = "POLY"; - availableTags[3] = "ETH"; - return availableTags; - } - -} diff --git a/contracts/modules/Experimental/TransferManager/BlacklistTransferManager.sol b/contracts/modules/TransferManager/BTM/BlacklistTransferManager.sol similarity index 57% rename from contracts/modules/Experimental/TransferManager/BlacklistTransferManager.sol rename to contracts/modules/TransferManager/BTM/BlacklistTransferManager.sol index d1e653803..d121ad785 100644 --- a/contracts/modules/Experimental/TransferManager/BlacklistTransferManager.sol +++ b/contracts/modules/TransferManager/BTM/BlacklistTransferManager.sol @@ -1,55 +1,31 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; -import "../../TransferManager/ITransferManager.sol"; +import "../TransferManager.sol"; +import "./BlacklistTransferManagerStorage.sol"; import "openzeppelin-solidity/contracts/math/SafeMath.sol"; /** * @title Transfer Manager module to automate blacklist and restrict transfers */ -contract BlacklistTransferManager is ITransferManager { +contract BlacklistTransferManager is BlacklistTransferManagerStorage, TransferManager { using SafeMath for uint256; - bytes32 public constant ADMIN = "ADMIN"; - - struct BlacklistsDetails { - uint256 startTime; - uint256 endTime; - uint256 repeatPeriodTime; - } - - //hold the different blacklist details corresponds to its name - mapping(bytes32 => BlacklistsDetails) public blacklists; - - //hold the different name of blacklist corresponds to a investor - mapping(address => bytes32[]) investorToBlacklist; - - //get list of the addresses for a particular blacklist - mapping(bytes32 => address[]) blacklistToInvestor; - - //mapping use to store the indexes for different blacklist types for a investor - mapping(address => mapping(bytes32 => uint256)) investorToIndex; - - //mapping use to store the indexes for different investor for a blacklist type - mapping(bytes32 => mapping(address => uint256)) blacklistToIndex; - - bytes32[] allBlacklists; - // Emit when new blacklist type is added event AddBlacklistType( - uint256 _startTime, - uint256 _endTime, - bytes32 _blacklistName, + uint256 _startTime, + uint256 _endTime, + bytes32 _blacklistName, uint256 _repeatPeriodTime ); - + // Emit when there is a change in the blacklist type event ModifyBlacklistType( uint256 _startTime, - uint256 _endTime, - bytes32 _blacklistName, + uint256 _endTime, + bytes32 _blacklistName, uint256 _repeatPeriodTime ); - + // Emit when the added blacklist type is deleted event DeleteBlacklistType( bytes32 _blacklistName @@ -57,26 +33,27 @@ contract BlacklistTransferManager is ITransferManager { // Emit when new investor is added to the blacklist type event AddInvestorToBlacklist( - address indexed _investor, + address indexed _investor, bytes32 _blacklistName ); - + // Emit when investor is deleted from the blacklist type event DeleteInvestorFromBlacklist( address indexed _investor, bytes32 _blacklistName ); - + /** * @notice Constructor * @param _securityToken Address of the security token * @param _polyAddress Address of the polytoken */ constructor (address _securityToken, address _polyAddress) - public - Module(_securityToken, _polyAddress) + public + Module(_securityToken, _polyAddress) { + } /** @@ -87,48 +64,92 @@ contract BlacklistTransferManager is ITransferManager { } - /** + /** * @notice Used to verify the transfer transaction * @param _from Address of the sender - * @dev Restrict the blacklist address to transfer tokens - * if the current time is between the timeframe define for the + * @dev Restrict the blacklist address to transfer tokens + * if the current time is between the timeframe define for the * blacklist type associated with the _from address */ - function verifyTransfer(address _from, address /* _to */, uint256 /* _amount */, bytes /* _data */, bool /* _isTransfer */) public returns(Result) { + function executeTransfer(address _from, address /*_to*/, uint256 /*_amount*/, bytes calldata /*_data*/) external returns(Result) { + (Result success, ) = _verifyTransfer(_from); + return success; + } + + + /** + * @notice Used to verify the transfer transaction (View) + * @param _from Address of the sender + * @dev Restrict the blacklist address to transfer tokens + * if the current time is between the timeframe define for the + * blacklist type associated with the _from address + */ + function verifyTransfer( + address _from, + address /* _to */, + uint256 /* _amount */, + bytes memory/* _data */ + ) + public + view + returns(Result, bytes32) + { + return _verifyTransfer(_from); + } + + function _verifyTransfer(address _from) internal view returns(Result, bytes32) { if (!paused) { if (investorToBlacklist[_from].length != 0) { for (uint256 i = 0; i < investorToBlacklist[_from].length; i++) { - uint256 endTimeTemp = blacklists[investorToBlacklist[_from][i]].endTime; - uint256 startTimeTemp = blacklists[investorToBlacklist[_from][i]].startTime; - uint256 repeatPeriodTimeTemp = blacklists[investorToBlacklist[_from][i]].repeatPeriodTime * 1 days; + bytes32 blacklistName = investorToBlacklist[_from][i]; + uint256 endTimeTemp = blacklists[blacklistName].endTime; + uint256 startTimeTemp = blacklists[blacklistName].startTime; + uint256 repeatPeriodTimeTemp = blacklists[blacklistName].repeatPeriodTime * 1 days; /*solium-disable-next-line security/no-block-members*/ if (now > startTimeTemp) { - // Find the repeating parameter that will be used to calculate the new startTime and endTime - // based on the new current time value - /*solium-disable-next-line security/no-block-members*/ - uint256 repeater = (now.sub(startTimeTemp)).div(repeatPeriodTimeTemp); - /*solium-disable-next-line security/no-block-members*/ - if (startTimeTemp.add(repeatPeriodTimeTemp.mul(repeater)) <= now && endTimeTemp.add(repeatPeriodTimeTemp.mul(repeater)) >= now) { - return Result.INVALID; - } - } - } - } + if (repeatPeriodTimeTemp > 0) { + // Find the repeating parameter that will be used to calculate the new startTime and endTime + // based on the new current time value + /*solium-disable-next-line security/no-block-members*/ + uint256 repeater = (now.sub(startTimeTemp)).div(repeatPeriodTimeTemp); + /*solium-disable-next-line security/no-block-members*/ + if (endTimeTemp.add(repeatPeriodTimeTemp.mul(repeater)) >= now) { + return (Result.INVALID, bytes32(uint256(address(this)) << 96)); + } + } else if(endTimeTemp >= now) { + return (Result.INVALID, bytes32(uint256(address(this)) << 96)); + } + } + } + } } - return Result.NA; + return (Result.NA, bytes32(0)); } + /** * @notice Used to add the blacklist type * @param _startTime Start date of the blacklist type * @param _endTime End date of the blacklist type * @param _blacklistName Name of the blacklist type - * @param _repeatPeriodTime Repeat period of the blacklist type + * @param _repeatPeriodTime Repeat period of the blacklist type in days */ function addBlacklistType(uint256 _startTime, uint256 _endTime, bytes32 _blacklistName, uint256 _repeatPeriodTime) public withPerm(ADMIN) { _addBlacklistType(_startTime, _endTime, _blacklistName, _repeatPeriodTime); } - + + function _addBlacklistType(uint256 _startTime, uint256 _endTime, bytes32 _blacklistName, uint256 _repeatPeriodTime) internal { + require(blacklists[_blacklistName].endTime == 0, "Blacklist type already exist"); + _addBlacklistTypeDetails(_startTime, _endTime, _blacklistName, _repeatPeriodTime); + allBlacklists.push(_blacklistName); + emit AddBlacklistType(_startTime, _endTime, _blacklistName, _repeatPeriodTime); + } + + function _addBlacklistTypeDetails(uint256 _startTime, uint256 _endTime, bytes32 _blacklistName, uint256 _repeatPeriodTime) internal { + _validParams(_startTime, _endTime, _blacklistName, _repeatPeriodTime); + blacklists[_blacklistName] = BlacklistsDetails(_startTime, _endTime, _repeatPeriodTime); + } + /** * @notice Used to add the multiple blacklist type * @param _startTimes Start date of the blacklist type @@ -136,22 +157,21 @@ contract BlacklistTransferManager is ITransferManager { * @param _blacklistNames Name of the blacklist type * @param _repeatPeriodTimes Repeat period of the blacklist type */ - function addBlacklistTypeMulti(uint256[] _startTimes, uint256[] _endTimes, bytes32[] _blacklistNames, uint256[] _repeatPeriodTimes) external withPerm(ADMIN) { - require (_startTimes.length == _endTimes.length && _endTimes.length == _blacklistNames.length && _blacklistNames.length == _repeatPeriodTimes.length, "Input array's length mismatch"); - for (uint256 i = 0; i < _startTimes.length; i++){ + function addBlacklistTypeMulti( + uint256[] memory _startTimes, + uint256[] memory _endTimes, + bytes32[] memory _blacklistNames, + uint256[] memory _repeatPeriodTimes + ) + public + withPerm(ADMIN) + { + require (_startTimes.length == _endTimes.length && _endTimes.length == _blacklistNames.length && _blacklistNames.length == _repeatPeriodTimes.length, "Input array's length mismatch"); + for (uint256 i = 0; i < _startTimes.length; i++) { _addBlacklistType(_startTimes[i], _endTimes[i], _blacklistNames[i], _repeatPeriodTimes[i]); } } - /** - * @notice Internal function - */ - function _validParams(uint256 _startTime, uint256 _endTime, bytes32 _blacklistName, uint256 _repeatPeriodTime) internal view { - require(_blacklistName != bytes32(0), "Invalid blacklist name"); - require(_startTime >= now && _startTime < _endTime, "Invalid start or end date"); - require(_repeatPeriodTime.mul(1 days) >= _endTime.sub(_startTime) || _repeatPeriodTime == 0); - } - /** * @notice Used to modify the details of a given blacklist type * @param _startTime Start date of the blacklist type @@ -160,9 +180,12 @@ contract BlacklistTransferManager is ITransferManager { * @param _repeatPeriodTime Repeat period of the blacklist type */ function modifyBlacklistType(uint256 _startTime, uint256 _endTime, bytes32 _blacklistName, uint256 _repeatPeriodTime) public withPerm(ADMIN) { - require(blacklists[_blacklistName].endTime != 0, "Blacklist type doesn't exist"); - _validParams(_startTime, _endTime, _blacklistName, _repeatPeriodTime); - blacklists[_blacklistName] = BlacklistsDetails(_startTime, _endTime, _repeatPeriodTime); + _modifyBlacklistType(_startTime, _endTime, _blacklistName, _repeatPeriodTime); + } + + function _modifyBlacklistType(uint256 _startTime, uint256 _endTime, bytes32 _blacklistName, uint256 _repeatPeriodTime) internal { + require(blacklists[_blacklistName].endTime != 0, "Blacklist type doesn't exist"); + _addBlacklistTypeDetails(_startTime, _endTime, _blacklistName, _repeatPeriodTime); emit ModifyBlacklistType(_startTime, _endTime, _blacklistName, _repeatPeriodTime); } @@ -173,10 +196,18 @@ contract BlacklistTransferManager is ITransferManager { * @param _blacklistNames Name of the blacklist type * @param _repeatPeriodTimes Repeat period of the blacklist type */ - function modifyBlacklistTypeMulti(uint256[] _startTimes, uint256[] _endTimes, bytes32[] _blacklistNames, uint256[] _repeatPeriodTimes) external withPerm(ADMIN) { - require (_startTimes.length == _endTimes.length && _endTimes.length == _blacklistNames.length && _blacklistNames.length == _repeatPeriodTimes.length, "Input array's length mismatch"); - for (uint256 i = 0; i < _startTimes.length; i++){ - modifyBlacklistType(_startTimes[i], _endTimes[i], _blacklistNames[i], _repeatPeriodTimes[i]); + function modifyBlacklistTypeMulti( + uint256[] memory _startTimes, + uint256[] memory _endTimes, + bytes32[] memory _blacklistNames, + uint256[] memory _repeatPeriodTimes + ) + public + withPerm(ADMIN) + { + require (_startTimes.length == _endTimes.length && _endTimes.length == _blacklistNames.length && _blacklistNames.length == _repeatPeriodTimes.length, "Input array's length mismatch"); + for (uint256 i = 0; i < _startTimes.length; i++) { + _modifyBlacklistType(_startTimes[i], _endTimes[i], _blacklistNames[i], _repeatPeriodTimes[i]); } } @@ -185,18 +216,23 @@ contract BlacklistTransferManager is ITransferManager { * @param _blacklistName Name of the blacklist type */ function deleteBlacklistType(bytes32 _blacklistName) public withPerm(ADMIN) { + _deleteBlacklistType(_blacklistName); + } + + function _deleteBlacklistType(bytes32 _blacklistName) internal { require(blacklists[_blacklistName].endTime != 0, "Blacklist type doesn’t exist"); require(blacklistToInvestor[_blacklistName].length == 0, "Investors are associated with the blacklist"); - // delete blacklist type + // delete blacklist type delete(blacklists[_blacklistName]); uint256 i = 0; - for (i = 0; i < allBlacklists.length; i++) { + uint256 blackListLength = allBlacklists.length; + for (i = 0; i < blackListLength; i++) { if (allBlacklists[i] == _blacklistName) { break; } } - if (i != allBlacklists.length -1) { - allBlacklists[i] = allBlacklists[allBlacklists.length -1]; + if (i != blackListLength - 1) { + allBlacklists[i] = allBlacklists[blackListLength -1]; } allBlacklists.length--; emit DeleteBlacklistType(_blacklistName); @@ -206,9 +242,9 @@ contract BlacklistTransferManager is ITransferManager { * @notice Used to delete the multiple blacklist type * @param _blacklistNames Name of the blacklist type */ - function deleteBlacklistTypeMulti(bytes32[] _blacklistNames) external withPerm(ADMIN) { - for(uint256 i = 0; i < _blacklistNames.length; i++){ - deleteBlacklistType(_blacklistNames[i]); + function deleteBlacklistTypeMulti(bytes32[] memory _blacklistNames) public withPerm(ADMIN) { + for(uint256 i = 0; i < _blacklistNames.length; i++) { + _deleteBlacklistType(_blacklistNames[i]); } } @@ -218,13 +254,15 @@ contract BlacklistTransferManager is ITransferManager { * @param _blacklistName Name of the blacklist */ function addInvestorToBlacklist(address _investor, bytes32 _blacklistName) public withPerm(ADMIN) { + _addInvestorToBlacklist(_investor, _blacklistName); + } + + function _addInvestorToBlacklist(address _investor, bytes32 _blacklistName) internal { require(blacklists[_blacklistName].endTime != 0, "Blacklist type doesn't exist"); require(_investor != address(0), "Invalid investor address"); - uint256 index = investorToIndex[_investor][_blacklistName]; - if (index < investorToBlacklist[_investor].length) - require(investorToBlacklist[_investor][index] != _blacklistName, "Blacklist already added to investor"); + require(investorToIndex[_investor][_blacklistName] == 0, "Blacklist already added to investor"); uint256 investorIndex = investorToBlacklist[_investor].length; - // Add blacklist index to the investor + // Add blacklist index to the investor investorToIndex[_investor][_blacklistName] = investorIndex; uint256 blacklistIndex = blacklistToInvestor[_blacklistName].length; // Add investor index to the blacklist @@ -239,9 +277,9 @@ contract BlacklistTransferManager is ITransferManager { * @param _investors Address of the investor * @param _blacklistName Name of the blacklist */ - function addInvestorToBlacklistMulti(address[] _investors, bytes32 _blacklistName) external withPerm(ADMIN){ - for(uint256 i = 0; i < _investors.length; i++){ - addInvestorToBlacklist(_investors[i], _blacklistName); + function addInvestorToBlacklistMulti(address[] memory _investors, bytes32 _blacklistName) public withPerm(ADMIN) { + for(uint256 i = 0; i < _investors.length; i++) { + _addInvestorToBlacklist(_investors[i], _blacklistName); } } @@ -250,10 +288,10 @@ contract BlacklistTransferManager is ITransferManager { * @param _investors Address of the investor * @param _blacklistNames Name of the blacklist */ - function addMultiInvestorToBlacklistMulti(address[] _investors, bytes32[] _blacklistNames) external withPerm(ADMIN){ - require (_investors.length == _blacklistNames.length, "Input array's length mismatch"); - for(uint256 i = 0; i < _investors.length; i++){ - addInvestorToBlacklist(_investors[i], _blacklistNames[i]); + function addMultiInvestorToBlacklistMulti(address[] memory _investors, bytes32[] memory _blacklistNames) public withPerm(ADMIN) { + require (_investors.length == _blacklistNames.length, "Input array's length mismatch"); + for(uint256 i = 0; i < _investors.length; i++) { + _addInvestorToBlacklist(_investors[i], _blacklistNames[i]); } } @@ -265,32 +303,24 @@ contract BlacklistTransferManager is ITransferManager { * @param _repeatPeriodTime Repeat period of the blacklist type * @param _investor Address of the investor */ - function addInvestorToNewBlacklist(uint256 _startTime, uint256 _endTime, bytes32 _blacklistName, uint256 _repeatPeriodTime, address _investor) external withPerm(ADMIN){ + function addInvestorToNewBlacklist( + uint256 _startTime, + uint256 _endTime, + bytes32 _blacklistName, + uint256 _repeatPeriodTime, + address _investor + ) public withPerm(ADMIN) { _addBlacklistType(_startTime, _endTime, _blacklistName, _repeatPeriodTime); - addInvestorToBlacklist(_investor, _blacklistName); + _addInvestorToBlacklist(_investor, _blacklistName); } /** - * @notice Used to delete the investor from all the associated blacklist types - * @param _investor Address of the investor - */ - function deleteInvestorFromAllBlacklist(address _investor) public withPerm(ADMIN) { - require(_investor != address(0), "Invalid investor address"); - require(investorToBlacklist[_investor].length != 0, "Investor is not associated to any blacklist type"); - uint256 index = investorToBlacklist[_investor].length - 1; - for (uint256 i = index; i >= 0 && i <= index; i--){ - deleteInvestorFromBlacklist(_investor, investorToBlacklist[_investor][i]); - } - } - - /** - * @notice Used to delete the multiple investor from all the associated blacklist types + * @notice Used to delete the investor from the blacklist * @param _investor Address of the investor + * @param _blacklistName Name of the blacklist */ - function deleteInvestorFromAllBlacklistMulti(address[] _investor) external withPerm(ADMIN) { - for(uint256 i = 0; i < _investor.length; i++){ - deleteInvestorFromAllBlacklist(_investor[i]); - } + function deleteInvestorFromBlacklist(address _investor, bytes32 _blacklistName) public withPerm(ADMIN) { + _deleteInvestorFromBlacklist(_investor, _blacklistName); } /** @@ -298,7 +328,7 @@ contract BlacklistTransferManager is ITransferManager { * @param _investor Address of the investor * @param _blacklistName Name of the blacklist */ - function deleteInvestorFromBlacklist(address _investor, bytes32 _blacklistName) public withPerm(ADMIN) { + function _deleteInvestorFromBlacklist(address _investor, bytes32 _blacklistName) internal { require(_investor != address(0), "Invalid investor address"); require(_blacklistName != bytes32(0),"Invalid blacklist name"); require(investorToBlacklist[_investor][investorToIndex[_investor][_blacklistName]] == _blacklistName, "Investor not associated to the blacklist"); @@ -325,32 +355,64 @@ contract BlacklistTransferManager is ITransferManager { emit DeleteInvestorFromBlacklist(_investor, _blacklistName); } + /** + * @notice Used to delete the investor from all the associated blacklist types + * @param _investor Address of the investor + */ + function deleteInvestorFromAllBlacklist(address _investor) public withPerm(ADMIN) { + _deleteInvestorFromAllBlacklist(_investor); + } + + /** + * @notice Used to delete the investor from all the associated blacklist types + * @param _investor Address of the investor + */ + function _deleteInvestorFromAllBlacklist(address _investor) internal { + require(_investor != address(0), "Invalid investor address"); + require(investorToBlacklist[_investor].length >= 1, "Investor is not present in the blacklist"); + uint256 index = investorToBlacklist[_investor].length - 1; + for (uint256 i = index; i >= 0 && i <= index; i--) { + _deleteInvestorFromBlacklist(_investor, investorToBlacklist[_investor][i]); + } + } + + /** + * @notice Used to delete the multiple investor from all the associated blacklist types + * @param _investor Address of the investor + */ + function deleteInvestorFromAllBlacklistMulti(address[] memory _investor) public withPerm(ADMIN) { + for(uint256 i = 0; i < _investor.length; i++) { + _deleteInvestorFromAllBlacklist(_investor[i]); + } + } + /** * @notice Used to delete the multiple investor from the blacklist * @param _investors address of the investor * @param _blacklistNames name of the blacklist */ - function deleteMultiInvestorsFromBlacklistMulti(address[] _investors, bytes32[] _blacklistNames) external withPerm(ADMIN) { - require (_investors.length == _blacklistNames.length, "Input array's length mismatch"); - for(uint256 i = 0; i < _investors.length; i++){ - deleteInvestorFromBlacklist(_investors[i], _blacklistNames[i]); + function deleteMultiInvestorsFromBlacklistMulti(address[] memory _investors, bytes32[] memory _blacklistNames) public withPerm(ADMIN) { + require (_investors.length == _blacklistNames.length, "Input array's length mismatch"); + for(uint256 i = 0; i < _investors.length; i++) { + _deleteInvestorFromBlacklist(_investors[i], _blacklistNames[i]); } } - function _addBlacklistType(uint256 _startTime, uint256 _endTime, bytes32 _blacklistName, uint256 _repeatPeriodTime) internal { - require(blacklists[_blacklistName].endTime == 0, "Blacklist type already exist"); - _validParams(_startTime, _endTime, _blacklistName, _repeatPeriodTime); - blacklists[_blacklistName] = BlacklistsDetails(_startTime, _endTime, _repeatPeriodTime); - allBlacklists.push(_blacklistName); - emit AddBlacklistType(_startTime, _endTime, _blacklistName, _repeatPeriodTime); + /** + * @notice Internal function + */ + function _validParams(uint256 _startTime, uint256 _endTime, bytes32 _blacklistName, uint256 _repeatPeriodTime) internal view { + require(_blacklistName != bytes32(0), "Invalid blacklist name"); + require(_startTime >= now && _startTime < _endTime, "Invalid start or end date"); + require(_repeatPeriodTime.mul(1 days) >= _endTime.sub(_startTime) || _repeatPeriodTime == 0); } - + /** * @notice get the list of the investors of a blacklist type * @param _blacklistName Name of the blacklist type * @return address List of investors associated with the blacklist */ - function getListOfAddresses(bytes32 _blacklistName) external view returns(address[]) { + function getListOfAddresses(bytes32 _blacklistName) external view returns(address[] memory) { require(blacklists[_blacklistName].endTime != 0, "Blacklist type doesn't exist"); return blacklistToInvestor[_blacklistName]; } @@ -360,7 +422,7 @@ contract BlacklistTransferManager is ITransferManager { * @param _user Address of the user * @return bytes32 List of blacklist names associated with the given address */ - function getBlacklistNamesToUser(address _user) external view returns(bytes32[]) { + function getBlacklistNamesToUser(address _user) external view returns(bytes32[] memory) { return investorToBlacklist[_user]; } @@ -368,18 +430,34 @@ contract BlacklistTransferManager is ITransferManager { * @notice get the list of blacklist names * @return bytes32 Array of blacklist names */ - function getAllBlacklists() external view returns(bytes32[]) { + function getAllBlacklists() external view returns(bytes32[] memory) { return allBlacklists; } + /** + * @notice return the amount of tokens for a given user as per the partition + * @param _partition Identifier + * @param _tokenHolder Whom token amount need to query + * @param _additionalBalance It is the `_value` that transfer during transfer/transferFrom function call + */ + function getTokensByPartition(bytes32 _partition, address _tokenHolder, uint256 _additionalBalance) external view returns(uint256) { + uint256 currentBalance = (msg.sender == address(securityToken)) ? (securityToken.balanceOf(_tokenHolder)).add(_additionalBalance) : securityToken.balanceOf(_tokenHolder); + if (paused && _partition == UNLOCKED) + return currentBalance; + + (Result success, ) = verifyTransfer(_tokenHolder, address(0), 0, "0x0"); + if ((_partition == LOCKED && success == Result.INVALID) || (_partition == UNLOCKED && success != Result.INVALID)) + return currentBalance; + else + return 0; + } + /** * @notice Return the permissions flag that are associated with blacklist transfer manager */ - function getPermissions() public view returns(bytes32[]) { + function getPermissions() public view returns(bytes32[] memory) { bytes32[] memory allPermissions = new bytes32[](1); allPermissions[0] = ADMIN; return allPermissions; } } - - diff --git a/contracts/modules/TransferManager/BTM/BlacklistTransferManagerFactory.sol b/contracts/modules/TransferManager/BTM/BlacklistTransferManagerFactory.sol new file mode 100644 index 000000000..2d9216d7c --- /dev/null +++ b/contracts/modules/TransferManager/BTM/BlacklistTransferManagerFactory.sol @@ -0,0 +1,47 @@ +pragma solidity 0.5.8; + +import "../../UpgradableModuleFactory.sol"; +import "./BlacklistTransferManagerProxy.sol"; + +/** + * @title Factory for deploying BlacklistTransferManager module + */ +contract BlacklistTransferManagerFactory is UpgradableModuleFactory { + + /** + * @notice Constructor + * @param _setupCost Setup cost of the module + * @param _logicContract Contract address that contains the logic related to `description` + * @param _polymathRegistry Address of the Polymath registry + * @param _isCostInPoly true = cost in Poly, false = USD + */ + constructor( + uint256 _setupCost, + address _logicContract, + address _polymathRegistry, + bool _isCostInPoly + ) + public UpgradableModuleFactory("3.0.0", _setupCost, _logicContract, _polymathRegistry, _isCostInPoly) + { + name = "BlacklistTransferManager"; + title = "Blacklist Transfer Manager"; + description = "Automate blacklist to restrict selling"; + typesData.push(2); + tagsData.push("Blacklist"); + tagsData.push("Transfer Restriction"); + compatibleSTVersionRange["lowerBound"] = VersionUtils.pack(uint8(3), uint8(0), uint8(0)); + compatibleSTVersionRange["upperBound"] = VersionUtils.pack(uint8(3), uint8(0), uint8(0)); + } + + /** + * @notice Used to launch the Module with the help of factory + * @param _data Data used for the intialization of the module factory variables + * @return address Contract address of the Module + */ + function deploy(bytes calldata _data) external returns(address) { + address blacklistTransferManager = address(new BlacklistTransferManagerProxy(logicContracts[latestUpgrade].version, msg.sender, polymathRegistry.getAddress("PolyToken"), logicContracts[latestUpgrade].logicContract)); + _initializeModule(blacklistTransferManager, _data); + return blacklistTransferManager; + } + +} diff --git a/contracts/modules/TransferManager/BTM/BlacklistTransferManagerProxy.sol b/contracts/modules/TransferManager/BTM/BlacklistTransferManagerProxy.sol new file mode 100644 index 000000000..8914ba0fa --- /dev/null +++ b/contracts/modules/TransferManager/BTM/BlacklistTransferManagerProxy.sol @@ -0,0 +1,35 @@ +pragma solidity 0.5.8; + +import "../../../proxy/OwnedUpgradeabilityProxy.sol"; +import "./BlacklistTransferManagerStorage.sol"; +import "../../../Pausable.sol"; +import "../../../storage/modules/ModuleStorage.sol"; + +/** + * @title CountTransferManager module Proxy + */ +contract BlacklistTransferManagerProxy is BlacklistTransferManagerStorage, ModuleStorage, Pausable, OwnedUpgradeabilityProxy { + + /** + * @notice Constructor + * @param _securityToken Address of the security token + * @param _polyAddress Address of the polytoken + * @param _implementation representing the address of the new implementation to be set + */ + constructor ( + string memory _version, + address _securityToken, + address _polyAddress, + address _implementation + ) + public + ModuleStorage(_securityToken, _polyAddress) + { + require( + _implementation != address(0), + "Implementation address should not be 0x" + ); + _upgradeTo(_version, _implementation); + } + +} diff --git a/contracts/modules/TransferManager/BTM/BlacklistTransferManagerStorage.sol b/contracts/modules/TransferManager/BTM/BlacklistTransferManagerStorage.sol new file mode 100644 index 000000000..229c1a994 --- /dev/null +++ b/contracts/modules/TransferManager/BTM/BlacklistTransferManagerStorage.sol @@ -0,0 +1,31 @@ +pragma solidity 0.5.8; + +/** + * @title Contract used to store layout for the CountTransferManager storage + */ +contract BlacklistTransferManagerStorage { + + struct BlacklistsDetails { + uint256 startTime; + uint256 endTime; + uint256 repeatPeriodTime; + } + + //hold the different blacklist details corresponds to its name + mapping(bytes32 => BlacklistsDetails) public blacklists; + + //hold the different name of blacklist corresponds to a investor + mapping(address => bytes32[]) investorToBlacklist; + + //get list of the addresses for a particular blacklist + mapping(bytes32 => address[]) blacklistToInvestor; + + //mapping use to store the indexes for different blacklist types for a investor + mapping(address => mapping(bytes32 => uint256)) investorToIndex; + + //mapping use to store the indexes for different investor for a blacklist type + mapping(bytes32 => mapping(address => uint256)) blacklistToIndex; + + bytes32[] allBlacklists; + +} diff --git a/contracts/modules/TransferManager/CTM/CountTransferManager.sol b/contracts/modules/TransferManager/CTM/CountTransferManager.sol new file mode 100644 index 000000000..6cd8c738a --- /dev/null +++ b/contracts/modules/TransferManager/CTM/CountTransferManager.sol @@ -0,0 +1,132 @@ +pragma solidity 0.5.8; + +import "../TransferManager.sol"; +import "./CountTransferManagerStorage.sol"; +import "../../../interfaces/ISecurityToken.sol"; + +/** + * @title Transfer Manager for limiting maximum number of token holders + */ +contract CountTransferManager is CountTransferManagerStorage, TransferManager { + + event ModifyHolderCount(uint256 _oldHolderCount, uint256 _newHolderCount); + + /** + * @notice Constructor + * @param _securityToken Address of the security token + */ + constructor(address _securityToken, address _polyToken) public Module(_securityToken, _polyToken) { + + } + + /** @notice Used to verify the transfer transaction and prevent a transfer if it passes the allowed amount of token holders + * @param _from Address of the sender + * @param _to Address of the receiver + * @param _amount Amount to send + */ + function executeTransfer( + address _from, + address _to, + uint256 _amount, + bytes calldata /*_data*/ + ) + external + returns(Result) + { + (Result success, ) = _verifyTransfer(_from, _to, _amount, securityToken.holderCount()); + return success; + } + + /** + * @notice Used to verify the transfer transaction and prevent a transfer if it passes the allowed amount of token holders + * @dev module.verifyTransfer is called by SecToken.canTransfer and does not receive the updated holderCount therefore + * verifyTransfer has to manually account for pot. tokenholder changes (by mimicking TokenLib.adjustInvestorCount). + * module.executeTransfer is called by SecToken.transfer|issue|others and receives an updated holderCount + * as sectoken calls TokenLib.adjustInvestorCount before executeTransfer. + * @param _from Address of the sender + * @param _to Address of the receiver + * @param _amount Amount to send + */ + function verifyTransfer( + address _from, + address _to, + uint256 _amount, + bytes memory /* _data */ + ) + public + view + returns(Result, bytes32) + { + uint256 holderCount = securityToken.holderCount(); + if (_amount != 0 && _from != _to) { + // Check whether receiver is a new token holder + if (_to != address(0) && securityToken.balanceOf(_to) == 0) { + holderCount++; + } + // Check whether sender is moving all of their tokens + if (_amount == securityToken.balanceOf(_from)) { + holderCount--; + } + } + + return _verifyTransfer(_from, _to, _amount, holderCount); + } + + function _verifyTransfer( + address _from, + address _to, + uint256 _amount, + uint256 _holderCount + ) + internal + view + returns(Result, bytes32) + { + if (!paused) { + if (maxHolderCount < _holderCount) { + // Allow transfers to existing maxHolders + if (securityToken.balanceOf(_to) != 0 || securityToken.balanceOf(_from) == _amount) { + return (Result.NA, bytes32(0)); + } + return (Result.INVALID, bytes32(uint256(address(this)) << 96)); + } + return (Result.NA, bytes32(0)); + } + return (Result.NA, bytes32(0)); + } + + + /** + * @notice Used to initialize the variables of the contract + * @param _maxHolderCount Maximum no. of holders this module allows the SecurityToken to have + */ + function configure(uint256 _maxHolderCount) public onlyFactory { + maxHolderCount = _maxHolderCount; + } + + /** + * @notice Sets the cap for the amount of token holders there can be + * @param _maxHolderCount is the new maximum amount of token holders + */ + function changeHolderCount(uint256 _maxHolderCount) public withPerm(ADMIN) { + emit ModifyHolderCount(maxHolderCount, _maxHolderCount); + maxHolderCount = _maxHolderCount; + } + + /** + * @notice This function returns the signature of configure function + */ + function getInitFunction() public pure returns(bytes4) { + return this.configure.selector; + } + + /** + * @notice Returns the permissions flag that are associated with CountTransferManager + */ + function getPermissions() public view returns(bytes32[] memory) { + bytes32[] memory allPermissions = new bytes32[](1); + allPermissions[0] = ADMIN; + return allPermissions; + } + +} diff --git a/contracts/modules/TransferManager/CTM/CountTransferManagerFactory.sol b/contracts/modules/TransferManager/CTM/CountTransferManagerFactory.sol new file mode 100644 index 000000000..878304272 --- /dev/null +++ b/contracts/modules/TransferManager/CTM/CountTransferManagerFactory.sol @@ -0,0 +1,48 @@ +pragma solidity 0.5.8; + +import "../../UpgradableModuleFactory.sol"; +import "./CountTransferManagerProxy.sol"; + +/** + * @title Factory for deploying CountTransferManager module + */ +contract CountTransferManagerFactory is UpgradableModuleFactory { + + /** + * @notice Constructor + * @param _setupCost Setup cost of the module + * @param _logicContract Contract address that contains the logic related to `description` + * @param _polymathRegistry Address of the Polymath registry + * @param _isCostInPoly true = cost in Poly, false = USD + */ + constructor ( + uint256 _setupCost, + address _logicContract, + address _polymathRegistry, + bool _isCostInPoly + ) + public + UpgradableModuleFactory("3.0.0", _setupCost, _logicContract, _polymathRegistry, _isCostInPoly) + { + name = "CountTransferManager"; + title = "Count Transfer Manager"; + description = "Restrict the number of investors"; + typesData.push(2); + tagsData.push("Count"); + tagsData.push("Transfer Restriction"); + compatibleSTVersionRange["lowerBound"] = VersionUtils.pack(uint8(3), uint8(0), uint8(0)); + compatibleSTVersionRange["upperBound"] = VersionUtils.pack(uint8(3), uint8(0), uint8(0)); + } + + /** + * @notice Used to launch the Module with the help of factory + * @param _data Data used for the intialization of the module factory variables + * @return address Contract address of the Module + */ + function deploy(bytes calldata _data) external returns(address) { + address countTransferManager = address(new CountTransferManagerProxy(logicContracts[latestUpgrade].version, msg.sender, polymathRegistry.getAddress("PolyToken"), logicContracts[latestUpgrade].logicContract)); + _initializeModule(countTransferManager, _data); + return countTransferManager; + } + +} diff --git a/contracts/modules/TransferManager/CTM/CountTransferManagerProxy.sol b/contracts/modules/TransferManager/CTM/CountTransferManagerProxy.sol new file mode 100644 index 000000000..8a302309b --- /dev/null +++ b/contracts/modules/TransferManager/CTM/CountTransferManagerProxy.sol @@ -0,0 +1,35 @@ +pragma solidity 0.5.8; + +import "../../../proxy/OwnedUpgradeabilityProxy.sol"; +import "./CountTransferManagerStorage.sol"; +import "../../../Pausable.sol"; +import "../../../storage/modules/ModuleStorage.sol"; + +/** + * @title CountTransferManager module Proxy + */ +contract CountTransferManagerProxy is CountTransferManagerStorage, ModuleStorage, Pausable, OwnedUpgradeabilityProxy { + + /** + * @notice Constructor + * @param _securityToken Address of the security token + * @param _polyAddress Address of the polytoken + * @param _implementation representing the address of the new implementation to be set + */ + constructor ( + string memory _version, + address _securityToken, + address _polyAddress, + address _implementation + ) + public + ModuleStorage(_securityToken, _polyAddress) + { + require( + _implementation != address(0), + "Implementation address should not be 0x" + ); + _upgradeTo(_version, _implementation); + } + +} diff --git a/contracts/modules/TransferManager/CTM/CountTransferManagerStorage.sol b/contracts/modules/TransferManager/CTM/CountTransferManagerStorage.sol new file mode 100644 index 000000000..cd262a4ab --- /dev/null +++ b/contracts/modules/TransferManager/CTM/CountTransferManagerStorage.sol @@ -0,0 +1,11 @@ +pragma solidity 0.5.8; + +/** + * @title Contract used to store layout for the CountTransferManager storage + */ +contract CountTransferManagerStorage { + + // The maximum number of concurrent token holders + uint256 public maxHolderCount; + +} diff --git a/contracts/modules/TransferManager/CountTransferManager.sol b/contracts/modules/TransferManager/CountTransferManager.sol deleted file mode 100644 index 86a537ebe..000000000 --- a/contracts/modules/TransferManager/CountTransferManager.sol +++ /dev/null @@ -1,89 +0,0 @@ -pragma solidity ^0.4.24; - -import "./ITransferManager.sol"; - -/** - * @title Transfer Manager for limiting maximum number of token holders - */ -contract CountTransferManager is ITransferManager { - - // The maximum number of concurrent token holders - uint256 public maxHolderCount; - - bytes32 public constant ADMIN = "ADMIN"; - - event ModifyHolderCount(uint256 _oldHolderCount, uint256 _newHolderCount); - - /** - * @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 Used to verify the transfer transaction and prevent a transfer if it passes the allowed amount of token holders - * @param _from Address of the sender - * @param _to Address of the receiver - * @param _amount Amount to send - */ - function verifyTransfer( - address _from, - address _to, - uint256 _amount, - bytes /* _data */, - bool /* _isTransfer */ - ) - public - returns(Result) - { - if (!paused) { - if (maxHolderCount < ISecurityToken(securityToken).getInvestorCount()) { - // Allow transfers to existing maxHolders - if (ISecurityToken(securityToken).balanceOf(_to) != 0 || ISecurityToken(securityToken).balanceOf(_from) == _amount) { - return Result.NA; - } - return Result.INVALID; - } - return Result.NA; - } - return Result.NA; - } - - /** - * @notice Used to initialize the variables of the contract - * @param _maxHolderCount Maximum no. of holders this module allows the SecurityToken to have - */ - function configure(uint256 _maxHolderCount) public onlyFactory { - maxHolderCount = _maxHolderCount; - } - - /** - * @notice Sets the cap for the amount of token holders there can be - * @param _maxHolderCount is the new maximum amount of token holders - */ - function changeHolderCount(uint256 _maxHolderCount) public withPerm(ADMIN) { - emit ModifyHolderCount(maxHolderCount, _maxHolderCount); - maxHolderCount = _maxHolderCount; - } - - /** - * @notice This function returns the signature of configure function - */ - function getInitFunction() public pure returns (bytes4) { - return bytes4(keccak256("configure(uint256)")); - } - - /** - * @notice Returns the permissions flag that are associated with CountTransferManager - */ - function getPermissions() public view returns(bytes32[]) { - bytes32[] memory allPermissions = new bytes32[](1); - allPermissions[0] = ADMIN; - return allPermissions; - } - -} diff --git a/contracts/modules/TransferManager/CountTransferManagerFactory.sol b/contracts/modules/TransferManager/CountTransferManagerFactory.sol deleted file mode 100644 index c42cebc61..000000000 --- a/contracts/modules/TransferManager/CountTransferManagerFactory.sol +++ /dev/null @@ -1,70 +0,0 @@ -pragma solidity ^0.4.24; - -import "./CountTransferManager.sol"; -import "../ModuleFactory.sol"; -import "../../libraries/Util.sol"; - -/** - * @title Factory for deploying CountTransferManager module - */ -contract CountTransferManagerFactory is ModuleFactory { - - /** - * @notice Constructor - * @param _polyAddress Address of the polytoken - */ - constructor (address _polyAddress, uint256 _setupCost, uint256 _usageCost, uint256 _subscriptionCost) public - ModuleFactory(_polyAddress, _setupCost, _usageCost, _subscriptionCost) - { - version = "2.1.0"; - name = "CountTransferManager"; - title = "Count Transfer Manager"; - description = "Restrict the number of investors"; - compatibleSTVersionRange["lowerBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0)); - compatibleSTVersionRange["upperBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0)); - } - - /** - * @notice Used to launch the Module with the help of factory - * @param _data Data used for the intialization of the module factory variables - * @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 due to insufficent Allowance provided"); - CountTransferManager countTransferManager = new CountTransferManager(msg.sender, address(polyToken)); - require(Util.getSig(_data) == countTransferManager.getInitFunction(), "Provided data is not valid"); - /*solium-disable-next-line security/no-low-level-calls*/ - require(address(countTransferManager).call(_data), "Unsuccessful call"); - /*solium-disable-next-line security/no-block-members*/ - emit GenerateModuleFromFactory(address(countTransferManager), getName(), address(this), msg.sender, setupCost, now); - return address(countTransferManager); - - } - - /** - * @notice Type of the Module factory - */ - function getTypes() external view returns(uint8[]) { - uint8[] memory res = new uint8[](1); - res[0] = 2; - return res; - } - - /** - * @notice Returns the instructions associated with the module - */ - function getInstructions() external view returns(string) { - return "Allows an issuer to restrict the total number of non-zero token holders"; - } - - /** - * @notice Get the tags related to the module factory - */ - function getTags() external view returns(bytes32[]) { - bytes32[] memory availableTags = new bytes32[](2); - availableTags[0] = "Count"; - availableTags[1] = "Transfer Restriction"; - return availableTags; - } -} diff --git a/contracts/modules/TransferManager/GTM/GeneralTransferManager.sol b/contracts/modules/TransferManager/GTM/GeneralTransferManager.sol new file mode 100644 index 000000000..923774349 --- /dev/null +++ b/contracts/modules/TransferManager/GTM/GeneralTransferManager.sol @@ -0,0 +1,704 @@ +pragma solidity 0.5.8; + +import "../TransferManager.sol"; +import "../../../libraries/Encoder.sol"; +import "../../../libraries/VersionUtils.sol"; +import "openzeppelin-solidity/contracts/math/SafeMath.sol"; +import "openzeppelin-solidity/contracts/cryptography/ECDSA.sol"; +import "./GeneralTransferManagerStorage.sol"; + +/** + * @title Transfer Manager module for core transfer validation functionality + */ +contract GeneralTransferManager is GeneralTransferManagerStorage, TransferManager { + using SafeMath for uint256; + using ECDSA for bytes32; + + // Emit when Issuance address get changed + event ChangeIssuanceAddress(address _issuanceAddress); + + // Emit when investor details get modified related to their whitelisting + event ChangeDefaults(uint64 _defaultCanSendAfter, uint64 _defaultCanReceiveAfter); + + // _canSendAfter is the time from which the _investor can send tokens + // _canReceiveAfter is the time from which the _investor can receive tokens + // if allowAllWhitelistIssuances is TRUE, then _canReceiveAfter is ignored when receiving tokens from the issuance address + // if allowAllWhitelistTransfers is TRUE, then _canReceiveAfter and _canSendAfter is ignored when sending or receiving tokens + // in any case, any investor sending or receiving tokens, must have a _expiryTime in the future + event ModifyKYCData( + address indexed _investor, + address indexed _addedBy, + uint64 _canSendAfter, + uint64 _canReceiveAfter, + uint64 _expiryTime + ); + + event ModifyInvestorFlag( + address indexed _investor, + uint8 indexed _flag, + bool _value + ); + + event ModifyTransferRequirements( + TransferType indexed _transferType, + bool _fromValidKYC, + bool _toValidKYC, + bool _fromRestricted, + bool _toRestricted + ); + + /** + * @notice Constructor + * @param _securityToken Address of the security token + */ + constructor(address _securityToken, address _polyToken) + public + Module(_securityToken, _polyToken) + { + + } + + /** + * @notice This function returns the signature of configure function + */ + function getInitFunction() public pure returns(bytes4) { + return bytes4(0); + } + + /** + * @notice Used to change the default times used when canSendAfter / canReceiveAfter are zero + * @param _defaultCanSendAfter default for zero canSendAfter + * @param _defaultCanReceiveAfter default for zero canReceiveAfter + */ + function changeDefaults(uint64 _defaultCanSendAfter, uint64 _defaultCanReceiveAfter) public withPerm(ADMIN) { + /* 0 values are also allowed as they represent that the Issuer + does not want a default value for these variables. + 0 is also the default value of these variables */ + defaults.canSendAfter = _defaultCanSendAfter; + defaults.canReceiveAfter = _defaultCanReceiveAfter; + emit ChangeDefaults(_defaultCanSendAfter, _defaultCanReceiveAfter); + } + + /** + * @notice Used to change the Issuance Address + * @param _issuanceAddress new address for the issuance + */ + function changeIssuanceAddress(address _issuanceAddress) public withPerm(ADMIN) { + // address(0x0) is also a valid value and in most cases, the address that issues tokens is 0x0. + issuanceAddress = _issuanceAddress; + emit ChangeIssuanceAddress(_issuanceAddress); + } + + /** + * @notice Default implementation of verifyTransfer used by SecurityToken + * If the transfer request comes from the STO, it only checks that the investor is in the whitelist + * If the transfer request comes from a token holder, it checks that: + * a) Both are on the whitelist + * b) Seller's sale lockup period is over + * c) Buyer's purchase lockup is over + * @param _from Address of the sender + * @param _to Address of the receiver + */ + function executeTransfer( + address _from, + address _to, + uint256 /*_amount*/, + bytes calldata _data + ) external returns(Result) { + if (_data.length > 32) { + address target; + uint256 nonce; + uint256 validFrom; + uint256 validTo; + bytes memory data; + (target, nonce, validFrom, validTo, data) = abi.decode(_data, (address, uint256, uint256, uint256, bytes)); + if (target == address(this)) + _processTransferSignature(nonce, validFrom, validTo, data); + } + (Result success,) = _verifyTransfer(_from, _to); + return success; + } + + function _processTransferSignature(uint256 _nonce, uint256 _validFrom, uint256 _validTo, bytes memory _data) internal { + address[] memory investor; + uint256[] memory canSendAfter; + uint256[] memory canReceiveAfter; + uint256[] memory expiryTime; + bytes memory signature; + (investor, canSendAfter, canReceiveAfter, expiryTime, signature) = + abi.decode(_data, (address[], uint256[], uint256[], uint256[], bytes)); + _modifyKYCDataSignedMulti(investor, canSendAfter, canReceiveAfter, expiryTime, _validFrom, _validTo, _nonce, signature); + } + + /** + * @notice Default implementation of verifyTransfer used by SecurityToken + * @param _from Address of the sender + * @param _to Address of the receiver + */ + function verifyTransfer( + address _from, + address _to, + uint256 /*_amount*/, + bytes memory /* _data */ + ) + public + view + returns(Result, bytes32) + { + return _verifyTransfer(_from, _to); + } + + function _verifyTransfer( + address _from, + address _to + ) + internal + view + returns(Result, bytes32) + { + if (!paused) { + TransferRequirements memory txReq; + uint64 canSendAfter; + uint64 fromExpiry; + uint64 toExpiry; + uint64 canReceiveAfter; + + if (_from == issuanceAddress) { + txReq = transferRequirements[uint8(TransferType.ISSUANCE)]; + } else if (_to == address(0)) { + txReq = transferRequirements[uint8(TransferType.REDEMPTION)]; + } else { + txReq = transferRequirements[uint8(TransferType.GENERAL)]; + } + + (canSendAfter, fromExpiry, canReceiveAfter, toExpiry) = _getValuesForTransfer(_from, _to); + + if ((txReq.fromValidKYC && !_validExpiry(fromExpiry)) || (txReq.toValidKYC && !_validExpiry(toExpiry))) { + return (Result.NA, bytes32(0)); + } + + (canSendAfter, canReceiveAfter) = _adjustTimes(canSendAfter, canReceiveAfter); + + if ((txReq.fromRestricted && !_validLockTime(canSendAfter)) || (txReq.toRestricted && !_validLockTime(canReceiveAfter))) { + return (Result.NA, bytes32(0)); + } + + return (Result.VALID, getAddressBytes32()); + } + return (Result.NA, bytes32(0)); + } + + /** + * @notice Modifies the successful checks required for a transfer to be deemed valid. + * @param _transferType Type of transfer (0 = General, 1 = Issuance, 2 = Redemption) + * @param _fromValidKYC Defines if KYC is required for the sender + * @param _toValidKYC Defines if KYC is required for the receiver + * @param _fromRestricted Defines if transfer time restriction is checked for the sender + * @param _toRestricted Defines if transfer time restriction is checked for the receiver + */ + function modifyTransferRequirements( + TransferType _transferType, + bool _fromValidKYC, + bool _toValidKYC, + bool _fromRestricted, + bool _toRestricted + ) public withPerm(ADMIN) { + _modifyTransferRequirements( + _transferType, + _fromValidKYC, + _toValidKYC, + _fromRestricted, + _toRestricted + ); + } + + /** + * @notice Modifies the successful checks required for transfers. + * @param _transferTypes Types of transfer (0 = General, 1 = Issuance, 2 = Redemption) + * @param _fromValidKYC Defines if KYC is required for the sender + * @param _toValidKYC Defines if KYC is required for the receiver + * @param _fromRestricted Defines if transfer time restriction is checked for the sender + * @param _toRestricted Defines if transfer time restriction is checked for the receiver + */ + function modifyTransferRequirementsMulti( + TransferType[] memory _transferTypes, + bool[] memory _fromValidKYC, + bool[] memory _toValidKYC, + bool[] memory _fromRestricted, + bool[] memory _toRestricted + ) public withPerm(ADMIN) { + require( + _transferTypes.length == _fromValidKYC.length && + _fromValidKYC.length == _toValidKYC.length && + _toValidKYC.length == _fromRestricted.length && + _fromRestricted.length == _toRestricted.length, + "Mismatched input lengths" + ); + + for (uint256 i = 0; i < _transferTypes.length; i++) { + _modifyTransferRequirements( + _transferTypes[i], + _fromValidKYC[i], + _toValidKYC[i], + _fromRestricted[i], + _toRestricted[i] + ); + } + } + + function _modifyTransferRequirements( + TransferType _transferType, + bool _fromValidKYC, + bool _toValidKYC, + bool _fromRestricted, + bool _toRestricted + ) internal { + transferRequirements[uint8(_transferType)] = + TransferRequirements( + _fromValidKYC, + _toValidKYC, + _fromRestricted, + _toRestricted + ); + + emit ModifyTransferRequirements( + _transferType, + _fromValidKYC, + _toValidKYC, + _fromRestricted, + _toRestricted + ); + } + + + /** + * @notice Add or remove KYC info of an investor. + * @param _investor is the address to whitelist + * @param _canSendAfter is the moment when the sale lockup period ends and the investor can freely sell or transfer their tokens + * @param _canReceiveAfter is the moment when the purchase lockup period ends and the investor can freely purchase or receive tokens from others + * @param _expiryTime is the moment till investors KYC will be validated. After that investor need to do re-KYC + */ + function modifyKYCData( + address _investor, + uint64 _canSendAfter, + uint64 _canReceiveAfter, + uint64 _expiryTime + ) + public + withPerm(ADMIN) + { + _modifyKYCData(_investor, _canSendAfter, _canReceiveAfter, _expiryTime); + } + + function _modifyKYCData(address _investor, uint64 _canSendAfter, uint64 _canReceiveAfter, uint64 _expiryTime) internal { + require(_investor != address(0), "Invalid investor"); + IDataStore dataStore = getDataStore(); + if (!_isExistingInvestor(_investor, dataStore)) { + dataStore.insertAddress(INVESTORSKEY, _investor); + } + uint256 _data = VersionUtils.packKYC(_canSendAfter, _canReceiveAfter, _expiryTime, uint8(1)); + dataStore.setUint256(_getKey(WHITELIST, _investor), _data); + emit ModifyKYCData(_investor, msg.sender, _canSendAfter, _canReceiveAfter, _expiryTime); + } + + /** + * @notice Add or remove KYC info of an investor. + * @param _investors is the address to whitelist + * @param _canSendAfter is the moment when the sale lockup period ends and the investor can freely sell his tokens + * @param _canReceiveAfter is the moment when the purchase lockup period ends and the investor can freely purchase tokens from others + * @param _expiryTime is the moment till investors KYC will be validated. After that investor need to do re-KYC + */ + function modifyKYCDataMulti( + address[] memory _investors, + uint64[] memory _canSendAfter, + uint64[] memory _canReceiveAfter, + uint64[] memory _expiryTime + ) + public + withPerm(ADMIN) + { + require( + _investors.length == _canSendAfter.length && + _canSendAfter.length == _canReceiveAfter.length && + _canReceiveAfter.length == _expiryTime.length, + "Mismatched input lengths" + ); + for (uint256 i = 0; i < _investors.length; i++) { + _modifyKYCData(_investors[i], _canSendAfter[i], _canReceiveAfter[i], _expiryTime[i]); + } + } + + /** + * @notice Used to modify investor Flag. + * @dev Flags are properties about investors that can be true or false like isAccredited + * @param _investor is the address of the investor. + * @param _flag index of flag to change. flag is used to know specifics about investor like isAccredited. + * @param _value value of the flag. a flag can be true or false. + */ + function modifyInvestorFlag( + address _investor, + uint8 _flag, + bool _value + ) + public + withPerm(ADMIN) + { + _modifyInvestorFlag(_investor, _flag, _value); + } + + + function _modifyInvestorFlag(address _investor, uint8 _flag, bool _value) internal { + require(_investor != address(0), "Invalid investor"); + IDataStore dataStore = getDataStore(); + if (!_isExistingInvestor(_investor, dataStore)) { + dataStore.insertAddress(INVESTORSKEY, _investor); + //KYC data can not be present if _isExistingInvestor is false and hence we can set packed KYC as uint256(1) to set `added` as true + dataStore.setUint256(_getKey(WHITELIST, _investor), uint256(1)); + } + //NB Flags are packed together in a uint256 to save gas. We can have a maximum of 256 flags. + uint256 flags = dataStore.getUint256(_getKey(INVESTORFLAGS, _investor)); + if (_value) + flags = flags | (ONE << _flag); + else + flags = flags & ~(ONE << _flag); + dataStore.setUint256(_getKey(INVESTORFLAGS, _investor), flags); + emit ModifyInvestorFlag(_investor, _flag, _value); + } + + /** + * @notice Used to modify investor data. + * @param _investors List of the addresses to modify data about. + * @param _flag index of flag to change. flag is used to know specifics about investor like isAccredited. + * @param _value value of the flag. a flag can be true or false. + */ + function modifyInvestorFlagMulti( + address[] memory _investors, + uint8[] memory _flag, + bool[] memory _value + ) + public + withPerm(ADMIN) + { + require( + _investors.length == _flag.length && + _flag.length == _value.length, + "Mismatched input lengths" + ); + for (uint256 i = 0; i < _investors.length; i++) { + _modifyInvestorFlag(_investors[i], _flag[i], _value[i]); + } + } + + /** + * @notice Adds or removes addresses from the whitelist - can be called by anyone with a valid signature + * @param _investor is the address to whitelist + * @param _canSendAfter is the moment when the sale lockup period ends and the investor can freely sell his tokens + * @param _canReceiveAfter is the moment when the purchase lockup period ends and the investor can freely purchase tokens from others + * @param _expiryTime is the moment till investors KYC will be validated. After that investor need to do re-KYC + * @param _validFrom is the time that this signature is valid from + * @param _validTo is the time that this signature is valid until + * @param _nonce nonce of signature (avoid replay attack) + * @param _signature issuer signature + */ + function modifyKYCDataSigned( + address _investor, + uint256 _canSendAfter, + uint256 _canReceiveAfter, + uint256 _expiryTime, + uint256 _validFrom, + uint256 _validTo, + uint256 _nonce, + bytes memory _signature + ) + public + { + require( + _modifyKYCDataSigned(_investor, _canSendAfter, _canReceiveAfter, _expiryTime, _validFrom, _validTo, _nonce, _signature), + "Invalid signature or data" + ); + } + + function _modifyKYCDataSigned( + address _investor, + uint256 _canSendAfter, + uint256 _canReceiveAfter, + uint256 _expiryTime, + uint256 _validFrom, + uint256 _validTo, + uint256 _nonce, + bytes memory _signature + ) + internal + returns(bool) + { + /*solium-disable-next-line security/no-block-members*/ + if(_validFrom > now || _validTo < now || _investor == address(0)) + return false; + bytes32 hash = keccak256( + abi.encodePacked(this, _investor, _canSendAfter, _canReceiveAfter, _expiryTime, _validFrom, _validTo, _nonce) + ); + if (_checkSig(hash, _signature, _nonce)) { + require( + uint64(_canSendAfter) == _canSendAfter && + uint64(_canReceiveAfter) == _canReceiveAfter && + uint64(_expiryTime) == _expiryTime, + "uint64 overflow" + ); + _modifyKYCData(_investor, uint64(_canSendAfter), uint64(_canReceiveAfter), uint64(_expiryTime)); + return true; + } + return false; + } + + /** + * @notice Adds or removes addresses from the whitelist - can be called by anyone with a valid signature + * @dev using uint256 for some uint256 variables as web3 wasn;t packing and hashing uint64 arrays properly + * @param _investor is the address to whitelist + * @param _canSendAfter is the moment when the sale lockup period ends and the investor can freely sell his tokens + * @param _canReceiveAfter is the moment when the purchase lockup period ends and the investor can freely purchase tokens from others + * @param _expiryTime is the moment till investors KYC will be validated. After that investor need to do re-KYC + * @param _validFrom is the time that this signature is valid from + * @param _validTo is the time that this signature is valid until + * @param _nonce nonce of signature (avoid replay attack) + * @param _signature issuer signature + */ + function modifyKYCDataSignedMulti( + address[] memory _investor, + uint256[] memory _canSendAfter, + uint256[] memory _canReceiveAfter, + uint256[] memory _expiryTime, + uint256 _validFrom, + uint256 _validTo, + uint256 _nonce, + bytes memory _signature + ) + public + { + require( + _modifyKYCDataSignedMulti(_investor, _canSendAfter, _canReceiveAfter, _expiryTime, _validFrom, _validTo, _nonce, _signature), + "Invalid signature or data" + ); + } + + function _modifyKYCDataSignedMulti( + address[] memory _investor, + uint256[] memory _canSendAfter, + uint256[] memory _canReceiveAfter, + uint256[] memory _expiryTime, + uint256 _validFrom, + uint256 _validTo, + uint256 _nonce, + bytes memory _signature + ) + internal + returns(bool) + { + if (_investor.length != _canSendAfter.length || + _canSendAfter.length != _canReceiveAfter.length || + _canReceiveAfter.length != _expiryTime.length + ) { + return false; + } + + if (_validFrom > now || _validTo < now) { + return false; + } + + bytes32 hash = keccak256( + abi.encodePacked(this, _investor, _canSendAfter, _canReceiveAfter, _expiryTime, _validFrom, _validTo, _nonce) + ); + + if (_checkSig(hash, _signature, _nonce)) { + for (uint256 i = 0; i < _investor.length; i++) { + if (uint64(_canSendAfter[i]) == _canSendAfter[i] && + uint64(_canReceiveAfter[i]) == _canReceiveAfter[i] && + uint64(_expiryTime[i]) == _expiryTime[i] + ) + _modifyKYCData(_investor[i], uint64(_canSendAfter[i]), uint64(_canReceiveAfter[i]), uint64(_expiryTime[i])); + } + return true; + } + return false; + } + + /** + * @notice Used to verify the signature + */ + function _checkSig(bytes32 _hash, bytes memory _signature, uint256 _nonce) internal returns(bool) { + //Check that the signature is valid + //sig should be signing - _investor, _canSendAfter, _canReceiveAfter & _expiryTime and be signed by the issuer address + address signer = _hash.toEthSignedMessageHash().recover(_signature); + if (nonceMap[signer][_nonce] || !_checkPerm(OPERATOR, signer)) { + return false; + } + nonceMap[signer][_nonce] = true; + return true; + } + + /** + * @notice Internal function used to check whether the KYC of investor is valid + * @param _expiryTime Expiry time of the investor + */ + function _validExpiry(uint64 _expiryTime) internal view returns(bool valid) { + if (_expiryTime >= uint64(now)) /*solium-disable-line security/no-block-members*/ + valid = true; + } + + /** + * @notice Internal function used to check whether the lock time of investor is valid + * @param _lockTime Lock time of the investor + */ + function _validLockTime(uint64 _lockTime) internal view returns(bool valid) { + if (_lockTime <= uint64(now)) /*solium-disable-line security/no-block-members*/ + valid = true; + } + + /** + * @notice Internal function to adjust times using default values + */ + function _adjustTimes(uint64 _canSendAfter, uint64 _canReceiveAfter) internal view returns(uint64, uint64) { + if (_canSendAfter == 0) { + _canSendAfter = defaults.canSendAfter; + } + if (_canReceiveAfter == 0) { + _canReceiveAfter = defaults.canReceiveAfter; + } + return (_canSendAfter, _canReceiveAfter); + } + + function _getKey(bytes32 _key1, address _key2) internal pure returns(bytes32) { + return bytes32(keccak256(abi.encodePacked(_key1, _key2))); + } + + function _getKYCValues(address _investor, IDataStore dataStore) internal view returns( + uint64 canSendAfter, + uint64 canReceiveAfter, + uint64 expiryTime, + uint8 added + ) + { + uint256 data = dataStore.getUint256(_getKey(WHITELIST, _investor)); + (canSendAfter, canReceiveAfter, expiryTime, added) = VersionUtils.unpackKYC(data); + } + + function _isExistingInvestor(address _investor, IDataStore dataStore) internal view returns(bool) { + uint256 data = dataStore.getUint256(_getKey(WHITELIST, _investor)); + //extracts `added` from packed `_whitelistData` + return uint8(data) == 0 ? false : true; + } + + function _getValuesForTransfer(address _from, address _to) internal view returns(uint64 canSendAfter, uint64 fromExpiry, uint64 canReceiveAfter, uint64 toExpiry) { + IDataStore dataStore = getDataStore(); + (canSendAfter, , fromExpiry, ) = _getKYCValues(_from, dataStore); + (, canReceiveAfter, toExpiry, ) = _getKYCValues(_to, dataStore); + } + + /** + * @dev Returns list of all investors + */ + function getAllInvestors() public view returns(address[] memory investors) { + IDataStore dataStore = getDataStore(); + investors = dataStore.getAddressArray(INVESTORSKEY); + } + + /** + * @dev Returns list of investors in a range + */ + function getInvestors(uint256 _fromIndex, uint256 _toIndex) public view returns(address[] memory investors) { + IDataStore dataStore = getDataStore(); + investors = dataStore.getAddressArrayElements(INVESTORSKEY, _fromIndex, _toIndex); + } + + function getAllInvestorFlags() public view returns(address[] memory investors, uint256[] memory flags) { + investors = getAllInvestors(); + flags = new uint256[](investors.length); + for (uint256 i = 0; i < investors.length; i++) { + flags[i] = _getInvestorFlags(investors[i]); + } + } + + function getInvestorFlag(address _investor, uint8 _flag) public view returns(bool value) { + uint256 flag = (_getInvestorFlags(_investor) >> _flag) & ONE; + value = flag > 0 ? true : false; + } + + function getInvestorFlags(address _investor) public view returns(uint256 flags) { + flags = _getInvestorFlags(_investor); + } + + function _getInvestorFlags(address _investor) internal view returns(uint256 flags) { + IDataStore dataStore = getDataStore(); + flags = dataStore.getUint256(_getKey(INVESTORFLAGS, _investor)); + } + + /** + * @dev Returns list of all investors data + */ + function getAllKYCData() external view returns( + address[] memory investors, + uint256[] memory canSendAfters, + uint256[] memory canReceiveAfters, + uint256[] memory expiryTimes + ) { + investors = getAllInvestors(); + (canSendAfters, canReceiveAfters, expiryTimes) = _kycData(investors); + return (investors, canSendAfters, canReceiveAfters, expiryTimes); + } + + /** + * @dev Returns list of specified investors data + */ + function getKYCData(address[] calldata _investors) external view returns( + uint256[] memory, + uint256[] memory, + uint256[] memory + ) { + return _kycData(_investors); + } + + function _kycData(address[] memory _investors) internal view returns( + uint256[] memory, + uint256[] memory, + uint256[] memory + ) { + uint256[] memory canSendAfters = new uint256[](_investors.length); + uint256[] memory canReceiveAfters = new uint256[](_investors.length); + uint256[] memory expiryTimes = new uint256[](_investors.length); + for (uint256 i = 0; i < _investors.length; i++) { + (canSendAfters[i], canReceiveAfters[i], expiryTimes[i], ) = _getKYCValues(_investors[i], getDataStore()); + } + return (canSendAfters, canReceiveAfters, expiryTimes); + } + + /** + * @notice Return the permissions flag that are associated with general trnasfer manager + */ + function getPermissions() public view returns(bytes32[] memory) { + bytes32[] memory allPermissions = new bytes32[](1); + allPermissions[0] = ADMIN; + return allPermissions; + } + + /** + * @notice return the amount of tokens for a given user as per the partition + * @param _partition Identifier + * @param _tokenHolder Whom token amount need to query + * @param _additionalBalance It is the `_value` that transfer during transfer/transferFrom function call + */ + function getTokensByPartition(bytes32 _partition, address _tokenHolder, uint256 _additionalBalance) external view returns(uint256) { + uint256 currentBalance = (msg.sender == address(securityToken)) ? (securityToken.balanceOf(_tokenHolder)).add(_additionalBalance) : securityToken.balanceOf(_tokenHolder); + uint256 canSendAfter; + (canSendAfter,,,) = _getKYCValues(_tokenHolder, getDataStore()); + canSendAfter = (canSendAfter == 0 ? defaults.canSendAfter: canSendAfter); + bool unlockedCheck = paused ? _partition == UNLOCKED : (_partition == UNLOCKED && now >= canSendAfter); + if (((_partition == LOCKED && now < canSendAfter) && !paused) || unlockedCheck) + return currentBalance; + else + return 0; + } + + function getAddressBytes32() public view returns(bytes32) { + return bytes32(uint256(address(this)) << 96); + } + +} diff --git a/contracts/modules/TransferManager/GTM/GeneralTransferManagerFactory.sol b/contracts/modules/TransferManager/GTM/GeneralTransferManagerFactory.sol new file mode 100644 index 000000000..606f334fc --- /dev/null +++ b/contracts/modules/TransferManager/GTM/GeneralTransferManagerFactory.sol @@ -0,0 +1,53 @@ +pragma solidity 0.5.8; + +import "./GeneralTransferManagerProxy.sol"; +import "../../UpgradableModuleFactory.sol"; + +/** + * @title Factory for deploying GeneralTransferManager module + */ +contract GeneralTransferManagerFactory is UpgradableModuleFactory { + + /** + * @notice Constructor + * @param _setupCost Setup cost of the module + * @param _logicContract Contract address that contains the logic related to `description` + * @param _polymathRegistry Address of the Polymath registry + * @param _isCostInPoly true = cost in Poly, false = USD + */ + constructor ( + uint256 _setupCost, + address _logicContract, + address _polymathRegistry, + bool _isCostInPoly + ) + public + UpgradableModuleFactory("3.0.0", _setupCost, _logicContract, _polymathRegistry, _isCostInPoly) + { + name = "GeneralTransferManager"; + title = "General Transfer Manager"; + description = "Manage transfers using a time based whitelist"; + typesData.push(2); + typesData.push(6); + tagsData.push("General"); + tagsData.push("Transfer Restriction"); + compatibleSTVersionRange["lowerBound"] = VersionUtils.pack(uint8(3), uint8(0), uint8(0)); + compatibleSTVersionRange["upperBound"] = VersionUtils.pack(uint8(3), uint8(0), uint8(0)); + } + + /** + * @notice Used to launch the Module with the help of factory + * @return address Contract address of the Module + */ + function deploy( + bytes calldata _data + ) + external + returns(address) + { + address generalTransferManager = address(new GeneralTransferManagerProxy(logicContracts[latestUpgrade].version, msg.sender, polymathRegistry.getAddress("PolyToken"), logicContracts[latestUpgrade].logicContract)); + _initializeModule(generalTransferManager, _data); + return generalTransferManager; + } + +} diff --git a/contracts/modules/TransferManager/GTM/GeneralTransferManagerProxy.sol b/contracts/modules/TransferManager/GTM/GeneralTransferManagerProxy.sol new file mode 100644 index 000000000..3cde10800 --- /dev/null +++ b/contracts/modules/TransferManager/GTM/GeneralTransferManagerProxy.sol @@ -0,0 +1,34 @@ +pragma solidity 0.5.8; + +import "../../../proxy/OwnedUpgradeabilityProxy.sol"; +import "./GeneralTransferManagerStorage.sol"; +import "../../../Pausable.sol"; +import "../../../storage/modules/ModuleStorage.sol"; + +/** + * @title Transfer Manager module for core transfer validation functionality + */ +contract GeneralTransferManagerProxy is GeneralTransferManagerStorage, ModuleStorage, Pausable, OwnedUpgradeabilityProxy { + /** + * @notice Constructor + * @param _securityToken Address of the security token + * @param _polyAddress Address of the polytoken + * @param _implementation representing the address of the new implementation to be set + */ + constructor( + string memory _version, + address _securityToken, + address _polyAddress, + address _implementation + ) + public + ModuleStorage(_securityToken, _polyAddress) + { + require(_implementation != address(0), "Implementation address should not be 0x"); + _upgradeTo(_version, _implementation); + transferRequirements[uint8(TransferType.GENERAL)] = TransferRequirements(true, true, true, true); + transferRequirements[uint8(TransferType.ISSUANCE)] = TransferRequirements(false, true, false, false); + transferRequirements[uint8(TransferType.REDEMPTION)] = TransferRequirements(true, false, false, false); + } + +} diff --git a/contracts/modules/TransferManager/GTM/GeneralTransferManagerStorage.sol b/contracts/modules/TransferManager/GTM/GeneralTransferManagerStorage.sol new file mode 100644 index 000000000..911af513c --- /dev/null +++ b/contracts/modules/TransferManager/GTM/GeneralTransferManagerStorage.sol @@ -0,0 +1,39 @@ +pragma solidity 0.5.8; + +/** + * @title Transfer Manager module for core transfer validation functionality + */ +contract GeneralTransferManagerStorage { + + bytes32 public constant WHITELIST = "WHITELIST"; + bytes32 public constant INVESTORSKEY = 0xdf3a8dd24acdd05addfc6aeffef7574d2de3f844535ec91e8e0f3e45dba96731; //keccak256(abi.encodePacked("INVESTORS")) + bytes32 public constant INVESTORFLAGS = "INVESTORFLAGS"; + uint256 internal constant ONE = uint256(1); + + enum TransferType { GENERAL, ISSUANCE, REDEMPTION } + + //Address from which issuances come + address public issuanceAddress; + + // Allows all TimeRestrictions to be offset + struct Defaults { + uint64 canSendAfter; + uint64 canReceiveAfter; + } + + // Offset to be applied to all timings (except KYC expiry) + Defaults public defaults; + + // Map of used nonces by customer + mapping(address => mapping(uint256 => bool)) public nonceMap; + + struct TransferRequirements { + bool fromValidKYC; + bool toValidKYC; + bool fromRestricted; + bool toRestricted; + } + + mapping(uint8 => TransferRequirements) public transferRequirements; + // General = 0, Issuance = 1, Redemption = 2 +} diff --git a/contracts/modules/TransferManager/GeneralTransferManager.sol b/contracts/modules/TransferManager/GeneralTransferManager.sol deleted file mode 100644 index 0d2f96700..000000000 --- a/contracts/modules/TransferManager/GeneralTransferManager.sol +++ /dev/null @@ -1,402 +0,0 @@ -pragma solidity ^0.4.24; - -import "./ITransferManager.sol"; -import "../../storage/GeneralTransferManagerStorage.sol"; -import "openzeppelin-solidity/contracts/math/SafeMath.sol"; - -/** - * @title Transfer Manager module for core transfer validation functionality - */ -contract GeneralTransferManager is GeneralTransferManagerStorage, ITransferManager { - - using SafeMath for uint256; - - // Emit when Issuance address get changed - event ChangeIssuanceAddress(address _issuanceAddress); - // Emit when there is change in the flag variable called allowAllTransfers - event AllowAllTransfers(bool _allowAllTransfers); - // Emit when there is change in the flag variable called allowAllWhitelistTransfers - event AllowAllWhitelistTransfers(bool _allowAllWhitelistTransfers); - // Emit when there is change in the flag variable called allowAllWhitelistIssuances - event AllowAllWhitelistIssuances(bool _allowAllWhitelistIssuances); - // Emit when there is change in the flag variable called allowAllBurnTransfers - event AllowAllBurnTransfers(bool _allowAllBurnTransfers); - // Emit when there is change in the flag variable called signingAddress - event ChangeSigningAddress(address _signingAddress); - // Emit when investor details get modified related to their whitelisting - event ChangeDefaults(uint64 _defaultCanSendAfter, uint64 _defaultCanReceiveAfter); - - // _canSendAfter is the time from which the _investor can send tokens - // _canReceiveAfter is the time from which the _investor can receive tokens - // if allowAllWhitelistIssuances is TRUE, then _canReceiveAfter is ignored when receiving tokens from the issuance address - // if allowAllWhitelistTransfers is TRUE, then _canReceiveAfter and _canSendAfter is ignored when sending or receiving tokens - // in any case, any investor sending or receiving tokens, must have a _expiryTime in the future - event ModifyWhitelist( - address indexed _investor, - uint256 _dateAdded, - address indexed _addedBy, - uint256 _canSendAfter, - uint256 _canReceiveAfter, - uint256 _expiryTime, - bool _canBuyFromSTO - ); - - /** - * @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 This function returns the signature of configure function - */ - function getInitFunction() public pure returns (bytes4) { - return bytes4(0); - } - - /** - * @notice Used to change the default times used when canSendAfter / canReceiveAfter are zero - * @param _defaultCanSendAfter default for zero canSendAfter - * @param _defaultCanReceiveAfter default for zero canReceiveAfter - */ - function changeDefaults(uint64 _defaultCanSendAfter, uint64 _defaultCanReceiveAfter) public withPerm(FLAGS) { - /* 0 values are also allowed as they represent that the Issuer - does not want a default value for these variables. - 0 is also the default value of these variables */ - defaults.canSendAfter = _defaultCanSendAfter; - defaults.canReceiveAfter = _defaultCanReceiveAfter; - emit ChangeDefaults(_defaultCanSendAfter, _defaultCanReceiveAfter); - } - - /** - * @notice Used to change the Issuance Address - * @param _issuanceAddress new address for the issuance - */ - function changeIssuanceAddress(address _issuanceAddress) public withPerm(FLAGS) { - // address(0x0) is also a valid value and in most cases, the address that issues tokens is 0x0. - issuanceAddress = _issuanceAddress; - emit ChangeIssuanceAddress(_issuanceAddress); - } - - /** - * @notice Used to change the Sigining Address - * @param _signingAddress new address for the signing - */ - function changeSigningAddress(address _signingAddress) public withPerm(FLAGS) { - /* address(0x0) is also a valid value as an Issuer might want to - give this permission to nobody (except their own address). - 0x0 is also the default value */ - signingAddress = _signingAddress; - emit ChangeSigningAddress(_signingAddress); - } - - /** - * @notice Used to change the flag - true - It refers there are no transfer restrictions, for any addresses - false - It refers transfers are restricted for all addresses. - * @param _allowAllTransfers flag value - */ - function changeAllowAllTransfers(bool _allowAllTransfers) public withPerm(FLAGS) { - allowAllTransfers = _allowAllTransfers; - emit AllowAllTransfers(_allowAllTransfers); - } - - /** - * @notice Used to change the flag - true - It refers that time lock is ignored for transfers (address must still be on whitelist) - false - It refers transfers are restricted for all addresses. - * @param _allowAllWhitelistTransfers flag value - */ - function changeAllowAllWhitelistTransfers(bool _allowAllWhitelistTransfers) public withPerm(FLAGS) { - allowAllWhitelistTransfers = _allowAllWhitelistTransfers; - emit AllowAllWhitelistTransfers(_allowAllWhitelistTransfers); - } - - /** - * @notice Used to change the flag - true - It refers that time lock is ignored for issuances (address must still be on whitelist) - false - It refers transfers are restricted for all addresses. - * @param _allowAllWhitelistIssuances flag value - */ - function changeAllowAllWhitelistIssuances(bool _allowAllWhitelistIssuances) public withPerm(FLAGS) { - allowAllWhitelistIssuances = _allowAllWhitelistIssuances; - emit AllowAllWhitelistIssuances(_allowAllWhitelistIssuances); - } - - /** - * @notice Used to change the flag - true - It allow to burn the tokens - false - It deactivate the burning mechanism. - * @param _allowAllBurnTransfers flag value - */ - function changeAllowAllBurnTransfers(bool _allowAllBurnTransfers) public withPerm(FLAGS) { - allowAllBurnTransfers = _allowAllBurnTransfers; - emit AllowAllBurnTransfers(_allowAllBurnTransfers); - } - - /** - * @notice Default implementation of verifyTransfer used by SecurityToken - * If the transfer request comes from the STO, it only checks that the investor is in the whitelist - * If the transfer request comes from a token holder, it checks that: - * a) Both are on the whitelist - * b) Seller's sale lockup period is over - * c) Buyer's purchase lockup is over - * @param _from Address of the sender - * @param _to Address of the receiver - */ - function verifyTransfer(address _from, address _to, uint256 /*_amount*/, bytes /* _data */, bool /* _isTransfer */) public returns(Result) { - if (!paused) { - if (allowAllTransfers) { - //All transfers allowed, regardless of whitelist - return Result.VALID; - } - if (allowAllBurnTransfers && (_to == address(0))) { - return Result.VALID; - } - if (allowAllWhitelistTransfers) { - //Anyone on the whitelist can transfer, regardless of time - return (_onWhitelist(_to) && _onWhitelist(_from)) ? Result.VALID : Result.NA; - } - - (uint64 adjustedCanSendAfter, uint64 adjustedCanReceiveAfter) = _adjustTimes(whitelist[_from].canSendAfter, whitelist[_to].canReceiveAfter); - if (_from == issuanceAddress) { - // Possible STO transaction, but investor not allowed to purchased from STO - if ((whitelist[_to].canBuyFromSTO == 0) && _isSTOAttached()) { - return Result.NA; - } - // if allowAllWhitelistIssuances is true, so time stamp ignored - if (allowAllWhitelistIssuances) { - return _onWhitelist(_to) ? Result.VALID : Result.NA; - } else { - return (_onWhitelist(_to) && (adjustedCanReceiveAfter <= uint64(now))) ? Result.VALID : Result.NA; - } - } - - //Anyone on the whitelist can transfer provided the blocknumber is large enough - /*solium-disable-next-line security/no-block-members*/ - return ((_onWhitelist(_from) && (adjustedCanSendAfter <= uint64(now))) && - (_onWhitelist(_to) && (adjustedCanReceiveAfter <= uint64(now)))) ? Result.VALID : Result.NA; /*solium-disable-line security/no-block-members*/ - } - return Result.NA; - } - - /** - * @notice Adds or removes addresses from the whitelist. - * @param _investor is the address to whitelist - * @param _canSendAfter the moment when the sale lockup period ends and the investor can freely sell or transfer away their tokens - * @param _canReceiveAfter the moment when the purchase lockup period ends and the investor can freely purchase or receive from others - * @param _expiryTime is the moment till investors KYC will be validated. After that investor need to do re-KYC - * @param _canBuyFromSTO is used to know whether the investor is restricted investor or not. - */ - function modifyWhitelist( - address _investor, - uint256 _canSendAfter, - uint256 _canReceiveAfter, - uint256 _expiryTime, - bool _canBuyFromSTO - ) - public - withPerm(WHITELIST) - { - _modifyWhitelist(_investor, _canSendAfter, _canReceiveAfter, _expiryTime, _canBuyFromSTO); - } - - /** - * @notice Adds or removes addresses from the whitelist. - * @param _investor is the address to whitelist - * @param _canSendAfter is the moment when the sale lockup period ends and the investor can freely sell his tokens - * @param _canReceiveAfter is the moment when the purchase lockup period ends and the investor can freely purchase tokens from others - * @param _expiryTime is the moment till investors KYC will be validated. After that investor need to do re-KYC - * @param _canBuyFromSTO is used to know whether the investor is restricted investor or not. - */ - function _modifyWhitelist( - address _investor, - uint256 _canSendAfter, - uint256 _canReceiveAfter, - uint256 _expiryTime, - bool _canBuyFromSTO - ) - internal - { - require(_investor != address(0), "Invalid investor"); - uint8 canBuyFromSTO = 0; - if (_canBuyFromSTO) { - canBuyFromSTO = 1; - } - if (whitelist[_investor].added == uint8(0)) { - investors.push(_investor); - } - require( - uint64(_canSendAfter) == _canSendAfter && - uint64(_canReceiveAfter) == _canReceiveAfter && - uint64(_expiryTime) == _expiryTime, - "uint64 overflow" - ); - whitelist[_investor] = TimeRestriction(uint64(_canSendAfter), uint64(_canReceiveAfter), uint64(_expiryTime), canBuyFromSTO, uint8(1)); - emit ModifyWhitelist(_investor, now, msg.sender, _canSendAfter, _canReceiveAfter, _expiryTime, _canBuyFromSTO); - } - - /** - * @notice Adds or removes addresses from the whitelist. - * @param _investors List of the addresses to whitelist - * @param _canSendAfters An array of the moment when the sale lockup period ends and the investor can freely sell his tokens - * @param _canReceiveAfters An array of the moment when the purchase lockup period ends and the investor can freely purchase tokens from others - * @param _expiryTimes An array of the moment till investors KYC will be validated. After that investor need to do re-KYC - * @param _canBuyFromSTO An array of boolean values - */ - function modifyWhitelistMulti( - address[] _investors, - uint256[] _canSendAfters, - uint256[] _canReceiveAfters, - uint256[] _expiryTimes, - bool[] _canBuyFromSTO - ) public withPerm(WHITELIST) { - require(_investors.length == _canSendAfters.length, "Mismatched input lengths"); - require(_canSendAfters.length == _canReceiveAfters.length, "Mismatched input lengths"); - require(_canReceiveAfters.length == _expiryTimes.length, "Mismatched input lengths"); - require(_canBuyFromSTO.length == _canReceiveAfters.length, "Mismatched input length"); - for (uint256 i = 0; i < _investors.length; i++) { - _modifyWhitelist(_investors[i], _canSendAfters[i], _canReceiveAfters[i], _expiryTimes[i], _canBuyFromSTO[i]); - } - } - - /** - * @notice Adds or removes addresses from the whitelist - can be called by anyone with a valid signature - * @param _investor is the address to whitelist - * @param _canSendAfter is the moment when the sale lockup period ends and the investor can freely sell his tokens - * @param _canReceiveAfter is the moment when the purchase lockup period ends and the investor can freely purchase tokens from others - * @param _expiryTime is the moment till investors KYC will be validated. After that investor need to do re-KYC - * @param _canBuyFromSTO is used to know whether the investor is restricted investor or not. - * @param _validFrom is the time that this signature is valid from - * @param _validTo is the time that this signature is valid until - * @param _nonce nonce of signature (avoid replay attack) - * @param _v issuer signature - * @param _r issuer signature - * @param _s issuer signature - */ - function modifyWhitelistSigned( - address _investor, - uint256 _canSendAfter, - uint256 _canReceiveAfter, - uint256 _expiryTime, - bool _canBuyFromSTO, - uint256 _validFrom, - uint256 _validTo, - uint256 _nonce, - uint8 _v, - bytes32 _r, - bytes32 _s - ) public { - /*solium-disable-next-line security/no-block-members*/ - require(_validFrom <= now, "ValidFrom is too early"); - /*solium-disable-next-line security/no-block-members*/ - require(_validTo >= now, "ValidTo is too late"); - require(!nonceMap[_investor][_nonce], "Already used signature"); - nonceMap[_investor][_nonce] = true; - bytes32 hash = keccak256( - abi.encodePacked(this, _investor, _canSendAfter, _canReceiveAfter, _expiryTime, _canBuyFromSTO, _validFrom, _validTo, _nonce) - ); - _checkSig(hash, _v, _r, _s); - _modifyWhitelist(_investor, _canSendAfter, _canReceiveAfter, _expiryTime, _canBuyFromSTO); - } - - /** - * @notice Used to verify the signature - */ - function _checkSig(bytes32 _hash, uint8 _v, bytes32 _r, bytes32 _s) internal view { - //Check that the signature is valid - //sig should be signing - _investor, _canSendAfter, _canReceiveAfter & _expiryTime and be signed by the issuer address - address signer = ecrecover(keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", _hash)), _v, _r, _s); - require(signer == Ownable(securityToken).owner() || signer == signingAddress, "Incorrect signer"); - } - - /** - * @notice Internal function used to check whether the investor is in the whitelist or not - & also checks whether the KYC of investor get expired or not - * @param _investor Address of the investor - */ - function _onWhitelist(address _investor) internal view returns(bool) { - return (whitelist[_investor].expiryTime >= uint64(now)); /*solium-disable-line security/no-block-members*/ - } - - /** - * @notice Internal function use to know whether the STO is attached or not - */ - function _isSTOAttached() internal view returns(bool) { - bool attached = ISecurityToken(securityToken).getModulesByType(3).length > 0; - return attached; - } - - /** - * @notice Internal function to adjust times using default values - */ - function _adjustTimes(uint64 _canSendAfter, uint64 _canReceiveAfter) internal view returns(uint64, uint64) { - uint64 adjustedCanSendAfter = _canSendAfter; - uint64 adjustedCanReceiveAfter = _canReceiveAfter; - if (_canSendAfter == 0) { - adjustedCanSendAfter = defaults.canSendAfter; - } - if (_canReceiveAfter == 0) { - adjustedCanReceiveAfter = defaults.canReceiveAfter; - } - return (adjustedCanSendAfter, adjustedCanReceiveAfter); - } - - /** - * @dev Returns list of all investors - */ - function getInvestors() external view returns(address[]) { - return investors; - } - - /** - * @dev Returns list of all investors data - */ - function getAllInvestorsData() external view returns(address[], uint256[], uint256[], uint256[], bool[]) { - (uint256[] memory canSendAfters, uint256[] memory canReceiveAfters, uint256[] memory expiryTimes, bool[] memory canBuyFromSTOs) - = _investorsData(investors); - return (investors, canSendAfters, canReceiveAfters, expiryTimes, canBuyFromSTOs); - - } - - /** - * @dev Returns list of specified investors data - */ - function getInvestorsData(address[] _investors) external view returns(uint256[], uint256[], uint256[], bool[]) { - return _investorsData(_investors); - } - - function _investorsData(address[] _investors) internal view returns(uint256[], uint256[], uint256[], bool[]) { - uint256[] memory canSendAfters = new uint256[](_investors.length); - uint256[] memory canReceiveAfters = new uint256[](_investors.length); - uint256[] memory expiryTimes = new uint256[](_investors.length); - bool[] memory canBuyFromSTOs = new bool[](_investors.length); - for (uint256 i = 0; i < _investors.length; i++) { - canSendAfters[i] = whitelist[_investors[i]].canSendAfter; - canReceiveAfters[i] = whitelist[_investors[i]].canReceiveAfter; - expiryTimes[i] = whitelist[_investors[i]].expiryTime; - if (whitelist[_investors[i]].canBuyFromSTO == 0) { - canBuyFromSTOs[i] = false; - } else { - canBuyFromSTOs[i] = true; - } - } - return (canSendAfters, canReceiveAfters, expiryTimes, canBuyFromSTOs); - } - - /** - * @notice Return the permissions flag that are associated with general trnasfer manager - */ - function getPermissions() public view returns(bytes32[]) { - bytes32[] memory allPermissions = new bytes32[](2); - allPermissions[0] = WHITELIST; - allPermissions[1] = FLAGS; - return allPermissions; - } - -} diff --git a/contracts/modules/TransferManager/GeneralTransferManagerFactory.sol b/contracts/modules/TransferManager/GeneralTransferManagerFactory.sol deleted file mode 100644 index cffc61a32..000000000 --- a/contracts/modules/TransferManager/GeneralTransferManagerFactory.sol +++ /dev/null @@ -1,77 +0,0 @@ -pragma solidity ^0.4.24; - -import "../../proxy/GeneralTransferManagerProxy.sol"; -import "../ModuleFactory.sol"; - -/** - * @title Factory for deploying GeneralTransferManager module - */ -contract GeneralTransferManagerFactory is ModuleFactory { - - address public logicContract; - - /** - * @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 - * @param _logicContract Contract address that contains the logic related to `description` - */ - constructor (address _polyAddress, uint256 _setupCost, uint256 _usageCost, uint256 _subscriptionCost, address _logicContract) public - ModuleFactory(_polyAddress, _setupCost, _usageCost, _subscriptionCost) - { - require(_logicContract != address(0), "Invalid logic contract"); - version = "2.1.0"; - name = "GeneralTransferManager"; - title = "General Transfer Manager"; - description = "Manage transfers using a time based whitelist"; - compatibleSTVersionRange["lowerBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0)); - compatibleSTVersionRange["upperBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0)); - logicContract = _logicContract; - } - - - /** - * @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"); - address generalTransferManager = new GeneralTransferManagerProxy(msg.sender, address(polyToken), logicContract); - /*solium-disable-next-line security/no-block-members*/ - emit GenerateModuleFromFactory(address(generalTransferManager), getName(), address(this), msg.sender, setupCost, now); - return address(generalTransferManager); - } - - - /** - * @notice Type of the Module factory - */ - function getTypes() external view returns(uint8[]) { - uint8[] memory res = new uint8[](1); - res[0] = 2; - return res; - } - - /** - * @notice Returns the instructions associated with the module - */ - function getInstructions() external view returns(string) { - /*solium-disable-next-line max-len*/ - return "Allows an issuer to maintain a time based whitelist of authorised token holders.Addresses are added via modifyWhitelist and take a fromTime (the time from which they can send tokens) and a toTime (the time from which they can receive tokens). There are additional flags, allowAllWhitelistIssuances, allowAllWhitelistTransfers & allowAllTransfers which allow you to set corresponding contract level behaviour. Init function takes no parameters."; - } - - /** - * @notice Get the tags related to the module factory - */ - function getTags() public view returns(bytes32[]) { - bytes32[] memory availableTags = new bytes32[](2); - availableTags[0] = "General"; - availableTags[1] = "Transfer Restriction"; - return availableTags; - } - - -} diff --git a/contracts/modules/TransferManager/ITransferManager.sol b/contracts/modules/TransferManager/ITransferManager.sol deleted file mode 100644 index a8cfa4475..000000000 --- a/contracts/modules/TransferManager/ITransferManager.sol +++ /dev/null @@ -1,28 +0,0 @@ -pragma solidity ^0.4.24; - -import "../../Pausable.sol"; -import "../Module.sol"; - -/** - * @title Interface to be implemented by all Transfer Manager modules - * @dev abstract contract - */ -contract ITransferManager is Module, Pausable { - - //If verifyTransfer returns: - // FORCE_VALID, the transaction will always be valid, regardless of other TM results - // INVALID, then the transfer should not be allowed regardless of other TM results - // VALID, then the transfer is valid for this TM - // NA, then the result from this TM is ignored - enum Result {INVALID, NA, VALID, FORCE_VALID} - - function verifyTransfer(address _from, address _to, uint256 _amount, bytes _data, bool _isTransfer) public returns(Result); - - function unpause() public onlyOwner { - super._unpause(); - } - - function pause() public onlyOwner { - super._pause(); - } -} diff --git a/contracts/modules/Experimental/TransferManager/LockUpTransferManager.sol b/contracts/modules/TransferManager/LTM/LockUpTransferManager.sol similarity index 72% rename from contracts/modules/Experimental/TransferManager/LockUpTransferManager.sol rename to contracts/modules/TransferManager/LTM/LockUpTransferManager.sol index 93f271420..d8a0d7855 100644 --- a/contracts/modules/Experimental/TransferManager/LockUpTransferManager.sol +++ b/contracts/modules/TransferManager/LTM/LockUpTransferManager.sol @@ -1,36 +1,13 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; -import "../../TransferManager/ITransferManager.sol"; +import "../../TransferManager/TransferManager.sol"; +import "./LockUpTransferManagerStorage.sol"; import "openzeppelin-solidity/contracts/math/SafeMath.sol"; +import "openzeppelin-solidity/contracts/math/Math.sol"; -contract LockUpTransferManager is ITransferManager { - +contract LockUpTransferManager is LockUpTransferManagerStorage, TransferManager { using SafeMath for uint256; - // permission definition - bytes32 public constant ADMIN = "ADMIN"; - - // a per-user lockup - struct LockUp { - uint256 lockupAmount; // Amount to be locked - uint256 startTime; // when this lockup starts (seconds) - uint256 lockUpPeriodSeconds; // total period of lockup (seconds) - uint256 releaseFrequencySeconds; // how often to release a tranche of tokens (seconds) - } - - // mapping use to store the lockup details corresponds to lockup name - mapping (bytes32 => LockUp) public lockups; - // mapping user addresses to an array of lockups name for that user - mapping (address => bytes32[]) internal userToLockups; - // get list of the addresses for a particular lockupName - mapping (bytes32 => address[]) internal lockupToUsers; - // holds lockup index corresponds to user address. userAddress => lockupName => lockupIndex - mapping (address => mapping(bytes32 => uint256)) internal userToLockupIndex; - // holds the user address index corresponds to the lockup. lockupName => userAddress => userIndex - mapping (bytes32 => mapping(address => uint256)) internal lockupToUserIndex; - - bytes32[] lockupArray; - event AddLockUpToUser( address indexed _userAddress, bytes32 indexed _lockupName @@ -74,15 +51,49 @@ contract LockUpTransferManager is ITransferManager { * @param _from Address of the sender * @param _amount The amount of tokens to transfer */ - function verifyTransfer(address _from, address /* _to*/, uint256 _amount, bytes /* _data */, bool /*_isTransfer*/) public returns(Result) { + function executeTransfer(address _from, address /*_to*/, uint256 _amount, bytes calldata /*_data*/) external returns(Result) { + (Result success,) = _verifyTransfer(_from, _amount); + return success; + } + + /** @notice Used to verify the transfer transaction and prevent locked up tokens from being transferred + * @param _from Address of the sender + * @param _amount The amount of tokens to transfer + */ + function verifyTransfer( + address _from, + address /* _to*/, + uint256 _amount, + bytes memory /* _data */ + ) + public + view + returns(Result, bytes32) + { + return _verifyTransfer(_from, _amount); + } + + /** @notice Used to verify the transfer transaction and prevent locked up tokens from being transferred + * @param _from Address of the sender + * @param _amount The amount of tokens to transfer + */ + function _verifyTransfer( + address _from, + uint256 _amount + ) + internal + view + returns(Result, bytes32) + { // only attempt to verify the transfer if the token is unpaused, this isn't a mint txn, and there exists a lockup for this user - if (!paused && _from != address(0) && userToLockups[_from].length != 0) { + if (!paused && _from != address(0) && userToLockups[_from].length != 0) { // check if this transfer is valid return _checkIfValidTransfer(_from, _amount); } - return Result.NA; + return (Result.NA, bytes32(0)); } + /** * @notice Use to add the new lockup type * @param _lockupAmount Amount of tokens that need to lock. @@ -98,8 +109,8 @@ contract LockUpTransferManager is ITransferManager { uint256 _releaseFrequencySeconds, bytes32 _lockupName ) - public - withPerm(ADMIN) + external + withPerm(ADMIN) { _addNewLockUpType( _lockupAmount, @@ -119,13 +130,13 @@ contract LockUpTransferManager is ITransferManager { * @param _lockupNames Array of names of the lockup */ function addNewLockUpTypeMulti( - uint256[] _lockupAmounts, - uint256[] _startTimes, - uint256[] _lockUpPeriodsSeconds, - uint256[] _releaseFrequenciesSeconds, - bytes32[] _lockupNames - ) - external + uint256[] memory _lockupAmounts, + uint256[] memory _startTimes, + uint256[] memory _lockUpPeriodsSeconds, + uint256[] memory _releaseFrequenciesSeconds, + bytes32[] memory _lockupNames + ) + public withPerm(ADMIN) { require( @@ -133,7 +144,7 @@ contract LockUpTransferManager is ITransferManager { _lockupNames.length == _releaseFrequenciesSeconds.length && /*solium-disable-line operator-whitespace*/ _lockupNames.length == _startTimes.length && /*solium-disable-line operator-whitespace*/ _lockupNames.length == _lockupAmounts.length, - "Input array length mismatch" + "Length mismatch" ); for (uint256 i = 0; i < _lockupNames.length; i++) { _addNewLockUpType( @@ -144,7 +155,7 @@ contract LockUpTransferManager is ITransferManager { _lockupNames[i] ); } - } + } /** * @notice Add the lockup to a user @@ -155,25 +166,25 @@ contract LockUpTransferManager is ITransferManager { address _userAddress, bytes32 _lockupName ) - public + external withPerm(ADMIN) { _addLockUpByName(_userAddress, _lockupName); } /** - * @notice Add the lockup to a user - * @param _userAddresses Address of the user - * @param _lockupNames Name of the lockup + * @notice Add lockups to users + * @param _userAddresses Array of addresses of the users + * @param _lockupNames Array of names of the lockups */ function addLockUpByNameMulti( - address[] _userAddresses, - bytes32[] _lockupNames + address[] memory _userAddresses, + bytes32[] memory _lockupNames ) - external + public withPerm(ADMIN) { - require(_userAddresses.length == _lockupNames.length, "Length mismatch"); + _checkLengthOfArray(_userAddresses.length, _lockupNames.length); for (uint256 i = 0; i < _userAddresses.length; i++) { _addLockUpByName(_userAddresses[i], _lockupNames[i]); } @@ -195,18 +206,18 @@ contract LockUpTransferManager is ITransferManager { uint256 _lockUpPeriodSeconds, uint256 _releaseFrequencySeconds, bytes32 _lockupName - ) - external + ) + external withPerm(ADMIN) - { - _addNewLockUpToUser( + { + _addNewLockUpToUser( _userAddress, _lockupAmount, _startTime, _lockUpPeriodSeconds, _releaseFrequencySeconds, _lockupName - ); + ); } /** @@ -219,13 +230,13 @@ contract LockUpTransferManager is ITransferManager { * @param _lockupNames Array of names of the lockup */ function addNewLockUpToUserMulti( - address[] _userAddresses, - uint256[] _lockupAmounts, - uint256[] _startTimes, - uint256[] _lockUpPeriodsSeconds, - uint256[] _releaseFrequenciesSeconds, - bytes32[] _lockupNames - ) + address[] memory _userAddresses, + uint256[] memory _lockupAmounts, + uint256[] memory _startTimes, + uint256[] memory _lockUpPeriodsSeconds, + uint256[] memory _releaseFrequenciesSeconds, + bytes32[] memory _lockupNames + ) public withPerm(ADMIN) { @@ -235,7 +246,7 @@ contract LockUpTransferManager is ITransferManager { _userAddresses.length == _startTimes.length && /*solium-disable-line operator-whitespace*/ _userAddresses.length == _lockupAmounts.length && _userAddresses.length == _lockupNames.length, - "Input array length mismatch" + "Length mismatch" ); for (uint256 i = 0; i < _userAddresses.length; i++) { _addNewLockUpToUser(_userAddresses[i], _lockupAmounts[i], _startTimes[i], _lockUpPeriodsSeconds[i], _releaseFrequenciesSeconds[i], _lockupNames[i]); @@ -263,7 +274,7 @@ contract LockUpTransferManager is ITransferManager { * @notice Used to remove the multiple lockup type * @param _lockupNames Array of the lockup names. */ - function removeLockupTypeMulti(bytes32[] _lockupNames) external withPerm(ADMIN) { + function removeLockupTypeMulti(bytes32[] memory _lockupNames) public withPerm(ADMIN) { for (uint256 i = 0; i < _lockupNames.length; i++) { _removeLockupType(_lockupNames[i]); } @@ -272,10 +283,10 @@ contract LockUpTransferManager is ITransferManager { /** * @notice Use to remove the lockup for multiple users * @param _userAddresses Array of addresses of the user whose tokens are locked up - * @param _lockupNames Array of the names of the lockup that needs to be removed. + * @param _lockupNames Array of the names of the lockup that needs to be removed. */ - function removeLockUpFromUserMulti(address[] _userAddresses, bytes32[] _lockupNames) external withPerm(ADMIN) { - require(_userAddresses.length == _lockupNames.length, "Array length mismatch"); + function removeLockUpFromUserMulti(address[] memory _userAddresses, bytes32[] memory _lockupNames) public withPerm(ADMIN) { + _checkLengthOfArray(_userAddresses.length, _lockupNames.length); for (uint256 i = 0; i < _userAddresses.length; i++) { _removeLockUpFromUser(_userAddresses[i], _lockupNames[i]); } @@ -295,9 +306,9 @@ contract LockUpTransferManager is ITransferManager { uint256 _lockUpPeriodSeconds, uint256 _releaseFrequencySeconds, bytes32 _lockupName - ) - external - withPerm(ADMIN) + ) + external + withPerm(ADMIN) { _modifyLockUpType( _lockupAmount, @@ -314,16 +325,16 @@ contract LockUpTransferManager is ITransferManager { * @param _startTimes Array of the start time of the lockups (seconds) * @param _lockUpPeriodsSeconds Array of unix timestamp for the list of lockups (seconds). * @param _releaseFrequenciesSeconds How often to release a tranche of tokens (seconds) - * @param _lockupNames Array of the lockup names that needs to be modified + * @param _lockupNames Array of the lockup names that needs to be modified */ function modifyLockUpTypeMulti( - uint256[] _lockupAmounts, - uint256[] _startTimes, - uint256[] _lockUpPeriodsSeconds, - uint256[] _releaseFrequenciesSeconds, - bytes32[] _lockupNames - ) - public + uint256[] memory _lockupAmounts, + uint256[] memory _startTimes, + uint256[] memory _lockUpPeriodsSeconds, + uint256[] memory _releaseFrequenciesSeconds, + bytes32[] memory _lockupNames + ) + public withPerm(ADMIN) { require( @@ -331,7 +342,7 @@ contract LockUpTransferManager is ITransferManager { _lockupNames.length == _releaseFrequenciesSeconds.length && /*solium-disable-line operator-whitespace*/ _lockupNames.length == _startTimes.length && /*solium-disable-line operator-whitespace*/ _lockupNames.length == _lockupAmounts.length, - "Input array length mismatch" + "Length mismatch" ); for (uint256 i = 0; i < _lockupNames.length; i++) { _modifyLockUpType( @@ -367,13 +378,38 @@ contract LockUpTransferManager is ITransferManager { return (uint256(0), uint256(0), uint256(0), uint256(0), uint256(0)); } + /** + * @notice Return the data of the lockups + */ + function getAllLockupData() external view returns( + bytes32[] memory lockupNames, + uint256[] memory lockupAmounts, + uint256[] memory startTimes, + uint256[] memory lockUpPeriodSeconds, + uint256[] memory releaseFrequencySeconds, + uint256[] memory unlockedAmounts + ) + { + uint256 length = lockupArray.length; + lockupAmounts = new uint256[](length); + startTimes = new uint256[](length); + lockUpPeriodSeconds = new uint256[](length); + releaseFrequencySeconds = new uint256[](length); + unlockedAmounts = new uint256[](length); + lockupNames = new bytes32[](length); + for (uint256 i = 0; i < length; i++) { + (lockupAmounts[i], startTimes[i], lockUpPeriodSeconds[i], releaseFrequencySeconds[i], unlockedAmounts[i]) = getLockUp(lockupArray[i]); + lockupNames[i] = lockupArray[i]; + } + } + /** * @notice get the list of the users of a lockup type * @param _lockupName Name of the lockup type - * @return address List of users associated with the blacklist + * @return address List of users associated with the given lockup name */ - function getListOfAddresses(bytes32 _lockupName) external view returns(address[]) { - require(lockups[_lockupName].startTime != 0, "Blacklist type doesn't exist"); + function getListOfAddresses(bytes32 _lockupName) external view returns(address[] memory) { + _validLockUpCheck(_lockupName); return lockupToUsers[_lockupName]; } @@ -381,46 +417,16 @@ contract LockUpTransferManager is ITransferManager { * @notice get the list of lockups names * @return bytes32 Array of lockups names */ - function getAllLockups() external view returns(bytes32[]) { + function getAllLockups() external view returns(bytes32[] memory) { return lockupArray; } - /** - * @notice Return the data of the lockups - */ - function getAllLockupData() external view returns( - bytes32[] memory, - uint256[] memory, - uint256[] memory, - uint256[] memory, - uint256[] memory, - uint256[] memory - ) - { - uint256[] memory lockupAmounts = new uint256[](lockupArray.length); - uint256[] memory startTimes = new uint256[](lockupArray.length); - uint256[] memory lockUpPeriodSeconds = new uint256[](lockupArray.length); - uint256[] memory releaseFrequencySeconds = new uint256[](lockupArray.length); - uint256[] memory unlockedAmounts = new uint256[](lockupArray.length); - for (uint256 i = 0; i < lockupArray.length; i++) { - (lockupAmounts[i], startTimes[i], lockUpPeriodSeconds[i], releaseFrequencySeconds[i], unlockedAmounts[i]) = getLockUp(lockupArray[i]); - } - return ( - lockupArray, - lockupAmounts, - startTimes, - lockUpPeriodSeconds, - releaseFrequencySeconds, - unlockedAmounts - ); - } - /** * @notice get the list of the lockups for a given user * @param _user Address of the user * @return bytes32 List of lockups names associated with the given address */ - function getLockupsNamesToUser(address _user) external view returns(bytes32[]) { + function getLockupsNamesToUser(address _user) external view returns(bytes32[] memory) { return userToLockups[_user]; } @@ -430,10 +436,9 @@ contract LockUpTransferManager is ITransferManager { * @return uint256 Total locked tokens amount */ function getLockedTokenToUser(address _userAddress) public view returns(uint256) { - require(_userAddress != address(0), "Invalid address"); + _checkZeroAddress(_userAddress); bytes32[] memory userLockupNames = userToLockups[_userAddress]; uint256 totalRemainingLockedAmount = 0; - for (uint256 i = 0; i < userLockupNames.length; i++) { // Find out the remaining locked amount for a given lockup uint256 remainingLockedAmount = lockups[userLockupNames[i]].lockupAmount.sub(_getUnlockedAmountForLockup(userLockupNames[i])); @@ -448,14 +453,14 @@ contract LockUpTransferManager is ITransferManager { * @param _userAddress Address of the user whose lock ups should be checked * @param _amount Amount of tokens that need to transact */ - function _checkIfValidTransfer(address _userAddress, uint256 _amount) internal view returns (Result) { + function _checkIfValidTransfer(address _userAddress, uint256 _amount) internal view returns (Result, bytes32) { uint256 totalRemainingLockedAmount = getLockedTokenToUser(_userAddress); // Present balance of the user - uint256 currentBalance = ISecurityToken(securityToken).balanceOf(_userAddress); + uint256 currentBalance = securityToken.balanceOf(_userAddress); if ((currentBalance.sub(_amount)) >= totalRemainingLockedAmount) { - return Result.NA; + return (Result.NA, bytes32(0)); } - return Result.INVALID; + return (Result.INVALID, bytes32(uint256(address(this)) << 96)); } /** @@ -468,7 +473,7 @@ contract LockUpTransferManager is ITransferManager { } else if (lockups[_lockupName].startTime.add(lockups[_lockupName].lockUpPeriodSeconds) <= now) { return lockups[_lockupName].lockupAmount; } else { - // Calculate the no. of periods for a lockup + // Calculate the no. of periods for a lockup uint256 noOfPeriods = (lockups[_lockupName].lockUpPeriodSeconds).div(lockups[_lockupName].releaseFrequencySeconds); // Calculate the transaction time lies in which period /*solium-disable-next-line security/no-block-members*/ @@ -476,13 +481,13 @@ contract LockUpTransferManager is ITransferManager { // Find out the unlocked amount for a given lockup uint256 unLockedAmount = (lockups[_lockupName].lockupAmount.mul(elapsedPeriod)).div(noOfPeriods); return unLockedAmount; - } + } } function _removeLockupType(bytes32 _lockupName) internal { - require(lockups[_lockupName].startTime != 0, "Lockup type doesn’t exist"); - require(lockupToUsers[_lockupName].length == 0, "Users are associated with the lockup"); - // delete lockup type + _validLockUpCheck(_lockupName); + require(lockupToUsers[_lockupName].length == 0, "Users attached to lockup"); + // delete lockup type delete(lockups[_lockupName]); uint256 i = 0; for (i = 0; i < lockupArray.length; i++) { @@ -507,14 +512,11 @@ contract LockUpTransferManager is ITransferManager { internal { /*solium-disable-next-line security/no-block-members*/ - uint256 startTime = _startTime; - if (_startTime == 0) { - startTime = now; + _startTime = now; } - require(startTime >= now, "Invalid start time"); - require(lockups[_lockupName].lockupAmount != 0, "Doesn't exist"); - + _checkValidStartTime(_startTime); + _validLockUpCheck(_lockupName); _checkLockUpParams( _lockupAmount, _lockUpPeriodSeconds, @@ -523,14 +525,14 @@ contract LockUpTransferManager is ITransferManager { lockups[_lockupName] = LockUp( _lockupAmount, - startTime, + _startTime, _lockUpPeriodSeconds, _releaseFrequencySeconds ); - + emit ModifyLockUpType( _lockupAmount, - startTime, + _startTime, _lockUpPeriodSeconds, _releaseFrequencySeconds, _lockupName @@ -538,17 +540,17 @@ contract LockUpTransferManager is ITransferManager { } function _removeLockUpFromUser(address _userAddress, bytes32 _lockupName) internal { - require(_userAddress != address(0), "Invalid address"); - require(_lockupName != bytes32(0), "Invalid lockup name"); + _checkZeroAddress(_userAddress); + _checkValidName(_lockupName); require( userToLockups[_userAddress][userToLockupIndex[_userAddress][_lockupName]] == _lockupName, - "User not assosicated with given lockup" + "User not in lockup" ); // delete the user from the lockup type uint256 _lockupIndex = lockupToUserIndex[_lockupName][_userAddress]; uint256 _len = lockupToUsers[_lockupName].length; - if ( _lockupIndex != _len) { + if ( _lockupIndex != _len - 1) { lockupToUsers[_lockupName][_lockupIndex] = lockupToUsers[_lockupName][_len - 1]; lockupToUserIndex[_lockupName][lockupToUsers[_lockupName][_lockupIndex]] = _lockupIndex; } @@ -558,7 +560,7 @@ contract LockUpTransferManager is ITransferManager { // delete the lockup from the user uint256 _userIndex = userToLockupIndex[_userAddress][_lockupName]; _len = userToLockups[_userAddress].length; - if ( _userIndex != _len) { + if ( _userIndex != _len - 1) { userToLockups[_userAddress][_userIndex] = userToLockups[_userAddress][_len - 1]; userToLockupIndex[_userAddress][userToLockups[_userAddress][_userIndex]] = _userIndex; } @@ -575,10 +577,10 @@ contract LockUpTransferManager is ITransferManager { uint256 _lockUpPeriodSeconds, uint256 _releaseFrequencySeconds, bytes32 _lockupName - ) - internal - { - require(_userAddress != address(0), "Invalid address"); + ) + internal + { + _checkZeroAddress(_userAddress); _addNewLockUpType( _lockupAmount, _startTime, @@ -595,9 +597,14 @@ contract LockUpTransferManager is ITransferManager { ) internal { - require(_userAddress != address(0), "Invalid address"); - require(lockups[_lockupName].startTime >= now, "Lockup expired"); - + _checkZeroAddress(_userAddress); + _checkValidStartTime(lockups[_lockupName].startTime); + if(userToLockups[_userAddress].length > 0) { + require( + userToLockups[_userAddress][userToLockupIndex[_userAddress][_lockupName]] != _lockupName, + "User already in lockup" + ); + } userToLockupIndex[_userAddress][_lockupName] = userToLockups[_userAddress].length; lockupToUserIndex[_lockupName][_userAddress] = lockupToUsers[_lockupName].length; userToLockups[_userAddress].push(_lockupName); @@ -612,21 +619,20 @@ contract LockUpTransferManager is ITransferManager { uint256 _releaseFrequencySeconds, bytes32 _lockupName ) - internal + internal { - uint256 startTime = _startTime; - require(_lockupName != bytes32(0), "Invalid name"); - require(lockups[_lockupName].lockupAmount == 0, "Already exist"); /*solium-disable-next-line security/no-block-members*/ if (_startTime == 0) { - startTime = now; + _startTime = now; } - require(startTime >= now, "Invalid start time"); + _checkValidName(_lockupName); + require(lockups[_lockupName].lockupAmount == 0, "Already exist"); + _checkValidStartTime(_startTime); _checkLockUpParams(_lockupAmount, _lockUpPeriodSeconds, _releaseFrequencySeconds); - lockups[_lockupName] = LockUp(_lockupAmount, startTime, _lockUpPeriodSeconds, _releaseFrequencySeconds); + lockups[_lockupName] = LockUp(_lockupAmount, _startTime, _lockUpPeriodSeconds, _releaseFrequencySeconds); lockupArray.push(_lockupName); - emit AddNewLockUpType(_lockupName, _lockupAmount, startTime, _lockUpPeriodSeconds, _releaseFrequencySeconds); - } + emit AddNewLockUpType(_lockupName, _lockupAmount, _startTime, _lockUpPeriodSeconds, _releaseFrequencySeconds); + } /** * @notice Parameter checking function for creating or editing a lockup. @@ -639,13 +645,56 @@ contract LockUpTransferManager is ITransferManager { uint256 _lockupAmount, uint256 _lockUpPeriodSeconds, uint256 _releaseFrequencySeconds - ) - internal - pure - { - require(_lockUpPeriodSeconds != 0, "lockUpPeriodSeconds cannot be zero"); - require(_releaseFrequencySeconds != 0, "releaseFrequencySeconds cannot be zero"); - require(_lockupAmount != 0, "lockupAmount cannot be zero"); + ) + internal + pure + { + require( + _lockUpPeriodSeconds != 0 && + _releaseFrequencySeconds != 0 && + _lockupAmount != 0, + "Cannot be zero" + ); + } + + function _checkValidStartTime(uint256 _startTime) internal view { + require(_startTime >= now, "Invalid startTime or expired"); + } + + function _checkZeroAddress(address _userAddress) internal pure { + require(_userAddress != address(0), "Invalid address"); + } + + function _validLockUpCheck(bytes32 _lockupName) internal view { + require(lockups[_lockupName].startTime != 0, "Doesn't exist"); + } + + function _checkValidName(bytes32 _name) internal pure { + require(_name != bytes32(0), "Invalid name"); + } + + function _checkLengthOfArray(uint256 _length1, uint256 _length2) internal pure { + require(_length1 == _length2, "Length mismatch"); + } + + /** + * @notice return the amount of tokens for a given user as per the partition + * @param _partition Identifier + * @param _tokenHolder Whom token amount need to query + * @param _additionalBalance It is the `_value` that transfer during transfer/transferFrom function call + */ + function getTokensByPartition(bytes32 _partition, address _tokenHolder, uint256 _additionalBalance) external view returns(uint256){ + uint256 currentBalance = (msg.sender == address(securityToken)) ? (securityToken.balanceOf(_tokenHolder)).add(_additionalBalance) : securityToken.balanceOf(_tokenHolder); + uint256 lockedBalance = Math.min(getLockedTokenToUser(_tokenHolder), currentBalance); + if (paused) { + return (_partition == UNLOCKED ? currentBalance : uint256(0)); + } else { + if (_partition == LOCKED) + return lockedBalance; + else if (_partition == UNLOCKED) + return currentBalance.sub(lockedBalance); + } + return uint256(0); } /** @@ -658,7 +707,7 @@ contract LockUpTransferManager is ITransferManager { /** * @notice Returns the permissions flag that are associated with Percentage transfer Manager */ - function getPermissions() public view returns(bytes32[]) { + function getPermissions() public view returns(bytes32[] memory) { bytes32[] memory allPermissions = new bytes32[](1); allPermissions[0] = ADMIN; return allPermissions; diff --git a/contracts/modules/TransferManager/LTM/LockUpTransferManagerFactory.sol b/contracts/modules/TransferManager/LTM/LockUpTransferManagerFactory.sol new file mode 100644 index 000000000..913436884 --- /dev/null +++ b/contracts/modules/TransferManager/LTM/LockUpTransferManagerFactory.sol @@ -0,0 +1,52 @@ +pragma solidity 0.5.8; + +import "./LockUpTransferManagerProxy.sol"; +import "../../UpgradableModuleFactory.sol"; +import "./LockUpTransferManager.sol"; + +/** + * @title Factory for deploying LockUpTransferManager module + */ +contract LockUpTransferManagerFactory is UpgradableModuleFactory { + + /** + * @notice Constructor + * @param _setupCost Setup cost of the module + * @param _polymathRegistry Address of the Polymath registry + * @param _isCostInPoly true = cost in Poly, false = USD + */ + constructor( + uint256 _setupCost, + address _logicContract, + address _polymathRegistry, + bool _isCostInPoly + ) + public + UpgradableModuleFactory("3.0.0", _setupCost, _logicContract, _polymathRegistry, _isCostInPoly) + { + name = "LockUpTransferManager"; + title = "LockUp Transfer Manager"; + description = "Manage transfers using lock ups over time"; + typesData.push(2); + tagsData.push("LockUp"); + tagsData.push("Transfer Restriction"); + compatibleSTVersionRange["lowerBound"] = VersionUtils.pack(uint8(3), uint8(0), uint8(0)); + compatibleSTVersionRange["upperBound"] = VersionUtils.pack(uint8(3), uint8(0), uint8(0)); + } + + /** + * @notice Used to launch the Module with the help of factory + * @return address Contract address of the Module + */ + function deploy( + bytes calldata _data + ) + external + returns(address) + { + address lockUpTransferManager = address(new LockUpTransferManagerProxy(logicContracts[latestUpgrade].version, msg.sender, polymathRegistry.getAddress("PolyToken"), logicContracts[latestUpgrade].logicContract)); + _initializeModule(lockUpTransferManager, _data); + return lockUpTransferManager; + } + +} diff --git a/contracts/modules/TransferManager/LTM/LockUpTransferManagerProxy.sol b/contracts/modules/TransferManager/LTM/LockUpTransferManagerProxy.sol new file mode 100644 index 000000000..8d627cc73 --- /dev/null +++ b/contracts/modules/TransferManager/LTM/LockUpTransferManagerProxy.sol @@ -0,0 +1,35 @@ +pragma solidity 0.5.8; + +import "./LockUpTransferManagerStorage.sol"; +import "../../../proxy/OwnedUpgradeabilityProxy.sol"; +import "../../../Pausable.sol"; +import "../../../storage/modules/ModuleStorage.sol"; + +/** + * @title CountTransferManager module Proxy + */ +contract LockUpTransferManagerProxy is LockUpTransferManagerStorage, ModuleStorage, Pausable, OwnedUpgradeabilityProxy { + + /** + * @notice Constructor + * @param _securityToken Address of the security token + * @param _polyAddress Address of the polytoken + * @param _implementation representing the address of the new implementation to be set + */ + constructor ( + string memory _version, + address _securityToken, + address _polyAddress, + address _implementation + ) + public + ModuleStorage(_securityToken, _polyAddress) + { + require( + _implementation != address(0), + "Implementation address should not be 0x" + ); + _upgradeTo(_version, _implementation); + } + +} diff --git a/contracts/modules/TransferManager/LTM/LockUpTransferManagerStorage.sol b/contracts/modules/TransferManager/LTM/LockUpTransferManagerStorage.sol new file mode 100644 index 000000000..aaaf855da --- /dev/null +++ b/contracts/modules/TransferManager/LTM/LockUpTransferManagerStorage.sol @@ -0,0 +1,29 @@ +pragma solidity 0.5.8; + +/** + * @title Wallet for core vesting escrow functionality + */ +contract LockUpTransferManagerStorage { + + // a per-user lockup + struct LockUp { + uint256 lockupAmount; // Amount to be locked + uint256 startTime; // when this lockup starts (seconds) + uint256 lockUpPeriodSeconds; // total period of lockup (seconds) + uint256 releaseFrequencySeconds; // how often to release a tranche of tokens (seconds) + } + + // mapping use to store the lockup details corresponds to lockup name + mapping (bytes32 => LockUp) public lockups; + // mapping user addresses to an array of lockups name for that user + mapping (address => bytes32[]) internal userToLockups; + // get list of the addresses for a particular lockupName + mapping (bytes32 => address[]) internal lockupToUsers; + // holds lockup index corresponds to user address. userAddress => lockupName => lockupIndex + mapping (address => mapping(bytes32 => uint256)) internal userToLockupIndex; + // holds the user address index corresponds to the lockup. lockupName => userAddress => userIndex + mapping (bytes32 => mapping(address => uint256)) internal lockupToUserIndex; + + bytes32[] lockupArray; + +} diff --git a/contracts/modules/TransferManager/ManualApprovalTransferManager.sol b/contracts/modules/TransferManager/MATM/ManualApprovalTransferManager.sol similarity index 74% rename from contracts/modules/TransferManager/ManualApprovalTransferManager.sol rename to contracts/modules/TransferManager/MATM/ManualApprovalTransferManager.sol index 5e95f6513..c63e03a3c 100644 --- a/contracts/modules/TransferManager/ManualApprovalTransferManager.sol +++ b/contracts/modules/TransferManager/MATM/ManualApprovalTransferManager.sol @@ -1,32 +1,15 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; -import "./ITransferManager.sol"; +import "../TransferManager.sol"; import "openzeppelin-solidity/contracts/math/SafeMath.sol"; +import "./ManualApprovalTransferManagerStorage.sol"; /** * @title Transfer Manager module for manually approving transactions between accounts */ -contract ManualApprovalTransferManager is ITransferManager { +contract ManualApprovalTransferManager is ManualApprovalTransferManagerStorage, TransferManager { using SafeMath for uint256; - bytes32 public constant TRANSFER_APPROVAL = "TRANSFER_APPROVAL"; - - //Manual approval is an allowance (that has been approved) with an expiry time - struct ManualApproval { - address from; - address to; - uint256 allowance; - uint256 expiryTime; - bytes32 description; - } - - mapping (address => mapping (address => uint256)) public approvalIndex; - - // An array to track all approvals. It is an unbounded array but it's not a problem as - // it is never looped through in an onchain call. It is defined as an Array instead of mapping - // just to make it easier for users to fetch list of all approvals through constant functions. - ManualApproval[] public approvals; - event AddManualApproval( address indexed _from, address indexed _to, @@ -54,18 +37,15 @@ contract ManualApprovalTransferManager is ITransferManager { /** * @notice Constructor * @param _securityToken Address of the security token - * @param _polyAddress Address of the polytoken */ - constructor (address _securityToken, address _polyAddress) - public - Module(_securityToken, _polyAddress) - { + constructor(address _securityToken, address _polyToken) public Module(_securityToken, _polyToken) { + } /** * @notice This function returns the signature of configure function */ - function getInitFunction() public pure returns (bytes4) { + function getInitFunction() public pure returns(bytes4) { return bytes4(0); } @@ -74,26 +54,68 @@ contract ManualApprovalTransferManager is ITransferManager { * @param _from Address of the sender * @param _to Address of the receiver * @param _amount The amount of tokens to transfer - * @param _isTransfer Whether or not this is an actual transfer or just a test to see if the tokens would be transferrable */ - function verifyTransfer(address _from, address _to, uint256 _amount, bytes /* _data */, bool _isTransfer) public returns(Result) { - // function must only be called by the associated security token if _isTransfer == true - require(_isTransfer == false || msg.sender == securityToken, "Sender is not the owner"); + function executeTransfer( + address _from, + address _to, + uint256 _amount, + bytes calldata /* _data */ + ) + external + onlySecurityToken + returns(Result) + { + + (Result success, bytes32 esc) = _verifyTransfer(_from, _to, _amount); + if (esc != bytes32(0)) { + uint256 index = approvalIndex[_from][_to] - 1; + ManualApproval storage approval = approvals[index]; + approval.allowance = approval.allowance.sub(_amount); + } + return (success); + } + + + /** + * @notice Used to verify the transfer transaction and allow a manually approved transqaction to bypass other restrictions + * @param _from Address of the sender + * @param _to Address of the receiver + * @param _amount The amount of tokens to transfer + */ + function verifyTransfer( + address _from, + address _to, + uint256 _amount, + bytes memory /* _data */ + ) + public + view + returns(Result, bytes32) + { + return _verifyTransfer(_from, _to, _amount); + } + + function _verifyTransfer( + address _from, + address _to, + uint256 _amount + ) + internal + view + returns(Result, bytes32) + { uint256 index = approvalIndex[_from][_to]; if (!paused && index != 0) { - index--; //Actual index is stored index - 1. - ManualApproval storage approval = approvals[index]; - uint256 allowance = approval.allowance; - if ((approval.expiryTime >= now) && (allowance >= _amount)) { - if (_isTransfer) { - approval.allowance = allowance - _amount; - } - return Result.VALID; + index--; //Actual index is storedIndex - 1 + ManualApproval memory approval = approvals[index]; + if ((approval.expiryTime >= now) && (approval.allowance >= _amount)) { + return (Result.VALID, bytes32(uint256(address(this)) << 96)); } } - return Result.NA; + return (Result.NA, bytes32(0)); } + /** * @notice Adds a pair of addresses to manual approvals * @param _from is the address from which transfers are approved @@ -110,18 +132,16 @@ contract ManualApprovalTransferManager is ITransferManager { bytes32 _description ) external - withPerm(TRANSFER_APPROVAL) + withPerm(ADMIN) { _addManualApproval(_from, _to, _allowance, _expiryTime, _description); } function _addManualApproval(address _from, address _to, uint256 _allowance, uint256 _expiryTime, bytes32 _description) internal { - require(_to != address(0), "Invalid to address"); require(_expiryTime > now, "Invalid expiry time"); require(_allowance > 0, "Invalid allowance"); - uint256 index = approvalIndex[_from][_to]; - if (index != 0) { - index--; //Actual index is stored index - 1. + if (approvalIndex[_from][_to] != 0) { + uint256 index = approvalIndex[_from][_to] - 1; require(approvals[index].expiryTime < now || approvals[index].allowance == 0, "Approval already exists"); _revokeManualApproval(_from, _to); } @@ -139,14 +159,14 @@ contract ManualApprovalTransferManager is ITransferManager { * @param _descriptions is the description array for these manual approvals */ function addManualApprovalMulti( - address[] _from, - address[] _to, - uint256[] _allowances, - uint256[] _expiryTimes, - bytes32[] _descriptions + address[] memory _from, + address[] memory _to, + uint256[] memory _allowances, + uint256[] memory _expiryTimes, + bytes32[] memory _descriptions ) - external - withPerm(TRANSFER_APPROVAL) + public + withPerm(ADMIN) { _checkInputLengthArray(_from, _to, _allowances, _expiryTimes, _descriptions); for (uint256 i = 0; i < _from.length; i++){ @@ -173,7 +193,7 @@ contract ManualApprovalTransferManager is ITransferManager { bool _increase ) external - withPerm(TRANSFER_APPROVAL) + withPerm(ADMIN) { _modifyManualApproval(_from, _to, _expiryTime, _changeInAllowance, _description, _increase); } @@ -188,7 +208,6 @@ contract ManualApprovalTransferManager is ITransferManager { ) internal { - require(_to != address(0), "Invalid to address"); /*solium-disable-next-line security/no-block-members*/ require(_expiryTime > now, "Invalid expiry time"); uint256 index = approvalIndex[_from][_to]; @@ -213,7 +232,6 @@ contract ManualApprovalTransferManager is ITransferManager { } approval.allowance = allowance; } - // Greedy storage technique if (expiryTime != _expiryTime) { approval.expiryTime = _expiryTime; @@ -221,7 +239,6 @@ contract ManualApprovalTransferManager is ITransferManager { if (approval.description != _description) { approval.description = _description; } - emit ModifyManualApproval(_from, _to, _expiryTime, allowance, _description, msg.sender); } @@ -230,26 +247,26 @@ contract ManualApprovalTransferManager is ITransferManager { * @param _from is the address array from which transfers are approved * @param _to is the address array to which transfers are approved * @param _expiryTimes is the array of the times until which eath transfer is allowed - * @param _changedAllowances is the array of approved amounts + * @param _changeInAllowance is the array of change in allowances * @param _descriptions is the description array for these manual approvals - * @param _increase Array of bool values which tells whether the allowances will be increased (true) or decreased (false) + * @param _increase Array of bools that tells whether the allowances will be increased (true) or decreased (false). * or any value when there is no change in allowances */ function modifyManualApprovalMulti( - address[] _from, - address[] _to, - uint256[] _expiryTimes, - uint256[] _changedAllowances, - bytes32[] _descriptions, - bool[] _increase + address[] memory _from, + address[] memory _to, + uint256[] memory _expiryTimes, + uint256[] memory _changeInAllowance, + bytes32[] memory _descriptions, + bool[] memory _increase ) public - withPerm(TRANSFER_APPROVAL) + withPerm(ADMIN) { - _checkInputLengthArray(_from, _to, _changedAllowances, _expiryTimes, _descriptions); - require(_increase.length == _changedAllowances.length, "Input length array mismatch"); + _checkInputLengthArray(_from, _to, _changeInAllowance, _expiryTimes, _descriptions); + require(_increase.length == _changeInAllowance.length, "Input length array mismatch"); for (uint256 i = 0; i < _from.length; i++) { - _modifyManualApproval(_from[i], _to[i], _expiryTimes[i], _changedAllowances[i], _descriptions[i], _increase[i]); + _modifyManualApproval(_from[i], _to[i], _expiryTimes[i], _changeInAllowance[i], _descriptions[i], _increase[i]); } } @@ -258,16 +275,17 @@ contract ManualApprovalTransferManager is ITransferManager { * @param _from is the address from which transfers are approved * @param _to is the address to which transfers are approved */ - function revokeManualApproval(address _from, address _to) external withPerm(TRANSFER_APPROVAL) { + function revokeManualApproval(address _from, address _to) external withPerm(ADMIN) { _revokeManualApproval(_from, _to); } function _revokeManualApproval(address _from, address _to) internal { uint256 index = approvalIndex[_from][_to]; - require(index != 0, "Approval does not exist"); - index--; //Actual index is stored index - 1. - uint256 lastApprovalIndex = approvals.length - 1; + require(index != 0, "Approval not exist"); + // find the record in active approvals array & delete it + index--; //Index is stored after incrementation so that 0 represents non existant index + uint256 lastApprovalIndex = approvals.length - 1; if (index != lastApprovalIndex) { approvals[index] = approvals[lastApprovalIndex]; approvalIndex[approvals[index].from][approvals[index].to] = index + 1; @@ -282,7 +300,7 @@ contract ManualApprovalTransferManager is ITransferManager { * @param _from is the address array from which transfers are approved * @param _to is the address array to which transfers are approved */ - function revokeManualApprovalMulti(address[] _from, address[] _to) external withPerm(TRANSFER_APPROVAL) { + function revokeManualApprovalMulti(address[] calldata _from, address[] calldata _to) external withPerm(ADMIN) { require(_from.length == _to.length, "Input array length mismatch"); for(uint256 i = 0; i < _from.length; i++){ _revokeManualApproval(_from[i], _to[i]); @@ -290,11 +308,11 @@ contract ManualApprovalTransferManager is ITransferManager { } function _checkInputLengthArray( - address[] _from, - address[] _to, - uint256[] _expiryTimes, - uint256[] _allowances, - bytes32[] _descriptions + address[] memory _from, + address[] memory _to, + uint256[] memory _expiryTimes, + uint256[] memory _allowances, + bytes32[] memory _descriptions ) internal pure @@ -317,7 +335,7 @@ contract ManualApprovalTransferManager is ITransferManager { * @return uint256[] expiry times provided to the approvals * @return bytes32[] descriptions provided to the approvals */ - function getActiveApprovalsToUser(address _user) external view returns(address[], address[], uint256[], uint256[], bytes32[]) { + function getActiveApprovalsToUser(address _user) external view returns(address[] memory, address[] memory, uint256[] memory, uint256[] memory, bytes32[] memory) { uint256 counter = 0; uint256 approvalsLength = approvals.length; for (uint256 i = 0; i < approvalsLength; i++) { @@ -333,7 +351,7 @@ contract ManualApprovalTransferManager is ITransferManager { bytes32[] memory description = new bytes32[](counter); counter = 0; - for (i = 0; i < approvalsLength; i++) { + for (uint256 i = 0; i < approvalsLength; i++) { if ((approvals[i].from == _user || approvals[i].to == _user) && approvals[i].expiryTime >= now) { @@ -369,6 +387,7 @@ contract ManualApprovalTransferManager is ITransferManager { ); } } + return (uint256(0), uint256(0), bytes32(0)); } /** @@ -386,13 +405,13 @@ contract ManualApprovalTransferManager is ITransferManager { * @return uint256[] expiry times provided to the approvals * @return bytes32[] descriptions provided to the approvals */ - function getAllApprovals() external view returns(address[], address[], uint256[], uint256[], bytes32[]) { - address[] memory from = new address[](approvals.length); - address[] memory to = new address[](approvals.length); - uint256[] memory allowance = new uint256[](approvals.length); - uint256[] memory expiryTime = new uint256[](approvals.length); - bytes32[] memory description = new bytes32[](approvals.length); + function getAllApprovals() external view returns(address[] memory, address[] memory, uint256[] memory, uint256[] memory, bytes32[] memory) { uint256 approvalsLength = approvals.length; + address[] memory from = new address[](approvalsLength); + address[] memory to = new address[](approvalsLength); + uint256[] memory allowance = new uint256[](approvalsLength); + uint256[] memory expiryTime = new uint256[](approvalsLength); + bytes32[] memory description = new bytes32[](approvalsLength); for (uint256 i = 0; i < approvalsLength; i++) { @@ -411,9 +430,9 @@ contract ManualApprovalTransferManager is ITransferManager { /** * @notice Returns the permissions flag that are associated with ManualApproval transfer manager */ - function getPermissions() public view returns(bytes32[]) { + function getPermissions() public view returns(bytes32[] memory) { bytes32[] memory allPermissions = new bytes32[](1); - allPermissions[0] = TRANSFER_APPROVAL; + allPermissions[0] = ADMIN; return allPermissions; } } diff --git a/contracts/modules/TransferManager/MATM/ManualApprovalTransferManagerFactory.sol b/contracts/modules/TransferManager/MATM/ManualApprovalTransferManagerFactory.sol new file mode 100644 index 000000000..2d9cdf8ec --- /dev/null +++ b/contracts/modules/TransferManager/MATM/ManualApprovalTransferManagerFactory.sol @@ -0,0 +1,52 @@ +pragma solidity 0.5.8; + +import "../../UpgradableModuleFactory.sol"; +import "./ManualApprovalTransferManagerProxy.sol"; + +/** + * @title Factory for deploying ManualApprovalTransferManager module + */ +contract ManualApprovalTransferManagerFactory is UpgradableModuleFactory { + + /** + * @notice Constructor + * @param _setupCost Setup cost of the module + * @param _logicContract Contract address that contains the logic related to `description` + * @param _polymathRegistry Address of the Polymath registry + * @param _isCostInPoly true = cost in Poly, false = USD + */ + constructor ( + uint256 _setupCost, + address _logicContract, + address _polymathRegistry, + bool _isCostInPoly + ) + public + UpgradableModuleFactory("3.0.0", _setupCost, _logicContract, _polymathRegistry, _isCostInPoly) + { + name = "ManualApprovalTransferManager"; + title = "Manual Approval Transfer Manager"; + description = "Manage transfers using single approvals"; + typesData.push(2); + tagsData.push("Manual Approval"); + tagsData.push("Transfer Restriction"); + compatibleSTVersionRange["lowerBound"] = VersionUtils.pack(uint8(3), uint8(0), uint8(0)); + compatibleSTVersionRange["upperBound"] = VersionUtils.pack(uint8(3), uint8(0), uint8(0)); + } + + /** + * @notice used to launch the Module with the help of factory + * @return address Contract address of the Module + */ + function deploy( + bytes calldata _data + ) + external + returns(address) + { + address manualTransferManager = address(new ManualApprovalTransferManagerProxy(logicContracts[latestUpgrade].version, msg.sender, polymathRegistry.getAddress("PolyToken"), logicContracts[latestUpgrade].logicContract)); + _initializeModule(manualTransferManager, _data); + return manualTransferManager; + } + +} diff --git a/contracts/modules/TransferManager/MATM/ManualApprovalTransferManagerProxy.sol b/contracts/modules/TransferManager/MATM/ManualApprovalTransferManagerProxy.sol new file mode 100644 index 000000000..c2997b254 --- /dev/null +++ b/contracts/modules/TransferManager/MATM/ManualApprovalTransferManagerProxy.sol @@ -0,0 +1,35 @@ +pragma solidity 0.5.8; + +import "../../../proxy/OwnedUpgradeabilityProxy.sol"; +import "./ManualApprovalTransferManagerStorage.sol"; +import "../../../Pausable.sol"; +import "../../../storage/modules/ModuleStorage.sol"; + +/** + @title ManualApprovalTransferManager module Proxy + */ +contract ManualApprovalTransferManagerProxy is ManualApprovalTransferManagerStorage, ModuleStorage, Pausable, OwnedUpgradeabilityProxy { + + /** + * @notice Constructor + * @param _securityToken Address of the security token + * @param _polyAddress Address of the polytoken + * @param _implementation representing the address of the new implementation to be set + */ + constructor ( + string memory _version, + address _securityToken, + address _polyAddress, + address _implementation + ) + public + ModuleStorage(_securityToken, _polyAddress) + { + require( + _implementation != address(0), + "Implementation address should not be 0x" + ); + _upgradeTo(_version, _implementation); + } + +} diff --git a/contracts/modules/TransferManager/MATM/ManualApprovalTransferManagerStorage.sol b/contracts/modules/TransferManager/MATM/ManualApprovalTransferManagerStorage.sol new file mode 100644 index 000000000..b97293968 --- /dev/null +++ b/contracts/modules/TransferManager/MATM/ManualApprovalTransferManagerStorage.sol @@ -0,0 +1,24 @@ +pragma solidity 0.5.8; + +/** + * @title Contract used to store layout for the ManualApprovalTransferManager storage + */ +contract ManualApprovalTransferManagerStorage { + + //Manual approval is an allowance (that has been approved) with an expiry time + struct ManualApproval { + address from; + address to; + uint256 allowance; + uint256 expiryTime; + bytes32 description; + } + + mapping (address => mapping (address => uint256)) public approvalIndex; + + // An array to track all approvals. It is an unbounded array but it's not a problem as + // it is never looped through in an onchain call. It is defined as an Array instead of mapping + // just to make it easier for users to fetch list of all approvals through constant functions. + ManualApproval[] public approvals; + +} diff --git a/contracts/modules/TransferManager/ManualApprovalTransferManagerFactory.sol b/contracts/modules/TransferManager/ManualApprovalTransferManagerFactory.sol deleted file mode 100644 index c7c962f4b..000000000 --- a/contracts/modules/TransferManager/ManualApprovalTransferManagerFactory.sol +++ /dev/null @@ -1,70 +0,0 @@ -pragma solidity ^0.4.24; - -import "./ManualApprovalTransferManager.sol"; -import "../ModuleFactory.sol"; - -/** - * @title Factory for deploying ManualApprovalTransferManager module - */ -contract ManualApprovalTransferManagerFactory is ModuleFactory { - - /** - * @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 - ModuleFactory(_polyAddress, _setupCost, _usageCost, _subscriptionCost) - { - version = "2.1.0"; - name = "ManualApprovalTransferManager"; - title = "Manual Approval Transfer Manager"; - description = "Manage transfers using single approvals"; - compatibleSTVersionRange["lowerBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0)); - compatibleSTVersionRange["upperBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0)); - } - - /** - * @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"); - address manualTransferManager = new ManualApprovalTransferManager(msg.sender, address(polyToken)); - /*solium-disable-next-line security/no-block-members*/ - emit GenerateModuleFromFactory(address(manualTransferManager), getName(), address(this), msg.sender, setupCost, now); - return address(manualTransferManager); - } - - /** - * @notice Type of the Module factory - */ - function getTypes() external view returns(uint8[]) { - uint8[] memory res = new uint8[](1); - res[0] = 2; - return res; - } - - /** - * @notice Returns the instructions associated with the module - */ - function getInstructions() external view returns(string) { - /*solium-disable-next-line max-len*/ - return "Allows an issuer to set manual approvals for specific pairs of addresses and amounts. Init function takes no parameters."; - } - - /** - * @notice Get the tags related to the module factory - */ - function getTags() external view returns(bytes32[]) { - bytes32[] memory availableTags = new bytes32[](2); - availableTags[0] = "ManualApproval"; - availableTags[1] = "Transfer Restriction"; - return availableTags; - } - - -} diff --git a/contracts/modules/TransferManager/PercentageTransferManager.sol b/contracts/modules/TransferManager/PTM/PercentageTransferManager.sol similarity index 61% rename from contracts/modules/TransferManager/PercentageTransferManager.sol rename to contracts/modules/TransferManager/PTM/PercentageTransferManager.sol index 162b090de..1a28969e1 100644 --- a/contracts/modules/TransferManager/PercentageTransferManager.sol +++ b/contracts/modules/TransferManager/PTM/PercentageTransferManager.sol @@ -1,52 +1,35 @@ /** - * DISCLAIMER: Under certain conditions, the limit could be bypassed if a large token holder - * redeems a huge portion of their tokens. It will cause the total supply to drop - * which can result in some other token holders having a percentage of tokens + * DISCLAIMER: Under certain conditions, the limit could be bypassed if a large token holder + * redeems a huge portion of their tokens. It will cause the total supply to drop + * which can result in some other token holders having a percentage of tokens * higher than the intended limit. */ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; -import "./ITransferManager.sol"; +import "../TransferManager.sol"; import "openzeppelin-solidity/contracts/math/SafeMath.sol"; +import "./PercentageTransferManagerStorage.sol"; /** * @title Transfer Manager module for limiting percentage of token supply a single address can hold */ -contract PercentageTransferManager is ITransferManager { +contract PercentageTransferManager is PercentageTransferManagerStorage, TransferManager { using SafeMath for uint256; - // Permission key for modifying the whitelist - bytes32 public constant WHITELIST = "WHITELIST"; - bytes32 public constant ADMIN = "ADMIN"; - - // Maximum percentage that any holder can have, multiplied by 10**16 - e.g. 20% is 20 * 10**16 - uint256 public maxHolderPercentage; - - // Ignore transactions which are part of the primary issuance - bool public allowPrimaryIssuance = true; - - // Addresses on this list are always able to send / receive tokens - mapping (address => bool) public whitelist; - event ModifyHolderPercentage(uint256 _oldHolderPercentage, uint256 _newHolderPercentage); - event ModifyWhitelist( - address _investor, - uint256 _dateAdded, - address _addedBy, - bool _valid - ); - event SetAllowPrimaryIssuance(bool _allowPrimaryIssuance, uint256 _timestamp); + event ModifyWhitelist(address _investor, address _addedBy, bool _valid); + event SetAllowPrimaryIssuance(bool _allowPrimaryIssuance); /** * @notice Constructor * @param _securityToken Address of the security token - * @param _polyAddress Address of the polytoken */ - constructor (address _securityToken, address _polyAddress) + constructor(address _securityToken, address _polyToken) public - Module(_securityToken, _polyAddress) + Module(_securityToken, _polyToken) { + } /** @notice Used to verify the transfer transaction and prevent a given account to end up with more tokens than allowed @@ -54,22 +37,62 @@ contract PercentageTransferManager is ITransferManager { * @param _to Address of the receiver * @param _amount The amount of tokens to transfer */ - function verifyTransfer(address _from, address _to, uint256 _amount, bytes /* _data */, bool /* _isTransfer */) public returns(Result) { + function executeTransfer( + address _from, + address _to, + uint256 _amount, + bytes calldata /* _data */ + ) + external + returns(Result) + { + (Result success,) = _verifyTransfer(_from, _to, _amount); + return success; + } + + /** + * @notice Used to verify the transfer transaction and prevent a given account to end up with more tokens than allowed + * @param _from Address of the sender + * @param _to Address of the receiver + * @param _amount The amount of tokens to transfer + */ + function verifyTransfer( + address _from, + address _to, + uint256 _amount, + bytes memory /*_data*/ + ) + public + view + returns(Result, bytes32) + { + return _verifyTransfer(_from, _to, _amount); + } + + function _verifyTransfer( + address _from, + address _to, + uint256 _amount + ) + internal + view + returns(Result, bytes32) + { if (!paused) { if (_from == address(0) && allowPrimaryIssuance) { - return Result.NA; + return (Result.NA, bytes32(0)); } // If an address is on the whitelist, it is allowed to hold more than maxHolderPercentage of the tokens. if (whitelist[_to]) { - return Result.NA; + return (Result.NA, bytes32(0)); } - uint256 newBalance = ISecurityToken(securityToken).balanceOf(_to).add(_amount); - if (newBalance.mul(uint256(10)**18).div(ISecurityToken(securityToken).totalSupply()) > maxHolderPercentage) { - return Result.INVALID; + uint256 newBalance = securityToken.balanceOf(_to).add(_amount); + if (newBalance.mul(uint256(10) ** 18).div(securityToken.totalSupply()) > maxHolderPercentage) { + return (Result.INVALID, bytes32(uint256(address(this)) << 96)); } - return Result.NA; + return (Result.NA, bytes32(0)); } - return Result.NA; + return (Result.NA, bytes32(0)); } /** @@ -84,8 +107,8 @@ contract PercentageTransferManager is ITransferManager { /** * @notice This function returns the signature of configure function */ - function getInitFunction() public pure returns (bytes4) { - return bytes4(keccak256("configure(uint256,bool)")); + function getInitFunction() public pure returns(bytes4) { + return this.configure.selector; } /** @@ -102,10 +125,10 @@ contract PercentageTransferManager is ITransferManager { * @param _investor is the address to whitelist * @param _valid whether or not the address it to be added or removed from the whitelist */ - function modifyWhitelist(address _investor, bool _valid) public withPerm(WHITELIST) { + function modifyWhitelist(address _investor, bool _valid) public withPerm(ADMIN) { whitelist[_investor] = _valid; /*solium-disable-next-line security/no-block-members*/ - emit ModifyWhitelist(_investor, now, msg.sender, _valid); + emit ModifyWhitelist(_investor, msg.sender, _valid); } /** @@ -113,7 +136,7 @@ contract PercentageTransferManager is ITransferManager { * @param _investors Array of the addresses to whitelist * @param _valids Array of boolean value to decide whether or not the address it to be added or removed from the whitelist */ - function modifyWhitelistMulti(address[] _investors, bool[] _valids) public withPerm(WHITELIST) { + function modifyWhitelistMulti(address[] memory _investors, bool[] memory _valids) public withPerm(ADMIN) { require(_investors.length == _valids.length, "Input array length mis-match"); for (uint i = 0; i < _investors.length; i++) { modifyWhitelist(_investors[i], _valids[i]); @@ -128,15 +151,14 @@ contract PercentageTransferManager is ITransferManager { require(_allowPrimaryIssuance != allowPrimaryIssuance, "Must change setting"); allowPrimaryIssuance = _allowPrimaryIssuance; /*solium-disable-next-line security/no-block-members*/ - emit SetAllowPrimaryIssuance(_allowPrimaryIssuance, now); + emit SetAllowPrimaryIssuance(_allowPrimaryIssuance); } /** * @notice Return the permissions flag that are associated with Percentage transfer Manager */ - function getPermissions() public view returns(bytes32[]) { + function getPermissions() public view returns(bytes32[] memory) { bytes32[] memory allPermissions = new bytes32[](2); - allPermissions[0] = WHITELIST; allPermissions[1] = ADMIN; return allPermissions; } diff --git a/contracts/modules/TransferManager/PTM/PercentageTransferManagerFactory.sol b/contracts/modules/TransferManager/PTM/PercentageTransferManagerFactory.sol new file mode 100644 index 000000000..ba2c0684a --- /dev/null +++ b/contracts/modules/TransferManager/PTM/PercentageTransferManagerFactory.sol @@ -0,0 +1,48 @@ +pragma solidity 0.5.8; + +import "../../UpgradableModuleFactory.sol"; +import "./PercentageTransferManagerProxy.sol"; + +/** + * @title Factory for deploying PercentageTransferManager module + */ +contract PercentageTransferManagerFactory is UpgradableModuleFactory { + + /** + * @notice Constructor + * @param _setupCost Setup cost of the module + * @param _logicContract Contract address that contains the logic related to `description` + * @param _polymathRegistry Address of the Polymath registry + * @param _isCostInPoly true = cost in Poly, false = USD + */ + constructor ( + uint256 _setupCost, + address _logicContract, + address _polymathRegistry, + bool _isCostInPoly + ) + public + UpgradableModuleFactory("3.0.0", _setupCost, _logicContract, _polymathRegistry, _isCostInPoly) + { + name = "PercentageTransferManager"; + title = "Percentage Transfer Manager"; + description = "Restrict the number of investors"; + typesData.push(2); + tagsData.push("Percentage"); + tagsData.push("Transfer Restriction"); + compatibleSTVersionRange["lowerBound"] = VersionUtils.pack(uint8(3), uint8(0), uint8(0)); + compatibleSTVersionRange["upperBound"] = VersionUtils.pack(uint8(3), uint8(0), uint8(0)); + } + + /** + * @notice used to launch the Module with the help of factory + * @param _data Data used for the intialization of the module factory variables + * @return address Contract address of the Module + */ + function deploy(bytes calldata _data) external returns(address) { + address percentageTransferManager = address(new PercentageTransferManagerProxy(logicContracts[latestUpgrade].version, msg.sender, polymathRegistry.getAddress("PolyToken"), logicContracts[latestUpgrade].logicContract)); + _initializeModule(percentageTransferManager, _data); + return percentageTransferManager; + } + +} diff --git a/contracts/modules/TransferManager/PTM/PercentageTransferManagerProxy.sol b/contracts/modules/TransferManager/PTM/PercentageTransferManagerProxy.sol new file mode 100644 index 000000000..840da18b2 --- /dev/null +++ b/contracts/modules/TransferManager/PTM/PercentageTransferManagerProxy.sol @@ -0,0 +1,30 @@ +pragma solidity 0.5.8; + +import "../../../proxy/OwnedUpgradeabilityProxy.sol"; +import "./PercentageTransferManagerStorage.sol"; +import "../../../Pausable.sol"; +import "../../../storage/modules/ModuleStorage.sol"; + +/** + * @title PercentageTransferManager module Proxy + */ +contract PercentageTransferManagerProxy is PercentageTransferManagerStorage, ModuleStorage, Pausable, OwnedUpgradeabilityProxy { + + /** + * @notice Constructor + * @param _securityToken Address of the security token + * @param _polyAddress Address of the polytoken + * @param _implementation representing the address of the new implementation to be set + */ + constructor (string memory _version, address _securityToken, address _polyAddress, address _implementation) + public + ModuleStorage(_securityToken, _polyAddress) + { + require( + _implementation != address(0), + "Implementation address should not be 0x" + ); + _upgradeTo(_version, _implementation); + } + +} diff --git a/contracts/modules/TransferManager/PTM/PercentageTransferManagerStorage.sol b/contracts/modules/TransferManager/PTM/PercentageTransferManagerStorage.sol new file mode 100644 index 000000000..018d2134f --- /dev/null +++ b/contracts/modules/TransferManager/PTM/PercentageTransferManagerStorage.sol @@ -0,0 +1,17 @@ +pragma solidity 0.5.8; + +/** + * @title Contract used to store layout for the PercentageTransferManager storage + */ +contract PercentageTransferManagerStorage { + + // Maximum percentage that any holder can have, multiplied by 10**16 - e.g. 20% is 20 * 10**16 + uint256 public maxHolderPercentage; + + // Ignore transactions which are part of the primary issuance + bool public allowPrimaryIssuance = true; + + // Addresses on this list are always able to send / receive tokens + mapping (address => bool) public whitelist; + +} diff --git a/contracts/modules/TransferManager/PercentageTransferManagerFactory.sol b/contracts/modules/TransferManager/PercentageTransferManagerFactory.sol deleted file mode 100644 index 1702b6f08..000000000 --- a/contracts/modules/TransferManager/PercentageTransferManagerFactory.sol +++ /dev/null @@ -1,71 +0,0 @@ -pragma solidity ^0.4.24; - -import "./PercentageTransferManager.sol"; -import "../ModuleFactory.sol"; -import "../../libraries/Util.sol"; - -/** - * @title Factory for deploying PercentageTransferManager module - */ -contract PercentageTransferManagerFactory is ModuleFactory { - - /** - * @notice Constructor - * @param _polyAddress Address of the polytoken - */ - constructor (address _polyAddress, uint256 _setupCost, uint256 _usageCost, uint256 _subscriptionCost) public - ModuleFactory(_polyAddress, _setupCost, _usageCost, _subscriptionCost) - { - version = "1.0.0"; - name = "PercentageTransferManager"; - title = "Percentage Transfer Manager"; - description = "Restrict the number of investors"; - compatibleSTVersionRange["lowerBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0)); - compatibleSTVersionRange["upperBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0)); - } - - /** - * @notice used to launch the Module with the help of factory - * @param _data Data used for the intialization of the module factory variables - * @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"); - PercentageTransferManager percentageTransferManager = new PercentageTransferManager(msg.sender, address(polyToken)); - require(Util.getSig(_data) == percentageTransferManager.getInitFunction(), "Provided data is not valid"); - /*solium-disable-next-line security/no-low-level-calls*/ - require(address(percentageTransferManager).call(_data), "Unsuccessful call"); - /*solium-disable-next-line security/no-block-members*/ - emit GenerateModuleFromFactory(address(percentageTransferManager), getName(), address(this), msg.sender, setupCost, now); - return address(percentageTransferManager); - - } - - /** - * @notice Type of the Module factory - * @return uint8 - */ - function getTypes() external view returns(uint8[]) { - uint8[] memory res = new uint8[](1); - res[0] = 2; - return res; - } - - /** - * @notice Returns the instructions associated with the module - */ - function getInstructions() external view returns(string) { - return "Allows an issuer to restrict the total number of non-zero token holders"; - } - - /** - * @notice Get the tags related to the module factory - */ - function getTags() external view returns(bytes32[]) { - bytes32[] memory availableTags = new bytes32[](2); - availableTags[0] = "Percentage"; - availableTags[1] = "Transfer Restriction"; - return availableTags; - } -} diff --git a/contracts/modules/TransferManager/TransferManager.sol b/contracts/modules/TransferManager/TransferManager.sol new file mode 100644 index 000000000..d091669df --- /dev/null +++ b/contracts/modules/TransferManager/TransferManager.sol @@ -0,0 +1,33 @@ +pragma solidity 0.5.8; + +import "../Module.sol"; +import "../../interfaces/ITransferManager.sol"; + +/** + * @title Base abstract contract to be implemented by all Transfer Manager modules + */ +contract TransferManager is ITransferManager, Module { + + bytes32 public constant LOCKED = "LOCKED"; + bytes32 public constant UNLOCKED = "UNLOCKED"; + + modifier onlySecurityToken() { + require(msg.sender == address(securityToken), "Sender is not owner"); + _; + } + + // Provide default versions of ERC1410 functions that can be overriden + + /** + * @notice return the amount of tokens for a given user as per the partition + * @dev returning the balance of token holder against the UNLOCKED partition. + * This condition is valid only when the base contract doesn't implement the + * `getTokensByPartition()` function. + */ + function getTokensByPartition(bytes32 _partition, address _tokenHolder, uint256 /*_additionalBalance*/) external view returns(uint256) { + if (_partition == UNLOCKED) + return securityToken.balanceOf(_tokenHolder); + return uint256(0); + } + +} diff --git a/contracts/modules/TransferManager/VolumeRestrictionTM.sol b/contracts/modules/TransferManager/VRTM/VolumeRestrictionTM.sol similarity index 67% rename from contracts/modules/TransferManager/VolumeRestrictionTM.sol rename to contracts/modules/TransferManager/VRTM/VolumeRestrictionTM.sol index 034a0ef36..c2c229342 100644 --- a/contracts/modules/TransferManager/VolumeRestrictionTM.sol +++ b/contracts/modules/TransferManager/VRTM/VolumeRestrictionTM.sol @@ -1,19 +1,16 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; -import "./ITransferManager.sol"; import "openzeppelin-solidity/contracts/math/SafeMath.sol"; -import "../../libraries/BokkyPooBahsDateTimeLibrary.sol"; -import "../../libraries/VolumeRestrictionLib.sol"; - -contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { +import "openzeppelin-solidity/contracts/math/Math.sol"; +import "../../../libraries/BokkyPooBahsDateTimeLibrary.sol"; +import "../../../libraries/VolumeRestrictionLib.sol"; +import "../TransferManager.sol"; +contract VolumeRestrictionTM is VolumeRestrictionTMStorage, TransferManager { using SafeMath for uint256; - // permission definition - bytes32 internal constant ADMIN = "ADMIN"; - // Emit when the token holder is added/removed from the exemption list - event ChangedExemptWalletList(address indexed _wallet, bool _change); + event ChangedExemptWalletList(address indexed _wallet, bool _exempted); // Emit when the new individual restriction is added corresponds to new token holders event AddIndividualRestriction( address indexed _holder, @@ -108,48 +105,102 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { * whose volume of tokens will voilate the maximum volume transfer restriction * @param _from Address of the sender * @param _amount The amount of tokens to transfer - * @param _isTransfer Whether or not this is an actual transfer or just a test to see if the tokens would be transferrable */ - function verifyTransfer(address _from, address /*_to */, uint256 _amount, bytes /*_data*/, bool _isTransfer) public returns (Result) { + function executeTransfer(address _from, address /*_to */, uint256 _amount, bytes calldata /*_data*/) external onlySecurityToken returns (Result success) { + uint256 fromTimestamp; + uint256 sumOfLastPeriod; + uint256 daysCovered; + uint256 dailyTime; + uint256 endTime; + bool isGlobal; + (success, fromTimestamp, sumOfLastPeriod, daysCovered, dailyTime, endTime, ,isGlobal) = _verifyTransfer(_from, _amount); + if (fromTimestamp != 0 || dailyTime != 0) { + _updateStorage( + _from, + _amount, + fromTimestamp, + sumOfLastPeriod, + daysCovered, + dailyTime, + endTime, + isGlobal + ); + } + return success; + } + + /** + * @notice Used to verify the transfer/transferFrom transaction and prevent tranaction + * whose volume of tokens will voilate the maximum volume transfer restriction + * @param _from Address of the sender + * @param _amount The amount of tokens to transfer + */ + function verifyTransfer( + address _from, + address /*_to*/ , + uint256 _amount, + bytes memory /*_data*/ + ) + public + view + returns (Result, bytes32) + { + + (Result success,,,,,,,) = _verifyTransfer(_from, _amount); + if (success == Result.INVALID) + return (success, bytes32(uint256(address(this)) << 96)); + return (Result.NA, bytes32(0)); + } + + /** + * @notice Used to verify the transfer/transferFrom transaction and prevent tranaction + * whose volume of tokens will voilate the maximum volume transfer restriction + * @param _from Address of the sender + * @param _amount The amount of tokens to transfer + */ + function _verifyTransfer( + address _from, + uint256 _amount + ) + internal + view + returns (Result, uint256, uint256, uint256, uint256, uint256, uint256, bool) + { // If `_from` is present in the exemptionList or it is `0x0` address then it will not follow the vol restriction - if (!paused && _from != address(0) && exemptIndex[_from] == 0) { - // Function must only be called by the associated security token if _isTransfer == true - require(msg.sender == securityToken || !_isTransfer); + if (!paused && _from != address(0) && exemptions.exemptIndex[_from] == 0) { // Checking the individual restriction if the `_from` comes in the individual category - if ((individualRestriction[_from].endTime >= now && individualRestriction[_from].startTime <= now) - || (individualDailyRestriction[_from].endTime >= now && individualDailyRestriction[_from].startTime <= now)) { - - return _restrictionCheck(_isTransfer, _from, _amount, userToBucket[_from], individualRestriction[_from], false); + if ((individualRestrictions.individualRestriction[_from].endTime >= now && individualRestrictions.individualRestriction[_from].startTime <= now) + || (individualRestrictions.individualDailyRestriction[_from].endTime >= now && individualRestrictions.individualDailyRestriction[_from].startTime <= now)) { + return _restrictionCheck(_amount, _from, false, individualRestrictions.individualRestriction[_from]); // If the `_from` doesn't fall under the individual category. It will processed with in the global category automatically - } else if ((defaultRestriction.endTime >= now && defaultRestriction.startTime <= now) - || (defaultDailyRestriction.endTime >= now && defaultDailyRestriction.startTime <= now)) { + } else if ((globalRestrictions.defaultRestriction.endTime >= now && globalRestrictions.defaultRestriction.startTime <= now) + || (globalRestrictions.defaultDailyRestriction.endTime >= now && globalRestrictions.defaultDailyRestriction.startTime <= now)) { - return _restrictionCheck(_isTransfer, _from, _amount, defaultUserToBucket[_from], defaultRestriction, true); + return _restrictionCheck(_amount, _from, true, globalRestrictions.defaultRestriction); } } - return Result.NA; + return (Result.NA, 0, 0, 0, 0, 0, 0, false); } /** * @notice Add/Remove wallet address from the exempt list * @param _wallet Ethereum wallet/contract address that need to be exempted - * @param _change Boolean value used to add (i.e true) or remove (i.e false) from the list + * @param _exempted Boolean value used to add (i.e true) or remove (i.e false) from the list */ - function changeExemptWalletList(address _wallet, bool _change) external withPerm(ADMIN) { + function changeExemptWalletList(address _wallet, bool _exempted) public withPerm(ADMIN) { require(_wallet != address(0)); - uint256 exemptIndexWallet = exemptIndex[_wallet]; - require((exemptIndexWallet == 0) == _change); - if (_change) { - exemptAddresses.push(_wallet); - exemptIndex[_wallet] = exemptAddresses.length; + require((exemptions.exemptIndex[_wallet] == 0) == _exempted); + if (_exempted) { + exemptions.exemptAddresses.push(_wallet); + exemptions.exemptIndex[_wallet] = exemptions.exemptAddresses.length; } else { - exemptAddresses[exemptIndexWallet - 1] = exemptAddresses[exemptAddresses.length - 1]; - exemptIndex[exemptAddresses[exemptIndexWallet - 1]] = exemptIndexWallet; - delete exemptIndex[_wallet]; - exemptAddresses.length --; + exemptions.exemptAddresses[exemptions.exemptIndex[_wallet] - 1] = exemptions.exemptAddresses[exemptions.exemptAddresses.length - 1]; + exemptions.exemptIndex[exemptions.exemptAddresses[exemptions.exemptIndex[_wallet] - 1]] = exemptions.exemptIndex[_wallet]; + delete exemptions.exemptIndex[_wallet]; + exemptions.exemptAddresses.length--; } - emit ChangedExemptWalletList(_wallet, _change); + emit ChangedExemptWalletList(_wallet, _exempted); } /** @@ -170,49 +221,35 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { uint256 _endTime, RestrictionType _restrictionType ) - external + public withPerm(ADMIN) - { - _addIndividualRestriction( - _holder, - _allowedTokens, - _startTime, - _rollingPeriodInDays, - _endTime, - _restrictionType - ); - } - - /// @notice Internal function to facilitate the addition of individual restriction - function _addIndividualRestriction( - address _holder, - uint256 _allowedTokens, - uint256 _startTime, - uint256 _rollingPeriodInDays, - uint256 _endTime, - RestrictionType _restrictionType - ) - internal { // It will help to reduce the chances of transaction failure (Specially when the issuer // wants to set the startTime near to the current block.timestamp) and minting delayed because // of the gas fee or network congestion that lead to the process block timestamp may grater // than the given startTime. uint256 startTime = _getValidStartTime(_startTime); - require(_holder != address(0) && exemptIndex[_holder] == 0, "Invalid address"); + require(_holder != address(0) && exemptions.exemptIndex[_holder] == 0, "Invalid address"); _checkInputParams(_allowedTokens, startTime, _rollingPeriodInDays, _endTime, _restrictionType, now, false); - if (individualRestriction[_holder].endTime != 0) { - _removeIndividualRestriction(_holder); + if (individualRestrictions.individualRestriction[_holder].endTime != 0) { + removeIndividualRestriction(_holder); } - individualRestriction[_holder] = VolumeRestriction( + individualRestrictions.individualRestriction[_holder] = VolumeRestriction( _allowedTokens, startTime, _rollingPeriodInDays, _endTime, _restrictionType ); - VolumeRestrictionLib.addRestrictionData(holderData, _holder, TypeOfPeriod.MultipleDays, individualRestriction[_holder].endTime); + VolumeRestrictionLib + .addRestrictionData( + holderToRestrictionType, + _holder, + TypeOfPeriod.MultipleDays, + individualRestrictions.individualRestriction[_holder].endTime, + getDataStore() + ); emit AddIndividualRestriction( _holder, _allowedTokens, @@ -224,13 +261,13 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { } /** - * @notice Use to add the new individual daily restriction for a given token holder + * @notice Use to add the new individual daily restriction for all token holder * @param _holder Address of the token holder, whom restriction will be implied * @param _allowedTokens Amount of tokens allowed to be traded for all token holder. * @param _startTime Unix timestamp at which restriction get into effect * @param _endTime Unix timestamp at which restriction effects will gets end. * @param _restrictionType Whether it will be `Fixed` (fixed no. of tokens allowed to transact) - * or `Percentage` (tokens are calculated as per the totalSupply in the fly). + * or `Percentage` (tokens are calculated as per the totalSupply in the fly). */ function addIndividualDailyRestriction( address _holder, @@ -239,41 +276,29 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { uint256 _endTime, RestrictionType _restrictionType ) - external + public withPerm(ADMIN) - { - _addIndividualDailyRestriction( - _holder, - _allowedTokens, - _startTime, - _endTime, - _restrictionType - ); - } - - /// @notice Internal function to facilitate the addition of individual daily restriction - function _addIndividualDailyRestriction( - address _holder, - uint256 _allowedTokens, - uint256 _startTime, - uint256 _endTime, - RestrictionType _restrictionType - ) - internal { uint256 startTime = _getValidStartTime(_startTime); _checkInputParams(_allowedTokens, startTime, 1, _endTime, _restrictionType, now, false); - if (individualDailyRestriction[_holder].endTime != 0) { - _removeIndividualDailyRestriction(_holder); + if (individualRestrictions.individualDailyRestriction[_holder].endTime != 0) { + removeIndividualDailyRestriction(_holder); } - individualDailyRestriction[_holder] = VolumeRestriction( + individualRestrictions.individualDailyRestriction[_holder] = VolumeRestriction( _allowedTokens, startTime, 1, _endTime, _restrictionType ); - VolumeRestrictionLib.addRestrictionData(holderData, _holder, TypeOfPeriod.OneDay, individualRestriction[_holder].endTime); + VolumeRestrictionLib + .addRestrictionData( + holderToRestrictionType, + _holder, + TypeOfPeriod.OneDay, + individualRestrictions.individualRestriction[_holder].endTime, + getDataStore() + ); emit AddIndividualDailyRestriction( _holder, _allowedTokens, @@ -294,19 +319,18 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { * or `Percentage` (tokens are calculated as per the totalSupply in the fly). */ function addIndividualDailyRestrictionMulti( - address[] _holders, - uint256[] _allowedTokens, - uint256[] _startTimes, - uint256[] _endTimes, - RestrictionType[] _restrictionTypes + address[] memory _holders, + uint256[] memory _allowedTokens, + uint256[] memory _startTimes, + uint256[] memory _endTimes, + RestrictionType[] memory _restrictionTypes ) - public //Marked public to save code size - withPerm(ADMIN) + public { //NB - we duplicate _startTimes below to allow function reuse _checkLengthOfArray(_holders, _allowedTokens, _startTimes, _startTimes, _endTimes, _restrictionTypes); for (uint256 i = 0; i < _holders.length; i++) { - _addIndividualDailyRestriction( + addIndividualDailyRestriction( _holders[i], _allowedTokens[i], _startTimes[i], @@ -327,19 +351,18 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { * or `Percentage` (tokens are calculated as per the totalSupply in the fly). */ function addIndividualRestrictionMulti( - address[] _holders, - uint256[] _allowedTokens, - uint256[] _startTimes, - uint256[] _rollingPeriodInDays, - uint256[] _endTimes, - RestrictionType[] _restrictionTypes + address[] memory _holders, + uint256[] memory _allowedTokens, + uint256[] memory _startTimes, + uint256[] memory _rollingPeriodInDays, + uint256[] memory _endTimes, + RestrictionType[] memory _restrictionTypes ) - public //Marked public to save code size - withPerm(ADMIN) + public { _checkLengthOfArray(_holders, _allowedTokens, _startTimes, _rollingPeriodInDays, _endTimes, _restrictionTypes); for (uint256 i = 0; i < _holders.length; i++) { - _addIndividualRestriction( + addIndividualRestriction( _holders[i], _allowedTokens[i], _startTimes[i], @@ -366,12 +389,12 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { uint256 _endTime, RestrictionType _restrictionType ) - public //Marked public to save code size + external withPerm(ADMIN) { uint256 startTime = _getValidStartTime(_startTime); _checkInputParams(_allowedTokens, startTime, _rollingPeriodInDays, _endTime, _restrictionType, now, false); - defaultRestriction = VolumeRestriction( + globalRestrictions.defaultRestriction = VolumeRestriction( _allowedTokens, startTime, _rollingPeriodInDays, @@ -401,12 +424,12 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { uint256 _endTime, RestrictionType _restrictionType ) - public //Marked public to save code size + external withPerm(ADMIN) { uint256 startTime = _getValidStartTime(_startTime); _checkInputParams(_allowedTokens, startTime, 1, _endTime, _restrictionType, now, false); - defaultDailyRestriction = VolumeRestriction( + globalRestrictions.defaultDailyRestriction = VolumeRestriction( _allowedTokens, startTime, 1, @@ -426,19 +449,18 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { * @notice use to remove the individual restriction for a given address * @param _holder Address of the user */ - function removeIndividualRestriction(address _holder) external withPerm(ADMIN) { + function removeIndividualRestriction(address _holder) public withPerm(ADMIN) { _removeIndividualRestriction(_holder); } - /// @notice Internal function to facilitate the removal of individual restriction function _removeIndividualRestriction(address _holder) internal { require(_holder != address(0)); - require(individualRestriction[_holder].endTime != 0); - individualRestriction[_holder] = VolumeRestriction(0, 0, 0, 0, RestrictionType(0)); - VolumeRestrictionLib.deleteHolderFromList(holderData, _holder, TypeOfPeriod.OneDay); - userToBucket[_holder].lastTradedDayTime = 0; - userToBucket[_holder].sumOfLastPeriod = 0; - userToBucket[_holder].daysCovered = 0; + require(individualRestrictions.individualRestriction[_holder].endTime != 0); + individualRestrictions.individualRestriction[_holder] = VolumeRestriction(0, 0, 0, 0, RestrictionType(0)); + VolumeRestrictionLib.deleteHolderFromList(holderToRestrictionType, _holder, getDataStore(), TypeOfPeriod.OneDay); + bucketData.userToBucket[_holder].lastTradedDayTime = 0; + bucketData.userToBucket[_holder].sumOfLastPeriod = 0; + bucketData.userToBucket[_holder].daysCovered = 0; emit IndividualRestrictionRemoved(_holder); } @@ -446,7 +468,7 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { * @notice use to remove the individual restriction for a given address * @param _holders Array of address of the user */ - function removeIndividualRestrictionMulti(address[] _holders) external withPerm(ADMIN) { + function removeIndividualRestrictionMulti(address[] memory _holders) public withPerm(ADMIN) { for (uint256 i = 0; i < _holders.length; i++) { _removeIndividualRestriction(_holders[i]); } @@ -456,17 +478,16 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { * @notice use to remove the individual daily restriction for a given address * @param _holder Address of the user */ - function removeIndividualDailyRestriction(address _holder) external withPerm(ADMIN) { + function removeIndividualDailyRestriction(address _holder) public withPerm(ADMIN) { _removeIndividualDailyRestriction(_holder); } - /// @notice Internal function to facilitate the removal of individual daily restriction function _removeIndividualDailyRestriction(address _holder) internal { require(_holder != address(0)); - require(individualDailyRestriction[_holder].endTime != 0); - individualDailyRestriction[_holder] = VolumeRestriction(0, 0, 0, 0, RestrictionType(0)); - VolumeRestrictionLib.deleteHolderFromList(holderData, _holder, TypeOfPeriod.MultipleDays); - userToBucket[_holder].dailyLastTradedDayTime = 0; + require(individualRestrictions.individualDailyRestriction[_holder].endTime != 0); + individualRestrictions.individualDailyRestriction[_holder] = VolumeRestriction(0, 0, 0, 0, RestrictionType(0)); + VolumeRestrictionLib.deleteHolderFromList(holderToRestrictionType, _holder, getDataStore(), TypeOfPeriod.MultipleDays); + bucketData.userToBucket[_holder].dailyLastTradedDayTime = 0; emit IndividualDailyRestrictionRemoved(_holder); } @@ -474,7 +495,7 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { * @notice use to remove the individual daily restriction for a given address * @param _holders Array of address of the user */ - function removeIndividualDailyRestrictionMulti(address[] _holders) external withPerm(ADMIN) { + function removeIndividualDailyRestrictionMulti(address[] memory _holders) public withPerm(ADMIN) { for (uint256 i = 0; i < _holders.length; i++) { _removeIndividualDailyRestriction(_holders[i]); } @@ -483,9 +504,9 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { /** * @notice Use to remove the default restriction */ - function removeDefaultRestriction() external withPerm(ADMIN) { - require(defaultRestriction.endTime != 0); - defaultRestriction = VolumeRestriction(0, 0, 0, 0, RestrictionType(0)); + function removeDefaultRestriction() public withPerm(ADMIN) { + require(globalRestrictions.defaultRestriction.endTime != 0); + globalRestrictions.defaultRestriction = VolumeRestriction(0, 0, 0, 0, RestrictionType(0)); emit DefaultRestrictionRemoved(); } @@ -493,8 +514,8 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { * @notice Use to remove the daily default restriction */ function removeDefaultDailyRestriction() external withPerm(ADMIN) { - require(defaultDailyRestriction.endTime != 0); - defaultDailyRestriction = VolumeRestriction(0, 0, 0, 0, RestrictionType(0)); + require(globalRestrictions.defaultDailyRestriction.endTime != 0); + globalRestrictions.defaultDailyRestriction = VolumeRestriction(0, 0, 0, 0, RestrictionType(0)); emit DefaultDailyRestrictionRemoved(); } @@ -516,34 +537,13 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { uint256 _endTime, RestrictionType _restrictionType ) - external + public withPerm(ADMIN) { - _modifyIndividualRestriction( - _holder, - _allowedTokens, - _startTime, - _rollingPeriodInDays, - _endTime, - _restrictionType - ); - } - - /// @notice Internal function to facilitate the modification of individual restriction - function _modifyIndividualRestriction( - address _holder, - uint256 _allowedTokens, - uint256 _startTime, - uint256 _rollingPeriodInDays, - uint256 _endTime, - RestrictionType _restrictionType - ) - internal - { - _isAllowedToModify(individualRestriction[_holder].startTime); + _isAllowedToModify(individualRestrictions.individualRestriction[_holder].startTime); uint256 startTime = _getValidStartTime(_startTime); _checkInputParams(_allowedTokens, startTime, _rollingPeriodInDays, _endTime, _restrictionType, now, false); - individualRestriction[_holder] = VolumeRestriction( + individualRestrictions.individualRestriction[_holder] = VolumeRestriction( _allowedTokens, startTime, _rollingPeriodInDays, @@ -557,7 +557,7 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { _rollingPeriodInDays, _endTime, _restrictionType - ); + ); } /** @@ -578,34 +578,13 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { uint256 _endTime, RestrictionType _restrictionType ) - external + public withPerm(ADMIN) - { - _modifyIndividualDailyRestriction( - _holder, - _allowedTokens, - _startTime, - _endTime, - _restrictionType - ); - } - - /// @notice Internal function to facilitate the modification of individual daily restriction - function _modifyIndividualDailyRestriction( - address _holder, - uint256 _allowedTokens, - uint256 _startTime, - uint256 _endTime, - RestrictionType _restrictionType - ) - internal { uint256 startTime = _getValidStartTime(_startTime); - _checkInputParams(_allowedTokens, startTime, 1, _endTime, _restrictionType, - (individualDailyRestriction[_holder].startTime <= now ? individualDailyRestriction[_holder].startTime : now), - true - ); - individualDailyRestriction[_holder] = VolumeRestriction( + uint256 checkTime = (individualRestrictions.individualDailyRestriction[_holder].startTime <= now ? individualRestrictions.individualDailyRestriction[_holder].startTime : now); + _checkInputParams(_allowedTokens, startTime, 1, _endTime, _restrictionType, checkTime, true); + individualRestrictions.individualDailyRestriction[_holder] = VolumeRestriction( _allowedTokens, startTime, 1, @@ -632,19 +611,18 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { * or `Percentage` (tokens are calculated as per the totalSupply in the fly). */ function modifyIndividualDailyRestrictionMulti( - address[] _holders, - uint256[] _allowedTokens, - uint256[] _startTimes, - uint256[] _endTimes, - RestrictionType[] _restrictionTypes + address[] memory _holders, + uint256[] memory _allowedTokens, + uint256[] memory _startTimes, + uint256[] memory _endTimes, + RestrictionType[] memory _restrictionTypes ) - public //Marked public to save code size - withPerm(ADMIN) + public { //NB - we duplicate _startTimes below to allow function reuse _checkLengthOfArray(_holders, _allowedTokens, _startTimes, _startTimes, _endTimes, _restrictionTypes); for (uint256 i = 0; i < _holders.length; i++) { - _modifyIndividualDailyRestriction( + modifyIndividualDailyRestriction( _holders[i], _allowedTokens[i], _startTimes[i], @@ -665,19 +643,18 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { * or `Percentage` (tokens are calculated as per the totalSupply in the fly). */ function modifyIndividualRestrictionMulti( - address[] _holders, - uint256[] _allowedTokens, - uint256[] _startTimes, - uint256[] _rollingPeriodInDays, - uint256[] _endTimes, - RestrictionType[] _restrictionTypes + address[] memory _holders, + uint256[] memory _allowedTokens, + uint256[] memory _startTimes, + uint256[] memory _rollingPeriodInDays, + uint256[] memory _endTimes, + RestrictionType[] memory _restrictionTypes ) - public //Marked public to save code size - withPerm(ADMIN) + public { _checkLengthOfArray(_holders, _allowedTokens, _startTimes, _rollingPeriodInDays, _endTimes, _restrictionTypes); for (uint256 i = 0; i < _holders.length; i++) { - _modifyIndividualRestriction( + modifyIndividualRestriction( _holders[i], _allowedTokens[i], _startTimes[i], @@ -704,13 +681,13 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { uint256 _endTime, RestrictionType _restrictionType ) - public //Marked public to save code size + external withPerm(ADMIN) { - _isAllowedToModify(defaultRestriction.startTime); + _isAllowedToModify(globalRestrictions.defaultRestriction.startTime); uint256 startTime = _getValidStartTime(_startTime); _checkInputParams(_allowedTokens, startTime, _rollingPeriodInDays, _endTime, _restrictionType, now, false); - defaultRestriction = VolumeRestriction( + globalRestrictions.defaultRestriction = VolumeRestriction( _allowedTokens, startTime, _rollingPeriodInDays, @@ -742,17 +719,17 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { uint256 _endTime, RestrictionType _restrictionType ) - public //Marked public to save code size + external withPerm(ADMIN) { uint256 startTime = _getValidStartTime(_startTime); // If old startTime is already passed then new startTime should be greater than or equal to the // old startTime otherwise any past startTime can be allowed in compare to earlier startTime. _checkInputParams(_allowedTokens, startTime, 1, _endTime, _restrictionType, - (defaultDailyRestriction.startTime <= now ? defaultDailyRestriction.startTime : now), + (globalRestrictions.defaultDailyRestriction.startTime <= now ? globalRestrictions.defaultDailyRestriction.startTime : now), true ); - defaultDailyRestriction = VolumeRestriction( + globalRestrictions.defaultDailyRestriction = VolumeRestriction( _allowedTokens, startTime, 1, @@ -773,25 +750,29 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { * If it validates then it also update the storage corressponds to the default restriction */ function _restrictionCheck( - bool _isTransfer, - address _from, uint256 _amount, - BucketDetails memory _bucketDetails, - VolumeRestriction memory _restriction, - bool _isDefault + address _from, + bool _isDefault, + VolumeRestriction memory _restriction ) internal - returns (Result) - { + view + returns ( + Result success, + uint256 fromTimestamp, + uint256 sumOfLastPeriod, + uint256 daysCovered, + uint256 dailyTime, + uint256 endTime, + uint256 allowedAmountToTransact, + bool allowedDaily + ) { // using the variable to avoid stack too deep error - VolumeRestriction memory dailyRestriction = _isDefault ? defaultDailyRestriction :individualDailyRestriction[_from]; - uint256 daysCovered = _restriction.rollingPeriodInDays; - uint256 fromTimestamp = 0; - uint256 sumOfLastPeriod = 0; - uint256 dailyTime = 0; + VolumeRestriction memory dailyRestriction = _isDefault ? globalRestrictions.defaultDailyRestriction :individualRestrictions.individualDailyRestriction[_from]; + BucketDetails memory _bucketDetails = _isDefault ? bucketData.defaultUserToBucket[_from]: bucketData.userToBucket[_from]; + daysCovered = _restriction.rollingPeriodInDays; // This variable works for both allowedDefault or allowedIndividual bool allowedDefault = true; - bool allowedDaily; if (_restriction.endTime >= now && _restriction.startTime <= now) { if (_bucketDetails.lastTradedDayTime < _restriction.startTime) { // It will execute when the txn is performed first time after the addition of individual restriction @@ -804,37 +785,48 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { // Check with the bucket and parse all the new timestamps to calculate the sumOfLastPeriod // re-using the local variables to avoid the stack too deep error. (sumOfLastPeriod, fromTimestamp, daysCovered) = _bucketCheck( + _from, + _isDefault, fromTimestamp, BokkyPooBahsDateTimeLibrary.diffDays(fromTimestamp, now), - _from, + daysCovered, - _bucketDetails, - _isDefault + _bucketDetails ); // validation of the transaction amount - if ( - !_checkValidAmountToTransact( - _isDefault, _from, sumOfLastPeriod, _amount, _restriction.typeOfRestriction, _restriction.allowedTokens - ) - ) { + // reusing the local variable to avoid stack too deep error + // here variable allowedAmountToTransact is representing the allowedAmount + (allowedDefault, allowedAmountToTransact) = _checkValidAmountToTransact(_amount, _from, _isDefault, _restriction, sumOfLastPeriod); + if (!allowedDefault) { allowedDefault = false; } } - (allowedDaily, dailyTime) = _dailyTxCheck(_from, _amount, _bucketDetails.dailyLastTradedDayTime, dailyRestriction, _isDefault); + // reusing the local variable to avoid stack too deep error + // here variable endTime is representing the allowedDailyAmount + (allowedDaily, dailyTime, endTime) = _dailyTxCheck(_amount, _from, _isDefault, _bucketDetails.dailyLastTradedDayTime, dailyRestriction); + success = ((allowedDaily && allowedDefault) == true ? Result.NA : Result.INVALID); + allowedAmountToTransact = _validAllowedAmount(dailyRestriction, _restriction, allowedAmountToTransact, endTime); + endTime = dailyRestriction.endTime; + allowedDaily = _isDefault; + } - if (_isTransfer) { - _updateStorage( - _from, - _amount, - fromTimestamp, - sumOfLastPeriod, - daysCovered, - dailyTime, - _isDefault - ); - } - return ((allowedDaily && allowedDefault) == true ? Result.NA : Result.INVALID); + function _validAllowedAmount( + VolumeRestriction memory dailyRestriction, + VolumeRestriction memory restriction, + uint256 allowedAmount, + uint256 allowedDailyAmount + ) + internal + view + returns (uint256) + { + if (now > dailyRestriction.endTime || now < dailyRestriction.startTime) + return allowedAmount; + else if (now > restriction.endTime || now < restriction.startTime) + return allowedDailyAmount; + else + return Math.min(allowedDailyAmount, allowedAmount); } /** @@ -842,7 +834,7 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { * default to individual or vice versa. It will return true when last transaction traded by the user * and the current txn timestamp lies in the same day. * NB - Instead of comparing the current day transaction amount, we are comparing the total amount traded - * on the lastTradedDayTime that makes the restriction strict. The reason is not availability of amount + * on the lastTradedDayTime that makes the restriction strict. The reason is not availability of amount * that transacted on the current day (because of bucket desgin). */ function _isValidAmountAfterRestrictionChanges( @@ -857,8 +849,8 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { returns(bool) { // Always use the alternate bucket details as per the current transaction restriction - BucketDetails storage bucketDetails = _isDefault ? userToBucket[_from] : defaultUserToBucket[_from]; - uint256 amountTradedLastDay = _isDefault ? bucket[_from][bucketDetails.lastTradedDayTime]: defaultBucket[_from][bucketDetails.lastTradedDayTime]; + BucketDetails storage bucketDetails = _isDefault ? bucketData.userToBucket[_from] : bucketData.defaultUserToBucket[_from]; + uint256 amountTradedLastDay = _isDefault ? bucketData.bucket[_from][bucketDetails.lastTradedDayTime]: bucketData.defaultBucket[_from][bucketDetails.lastTradedDayTime]; return VolumeRestrictionLib.isValidAmountAfterRestrictionChanges( amountTradedLastDay, _amount, @@ -868,77 +860,45 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { ); } - function _checkValidAmountToTransact( - bool _isDefault, - address _from, - uint256 _sumOfLastPeriod, - uint256 _amountToTransact, - RestrictionType _typeOfRestriction, - uint256 _allowedTokens - ) - internal - view - returns (bool) - { - uint256 allowedAmount; - if (_typeOfRestriction == RestrictionType.Percentage) { - allowedAmount = (_allowedTokens.mul(ISecurityToken(securityToken).totalSupply())) / uint256(10) ** 18; - } else { - allowedAmount = _allowedTokens; - } - // Validation on the amount to transact - bool allowed = allowedAmount >= _sumOfLastPeriod.add(_amountToTransact); - return (allowed && _isValidAmountAfterRestrictionChanges(_isDefault, _from, _amountToTransact, _sumOfLastPeriod, allowedAmount)); - } - function _dailyTxCheck( - address _from, uint256 _amount, + address _from, + bool _isDefault, uint256 _dailyLastTradedDayTime, - VolumeRestriction memory _restriction, - bool _isDefault + VolumeRestriction memory _restriction ) internal view - returns(bool, uint256) + returns(bool, uint256, uint256) { // Checking whether the daily restriction is added or not if yes then calculate // the total amount get traded on a particular day (~ _fromTime) if ( now <= _restriction.endTime && now >= _restriction.startTime) { uint256 txSumOfDay = 0; - // This if condition will be executed when the individual daily restriction executed first time if (_dailyLastTradedDayTime == 0 || _dailyLastTradedDayTime < _restriction.startTime) + // This if condition will be executed when the individual daily restriction executed first time _dailyLastTradedDayTime = _restriction.startTime.add(BokkyPooBahsDateTimeLibrary.diffDays(_restriction.startTime, now).mul(1 days)); else if (now.sub(_dailyLastTradedDayTime) >= 1 days) _dailyLastTradedDayTime = _dailyLastTradedDayTime.add(BokkyPooBahsDateTimeLibrary.diffDays(_dailyLastTradedDayTime, now).mul(1 days)); // Assgining total sum traded on dailyLastTradedDayTime timestamp if (_isDefault) - txSumOfDay = defaultBucket[_from][_dailyLastTradedDayTime]; + txSumOfDay = bucketData.defaultBucket[_from][_dailyLastTradedDayTime]; else - txSumOfDay = bucket[_from][_dailyLastTradedDayTime]; - return ( - _checkValidAmountToTransact( - _isDefault, - _from, - txSumOfDay, - _amount, - _restriction.typeOfRestriction, - _restriction.allowedTokens - ), - _dailyLastTradedDayTime - ); + txSumOfDay = bucketData.bucket[_from][_dailyLastTradedDayTime]; + (bool isAllowed, uint256 allowedAmount) = _checkValidAmountToTransact(_amount, _from, _isDefault, _restriction, txSumOfDay); + return (isAllowed, _dailyLastTradedDayTime, allowedAmount); } - return (true, _dailyLastTradedDayTime); + return (true, _dailyLastTradedDayTime, _amount); } /// Internal function for the bucket check function _bucketCheck( + address _from, + bool isDefault, uint256 _fromTime, uint256 _diffDays, - address _from, uint256 _rollingPeriodInDays, - BucketDetails memory _bucketDetails, - bool isDefault + BucketDetails memory _bucketDetails ) internal view @@ -965,9 +925,9 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { uint256 temp = _bucketDetails.daysCovered.sub(counter.sub(_rollingPeriodInDays)); temp = _bucketDetails.lastTradedDayTime.sub(temp.mul(1 days)); if (isDefault) - sumOfLastPeriod = sumOfLastPeriod.sub(defaultBucket[_from][temp]); + sumOfLastPeriod = sumOfLastPeriod.sub(bucketData.defaultBucket[_from][temp]); else - sumOfLastPeriod = sumOfLastPeriod.sub(bucket[_from][temp]); + sumOfLastPeriod = sumOfLastPeriod.sub(bucketData.bucket[_from][temp]); } // Adding the last amount that is transacted on the `_fromTime` not actually doing it but left written to understand // the alogrithm @@ -981,6 +941,40 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { return (sumOfLastPeriod, _fromTime, counter); } + function _checkValidAmountToTransact( + uint256 _amountToTransact, + address _from, + bool _isDefault, + VolumeRestriction memory _restriction, + uint256 _sumOfLastPeriod + ) + internal + view + returns (bool, uint256) + { + uint256 allowedAmount = _allowedAmountToTransact(_sumOfLastPeriod, _restriction); + // Validation on the amount to transact + bool allowed = allowedAmount >= _amountToTransact; + return ((allowed && _isValidAmountAfterRestrictionChanges(_isDefault, _from, _amountToTransact, _sumOfLastPeriod, allowedAmount)), allowedAmount); + } + + function _allowedAmountToTransact( + uint256 _sumOfLastPeriod, + VolumeRestriction memory _restriction + ) + internal + view + returns (uint256) + { + uint256 _allowedAmount = 0; + if (_restriction.typeOfRestriction == RestrictionType.Percentage) { + _allowedAmount = (_restriction.allowedTokens.mul(securityToken.totalSupply())) / uint256(10) ** 18; + } else { + _allowedAmount = _restriction.allowedTokens; + } + return _allowedAmount.sub(_sumOfLastPeriod); + } + function _updateStorage( address _from, uint256 _amount, @@ -988,18 +982,18 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { uint256 _sumOfLastPeriod, uint256 _daysCovered, uint256 _dailyLastTradedDayTime, + uint256 _endTime, bool isDefault ) internal { if (isDefault){ - BucketDetails storage defaultUserToBucketDetails = defaultUserToBucket[_from]; - _updateStorageActual(_from, _amount, _lastTradedDayTime, _sumOfLastPeriod, _daysCovered, _dailyLastTradedDayTime, defaultDailyRestriction.endTime, true, defaultUserToBucketDetails); + BucketDetails storage defaultUserToBucketDetails = bucketData.defaultUserToBucket[_from]; + _updateStorageActual(_from, _amount, _lastTradedDayTime, _sumOfLastPeriod, _daysCovered, _dailyLastTradedDayTime, _endTime, true, defaultUserToBucketDetails); } else { - BucketDetails storage userToBucketDetails = userToBucket[_from]; - uint256 _endTime = individualDailyRestriction[_from].endTime; + BucketDetails storage userToBucketDetails = bucketData.userToBucket[_from]; _updateStorageActual(_from, _amount, _lastTradedDayTime, _sumOfLastPeriod, _daysCovered, _dailyLastTradedDayTime, _endTime, false, userToBucketDetails); } } @@ -1031,22 +1025,22 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { } // Assigning the latest transaction timestamp details.lastTradedTimestamp = now; + if (_amount != 0) { if (_lastTradedDayTime !=0) { details.sumOfLastPeriod = _sumOfLastPeriod.add(_amount); // Increasing the total amount of the day by `_amount` if (isDefault) - defaultBucket[_from][_lastTradedDayTime] = defaultBucket[_from][_lastTradedDayTime].add(_amount); + bucketData.defaultBucket[_from][_lastTradedDayTime] = bucketData.defaultBucket[_from][_lastTradedDayTime].add(_amount); else - bucket[_from][_lastTradedDayTime] = bucket[_from][_lastTradedDayTime].add(_amount); + bucketData.bucket[_from][_lastTradedDayTime] = bucketData.bucket[_from][_lastTradedDayTime].add(_amount); } if ((_dailyLastTradedDayTime != _lastTradedDayTime) && _dailyLastTradedDayTime != 0 && now <= _endTime) { // Increasing the total amount of the day by `_amount` if (isDefault) - defaultBucket[_from][_dailyLastTradedDayTime] = defaultBucket[_from][_dailyLastTradedDayTime].add(_amount); + bucketData.defaultBucket[_from][_dailyLastTradedDayTime] = bucketData.defaultBucket[_from][_dailyLastTradedDayTime].add(_amount); else - bucket[_from][_dailyLastTradedDayTime] = bucket[_from][_dailyLastTradedDayTime].add(_amount); - + bucketData.bucket[_from][_dailyLastTradedDayTime] = bucketData.bucket[_from][_dailyLastTradedDayTime].add(_amount); } } } @@ -1089,6 +1083,39 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { return _startTime; } + /** + * @notice return the amount of tokens for a given user as per the partition + * @param _partition Identifier + * @param _tokenHolder Whom token amount need to query + * @param _additionalBalance It is the `_value` that transfer during transfer/transferFrom function call + */ + function getTokensByPartition(bytes32 _partition, address _tokenHolder, uint256 _additionalBalance) external view returns(uint256) { + uint256 allowedAmountToTransact; + uint256 fromTimestamp; + uint256 dailyTime; + uint256 currentBalance = (msg.sender == address(securityToken)) ? (securityToken.balanceOf(_tokenHolder)).add(_additionalBalance) : securityToken.balanceOf(_tokenHolder); + if (paused) + return (_partition == UNLOCKED ? currentBalance: 0); + + (,fromTimestamp,,,dailyTime,,allowedAmountToTransact,) = _verifyTransfer(_tokenHolder, 0); + if (_partition == LOCKED) { + if (allowedAmountToTransact == 0 && fromTimestamp == 0 && dailyTime == 0) + return 0; + else if (allowedAmountToTransact == 0) + return currentBalance; + else + return (currentBalance.sub(allowedAmountToTransact)); + } + else if (_partition == UNLOCKED) { + if (allowedAmountToTransact == 0 && fromTimestamp == 0 && dailyTime == 0) + return currentBalance; + else if (allowedAmountToTransact == 0) + return 0; + else + return allowedAmountToTransact; + } + } + /** * @notice Use to get the bucket details for a given address * @param _user Address of the token holder for whom the bucket details has queried @@ -1098,8 +1125,8 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { * @return uint256 24h lastTradedDayTime * @return uint256 Timestamp at which last transaction get executed */ - function getIndividualBucketDetailsToUser(address _user) external view returns(uint256, uint256, uint256, uint256, uint256) { - return _getBucketDetails(userToBucket[_user]); + function getIndividualBucketDetailsToUser(address _user) public view returns(uint256, uint256, uint256, uint256, uint256) { + return _getBucketDetails(bucketData.userToBucket[_user]); } /** @@ -1111,8 +1138,8 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { * @return uint256 24h lastTradedDayTime * @return uint256 Timestamp at which last transaction get executed */ - function getDefaultBucketDetailsToUser(address _user) external view returns(uint256, uint256, uint256, uint256, uint256) { - return _getBucketDetails(defaultUserToBucket[_user]); + function getDefaultBucketDetailsToUser(address _user) public view returns(uint256, uint256, uint256, uint256, uint256) { + return _getBucketDetails(bucketData.defaultUserToBucket[_user]); } function _getBucketDetails(BucketDetails storage _bucket) internal view returns( @@ -1137,21 +1164,47 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { * @param _at Timestamp */ function getTotalTradedByUser(address _user, uint256 _at) external view returns(uint256) { - return (bucket[_user][_at].add(defaultBucket[_user][_at])); + return (bucketData.bucket[_user][_at].add(bucketData.defaultBucket[_user][_at])); } /** * @notice This function returns the signature of configure function */ - function getInitFunction() public view returns(bytes4) { + function getInitFunction() public pure returns(bytes4) { return bytes4(0); } /** * @notice Use to return the list of exempted addresses */ - function getExemptAddress() external view returns(address[]) { - return exemptAddresses; + function getExemptAddress() external view returns(address[] memory) { + return exemptions.exemptAddresses; + } + + function getIndividualRestriction(address _investor) external view returns(uint256, uint256, uint256, uint256, RestrictionType) { + return _volumeRestrictionSplay(individualRestrictions.individualRestriction[_investor]); + } + + function getIndividualDailyRestriction(address _investor) external view returns(uint256, uint256, uint256, uint256, RestrictionType) { + return _volumeRestrictionSplay(individualRestrictions.individualDailyRestriction[_investor]); + } + + function getDefaultRestriction() external view returns(uint256, uint256, uint256, uint256, RestrictionType) { + return _volumeRestrictionSplay(globalRestrictions.defaultRestriction); + } + + function getDefaultDailyRestriction() external view returns(uint256, uint256, uint256, uint256, RestrictionType) { + return _volumeRestrictionSplay(globalRestrictions.defaultDailyRestriction); + } + + function _volumeRestrictionSplay(VolumeRestriction memory _volumeRestriction) internal pure returns(uint256, uint256, uint256, uint256, RestrictionType) { + return ( + _volumeRestriction.allowedTokens, + _volumeRestriction.startTime, + _volumeRestriction.rollingPeriodInDays, + _volumeRestriction.endTime, + _volumeRestriction.typeOfRestriction + ); } /** @@ -1161,10 +1214,10 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { * @return uint256 List of the start time of the restriction corresponds to restricted address * @return uint256 List of the rolling period in days for a restriction corresponds to restricted address. * @return uint256 List of the end time of the restriction corresponds to restricted address. - * @return RestrictionType List of the type of restriction to validate the value of the `allowedTokens` + * @return uint8 List of the type of restriction to validate the value of the `allowedTokens` * of the restriction corresponds to restricted address */ - function getRestrictedData() external view returns( + function getRestrictionData() external view returns( address[] memory allAddresses, uint256[] memory allowedTokens, uint256[] memory startTime, @@ -1172,64 +1225,16 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { uint256[] memory endTime, RestrictionType[] memory typeOfRestriction ) { - uint256 counter = 0; - uint256 i = 0; - for (i = 0; i < holderData.restrictedAddresses.length; i++) { - counter = counter + uint256( - holderData.restrictedHolders[holderData.restrictedAddresses[i]].typeOfPeriod == TypeOfPeriod.Both ? TypeOfPeriod.Both : TypeOfPeriod.OneDay - ); - } - allAddresses = new address[](counter); - allowedTokens = new uint256[](counter); - startTime = new uint256[](counter); - rollingPeriodInDays = new uint256[](counter); - endTime = new uint256[](counter); - typeOfRestriction = new RestrictionType[](counter); - counter = 0; - for (i = 0; i < holderData.restrictedAddresses.length; i++) { - allAddresses[counter] = holderData.restrictedAddresses[i]; - if (holderData.restrictedHolders[holderData.restrictedAddresses[i]].typeOfPeriod == TypeOfPeriod.MultipleDays) { - _setValues(individualRestriction[holderData.restrictedAddresses[i]], allowedTokens, startTime, rollingPeriodInDays, endTime, typeOfRestriction, counter); - } - else if (holderData.restrictedHolders[holderData.restrictedAddresses[i]].typeOfPeriod == TypeOfPeriod.OneDay) { - _setValues(individualDailyRestriction[holderData.restrictedAddresses[i]], allowedTokens, startTime, rollingPeriodInDays, endTime, typeOfRestriction, counter); - } - else if (holderData.restrictedHolders[holderData.restrictedAddresses[i]].typeOfPeriod == TypeOfPeriod.Both) { - _setValues(individualRestriction[holderData.restrictedAddresses[i]], allowedTokens, startTime, rollingPeriodInDays, endTime, typeOfRestriction, counter); - counter = counter + 1; - allAddresses[counter] = holderData.restrictedAddresses[i]; - _setValues(individualDailyRestriction[holderData.restrictedAddresses[i]], allowedTokens, startTime, rollingPeriodInDays, endTime, typeOfRestriction, counter); - } - counter ++; - } + return VolumeRestrictionLib.getRestrictionData(holderToRestrictionType, individualRestrictions, getDataStore()); } - function _setValues( - VolumeRestriction memory _restriction, + function _checkLengthOfArray( + address[] memory _holders, uint256[] memory _allowedTokens, - uint256[] memory _startTime, + uint256[] memory _startTimes, uint256[] memory _rollingPeriodInDays, - uint256[] memory _endTime, - RestrictionType[] memory _typeOfRestriction, - uint256 _index - ) - internal - pure - { - _allowedTokens[_index] = _restriction.allowedTokens; - _startTime[_index] = _restriction.startTime; - _rollingPeriodInDays[_index] = _restriction.rollingPeriodInDays; - _endTime[_index] = _restriction.endTime; - _typeOfRestriction[_index] = _restriction.typeOfRestriction; - } - - function _checkLengthOfArray( - address[] _holders, - uint256[] _allowedTokens, - uint256[] _startTimes, - uint256[] _rollingPeriodInDays, - uint256[] _endTimes, - RestrictionType[] _restrictionTypes + uint256[] memory _endTimes, + RestrictionType[] memory _restrictionTypes ) internal pure diff --git a/contracts/modules/TransferManager/VRTM/VolumeRestrictionTMFactory.sol b/contracts/modules/TransferManager/VRTM/VolumeRestrictionTMFactory.sol new file mode 100644 index 000000000..25a92ffdb --- /dev/null +++ b/contracts/modules/TransferManager/VRTM/VolumeRestrictionTMFactory.sol @@ -0,0 +1,49 @@ +pragma solidity 0.5.8; + +import "./VolumeRestrictionTMProxy.sol"; +import "../../UpgradableModuleFactory.sol"; + +/** + * @title Factory for deploying VolumeRestrictionTM module + */ +contract VolumeRestrictionTMFactory is UpgradableModuleFactory { + + /** + * @notice Constructor + * @param _setupCost Setup cost of the module + * @param _logicContract Contract address that contains the logic related to `description` + * @param _polymathRegistry Address of the Polymath registry + * @param _isCostInPoly true = cost in Poly, false = USD + */ + constructor ( + uint256 _setupCost, + address _logicContract, + address _polymathRegistry, + bool _isCostInPoly + ) + public + UpgradableModuleFactory("3.0.0", _setupCost, _logicContract, _polymathRegistry, _isCostInPoly) + { + name = "VolumeRestrictionTM"; + title = "Volume Restriction Transfer Manager"; + description = "Manage transfers based on the volume of tokens that needs to be transact"; + typesData.push(2); + typesData.push(6); + tagsData.push("Rolling Period"); + tagsData.push("Volume"); + tagsData.push("Transfer Restriction"); + compatibleSTVersionRange["lowerBound"] = VersionUtils.pack(uint8(3), uint8(0), uint8(0)); + compatibleSTVersionRange["upperBound"] = VersionUtils.pack(uint8(3), uint8(0), uint8(0)); + } + + /** + * @notice Used to launch the Module with the help of factory + * @return address Contract address of the Module + */ + function deploy(bytes calldata _data) external returns(address) { + address volumeRestrictionTransferManager = address(new VolumeRestrictionTMProxy(logicContracts[latestUpgrade].version, msg.sender, polymathRegistry.getAddress("PolyToken"), logicContracts[latestUpgrade].logicContract)); + _initializeModule(volumeRestrictionTransferManager, _data); + return volumeRestrictionTransferManager; + } + +} diff --git a/contracts/proxy/VolumeRestrictionTMProxy.sol b/contracts/modules/TransferManager/VRTM/VolumeRestrictionTMProxy.sol similarity index 58% rename from contracts/proxy/VolumeRestrictionTMProxy.sol rename to contracts/modules/TransferManager/VRTM/VolumeRestrictionTMProxy.sol index 0f5cc7f5a..f084e8c02 100644 --- a/contracts/proxy/VolumeRestrictionTMProxy.sol +++ b/contracts/modules/TransferManager/VRTM/VolumeRestrictionTMProxy.sol @@ -1,14 +1,14 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; -import "../storage/VolumeRestrictionTMStorage.sol"; -import "./OwnedProxy.sol"; -import "../Pausable.sol"; -import "../modules/ModuleStorage.sol"; +import "../../../proxy/OwnedUpgradeabilityProxy.sol"; +import "./VolumeRestrictionTMStorage.sol"; +import "../../../Pausable.sol"; +import "../../../storage/modules/ModuleStorage.sol"; /** * @title Transfer Manager module for core transfer validation functionality */ -contract VolumeRestrictionTMProxy is VolumeRestrictionTMStorage, ModuleStorage, Pausable, OwnedProxy { +contract VolumeRestrictionTMProxy is VolumeRestrictionTMStorage, ModuleStorage, Pausable, OwnedUpgradeabilityProxy { /** * @notice Constructor @@ -16,7 +16,7 @@ contract VolumeRestrictionTMProxy is VolumeRestrictionTMStorage, ModuleStorage, * @param _polyAddress Address of the polytoken * @param _implementation representing the address of the new implementation to be set */ - constructor (address _securityToken, address _polyAddress, address _implementation) + constructor (string memory _version, address _securityToken, address _polyAddress, address _implementation) public ModuleStorage(_securityToken, _polyAddress) { @@ -24,7 +24,7 @@ contract VolumeRestrictionTMProxy is VolumeRestrictionTMStorage, ModuleStorage, _implementation != address(0), "Implementation address should not be 0x" ); - __implementation = _implementation; + _upgradeTo(_version, _implementation); } } diff --git a/contracts/modules/TransferManager/VRTM/VolumeRestrictionTMStorage.sol b/contracts/modules/TransferManager/VRTM/VolumeRestrictionTMStorage.sol new file mode 100644 index 000000000..febd4fc10 --- /dev/null +++ b/contracts/modules/TransferManager/VRTM/VolumeRestrictionTMStorage.sol @@ -0,0 +1,75 @@ +pragma solidity 0.5.8; + +/** + * @title Storage layout for VolumeRestrictionTM + */ +contract VolumeRestrictionTMStorage { + + enum RestrictionType { Fixed, Percentage } + + enum TypeOfPeriod { MultipleDays, OneDay, Both } + + // Store the type of restriction corresponds to token holder address + mapping(address => TypeOfPeriod) holderToRestrictionType; + + struct VolumeRestriction { + // If typeOfRestriction is `Percentage` then allowedTokens will be in + // the % (w.r.t to totalSupply) with a multiplier of 10**16 . else it + // will be fixed amount of tokens + uint256 allowedTokens; + uint256 startTime; + uint256 rollingPeriodInDays; + uint256 endTime; + RestrictionType typeOfRestriction; + } + + struct IndividualRestrictions { + // Restriction stored corresponds to a particular token holder + mapping(address => VolumeRestriction) individualRestriction; + // Daily restriction stored corresponds to a particular token holder + mapping(address => VolumeRestriction) individualDailyRestriction; + } + + // Individual and daily restrictions for investors + IndividualRestrictions individualRestrictions; + + struct GlobalRestrictions { + // Global restriction that applies to all token holders + VolumeRestriction defaultRestriction; + // Daily global restriction that applies to all token holders (Total ST traded daily is restricted) + VolumeRestriction defaultDailyRestriction; + } + + // Individual and daily restrictions for investors + GlobalRestrictions globalRestrictions; + + struct BucketDetails { + uint256 lastTradedDayTime; + uint256 sumOfLastPeriod; // It is the sum of transacted amount within the last rollingPeriodDays + uint256 daysCovered; // No of days covered till (from the startTime of VolumeRestriction) + uint256 dailyLastTradedDayTime; + uint256 lastTradedTimestamp; // It is the timestamp at which last transaction get executed + } + + struct BucketData { + // Storing _from => day's timestamp => total amount transact in a day --individual + mapping(address => mapping(uint256 => uint256)) bucket; + // Storing _from => day's timestamp => total amount transact in a day --individual + mapping(address => mapping(uint256 => uint256)) defaultBucket; + // Storing the information that used to validate the transaction + mapping(address => BucketDetails) userToBucket; + // Storing the information related to default restriction + mapping(address => BucketDetails) defaultUserToBucket; + } + + BucketData bucketData; + + // Hold exempt index + struct Exemptions { + mapping(address => uint256) exemptIndex; + address[] exemptAddresses; + } + + Exemptions exemptions; + +} diff --git a/contracts/modules/TransferManager/VolumeRestrictionTMFactory.sol b/contracts/modules/TransferManager/VolumeRestrictionTMFactory.sol deleted file mode 100644 index 7d2e27cc0..000000000 --- a/contracts/modules/TransferManager/VolumeRestrictionTMFactory.sol +++ /dev/null @@ -1,75 +0,0 @@ -pragma solidity ^0.4.24; - -import "../../proxy/VolumeRestrictionTMProxy.sol"; -import "../ModuleFactory.sol"; - -/** - * @title Factory for deploying VolumeRestrictionTM module - */ -contract VolumeRestrictionTMFactory is ModuleFactory { - - address public logicContract; - - /** - * @notice Constructor - * @param _polyAddress Address of the polytoken - */ - constructor (address _polyAddress, uint256 _setupCost, uint256 _usageCost, uint256 _subscriptionCost, address _logicContract) public - ModuleFactory(_polyAddress, _setupCost, _usageCost, _subscriptionCost) - { - require(_logicContract != address(0), "Invalid address"); - version = "1.0.0"; - name = "VolumeRestrictionTM"; - title = "Volume Restriction Transfer Manager"; - description = "Manage transfers based on the volume of tokens that needs to be transact"; - compatibleSTVersionRange["lowerBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0)); - compatibleSTVersionRange["upperBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0)); - logicContract = _logicContract; - } - - - /** - * @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), "Insufficent Allowance"); - address volumeRestrictionTransferManager = new VolumeRestrictionTMProxy(msg.sender, address(polyToken), logicContract); - /*solium-disable-next-line security/no-block-members*/ - emit GenerateModuleFromFactory(volumeRestrictionTransferManager, getName(), address(this), msg.sender, setupCost, now); - return volumeRestrictionTransferManager; - } - - - /** - * @notice Type of the Module factory - */ - function getTypes() external view returns(uint8[]) { - uint8[] memory res = new uint8[](1); - res[0] = 2; - return res; - } - - /** - * @notice Returns the instructions associated with the module - */ - function getInstructions() external view returns(string) { - /*solium-disable-next-line max-len*/ - return "Module used to restrict the volume of tokens traded by the token holders"; - } - - /** - * @notice Get the tags related to the module factory - */ - function getTags() public view returns(bytes32[]) { - bytes32[] memory availableTags = new bytes32[](5); - availableTags[0] = "Maximum Volume"; - availableTags[1] = "Transfer Restriction"; - availableTags[2] = "Daily Restriction"; - availableTags[3] = "Individual Restriction"; - availableTags[4] = "Default Restriction"; - return availableTags; - } - -} \ No newline at end of file diff --git a/contracts/modules/UpgradableModuleFactory.sol b/contracts/modules/UpgradableModuleFactory.sol new file mode 100644 index 000000000..f09d467fd --- /dev/null +++ b/contracts/modules/UpgradableModuleFactory.sol @@ -0,0 +1,139 @@ +pragma solidity 0.5.8; + +import "./ModuleFactory.sol"; +import "../interfaces/IModuleRegistry.sol"; +import "../proxy/OwnedUpgradeabilityProxy.sol"; + + +/** + * @title Factory for deploying upgradable modules + */ +contract UpgradableModuleFactory is ModuleFactory { + + event LogicContractSet(string _version, uint256 _upgrade, address _logicContract, bytes _upgradeData); + + event ModuleUpgraded( + address indexed _module, + address indexed _securityToken, + uint256 indexed _version + ); + + struct LogicContract { + string version; + address logicContract; + bytes upgradeData; + } + + // Mapping from version to logic contract + mapping (uint256 => LogicContract) public logicContracts; + + // Mapping from Security Token address, to deployed proxy module address, to module version + mapping (address => mapping (address => uint256)) public modules; + + // Mapping of which security token owns a given module + mapping (address => address) public moduleToSecurityToken; + + // Current version + uint256 public latestUpgrade; + + /** + * @notice Constructor + * @param _setupCost Setup cost of the module + * @param _logicContract Contract address that contains the logic related to `description` + * @param _polymathRegistry Address of the Polymath registry + * @param _isCostInPoly true = cost in Poly, false = USD + */ + constructor( + string memory _version, + uint256 _setupCost, + address _logicContract, + address _polymathRegistry, + bool _isCostInPoly + ) + public ModuleFactory(_setupCost, _polymathRegistry, _isCostInPoly) + { + require(_logicContract != address(0), "Invalid address"); + logicContracts[latestUpgrade].logicContract = _logicContract; + logicContracts[latestUpgrade].version = _version; + } + + /** + * @notice Used to upgrade the module factory + * @param _version Version of upgraded module + * @param _logicContract Address of deployed module logic contract referenced from proxy + * @param _upgradeData Data to be passed in call to upgradeToAndCall when a token upgrades its module + */ + function setLogicContract(string calldata _version, address _logicContract, bytes calldata _upgradeData) external onlyOwner { + require(keccak256(abi.encodePacked(_version)) != keccak256(abi.encodePacked(logicContracts[latestUpgrade].version)), "Same version"); + require(_logicContract != logicContracts[latestUpgrade].logicContract, "Same version"); + require(_logicContract != address(0), "Invalid address"); + latestUpgrade++; + _modifyLogicContract(latestUpgrade, _version, _logicContract, _upgradeData); + } + + /** + * @notice Used to update an existing token logic contract + * @param _upgrade logic contract to upgrade + * @param _version Version of upgraded module + * @param _logicContract Address of deployed module logic contract referenced from proxy + * @param _upgradeData Data to be passed in call to upgradeToAndCall when a token upgrades its module + */ + function updateLogicContract(uint256 _upgrade, string calldata _version, address _logicContract, bytes calldata _upgradeData) external onlyOwner { + require(_upgrade <= latestUpgrade, "Invalid upgrade"); + // version & contract must differ from previous version, otherwise upgrade proxy will fail + if (_upgrade > 0) { + require(keccak256(abi.encodePacked(_version)) != keccak256(abi.encodePacked(logicContracts[_upgrade - 1].version)), "Same version"); + require(_logicContract != logicContracts[_upgrade - 1].logicContract, "Same version"); + } + require(_logicContract != address(0), "Invalid address"); + require(_upgradeData.length > 4, "Invalid Upgrade"); + _modifyLogicContract(_upgrade, _version, _logicContract, _upgradeData); + } + + function _modifyLogicContract(uint256 _upgrade, string memory _version, address _logicContract, bytes memory _upgradeData) internal { + logicContracts[_upgrade].version = _version; + logicContracts[_upgrade].logicContract = _logicContract; + logicContracts[_upgrade].upgradeData = _upgradeData; + IModuleRegistry moduleRegistry = IModuleRegistry(polymathRegistry.getAddress("ModuleRegistry")); + moduleRegistry.unverifyModule(address(this)); + emit LogicContractSet(_version, _upgrade, _logicContract, _upgradeData); + } + + /** + * @notice Used by a security token to upgrade a given module + * @param _module Address of (proxy) module to be upgraded + */ + function upgrade(address _module) external { + // Only allow the owner of a module to upgrade it + require(moduleToSecurityToken[_module] == msg.sender, "Incorrect caller"); + // Only allow issuers to upgrade in single step verisons to preserve upgradeToAndCall semantics + uint256 newVersion = modules[msg.sender][_module] + 1; + require(newVersion <= latestUpgrade, "Incorrect version"); + OwnedUpgradeabilityProxy(address(uint160(_module))).upgradeToAndCall(logicContracts[newVersion].version, logicContracts[newVersion].logicContract, logicContracts[newVersion].upgradeData); + modules[msg.sender][_module] = newVersion; + emit ModuleUpgraded( + _module, + msg.sender, + newVersion + ); + } + + /** + * @notice Used to initialize the module + * @param _module Address of module + * @param _data Data used for the intialization of the module factory variables + */ + function _initializeModule(address _module, bytes memory _data) internal { + super._initializeModule(_module, _data); + moduleToSecurityToken[_module] = msg.sender; + modules[msg.sender][_module] = latestUpgrade; + } + + /** + * @notice Get the version related to the module factory + */ + function version() external view returns(string memory) { + return logicContracts[latestUpgrade].version; + } + +} diff --git a/contracts/modules/Experimental/Wallet/VestingEscrowWallet.sol b/contracts/modules/Wallet/VestingEscrowWallet.sol similarity index 90% rename from contracts/modules/Experimental/Wallet/VestingEscrowWallet.sol rename to contracts/modules/Wallet/VestingEscrowWallet.sol index c53e1143f..3fe28d1ec 100644 --- a/contracts/modules/Experimental/Wallet/VestingEscrowWallet.sol +++ b/contracts/modules/Wallet/VestingEscrowWallet.sol @@ -1,17 +1,15 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; import "openzeppelin-solidity/contracts/math/SafeMath.sol"; -import "../../../storage/VestingEscrowWalletStorage.sol"; -import "./IWallet.sol"; +import "./Wallet.sol"; +import "./VestingEscrowWalletStorage.sol"; /** * @title Wallet for core vesting escrow functionality */ -contract VestingEscrowWallet is VestingEscrowWalletStorage, IWallet { +contract VestingEscrowWallet is VestingEscrowWalletStorage, Wallet { using SafeMath for uint256; - bytes32 public constant ADMIN = "ADMIN"; - // States used to represent the status of the schedule enum State {CREATED, STARTED, COMPLETED} @@ -59,7 +57,7 @@ contract VestingEscrowWallet is VestingEscrowWalletStorage, IWallet { * @notice This function returns the signature of the configure function */ function getInitFunction() public pure returns (bytes4) { - return bytes4(keccak256("configure(address)")); + return this.configure.selector; } /** @@ -67,16 +65,19 @@ contract VestingEscrowWallet is VestingEscrowWalletStorage, IWallet { * @param _treasuryWallet Address of the treasury wallet */ function configure(address _treasuryWallet) public onlyFactory { - require(_treasuryWallet != address(0), "Invalid address"); - treasuryWallet = _treasuryWallet; + _setWallet(_treasuryWallet); } /** * @notice Used to change the treasury wallet address * @param _newTreasuryWallet Address of the treasury wallet */ - function changeTreasuryWallet(address _newTreasuryWallet) public onlyOwner { - require(_newTreasuryWallet != address(0)); + function changeTreasuryWallet(address _newTreasuryWallet) public { + _onlySecurityTokenOwner(); + _setWallet(_newTreasuryWallet); + } + + function _setWallet(address _newTreasuryWallet) internal { emit TreasuryWalletChanged(_newTreasuryWallet, treasuryWallet); treasuryWallet = _newTreasuryWallet; } @@ -92,8 +93,8 @@ contract VestingEscrowWallet is VestingEscrowWalletStorage, IWallet { function _depositTokens(uint256 _numberOfTokens) internal { require(_numberOfTokens > 0, "Should be > 0"); require( - ISecurityToken(securityToken).transferFrom(msg.sender, address(this), _numberOfTokens), - "Failed transferFrom due to insufficent Allowance provided" + securityToken.transferFrom(msg.sender, address(this), _numberOfTokens), + "Failed transferFrom" ); unassignedTokens = unassignedTokens.add(_numberOfTokens); emit DepositTokens(_numberOfTokens, msg.sender); @@ -103,27 +104,38 @@ contract VestingEscrowWallet is VestingEscrowWalletStorage, IWallet { * @notice Sends unassigned tokens to the treasury wallet * @param _amount Amount of tokens that should be send to the treasury wallet */ - function sendToTreasury(uint256 _amount) external withPerm(ADMIN) { + function sendToTreasury(uint256 _amount) public withPerm(OPERATOR) { require(_amount > 0, "Amount cannot be zero"); require(_amount <= unassignedTokens, "Amount is greater than unassigned tokens"); - uint256 amount = unassignedTokens; - unassignedTokens = 0; - require(ISecurityToken(securityToken).transfer(treasuryWallet, amount), "Transfer failed"); - emit SendToTreasury(amount, msg.sender); + unassignedTokens = unassignedTokens - _amount; + require(securityToken.transfer(getTreasuryWallet(), _amount), "Transfer failed"); + emit SendToTreasury(_amount, msg.sender); + } + + /** + * @notice Returns the treasury wallet address + */ + function getTreasuryWallet() public view returns(address) { + if (treasuryWallet == address(0)) { + address wallet = IDataStore(getDataStore()).getAddress(TREASURY); + require(wallet != address(0), "Invalid address"); + return wallet; + } else + return treasuryWallet; } /** * @notice Pushes available tokens to the beneficiary's address * @param _beneficiary Address of the beneficiary who will receive tokens */ - function pushAvailableTokens(address _beneficiary) public withPerm(ADMIN) { + function pushAvailableTokens(address _beneficiary) public withPerm(OPERATOR) { _sendTokens(_beneficiary); } /** * @notice Used to withdraw available tokens by beneficiary */ - function pullAvailableTokens() external { + function pullAvailableTokens() external whenNotPaused { _sendTokens(msg.sender); } @@ -177,7 +189,7 @@ contract VestingEscrowWallet is VestingEscrowWalletStorage, IWallet { * @notice Gets the list of the template names those can be used for creating schedule * @return bytes32 Array of all template names were created */ - function getAllTemplateNames() external view returns(bytes32[]) { + function getAllTemplateNames() external view returns(bytes32[] memory) { return templateNames; } @@ -261,7 +273,7 @@ contract VestingEscrowWallet is VestingEscrowWalletStorage, IWallet { * @param _templateName Name of the template was used for schedule creation * @param _startTime Start time of the created vesting schedule */ - function modifySchedule(address _beneficiary, bytes32 _templateName, uint256 _startTime) public withPerm(ADMIN) { + function modifySchedule(address _beneficiary, bytes32 _templateName, uint256 _startTime) external withPerm(ADMIN) { _modifySchedule(_beneficiary, _templateName, _startTime); } @@ -377,7 +389,7 @@ contract VestingEscrowWallet is VestingEscrowWalletStorage, IWallet { * @param _beneficiary Address of the beneficiary * @return List of the template names that were used for schedule creation */ - function getTemplateNames(address _beneficiary) external view returns(bytes32[]) { + function getTemplateNames(address _beneficiary) external view returns(bytes32[] memory) { require(_beneficiary != address(0), "Invalid address"); return userToTemplates[_beneficiary]; } @@ -420,8 +432,8 @@ contract VestingEscrowWallet is VestingEscrowWalletStorage, IWallet { * @param _fromIndex Start index of array of beneficiary's addresses * @param _toIndex End index of array of beneficiary's addresses */ - function pushAvailableTokensMulti(uint256 _fromIndex, uint256 _toIndex) external withPerm(ADMIN) { - require(_toIndex <= beneficiaries.length - 1, "Array out of bound"); + function pushAvailableTokensMulti(uint256 _fromIndex, uint256 _toIndex) public withPerm(OPERATOR) { + require(_toIndex < beneficiaries.length, "Array out of bound"); for (uint256 i = _fromIndex; i <= _toIndex; i++) { if (schedules[beneficiaries[i]].length !=0) pushAvailableTokens(beneficiaries[i]); @@ -438,12 +450,12 @@ contract VestingEscrowWallet is VestingEscrowWalletStorage, IWallet { * @param _startTimes Array of the vesting start time */ function addScheduleMulti( - address[] _beneficiaries, - bytes32[] _templateNames, - uint256[] _numberOfTokens, - uint256[] _durations, - uint256[] _frequencies, - uint256[] _startTimes + address[] memory _beneficiaries, + bytes32[] memory _templateNames, + uint256[] memory _numberOfTokens, + uint256[] memory _durations, + uint256[] memory _frequencies, + uint256[] memory _startTimes ) public withPerm(ADMIN) @@ -467,7 +479,14 @@ contract VestingEscrowWallet is VestingEscrowWalletStorage, IWallet { * @param _templateNames Array of the template names were used for schedule creation * @param _startTimes Array of the vesting start time */ - function addScheduleFromTemplateMulti(address[] _beneficiaries, bytes32[] _templateNames, uint256[] _startTimes) external withPerm(ADMIN) { + function addScheduleFromTemplateMulti( + address[] memory _beneficiaries, + bytes32[] memory _templateNames, + uint256[] memory _startTimes + ) + public + withPerm(ADMIN) + { require(_beneficiaries.length == _templateNames.length && _beneficiaries.length == _startTimes.length, "Arrays sizes mismatch"); for (uint256 i = 0; i < _beneficiaries.length; i++) { _addScheduleFromTemplate(_beneficiaries[i], _templateNames[i], _startTimes[i]); @@ -478,7 +497,7 @@ contract VestingEscrowWallet is VestingEscrowWalletStorage, IWallet { * @notice Used to bulk revoke vesting schedules for each of the beneficiaries * @param _beneficiaries Array of the beneficiary's addresses */ - function revokeSchedulesMulti(address[] _beneficiaries) external withPerm(ADMIN) { + function revokeSchedulesMulti(address[] memory _beneficiaries) public withPerm(ADMIN) { for (uint256 i = 0; i < _beneficiaries.length; i++) { _revokeAllSchedules(_beneficiaries[i]); } @@ -491,9 +510,9 @@ contract VestingEscrowWallet is VestingEscrowWalletStorage, IWallet { * @param _startTimes Array of the vesting start time */ function modifyScheduleMulti( - address[] _beneficiaries, - bytes32[] _templateNames, - uint256[] _startTimes + address[] memory _beneficiaries, + bytes32[] memory _templateNames, + uint256[] memory _startTimes ) public withPerm(ADMIN) @@ -528,7 +547,7 @@ contract VestingEscrowWallet is VestingEscrowWalletStorage, IWallet { uint256 periodCount = _duration.div(_frequency); require(_numberOfTokens % periodCount == 0); uint256 amountPerPeriod = _numberOfTokens.div(periodCount); - require(amountPerPeriod % ISecurityToken(securityToken).granularity() == 0, "Invalid granularity"); + require(amountPerPeriod % securityToken.granularity() == 0, "Invalid granularity"); } function _sendTokens(address _beneficiary) internal { @@ -541,7 +560,7 @@ contract VestingEscrowWallet is VestingEscrowWalletStorage, IWallet { uint256 amount = _getAvailableTokens(_beneficiary, _index); if (amount > 0) { schedules[_beneficiary][_index].claimedTokens = schedules[_beneficiary][_index].claimedTokens.add(amount); - require(ISecurityToken(securityToken).transfer(_beneficiary, amount), "Transfer failed"); + require(securityToken.transfer(_beneficiary, amount), "Transfer failed"); emit SendTokens(_beneficiary, amount); } } @@ -549,9 +568,10 @@ contract VestingEscrowWallet is VestingEscrowWalletStorage, IWallet { /** * @notice Return the permissions flag that are associated with VestingEscrowWallet */ - function getPermissions() public view returns(bytes32[]) { - bytes32[] memory allPermissions = new bytes32[](1); + function getPermissions() public view returns(bytes32[] memory) { + bytes32[] memory allPermissions = new bytes32[](2); allPermissions[0] = ADMIN; + allPermissions[1] = OPERATOR; return allPermissions; } diff --git a/contracts/modules/Wallet/VestingEscrowWalletFactory.sol b/contracts/modules/Wallet/VestingEscrowWalletFactory.sol new file mode 100644 index 000000000..725de9e08 --- /dev/null +++ b/contracts/modules/Wallet/VestingEscrowWalletFactory.sol @@ -0,0 +1,45 @@ +pragma solidity 0.5.8; + +import "./VestingEscrowWalletProxy.sol"; +import "../UpgradableModuleFactory.sol"; + +/** + * @title Factory for deploying VestingEscrowWallet module + */ +contract VestingEscrowWalletFactory is UpgradableModuleFactory { + + /** + * @notice Constructor + */ + constructor ( + uint256 _setupCost, + address _logicContract, + address _polymathRegistry, + bool _isCostInPoly + ) + public + UpgradableModuleFactory("3.0.0", _setupCost, _logicContract, _polymathRegistry, _isCostInPoly) + { + name = "VestingEscrowWallet"; + title = "Vesting Escrow Wallet"; + description = "Manage vesting schedules to employees / affiliates"; + typesData.push(7); + tagsData.push("Vesting"); + tagsData.push("Escrow"); + tagsData.push("Transfer Restriction"); + compatibleSTVersionRange["lowerBound"] = VersionUtils.pack(uint8(3), uint8(0), uint8(0)); + compatibleSTVersionRange["upperBound"] = VersionUtils.pack(uint8(3), uint8(0), uint8(0)); + } + + /** + * @notice Used to launch the Module with the help of factory + * _data Data used for the intialization of the module factory variables + * @return address Contract address of the Module + */ + function deploy(bytes calldata _data) external returns(address) { + address vestingEscrowWallet = address(new VestingEscrowWalletProxy(logicContracts[latestUpgrade].version, msg.sender, polymathRegistry.getAddress("PolyToken"), logicContracts[latestUpgrade].logicContract)); + _initializeModule(vestingEscrowWallet, _data); + return vestingEscrowWallet; + } + +} diff --git a/contracts/proxy/VestingEscrowWalletProxy.sol b/contracts/modules/Wallet/VestingEscrowWalletProxy.sol similarity index 58% rename from contracts/proxy/VestingEscrowWalletProxy.sol rename to contracts/modules/Wallet/VestingEscrowWalletProxy.sol index 8f7be97ce..05b74c77d 100644 --- a/contracts/proxy/VestingEscrowWalletProxy.sol +++ b/contracts/modules/Wallet/VestingEscrowWalletProxy.sol @@ -1,20 +1,20 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; -import "../storage/VestingEscrowWalletStorage.sol"; -import "./OwnedProxy.sol"; -import "../Pausable.sol"; -import "../modules/ModuleStorage.sol"; +import "../../proxy/OwnedUpgradeabilityProxy.sol"; +import "./VestingEscrowWalletStorage.sol"; +import "../../Pausable.sol"; +import "../../storage/modules/ModuleStorage.sol"; /** * @title Escrow wallet module for vesting functionality */ -contract VestingEscrowWalletProxy is VestingEscrowWalletStorage, ModuleStorage, Pausable, OwnedProxy { +contract VestingEscrowWalletProxy is VestingEscrowWalletStorage, ModuleStorage, Pausable, OwnedUpgradeabilityProxy { /** * @notice Constructor * @param _securityToken Address of the security token * @param _polyAddress Address of the polytoken * @param _implementation representing the address of the new implementation to be set */ - constructor (address _securityToken, address _polyAddress, address _implementation) + constructor (string memory _version, address _securityToken, address _polyAddress, address _implementation) public ModuleStorage(_securityToken, _polyAddress) { @@ -22,6 +22,6 @@ contract VestingEscrowWalletProxy is VestingEscrowWalletStorage, ModuleStorage, _implementation != address(0), "Implementation address should not be 0x" ); - __implementation = _implementation; + _upgradeTo(_version, _implementation); } - } \ No newline at end of file + } diff --git a/contracts/storage/VestingEscrowWalletStorage.sol b/contracts/modules/Wallet/VestingEscrowWalletStorage.sol similarity index 97% rename from contracts/storage/VestingEscrowWalletStorage.sol rename to contracts/modules/Wallet/VestingEscrowWalletStorage.sol index af40d32bf..1c8b27a9f 100644 --- a/contracts/storage/VestingEscrowWalletStorage.sol +++ b/contracts/modules/Wallet/VestingEscrowWalletStorage.sol @@ -1,10 +1,10 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; /** * @title Wallet for core vesting escrow functionality */ contract VestingEscrowWalletStorage { - + struct Schedule { // Name of the template bytes32 templateName; @@ -38,7 +38,7 @@ contract VestingEscrowWalletStorage { mapping(address => Schedule[]) public schedules; // Holds template names array corresponds to the affiliate/employee address mapping(address => bytes32[]) internal userToTemplates; - // Mapping use to store the indexes for different template names for a user. + // Mapping use to store the indexes for different template names for a user. // affiliate/employee address => template name => index mapping(address => mapping(bytes32 => uint256)) internal userToTemplateIndex; // Holds affiliate/employee addresses coressponds to the template name @@ -51,4 +51,4 @@ contract VestingEscrowWalletStorage { // List of all template names bytes32[] public templateNames; -} \ No newline at end of file +} diff --git a/contracts/modules/Wallet/Wallet.sol b/contracts/modules/Wallet/Wallet.sol new file mode 100644 index 000000000..0b8b48002 --- /dev/null +++ b/contracts/modules/Wallet/Wallet.sol @@ -0,0 +1,11 @@ +pragma solidity 0.5.8; + +import "../Module.sol"; + +/** + * @title Interface to be implemented by all Wallet modules + * @dev abstract contract + */ +contract Wallet is Module { + +} diff --git a/contracts/oracles/MakerDAOOracle.sol b/contracts/oracles/MakerDAOOracle.sol index 51773cd7d..1db3d956b 100644 --- a/contracts/oracles/MakerDAOOracle.sol +++ b/contracts/oracles/MakerDAOOracle.sol @@ -1,12 +1,11 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; import "../interfaces/IOracle.sol"; import "../external/IMedianizer.sol"; import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; contract MakerDAOOracle is IOracle, Ownable { - - address public medianizer; + IMedianizer public medianizer; address public currencyAddress; bytes32 public currencySymbol; @@ -14,9 +13,9 @@ contract MakerDAOOracle is IOracle, Ownable { uint256 public manualPrice; /*solium-disable-next-line security/no-block-members*/ - event ChangeMedianizer(address _newMedianizer, address _oldMedianizer, uint256 _now); - event SetManualPrice(uint256 _oldPrice, uint256 _newPrice, uint256 _time); - event SetManualOverride(bool _override, uint256 _time); + event ChangeMedianizer(address _newMedianizer, address _oldMedianizer); + event SetManualPrice(uint256 _oldPrice, uint256 _newPrice); + event SetManualOverride(bool _override); /** * @notice Creates a new Maker based oracle @@ -24,8 +23,8 @@ contract MakerDAOOracle is IOracle, Ownable { * @param _currencyAddress Address of currency (0x0 for ETH) * @param _currencySymbol Symbol of currency */ - constructor (address _medianizer, address _currencyAddress, bytes32 _currencySymbol) public { - medianizer = _medianizer; + constructor(address _medianizer, address _currencyAddress, bytes32 _currencySymbol) public { + medianizer = IMedianizer(_medianizer); currencyAddress = _currencyAddress; currencySymbol = _currencySymbol; } @@ -37,8 +36,8 @@ contract MakerDAOOracle is IOracle, Ownable { function changeMedianier(address _medianizer) public onlyOwner { require(_medianizer != address(0), "0x not allowed"); /*solium-disable-next-line security/no-block-members*/ - emit ChangeMedianizer(_medianizer, medianizer, now); - medianizer = _medianizer; + emit ChangeMedianizer(_medianizer, address(medianizer)); + medianizer = IMedianizer(_medianizer); } /** @@ -66,11 +65,11 @@ contract MakerDAOOracle is IOracle, Ownable { /** * @notice Returns price - should throw if not valid */ - function getPrice() external view returns(uint256) { + function getPrice() external returns(uint256) { if (manualOverride) { return manualPrice; } - (bytes32 price, bool valid) = IMedianizer(medianizer).peek(); + (bytes32 price, bool valid) = medianizer.peek(); require(valid, "MakerDAO Oracle returning invalid value"); return uint256(price); } @@ -81,7 +80,7 @@ contract MakerDAOOracle is IOracle, Ownable { */ function setManualPrice(uint256 _price) public onlyOwner { /*solium-disable-next-line security/no-block-members*/ - emit SetManualPrice(manualPrice, _price, now); + emit SetManualPrice(manualPrice, _price); manualPrice = _price; } @@ -92,7 +91,7 @@ contract MakerDAOOracle is IOracle, Ownable { function setManualOverride(bool _override) public onlyOwner { manualOverride = _override; /*solium-disable-next-line security/no-block-members*/ - emit SetManualOverride(_override, now); + emit SetManualOverride(_override); } } diff --git a/contracts/oracles/PolyOracle.sol b/contracts/oracles/PolyOracle.sol index d46242929..367f19b0c 100644 --- a/contracts/oracles/PolyOracle.sol +++ b/contracts/oracles/PolyOracle.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; import "../external/oraclizeAPI.sol"; import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; @@ -11,38 +11,39 @@ contract PolyOracle is usingOraclize, IOracle, Ownable { /*solium-disable-next-line max-len*/ string public oracleURL = "[URL] json(https://pro-api.coinmarketcap.com/v1/cryptocurrency/quotes/latest?id=2496&convert=USD&CMC_PRO_API_KEY=${[decrypt] BCA0Bqxmn3jkSENepaHxQv09Z/vGdEO9apO+B9RplHyV3qOL/dw5Indlei3hoXrGk9G14My8MFpHJycB7UoVnl+4mlzEsjTlS2UBAYVrl0fAepfiSyM30/GMZAoJmDagY+0YyNZvpkgXn86Q/59Bi48PWEet}).data.\"2496\".quote.USD.price"; string public oracleQueryType = "nested"; - uint256 public sanityBounds = 20*10**16; + uint256 public sanityBounds = 20 * 10 ** 16; uint256 public gasLimit = 100000; uint256 public oraclizeTimeTolerance = 5 minutes; uint256 public staleTime = 6 hours; + // POLYUSD poly units = 1 * 10^18 USD units (1 USD) uint256 private POLYUSD; uint256 public latestUpdate; uint256 public latestScheduledUpdate; - mapping (bytes32 => uint256) public requestIds; - mapping (bytes32 => bool) public ignoreRequestIds; + mapping(bytes32 => uint256) public requestIds; + mapping(bytes32 => bool) public ignoreRequestIds; - mapping (address => bool) public admin; + mapping(address => bool) public admin; bool public freezeOracle; event PriceUpdated(uint256 _price, uint256 _oldPrice, bytes32 _queryId, uint256 _time); event NewOraclizeQuery(uint256 _time, bytes32 _queryId, string _query); - event AdminSet(address _admin, bool _valid, uint256 _time); + event AdminSet(address _admin, bool _valid); event StalePriceUpdate(bytes32 _queryId, uint256 _time, string _result); - modifier isAdminOrOwner { - require(admin[msg.sender] || msg.sender == owner, "Address is not admin or owner"); + modifier isAdminOrOwner() { + require(admin[msg.sender] || msg.sender == owner(), "Address is not admin or owner"); _; } /** * @notice Constructor - accepts ETH to initialise a balance for subsequent Oraclize queries */ - constructor() payable public { + constructor() public payable { // Use 50 gwei for now - oraclize_setCustomGasPrice(50*10**9); + oraclize_setCustomGasPrice(50 * 10 ** 9); } /** @@ -50,7 +51,7 @@ contract PolyOracle is usingOraclize, IOracle, Ownable { * @param _requestId requestId corresponding to Oraclize query * @param _result data returned by Oraclize URL query */ - function __callback(bytes32 _requestId, string _result) public { + function __callback(bytes32 _requestId, string memory _result) public { require(msg.sender == oraclize_cbAddress(), "Only Oraclize can access this method"); require(!freezeOracle, "Oracle is frozen"); require(!ignoreRequestIds[_requestId], "Ignoring requestId"); @@ -63,7 +64,7 @@ contract PolyOracle is usingOraclize, IOracle, Ownable { /*solium-disable-next-line security/no-block-members*/ require(requestIds[_requestId] <= now + oraclizeTimeTolerance, "Result is early"); uint256 newPOLYUSD = parseInt(_result, 18); - uint256 bound = POLYUSD.mul(sanityBounds).div(10**18); + uint256 bound = POLYUSD.mul(sanityBounds).div(10 ** 18); if (latestUpdate != 0) { require(newPOLYUSD <= POLYUSD.add(bound), "Result is too large"); require(newPOLYUSD >= POLYUSD.sub(bound), "Result is too small"); @@ -77,7 +78,7 @@ contract PolyOracle is usingOraclize, IOracle, Ownable { * @notice Allows owner to schedule future Oraclize calls * @param _times UNIX timestamps to schedule Oraclize calls as of. Empty list means trigger an immediate query. */ - function schedulePriceUpdatesFixed(uint256[] _times) public payable isAdminOrOwner { + function schedulePriceUpdatesFixed(uint256[] memory _times) public payable isAdminOrOwner { bytes32 requestId; uint256 maximumScheduledUpdated; if (_times.length == 0) { @@ -155,7 +156,7 @@ contract PolyOracle is usingOraclize, IOracle, Ownable { * @notice Allows owner to set URL used in Oraclize queries * @param _oracleURL URL to use */ - function setOracleURL(string _oracleURL) public onlyOwner { + function setOracleURL(string memory _oracleURL) public onlyOwner { oracleURL = _oracleURL; } @@ -163,7 +164,7 @@ contract PolyOracle is usingOraclize, IOracle, Ownable { * @notice Allows owner to set type used in Oraclize queries * @param _oracleQueryType to use */ - function setOracleQueryType(string _oracleQueryType) public onlyOwner { + function setOracleQueryType(string memory _oracleQueryType) public onlyOwner { oracleQueryType = _oracleQueryType; } @@ -215,7 +216,7 @@ contract PolyOracle is usingOraclize, IOracle, Ownable { * @param _requestIds Oraclize queryIds (as logged out when Oraclize query is scheduled) * @param _ignore whether or not they should be ignored */ - function setIgnoreRequestIds(bytes32[] _requestIds, bool[] _ignore) public onlyOwner { + function setIgnoreRequestIds(bytes32[] memory _requestIds, bool[] memory _ignore) public onlyOwner { require(_requestIds.length == _ignore.length, "Incorrect parameter lengths"); for (uint256 i = 0; i < _requestIds.length; i++) { ignoreRequestIds[_requestIds[i]] = _ignore[i]; @@ -230,7 +231,7 @@ contract PolyOracle is usingOraclize, IOracle, Ownable { function setAdmin(address _admin, bool _valid) public onlyOwner { admin[_admin] = _valid; /*solium-disable-next-line security/no-block-members*/ - emit AdminSet(_admin, _valid, now); + emit AdminSet(_admin, _valid); } /** @@ -265,7 +266,7 @@ contract PolyOracle is usingOraclize, IOracle, Ownable { /** * @notice Returns price - should throw if not valid */ - function getPrice() external view returns(uint256) { + function getPrice() external returns(uint256) { /*solium-disable-next-line security/no-block-members*/ require(latestUpdate >= now - staleTime, "Invalid price"); return POLYUSD; diff --git a/contracts/oracles/StableOracle.sol b/contracts/oracles/StableOracle.sol new file mode 100644 index 000000000..aaa8014e5 --- /dev/null +++ b/contracts/oracles/StableOracle.sol @@ -0,0 +1,113 @@ +pragma solidity 0.5.8; + +import "../interfaces/IOracle.sol"; +import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; +import "openzeppelin-solidity/contracts/math/SafeMath.sol"; + +contract StableOracle is IOracle, Ownable { + using SafeMath for uint256; + + IOracle public oracle; + uint256 public lastPrice; + uint256 public evictPercentage; //% multiplid by 10**16 + + bool public manualOverride; + uint256 public manualPrice; + + /*solium-disable-next-line security/no-block-members*/ + event ChangeOracle(address _oldOracle, address _newOracle); + event ChangeEvictPercentage(uint256 _oldEvictPercentage, uint256 _newEvictPercentage); + event SetManualPrice(uint256 _oldPrice, uint256 _newPrice); + event SetManualOverride(bool _override); + + /** + * @notice Creates a new stable oracle based on existing oracle + * @param _oracle address of underlying oracle + */ + constructor(address _oracle, uint256 _evictPercentage) public { + require(_oracle != address(0), "Invalid oracle"); + oracle = IOracle(_oracle); + evictPercentage = _evictPercentage; + } + + /** + * @notice Updates medianizer address + * @param _oracle Address of underlying oracle + */ + function changeOracle(address _oracle) public onlyOwner { + require(_oracle != address(0), "Invalid oracle"); + /*solium-disable-next-line security/no-block-members*/ + emit ChangeOracle(address(oracle), _oracle); + oracle = IOracle(_oracle); + } + + /** + * @notice Updates eviction percentage + * @param _evictPercentage Percentage multiplied by 10**16 + */ + function changeEvictPercentage(uint256 _evictPercentage) public onlyOwner { + emit ChangeEvictPercentage(evictPercentage, _evictPercentage); + evictPercentage = _evictPercentage; + } + + /** + * @notice Returns address of oracle currency (0x0 for ETH) + */ + function getCurrencyAddress() external view returns(address) { + return oracle.getCurrencyAddress(); + } + + /** + * @notice Returns symbol of oracle currency (0x0 for ETH) + */ + function getCurrencySymbol() external view returns(bytes32) { + return oracle.getCurrencySymbol(); + } + + /** + * @notice Returns denomination of price + */ + function getCurrencyDenominated() external view returns(bytes32) { + return oracle.getCurrencyDenominated(); + } + + /** + * @notice Returns price - should throw if not valid + */ + function getPrice() external returns(uint256) { + if (manualOverride) { + return manualPrice; + } + uint256 currentPrice = oracle.getPrice(); + if ((lastPrice == 0) || (_change(currentPrice, lastPrice) >= evictPercentage)) { + lastPrice = currentPrice; + } + return lastPrice; + } + + function _change(uint256 _newPrice, uint256 _oldPrice) internal pure returns(uint256) { + uint256 diff = _newPrice > _oldPrice ? _newPrice.sub(_oldPrice) : _oldPrice.sub(_newPrice); + return diff.mul(10**18).div(_oldPrice); + } + + /** + * @notice Set a manual price. NA - this will only be used if manualOverride == true + * @param _price Price to set + */ + function setManualPrice(uint256 _price) public onlyOwner { + /*solium-disable-next-line security/no-block-members*/ + emit SetManualPrice(manualPrice, _price); + manualPrice = _price; + } + + /** + * @notice Determine whether manual price is used or not + * @param _override Whether to use the manual override price or not + */ + function setManualOverride(bool _override) public onlyOwner { + manualOverride = _override; + /*solium-disable-next-line security/no-block-members*/ + emit SetManualOverride(_override); + } + +} diff --git a/contracts/proxy/ERC20DividendCheckpointProxy.sol b/contracts/proxy/ERC20DividendCheckpointProxy.sol deleted file mode 100644 index 8839d30e1..000000000 --- a/contracts/proxy/ERC20DividendCheckpointProxy.sol +++ /dev/null @@ -1,31 +0,0 @@ -pragma solidity ^0.4.24; - -import "../modules/Checkpoint/ERC20DividendCheckpointStorage.sol"; -import "../modules/Checkpoint/DividendCheckpointStorage.sol"; -import "./OwnedProxy.sol"; -import "../Pausable.sol"; -import "../modules/ModuleStorage.sol"; - -/** - * @title Transfer Manager module for core transfer validation functionality - */ -contract ERC20DividendCheckpointProxy is ERC20DividendCheckpointStorage, DividendCheckpointStorage, ModuleStorage, Pausable, OwnedProxy { - - /** - * @notice Constructor - * @param _securityToken Address of the security token - * @param _polyAddress Address of the polytoken - * @param _implementation representing the address of the new implementation to be set - */ - constructor (address _securityToken, address _polyAddress, address _implementation) - public - ModuleStorage(_securityToken, _polyAddress) - { - require( - _implementation != address(0), - "Implementation address should not be 0x" - ); - __implementation = _implementation; - } - -} diff --git a/contracts/proxy/EtherDividendCheckpointProxy.sol b/contracts/proxy/EtherDividendCheckpointProxy.sol deleted file mode 100644 index 40b1c1332..000000000 --- a/contracts/proxy/EtherDividendCheckpointProxy.sol +++ /dev/null @@ -1,30 +0,0 @@ -pragma solidity ^0.4.24; - -import "../modules/Checkpoint/DividendCheckpointStorage.sol"; -import "./OwnedProxy.sol"; -import "../Pausable.sol"; -import "../modules/ModuleStorage.sol"; - -/** - * @title Transfer Manager module for core transfer validation functionality - */ -contract EtherDividendCheckpointProxy is DividendCheckpointStorage, ModuleStorage, Pausable, OwnedProxy { - - /** - * @notice Constructor - * @param _securityToken Address of the security token - * @param _polyAddress Address of the polytoken - * @param _implementation representing the address of the new implementation to be set - */ - constructor (address _securityToken, address _polyAddress, address _implementation) - public - ModuleStorage(_securityToken, _polyAddress) - { - require( - _implementation != address(0), - "Implementation address should not be 0x" - ); - __implementation = _implementation; - } - -} diff --git a/contracts/proxy/GeneralTransferManagerProxy.sol b/contracts/proxy/GeneralTransferManagerProxy.sol deleted file mode 100644 index 0fbaa7880..000000000 --- a/contracts/proxy/GeneralTransferManagerProxy.sol +++ /dev/null @@ -1,30 +0,0 @@ -pragma solidity ^0.4.24; - -import "../storage/GeneralTransferManagerStorage.sol"; -import "./OwnedProxy.sol"; -import "../Pausable.sol"; -import "../modules/ModuleStorage.sol"; - -/** - * @title Transfer Manager module for core transfer validation functionality - */ -contract GeneralTransferManagerProxy is GeneralTransferManagerStorage, ModuleStorage, Pausable, OwnedProxy { - - /** - * @notice Constructor - * @param _securityToken Address of the security token - * @param _polyAddress Address of the polytoken - * @param _implementation representing the address of the new implementation to be set - */ - constructor (address _securityToken, address _polyAddress, address _implementation) - public - ModuleStorage(_securityToken, _polyAddress) - { - require( - _implementation != address(0), - "Implementation address should not be 0x" - ); - __implementation = _implementation; - } - -} diff --git a/contracts/proxy/ModuleRegistryProxy.sol b/contracts/proxy/ModuleRegistryProxy.sol index aea5a70c5..9c286993f 100644 --- a/contracts/proxy/ModuleRegistryProxy.sol +++ b/contracts/proxy/ModuleRegistryProxy.sol @@ -1,9 +1,8 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; import "../storage/EternalStorage.sol"; import "./OwnedUpgradeabilityProxy.sol"; - /** * @title ModuleRegistryProxy * @dev This proxy holds the storage of the ModuleRegistry contract and delegates every call to the current implementation set. @@ -13,4 +12,4 @@ import "./OwnedUpgradeabilityProxy.sol"; /*solium-disable-next-line no-empty-blocks*/ contract ModuleRegistryProxy is EternalStorage, OwnedUpgradeabilityProxy { -} \ No newline at end of file +} diff --git a/contracts/proxy/OwnedProxy.sol b/contracts/proxy/OwnedProxy.sol deleted file mode 100644 index b75142fbe..000000000 --- a/contracts/proxy/OwnedProxy.sol +++ /dev/null @@ -1,91 +0,0 @@ -pragma solidity ^0.4.18; - -import "./Proxy.sol"; - -/** - * @title OwnedProxy - * @dev This contract combines an upgradeability proxy with basic authorization control functionalities - */ -contract OwnedProxy is Proxy { - - // Owner of the contract - address private __owner; - - // Address of the current implementation - address internal __implementation; - - /** - * @dev Event to show ownership has been transferred - * @param _previousOwner representing the address of the previous owner - * @param _newOwner representing the address of the new owner - */ - event ProxyOwnershipTransferred(address _previousOwner, address _newOwner); - - /** - * @dev Throws if called by any account other than the owner. - */ - modifier ifOwner() { - if (msg.sender == _owner()) { - _; - } else { - _fallback(); - } - } - - /** - * @dev the constructor sets the original owner of the contract to the sender account. - */ - constructor() public { - _setOwner(msg.sender); - } - - /** - * @dev Tells the address of the owner - * @return the address of the owner - */ - function _owner() internal view returns (address) { - return __owner; - } - - /** - * @dev Sets the address of the owner - */ - function _setOwner(address _newOwner) internal { - require(_newOwner != address(0), "Address should not be 0x"); - __owner = _newOwner; - } - - /** - * @notice Internal function to provide the address of the implementation contract - */ - function _implementation() internal view returns (address) { - return __implementation; - } - - /** - * @dev Tells the address of the proxy owner - * @return the address of the proxy owner - */ - function proxyOwner() external ifOwner returns (address) { - return _owner(); - } - - /** - * @dev Tells the address of the current implementation - * @return address of the current implementation - */ - function implementation() external ifOwner returns (address) { - return _implementation(); - } - - /** - * @dev Allows the current owner to transfer control of the contract to a newOwner. - * @param _newOwner The address to transfer ownership to. - */ - function transferProxyOwnership(address _newOwner) external ifOwner { - require(_newOwner != address(0), "Address should not be 0x"); - emit ProxyOwnershipTransferred(_owner(), _newOwner); - _setOwner(_newOwner); - } - -} diff --git a/contracts/proxy/OwnedUpgradeabilityProxy.sol b/contracts/proxy/OwnedUpgradeabilityProxy.sol index 7ec520630..7e3b837f2 100644 --- a/contracts/proxy/OwnedUpgradeabilityProxy.sol +++ b/contracts/proxy/OwnedUpgradeabilityProxy.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.4.18; +pragma solidity 0.5.8; import "./UpgradeabilityProxy.sol"; @@ -7,7 +7,6 @@ import "./UpgradeabilityProxy.sol"; * @dev This contract combines an upgradeability proxy with basic authorization control functionalities */ contract OwnedUpgradeabilityProxy is UpgradeabilityProxy { - // Owner of the contract address private __upgradeabilityOwner; @@ -40,7 +39,7 @@ contract OwnedUpgradeabilityProxy is UpgradeabilityProxy { * @dev Tells the address of the owner * @return the address of the owner */ - function _upgradeabilityOwner() internal view returns (address) { + function _upgradeabilityOwner() internal view returns(address) { return __upgradeabilityOwner; } @@ -55,7 +54,7 @@ contract OwnedUpgradeabilityProxy is UpgradeabilityProxy { /** * @notice Internal function to provide the address of the implementation contract */ - function _implementation() internal view returns (address) { + function _implementation() internal view returns(address) { return __implementation; } @@ -63,7 +62,7 @@ contract OwnedUpgradeabilityProxy is UpgradeabilityProxy { * @dev Tells the address of the proxy owner * @return the address of the proxy owner */ - function proxyOwner() external ifOwner returns (address) { + function proxyOwner() external ifOwner returns(address) { return _upgradeabilityOwner(); } @@ -71,7 +70,7 @@ contract OwnedUpgradeabilityProxy is UpgradeabilityProxy { * @dev Tells the version name of the current implementation * @return string representing the name of the current version */ - function version() external ifOwner returns (string) { + function version() external ifOwner returns(string memory) { return __version; } @@ -79,7 +78,7 @@ contract OwnedUpgradeabilityProxy is UpgradeabilityProxy { * @dev Tells the address of the current implementation * @return address of the current implementation */ - function implementation() external ifOwner returns (address) { + function implementation() external ifOwner returns(address) { return _implementation(); } @@ -98,7 +97,7 @@ contract OwnedUpgradeabilityProxy is UpgradeabilityProxy { * @param _newVersion representing the version name of the new implementation to be set. * @param _newImplementation representing the address of the new implementation to be set. */ - function upgradeTo(string _newVersion, address _newImplementation) external ifOwner { + function upgradeTo(string calldata _newVersion, address _newImplementation) external ifOwner { _upgradeTo(_newVersion, _newImplementation); } @@ -110,10 +109,16 @@ contract OwnedUpgradeabilityProxy is UpgradeabilityProxy { * @param _data represents the msg.data to bet sent in the low level call. This parameter may include the function * signature of the implementation to be called with the needed payload */ - function upgradeToAndCall(string _newVersion, address _newImplementation, bytes _data) external payable ifOwner { + function upgradeToAndCall(string calldata _newVersion, address _newImplementation, bytes calldata _data) external payable ifOwner { + _upgradeToAndCall(_newVersion, _newImplementation, _data); + } + + function _upgradeToAndCall(string memory _newVersion, address _newImplementation, bytes memory _data) internal { _upgradeTo(_newVersion, _newImplementation); + bool success; /*solium-disable-next-line security/no-call-value*/ - require(address(this).call.value(msg.value)(_data), "Fail in executing the function of implementation contract"); + (success, ) = address(this).call.value(msg.value)(_data); + require(success, "Fail in executing the function of implementation contract"); } } diff --git a/contracts/proxy/Proxy.sol b/contracts/proxy/Proxy.sol index 6cfbce44f..aa19442ca 100644 --- a/contracts/proxy/Proxy.sol +++ b/contracts/proxy/Proxy.sol @@ -1,16 +1,15 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; /** * @title Proxy * @dev Gives the possibility to delegate any call to a foreign implementation. */ contract Proxy { - /** * @dev Tells the address of the implementation where every call will be delegated. * @return address of the implementation to which it will be delegated */ - function _implementation() internal view returns (address); + function _implementation() internal view returns(address); /** * @dev Fallback function. @@ -31,14 +30,11 @@ contract Proxy { // block because it will not return to Solidity code. We overwrite the // Solidity scratch pad at memory position 0. calldatacopy(0, 0, calldatasize) - // Call the implementation. // out and outsize are 0 because we don't know the size yet. let result := delegatecall(gas, implementation, 0, calldatasize, 0, 0) - // Copy the returned data. returndatacopy(0, 0, returndatasize) - switch result // delegatecall returns 0 on error. case 0 { revert(0, returndatasize) } @@ -46,7 +42,7 @@ contract Proxy { } } - function () public payable { + function() external payable { _fallback(); } -} \ No newline at end of file +} diff --git a/contracts/proxy/SecurityTokenRegistryProxy.sol b/contracts/proxy/SecurityTokenRegistryProxy.sol index 6acc6e38f..4a30439e8 100644 --- a/contracts/proxy/SecurityTokenRegistryProxy.sol +++ b/contracts/proxy/SecurityTokenRegistryProxy.sol @@ -1,9 +1,8 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; import "../storage/EternalStorage.sol"; import "./OwnedUpgradeabilityProxy.sol"; - /** * @title SecurityTokenRegistryProxy * @dev This proxy holds the storage of the SecurityTokenRegistry contract and delegates every call to the current implementation set. @@ -13,4 +12,4 @@ import "./OwnedUpgradeabilityProxy.sol"; /*solium-disable-next-line no-empty-blocks*/ contract SecurityTokenRegistryProxy is EternalStorage, OwnedUpgradeabilityProxy { -} \ No newline at end of file +} diff --git a/contracts/proxy/USDTieredSTOProxy.sol b/contracts/proxy/USDTieredSTOProxy.sol deleted file mode 100644 index 050e14a21..000000000 --- a/contracts/proxy/USDTieredSTOProxy.sol +++ /dev/null @@ -1,32 +0,0 @@ -pragma solidity ^0.4.24; - -import "../storage/USDTieredSTOStorage.sol"; -import "./OwnedProxy.sol"; -import "../Pausable.sol"; -import "openzeppelin-solidity/contracts/ReentrancyGuard.sol"; -import "../modules/STO/STOStorage.sol"; -import "../modules/ModuleStorage.sol"; - -/** - * @title USDTiered STO module Proxy - */ -contract USDTieredSTOProxy is USDTieredSTOStorage, STOStorage, ModuleStorage, Pausable, ReentrancyGuard, OwnedProxy { - - /** - * @notice Constructor - * @param _securityToken Address of the security token - * @param _polyAddress Address of the polytoken - * @param _implementation representing the address of the new implementation to be set - */ - constructor (address _securityToken, address _polyAddress, address _implementation) - public - ModuleStorage(_securityToken, _polyAddress) - { - require( - _implementation != address(0), - "Implementation address should not be 0x" - ); - __implementation = _implementation; - } - -} diff --git a/contracts/proxy/UpgradeabilityProxy.sol b/contracts/proxy/UpgradeabilityProxy.sol index 0f7806b71..59949f90d 100644 --- a/contracts/proxy/UpgradeabilityProxy.sol +++ b/contracts/proxy/UpgradeabilityProxy.sol @@ -1,14 +1,13 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; import "./Proxy.sol"; -import "openzeppelin-solidity/contracts/AddressUtils.sol"; +import "openzeppelin-solidity/contracts/utils/Address.sol"; /** * @title UpgradeabilityProxy * @dev This contract represents a proxy where the implementation address to which it will delegate can be upgraded */ contract UpgradeabilityProxy is Proxy { - // Version name of the current implementation string internal __version; @@ -27,12 +26,12 @@ contract UpgradeabilityProxy is Proxy { * @param _newVersion representing the version name of the new implementation to be set * @param _newImplementation representing the address of the new implementation to be set */ - function _upgradeTo(string _newVersion, address _newImplementation) internal { + function _upgradeTo(string memory _newVersion, address _newImplementation) internal { require( __implementation != _newImplementation && _newImplementation != address(0), "Old address is not allowed and implementation address should not be 0x" ); - require(AddressUtils.isContract(_newImplementation), "Cannot set a proxy implementation to a non-contract address"); + require(Address.isContract(_newImplementation), "Cannot set a proxy implementation to a non-contract address"); require(bytes(_newVersion).length > 0, "Version should not be empty string"); require(keccak256(abi.encodePacked(__version)) != keccak256(abi.encodePacked(_newVersion)), "New version equals to current"); __version = _newVersion; @@ -40,4 +39,4 @@ contract UpgradeabilityProxy is Proxy { emit Upgraded(_newVersion, _newImplementation); } -} \ No newline at end of file +} diff --git a/contracts/storage/EternalStorage.sol b/contracts/storage/EternalStorage.sol index a3f76203b..e7ff6acb5 100644 --- a/contracts/storage/EternalStorage.sol +++ b/contracts/storage/EternalStorage.sol @@ -1,7 +1,6 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; contract EternalStorage { - /// @notice Internal mappings used to store all kinds on data into the contract mapping(bytes32 => uint256) internal uintStorage; mapping(bytes32 => string) internal stringStorage; @@ -53,10 +52,14 @@ contract EternalStorage { bytes32Storage[_key] = _value; } - function set(bytes32 _key, string _value) internal { + function set(bytes32 _key, string memory _value) internal { stringStorage[_key] = _value; } + function set(bytes32 _key, bytes memory _value) internal { + bytesStorage[_key] = _value; + } + //////////////////////////// // deleteArray functions //////////////////////////// @@ -66,7 +69,6 @@ contract EternalStorage { /// in this case we have the helper function deleteArrayBytes32() which will do it for us /// deleteArrayBytes32(keccak256(abi.encodePacked("tokensOwnedByOwner", 0x1), 3); -- it will delete the index 3 - //Deletes from mapping (bytes32 => array[]) at index _index function deleteArrayAddress(bytes32 _key, uint256 _index) internal { address[] storage array = addressArrayStorage[_key]; @@ -118,7 +120,7 @@ contract EternalStorage { bytes32ArrayStorage[_key].push(_value); } - function pushArray(bytes32 _key, string _value) internal { + function pushArray(bytes32 _key, string memory _value) internal { stringArrayStorage[_key].push(_value); } @@ -132,21 +134,21 @@ contract EternalStorage { /// @notice used to intialize the array /// Ex1- mapping (address => address[]) public reputation; /// reputation[0x1] = new address[](0); It can be replaced as - /// setArray(hash('reputation', 0x1), new address[](0)); - - function setArray(bytes32 _key, address[] _value) internal { + /// setArray(hash('reputation', 0x1), new address[](0)); + + function setArray(bytes32 _key, address[] memory _value) internal { addressArrayStorage[_key] = _value; } - function setArray(bytes32 _key, uint256[] _value) internal { + function setArray(bytes32 _key, uint256[] memory _value) internal { uintArrayStorage[_key] = _value; } - function setArray(bytes32 _key, bytes32[] _value) internal { + function setArray(bytes32 _key, bytes32[] memory _value) internal { bytes32ArrayStorage[_key] = _value; } - function setArray(bytes32 _key, string[] _value) internal { + function setArray(bytes32 _key, string[] memory _value) internal { stringArrayStorage[_key] = _value; } @@ -159,15 +161,15 @@ contract EternalStorage { /// Ex2- uint256 _len = tokensOwnedByOwner[0x1].length; replace with /// getArrayBytes32(keccak256(abi.encodePacked("tokensOwnedByOwner", 0x1)).length; - function getArrayAddress(bytes32 _key) public view returns(address[]) { + function getArrayAddress(bytes32 _key) public view returns(address[] memory) { return addressArrayStorage[_key]; } - function getArrayBytes32(bytes32 _key) public view returns(bytes32[]) { + function getArrayBytes32(bytes32 _key) public view returns(bytes32[] memory) { return bytes32ArrayStorage[_key]; } - function getArrayUint(bytes32 _key) public view returns(uint[]) { + function getArrayUint(bytes32 _key) public view returns(uint[] memory) { return uintArrayStorage[_key]; } @@ -176,8 +178,8 @@ contract EternalStorage { /////////////////////////////////// /// @notice set the value of particular index of the address array /// Ex1- mapping(bytes32 => address[]) moduleList; - /// general way is -- moduleList[moduleType][index] = temp; - /// It can be re-write as -- setArrayIndexValue(keccak256(abi.encodePacked('moduleList', moduleType)), index, temp); + /// general way is -- moduleList[moduleType][index] = temp; + /// It can be re-write as -- setArrayIndexValue(keccak256(abi.encodePacked('moduleList', moduleType)), index, temp); function setArrayIndexValue(bytes32 _key, uint256 _index, address _value) internal { addressArrayStorage[_key][_index] = _value; @@ -191,13 +193,12 @@ contract EternalStorage { bytes32ArrayStorage[_key][_index] = _value; } - function setArrayIndexValue(bytes32 _key, uint256 _index, string _value) internal { + function setArrayIndexValue(bytes32 _key, uint256 _index, string memory _value) internal { stringArrayStorage[_key][_index] = _value; } /// Public getters functions - //////////////////// - /// @notice Get function use to get the value of the singleton state variables + /////////////////////// @notice Get function use to get the value of the singleton state variables /// Ex1- string public version = "0.0.1"; /// string _version = getString(keccak256(abi.encodePacked("version")); /// Ex2 - assert(temp1 == temp2); replace to @@ -214,7 +215,7 @@ contract EternalStorage { return boolStorage[_variable]; } - function getStringValue(bytes32 _variable) public view returns(string) { + function getStringValue(bytes32 _variable) public view returns(string memory) { return stringStorage[_variable]; } @@ -226,7 +227,7 @@ contract EternalStorage { return bytes32Storage[_variable]; } - function getBytesValue(bytes32 _variable) public view returns(bytes) { + function getBytesValue(bytes32 _variable) public view returns(bytes memory) { return bytesStorage[_variable]; } diff --git a/contracts/storage/GeneralTransferManagerStorage.sol b/contracts/storage/GeneralTransferManagerStorage.sol deleted file mode 100644 index 87f886ae7..000000000 --- a/contracts/storage/GeneralTransferManagerStorage.sol +++ /dev/null @@ -1,55 +0,0 @@ -pragma solidity ^0.4.24; - -/** - * @title Transfer Manager module for core transfer validation functionality - */ -contract GeneralTransferManagerStorage { - - //Address from which issuances come - address public issuanceAddress = address(0); - - //Address which can sign whitelist changes - address public signingAddress = address(0); - - bytes32 public constant WHITELIST = "WHITELIST"; - bytes32 public constant FLAGS = "FLAGS"; - - //from and to timestamps that an investor can send / receive tokens respectively - struct TimeRestriction { - //the moment when the sale lockup period ends and the investor can freely sell or transfer away their tokens - uint64 canSendAfter; - //the moment when the purchase lockup period ends and the investor can freely purchase or receive from others - uint64 canReceiveAfter; - uint64 expiryTime; - uint8 canBuyFromSTO; - uint8 added; - } - - // Allows all TimeRestrictions to be offset - struct Defaults { - uint64 canSendAfter; - uint64 canReceiveAfter; - } - - // Offset to be applied to all timings (except KYC expiry) - Defaults public defaults; - - // List of all addresses that have been added to the GTM at some point - address[] public investors; - - // An address can only send / receive tokens once their corresponding uint256 > block.number - // (unless allowAllTransfers == true or allowAllWhitelistTransfers == true) - mapping (address => TimeRestriction) public whitelist; - // Map of used nonces by customer - mapping(address => mapping(uint256 => bool)) public nonceMap; - - //If true, there are no transfer restrictions, for any addresses - bool public allowAllTransfers = false; - //If true, time lock is ignored for transfers (address must still be on whitelist) - bool public allowAllWhitelistTransfers = false; - //If true, time lock is ignored for issuances (address must still be on whitelist) - bool public allowAllWhitelistIssuances = true; - //If true, time lock is ignored for burn transactions - bool public allowAllBurnTransfers = false; - -} diff --git a/contracts/storage/VolumeRestrictionTMStorage.sol b/contracts/storage/VolumeRestrictionTMStorage.sol deleted file mode 100644 index 578bc8838..000000000 --- a/contracts/storage/VolumeRestrictionTMStorage.sol +++ /dev/null @@ -1,67 +0,0 @@ -pragma solidity ^0.4.24; - -/** - * @title Storage layout for VolumeRestrictionTM - */ -contract VolumeRestrictionTMStorage { - - enum RestrictionType { Fixed, Percentage } - - enum TypeOfPeriod { MultipleDays, OneDay, Both } - - struct RestrictedHolder { - // 1 represent true & 0 for false - uint8 seen; - // Type of period will be enum index of TypeOfPeriod enum - TypeOfPeriod typeOfPeriod; - // Index of the array where the holder address lives - uint128 index; - } - - struct RestrictedData { - mapping(address => RestrictedHolder) restrictedHolders; - address[] restrictedAddresses; - } - - struct VolumeRestriction { - // If typeOfRestriction is `Percentage` then allowedTokens will be in - // the % (w.r.t to totalSupply) with a multiplier of 10**16 . else it - // will be fixed amount of tokens - uint256 allowedTokens; - uint256 startTime; - uint256 rollingPeriodInDays; - uint256 endTime; - RestrictionType typeOfRestriction; - } - - struct BucketDetails { - uint256 lastTradedDayTime; - uint256 sumOfLastPeriod; // It is the sum of transacted amount within the last rollingPeriodDays - uint256 daysCovered; // No of days covered till (from the startTime of VolumeRestriction) - uint256 dailyLastTradedDayTime; - uint256 lastTradedTimestamp; // It is the timestamp at which last transaction get executed - } - - // Global restriction that applies to all token holders - VolumeRestriction public defaultRestriction; - // Daily global restriction that applies to all token holders (Total ST traded daily is restricted) - VolumeRestriction public defaultDailyRestriction; - // Restriction stored corresponds to a particular token holder - mapping(address => VolumeRestriction) public individualRestriction; - // Daily restriction stored corresponds to a particular token holder - mapping(address => VolumeRestriction) public individualDailyRestriction; - // Storing _from => day's timestamp => total amount transact in a day --individual - mapping(address => mapping(uint256 => uint256)) internal bucket; - // Storing _from => day's timestamp => total amount transact in a day --default - mapping(address => mapping(uint256 => uint256)) internal defaultBucket; - // Storing the information that used to validate the transaction - mapping(address => BucketDetails) internal userToBucket; - // Storing the information related to default restriction - mapping(address => BucketDetails) internal defaultUserToBucket; - // Restricted data (refernce from the VolumeRestrictionLib library ) - RestrictedData holderData; - // Hold exempt index - mapping(address => uint256) exemptIndex; - address[] public exemptAddresses; - -} diff --git a/contracts/modules/Checkpoint/DividendCheckpointStorage.sol b/contracts/storage/modules/Checkpoint/Dividend/DividendCheckpointStorage.sol similarity index 89% rename from contracts/modules/Checkpoint/DividendCheckpointStorage.sol rename to contracts/storage/modules/Checkpoint/Dividend/DividendCheckpointStorage.sol index 2ae0473ac..94fc0962b 100644 --- a/contracts/modules/Checkpoint/DividendCheckpointStorage.sol +++ b/contracts/storage/modules/Checkpoint/Dividend/DividendCheckpointStorage.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; /** * @title Holds the storage variable for the DividendCheckpoint modules (i.e ERC20, Ether) @@ -7,11 +7,8 @@ pragma solidity ^0.4.24; contract DividendCheckpointStorage { // Address to which reclaimed dividends and withholding tax is sent - address public wallet; + address payable public wallet; uint256 public EXCLUDED_ADDRESS_LIMIT = 150; - bytes32 public constant DISTRIBUTE = "DISTRIBUTE"; - bytes32 public constant MANAGE = "MANAGE"; - bytes32 public constant CHECKPOINT = "CHECKPOINT"; struct Dividend { uint256 checkpointId; @@ -40,4 +37,7 @@ contract DividendCheckpointStorage { // 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; + } diff --git a/contracts/storage/modules/Checkpoint/Voting/VotingCheckpointStorage.sol b/contracts/storage/modules/Checkpoint/Voting/VotingCheckpointStorage.sol new file mode 100644 index 000000000..d4b728572 --- /dev/null +++ b/contracts/storage/modules/Checkpoint/Voting/VotingCheckpointStorage.sol @@ -0,0 +1,8 @@ +pragma solidity 0.5.8; + +contract VotingCheckpointStorage { + + mapping(address => uint256) defaultExemptIndex; + address[] defaultExemptedVoters; + +} diff --git a/contracts/storage/modules/ModuleStorage.sol b/contracts/storage/modules/ModuleStorage.sol new file mode 100644 index 000000000..9e34f16df --- /dev/null +++ b/contracts/storage/modules/ModuleStorage.sol @@ -0,0 +1,33 @@ +pragma solidity 0.5.8; + +import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; +import "../../interfaces/ISecurityToken.sol"; +/** + * @title Storage for Module contract + * @notice Contract is abstract + */ +contract ModuleStorage { + address public factory; + + ISecurityToken public securityToken; + + // Permission flag + bytes32 public constant ADMIN = "ADMIN"; + bytes32 public constant OPERATOR = "OPERATOR"; + + bytes32 internal constant TREASURY = 0xaae8817359f3dcb67d050f44f3e49f982e0359d90ca4b5f18569926304aaece6; // keccak256(abi.encodePacked("TREASURY_WALLET")) + + IERC20 public polyToken; + + /** + * @notice Constructor + * @param _securityToken Address of the security token + * @param _polyAddress Address of the polytoken + */ + constructor(address _securityToken, address _polyAddress) public { + securityToken = ISecurityToken(_securityToken); + factory = msg.sender; + polyToken = IERC20(_polyAddress); + } + +} diff --git a/contracts/storage/modules/STO/ISTOStorage.sol b/contracts/storage/modules/STO/ISTOStorage.sol new file mode 100644 index 000000000..ccddb7e7f --- /dev/null +++ b/contracts/storage/modules/STO/ISTOStorage.sol @@ -0,0 +1,24 @@ +pragma solidity 0.5.8; + +/** + * @title Storage layout for the ISTO contract + */ +contract ISTOStorage { + + mapping (uint8 => bool) public fundRaiseTypes; + mapping (uint8 => uint256) public fundsRaised; + + // Start time of the STO + uint256 public startTime; + // End time of the STO + uint256 public endTime; + // Time STO was paused + uint256 public pausedTime; + // Number of individual investors + uint256 public investorCount; + // Address where ETH & POLY funds are delivered + address public wallet; + // Final amount of tokens sold + uint256 public totalTokensSold; + +} diff --git a/contracts/modules/STO/STOStorage.sol b/contracts/storage/modules/STO/STOStorage.sol similarity index 76% rename from contracts/modules/STO/STOStorage.sol rename to contracts/storage/modules/STO/STOStorage.sol index 2e2401fb0..e28a932fc 100644 --- a/contracts/modules/STO/STOStorage.sol +++ b/contracts/storage/modules/STO/STOStorage.sol @@ -1,9 +1,11 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; /** * @title Storage layout for the STO contract */ + contract STOStorage { + bytes32 internal constant INVESTORFLAGS = "INVESTORFLAGS"; mapping (uint8 => bool) public fundRaiseTypes; mapping (uint8 => uint256) public fundsRaised; @@ -17,8 +19,8 @@ contract STOStorage { // Number of individual investors uint256 public investorCount; // Address where ETH & POLY funds are delivered - address public wallet; - // Final amount of tokens sold + address payable public wallet; + // Final amount of tokens sold uint256 public totalTokensSold; } diff --git a/contracts/tokens/OZStorage.sol b/contracts/tokens/OZStorage.sol new file mode 100644 index 000000000..051601005 --- /dev/null +++ b/contracts/tokens/OZStorage.sol @@ -0,0 +1,31 @@ +pragma solidity 0.5.8; + +/* + * @dev It is the contract that contains the storage items related to the ERC20 contract implementaiton + * of the openzeppelin-solidity. Used to allow the storage declaration of ERC20 to the STGetter contract +*/ + +contract OZStorage { + + mapping (address => uint256) private _balances; + + mapping (address => mapping (address => uint256)) private _allowed; + + uint256 private _totalSupply; + + /// @dev counter to allow mutex lock with only one SSTORE operation + uint256 private _guardCounter; + + function totalSupply() internal view returns (uint256) { + return _totalSupply; + } + + function balanceOf(address _investor) internal view returns(uint256) { + return _balances[_investor]; + } + + function _allowance(address owner, address spender) internal view returns(uint256) { + return _allowed[owner][spender]; + } + +} diff --git a/contracts/tokens/STFactory.sol b/contracts/tokens/STFactory.sol index 85153e816..22c88af01 100644 --- a/contracts/tokens/STFactory.sol +++ b/contracts/tokens/STFactory.sol @@ -1,17 +1,72 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; -import "./SecurityToken.sol"; +import "./SecurityTokenProxy.sol"; +import "../proxy/OwnedUpgradeabilityProxy.sol"; import "../interfaces/ISTFactory.sol"; +import "../interfaces/ISecurityToken.sol"; +import "../interfaces/IPolymathRegistry.sol"; +import "../interfaces/IOwnable.sol"; +import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; +import "../interfaces/IModuleRegistry.sol"; +import "../interfaces/IPolymathRegistry.sol"; +import "../datastore/DataStoreFactory.sol"; /** * @title Proxy for deploying SecurityToken instances */ -contract STFactory is ISTFactory { +contract STFactory is ISTFactory, Ownable { address public transferManagerFactory; + DataStoreFactory public dataStoreFactory; + IPolymathRegistry public polymathRegistry; - constructor (address _transferManagerFactory) public { + // Mapping from Security Token address to token upgrade version. + // A mapping to 0 means a token has not yet been deployed + mapping (address => uint256) tokenUpgrade; + + struct LogicContract { + string version; + address logicContract; + bytes initializationData; // Called when first creating token + bytes upgradeData; // Called when upgrading token from previous version + } + + mapping (uint256 => LogicContract) logicContracts; + + uint256 public latestUpgrade; + + event LogicContractSet(string _version, uint256 _upgrade, address _logicContract, bytes _initializationData, bytes _upgradeData); + event TokenUpgraded( + address indexed _securityToken, + uint256 indexed _version + ); + event DefaultTransferManagerUpdated(address indexed _oldTransferManagerFactory, address indexed _newTransferManagerFactory); + event DefaultDataStoreUpdated(address indexed _oldDataStoreFactory, address indexed _newDataStoreFactory); + + constructor( + address _polymathRegistry, + address _transferManagerFactory, + address _dataStoreFactory, + string memory _version, + address _logicContract, + bytes memory _initializationData + ) + public + { + require(_logicContract != address(0), "Invalid Address"); + require(_transferManagerFactory != address(0), "Invalid Address"); + require(_dataStoreFactory != address(0), "Invalid Address"); + require(_polymathRegistry != address(0), "Invalid Address"); + require(_initializationData.length > 4, "Invalid Initialization"); transferManagerFactory = _transferManagerFactory; + dataStoreFactory = DataStoreFactory(_dataStoreFactory); + polymathRegistry = IPolymathRegistry(_polymathRegistry); + + // Start at 1 so that we can distinguish deployed tokens in tokenUpgrade + latestUpgrade = 1; + logicContracts[latestUpgrade].logicContract = _logicContract; + logicContracts[latestUpgrade].initializationData = _initializationData; + logicContracts[latestUpgrade].version = _version; } /** @@ -19,24 +74,154 @@ contract STFactory is ISTFactory { * Future versions of the proxy can attach different modules or pass different parameters. */ function deployToken( - string _name, - string _symbol, + string calldata _name, + string calldata _symbol, uint8 _decimals, - string _tokenDetails, + string calldata _tokenDetails, address _issuer, bool _divisible, - address _polymathRegistry - ) external returns (address) { - address newSecurityTokenAddress = new SecurityToken( + address _treasuryWallet + ) + external + returns(address) + { + address securityToken = _deploy( _name, _symbol, _decimals, - _divisible ? 1 : uint256(10)**_decimals, _tokenDetails, - _polymathRegistry + _divisible ); - SecurityToken(newSecurityTokenAddress).addModule(transferManagerFactory, "", 0, 0); - SecurityToken(newSecurityTokenAddress).transferOwnership(_issuer); - return newSecurityTokenAddress; + //NB When dataStore is generated, the security token address is automatically set via the constructor in DataStoreProxy. + if (address(dataStoreFactory) != address(0)) { + ISecurityToken(securityToken).changeDataStore(dataStoreFactory.generateDataStore(securityToken)); + } + ISecurityToken(securityToken).changeTreasuryWallet(_treasuryWallet); + if (transferManagerFactory != address(0)) { + ISecurityToken(securityToken).addModule(transferManagerFactory, "", 0, 0, false); + } + IOwnable(securityToken).transferOwnership(_issuer); + return securityToken; + } + + function _deploy( + string memory _name, + string memory _symbol, + uint8 _decimals, + string memory _tokenDetails, + bool _divisible + ) internal returns(address) { + // Creates proxy contract and sets some initial storage + SecurityTokenProxy proxy = new SecurityTokenProxy( + _name, + _symbol, + _decimals, + _divisible ? 1 : uint256(10) ** _decimals, + _tokenDetails, + address(polymathRegistry) + ); + // Sets logic contract + proxy.upgradeTo(logicContracts[latestUpgrade].version, logicContracts[latestUpgrade].logicContract); + // Initialises security token contract - needed for functions that can only be called by the + // owner of the contract, or are specific to this particular logic contract (e.g. setting version) + (bool success, ) = address(proxy).call(logicContracts[latestUpgrade].initializationData); + require(success, "Unsuccessful initialization"); + tokenUpgrade[address(proxy)] = latestUpgrade; + return address(proxy); + } + + /** + * @notice Used to set a new token logic contract + * @param _version Version of upgraded module + * @param _logicContract Address of deployed module logic contract referenced from proxy + * @param _initializationData Initialization data that used to intialize value in the securityToken + * @param _upgradeData Data to be passed in call to upgradeToAndCall when a token upgrades its module + */ + function setLogicContract(string calldata _version, address _logicContract, bytes calldata _initializationData, bytes calldata _upgradeData) external onlyOwner { + require(keccak256(abi.encodePacked(_version)) != keccak256(abi.encodePacked(logicContracts[latestUpgrade].version)), "Same version"); + require(_logicContract != logicContracts[latestUpgrade].logicContract, "Same version"); + require(_logicContract != address(0), "Invalid address"); + require(_initializationData.length > 4, "Invalid Initialization"); + require(_upgradeData.length > 4, "Invalid Upgrade"); + latestUpgrade++; + _modifyLogicContract(latestUpgrade, _version, _logicContract, _initializationData, _upgradeData); } + + /** + * @notice Used to update an existing token logic contract + * @param _upgrade logic contract to upgrade + * @param _version Version of upgraded module + * @param _logicContract Address of deployed module logic contract referenced from proxy + * @param _upgradeData Data to be passed in call to upgradeToAndCall when a token upgrades its module + */ + function updateLogicContract(uint256 _upgrade, string calldata _version, address _logicContract, bytes calldata _initializationData, bytes calldata _upgradeData) external onlyOwner { + require(_upgrade <= latestUpgrade, "Invalid upgrade"); + require(_upgrade > 0, "Invalid upgrade"); + // version & contract must differ from previous version, otherwise upgrade proxy will fail + if (_upgrade > 1) { + require(keccak256(abi.encodePacked(_version)) != keccak256(abi.encodePacked(logicContracts[_upgrade - 1].version)), "Same version"); + require(_logicContract != logicContracts[_upgrade - 1].logicContract, "Same version"); + } + require(_logicContract != address(0), "Invalid address"); + require(_initializationData.length > 4, "Invalid Initialization"); + require(_upgradeData.length > 4, "Invalid Upgrade"); + _modifyLogicContract(_upgrade, _version, _logicContract, _initializationData, _upgradeData); + } + + function _modifyLogicContract(uint256 _upgrade, string memory _version, address _logicContract, bytes memory _initializationData, bytes memory _upgradeData) internal { + logicContracts[_upgrade].version = _version; + logicContracts[_upgrade].logicContract = _logicContract; + logicContracts[_upgrade].upgradeData = _upgradeData; + logicContracts[_upgrade].initializationData = _initializationData; + emit LogicContractSet(_version, _upgrade, _logicContract, _initializationData, _upgradeData); + } + /** + * @notice Used to upgrade a token + * @param _maxModuleType maximum module type enumeration + */ + function upgradeToken(uint8 _maxModuleType) external { + // Check the token was created by this factory + require(tokenUpgrade[msg.sender] != 0, "Invalid token"); + uint256 newVersion = tokenUpgrade[msg.sender] + 1; + require(newVersion <= latestUpgrade, "Incorrect version"); + OwnedUpgradeabilityProxy(address(uint160(msg.sender))).upgradeToAndCall(logicContracts[newVersion].version, logicContracts[newVersion].logicContract, logicContracts[newVersion].upgradeData); + tokenUpgrade[msg.sender] = newVersion; + // Check that all modules remain valid + IModuleRegistry moduleRegistry = IModuleRegistry(polymathRegistry.getAddress("ModuleRegistry")); + address moduleFactory; + bool isArchived; + for (uint8 i = 1; i < _maxModuleType; i++) { + address[] memory modules = ISecurityToken(msg.sender).getModulesByType(i); + for (uint256 j = 0; j < modules.length; j++) { + (,, moduleFactory, isArchived,,) = ISecurityToken(msg.sender).getModule(modules[j]); + if (!isArchived) { + require(moduleRegistry.isCompatibleModule(moduleFactory, msg.sender), "Incompatible Modules"); + } + } + } + emit TokenUpgraded(msg.sender, newVersion); + } + + /** + * @notice Used to set a new default transfer manager + * @dev Setting this to address(0) means don't deploy a default TM + * @param _transferManagerFactory Address of new default transfer manager factory + */ + function updateDefaultTransferManager(address _transferManagerFactory) external onlyOwner { + // NB - setting this to address(0) means don't deploy a default TM + emit DefaultTransferManagerUpdated(transferManagerFactory, _transferManagerFactory); + transferManagerFactory = _transferManagerFactory; + } + + /** + * @notice Used to set a new default data store + * @dev Setting this to address(0) means don't deploy a default data store + * @param _dataStoreFactory Address of new default data store factory + */ + function updateDefaultDataStore(address _dataStoreFactory) external onlyOwner { + // NB - setting this to address(0) means don't deploy a default TM + emit DefaultDataStoreUpdated(address(dataStoreFactory), address(_dataStoreFactory)); + dataStoreFactory = DataStoreFactory(_dataStoreFactory); + } + } diff --git a/contracts/tokens/STGetter.sol b/contracts/tokens/STGetter.sol new file mode 100644 index 000000000..d4810d948 --- /dev/null +++ b/contracts/tokens/STGetter.sol @@ -0,0 +1,267 @@ +pragma solidity 0.5.8; + +import "./OZStorage.sol"; +import "./SecurityTokenStorage.sol"; +import "../libraries/TokenLib.sol"; +import "openzeppelin-solidity/contracts/math/SafeMath.sol"; +import "../modules/PermissionManager/IPermissionManager.sol"; + +contract STGetter is OZStorage, SecurityTokenStorage { + + using SafeMath for uint256; + + /** + * @notice A security token issuer can specify that issuance has finished for the token + * (i.e. no new tokens can be minted or issued). + * @dev If a token returns FALSE for `isIssuable()` then it MUST always return FALSE in the future. + * If a token returns FALSE for `isIssuable()` then it MUST never allow additional tokens to be issued. + * @return bool `true` signifies the minting is allowed. While `false` denotes the end of minting + */ + function isIssuable() external view returns (bool) { + return issuance; + } + + /** + * @notice Gets list of times that checkpoints were created + * @return List of checkpoint times + */ + function getCheckpointTimes() external view returns(uint256[] memory) { + return checkpointTimes; + } + + /** + * @notice Returns the count of address that were added as (potential) investors + * @return Investor count + */ + function getInvestorCount() external view returns(uint256) { + return dataStore.getAddressArrayLength(INVESTORSKEY); + } + + /** + * @notice returns an array of investors + * NB - this length may differ from investorCount as it contains all investors that ever held tokens + * @return list of addresses + */ + function getInvestors() public view returns(address[] memory investors) { + investors = dataStore.getAddressArray(INVESTORSKEY); + } + + /** + * @notice returns an array of investors with non zero balance at a given checkpoint + * @param _checkpointId Checkpoint id at which investor list is to be populated + * @return list of investors + */ + function getInvestorsAt(uint256 _checkpointId) external view returns(address[] memory) { + uint256 count; + uint256 i; + address[] memory investors = dataStore.getAddressArray(INVESTORSKEY); + for (i = 0; i < investors.length; i++) { + if (balanceOfAt(investors[i], _checkpointId) > 0) { + count++; + } else { + investors[i] = address(0); + } + } + address[] memory holders = new address[](count); + count = 0; + for (i = 0; i < investors.length; i++) { + if (investors[i] != address(0)) { + holders[count] = investors[i]; + count++; + } + } + return holders; + } + + /** + * @notice returns an array of investors with non zero balance at a given checkpoint + * @param _checkpointId Checkpoint id at which investor list is to be populated + * @param _start Position of investor to start iteration from + * @param _end Position of investor to stop iteration at + * @return list of investors + */ + function getInvestorsSubsetAt(uint256 _checkpointId, uint256 _start, uint256 _end) external view returns(address[] memory) { + uint256 count; + uint256 i; + address[] memory investors = dataStore.getAddressArrayElements(INVESTORSKEY, _start, _end); + for (i = 0; i < investors.length; i++) { + if (balanceOfAt(investors[i], _checkpointId) > 0) { + count++; + } else { + investors[i] = address(0); + } + } + address[] memory holders = new address[](count); + count = 0; + for (i = 0; i < investors.length; i++) { + if (investors[i] != address(0)) { + holders[count] = investors[i]; + count++; + } + } + return holders; + } + + /** + * @notice Returns the data associated to a module + * @param _module address of the module + * @return bytes32 name + * @return address module address + * @return address module factory address + * @return bool module archived + * @return uint8 array of module types + * @return bytes32 module label + */ + function getModule(address _module) external view returns(bytes32, address, address, bool, uint8[] memory, bytes32) { + return ( + modulesToData[_module].name, + modulesToData[_module].module, + modulesToData[_module].moduleFactory, + modulesToData[_module].isArchived, + modulesToData[_module].moduleTypes, + modulesToData[_module].label + ); + } + + /** + * @notice Returns a list of modules that match the provided name + * @param _name name of the module + * @return address[] list of modules with this name + */ + function getModulesByName(bytes32 _name) external view returns(address[] memory) { + return names[_name]; + } + + /** + * @notice Returns a list of modules that match the provided module type + * @param _type type of the module + * @return address[] list of modules with this type + */ + function getModulesByType(uint8 _type) external view returns(address[] memory) { + return modules[_type]; + } + + /** + * @notice use to return the global treasury wallet + */ + function getTreasuryWallet() external view returns(address) { + return dataStore.getAddress(TREASURY); + } + + /** + * @notice Queries balances as of a defined checkpoint + * @param _investor Investor to query balance for + * @param _checkpointId Checkpoint ID to query as of + */ + function balanceOfAt(address _investor, uint256 _checkpointId) public view returns(uint256) { + require(_checkpointId <= currentCheckpointId); + return TokenLib.getValueAt(checkpointBalances[_investor], _checkpointId, balanceOf(_investor)); + } + + /** + * @notice Queries totalSupply as of a defined checkpoint + * @param _checkpointId Checkpoint ID to query + * @return uint256 + */ + function totalSupplyAt(uint256 _checkpointId) external view returns(uint256) { + require(_checkpointId <= currentCheckpointId); + return checkpointTotalSupply[_checkpointId]; + } + + /** + * @notice generates subset of investors + * NB - can be used in batches if investor list is large. start and end both are included in array. + * @param _start Position of investor to start iteration from + * @param _end Position of investor to stop iteration at + * @return list of investors + */ + function iterateInvestors(uint256 _start, uint256 _end) external view returns(address[] memory) { + return dataStore.getAddressArrayElements(INVESTORSKEY, _start, _end); + } + + /** + * @notice Validate permissions with PermissionManager if it exists, If no Permission return false + * @dev Note that IModule withPerm will allow ST owner all permissions anyway + * @dev this allows individual modules to override this logic if needed (to not allow ST owner all permissions) + * @param _delegate address of delegate + * @param _module address of PermissionManager module + * @param _perm the permissions + * @return success + */ + function checkPermission(address _delegate, address _module, bytes32 _perm) public view returns(bool) { + for (uint256 i = 0; i < modules[PERMISSION_KEY].length; i++) { + if (!modulesToData[modules[PERMISSION_KEY][i]].isArchived) { + if (IPermissionManager(modules[PERMISSION_KEY][i]).checkPermission(_delegate, _module, _perm)) { + return true; + } + } + } + return false; + } + + /** + * @notice Determines whether `_operator` is an operator for all partitions of `_tokenHolder` + * @param _operator The operator to check + * @param _tokenHolder The token holder to check + * @return Whether the `_operator` is an operator for all partitions of `_tokenHolder` + */ + function isOperator(address _operator, address _tokenHolder) external view returns (bool) { + return (_allowance(_tokenHolder, _operator) == uint(-1)); + } + + /** + * @notice Determines whether `_operator` is an operator for a specified partition of `_tokenHolder` + * @param _partition The partition to check + * @param _operator The operator to check + * @param _tokenHolder The token holder to check + * @return Whether the `_operator` is an operator for a specified partition of `_tokenHolder` + */ + function isOperatorForPartition(bytes32 _partition, address _operator, address _tokenHolder) external view returns (bool) { + return partitionApprovals[_tokenHolder][_partition][_operator]; + } + + /** + * @notice Return all partitions + * @return List of partitions + */ + function partitionsOf(address /*_tokenHolder*/) external pure returns (bytes32[] memory) { + bytes32[] memory result = new bytes32[](2); + result[0] = UNLOCKED; + result[1] = LOCKED; + return result; + } + + /** + * @notice Returns the version of the SecurityToken + */ + function getVersion() external view returns(uint8[] memory) { + uint8[] memory version = new uint8[](3); + version[0] = securityTokenVersion.major; + version[1] = securityTokenVersion.minor; + version[2] = securityTokenVersion.patch; + return version; + } + + /** + * @notice Used to return the details of a document with a known name (`bytes32`). + * @param _name Name of the document + * @return string The URI associated with the document. + * @return bytes32 The hash (of the contents) of the document. + * @return uint256 the timestamp at which the document was last modified. + */ + function getDocument(bytes32 _name) external view returns (string memory, bytes32, uint256) { + return ( + _documents[_name].uri, + _documents[_name].docHash, + _documents[_name].lastModified + ); + } + + /** + * @notice Used to retrieve a full list of documents attached to the smart contract. + * @return bytes32 List of all documents names present in the contract. + */ + function getAllDocuments() external view returns (bytes32[] memory) { + return _docNames; + } +} diff --git a/contracts/tokens/SecurityToken.sol b/contracts/tokens/SecurityToken.sol index d2aaa9b55..f8a57579e 100644 --- a/contracts/tokens/SecurityToken.sol +++ b/contracts/tokens/SecurityToken.sol @@ -1,142 +1,105 @@ -pragma solidity ^0.4.24; +pragma solidity 0.5.8; -import "openzeppelin-solidity/contracts/math/Math.sol"; -import "../interfaces/IERC20.sol"; +import "../proxy/Proxy.sol"; import "../interfaces/IModule.sol"; -import "../interfaces/IModuleFactory.sol"; -import "../interfaces/IModuleRegistry.sol"; -import "../interfaces/IFeatureRegistry.sol"; -import "../modules/TransferManager/ITransferManager.sol"; -import "../RegistryUpdater.sol"; -import "../libraries/Util.sol"; -import "openzeppelin-solidity/contracts/ReentrancyGuard.sol"; -import "openzeppelin-solidity/contracts/token/ERC20/StandardToken.sol"; -import "openzeppelin-solidity/contracts/token/ERC20/DetailedERC20.sol"; +import "./SecurityTokenStorage.sol"; import "../libraries/TokenLib.sol"; +import "../libraries/StatusCodes.sol"; +import "../interfaces/IDataStore.sol"; +import "../interfaces/IUpgradableTokenFactory.sol"; +import "../interfaces/IModuleFactory.sol"; +import "../interfaces/token/IERC1410.sol"; +import "../interfaces/token/IERC1594.sol"; +import "../interfaces/token/IERC1643.sol"; +import "../interfaces/token/IERC1644.sol"; +import "../interfaces/ITransferManager.sol"; +import "openzeppelin-solidity/contracts/utils/ReentrancyGuard.sol"; +import "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol"; /** -* @title Security Token contract -* @notice SecurityToken is an ERC20 token with added capabilities: -* @notice - Implements the ST-20 Interface -* @notice - Transfers are restricted -* @notice - Modules can be attached to it to control its behaviour -* @notice - ST should not be deployed directly, but rather the SecurityTokenRegistry should be used -* @notice - ST does not inherit from ISecurityToken due to: -* @notice - https://github.com/ethereum/solidity/issues/4847 -*/ -contract SecurityToken is StandardToken, DetailedERC20, ReentrancyGuard, RegistryUpdater { - using SafeMath for uint256; - - TokenLib.InvestorDataStorage investorData; - - // Used to hold the semantic version data - struct SemanticVersion { - uint8 major; - uint8 minor; - uint8 patch; - } - - SemanticVersion securityTokenVersion; - - // off-chain data - string public tokenDetails; - - uint8 constant PERMISSION_KEY = 1; - uint8 constant TRANSFER_KEY = 2; - uint8 constant MINT_KEY = 3; - uint8 constant CHECKPOINT_KEY = 4; - uint8 constant BURN_KEY = 5; - - uint256 public granularity; - - // Value of current checkpoint - uint256 public currentCheckpointId; - - // Used to temporarily halt all transactions - bool public transfersFrozen; - - // Used to permanently halt all minting - bool public mintingFrozen; - - // Used to permanently halt controller actions - bool public controllerDisabled; - - // Address whitelisted by issuer as controller - address public controller; - - // Records added modules - module list should be order agnostic! - mapping (uint8 => address[]) modules; - - // Records information about the module - mapping (address => TokenLib.ModuleData) modulesToData; - - // Records added module names - module list should be order agnostic! - mapping (bytes32 => address[]) names; - - // Map each investor to a series of checkpoints - mapping (address => TokenLib.Checkpoint[]) checkpointBalances; - - // List of checkpoints that relate to total supply - TokenLib.Checkpoint[] checkpointTotalSupply; + * @title Security Token contract + * @notice SecurityToken is an ERC1400 token with added capabilities: + * @notice - Implements the ERC1400 Interface + * @notice - Transfers are restricted + * @notice - Modules can be attached to it to control its behaviour + * @notice - ST should not be deployed directly, but rather the SecurityTokenRegistry should be used + * @notice - ST does not inherit from ISecurityToken due to: + * @notice - https://github.com/ethereum/solidity/issues/4847 + */ +contract SecurityToken is ERC20, ReentrancyGuard, SecurityTokenStorage, IERC1594, IERC1643, IERC1644, IERC1410, Proxy { - // Times at which each checkpoint was created - uint256[] checkpointTimes; + using SafeMath for uint256; // Emit at the time when module get added event ModuleAdded( uint8[] _types, - bytes32 _name, - address _moduleFactory, + bytes32 indexed _name, + address indexed _moduleFactory, address _module, uint256 _moduleCost, uint256 _budget, - uint256 _timestamp + bytes32 _label, + bool _archived ); - + // Emit when Module get upgraded from the securityToken + event ModuleUpgraded(uint8[] _types, address _module); // Emit when the token details get updated event UpdateTokenDetails(string _oldDetails, string _newDetails); + // Emit when the token name get updated + event UpdateTokenName(string _oldName, string _newName); // Emit when the granularity get changed event GranularityChanged(uint256 _oldGranularity, uint256 _newGranularity); + // Emit when is permanently frozen by the issuer + event FreezeIssuance(); + // Emit when transfers are frozen or unfrozen + event FreezeTransfers(bool _status); + // Emit when new checkpoint created + event CheckpointCreated(uint256 indexed _checkpointId, uint256 _investorLength); + // Events to log controller actions + event SetController(address indexed _oldController, address indexed _newController); + //Event emit when the global treasury wallet address get changed + event TreasuryWalletChanged(address _oldTreasuryWallet, address _newTreasuryWallet); + event DisableController(); + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + event TokenUpgraded(uint8 _major, uint8 _minor, uint8 _patch); + // Emit when Module get archived from the securityToken - event ModuleArchived(uint8[] _types, address _module, uint256 _timestamp); + event ModuleArchived(uint8[] _types, address _module); //Event emitted by the tokenLib. // Emit when Module get unarchived from the securityToken - event ModuleUnarchived(uint8[] _types, address _module, uint256 _timestamp); + event ModuleUnarchived(uint8[] _types, address _module); //Event emitted by the tokenLib. // Emit when Module get removed from the securityToken - event ModuleRemoved(uint8[] _types, address _module, uint256 _timestamp); + event ModuleRemoved(uint8[] _types, address _module); //Event emitted by the tokenLib. // Emit when the budget allocated to a module is changed - event ModuleBudgetChanged(uint8[] _moduleTypes, address _module, uint256 _oldBudget, uint256 _budget); - // Emit when transfers are frozen or unfrozen - event FreezeTransfers(bool _status, uint256 _timestamp); - // Emit when new checkpoint created - event CheckpointCreated(uint256 indexed _checkpointId, uint256 _timestamp); - // Emit when is permanently frozen by the issuer - event FreezeMinting(uint256 _timestamp); - // Events to log minting and burning - event Minted(address indexed _to, uint256 _value); - event Burnt(address indexed _from, uint256 _value); + event ModuleBudgetChanged(uint8[] _moduleTypes, address _module, uint256 _oldBudget, uint256 _budget); //Event emitted by the tokenLib. - // Events to log controller actions - event SetController(address indexed _oldController, address indexed _newController); - event ForceTransfer( - address indexed _controller, - address indexed _from, - address indexed _to, - uint256 _value, - bool _verifyTransfer, - bytes _data - ); - event ForceBurn( - address indexed _controller, - address indexed _from, - uint256 _value, - bool _verifyTransfer, - bytes _data - ); - event DisableController(uint256 _timestamp); + // Constructor + constructor() public { + initialized = true; + } + + /** + * @notice Initialization function + * @dev Expected to be called atomically with the proxy being created, by the owner of the token + * @dev Can only be called once + */ + function initialize(address _getterDelegate) public { + //Expected to be called atomically with the proxy being created + require(!initialized, "Already initialized"); + getterDelegate = _getterDelegate; + securityTokenVersion = SemanticVersion(3, 0, 0); + updateFromRegistry(); + tokenFactory = msg.sender; + initialized = true; + } - function _isModule(address _module, uint8 _type) internal view returns (bool) { - require(modulesToData[_module].module == _module, "Wrong address"); - require(!modulesToData[_module].isArchived, "Module archived"); + /** + * @notice Checks if an address is a module of certain type + * @param _module Address to check + * @param _type type to check against + */ + function isModule(address _module, uint8 _type) public view returns(bool) { + if (modulesToData[_module].module != _module || modulesToData[_module].isArchived) + return false; for (uint256 i = 0; i < modulesToData[_module].moduleTypes.length; i++) { if (modulesToData[_module].moduleTypes[i] == _type) { return true; @@ -145,255 +108,224 @@ contract SecurityToken is StandardToken, DetailedERC20, ReentrancyGuard, Registr return false; } - // Require msg.sender to be the specified module type - modifier onlyModule(uint8 _type) { - require(_isModule(msg.sender, _type)); - _; + // Require msg.sender to be the specified module type or the owner of the token + function _onlyModuleOrOwner(uint8 _type) internal view { + if (msg.sender != owner()) + require(isModule(msg.sender, _type)); } - // Require msg.sender to be the specified module type or the owner of the token - modifier onlyModuleOrOwner(uint8 _type) { - if (msg.sender == owner) { - _; - } else { - require(_isModule(msg.sender, _type)); - _; - } + function _isValidPartition(bytes32 _partition) internal pure { + require(_partition == UNLOCKED, "Invalid partition"); } - modifier checkGranularity(uint256 _value) { - require(_value % granularity == 0, "Invalid granularity"); - _; + function _isValidOperator(address _from, address _operator, bytes32 _partition) internal view { + _isAuthorised( + allowance(_from, _operator) == uint(-1) || partitionApprovals[_from][_partition][_operator] + ); } - modifier isMintingAllowed() { - require(!mintingFrozen, "Minting frozen"); - _; + function _zeroAddressCheck(address _entity) internal pure { + require(_entity != address(0), "Invalid address"); } - modifier isEnabled(string _nameKey) { - require(IFeatureRegistry(featureRegistry).getFeatureStatus(_nameKey)); - _; + function _isValidTransfer(bool _isTransfer) internal pure { + require(_isTransfer, "Transfer Invalid"); + } + + function _isValidRedeem(bool _isRedeem) internal pure { + require(_isRedeem, "Invalid redeem"); + } + + function _isSignedByOwner(bool _signed) internal pure { + require(_signed, "Owner did not sign"); + } + + function _isIssuanceAllowed() internal view { + require(issuance, "Issuance frozen"); + } + + // Function to check whether the msg.sender is authorised or not + function _onlyController() internal view { + _isAuthorised(msg.sender == controller && isControllable()); + } + + function _isAuthorised(bool _authorised) internal pure { + require(_authorised, "Not Authorised"); } /** - * @notice Revert if called by an account which is not a controller + * @dev Throws if called by any account other than the owner. + * @dev using the internal function instead of modifier to save the code size */ - modifier onlyController() { - require(msg.sender == controller, "Not controller"); - require(!controllerDisabled, "Controller disabled"); - _; + function _onlyOwner() internal view { + require(isOwner()); } /** - * @notice Constructor - * @param _name Name of the SecurityToken - * @param _symbol Symbol of the Token - * @param _decimals Decimals for the securityToken - * @param _granularity granular level of the token - * @param _tokenDetails Details of the token that are stored off-chain - * @param _polymathRegistry Contract address of the polymath registry + * @dev Require msg.sender to be the specified module type */ - constructor ( - string _name, - string _symbol, - uint8 _decimals, - uint256 _granularity, - string _tokenDetails, - address _polymathRegistry - ) - public - DetailedERC20(_name, _symbol, _decimals) - RegistryUpdater(_polymathRegistry) - { - //When it is created, the owner is the STR - updateFromRegistry(); - tokenDetails = _tokenDetails; - granularity = _granularity; - securityTokenVersion = SemanticVersion(2,0,0); + function _onlyModule(uint8 _type) internal view { + require(isModule(msg.sender, _type)); + } + + modifier checkGranularity(uint256 _value) { + require(_value % granularity == 0, "Invalid granularity"); + _; } /** - * @notice Attachs a module to the SecurityToken - * @dev E.G.: On deployment (through the STR) ST gets a TransferManager module attached to it - * @dev to control restrictions on transfers. - * @param _moduleFactory is the address of the module factory to be added - * @param _data is data packed into bytes used to further configure the module (See STO usage) - * @param _maxCost max amount of POLY willing to pay to the module. - * @param _budget max amount of ongoing POLY willing to assign to the module. - */ - function addModule( + * @notice Attachs a module to the SecurityToken + * @dev E.G.: On deployment (through the STR) ST gets a TransferManager module attached to it + * @dev to control restrictions on transfers. + * @param _moduleFactory is the address of the module factory to be added + * @param _data is data packed into bytes used to further configure the module (See STO usage) + * @param _maxCost max amount of POLY willing to pay to the module. + * @param _budget max amount of ongoing POLY willing to assign to the module. + * @param _label custom module label. + */ + function addModuleWithLabel( address _moduleFactory, - bytes _data, + bytes memory _data, uint256 _maxCost, - uint256 _budget - ) external onlyOwner nonReentrant { + uint256 _budget, + bytes32 _label, + bool _archived + ) + public + nonReentrant + { + _onlyOwner(); //Check that the module factory exists in the ModuleRegistry - will throw otherwise - IModuleRegistry(moduleRegistry).useModule(_moduleFactory); + moduleRegistry.useModule(_moduleFactory, false); IModuleFactory moduleFactory = IModuleFactory(_moduleFactory); uint8[] memory moduleTypes = moduleFactory.getTypes(); - uint256 moduleCost = moduleFactory.getSetupCost(); + uint256 moduleCost = moduleFactory.setupCostInPoly(); require(moduleCost <= _maxCost, "Invalid cost"); //Approve fee for module - ERC20(polyToken).approve(_moduleFactory, moduleCost); + polyToken.approve(_moduleFactory, moduleCost); //Creates instance of module from factory address module = moduleFactory.deploy(_data); require(modulesToData[module].module == address(0), "Module exists"); //Approve ongoing budget - ERC20(polyToken).approve(module, _budget); - //Add to SecurityToken module map - bytes32 moduleName = moduleFactory.getName(); - uint256[] memory moduleIndexes = new uint256[](moduleTypes.length); + polyToken.approve(module, _budget); + _addModuleData(moduleTypes, _moduleFactory, module, moduleCost, _budget, _label, _archived); + } + + function _addModuleData( + uint8[] memory _moduleTypes, + address _moduleFactory, + address _module, + uint256 _moduleCost, + uint256 _budget, + bytes32 _label, + bool _archived + ) internal { + bytes32 moduleName = IModuleFactory(_moduleFactory).name(); + uint256[] memory moduleIndexes = new uint256[](_moduleTypes.length); uint256 i; - for (i = 0; i < moduleTypes.length; i++) { - moduleIndexes[i] = modules[moduleTypes[i]].length; - modules[moduleTypes[i]].push(module); + for (i = 0; i < _moduleTypes.length; i++) { + moduleIndexes[i] = modules[_moduleTypes[i]].length; + modules[_moduleTypes[i]].push(_module); } - modulesToData[module] = TokenLib.ModuleData( - moduleName, module, _moduleFactory, false, moduleTypes, moduleIndexes, names[moduleName].length + modulesToData[_module] = ModuleData( + moduleName, + _module, + _moduleFactory, + _archived, + _moduleTypes, + moduleIndexes, + names[moduleName].length, + _label ); - names[moduleName].push(module); - //Emit log event - /*solium-disable-next-line security/no-block-members*/ - emit ModuleAdded(moduleTypes, moduleName, _moduleFactory, module, moduleCost, _budget, now); + names[moduleName].push(_module); + emit ModuleAdded(_moduleTypes, moduleName, _moduleFactory, _module, _moduleCost, _budget, _label, _archived); } /** - * @notice Archives a module attached to the SecurityToken - * @param _module address of module to archive + * @notice addModule function will call addModuleWithLabel() with an empty label for backward compatible */ - function archiveModule(address _module) external onlyOwner { - TokenLib.archiveModule(modulesToData[_module], _module); + function addModule(address _moduleFactory, bytes calldata _data, uint256 _maxCost, uint256 _budget, bool _archived) external { + addModuleWithLabel(_moduleFactory, _data, _maxCost, _budget, "", _archived); } /** - * @notice Unarchives a module attached to the SecurityToken - * @param _module address of module to unarchive + * @notice Archives a module attached to the SecurityToken + * @param _module address of module to archive */ - function unarchiveModule(address _module) external onlyOwner { - TokenLib.unarchiveModule(modulesToData[_module], _module); + function archiveModule(address _module) external { + _onlyOwner(); + TokenLib.archiveModule(modulesToData[_module]); } /** - * @notice Removes a module attached to the SecurityToken - * @param _module address of module to unarchive + * @notice Upgrades a module attached to the SecurityToken + * @param _module address of module to archive */ - function removeModule(address _module) external onlyOwner { - require(modulesToData[_module].isArchived, "Not archived"); - require(modulesToData[_module].module != address(0), "Module missing"); - /*solium-disable-next-line security/no-block-members*/ - emit ModuleRemoved(modulesToData[_module].moduleTypes, _module, now); - // Remove from module type list - uint8[] memory moduleTypes = modulesToData[_module].moduleTypes; - for (uint256 i = 0; i < moduleTypes.length; i++) { - _removeModuleWithIndex(moduleTypes[i], modulesToData[_module].moduleIndexes[i]); - /* modulesToData[_module].moduleType[moduleTypes[i]] = false; */ - } - // Remove from module names list - uint256 index = modulesToData[_module].nameIndex; - bytes32 name = modulesToData[_module].name; - uint256 length = names[name].length; - names[name][index] = names[name][length - 1]; - names[name].length = length - 1; - if ((length - 1) != index) { - modulesToData[names[name][index]].nameIndex = index; - } - // Remove from modulesToData - delete modulesToData[_module]; + function upgradeModule(address _module) external { + _onlyOwner(); + TokenLib.upgradeModule(moduleRegistry, modulesToData[_module]); } /** - * @notice Internal - Removes a module attached to the SecurityToken by index + * @notice Upgrades security token */ - function _removeModuleWithIndex(uint8 _type, uint256 _index) internal { - uint256 length = modules[_type].length; - modules[_type][_index] = modules[_type][length - 1]; - modules[_type].length = length - 1; - - if ((length - 1) != _index) { - //Need to find index of _type in moduleTypes of module we are moving - uint8[] memory newTypes = modulesToData[modules[_type][_index]].moduleTypes; - for (uint256 i = 0; i < newTypes.length; i++) { - if (newTypes[i] == _type) { - modulesToData[modules[_type][_index]].moduleIndexes[i] = _index; - } - } - } + function upgradeToken() external { + _onlyOwner(); + // 10 is the number of module types to check for incompatibilities before upgrading. + // The number is hard coded and kept low to keep usage low. + // We currently have 7 module types. If we ever create more than 3 new module types, + // We will upgrade the implementation accordinly. We understand the limitations of this approach. + IUpgradableTokenFactory(tokenFactory).upgradeToken(10); + emit TokenUpgraded(securityTokenVersion.major, securityTokenVersion.minor, securityTokenVersion.patch); } /** - * @notice Returns the data associated to a module - * @param _module address of the module - * @return bytes32 name - * @return address module address - * @return address module factory address - * @return bool module archived - * @return uint8 module type - */ - function getModule(address _module) external view returns (bytes32, address, address, bool, uint8[]) { - return (modulesToData[_module].name, - modulesToData[_module].module, - modulesToData[_module].moduleFactory, - modulesToData[_module].isArchived, - modulesToData[_module].moduleTypes); + * @notice Unarchives a module attached to the SecurityToken + * @param _module address of module to unarchive + */ + function unarchiveModule(address _module) external { + _onlyOwner(); + TokenLib.unarchiveModule(moduleRegistry, modulesToData[_module]); } /** - * @notice Returns a list of modules that match the provided name - * @param _name name of the module - * @return address[] list of modules with this name - */ - function getModulesByName(bytes32 _name) external view returns (address[]) { - return names[_name]; + * @notice Removes a module attached to the SecurityToken + * @param _module address of module to unarchive + */ + function removeModule(address _module) external { + _onlyOwner(); + TokenLib.removeModule(_module, modules, modulesToData, names); } /** - * @notice Returns a list of modules that match the provided module type - * @param _type type of the module - * @return address[] list of modules with this type - */ - function getModulesByType(uint8 _type) external view returns (address[]) { - return modules[_type]; - } - - /** * @notice Allows the owner to withdraw unspent POLY stored by them on the ST or any ERC20 token. * @dev Owner can transfer POLY to the ST which will be used to pay for modules that require a POLY fee. * @param _tokenContract Address of the ERC20Basic compliance token * @param _value amount of POLY to withdraw */ - function withdrawERC20(address _tokenContract, uint256 _value) external onlyOwner { - require(_tokenContract != address(0)); + function withdrawERC20(address _tokenContract, uint256 _value) external { + _onlyOwner(); IERC20 token = IERC20(_tokenContract); - require(token.transfer(owner, _value)); + require(token.transfer(owner(), _value)); } /** - * @notice allows owner to increase/decrease POLY approval of one of the modules * @param _module module address * @param _change change in allowance * @param _increase true if budget has to be increased, false if decrease */ - function changeModuleBudget(address _module, uint256 _change, bool _increase) external onlyOwner { - require(modulesToData[_module].module != address(0), "Module missing"); - uint256 currentAllowance = IERC20(polyToken).allowance(address(this), _module); - uint256 newAllowance; - if (_increase) { - require(IERC20(polyToken).increaseApproval(_module, _change), "IncreaseApproval fail"); - newAllowance = currentAllowance.add(_change); - } else { - require(IERC20(polyToken).decreaseApproval(_module, _change), "Insufficient allowance"); - newAllowance = currentAllowance.sub(_change); - } - emit ModuleBudgetChanged(modulesToData[_module].moduleTypes, _module, currentAllowance, newAllowance); + function changeModuleBudget(address _module, uint256 _change, bool _increase) external { + _onlyOwner(); + TokenLib.changeModuleBudget(_module, _change, _increase, polyToken, modulesToData); } /** * @notice updates the tokenDetails associated with the token * @param _newTokenDetails New token details */ - function updateTokenDetails(string _newTokenDetails) external onlyOwner { + function updateTokenDetails(string calldata _newTokenDetails) external { + _onlyOwner(); emit UpdateTokenDetails(tokenDetails, _newTokenDetails); tokenDetails = _newTokenDetails; } @@ -402,107 +334,75 @@ contract SecurityToken is StandardToken, DetailedERC20, ReentrancyGuard, Registr * @notice Allows owner to change token granularity * @param _granularity granularity level of the token */ - function changeGranularity(uint256 _granularity) external onlyOwner { + function changeGranularity(uint256 _granularity) external { + _onlyOwner(); require(_granularity != 0, "Invalid granularity"); emit GranularityChanged(granularity, _granularity); granularity = _granularity; } /** - * @notice Keeps track of the number of non-zero token holders - * @param _from sender of transfer - * @param _to receiver of transfer - * @param _value value of transfer + * @notice Allows owner to change data store + * @param _dataStore Address of the token data store */ - function _adjustInvestorCount(address _from, address _to, uint256 _value) internal { - TokenLib.adjustInvestorCount(investorData, _from, _to, _value, balanceOf(_to), balanceOf(_from)); + function changeDataStore(address _dataStore) external { + _onlyOwner(); + _zeroAddressCheck(_dataStore); + dataStore = IDataStore(_dataStore); } /** - * @notice returns an array of investors - * NB - this length may differ from investorCount as it contains all investors that ever held tokens - * @return list of addresses - */ - function getInvestors() external view returns(address[]) { - return investorData.investors; - } - - /** - * @notice returns an array of investors at a given checkpoint - * NB - this length may differ from investorCount as it contains all investors that ever held tokens - * @param _checkpointId Checkpoint id at which investor list is to be populated - * @return list of investors - */ - function getInvestorsAt(uint256 _checkpointId) external view returns(address[]) { - uint256 count = 0; - uint256 i; - for (i = 0; i < investorData.investors.length; i++) { - if (balanceOfAt(investorData.investors[i], _checkpointId) > 0) { - count++; - } - } - address[] memory investors = new address[](count); - count = 0; - for (i = 0; i < investorData.investors.length; i++) { - if (balanceOfAt(investorData.investors[i], _checkpointId) > 0) { - investors[count] = investorData.investors[i]; - count++; - } - } - return investors; + * @notice Allows owner to change token name + * @param _name new name of the token + */ + function changeName(string calldata _name) external { + _onlyOwner(); + require(bytes(_name).length > 0); + emit UpdateTokenName(name, _name); + name = _name; } /** - * @notice generates subset of investors - * NB - can be used in batches if investor list is large - * @param _start Position of investor to start iteration from - * @param _end Position of investor to stop iteration at - * @return list of investors + * @notice Allows to change the treasury wallet address + * @param _wallet Ethereum address of the treasury wallet */ - function iterateInvestors(uint256 _start, uint256 _end) external view returns(address[]) { - require(_end <= investorData.investors.length, "Invalid end"); - address[] memory investors = new address[](_end.sub(_start)); - uint256 index = 0; - for (uint256 i = _start; i < _end; i++) { - investors[index] = investorData.investors[i]; - index++; - } - return investors; + function changeTreasuryWallet(address _wallet) external { + _onlyOwner(); + _zeroAddressCheck(_wallet); + emit TreasuryWalletChanged(dataStore.getAddress(TREASURY), _wallet); + dataStore.setAddress(TREASURY, _wallet); } /** - * @notice Returns the investor count - * @return Investor count - */ - function getInvestorCount() external view returns(uint256) { - return investorData.investorCount; + * @notice Keeps track of the number of non-zero token holders + * @param _from sender of transfer + * @param _to receiver of transfer + * @param _value value of transfer + */ + function _adjustInvestorCount(address _from, address _to, uint256 _value) internal { + holderCount = TokenLib.adjustInvestorCount(holderCount, _from, _to, _value, balanceOf(_to), balanceOf(_from), dataStore); } /** * @notice freezes transfers */ - function freezeTransfers() external onlyOwner { - require(!transfersFrozen, "Already frozen"); + function freezeTransfers() external { + _onlyOwner(); + require(!transfersFrozen); transfersFrozen = true; /*solium-disable-next-line security/no-block-members*/ - emit FreezeTransfers(true, now); + emit FreezeTransfers(true); } /** * @notice Unfreeze transfers */ - function unfreezeTransfers() external onlyOwner { - require(transfersFrozen, "Not frozen"); + function unfreezeTransfers() external { + _onlyOwner(); + require(transfersFrozen); transfersFrozen = false; /*solium-disable-next-line security/no-block-members*/ - emit FreezeTransfers(false, now); - } - - /** - * @notice Internal - adjusts totalSupply at checkpoint before minting or burning tokens - */ - function _adjustTotalSupplyCheckpoints() internal { - TokenLib.adjustCheckpoints(checkpointTotalSupply, totalSupply(), currentCheckpointId); + emit FreezeTransfers(false); } /** @@ -519,21 +419,30 @@ contract SecurityToken is StandardToken, DetailedERC20, ReentrancyGuard, Registr * @param _value value of transfer * @return bool success */ - function transfer(address _to, uint256 _value) public returns (bool success) { - return transferWithData(_to, _value, ""); + function transfer(address _to, uint256 _value) public returns(bool success) { + _transferWithData(msg.sender, _to, _value, ""); + return true; } /** - * @notice Overloaded version of the transfer function - * @param _to receiver of transfer - * @param _value value of transfer - * @param _data data to indicate validation - * @return bool success + * @notice Transfer restrictions can take many forms and typically involve on-chain rules or whitelists. + * However for many types of approved transfers, maintaining an on-chain list of approved transfers can be + * cumbersome and expensive. An alternative is the co-signing approach, where in addition to the token holder + * approving a token transfer, and authorised entity provides signed data which further validates the transfer. + * @param _to address The address which you want to transfer to + * @param _value uint256 the amount of tokens to be transferred + * @param _data The `bytes _data` allows arbitrary data to be submitted alongside the transfer. + * for the token contract to interpret or record. This could be signed data authorising the transfer + * (e.g. a dynamic whitelist) but is flexible enough to accomadate other use-cases. */ - function transferWithData(address _to, uint256 _value, bytes _data) public returns (bool success) { - require(_updateTransfer(msg.sender, _to, _value, _data), "Transfer invalid"); - require(super.transfer(_to, _value)); - return true; + function transferWithData(address _to, uint256 _value, bytes memory _data) public { + _transferWithData(msg.sender, _to, _value, _data); + } + + function _transferWithData(address _from, address _to, uint256 _value, bytes memory _data) internal { + _isValidTransfer(_updateTransfer(_from, _to, _value, _data)); + // Using the internal function instead of super.transfer() in the favour of reducing the code size + _transfer(_from, _to, _value); } /** @@ -544,21 +453,179 @@ contract SecurityToken is StandardToken, DetailedERC20, ReentrancyGuard, Registr * @return bool success */ function transferFrom(address _from, address _to, uint256 _value) public returns(bool) { - return transferFromWithData(_from, _to, _value, ""); + transferFromWithData(_from, _to, _value, ""); + return true; } /** - * @notice Overloaded version of the transferFrom function - * @param _from sender of transfer - * @param _to receiver of transfer - * @param _value value of transfer - * @param _data data to indicate validation - * @return bool success + * @notice Transfer restrictions can take many forms and typically involve on-chain rules or whitelists. + * However for many types of approved transfers, maintaining an on-chain list of approved transfers can be + * cumbersome and expensive. An alternative is the co-signing approach, where in addition to the token holder + * approving a token transfer, and authorised entity provides signed data which further validates the transfer. + * @dev `msg.sender` MUST have a sufficient `allowance` set and this `allowance` must be debited by the `_value`. + * @param _from address The address which you want to send tokens from + * @param _to address The address which you want to transfer to + * @param _value uint256 the amount of tokens to be transferred + * @param _data The `bytes _data` allows arbitrary data to be submitted alongside the transfer. + * for the token contract to interpret or record. This could be signed data authorising the transfer + * (e.g. a dynamic whitelist) but is flexible enough to accomadate other use-cases. */ - function transferFromWithData(address _from, address _to, uint256 _value, bytes _data) public returns(bool) { - require(_updateTransfer(_from, _to, _value, _data), "Transfer invalid"); + function transferFromWithData(address _from, address _to, uint256 _value, bytes memory _data) public { + _isValidTransfer(_updateTransfer(_from, _to, _value, _data)); require(super.transferFrom(_from, _to, _value)); - return true; + } + + /** + * @notice Get the balance according to the provided partitions + * @param _partition Partition which differentiate the tokens. + * @param _tokenHolder Whom balance need to queried + * @return Amount of tokens as per the given partitions + */ + function balanceOfByPartition(bytes32 _partition, address _tokenHolder) public view returns(uint256) { + return _balanceOfByPartition(_partition, _tokenHolder, 0); + } + + function _balanceOfByPartition(bytes32 _partition, address _tokenHolder, uint256 _additionalBalance) internal view returns(uint256 partitionBalance) { + address[] memory tms = modules[TRANSFER_KEY]; + uint256 amount; + for (uint256 i = 0; i < tms.length; i++) { + amount = ITransferManager(tms[i]).getTokensByPartition(_partition, _tokenHolder, _additionalBalance); + // In UNLOCKED partition we are returning the minimum of all the unlocked balances + if (_partition == UNLOCKED) { + if (amount < partitionBalance || i == 0) + partitionBalance = amount; + } + // In locked partition we are returning the maximum of all the Locked balances + else { + if (partitionBalance < amount) + partitionBalance = amount; + } + } + } + + /** + * @notice Transfers the ownership of tokens from a specified partition from one address to another address + * @param _partition The partition from which to transfer tokens + * @param _to The address to which to transfer tokens to + * @param _value The amount of tokens to transfer from `_partition` + * @param _data Additional data attached to the transfer of tokens + * @return The partition to which the transferred tokens were allocated for the _to address + */ + function transferByPartition(bytes32 _partition, address _to, uint256 _value, bytes memory _data) public returns (bytes32) { + return _transferByPartition(msg.sender, _to, _value, _partition, _data, address(0), ""); + } + + function _transferByPartition( + address _from, + address _to, + uint256 _value, + bytes32 _partition, + bytes memory _data, + address _operator, + bytes memory _operatorData + ) + internal + returns(bytes32 toPartition) + { + _isValidPartition(_partition); + // Avoiding to add this check + // require(balanceOfByPartition(_partition, msg.sender) >= _value); + // NB - Above condition will be automatically checked using the executeTransfer() function execution. + // NB - passing `_additionalBalance` value is 0 because accessing the balance before transfer + uint256 lockedBalanceBeforeTransfer = _balanceOfByPartition(LOCKED, _to, 0); + _transferWithData(_from, _to, _value, _data); + // NB - passing `_additonalBalance` valie is 0 because balance of `_to` was updated in the transfer call + uint256 lockedBalanceAfterTransfer = _balanceOfByPartition(LOCKED, _to, 0); + toPartition = _returnPartition(lockedBalanceBeforeTransfer, lockedBalanceAfterTransfer, _value); + emit TransferByPartition(_partition, _operator, _from, _to, _value, _data, _operatorData); + } + + function _returnPartition(uint256 _beforeBalance, uint256 _afterBalance, uint256 _value) internal pure returns(bytes32 toPartition) { + // return LOCKED only when the transaction `_value` should be equal to the change in the LOCKED partition + // balance otherwise return UNLOCKED + toPartition = _afterBalance.sub(_beforeBalance) == _value ? LOCKED : UNLOCKED; // Returning the same partition UNLOCKED + } + + /////////////////////// + /// Operator Management + /////////////////////// + + /** + * @notice Authorises an operator for all partitions of `msg.sender`. + * NB - Allowing investors to authorize an investor to be an operator of all partitions + * but it doesn't mean we operator is allowed to transfer the LOCKED partition values. + * Logic for this restriction is written in `operatorTransferByPartition()` function. + * @param _operator An address which is being authorised. + */ + function authorizeOperator(address _operator) public { + _approve(msg.sender, _operator, uint(-1)); + emit AuthorizedOperator(_operator, msg.sender); + } + + /** + * @notice Revokes authorisation of an operator previously given for all partitions of `msg.sender`. + * NB - Allowing investors to authorize an investor to be an operator of all partitions + * but it doesn't mean we operator is allowed to transfer the LOCKED partition values. + * Logic for this restriction is written in `operatorTransferByPartition()` function. + * @param _operator An address which is being de-authorised + */ + function revokeOperator(address _operator) public { + _approve(msg.sender, _operator, 0); + emit RevokedOperator(_operator, msg.sender); + } + + /** + * @notice Authorises an operator for a given partition of `msg.sender` + * @param _partition The partition to which the operator is authorised + * @param _operator An address which is being authorised + */ + function authorizeOperatorByPartition(bytes32 _partition, address _operator) public { + _isValidPartition(_partition); + partitionApprovals[msg.sender][_partition][_operator] = true; + emit AuthorizedOperatorByPartition(_partition, _operator, msg.sender); + } + + /** + * @notice Revokes authorisation of an operator previously given for a specified partition of `msg.sender` + * @param _partition The partition to which the operator is de-authorised + * @param _operator An address which is being de-authorised + */ + function revokeOperatorByPartition(bytes32 _partition, address _operator) public { + _isValidPartition(_partition); + partitionApprovals[msg.sender][_partition][_operator] = false; + emit RevokedOperatorByPartition(_partition, _operator, msg.sender); + } + + /** + * @notice Transfers the ownership of tokens from a specified partition from one address to another address + * @param _partition The partition from which to transfer tokens. + * @param _from The address from which to transfer tokens from + * @param _to The address to which to transfer tokens to + * @param _value The amount of tokens to transfer from `_partition` + * @param _data Additional data attached to the transfer of tokens + * @param _operatorData Additional data attached to the transfer of tokens by the operator + * @return The partition to which the transferred tokens were allocated for the _to address + */ + function operatorTransferByPartition( + bytes32 _partition, + address _from, + address _to, + uint256 _value, + bytes calldata _data, + bytes calldata _operatorData + ) + external + returns (bytes32) + { + // For the current release we are only allowing UNLOCKED partition tokens to transact + _validateOperatorAndPartition(_partition, _from, msg.sender); + require(_operatorData[0] != 0); + return _transferByPartition(_from, _to, _value, _partition, _data, msg.sender, _operatorData); + } + + function _validateOperatorAndPartition(bytes32 _partition, address _from, address _operator) internal view { + _isValidPartition(_partition); + _isValidOperator(_from, _operator, _partition); } /** @@ -569,53 +636,52 @@ contract SecurityToken is StandardToken, DetailedERC20, ReentrancyGuard, Registr * @param _data data to indicate validation * @return bool success */ - function _updateTransfer(address _from, address _to, uint256 _value, bytes _data) internal nonReentrant returns(bool) { + function _updateTransfer(address _from, address _to, uint256 _value, bytes memory _data) internal nonReentrant returns(bool verified) { // NB - the ordering in this function implies the following: // - investor counts are updated before transfer managers are called - i.e. transfer managers will see //investor counts including the current transfer. // - checkpoints are updated after the transfer managers are called. This allows TMs to create //checkpoints as though they have been created before the current transactions, // - to avoid the situation where a transfer manager transfers tokens, and this function is called recursively, - //the function is marked as nonReentrant. This means that no TM can transfer (or mint / burn) tokens. + //the function is marked as nonReentrant. This means that no TM can transfer (or mint / burn) tokens in the execute transfer function. _adjustInvestorCount(_from, _to, _value); - bool verified = _verifyTransfer(_from, _to, _value, _data, true); + verified = _executeTransfer(_from, _to, _value, _data); _adjustBalanceCheckpoints(_from); _adjustBalanceCheckpoints(_to); - return verified; } /** * @notice Validate transfer with TransferManager module if it exists * @dev TransferManager module has a key of 2 - * @dev _isTransfer boolean flag is the deciding factor for whether the - * state variables gets modified or not within the different modules. i.e isTransfer = true - * leads to change in the modules environment otherwise _verifyTransfer() works as a read-only * function (no change in the state). * @param _from sender of transfer * @param _to receiver of transfer * @param _value value of transfer * @param _data data to indicate validation - * @param _isTransfer whether transfer is being executed * @return bool */ - function _verifyTransfer( + function _executeTransfer( address _from, address _to, uint256 _value, - bytes _data, - bool _isTransfer - ) internal checkGranularity(_value) returns (bool) { + bytes memory _data + ) + internal + checkGranularity(_value) + returns(bool) + { if (!transfersFrozen) { - bool isInvalid = false; - bool isValid = false; - bool isForceValid = false; - bool unarchived = false; + bool isInvalid; + bool isValid; + bool isForceValid; address module; - for (uint256 i = 0; i < modules[TRANSFER_KEY].length; i++) { + uint256 tmLength = modules[TRANSFER_KEY].length; + for (uint256 i = 0; i < tmLength; i++) { module = modules[TRANSFER_KEY][i]; if (!modulesToData[module].isArchived) { - unarchived = true; - ITransferManager.Result valid = ITransferManager(module).verifyTransfer(_from, _to, _value, _data, _isTransfer); + // refer to https://github.com/PolymathNetwork/polymath-core/wiki/Transfer-manager-results + // for understanding what these results mean + ITransferManager.Result valid = ITransferManager(module).executeTransfer(_from, _to, _value, _data); if (valid == ITransferManager.Result.INVALID) { isInvalid = true; } else if (valid == ITransferManager.Result.VALID) { @@ -625,236 +691,419 @@ contract SecurityToken is StandardToken, DetailedERC20, ReentrancyGuard, Registr } } } - // If no unarchived modules, return true by default - return unarchived ? (isForceValid ? true : (isInvalid ? false : isValid)) : true; + return isForceValid ? true : (isInvalid ? false : isValid); } return false; } /** - * @notice Validates a transfer with a TransferManager module if it exists - * @dev TransferManager module has a key of 2 - * @param _from sender of transfer - * @param _to receiver of transfer - * @param _value value of transfer - * @param _data data to indicate validation - * @return bool + * @notice Permanently freeze issuance of this security token. + * @dev It MUST NOT be possible to increase `totalSuppy` after this function is called. */ - function verifyTransfer(address _from, address _to, uint256 _value, bytes _data) public returns (bool) { - return _verifyTransfer(_from, _to, _value, _data, false); + function freezeIssuance(bytes calldata _signature) external { + _onlyOwner(); + _isIssuanceAllowed(); + _isSignedByOwner(owner() == TokenLib.recoverFreezeIssuanceAckSigner(_signature)); + issuance = false; + /*solium-disable-next-line security/no-block-members*/ + emit FreezeIssuance(); } /** - * @notice Permanently freeze minting of this security token. - * @dev It MUST NOT be possible to increase `totalSuppy` after this function is called. + * @notice This function must be called to increase the total supply (Corresponds to mint function of ERC20). + * @dev It only be called by the token issuer or the operator defined by the issuer. ERC1594 doesn't have + * have the any logic related to operator but its superset ERC1400 have the operator logic and this function + * is allowed to call by the operator. + * @param _tokenHolder The account that will receive the created tokens (account should be whitelisted or KYCed). + * @param _value The amount of tokens need to be issued + * @param _data The `bytes _data` allows arbitrary data to be submitted alongside the transfer. */ - function freezeMinting() external isMintingAllowed() isEnabled("freezeMintingAllowed") onlyOwner { - mintingFrozen = true; - /*solium-disable-next-line security/no-block-members*/ - emit FreezeMinting(now); + function issue( + address _tokenHolder, + uint256 _value, + bytes memory _data + ) + public // changed to public to save the code size and reuse the function + { + _isIssuanceAllowed(); + _onlyModuleOrOwner(MINT_KEY); + _issue(_tokenHolder, _value, _data); + } + + function _issue( + address _tokenHolder, + uint256 _value, + bytes memory _data + ) + internal + { + // Add a function to validate the `_data` parameter + _isValidTransfer(_updateTransfer(address(0), _tokenHolder, _value, _data)); + _mint(_tokenHolder, _value); + emit Issued(msg.sender, _tokenHolder, _value, _data); } /** - * @notice Mints new tokens and assigns them to the target _investor. - * @dev Can only be called by the issuer or STO attached to the token - * @param _investor Address where the minted tokens will be delivered - * @param _value Number of tokens be minted + * @notice issue new tokens and assigns them to the target _tokenHolder. + * @dev Can only be called by the issuer or STO attached to the token. + * @param _tokenHolders A list of addresses to whom the minted tokens will be dilivered + * @param _values A list of number of tokens get minted and transfer to corresponding address of the investor from _tokenHolders[] list * @return success */ - function mint(address _investor, uint256 _value) public returns (bool success) { - return mintWithData(_investor, _value, ""); + function issueMulti(address[] memory _tokenHolders, uint256[] memory _values) public { + _isIssuanceAllowed(); + _onlyModuleOrOwner(MINT_KEY); + // Remove reason string to reduce the code size + require(_tokenHolders.length == _values.length); + for (uint256 i = 0; i < _tokenHolders.length; i++) { + _issue(_tokenHolders[i], _values[i], ""); + } } /** - * @notice mints new tokens and assigns them to the target _investor. - * @dev Can only be called by the issuer or STO attached to the token - * @param _investor Address where the minted tokens will be delivered - * @param _value Number of tokens be minted - * @param _data data to indicate validation - * @return success + * @notice Increases totalSupply and the corresponding amount of the specified owners partition + * @param _partition The partition to allocate the increase in balance + * @param _tokenHolder The token holder whose balance should be increased + * @param _value The amount by which to increase the balance + * @param _data Additional data attached to the minting of tokens */ - function mintWithData( - address _investor, - uint256 _value, - bytes _data - ) public onlyModuleOrOwner(MINT_KEY) isMintingAllowed() returns (bool success) { - require(_investor != address(0), "Investor is 0"); - require(_updateTransfer(address(0), _investor, _value, _data), "Transfer invalid"); - _adjustTotalSupplyCheckpoints(); - totalSupply_ = totalSupply_.add(_value); - balances[_investor] = balances[_investor].add(_value); - emit Minted(_investor, _value); - emit Transfer(address(0), _investor, _value); - return true; + function issueByPartition(bytes32 _partition, address _tokenHolder, uint256 _value, bytes calldata _data) external { + _isValidPartition(_partition); + //Use issue instead of _issue function in the favour to saving code size + issue(_tokenHolder, _value, _data); + emit IssuedByPartition(_partition, _tokenHolder, _value, _data); } /** - * @notice Mints new tokens and assigns them to the target _investor. - * @dev Can only be called by the issuer or STO attached to the token. - * @param _investors A list of addresses to whom the minted tokens will be dilivered - * @param _values A list of number of tokens get minted and transfer to corresponding address of the investor from _investor[] list - * @return success + * @notice This function redeem an amount of the token of a msg.sender. For doing so msg.sender may incentivize + * using different ways that could be implemented with in the `redeem` function definition. But those implementations + * are out of the scope of the ERC1594. + * @param _value The amount of tokens need to be redeemed + * @param _data The `bytes _data` it can be used in the token contract to authenticate the redemption. */ - function mintMulti(address[] _investors, uint256[] _values) external returns (bool success) { - require(_investors.length == _values.length, "Incorrect inputs"); - for (uint256 i = 0; i < _investors.length; i++) { - mint(_investors[i], _values[i]); - } - return true; + function redeem(uint256 _value, bytes calldata _data) external { + _onlyModule(BURN_KEY); + _redeem(msg.sender, _value, _data); + } + + function _redeem(address _from, uint256 _value, bytes memory _data) internal { + // Add a function to validate the `_data` parameter + _isValidRedeem(_checkAndBurn(_from, _value, _data)); } /** - * @notice Validate permissions with PermissionManager if it exists, If no Permission return false - * @dev Note that IModule withPerm will allow ST owner all permissions anyway - * @dev this allows individual modules to override this logic if needed (to not allow ST owner all permissions) - * @param _delegate address of delegate - * @param _module address of PermissionManager module - * @param _perm the permissions - * @return success + * @notice Decreases totalSupply and the corresponding amount of the specified partition of msg.sender + * @param _partition The partition to allocate the decrease in balance + * @param _value The amount by which to decrease the balance + * @param _data Additional data attached to the burning of tokens */ - function checkPermission(address _delegate, address _module, bytes32 _perm) public view returns(bool) { - for (uint256 i = 0; i < modules[PERMISSION_KEY].length; i++) { - if (!modulesToData[modules[PERMISSION_KEY][i]].isArchived) - return TokenLib.checkPermission(modules[PERMISSION_KEY], _delegate, _module, _perm); - } - return false; + function redeemByPartition(bytes32 _partition, uint256 _value, bytes calldata _data) external { + _onlyModule(BURN_KEY); + _isValidPartition(_partition); + _redeemByPartition(_partition, msg.sender, _value, address(0), _data, ""); } - function _burn(address _from, uint256 _value, bytes _data) internal returns(bool) { - require(_value <= balances[_from], "Value too high"); - bool verified = _updateTransfer(_from, address(0), _value, _data); - _adjustTotalSupplyCheckpoints(); - balances[_from] = balances[_from].sub(_value); - totalSupply_ = totalSupply_.sub(_value); - emit Burnt(_from, _value); - emit Transfer(_from, address(0), _value); - return verified; + function _redeemByPartition( + bytes32 _partition, + address _from, + uint256 _value, + address _operator, + bytes memory _data, + bytes memory _operatorData + ) + internal + { + _redeem(_from, _value, _data); + emit RedeemedByPartition(_partition, _operator, _from, _value, _data, _operatorData); } /** - * @notice Burn function used to burn the securityToken - * @param _value No. of tokens that get burned - * @param _data data to indicate validation + * @notice Decreases totalSupply and the corresponding amount of the specified partition of tokenHolder + * @dev This function can only be called by the authorised operator. + * @param _partition The partition to allocate the decrease in balance. + * @param _tokenHolder The token holder whose balance should be decreased + * @param _value The amount by which to decrease the balance + * @param _data Additional data attached to the burning of tokens + * @param _operatorData Additional data attached to the transfer of tokens by the operator */ - function burnWithData(uint256 _value, bytes _data) public onlyModule(BURN_KEY) { - require(_burn(msg.sender, _value, _data), "Burn invalid"); + function operatorRedeemByPartition( + bytes32 _partition, + address _tokenHolder, + uint256 _value, + bytes calldata _data, + bytes calldata _operatorData + ) + external + { + _onlyModule(BURN_KEY); + require(_operatorData[0] != 0); + _zeroAddressCheck(_tokenHolder); + _validateOperatorAndPartition(_partition, _tokenHolder, msg.sender); + _redeemByPartition(_partition, _tokenHolder, _value, msg.sender, _data, _operatorData); + } + + function _checkAndBurn(address _from, uint256 _value, bytes memory _data) internal returns(bool verified) { + verified = _updateTransfer(_from, address(0), _value, _data); + _burn(_from, _value); + emit Redeemed(address(0), msg.sender, _value, _data); } /** - * @notice Burn function used to burn the securityToken on behalf of someone else - * @param _from Address for whom to burn tokens - * @param _value No. of tokens that get burned - * @param _data data to indicate validation + * @notice This function redeem an amount of the token of a msg.sender. For doing so msg.sender may incentivize + * using different ways that could be implemented with in the `redeem` function definition. But those implementations + * are out of the scope of the ERC1594. + * @dev It is analogy to `transferFrom` + * @param _tokenHolder The account whose tokens gets redeemed. + * @param _value The amount of tokens need to be redeemed + * @param _data The `bytes _data` it can be used in the token contract to authenticate the redemption. */ - function burnFromWithData(address _from, uint256 _value, bytes _data) public onlyModule(BURN_KEY) { - require(_value <= allowed[_from][msg.sender], "Value too high"); - allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value); - require(_burn(_from, _value, _data), "Burn invalid"); + function redeemFrom(address _tokenHolder, uint256 _value, bytes calldata _data) external { + _onlyModule(BURN_KEY); + // Add a function to validate the `_data` parameter + _isValidRedeem(_updateTransfer(_tokenHolder, address(0), _value, _data)); + _burnFrom(_tokenHolder, _value); + emit Redeemed(msg.sender, _tokenHolder, _value, _data); } /** * @notice Creates a checkpoint that can be used to query historical balances / totalSuppy * @return uint256 */ - function createCheckpoint() external onlyModuleOrOwner(CHECKPOINT_KEY) returns(uint256) { - require(currentCheckpointId < 2**256 - 1); + function createCheckpoint() external returns(uint256) { + _onlyModuleOrOwner(CHECKPOINT_KEY); + // currentCheckpointId can only be incremented by 1 and hence it can not be overflowed currentCheckpointId = currentCheckpointId + 1; /*solium-disable-next-line security/no-block-members*/ checkpointTimes.push(now); - /*solium-disable-next-line security/no-block-members*/ - emit CheckpointCreated(currentCheckpointId, now); + checkpointTotalSupply[currentCheckpointId] = totalSupply(); + emit CheckpointCreated(currentCheckpointId, dataStore.getAddressArrayLength(INVESTORSKEY)); return currentCheckpointId; } /** - * @notice Gets list of times that checkpoints were created - * @return List of checkpoint times + * @notice Used by the issuer to set the controller addresses + * @param _controller address of the controller */ - function getCheckpointTimes() external view returns(uint256[]) { - return checkpointTimes; + function setController(address _controller) external { + _onlyOwner(); + require(isControllable()); + emit SetController(controller, _controller); + controller = _controller; } /** - * @notice Queries totalSupply as of a defined checkpoint - * @param _checkpointId Checkpoint ID to query - * @return uint256 + * @notice Used by the issuer to permanently disable controller functionality + * @dev enabled via feature switch "disableControllerAllowed" */ - function totalSupplyAt(uint256 _checkpointId) external view returns(uint256) { - require(_checkpointId <= currentCheckpointId); - return TokenLib.getValueAt(checkpointTotalSupply, _checkpointId, totalSupply()); + function disableController(bytes calldata _signature) external { + _onlyOwner(); + _isSignedByOwner(owner() == TokenLib.recoverDisableControllerAckSigner(_signature)); + require(isControllable()); + controllerDisabled = true; + delete controller; + emit DisableController(); } /** - * @notice Queries balances as of a defined checkpoint - * @param _investor Investor to query balance for - * @param _checkpointId Checkpoint ID to query as of + * @notice Transfers of securities may fail for a number of reasons. So this function will used to understand the + * cause of failure by getting the byte value. Which will be the ESC that follows the EIP 1066. ESC can be mapped + * with a reson string to understand the failure cause, table of Ethereum status code will always reside off-chain + * @param _to address The address which you want to transfer to + * @param _value uint256 the amount of tokens to be transferred + * @param _data The `bytes _data` allows arbitrary data to be submitted alongside the transfer. + * @return byte Ethereum status code (ESC) + * @return bytes32 Application specific reason code */ - function balanceOfAt(address _investor, uint256 _checkpointId) public view returns(uint256) { - require(_checkpointId <= currentCheckpointId); - return TokenLib.getValueAt(checkpointBalances[_investor], _checkpointId, balanceOf(_investor)); + function canTransfer(address _to, uint256 _value, bytes calldata _data) external view returns (byte, bytes32) { + return _canTransfer(msg.sender, _to, _value, _data); } /** - * @notice Used by the issuer to set the controller addresses - * @param _controller address of the controller + * @notice Transfers of securities may fail for a number of reasons. So this function will used to understand the + * cause of failure by getting the byte value. Which will be the ESC that follows the EIP 1066. ESC can be mapped + * with a reson string to understand the failure cause, table of Ethereum status code will always reside off-chain + * @param _from address The address which you want to send tokens from + * @param _to address The address which you want to transfer to + * @param _value uint256 the amount of tokens to be transferred + * @param _data The `bytes _data` allows arbitrary data to be submitted alongside the transfer. + * @return byte Ethereum status code (ESC) + * @return bytes32 Application specific reason code */ - function setController(address _controller) public onlyOwner { - require(!controllerDisabled); - emit SetController(controller, _controller); - controller = _controller; + function canTransferFrom(address _from, address _to, uint256 _value, bytes calldata _data) external view returns (byte reasonCode, bytes32 appCode) { + (reasonCode, appCode) = _canTransfer(_from, _to, _value, _data); + if (_isSuccess(reasonCode) && _value > allowance(_from, msg.sender)) { + return (StatusCodes.code(StatusCodes.Status.InsufficientAllowance), bytes32(0)); + } + } + + function _canTransfer(address _from, address _to, uint256 _value, bytes memory _data) internal view returns (byte, bytes32) { + bytes32 appCode; + bool success; + if (_value % granularity != 0) { + return (StatusCodes.code(StatusCodes.Status.TransferFailure), bytes32(0)); + } + (success, appCode) = TokenLib.verifyTransfer(modules[TRANSFER_KEY], modulesToData, _from, _to, _value, _data, transfersFrozen); + return TokenLib.canTransfer(success, appCode, _to, _value, balanceOf(_from)); } /** - * @notice Used by the issuer to permanently disable controller functionality - * @dev enabled via feature switch "disableControllerAllowed" + * @notice The standard provides an on-chain function to determine whether a transfer will succeed, + * and return details indicating the reason if the transfer is not valid. + * @param _from The address from whom the tokens get transferred. + * @param _to The address to which to transfer tokens to. + * @param _partition The partition from which to transfer tokens + * @param _value The amount of tokens to transfer from `_partition` + * @param _data Additional data attached to the transfer of tokens + * @return ESC (Ethereum Status Code) following the EIP-1066 standard + * @return Application specific reason codes with additional details + * @return The partition to which the transferred tokens were allocated for the _to address */ - function disableController() external isEnabled("disableControllerAllowed") onlyOwner { - require(!controllerDisabled); - controllerDisabled = true; - delete controller; - /*solium-disable-next-line security/no-block-members*/ - emit DisableController(now); + function canTransferByPartition( + address _from, + address _to, + bytes32 _partition, + uint256 _value, + bytes calldata _data + ) + external + view + returns (byte reasonCode, bytes32 appStatusCode, bytes32 toPartition) + { + if (_partition == UNLOCKED) { + (reasonCode, appStatusCode) = _canTransfer(_from, _to, _value, _data); + if (_isSuccess(reasonCode)) { + uint256 beforeBalance = _balanceOfByPartition(LOCKED, _to, 0); + uint256 afterbalance = _balanceOfByPartition(LOCKED, _to, _value); + toPartition = _returnPartition(beforeBalance, afterbalance, _value); + } + return (reasonCode, appStatusCode, toPartition); + } + return (StatusCodes.code(StatusCodes.Status.TransferFailure), bytes32(0), bytes32(0)); } /** - * @notice Used by a controller to execute a forced transfer - * @param _from address from which to take tokens - * @param _to address where to send tokens - * @param _value amount of tokens to transfer - * @param _data data to indicate validation - * @param _log data attached to the transfer by controller to emit in event + * @notice Used to attach a new document to the contract, or update the URI or hash of an existing attached document + * @dev Can only be executed by the owner of the contract. + * @param _name Name of the document. It should be unique always + * @param _uri Off-chain uri of the document from where it is accessible to investors/advisors to read. + * @param _documentHash hash (of the contents) of the document. */ - function forceTransfer(address _from, address _to, uint256 _value, bytes _data, bytes _log) public onlyController { - require(_to != address(0)); - require(_value <= balances[_from]); - bool verified = _updateTransfer(_from, _to, _value, _data); - balances[_from] = balances[_from].sub(_value); - balances[_to] = balances[_to].add(_value); - emit ForceTransfer(msg.sender, _from, _to, _value, verified, _log); - emit Transfer(_from, _to, _value); + function setDocument(bytes32 _name, string calldata _uri, bytes32 _documentHash) external { + _onlyOwner(); + TokenLib.setDocument(_documents, _docNames, _docIndexes, _name, _uri, _documentHash); } /** - * @notice Used by a controller to execute a forced burn - * @param _from address from which to take tokens - * @param _value amount of tokens to transfer - * @param _data data to indicate validation - * @param _log data attached to the transfer by controller to emit in event + * @notice Used to remove an existing document from the contract by giving the name of the document. + * @dev Can only be executed by the owner of the contract. + * @param _name Name of the document. It should be unique always + */ + function removeDocument(bytes32 _name) external { + _onlyOwner(); + TokenLib.removeDocument(_documents, _docNames, _docIndexes, _name); + } + + /** + * @notice In order to provide transparency over whether `controllerTransfer` / `controllerRedeem` are useable + * or not `isControllable` function will be used. + * @dev If `isControllable` returns `false` then it always return `false` and + * `controllerTransfer` / `controllerRedeem` will always revert. + * @return bool `true` when controller address is non-zero otherwise return `false`. + */ + function isControllable() public view returns (bool) { + return !controllerDisabled; + } + + /** + * @notice This function allows an authorised address to transfer tokens between any two token holders. + * The transfer must still respect the balances of the token holders (so the transfer must be for at most + * `balanceOf(_from)` tokens) and potentially also need to respect other transfer restrictions. + * @dev This function can only be executed by the `controller` address. + * @param _from Address The address which you want to send tokens from + * @param _to Address The address which you want to transfer to + * @param _value uint256 the amount of tokens to be transferred + * @param _data data to validate the transfer. (It is not used in this reference implementation + * because use of `_data` parameter is implementation specific). + * @param _operatorData data attached to the transfer by controller to emit in event. (It is more like a reason string + * for calling this function (aka force transfer) which provides the transparency on-chain). + */ + function controllerTransfer(address _from, address _to, uint256 _value, bytes calldata _data, bytes calldata _operatorData) external { + _onlyController(); + _updateTransfer(_from, _to, _value, _data); + _transfer(_from, _to, _value); + emit ControllerTransfer(msg.sender, _from, _to, _value, _data, _operatorData); + } + + /** + * @notice This function allows an authorised address to redeem tokens for any token holder. + * The redemption must still respect the balances of the token holder (so the redemption must be for at most + * `balanceOf(_tokenHolder)` tokens) and potentially also need to respect other transfer restrictions. + * @dev This function can only be executed by the `controller` address. + * @param _tokenHolder The account whose tokens will be redeemed. + * @param _value uint256 the amount of tokens need to be redeemed. + * @param _data data to validate the transfer. (It is not used in this reference implementation + * because use of `_data` parameter is implementation specific). + * @param _operatorData data attached to the transfer by controller to emit in event. (It is more like a reason string + * for calling this function (aka force transfer) which provides the transparency on-chain). */ - function forceBurn(address _from, uint256 _value, bytes _data, bytes _log) public onlyController { - bool verified = _burn(_from, _value, _data); - emit ForceBurn(msg.sender, _from, _value, verified, _log); + function controllerRedeem(address _tokenHolder, uint256 _value, bytes calldata _data, bytes calldata _operatorData) external { + _onlyController(); + _checkAndBurn(_tokenHolder, _value, _data); + emit ControllerRedemption(msg.sender, _tokenHolder, _value, _data, _operatorData); + } + + function _implementation() internal view returns(address) { + return getterDelegate; + } + + function updateFromRegistry() public { + _onlyOwner(); + moduleRegistry = IModuleRegistry(polymathRegistry.getAddress("ModuleRegistry")); + securityTokenRegistry = ISecurityTokenRegistry(polymathRegistry.getAddress("SecurityTokenRegistry")); + polyToken = IERC20(polymathRegistry.getAddress("PolyToken")); + } + + //Ownable Functions + + /** + * @return the address of the owner. + */ + function owner() public view returns (address) { + return _owner; + } + + /** + * @return true if `msg.sender` is the owner of the contract. + */ + function isOwner() public view returns (bool) { + return msg.sender == _owner; + } + + /** + * @dev Allows the current owner to transfer control of the contract to a newOwner. + * @param newOwner The address to transfer ownership to. + */ + function transferOwnership(address newOwner) external { + _onlyOwner(); + _transferOwnership(newOwner); } /** - * @notice Returns the version of the SecurityToken + * @dev Transfers control of the contract to a newOwner. + * @param newOwner The address to transfer ownership to. */ - function getVersion() external view returns(uint8[]) { - uint8[] memory _version = new uint8[](3); - _version[0] = securityTokenVersion.major; - _version[1] = securityTokenVersion.minor; - _version[2] = securityTokenVersion.patch; - return _version; + function _transferOwnership(address newOwner) internal { + require(newOwner != address(0)); + emit OwnershipTransferred(_owner, newOwner); + _owner = newOwner; } + /** + * @dev Check if a status code represents success (ie: 0x*1) + * @param status Binary ERC-1066 status code + * @return successful A boolean representing if the status code represents success + */ + function _isSuccess(byte status) internal pure returns (bool successful) { + return (status & 0x0F) == 0x01; + } } diff --git a/contracts/tokens/SecurityTokenProxy.sol b/contracts/tokens/SecurityTokenProxy.sol new file mode 100644 index 000000000..8a5eb8d09 --- /dev/null +++ b/contracts/tokens/SecurityTokenProxy.sol @@ -0,0 +1,42 @@ +pragma solidity 0.5.8; + +import "../proxy/OwnedUpgradeabilityProxy.sol"; +import "./OZStorage.sol"; +import "./SecurityTokenStorage.sol"; + +/** + * @title USDTiered STO module Proxy + */ +contract SecurityTokenProxy is OZStorage, SecurityTokenStorage, OwnedUpgradeabilityProxy { + + /** + * @notice constructor + * @param _name Name of the SecurityToken + * @param _symbol Symbol of the Token + * @param _decimals Decimals for the securityToken + * @param _granularity granular level of the token + * @param _tokenDetails Details of the token that are stored off-chain + * @param _polymathRegistry Contract address of the polymath registry + */ + constructor( + string memory _name, + string memory _symbol, + uint8 _decimals, + uint256 _granularity, + string memory _tokenDetails, + address _polymathRegistry + ) + public + { + //Set storage variables - NB implementation not yet set + require(_polymathRegistry != address(0), "Invalid Address"); + name = _name; + symbol = _symbol; + decimals = _decimals; + polymathRegistry = IPolymathRegistry(_polymathRegistry); + tokenDetails = _tokenDetails; + granularity = _granularity; + _owner = msg.sender; + } + +} diff --git a/contracts/tokens/SecurityTokenStorage.sol b/contracts/tokens/SecurityTokenStorage.sol new file mode 100644 index 000000000..b32aa674a --- /dev/null +++ b/contracts/tokens/SecurityTokenStorage.sol @@ -0,0 +1,134 @@ +pragma solidity 0.5.8; + +import "../interfaces/IDataStore.sol"; +import "../interfaces/IModuleRegistry.sol"; +import "../interfaces/IPolymathRegistry.sol"; +import "../interfaces/ISecurityTokenRegistry.sol"; +import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; + +contract SecurityTokenStorage { + + uint8 internal constant PERMISSION_KEY = 1; + uint8 internal constant TRANSFER_KEY = 2; + uint8 internal constant MINT_KEY = 3; + uint8 internal constant CHECKPOINT_KEY = 4; + uint8 internal constant BURN_KEY = 5; + uint8 internal constant DATA_KEY = 6; + uint8 internal constant WALLET_KEY = 7; + + bytes32 internal constant INVESTORSKEY = 0xdf3a8dd24acdd05addfc6aeffef7574d2de3f844535ec91e8e0f3e45dba96731; //keccak256(abi.encodePacked("INVESTORS")) + bytes32 internal constant TREASURY = 0xaae8817359f3dcb67d050f44f3e49f982e0359d90ca4b5f18569926304aaece6; //keccak256(abi.encodePacked("TREASURY_WALLET")) + bytes32 internal constant LOCKED = "LOCKED"; + bytes32 internal constant UNLOCKED = "UNLOCKED"; + + ////////////////////////// + /// Document datastructure + ////////////////////////// + + struct Document { + bytes32 docHash; // Hash of the document + uint256 lastModified; // Timestamp at which document details was last modified + string uri; // URI of the document that exist off-chain + } + + // Used to hold the semantic version data + struct SemanticVersion { + uint8 major; + uint8 minor; + uint8 patch; + } + + // Struct for module data + struct ModuleData { + bytes32 name; + address module; + address moduleFactory; + bool isArchived; + uint8[] moduleTypes; + uint256[] moduleIndexes; + uint256 nameIndex; + bytes32 label; + } + + // Structures to maintain checkpoints of balances for governance / dividends + struct Checkpoint { + uint256 checkpointId; + uint256 value; + } + + //Naming scheme to match Ownable + address internal _owner; + address public tokenFactory; + bool public initialized; + + // ERC20 Details + string public name; + string public symbol; + uint8 public decimals; + + // Address of the controller which is a delegated entity + // set by the issuer/owner of the token + address public controller; + + IPolymathRegistry public polymathRegistry; + IModuleRegistry public moduleRegistry; + ISecurityTokenRegistry public securityTokenRegistry; + IERC20 public polyToken; + address public getterDelegate; + // Address of the data store used to store shared data + IDataStore public dataStore; + + uint256 public granularity; + + // Value of current checkpoint + uint256 public currentCheckpointId; + + // off-chain data + string public tokenDetails; + + // Used to permanently halt controller actions + bool public controllerDisabled = false; + + // Used to temporarily halt all transactions + bool public transfersFrozen; + + // Number of investors with non-zero balance + uint256 public holderCount; + + // Variable which tells whether issuance is ON or OFF forever + // Implementers need to implement one more function to reset the value of `issuance` variable + // to false. That function is not a part of the standard (EIP-1594) as it is depend on the various factors + // issuer, followed compliance rules etc. So issuers have the choice how they want to close the issuance. + bool internal issuance = true; + + // Array use to store all the document name present in the contracts + bytes32[] _docNames; + + // Times at which each checkpoint was created + uint256[] checkpointTimes; + + SemanticVersion securityTokenVersion; + + // Records added modules - module list should be order agnostic! + mapping(uint8 => address[]) modules; + + // Records information about the module + mapping(address => ModuleData) modulesToData; + + // Records added module names - module list should be order agnostic! + mapping(bytes32 => address[]) names; + + // Mapping of checkpoints that relate to total supply + mapping (uint256 => uint256) checkpointTotalSupply; + + // Map each investor to a series of checkpoints + mapping(address => Checkpoint[]) checkpointBalances; + + // mapping to store the documents details in the document + mapping(bytes32 => Document) internal _documents; + // mapping to store the document name indexes + mapping(bytes32 => uint256) internal _docIndexes; + // Mapping from (investor, partition, operator) to approved status + mapping (address => mapping (bytes32 => mapping (address => bool))) partitionApprovals; + +} diff --git a/docs/ethereum_status_codes.md b/docs/ethereum_status_codes.md new file mode 100644 index 000000000..60a2ff0d9 --- /dev/null +++ b/docs/ethereum_status_codes.md @@ -0,0 +1,46 @@ + +# For ERC1400 + +| Code | Reason | +| ------ | ------------------------------------------------------------- | +| `0x50` | transfer failure | +| `0x51` | transfer success | +| `0x52` | insufficient balance | +| `0x53` | insufficient allowance | +| `0x54` | transfers halted (contract paused) | +| `0x55` | funds locked (lockup period) | +| `0x56` | invalid sender | +| `0x57` | invalid receiver | +| `0x58` | invalid operator (transfer agent) | +| `0x59` | | +| `0x5a` | | +| `0x5b` | | +| `0x5a` | | +| `0x5b` | | +| `0x5c` | | +| `0x5d` | | +| `0x5e` | | +| `0x5f` | token meta or info + +# For Application specific (Polymath) + +| Code | Reason | +| ------ | ------------------------------------------------------------- | +| `0xA0` | Not affected | +| `0xA1` | Success | +| `0xA2` | Max holders reach | +| `0xA3` | Manual Approval Expired | +| `0xA4` | funds limit reached | +| `0xA5` | Tx Volume limit reached | +| `0xA6` | Blacklisted tx | +| `0xA7` | funds locked (lockup period) | +| `0xA8` | Invalid granularity | +| `0xA9` | | +| `0xAa` | | +| `0xAb` | | +| `0xAa` | | +| `0xAb` | | +| `0xAc` | | +| `0xAd` | | +| `0xAe` | | +| `0xAf` | token meta or info | diff --git a/docs/investor_flags.md b/docs/investor_flags.md new file mode 100644 index 000000000..0b8fb9c38 --- /dev/null +++ b/docs/investor_flags.md @@ -0,0 +1,28 @@ +# Flags List + + + + + + + + + + + + + + + + + + + + + + + + + + +
Flag Name Description
0 isAccredited Defines if an Investor is Accredited or not. True for Accredited, false for not Accredited.
1 canNotBuyFromSto Defines if an Investor is restricted from participating in STOs
2 isVolRestricted Defines if an Investor has the trade volume restriction (VRTM) or not
diff --git a/docs/permissions_list.md b/docs/permissions_list.md index 9e7a213db..37e4dd654 100644 --- a/docs/permissions_list.md +++ b/docs/permissions_list.md @@ -1,4 +1,4 @@ -# Permissions List +# Permissions List @@ -14,14 +14,23 @@ - + + + + + + + + + + - + @@ -42,26 +51,25 @@ - + + + - + - - - - + - + - + @@ -82,20 +90,13 @@ - + + + + - - - - - - - - - - - + @@ -104,10 +105,10 @@ - + - + @@ -115,16 +116,22 @@ - + - + + + + + + + @@ -132,26 +139,29 @@ - + + + + - + - + - + - + @@ -169,31 +179,50 @@ - - + + + + + + + + + + - + + + - - - + - + - - - + + + + + + + + + + + + + + + - @@ -290,14 +319,18 @@ - - + + - + - + + + + + @@ -320,9 +353,6 @@ - - - @@ -388,8 +418,3 @@
Checkpoint ERC20DividendCheckpoint pushDividendPayment() withPerm(DISTRIBUTE) withPerm(OPERATOR)
pushDividendPaymentToAddresses()
createCheckpoint()
reclaimDividend()
withdrawWithholding()
setDefaultExcluded() withPerm(MANAGE) withPerm(ADMIN)
setWithholding() createDividendWithCheckpointAndExclusions()
reclaimDividend() EtherDividendCheckpointpushDividendPayment() withPerm(OPERATOR)
withdrawWithholding() pushDividendPaymentToAddresses()
createCheckpoint() withPerm(CHECKPOINT)
EtherDividendCheckpointpushDividendPayment() withPerm(DISTRIBUTE) reclaimDividend()
pushDividendPaymentToAddresses() withdrawWithholding()
setDefaultExcluded() withPerm(MANAGE) withPerm(ADMIN)
setWithholding() createDividendWithCheckpointAndExclusions()
reclaimDividend() PermissionManager GeneralPermissionManageraddDelegate() withPerm(ADMIN)
withdrawWithholding()
createCheckpoint() withPerm(CHECKPOINT)
PermissionManager GeneralPermissionManageraddDelegate() withPerm(CHANGE_PERMISSION) deleteDelegate()
changePermission() changePermissionMulti()
STOSTO CappedSTO - - -
DummySTO -
USDTieredSTO USDTieredSTO modifyFunding() onlyOwner onlyOwner
modifyLimits()
modifyFunding()
modifyTiers()
modifyTimes()
modifyAddresses()
finalize()
changeAccredited() changeNonAccreditedLimit()
changeAllowBeneficialInvestments()
PreSaleSTO allocateTokens()withPerm(PRE_SALE_ADMIN)withPerm(ADMIN)
allocateTokensMulti()
TransferManagerTransferManager CountTransferManager changeHolderCount() withPerm(ADMIN)
GeneralTransferManagerGeneralTransferManager changeIssuanceAddress()withPerm(FLAGS)withPerm(ADMIN)
changeSigningAddress()changeAllowAllBurnTransfers()
modifyWhitelist()withPerm(WHITELIST)modifyKYCData()
modifyKYCDataMulti()
modifyInvestorFlag
modifyInvestorFlagMulti
modifyWhitelistMulti()ManualApprovalTransferManageraddManualApproval()withPerm(ADMIN)
ManualApprovalTransferManageraddManualApproval()withPerm(TRANSFER_APPROVAL)addManualApproval()
revokeManualApproval()addManualApprovalMulti()
PercentageTransferManagermodifyWhitelist()withPerm(WHITELIST)modifyManualApproval()
modifyManualApprovalMulti()
revokeManualApproval()
revokeManualApprovalMulti()
PercentageTransferManagermodifyWhitelist()withPerm(ADMIN)
modifyWhitelistMulti()
setAllowPrimaryIssuance() withPerm(ADMIN)
changeHolderPercentage() onlyOwner
depositTokens()withPerm(ADMIN)sendToTreasurywithPerm(OPERATOR)
sendToTreasury()pushAvailableTokens()
pushAvailableTokens()pushAvailableTokensMulti()
depositTokens()withPerm(ADMIN)
addTemplate()
revokeAllSchedules()
pushAvailableTokensMulti()
addScheduleMulti()
- - - - - diff --git a/migrations/1_deploy_token.js b/migrations/1_deploy_token.js index 47f0e97a9..dd22d1ee1 100644 --- a/migrations/1_deploy_token.js +++ b/migrations/1_deploy_token.js @@ -1,9 +1,8 @@ -const DevPolyToken = artifacts.require('./helpers/PolyTokenFaucet.sol') -const Web3 = require('web3') -web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545')) +const DevPolyToken = artifacts.require("./PolyTokenFaucet.sol"); +const Web3 = require("web3"); +web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); -module.exports = function (deployer, network, accounts) { - const PolymathAccount = accounts[0] - return deployer.deploy(DevPolyToken, {from: PolymathAccount}).then(() => {}) - -} \ No newline at end of file +module.exports = function(deployer, network, accounts) { + const PolymathAccount = accounts[0]; + return deployer.deploy(DevPolyToken, { from: PolymathAccount }).then(() => {}); +}; diff --git a/migrations/2_deploy_contracts.js b/migrations/2_deploy_contracts.js index ff27206a9..988c7e9de 100644 --- a/migrations/2_deploy_contracts.js +++ b/migrations/2_deploy_contracts.js @@ -1,422 +1,628 @@ -const PolymathRegistry = artifacts.require('./PolymathRegistry.sol') -const GeneralTransferManagerFactory = artifacts.require('./GeneralTransferManagerFactory.sol') -const GeneralTransferManagerLogic = artifacts.require('./GeneralTransferManager.sol') -const GeneralPermissionManagerFactory = artifacts.require('./GeneralPermissionManagerFactory.sol') -const PercentageTransferManagerFactory = artifacts.require('./PercentageTransferManagerFactory.sol') -const USDTieredSTOLogic = artifacts.require('./USDTieredSTO.sol'); -const CountTransferManagerFactory = artifacts.require('./CountTransferManagerFactory.sol') -const EtherDividendCheckpointLogic = artifacts.require('./EtherDividendCheckpoint.sol') -const ERC20DividendCheckpointLogic = artifacts.require('./ERC20DividendCheckpoint.sol') -const EtherDividendCheckpointFactory = artifacts.require('./EtherDividendCheckpointFactory.sol') -const ERC20DividendCheckpointFactory = artifacts.require('./ERC20DividendCheckpointFactory.sol') -const LockUpTransferManagerFactory = artifacts.require('./LockUpTransferManagerFactory.sol') -const BlacklistTransferManagerFactory = artifacts.require('./BlacklistTransferManagerFactory.sol') -const VestingEscrowWalletFactory = artifacts.require('./VestingEscrowWalletFactory.sol'); -const VestingEscrowWalletLogic = artifacts.require('./VestingEscrowWallet.sol'); -const ModuleRegistry = artifacts.require('./ModuleRegistry.sol'); -const ModuleRegistryProxy = artifacts.require('./ModuleRegistryProxy.sol'); -const ManualApprovalTransferManagerFactory = artifacts.require('./ManualApprovalTransferManagerFactory.sol') -const CappedSTOFactory = artifacts.require('./CappedSTOFactory.sol') -const USDTieredSTOFactory = artifacts.require('./USDTieredSTOFactory.sol') -const SecurityTokenRegistry = artifacts.require('./SecurityTokenRegistry.sol') -const SecurityTokenRegistryProxy = artifacts.require('./SecurityTokenRegistryProxy.sol') +const PolymathRegistry = artifacts.require("./PolymathRegistry.sol"); +const GeneralTransferManagerFactory = artifacts.require("./GeneralTransferManagerFactory.sol"); +const GeneralTransferManagerLogic = artifacts.require("./GeneralTransferManager.sol"); +const GeneralPermissionManagerLogic = artifacts.require("./GeneralPermissionManager.sol"); +const GeneralPermissionManagerFactory = artifacts.require("./GeneralPermissionManagerFactory.sol"); +const PercentageTransferManagerLogic = artifacts.require("./PercentageTransferManager.sol"); +const PercentageTransferManagerFactory = artifacts.require("./PercentageTransferManagerFactory.sol"); +const USDTieredSTOLogic = artifacts.require("./USDTieredSTO.sol"); +const CountTransferManagerFactory = artifacts.require("./CountTransferManagerFactory.sol"); +const CountTransferManagerLogic = artifacts.require("./CountTransferManager.sol"); +const EtherDividendCheckpointLogic = artifacts.require("./EtherDividendCheckpoint.sol"); +const ERC20DividendCheckpointLogic = artifacts.require("./ERC20DividendCheckpoint.sol"); +const EtherDividendCheckpointFactory = artifacts.require("./EtherDividendCheckpointFactory.sol"); +const ERC20DividendCheckpointFactory = artifacts.require("./ERC20DividendCheckpointFactory.sol"); +const ModuleRegistry = artifacts.require("./ModuleRegistry.sol"); +const ModuleRegistryProxy = artifacts.require("./ModuleRegistryProxy.sol"); +const ManualApprovalTransferManagerFactory = artifacts.require("./ManualApprovalTransferManagerFactory.sol"); +const ManualApprovalTransferManagerLogic = artifacts.require("./ManualApprovalTransferManager.sol"); +const CappedSTOFactory = artifacts.require("./CappedSTOFactory.sol"); +const CappedSTOLogic = artifacts.require("./CappedSTO.sol"); +const USDTieredSTOFactory = artifacts.require("./USDTieredSTOFactory.sol"); +const SecurityTokenRegistry = artifacts.require("./SecurityTokenRegistry.sol"); +const SecurityTokenRegistryProxy = artifacts.require("./SecurityTokenRegistryProxy.sol"); +const FeatureRegistry = artifacts.require("./FeatureRegistry.sol"); +const STFactory = artifacts.require("./tokens/STFactory.sol"); +const DevPolyToken = artifacts.require("./helpers/PolyTokenFaucet.sol"); +const MockOracle = artifacts.require("./MockOracle.sol"); +const StableOracle = artifacts.require("./StableOracle.sol"); +const TokenLib = artifacts.require("./TokenLib.sol"); +const SecurityTokenLogic = artifacts.require("./tokens/SecurityToken.sol"); +const MockSecurityTokenLogic = artifacts.require("./tokens/MockSecurityTokenLogic.sol"); +const STRGetter = artifacts.require('./STRGetter.sol'); +const STGetter = artifacts.require('./STGetter.sol'); +const MockSTGetter = artifacts.require('./MockSTGetter.sol'); +const DataStoreLogic = artifacts.require('./DataStore.sol'); +const DataStoreFactory = artifacts.require('./DataStoreFactory.sol'); const VolumeRestrictionTMFactory = artifacts.require('./VolumeRestrictionTMFactory.sol') const VolumeRestrictionTMLogic = artifacts.require('./VolumeRestrictionTM.sol'); -const FeatureRegistry = artifacts.require('./FeatureRegistry.sol') -const STFactory = artifacts.require('./tokens/STFactory.sol') -const DevPolyToken = artifacts.require('./helpers/PolyTokenFaucet.sol') -const MockOracle = artifacts.require('./MockOracle.sol') -const TokenLib = artifacts.require('./TokenLib.sol'); const VolumeRestrictionLib = artifacts.require('./VolumeRestrictionLib.sol'); -const SecurityToken = artifacts.require('./tokens/SecurityToken.sol') +const VestingEscrowWalletFactory = artifacts.require('./VestingEscrowWalletFactory.sol') +const VestingEscrowWalletLogic = artifacts.require('./VestingEscrowWallet.sol'); -let BigNumber = require('bignumber.js'); -const cappedSTOSetupCost = new BigNumber(20000).times(new BigNumber(10).pow(18)); // 20K POLY fee -const usdTieredSTOSetupCost = new BigNumber(100000).times(new BigNumber(10).pow(18)); // 100K POLY fee -const initRegFee = new BigNumber(250).times(new BigNumber(10).pow(18)); // 250 POLY fee for registering ticker or security token in registry +const Web3 = require("web3"); +let BN = Web3.utils.BN; +const nullAddress = "0x0000000000000000000000000000000000000000"; +const cappedSTOSetupCost = new BN(20000).mul(new BN(10).pow(new BN(18))); // 20K POLY fee +const usdTieredSTOSetupCost = new BN(100000).mul(new BN(10).pow(new BN(18))); // 100K POLY fee +const initRegFee = new BN(250).mul(new BN(10).pow(new BN(18))); // 250 POLY fee for registering ticker or security token in registry let PolyToken; let UsdToken; let ETHOracle; let POLYOracle; +let StablePOLYOracle; -const Web3 = require('web3') - -module.exports = function (deployer, network, accounts) { - // Ethereum account address hold by the Polymath (Act as the main account which have ownable permissions) - let PolymathAccount; - let moduleRegistry; - let polymathRegistry; - let web3 - if (network === 'development') { - web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545')) - PolymathAccount = accounts[0] - PolyToken = DevPolyToken.address // Development network polytoken address - deployer.deploy(DevPolyToken, { from: PolymathAccount }).then(() => { - DevPolyToken.deployed().then((mockedUSDToken) => { - UsdToken = mockedUSDToken.address; - }); - }); - deployer.deploy(MockOracle, PolyToken, "POLY", "USD", new BigNumber(0.5).times(new BigNumber(10).pow(18)), { from: PolymathAccount }).then(() => { - MockOracle.deployed().then((mockedOracle) => { - POLYOracle = mockedOracle.address; - }); - }); - deployer.deploy(MockOracle, 0, "ETH", "USD", new BigNumber(500).times(new BigNumber(10).pow(18)), { from: PolymathAccount }).then(() => { - MockOracle.deployed().then((mockedOracle) => { - ETHOracle = mockedOracle.address; - }); - }); +module.exports = function(deployer, network, accounts) { + // Ethereum account address hold by the Polymath (Act as the main account which have ownable permissions) + let PolymathAccount; + let moduleRegistry; + let polymathRegistry; + let web3; + if (network === "development") { + web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); + PolymathAccount = accounts[0]; + PolyToken = DevPolyToken.address; // Development network polytoken address + deployer.deploy(DevPolyToken, { from: PolymathAccount }).then(() => { + DevPolyToken.deployed().then(mockedUSDToken => { + UsdToken = mockedUSDToken.address; + }); + }); + deployer + .deploy( + MockOracle, + PolyToken, + web3.utils.fromAscii("POLY"), + web3.utils.fromAscii("USD"), + new BN(5).mul(new BN(10).pow(new BN(17))), + { from: PolymathAccount } + ).then(() => { + return MockOracle.deployed(); + }).then(mockedOracle => { + POLYOracle = mockedOracle.address; + }).then(() => { + return deployer + .deploy( + StableOracle, + POLYOracle, + new BN(10).mul(new BN(10).pow(new BN(16))), + { from: PolymathAccount } + ); + }).then(() => { + return StableOracle.deployed(); + }).then(stableOracle => { + StablePOLYOracle = stableOracle.address; + }); + deployer + .deploy( + MockOracle, + nullAddress, + web3.utils.fromAscii("ETH"), + web3.utils.fromAscii("USD"), + new BN(500).mul(new BN(10).pow(new BN(18))), + { from: PolymathAccount } + ) + .then(() => { + MockOracle.deployed().then(mockedOracle => { + ETHOracle = mockedOracle.address; + }); + }); + } else if (network === "kovan") { + web3 = new Web3(new Web3.providers.HttpProvider("https://kovan.infura.io/g5xfoQ0jFSE9S5LwM1Ei")); + PolymathAccount = accounts[0]; + PolyToken = "0xb347b9f5b56b431b2cf4e1d90a5995f7519ca792"; // PolyToken Kovan Faucet Address + POLYOracle = "0x461d98EF2A0c7Ac1416EF065840fF5d4C946206C"; // Poly Oracle Kovan Address + ETHOracle = "0xCE5551FC9d43E9D2CC255139169FC889352405C8"; // ETH Oracle Kovan Address + StablePOLYOracle = ""; // TODO + } else if (network === "mainnet") { + web3 = new Web3(new Web3.providers.HttpProvider("https://mainnet.infura.io/g5xfoQ0jFSE9S5LwM1Ei")); + PolymathAccount = accounts[0]; + PolyToken = "0x9992eC3cF6A55b00978cdDF2b27BC6882d88D1eC"; // Mainnet PolyToken Address + POLYOracle = "0x52cb4616E191Ff664B0bff247469ce7b74579D1B"; // Poly Oracle Mainnet Address + ETHOracle = "0x60055e9a93aae267da5a052e95846fa9469c0e7a"; // ETH Oracle Mainnet Address + StablePOLYOracle = ""; // TODO + } + if (network === "coverage") { + web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); + PolymathAccount = accounts[0]; + PolyToken = DevPolyToken.address; // Development network polytoken address + deployer + .deploy(MockOracle, PolyToken, web3.utils.fromAscii("POLY"), web3.utils.fromAscii("USD"), new BN(0.5).mul(new BN(10).pow(new BN(18))), { from: PolymathAccount }) + .then(() => { + return MockOracle.deployed(); + }).then(mockedOracle => { + POLYOracle = mockedOracle.address; + }).then(() => { + return deployer + .deploy( + StableOracle, + POLYOracle, + new BN(10).mul(new BN(10).pow(new BN(16))), + { from: PolymathAccount } + ) + }).then(() => { + return StableOracle.deployed(); + }).then(stableOracle => { + StablePOLYOracle = stableOracle.address; + }); + deployer.deploy(MockOracle, nullAddress, web3.utils.fromAscii("ETH"), web3.utils.fromAscii("USD"), new BN(500).mul(new BN(10).pow(new BN(18))), { from: PolymathAccount }).then(() => { + MockOracle.deployed().then(mockedOracle => { + ETHOracle = mockedOracle.address; + }); + }); + } - } else if (network === 'kovan') { - web3 = new Web3(new Web3.providers.HttpProvider('https://kovan.infura.io/g5xfoQ0jFSE9S5LwM1Ei')) - PolymathAccount = accounts[0] - PolyToken = '0xb347b9f5b56b431b2cf4e1d90a5995f7519ca792' // PolyToken Kovan Faucet Address - POLYOracle = '0x461d98EF2A0c7Ac1416EF065840fF5d4C946206C' // Poly Oracle Kovan Address - ETHOracle = '0xCE5551FC9d43E9D2CC255139169FC889352405C8' // ETH Oracle Kovan Address - } else if (network === 'mainnet') { - web3 = new Web3(new Web3.providers.HttpProvider('https://mainnet.infura.io/g5xfoQ0jFSE9S5LwM1Ei')) - PolymathAccount = accounts[0] - PolyToken = '0x9992eC3cF6A55b00978cdDF2b27BC6882d88D1eC' // Mainnet PolyToken Address - POLYOracle = '0x52cb4616E191Ff664B0bff247469ce7b74579D1B' // Poly Oracle Mainnet Address - ETHOracle = '0x60055e9a93aae267da5a052e95846fa9469c0e7a' // ETH Oracle Mainnet Address - } if (network === 'coverage') { - web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545')) - PolymathAccount = accounts[0] - PolyToken = DevPolyToken.address // Development network polytoken address - deployer.deploy(MockOracle, PolyToken, "POLY", "USD", new BigNumber(0.5).times(new BigNumber(10).pow(18)), { from: PolymathAccount }).then(() => { - MockOracle.deployed().then((mockedOracle) => { - POLYOracle = mockedOracle.address; - }); - }); - deployer.deploy(MockOracle, 0, "ETH", "USD", new BigNumber(500).times(new BigNumber(10).pow(18)), { from: PolymathAccount }).then(() => { - MockOracle.deployed().then((mockedOracle) => { - ETHOracle = mockedOracle.address; - }); - }); - } + const tokenInitBytes = { + name: "initialize", + type: "function", + inputs: [ + { + type: "address", + name: "_getterDelegate" + } + ] + }; - const functionSignatureProxy = { - name: 'initialize', - type: 'function', - inputs: [{ - type: 'address', - name: '_polymathRegistry' - }, { - type: 'address', - name: '_STFactory' - }, { - type: 'uint256', - name: '_stLaunchFee' - }, { - type: 'uint256', - name: '_tickerRegFee' - }, { - type: 'address', - name: '_polyToken' - }, { - type: 'address', - name: '_owner' - }] - }; + const functionSignatureProxy = { + name: "initialize", + type: "function", + inputs: [ + { + type: "address", + name: "_polymathRegistry" + }, + { + type: "uint256", + name: "_stLaunchFee" + }, + { + type: "uint256", + name: "_tickerRegFee" + }, + { + type: "address", + name: "_owner" + }, + { + type: 'address', + name: '_getterContract' + } + ] + }; - const functionSignatureProxyMR = { - name: 'initialize', - type: 'function', - inputs: [{ - type: 'address', - name: '_polymathRegistry' - }, { - type: 'address', - name: '_owner' - }] - }; + const functionSignatureProxyMR = { + name: "initialize", + type: "function", + inputs: [ + { + type: "address", + name: "_polymathRegistry" + }, + { + type: "address", + name: "_owner" + } + ] + }; - // POLYMATH NETWORK Configuration :: DO THIS ONLY ONCE - // A) Deploy the PolymathRegistry contract - return deployer.deploy(PolymathRegistry, { from: PolymathAccount }).then(() => { - return PolymathRegistry.deployed(); - }).then((_polymathRegistry) => { - polymathRegistry = _polymathRegistry; - return polymathRegistry.changeAddress("PolyToken", PolyToken, { from: PolymathAccount }); - }).then(() => { - // Deploy libraries - return deployer.deploy(TokenLib, { from: PolymathAccount }); - }).then(() => { - return deployer.deploy(VolumeRestrictionLib, { from: PolymathAccount }); - }).then(() => { + // POLYMATH NETWORK Configuration :: DO THIS ONLY ONCE + // A) Deploy the PolymathRegistry contract + return deployer + .deploy(PolymathRegistry, { from: PolymathAccount }) + .then(() => { + return PolymathRegistry.deployed(); + }) + .then(_polymathRegistry => { + polymathRegistry = _polymathRegistry; + return polymathRegistry.changeAddress("PolyToken", PolyToken, { from: PolymathAccount }); + }) + .then(() => { + // Deploy libraries + return deployer.deploy(TokenLib, { from: PolymathAccount }); + }) + .then(() => { + return deployer.deploy(VolumeRestrictionLib, { from: PolymathAccount }); + }) + .then(() => { + // Link libraries + deployer.link(VolumeRestrictionLib, VolumeRestrictionTMLogic); + deployer.link(TokenLib, SecurityTokenLogic); + deployer.link(TokenLib, MockSecurityTokenLogic); + deployer.link(TokenLib, STFactory); + deployer.link(TokenLib, STGetter); + deployer.link(TokenLib, MockSTGetter); + // A) Deploy the ModuleRegistry Contract (It contains the list of verified ModuleFactory) + return deployer.deploy(ModuleRegistry, { from: PolymathAccount }); + }) + .then(() => { + return deployer.deploy(ModuleRegistryProxy, { from: PolymathAccount }); + }) + .then(() => { + return ModuleRegistryProxy.at(ModuleRegistryProxy.address); + }) + .then(moduleRegistryProxy => { + let bytesProxyMR = web3.eth.abi.encodeFunctionCall(functionSignatureProxyMR, [polymathRegistry.address, PolymathAccount]); + return moduleRegistryProxy.upgradeToAndCall("1.0.0", ModuleRegistry.address, bytesProxyMR, { from: PolymathAccount }); + }) + .then(() => { + return ModuleRegistry.at(ModuleRegistryProxy.address); + }) + .then(moduleRegistryInstance => { + moduleRegistry = moduleRegistryInstance; + // Add module registry to polymath registry + return polymathRegistry.changeAddress("ModuleRegistry", ModuleRegistryProxy.address, { from: PolymathAccount }); + }) + .then(() => { + // B) Deploy the GeneralTransferManagerLogic Contract (Factory used to generate the GeneralTransferManager contract and this + // manager attach with the securityToken contract at the time of deployment) + return deployer.deploy(GeneralTransferManagerLogic, nullAddress, nullAddress, { from: PolymathAccount }); + }) + .then(() => { + // B) Deploy the GeneralPermissionManagerLogic Contract (Factory used to generate the GeneralPermissionManager contract and this + // manager attach with the securityToken contract at the time of deployment) + return deployer.deploy(GeneralPermissionManagerLogic, nullAddress, nullAddress, { from: PolymathAccount }); + }) + .then(() => { + // B) Deploy the CountTransferManagerLogic Contract (Factory used to generate the CountTransferManager contract and this + // manager attach with the securityToken contract at the time of deployment) + return deployer.deploy(CountTransferManagerLogic, nullAddress, nullAddress, { from: PolymathAccount }); + }) + .then(() => { + // B) Deploy the ManualApprovalTransferManagerLogic Contract (Factory used to generate the ManualApprovalTransferManager contract and this + // manager attach with the securityToken contract at the time of deployment) + return deployer.deploy(ManualApprovalTransferManagerLogic, nullAddress, nullAddress, { from: PolymathAccount }); + }) + .then(() => { + // B) Deploy the PercentageTransferManagerLogic Contract (Factory used to generate the PercentageTransferManager contract and this + // manager attach with the securityToken contract at the time of deployment) + return deployer.deploy(PercentageTransferManagerLogic, nullAddress, nullAddress, { from: PolymathAccount }); + }) + .then(() => { + // B) Deploy the ERC20DividendCheckpointLogic Contract (Factory used to generate the ERC20DividendCheckpoint contract and this + // manager attach with the securityToken contract at the time of deployment) + return deployer.deploy(ERC20DividendCheckpointLogic, nullAddress, nullAddress, { from: PolymathAccount }); + }) + .then(() => { + // B) Deploy the EtherDividendCheckpointLogic Contract (Factory used to generate the EtherDividendCheckpoint contract and this + // manager attach with the securityToken contract at the time of deployment) + return deployer.deploy(EtherDividendCheckpointLogic, nullAddress, nullAddress, { from: PolymathAccount }); + }) + .then(() => { + // B) Deploy the USDTieredSTOLogic Contract (Factory used to generate the USDTieredSTO contract and this + // manager attach with the securityToken contract at the time of deployment) + return deployer.deploy(USDTieredSTOLogic, nullAddress, nullAddress, { from: PolymathAccount }); + }) + .then(() => { + // B) Deploy the VolumeRestrictionTMLogic Contract (Factory used to generate the VolumeRestrictionTM contract and this + // manager attach with the securityToken contract at the time of deployment) + return deployer.deploy(VolumeRestrictionTMLogic, "0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", { from: PolymathAccount }); + }) + .then(() => { + // B) Deploy the CappedSTOLogic Contract (Factory used to generate the CappedSTO contract and this + // manager attach with the securityToken contract at the time of deployment) + return deployer.deploy(CappedSTOLogic, nullAddress, nullAddress, { from: PolymathAccount }); + }) + .then(() => { + // B) Deploy the VestingEscrowWalletLogic Contract (Factory used to generate the VestingEscrowWallet contract and this + // manager attach with the securityToken contract at the time of deployment) + return deployer.deploy(VestingEscrowWalletLogic, nullAddress, nullAddress, { from: PolymathAccount }); + }) + .then(() => { + // B) Deploy the DataStoreLogic Contract + return deployer.deploy(DataStoreLogic, { from: PolymathAccount }); + }) + .then(() => { + // B) Deploy the SecurityTokenLogic Contract + return deployer.deploy(SecurityTokenLogic, { from: PolymathAccount }); + }) + .then(() => { + // B) Deploy the DataStoreFactory Contract + return deployer.deploy(DataStoreFactory, DataStoreLogic.address, { from: PolymathAccount }); + }) + .then(() => { + // B) Deploy the GeneralTransferManagerFactory Contract (Factory used to generate the GeneralTransferManager contract and this + // manager attach with the securityToken contract at the time of deployment) + return deployer.deploy(GeneralTransferManagerFactory, new BN(0), GeneralTransferManagerLogic.address, polymathRegistry.address, { + from: PolymathAccount + }); + }) + .then(() => { + // C) Deploy the GeneralPermissionManagerFactory Contract (Factory used to generate the GeneralPermissionManager contract and + // this manager attach with the securityToken contract at the time of deployment) + return deployer.deploy(GeneralPermissionManagerFactory, new BN(0), GeneralPermissionManagerLogic.address, polymathRegistry.address, { + from: PolymathAccount + }); + }) + .then(() => { + // D) Deploy the CountTransferManagerFactory Contract (Factory used to generate the CountTransferManager contract use + // to track the counts of the investors of the security token) + return deployer.deploy(CountTransferManagerFactory, new BN(0), CountTransferManagerLogic.address, polymathRegistry.address, { + from: PolymathAccount + }); + }) + .then(() => { + // D) Deploy the PercentageTransferManagerFactory Contract (Factory used to generate the PercentageTransferManager contract use + // to track the percentage of investment the investors could do for a particular security token) + return deployer.deploy(PercentageTransferManagerFactory, new BN(0), PercentageTransferManagerLogic.address, polymathRegistry.address, { + from: PolymathAccount + }); + }) + .then(() => { + // D) Deploy the EtherDividendCheckpointFactory Contract (Factory used to generate the EtherDividendCheckpoint contract use + // to provide the functionality of the dividend in terms of ETH) + return deployer.deploy(EtherDividendCheckpointFactory, new BN(0), EtherDividendCheckpointLogic.address, polymathRegistry.address, { + from: PolymathAccount + }); + }) + .then(() => { + // D) Deploy the ERC20DividendCheckpointFactory Contract (Factory used to generate the ERC20DividendCheckpoint contract use + // to provide the functionality of the dividend in terms of ERC20 token) + return deployer.deploy(ERC20DividendCheckpointFactory, new BN(0), ERC20DividendCheckpointLogic.address, polymathRegistry.address, { + from: PolymathAccount + }); + }) + .then(() => { + // D) Deploy the VolumeRestrictionTMFactory Contract (Factory used to generate the VolumeRestrictionTM contract use + // to provide the functionality of restricting the token volume) + return deployer.deploy(VolumeRestrictionTMFactory, new BN(0), VolumeRestrictionTMLogic.address, polymathRegistry.address, { from: PolymathAccount }); + }) + .then(() => { + // D) Deploy the ManualApprovalTransferManagerFactory Contract (Factory used to generate the ManualApprovalTransferManager contract use + // to manual approve the transfer that will overcome the other transfer restrictions) + return deployer.deploy(ManualApprovalTransferManagerFactory, new BN(0), ManualApprovalTransferManagerLogic.address, polymathRegistry.address, { + from: PolymathAccount + }); + }) + .then(() => { + // D) Deploy the VestingEscrowWalletFactory Contract (Factory used to generate the ManualApprovalTransferManager contract use + // to manual approve the transfer that will overcome the other transfer restrictions) + return deployer.deploy(VestingEscrowWalletFactory, new BN(0), VestingEscrowWalletLogic.address, polymathRegistry.address, { + from: PolymathAccount + }); + }) + .then(() => { + // Deploy the STGetter contract (Logic contract that have the getters of the securityToken) + return deployer.deploy(STGetter, { from: PolymathAccount }); + }) + .then(() => { + // H) Deploy the STVersionProxy001 Contract which contains the logic of deployment of securityToken. + let tokenInitBytesCall = web3.eth.abi.encodeFunctionCall(tokenInitBytes, [STGetter.address]); + return deployer.deploy(STFactory, polymathRegistry.address, GeneralTransferManagerFactory.address, DataStoreFactory.address, "3.0.0", SecurityTokenLogic.address, tokenInitBytesCall, { from: PolymathAccount }); + }) + .then(() => { + // K) Deploy the FeatureRegistry contract to control feature switches + return deployer.deploy(FeatureRegistry, PolymathRegistry.address, { from: PolymathAccount }); + }) + .then(() => { + // Assign the address into the FeatureRegistry key + return polymathRegistry.changeAddress("FeatureRegistry", FeatureRegistry.address, { from: PolymathAccount }); + }) + .then(() => { + // J) Deploy the SecurityTokenRegistry contract (Used to hold the deployed secuirtyToken details. It also act as the interface to deploy the SecurityToken) + return deployer.deploy(SecurityTokenRegistry, { from: PolymathAccount }); + }) + .then(() => { + return deployer.deploy(SecurityTokenRegistryProxy, { from: PolymathAccount }); + }) + .then(() => { + return deployer.deploy(STRGetter, {from: PolymathAccount}); + }) + .then(() => { + return SecurityTokenRegistryProxy.at(SecurityTokenRegistryProxy.address); + }) + .then((securityTokenRegistryProxy) => { + let bytesProxy = web3.eth.abi.encodeFunctionCall(functionSignatureProxy, [ + PolymathRegistry.address, + initRegFee, + initRegFee, + PolymathAccount, + STRGetter.address + ]); + return securityTokenRegistryProxy.upgradeToAndCall("1.0.0", SecurityTokenRegistry.address, bytesProxy, { + from: PolymathAccount + }); + }) + .then(() => { + return SecurityTokenRegistry.at(SecurityTokenRegistryProxy.address); + }) + .then((securityTokenRegistry) => { + return securityTokenRegistry.setProtocolFactory(STFactory.address, 3, 0, 0); + }) + .then(() => { + return SecurityTokenRegistry.at(SecurityTokenRegistryProxy.address); + }) + .then((securityTokenRegistry) => { + return securityTokenRegistry.setLatestVersion(3, 0, 0); + }) + .then(() => { + // Assign the address into the SecurityTokenRegistry key + return polymathRegistry.changeAddress("SecurityTokenRegistry", SecurityTokenRegistryProxy.address, { from: PolymathAccount }); + }) + .then(() => { + // Update all addresses into the registry contract by calling the function updateFromregistry + return moduleRegistry.updateFromRegistry({ from: PolymathAccount }); + }) + .then(() => { + // D) Register the PercentageTransferManagerFactory in the ModuleRegistry to make the factory available at the protocol level. + // So any securityToken can use that factory to generate the PercentageTransferManager contract. + return moduleRegistry.registerModule(PercentageTransferManagerFactory.address, { from: PolymathAccount }); + }) + .then(() => { + // D) Register the CountTransferManagerFactory in the ModuleRegistry to make the factory available at the protocol level. + // So any securityToken can use that factory to generate the CountTransferManager contract. + return moduleRegistry.registerModule(CountTransferManagerFactory.address, { from: PolymathAccount }); + }) + .then(() => { + // D) Register the GeneralTransferManagerFactory in the ModuleRegistry to make the factory available at the protocol level. + // So any securityToken can use that factory to generate the GeneralTransferManager contract. + return moduleRegistry.registerModule(GeneralTransferManagerFactory.address, { from: PolymathAccount }); + }) + .then(() => { + // E) Register the GeneralPermissionManagerFactory in the ModuleRegistry to make the factory available at the protocol level. + // So any securityToken can use that factory to generate the GeneralPermissionManager contract. + return moduleRegistry.registerModule(GeneralPermissionManagerFactory.address, { from: PolymathAccount }); + }) + .then(() => { + // E) Register the GeneralPermissionManagerFactory in the ModuleRegistry to make the factory available at the protocol level. + // So any securityToken can use that factory to generate the GeneralPermissionManager contract. + return moduleRegistry.registerModule(EtherDividendCheckpointFactory.address, { from: PolymathAccount }); + }) + .then(() => { + // D) Register the VolumeRestrictionTMFactory in the ModuleRegistry to make the factory available at the protocol level. + // So any securityToken can use that factory to generate the VolumeRestrictionTM contract. + return moduleRegistry.registerModule(VolumeRestrictionTMFactory.address, { from: PolymathAccount }); + }) + .then(() => { + // D) Register the ManualApprovalTransferManagerFactory in the ModuleRegistry to make the factory available at the protocol level. + // So any securityToken can use that factory to generate the ManualApprovalTransferManager contract. + return moduleRegistry.registerModule(ManualApprovalTransferManagerFactory.address, { from: PolymathAccount }); + }) + .then(() => { + // E) Register the ERC20DividendCheckpointFactory in the ModuleRegistry to make the factory available at the protocol level. + // So any securityToken can use that factory to generate the ERC20DividendCheckpoint contract. + return moduleRegistry.registerModule(ERC20DividendCheckpointFactory.address, { from: PolymathAccount }); + }) + .then(() => { + // E) Register the VestingEscrowWalletFactory in the ModuleRegistry to make the factory available at the protocol level. + // So any securityToken can use that factory to generate the VestingEscrowWallet contract. + return moduleRegistry.registerModule(VestingEscrowWalletFactory.address, { from: PolymathAccount }); + }) + .then(() => { + // F) Once the GeneralTransferManagerFactory registered with the ModuleRegistry contract then for making them accessble to the securityToken + // contract, Factory should comes under the verified list of factories or those factories deployed by the securityToken issuers only. + // Here it gets verified because it is deployed by the third party account (Polymath Account) not with the issuer accounts. + return moduleRegistry.verifyModule(GeneralTransferManagerFactory.address, { from: PolymathAccount }); + }) + .then(() => { + // G) Once the CountTransferManagerFactory registered with the ModuleRegistry contract then for making them accessble to the securityToken + // contract, Factory should comes under the verified list of factories or those factories deployed by the securityToken issuers only. + // Here it gets verified because it is deployed by the third party account (Polymath Account) not with the issuer accounts. + return moduleRegistry.verifyModule(CountTransferManagerFactory.address, { from: PolymathAccount }); + }) + .then(() => { + // G) Once the PercentageTransferManagerFactory registered with the ModuleRegistry contract then for making them accessble to the securityToken + // contract, Factory should comes under the verified list of factories or those factories deployed by the securityToken issuers only. + // Here it gets verified because it is deployed by the third party account (Polymath Account) not with the issuer accounts. + return moduleRegistry.verifyModule(PercentageTransferManagerFactory.address, { from: PolymathAccount }); + }) + .then(() => { + // G) Once the GeneralPermissionManagerFactory registered with the ModuleRegistry contract then for making them accessble to the securityToken + // contract, Factory should comes under the verified list of factories or those factories deployed by the securityToken issuers only. + // Here it gets verified because it is deployed by the third party account (Polymath Account) not with the issuer accounts. + return moduleRegistry.verifyModule(GeneralPermissionManagerFactory.address, { from: PolymathAccount }); + }) + .then(() => { + // G) Once the EtherDividendCheckpointFactory registered with the ModuleRegistry contract then for making them accessble to the securityToken + // contract, Factory should comes under the verified list of factories or those factories deployed by the securityToken issuers only. + // Here it gets verified because it is deployed by the third party account (Polymath Account) not with the issuer accounts. + return moduleRegistry.verifyModule(EtherDividendCheckpointFactory.address, { from: PolymathAccount }); + }) + .then(() => { + // G) Once the ERC20DividendCheckpointFactory registered with the ModuleRegistry contract then for making them accessble to the securityToken + // contract, Factory should comes under the verified list of factories or those factories deployed by the securityToken issuers only. + // Here it gets verified because it is deployed by the third party account (Polymath Account) not with the issuer accounts. + return moduleRegistry.verifyModule(ERC20DividendCheckpointFactory.address, { from: PolymathAccount }); + }) + .then(() => { + // G) Once the VolumeRestrictionTMFactory registered with the ModuleRegistry contract then for making them accessble to the securityToken + // contract, Factory should comes under the verified list of factories or those factories deployed by the securityToken issuers only. + // Here it gets verified because it is deployed by the third party account (Polymath Account) not with the issuer accounts. + return moduleRegistry.verifyModule(VolumeRestrictionTMFactory.address, { from: PolymathAccount }); + }) + .then(() => { + // G) Once the ManualApprovalTransferManagerFactory registered with the ModuleRegistry contract then for making them accessble to the securityToken + // contract, Factory should comes under the verified list of factories or those factories deployed by the securityToken issuers only. + // Here it gets verified because it is deployed by the third party account (Polymath Account) not with the issuer accounts. + return moduleRegistry.verifyModule(ManualApprovalTransferManagerFactory.address, { from: PolymathAccount }); + }) + .then(() => { + // G) Once the VestingEscrowWalletFactory registered with the ModuleRegistry contract then for making them accessble to the securityToken + // contract, Factory should comes under the verified list of factories or those factories deployed by the securityToken issuers only. + // Here it gets verified because it is deployed by the third party account (Polymath Account) not with the issuer accounts. + return moduleRegistry.verifyModule(VestingEscrowWalletFactory.address, { from: PolymathAccount }); + }) + .then(() => { + // M) Deploy the CappedSTOFactory (Use to generate the CappedSTO contract which will used to collect the funds ). + return deployer.deploy(CappedSTOFactory, cappedSTOSetupCost, CappedSTOLogic.address, polymathRegistry.address, { from: PolymathAccount }); + }) + .then(() => { + // N) Register the CappedSTOFactory in the ModuleRegistry to make the factory available at the protocol level. + // So any securityToken can use that factory to generate the CappedSTOFactory contract. + return moduleRegistry.registerModule(CappedSTOFactory.address, { from: PolymathAccount }); + }) + .then(() => { + // G) Once the CappedSTOFactory registered with the ModuleRegistry contract then for making them accessble to the securityToken + // contract, Factory should comes under the verified list of factories or those factories deployed by the securityToken issuers only. + // Here it gets verified because it is deployed by the third party account (Polymath Account) not with the issuer accounts. + return moduleRegistry.verifyModule(CappedSTOFactory.address, { from: PolymathAccount }); + }) + .then(() => { + // H) Deploy the USDTieredSTOFactory (Use to generate the USDTieredSTOFactory contract which will used to collect the funds ). + return deployer.deploy(USDTieredSTOFactory, usdTieredSTOSetupCost, USDTieredSTOLogic.address, polymathRegistry.address, { from: PolymathAccount }); + }) + .then(() => { + // I) Register the USDTieredSTOFactory in the ModuleRegistry to make the factory available at the protocol level. + // So any securityToken can use that factory to generate the USDTieredSTOFactory contract. + return moduleRegistry.registerModule(USDTieredSTOFactory.address, { from: PolymathAccount }); + }) + .then(() => { + // J) Once the USDTieredSTOFactory registered with the ModuleRegistry contract then for making them accessble to the securityToken + // contract, Factory should comes under the verified list of factories or those factories deployed by the securityToken issuers only. + // Here it gets verified because it is deployed by the third party account (Polymath Account) not with the issuer accounts. + return moduleRegistry.verifyModule(USDTieredSTOFactory.address, { from: PolymathAccount }); + }) + .then(() => { + return polymathRegistry.changeAddress("PolyUsdOracle", POLYOracle, { from: PolymathAccount }); + }) + .then(() => { + return polymathRegistry.changeAddress("EthUsdOracle", ETHOracle, { from: PolymathAccount }); + }) + .then(() => { + // return deployer.deploy(SecurityToken, "a", "a", 18, 1, "a", polymathRegistry.address, STGetter.address, { from: PolymathAccount }); + return polymathRegistry.changeAddress("StablePolyUsdOracle", StablePOLYOracle, { from: PolymathAccount }); + }) + .then(() => { + console.log("\n"); + console.log(` - // Link libraries - deployer.link(VolumeRestrictionLib, VolumeRestrictionTMLogic); - deployer.link(TokenLib, SecurityToken); - deployer.link(TokenLib, STFactory); - // A) Deploy the ModuleRegistry Contract (It contains the list of verified ModuleFactory) - return deployer.deploy(ModuleRegistry, { from: PolymathAccount }); - }).then(() => { - return deployer.deploy(ModuleRegistryProxy, { from: PolymathAccount }); - }).then(() => { - let bytesProxyMR = web3.eth.abi.encodeFunctionCall(functionSignatureProxyMR, [polymathRegistry.address, PolymathAccount]); - return ModuleRegistryProxy.at(ModuleRegistryProxy.address).upgradeToAndCall("1.0.0", ModuleRegistry.address, bytesProxyMR, { from: PolymathAccount }); - }).then(() => { - moduleRegistry = ModuleRegistry.at(ModuleRegistryProxy.address); - // Add module registry to polymath registry - return polymathRegistry.changeAddress("ModuleRegistry", ModuleRegistryProxy.address, { from: PolymathAccount }); - }).then(() => { - // B) Deploy the GeneralTransferManagerLogic Contract (Factory used to generate the GeneralTransferManager contract and this - // manager attach with the securityToken contract at the time of deployment) - return deployer.deploy(GeneralTransferManagerLogic, "0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", { from: PolymathAccount }); - }).then(() => { - // B) Deploy the ERC20DividendCheckpointLogic Contract (Factory used to generate the ERC20DividendCheckpoint contract and this - // manager attach with the securityToken contract at the time of deployment) - return deployer.deploy(ERC20DividendCheckpointLogic, "0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", { from: PolymathAccount }); - }).then(() => { - // B) Deploy the EtherDividendCheckpointLogic Contract (Factory used to generate the EtherDividendCheckpoint contract and this - // manager attach with the securityToken contract at the time of deployment) - return deployer.deploy(EtherDividendCheckpointLogic, "0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", { from: PolymathAccount }); - }).then(() => { - // B) Deploy the VestingEscrowWalletLogic Contract (Factory used to generate the VestingEscrowWallet contract and this - // manager attach with the securityToken contract at the time of deployment) - return deployer.deploy(VestingEscrowWalletLogic, "0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", { from: PolymathAccount }); - }).then(() => { - // B) Deploy the USDTieredSTOLogic Contract (Factory used to generate the USDTieredSTO contract and this - // manager attach with the securityToken contract at the time of deployment) - return deployer.deploy(USDTieredSTOLogic, "0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", { from: PolymathAccount }); - }).then(() => { - // B) Deploy the VolumeRestrictionTMLogic Contract (Factory used to generate the VolumeRestrictionTM contract and this - // manager attach with the securityToken contract at the time of deployment) - return deployer.deploy(VolumeRestrictionTMLogic, "0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", { from: PolymathAccount }); - }).then(() => { - // B) Deploy the VestingEscrowWalletFactory Contract (Factory used to generate the VestingEscrowWallet contract and this - // manager attach with the securityToken contract at the time of deployment) - return deployer.deploy(VestingEscrowWalletFactory, PolyToken, 0, 0, 0, VestingEscrowWalletLogic.address, { from: PolymathAccount }); - }).then(() => { - // B) Deploy the GeneralTransferManagerFactory Contract (Factory used to generate the GeneralTransferManager contract and this - // manager attach with the securityToken contract at the time of deployment) - return deployer.deploy(GeneralTransferManagerFactory, PolyToken, 0, 0, 0, GeneralTransferManagerLogic.address, { from: PolymathAccount }); - }).then(() => { - // C) Deploy the LockUpTransferManagerFactory Contract (Factory used to generate the LockUpTransferManager contract and - // this manager attach with the securityToken contract at the time of deployment) - return deployer.deploy(LockUpTransferManagerFactory, PolyToken, 0, 0, 0, { from: PolymathAccount }); - }).then(() => { - // C) Deploy the GeneralPermissionManagerFactory Contract (Factory used to generate the GeneralPermissionManager contract and - // this manager attach with the securityToken contract at the time of deployment) - return deployer.deploy(GeneralPermissionManagerFactory, PolyToken, 0, 0, 0, { from: PolymathAccount }); - }).then(() => { - // D) Deploy the CountTransferManagerFactory Contract (Factory used to generate the CountTransferManager contract use - // to track the counts of the investors of the security token) - return deployer.deploy(CountTransferManagerFactory, PolyToken, 0, 0, 0, { from: PolymathAccount }); - }).then(() => { - // D) Deploy the BlacklistTransferManagerFactory Contract (Factory used to generate the BlacklistTransferManager contract use - // to to automate blacklist and restrict transfers) - return deployer.deploy(BlacklistTransferManagerFactory, PolyToken, 0, 0, 0, { from: PolymathAccount }); - }).then(() => { - // D) Deploy the PercentageTransferManagerFactory Contract (Factory used to generate the PercentageTransferManager contract use - // to track the percentage of investment the investors could do for a particular security token) - return deployer.deploy(PercentageTransferManagerFactory, PolyToken, 0, 0, 0, { from: PolymathAccount }); - }).then(() => { - // D) Deploy the EtherDividendCheckpointFactory Contract (Factory used to generate the EtherDividendCheckpoint contract use - // to provide the functionality of the dividend in terms of ETH) - return deployer.deploy(EtherDividendCheckpointFactory, PolyToken, 0, 0, 0, EtherDividendCheckpointLogic.address, { from: PolymathAccount }); - }).then(() => { - // D) Deploy the ERC20DividendCheckpointFactory Contract (Factory used to generate the ERC20DividendCheckpoint contract use - // to provide the functionality of the dividend in terms of ERC20 token) - return deployer.deploy(ERC20DividendCheckpointFactory, PolyToken, 0, 0, 0, ERC20DividendCheckpointLogic.address, { from: PolymathAccount }); - }).then(() => { - // D) Deploy the VolumeRestrictionTMFactory Contract (Factory used to generate the VolumeRestrictionTM contract use - // to provide the functionality of restricting the token volume) - return deployer.deploy(VolumeRestrictionTMFactory, PolyToken, 0, 0, 0, VolumeRestrictionTMLogic.address, { from: PolymathAccount }); - }).then(() => { - // D) Deploy the ManualApprovalTransferManagerFactory Contract (Factory used to generate the ManualApprovalTransferManager contract use - // to manual approve the transfer that will overcome the other transfer restrictions) - return deployer.deploy(ManualApprovalTransferManagerFactory, PolyToken, 0, 0, 0, { from: PolymathAccount }); - }).then(() => { - // H) Deploy the STVersionProxy001 Contract which contains the logic of deployment of securityToken. - return deployer.deploy(STFactory, GeneralTransferManagerFactory.address, { from: PolymathAccount }); - }).then(() => { - // K) Deploy the FeatureRegistry contract to control feature switches - return deployer.deploy(FeatureRegistry, PolymathRegistry.address, { from: PolymathAccount }); - }).then(() => { - // Assign the address into the FeatureRegistry key - return polymathRegistry.changeAddress("FeatureRegistry", FeatureRegistry.address, { from: PolymathAccount }); - }).then(() => { - // J) Deploy the SecurityTokenRegistry contract (Used to hold the deployed secuirtyToken details. It also act as the interface to deploy the SecurityToken) - return deployer.deploy(SecurityTokenRegistry, { from: PolymathAccount }) - }).then(() => { - return deployer.deploy(SecurityTokenRegistryProxy, { from: PolymathAccount }); - }).then(() => { - let bytesProxy = web3.eth.abi.encodeFunctionCall(functionSignatureProxy, [PolymathRegistry.address, STFactory.address, initRegFee, initRegFee, PolyToken, PolymathAccount]); - return SecurityTokenRegistryProxy.at(SecurityTokenRegistryProxy.address).upgradeToAndCall("1.0.0", SecurityTokenRegistry.address, bytesProxy, { from: PolymathAccount }); - }).then(() => { - // Assign the address into the SecurityTokenRegistry key - return polymathRegistry.changeAddress("SecurityTokenRegistry", SecurityTokenRegistryProxy.address, { from: PolymathAccount }); - }).then(() => { - // Update all addresses into the registry contract by calling the function updateFromregistry - return moduleRegistry.updateFromRegistry({ from: PolymathAccount }); - }).then(() => { - // D) Register the LockUpTransferManagerFactory in the ModuleRegistry to make the factory available at the protocol level. - // So any securityToken can use that factory to generate the LockUpTransferManager contract. - return moduleRegistry.registerModule(LockUpTransferManagerFactory.address, { from: PolymathAccount }); - }).then(() => { - // D) Register the PercentageTransferManagerFactory in the ModuleRegistry to make the factory available at the protocol level. - // So any securityToken can use that factory to generate the PercentageTransferManager contract. - return moduleRegistry.registerModule(PercentageTransferManagerFactory.address, { from: PolymathAccount }); - }).then(() => { - // D) Register the VestingEscrowWalletFactory in the ModuleRegistry to make the factory available at the protocol level. - // So any securityToken can use that factory to generate the VestingEscrowWallet contract. - return moduleRegistry.registerModule(VestingEscrowWalletFactory.address, { from: PolymathAccount }); - }).then(() => { - // D) Register the CountTransferManagerFactory in the ModuleRegistry to make the factory available at the protocol level. - // So any securityToken can use that factory to generate the CountTransferManager contract. - return moduleRegistry.registerModule(CountTransferManagerFactory.address, { from: PolymathAccount }); - }).then(() => { - // D) Register the GeneralTransferManagerFactory in the ModuleRegistry to make the factory available at the protocol level. - // So any securityToken can use that factory to generate the GeneralTransferManager contract. - return moduleRegistry.registerModule(GeneralTransferManagerFactory.address, { from: PolymathAccount }); - }).then(() => { - // D) Register the BlacklistTransferManagerFactory in the ModuleRegistry to make the factory available at the protocol level. - // So any securityToken can use that factory to generate the GeneralTransferManager contract. - return moduleRegistry.registerModule(BlacklistTransferManagerFactory.address, { from: PolymathAccount }); - }).then(() => { - // E) Register the GeneralPermissionManagerFactory in the ModuleRegistry to make the factory available at the protocol level. - // So any securityToken can use that factory to generate the GeneralPermissionManager contract. - return moduleRegistry.registerModule(GeneralPermissionManagerFactory.address, { from: PolymathAccount }); - }).then(() => { - // E) Register the GeneralPermissionManagerFactory in the ModuleRegistry to make the factory available at the protocol level. - // So any securityToken can use that factory to generate the GeneralPermissionManager contract. - return moduleRegistry.registerModule(EtherDividendCheckpointFactory.address, { from: PolymathAccount }); - }).then(() => { - // D) Register the VolumeRestrictionTMFactory in the ModuleRegistry to make the factory available at the protocol level. - // So any securityToken can use that factory to generate the VolumeRestrictionTM contract. - return moduleRegistry.registerModule(VolumeRestrictionTMFactory.address, { from: PolymathAccount }); - }).then(() => { - // D) Register the ManualApprovalTransferManagerFactory in the ModuleRegistry to make the factory available at the protocol level. - // So any securityToken can use that factory to generate the ManualApprovalTransferManager contract. - return moduleRegistry.registerModule(ManualApprovalTransferManagerFactory.address, { from: PolymathAccount }); - }).then(() => { - // E) Register the ERC20DividendCheckpointFactory in the ModuleRegistry to make the factory available at the protocol level. - // So any securityToken can use that factory to generate the ERC20DividendCheckpoint contract. - return moduleRegistry.registerModule(ERC20DividendCheckpointFactory.address, { from: PolymathAccount }); - }).then(() => { - // F) Once the GeneralTransferManagerFactory registered with the ModuleRegistry contract then for making them accessble to the securityToken - // contract, Factory should comes under the verified list of factories or those factories deployed by the securityToken issuers only. - // Here it gets verified because it is deployed by the third party account (Polymath Account) not with the issuer accounts. - return moduleRegistry.verifyModule(GeneralTransferManagerFactory.address, true, { from: PolymathAccount }); - }).then(() => { - // G) Once the BlacklistTransferManagerFactory registered with the ModuleRegistry contract then for making them accessble to the securityToken - // contract, Factory should comes under the verified list of factories or those factories deployed by the securityToken issuers only. - // Here it gets verified because it is deployed by the third party account (Polymath Account) not with the issuer accounts. - return moduleRegistry.verifyModule(BlacklistTransferManagerFactory.address, true, { from: PolymathAccount }); - }).then(() => { - // G) Once the CountTransferManagerFactory registered with the ModuleRegistry contract then for making them accessble to the securityToken - // contract, Factory should comes under the verified list of factories or those factories deployed by the securityToken issuers only. - // Here it gets verified because it is deployed by the third party account (Polymath Account) not with the issuer accounts. - return moduleRegistry.verifyModule(CountTransferManagerFactory.address, true, { from: PolymathAccount }); - }).then(() => { - // F) Once the LockUpTransferManagerFactory registered with the ModuleRegistry contract then for making them accessble to the securityToken - // contract, Factory should comes under the verified list of factories or those factories deployed by the securityToken issuers only. - // Here it gets verified because it is deployed by the third party account (Polymath Account) not with the issuer accounts. - return moduleRegistry.verifyModule(LockUpTransferManagerFactory.address, true, { from: PolymathAccount }); - }).then(() => { - // G) Once the PercentageTransferManagerFactory registered with the ModuleRegistry contract then for making them accessble to the securityToken - // contract, Factory should comes under the verified list of factories or those factories deployed by the securityToken issuers only. - // Here it gets verified because it is deployed by the third party account (Polymath Account) not with the issuer accounts. - return moduleRegistry.verifyModule(PercentageTransferManagerFactory.address, true, { from: PolymathAccount }); - }).then(() => { - // G) Once the GeneralPermissionManagerFactory registered with the ModuleRegistry contract then for making them accessble to the securityToken - // contract, Factory should comes under the verified list of factories or those factories deployed by the securityToken issuers only. - // Here it gets verified because it is deployed by the third party account (Polymath Account) not with the issuer accounts. - return moduleRegistry.verifyModule(GeneralPermissionManagerFactory.address, true, { from: PolymathAccount }) - }).then(() => { - // G) Once the EtherDividendCheckpointFactory registered with the ModuleRegistry contract then for making them accessble to the securityToken - // contract, Factory should comes under the verified list of factories or those factories deployed by the securityToken issuers only. - // Here it gets verified because it is deployed by the third party account (Polymath Account) not with the issuer accounts. - return moduleRegistry.verifyModule(EtherDividendCheckpointFactory.address, true, { from: PolymathAccount }); - }).then(() => { - // G) Once the ERC20DividendCheckpointFactory registered with the ModuleRegistry contract then for making them accessble to the securityToken - // contract, Factory should comes under the verified list of factories or those factories deployed by the securityToken issuers only. - // Here it gets verified because it is deployed by the third party account (Polymath Account) not with the issuer accounts. - return moduleRegistry.verifyModule(ERC20DividendCheckpointFactory.address, true, { from: PolymathAccount }); - }).then(() => { - // G) Once the VolumeRestrictionTMFactory registered with the ModuleRegistry contract then for making them accessble to the securityToken - // contract, Factory should comes under the verified list of factories or those factories deployed by the securityToken issuers only. - // Here it gets verified because it is deployed by the third party account (Polymath Account) not with the issuer accounts. - return moduleRegistry.verifyModule(VolumeRestrictionTMFactory.address, true, { from: PolymathAccount }); - }).then(() => { - // G) Once the ManualApprovalTransferManagerFactory registered with the ModuleRegistry contract then for making them accessble to the securityToken - // contract, Factory should comes under the verified list of factories or those factories deployed by the securityToken issuers only. - // Here it gets verified because it is deployed by the third party account (Polymath Account) not with the issuer accounts. - return moduleRegistry.verifyModule(ManualApprovalTransferManagerFactory.address, true, { from: PolymathAccount }); - }).then(() => { - // F) Once the VestingEscrowWalletFactory registered with the ModuleRegistry contract then for making them accessble to the securityToken - // contract, Factory should comes under the verified list of factories or those factories deployed by the securityToken issuers only. - // Here it gets verified because it is deployed by the third party account (Polymath Account) not with the issuer accounts. - return moduleRegistry.verifyModule(VestingEscrowWalletFactory.address, true, { from: PolymathAccount }); - }).then(() => { - // M) Deploy the CappedSTOFactory (Use to generate the CappedSTO contract which will used to collect the funds ). - return deployer.deploy(CappedSTOFactory, PolyToken, cappedSTOSetupCost, 0, 0, { from: PolymathAccount }) - }).then(() => { - // N) Register the CappedSTOFactory in the ModuleRegistry to make the factory available at the protocol level. - // So any securityToken can use that factory to generate the CappedSTOFactory contract. - return moduleRegistry.registerModule(CappedSTOFactory.address, { from: PolymathAccount }) - }).then(() => { - // G) Once the CappedSTOFactory registered with the ModuleRegistry contract then for making them accessble to the securityToken - // contract, Factory should comes under the verified list of factories or those factories deployed by the securityToken issuers only. - // Here it gets verified because it is deployed by the third party account (Polymath Account) not with the issuer accounts. - return moduleRegistry.verifyModule(CappedSTOFactory.address, true, { from: PolymathAccount }) - }).then(() => { - // H) Deploy the USDTieredSTOFactory (Use to generate the USDTieredSTOFactory contract which will used to collect the funds ). - return deployer.deploy(USDTieredSTOFactory, PolyToken, usdTieredSTOSetupCost, 0, 0, USDTieredSTOLogic.address, { from: PolymathAccount }) - }).then(() => { - // I) Register the USDTieredSTOFactory in the ModuleRegistry to make the factory available at the protocol level. - // So any securityToken can use that factory to generate the USDTieredSTOFactory contract. - return moduleRegistry.registerModule(USDTieredSTOFactory.address, { from: PolymathAccount }) - }).then(() => { - // J) Once the USDTieredSTOFactory registered with the ModuleRegistry contract then for making them accessble to the securityToken - // contract, Factory should comes under the verified list of factories or those factories deployed by the securityToken issuers only. - // Here it gets verified because it is deployed by the third party account (Polymath Account) not with the issuer accounts. - return moduleRegistry.verifyModule(USDTieredSTOFactory.address, true, { from: PolymathAccount }) - }).then(() => { - return polymathRegistry.changeAddress("PolyUsdOracle", POLYOracle, { from: PolymathAccount }); - }).then(() => { - return polymathRegistry.changeAddress("EthUsdOracle", ETHOracle, { from: PolymathAccount }); - }).then(() => { - return deployer.deploy(SecurityToken, 'a', 'a', 18, 1, 'a', polymathRegistry.address, { from: PolymathAccount }); - }).then(() => { - console.log('\n'); - console.log(` ----------------------- Polymath Network Smart Contracts: ----------------------- PolymathRegistry: ${PolymathRegistry.address} SecurityTokenRegistry (Proxy): ${SecurityTokenRegistryProxy.address} ModuleRegistry (Proxy): ${ModuleRegistryProxy.address} FeatureRegistry: ${FeatureRegistry.address} + STRGetter: ${STRGetter.address} ETHOracle: ${ETHOracle} POLYOracle: ${POLYOracle} + POLYStableOracle: ${StablePOLYOracle} STFactory: ${STFactory.address} GeneralTransferManagerLogic: ${GeneralTransferManagerLogic.address} GeneralTransferManagerFactory: ${GeneralTransferManagerFactory.address} + GeneralPermissionManagerLogic: ${GeneralPermissionManagerLogic.address} GeneralPermissionManagerFactory: ${GeneralPermissionManagerFactory.address} + CappedSTOLogic: ${CappedSTOLogic.address} CappedSTOFactory: ${CappedSTOFactory.address} - USDTieredSTOFactory: ${USDTieredSTOFactory.address} USDTieredSTOLogic: ${USDTieredSTOLogic.address} + USDTieredSTOFactory: ${USDTieredSTOFactory.address} + CountTransferManagerLogic: ${CountTransferManagerLogic.address} CountTransferManagerFactory: ${CountTransferManagerFactory.address} + PercentageTransferManagerLogic: ${PercentageTransferManagerLogic.address} PercentageTransferManagerFactory: ${PercentageTransferManagerFactory.address} + ManualApprovalTransferManagerLogic: ${ManualApprovalTransferManagerLogic.address} ManualApprovalTransferManagerFactory: ${ManualApprovalTransferManagerFactory.address} EtherDividendCheckpointLogic: ${EtherDividendCheckpointLogic.address} ERC20DividendCheckpointLogic: ${ERC20DividendCheckpointLogic.address} EtherDividendCheckpointFactory: ${EtherDividendCheckpointFactory.address} ERC20DividendCheckpointFactory: ${ERC20DividendCheckpointFactory.address} - LockUpTransferManagerFactory: ${LockUpTransferManagerFactory.address} - BlacklistTransferManagerFactory: ${BlacklistTransferManagerFactory.address} VolumeRestrictionTMFactory: ${VolumeRestrictionTMFactory.address} VolumeRestrictionTMLogic: ${VolumeRestrictionTMLogic.address} VestingEscrowWalletFactory: ${VestingEscrowWalletFactory.address} VestingEscrowWalletLogic: ${VestingEscrowWalletLogic.address} --------------------------------------------------------------------------------- `); - console.log('\n'); - // -------- END OF POLYMATH NETWORK Configuration -------// - }); -} + console.log("\n"); + // -------- END OF POLYMATH NETWORK Configuration -------// + }); +}; diff --git a/package.json b/package.json index a92b3b316..fe7227dcb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "polymath-core", - "version": "2.1.0", + "version": "3.0.0", "description": "Polymath Network Core Smart Contracts", "main": "truffle.js", "directories": { @@ -8,7 +8,9 @@ }, "scripts": { "test": "scripts/test.sh 2> /dev/null", - "deploy-kovan": "scripts/deploy.sh", + "clash-check": "node scripts/clashCheck.js", + "istr-check": "node scripts/ISTRCheck.js", + "gas": "scripts/gasUsage.sh", "wintest": "scripts\\wintest.cmd", "wincov": "scripts\\wincov.cmd", "docs": "scripts/docs.sh", @@ -35,7 +37,8 @@ "flatten-all": "npm run flatten-modules && npm run flatten-token && npm run flatten-mocks && npm run flatten-oracles && npm run flatten-proxies && npm run flatten && npm run flatten-proxyFactories", "ethereum-bridge": "node_modules/.bin/ethereum-bridge -H localhost:8545 -a 9 --dev", "st20generator": "node demo/ST20Generator", - "pretty": "prettier --write --print-width 140 --tab-width 4 \"**/*.js\"" + "pretty": "prettier --write --print-width 140 --tab-width 4 \"**/*.js\"", + "st-storage-layout-check": "node scripts/compareStorageLayout.js SecurityToken STGetter" }, "repository": { "type": "git", @@ -51,56 +54,54 @@ "ST20" ], "author": "Polymath Inc", - "license": "MIT", + "license": "Apache-2.0", "bugs": { "url": "https://github.com/PolymathNetwork/polymath-core/issues" }, "homepage": "https://github.com/PolymathNetwork/polymath-core#readme", "dependencies": { + "dotenv": "^8.0.0", + "openzeppelin-solidity": "2.2.0", + "truffle": "^5.0.4", + "truffle-hdwallet-provider": "^1.0.4", + "web3-provider-engine": "^14.1.0" + }, + "devDependencies": { "babel-polyfill": "6.26.0", "babel-preset-es2015": "6.24.1", "babel-preset-stage-2": "6.24.1", "babel-preset-stage-3": "6.24.1", "babel-register": "6.26.0", - "bignumber.js": "5.0.0", - "chalk": "^2.4.1", - "coveralls": "^3.0.2", - "csv-parse": "^4.2.0", - "ethers": "^4.0.20", - "fs": "0.0.2", - "openzeppelin-solidity": "1.10.0", - "prompt": "^1.0.0", - "truffle-hdwallet-provider-privkey": "^0.3.0", - "web3": "1.0.0-beta.34" - }, - "devDependencies": { - "eslint": "^5.10.0", + "chalk": "^2.4.2", + "coveralls": "^3.0.1", + "eslint": "^5.8.0", "eslint-config-standard": "^12.0.0", - "eslint-plugin-import": "^2.14.0", + "eslint-plugin-import": "^2.10.0", "eslint-plugin-node": "^8.0.0", "eslint-plugin-promise": "^4.0.1", "eslint-plugin-standard": "^4.0.0", + "eth-gas-reporter": "^0.1.12", "ethereum-bridge": "^0.6.1", "ethereumjs-abi": "^0.6.5", - "ganache-cli": "^6.2.5", + "fs": "0.0.2", + "ganache-cli": "6.1.8", "mocha-junit-reporter": "^1.18.0", "prettier": "^1.15.3", "request": "^2.88.0", "request-promise": "^4.2.2", - "sol-merger": "^0.1.3", + "sol-merger": "^0.1.2", "solidity-coverage": "^0.5.11", "solidity-docgen": "^0.1.1", - "solium": "^1.1.8", - "truffle": "4.1.14", - "truffle-hdwallet-provider": "^1.0.3", - "truffle-wallet-provider": "0.0.5" + "solium": "^1.1.6", + "table": "^5.2.3", + "web3": "1.0.0-beta.35" }, "greenkeeper": { "ignore": [ "openzeppelin-solidity", "web3", - "bignumber.js", - "truffle-hdwallet-provider-privkey" + "truffle-hdwallet-provider", + "ganache-cli" ] } } diff --git a/scripts/ISTRCheck.js b/scripts/ISTRCheck.js new file mode 100644 index 000000000..9bbaf7f64 --- /dev/null +++ b/scripts/ISTRCheck.js @@ -0,0 +1,118 @@ +const fs = require('fs'); +const exec = require('child_process').execSync; +const chalk = require('chalk'); + +// These functions/events are allowed to differ. (These are present in STR but not used and hence not defined in ISTR) +let exceptions = [ + "getBytes32Value", + "getBytesValue", + "getAddressValue", + "getArrayAddress", + "getBoolValue", + "getStringValue", + "getArrayBytes32", + "getUintValue", + "getArrayUint", + "initialize", + "getBytes32Value", + "getBytesValue", + "getAddressValue", + "getArrayAddress", + "getBoolValue", + "getStringValue", + "getArrayBytes32", + "getUintValue", + "getArrayUint", + undefined, // constructor +]; + +async function readFiles() { + if (!fs.existsSync("./build/contracts/ISecurityTokenRegistry.json")) { + console.log(chalk.yellow('Compiling contracts. This may take a while, please wait.')); + exec('./node_modules/.bin/truffle compile'); + } + return([ + JSON.parse(fs.readFileSync(`./build/contracts/ISecurityTokenRegistry.json`).toString()).abi, + [ + ...JSON.parse(fs.readFileSync(`./build/contracts/SecurityTokenRegistry.json`).toString()).abi, + ...JSON.parse(fs.readFileSync(`./build/contracts/STRGetter.json`).toString()).abi + ] + ]); +} + +async function checkISTR() { + // Reading ABIs from build files + let ABIs = await readFiles(); + + // Removing functions/events defined in exceptions array. + removeExceptions(ABIs); + + // Removing parameter and return names from ABIs as they can differ. + // Only cleaning ABIs of second file as first one can be cleaned in the next loop efficiently. + for (let i = 0; i < ABIs[1].length; i++) { + cleanABI(ABIs[1][i]); + } + + // This function removed duplicate elements from the ABI. + // i.e If the signature matches in Interface and the contract, it is removed from the ABI. + // This means that the left over elements after this loop are mistakes. + for (let i = 0; i < ABIs[0].length; i++) { + let fna = cleanABI(ABIs[0][i]); + for (let j = 0; j < ABIs[1].length; j++) { + let fnb = ABIs[1][j]; + if (fna.name == fnb.name && fna.type == fnb.type) { + if (JSON.stringify(fna) === JSON.stringify(fnb)) { + ABIs[0].splice(i, 1); + ABIs[1].splice(j, 1); + i--; + break; + } + } + } + } + + // If there is any element remaining in either ABI, it is an error. + if (ABIs[0].length >= 1 || ABIs[1].length > 1) { + for (let i = 0; i < ABIs[0].length; i++) { + console.log(ABIs[0][i].name); + } + for (let i = 0; i < ABIs[1].length; i++) { + console.log(ABIs[1][i].name); + } + console.log(chalk.red('The above Functions/events had no match found. Please synchronize the Interface with the contract.')); + process.exit(1); + } +} + +function cleanABI(element) { + if (element.type === 'event') { + for(let i = 0; i < element.inputs.length; i++) { + element.inputs[i].name = ""; + } + } else if (element.type === 'function') { + for(let i = 0; i < element.inputs.length; i++) { + element.inputs[i].name = ""; + } + for(let i = 0; i < element.outputs.length; i++) { + element.outputs[i].name = ""; + } + } + return element; +} + +function removeExceptions(ABIs) { + for (let i = 0; i < ABIs[0].length; i++) { + if (exceptions.includes(ABIs[0][i].name)) { + ABIs[0].splice(i, 1); + i--; + } + } + for (let i = 0; i < ABIs[1].length; i++) { + if (exceptions.includes(ABIs[1][i].name)) { + ABIs[1].splice(i, 1); + i--; + } + } +} + +checkISTR(); diff --git a/scripts/calculateSize.js b/scripts/calculateSize.js index 46cf238db..88d1e1d4c 100644 --- a/scripts/calculateSize.js +++ b/scripts/calculateSize.js @@ -1,30 +1,35 @@ -const fs = require('fs'); -const path = require('path'); -var size = new Array(); +const fs = require("fs"); +const path = require("path"); +const exec = require('child_process').execSync; +const chalk = require('chalk'); +const { table } = require("table"); - - -function readFiles() { - if (fs.existsSync('./build/contracts/')) { - let files = fs.readdirSync('./build/contracts/'); - return files; +async function readFiles() { + if (fs.existsSync("./build/contracts/")) { + return fs.readdirSync("./build/contracts/"); } else { - console.log("Directory doesn't exists"); + console.log('Compiling contracts. This may take a while, please wait.'); + exec('./node_modules/.bin/truffle compile'); + return fs.readdirSync("./build/contracts/"); } } async function printSize() { - let files = readFiles(); - files.forEach((item) => { - let content = JSON.parse(fs.readFileSync(`./build/contracts/${item}`).toString()).deployedBytecode; - let sizeInKB = ((content.toString()).length / 2) / 1024; - size.push(sizeInKB); - }); + let files = await readFiles(); console.log(`NOTE- Maximum size of contracts allowed to deloyed on the Ethereum mainnet is 24 KB(EIP170)`); console.log(`---- Size of the contracts ----`); - for(let i = 0; i < files.length; i++) { - console.log(`${path.basename(files[i], '.json')} - ${size[i]} KB`); - } + let dataTable = [['Contracts', 'Size in KB']]; + files.forEach(item => { + let content = JSON.parse(fs.readFileSync(`./build/contracts/${item}`).toString()).deployedBytecode; + let sizeInKB = content.toString().length / 2 / 1024; + if (sizeInKB > 24) + dataTable.push([chalk.red(path.basename(item, ".json")),chalk.red(sizeInKB)]); + else if (sizeInKB > 20) + dataTable.push([chalk.yellow(path.basename(item, ".json")),chalk.yellow(sizeInKB)]); + else + dataTable.push([chalk.green(path.basename(item, ".json")),chalk.green(sizeInKB)]); + }); + console.log(table(dataTable)); } -printSize(); \ No newline at end of file +printSize(); diff --git a/scripts/clashCheck.js b/scripts/clashCheck.js new file mode 100644 index 000000000..6133d4c0b --- /dev/null +++ b/scripts/clashCheck.js @@ -0,0 +1,56 @@ +const fs = require('fs'); +const exec = require('child_process').execSync; +const chalk = require('chalk'); +const Web3 = require('web3'); + +async function readFiles() { + if (fs.existsSync("./build/contracts/")) { + return fs.readdirSync("./build/contracts/"); + } else { + console.log(chalk.yellow('Compiling contracts. This may take a while, please wait.')); + exec('./node_modules/.bin/truffle compile'); + return fs.readdirSync("./build/contracts/"); + } +} + +async function checkClashes() { + console.log(chalk.green("Starting function selector clash check")); + let files = await readFiles(); + let contractFunctions = new Set(); + let functionSelectors = new Map(); + files.forEach(item => { + let ABI = JSON.parse(fs.readFileSync(`./build/contracts/${item}`).toString()).abi; + ABI.forEach(element => { + if (element['type'] == 'function') { + let functionSig = element['name'] + '('; + element['inputs'].forEach(input => { + functionSig = functionSig + input['type'] + ','; + }); + if(functionSig[functionSig.length - 1] == ',') + functionSig = functionSig.slice(0, -1) + ')'; + else + functionSig = functionSig + ')'; + contractFunctions.add(functionSig); + } + }); + }); + let clashesFound = false; + contractFunctions.forEach(functionSig => { + let fnSelector = Web3.utils.sha3(functionSig).slice(0, 10); + if(functionSelectors.has(fnSelector)) { + clashesFound = true; + console.log(chalk.red('Function selector clash found!', functionSelectors.get(fnSelector), 'and', functionSig, 'have the same function selector:', fnSelector)); + functionSelectors.set(fnSelector, functionSelectors.get(fnSelector) + ', ' + functionSig); + } else { + functionSelectors.set(fnSelector, functionSig); + } + }); + if (clashesFound) { + console.log(chalk.yellow("The clash(es) might be in two different contracts and hence not be am Issue.\nThis script can not detect this (yet) because of proxy contracts")); + console.log(chalk.red("Clash(es) found! Please fix.")); + process.exit(1); + } + console.log(chalk.green("Clash check finished. No Clashes found.")); +} + +checkClashes(); diff --git a/scripts/compareStorageLayout.js b/scripts/compareStorageLayout.js index c339bf095..6b08defa3 100644 --- a/scripts/compareStorageLayout.js +++ b/scripts/compareStorageLayout.js @@ -1,139 +1,147 @@ -const fs = require('fs'); -const _ = require('underscore'); -const solc = require('solc'); -const prompt = require('prompt'); -const path = require('path'); -const util = require('util'); -const exec = util.promisify(require('child_process').exec); - -prompt.start(); - -prompt.get(['LogicContract', 'ProxyContract'], async(err, result) => { - - let logicContract; - let proxyContract; - - const fileList = walkSync('./contracts', []); - - let paths = findPath(result.LogicContract, result.ProxyContract, fileList); - - if (paths.length == 2) { - - console.log("Contracts exists \n"); - - await flatContracts(paths); - - if (path.basename(paths[0]) === result.LogicContract) { - logicContract = fs.readFileSync(`./flat/${path.basename(paths[0])}`, 'utf8'); - } else { - logicContract = fs.readFileSync(`./flat/${path.basename(paths[1])}`, 'utf8'); - } - if (path.basename(paths[0]) === result.ProxyContract) { - proxyContract = fs.readFileSync(`./flat/${path.basename(paths[0])}`, 'utf8'); +const fs = require("fs"); +const _ = require("underscore"); +const solc = require("solc"); +const path = require("path"); +const util = require("util"); +const exec = util.promisify(require("child_process").exec); + +console.log(`Mandatory: Solc cli tool should be installed globally. Please put the contract name only in any order`); + +let contractA = process.argv.slice(2)[0]; +let contractB = process.argv.slice(2)[1]; + +compareStorage(contractA, contractB); + +async function compareStorage() { + + const fileList = walkSync("./contracts", []); + let paths = findPath(contractA, contractB, fileList); + + if (paths.length == 2) { + console.log("Contracts exists \n"); + + await flatContracts(paths); + let temp; + let contractAPath = `./flat/${path.basename(paths[0])}`; + let contractBPath = `./flat/${path.basename(paths[1])}`; + + if (path.basename(paths[0]) === contractA) { + temp = contractAPath; + contractAPath = contractBPath; + contractBPath = temp; + } + + let contractAAST = await getAST(contractAPath); + let contractBAST = await getAST(contractBPath); + // Deleting the temp folder (no longer required) + await flushTemp(); + + var result = compareStorageLayouts(parseContract(contractAAST), parseContract(contractBAST)); + if (!result) + process.exit(1); } else { - proxyContract = fs.readFileSync(`./flat/${path.basename(paths[1])}`, 'utf8'); - } - - let logicInput = { - 'contracts': logicContract - } - let proxyInput = { - 'contracts': proxyContract + console.log("Contracts doesn't exists"); } - - console.log(compareStorageLayouts(parseContract(logicInput), parseContract(proxyInput))); - - } else { - console.log("Contracts doesn't exists"); - } -}); +} function traverseAST(_input, _elements) { - if(_input.children) { - for(var i=0;i<_input.children.length;i++) { - traverseAST(_input.children[i], _elements); + if (_input.children) { + for (var i = 0; i < _input.children.length; i++) { + traverseAST(_input.children[i], _elements); + } } - } - _elements.push(_input); + _elements.push(_input); } function compareStorageLayouts(logicLayout, proxyLayout) { - function makeComp(x) { - return [x.constant, x.name, x.stateVariable, x.storageLocation, x.type, x.value, x.visibility].join(':'); - } - // if(newLayout.length < oldLayout.length) return false; - for(var i=0; i < logicLayout.length; i++) { - const a = logicLayout[i].attributes; - const comp1 = makeComp(a) - console.log(comp1); - const b = proxyLayout[i].attributes; - const comp2 = makeComp(b); - console.log(comp2); - if(comp1 != comp2) { - return false; + function makeComp(x) { + return [x.constant, x.name, x.stateVariable, x.storageLocation, x.type, x.value, x.visibility].join(":"); } - } - return true; + // if(newLayout.length < oldLayout.length) return false; + for (var i = 0; i < logicLayout.length; i++) { + const a = logicLayout[i].attributes; + const comp1 = makeComp(a); + console.log(comp1); + const b = proxyLayout[i].attributes; + const comp2 = makeComp(b); + console.log(comp2); + if (comp1 != comp2) { + return false; + } + } + return true; } function parseContract(input) { - var output = solc.compile({ sources: input }, 1, _.noop); - const elements = []; - const AST = output.sources.contracts.AST; - // console.log(AST); - traverseAST(AST, elements); - // console.log(elements); + const elements = []; + const AST = input; + traverseAST(AST, elements); + // console.log(elements); - // filter out all Contract Definitions - const contractDefinitions = _.filter(elements, (e,i) => e.name == 'ContractDefinition'); + // filter out all Contract Definitions + const contractDefinitions = _.filter(elements, (e, i) => e.name == "ContractDefinition"); - // filter out all linearizedBaseContracts - // pick the last one as the last contract always has the full inheritance - const linearizedBaseContracts = _.last(_.map(contractDefinitions, e => e.attributes.linearizedBaseContracts)); + // filter out all linearizedBaseContracts + // pick the last one as the last contract always has the full inheritance + const linearizedBaseContracts = _.last(_.map(contractDefinitions, e => e.attributes.linearizedBaseContracts)); - // get all stateVariables - const stateVariables = _.filter(elements, e => e.attributes && e.attributes.stateVariable ) + // get all stateVariables + const stateVariables = _.filter(elements, e => e.attributes && e.attributes.stateVariable); - // group them by scope - const stateVariableMap = _.groupBy(stateVariables, e => e.attributes.scope); + // group them by scope + const stateVariableMap = _.groupBy(stateVariables, e => e.attributes.scope); - orderedStateVariables = _.reduceRight(linearizedBaseContracts, (a, b) => { - return a.concat(stateVariableMap[b] || []) - }, []); - return orderedStateVariables; + orderedStateVariables = _.reduceRight( + linearizedBaseContracts, + (a, b) => { + return a.concat(stateVariableMap[b] || []); + }, + [] + ); + return orderedStateVariables; } -var walkSync = function(dir, filelist) { - files = fs.readdirSync(dir); - filelist = filelist || []; - files.forEach(function(file) { - if (fs.statSync(path.join(dir, file)).isDirectory()) { - filelist = walkSync(path.join(dir, file), filelist); - } - else { - filelist.push(path.join(dir, file)); - } - }); - return filelist; +function walkSync(dir, filelist) { + files = fs.readdirSync(dir); + filelist = filelist || []; + files.forEach(function(file) { + if (fs.statSync(path.join(dir, file)).isDirectory()) { + filelist = walkSync(path.join(dir, file), filelist); + } else { + filelist.push(path.join(dir, file)); + } + }); + return filelist; }; -var findPath = function(logicContractName, proxyContractName, fileList) { - let paths = new Array(); - for (let i =0; i < fileList.length; i++) { - if ((logicContractName === path.basename(fileList[i]) || logicContractName === (path.basename(fileList[i])).split(".")[0]) || - (proxyContractName === path.basename(fileList[i]) || proxyContractName === (path.basename(fileList[i])).split(".")[0])) { - paths.push(fileList[i]); +function findPath(logicContractName, proxyContractName, fileList) { + let paths = new Array(); + for (let i = 0; i < fileList.length; i++) { + if ( + logicContractName === path.basename(fileList[i]) || + logicContractName === path.basename(fileList[i]).split(".")[0] || + (proxyContractName === path.basename(fileList[i]) || proxyContractName === path.basename(fileList[i]).split(".")[0]) + ) { + paths.push(fileList[i]); + } } - } - return paths; -} + return paths; +}; async function flatContracts(_paths, _logic) { let promises = new Array(); - for (let i = 0; i< _paths.length; i++) { - promises.push(await exec(`./node_modules/.bin/sol-merger ${_paths[i]} ./flat`)); + for (let i = 0; i < _paths.length; i++) { + promises.push(await exec(`./node_modules/.bin/sol-merger ${_paths[i]} ./flat`)); } await Promise.all(promises); } +async function getAST(_filePath) { + await exec(`solc -o temp --ast-json ${_filePath}`, {maxBuffer: 1024 * 1000}); + return JSON.parse(fs.readFileSync(`./temp/${path.basename(_filePath)}_json.ast`, "utf8").toString()); +} + +async function flushTemp() { + await exec(`rm -rf temp`); +} \ No newline at end of file diff --git a/scripts/deploy.sh b/scripts/deploy.sh deleted file mode 100755 index f961fb5fd..000000000 --- a/scripts/deploy.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env bash - -node_modules/.bin/truffle deploy --network kovan -address=$(grep build/contracts/PolymathRegistry.json -e "\"address\":" | grep -o "0[0-9a-z]*") -commitId=$(git rev-parse HEAD) -branchName=$(git rev-parse --abbrev-ref HEAD) -commitName=$(git log -1 --pretty=%B) -data='{"text":"Somebody merged a new pull request!\nBranch Name: '$branchName'\nCommit Name: '$commitName'\nCommit ID: '$commitId'\nPolymath Registry Address(Kovan): '$address'"}' -curl -X POST -H 'Content-type: application/json' --data "$data" ${SLACK_DEVNOTIFACTIONS_WH} \ No newline at end of file diff --git a/scripts/docs.sh b/scripts/docs.sh index a65fbd71c..93d19f8fd 100755 --- a/scripts/docs.sh +++ b/scripts/docs.sh @@ -12,16 +12,15 @@ CORE_ROUTE=$PWD # functions that used to create the documentation create_docs() { - # getting the all available branches + # getting the all available branches 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)" -ge 1 ]; - then + then echo "$latestTag Branch is already present on remote" exit 0 fi - # Checkout and create the $latestTag branch git checkout -b $latestTag @@ -38,12 +37,12 @@ create_docs() { migrate=$(SOLC_ARGS="openzeppelin-solidity="$CORE_ROUTE"/node_modules/openzeppelin-solidity" \ solidity-docgen -x external/oraclizeAPI.sol,mocks/MockPolyOracle.sol,oracles/PolyOracle.sol $CORE_ROUTE $CORE_ROUTE/contracts $CORE_ROUTE/polymath-developer-portal/) - + echo "Successfully docs are generated..." - + echo "Installing npm dependencies..." yarn install > /dev/null 2>&1 - + echo "Gererate versioning docs..." yarn run version $versionNo @@ -62,7 +61,7 @@ solidity-docgen -x external/oraclizeAPI.sol,mocks/MockPolyOracle.sol,oracles/Pol echo "Removing the repository from the system...." cd ../../../ rm -rf polymath-developer-portal - exit 0 + exit 0 } reject_docs() { @@ -86,8 +85,9 @@ echo "Latest tag is: $latestTag" curl -o node_modules/solidity-docgen/lib/index.js https://raw.githubusercontent.com/maxsam4/solidity-docgen/build/lib/index.js # clone the polymath-developer-portal + if [ ! -d $DIRECTORY ]; then -git clone https://${GH_USR}:${GH_PWD}@github.com/PolymathNetwork/polymath-developer-portal.git > /dev/null 2>&1 +git clone https://${GH_USR}:${GH_PWD}@github.com/PolymathNetwork/polymath-developer-portal.git > /dev/null 2>&1 cd $DIRECTORY else cd $DIRECTORY @@ -99,9 +99,9 @@ cd website if [ ! -d $WEBSITE_DIRECTORY ]; then echo "Created: versioned_docs directory" -create_docs -else - for dir in $WEBSITE_DIRECTORY/*; +create_docs +else + for dir in $WEBSITE_DIRECTORY/*; do if [ "$(basename "$dir")" == "*" ]; then echo "There is no version specific folders" @@ -110,12 +110,12 @@ else reponame=$(echo $(basename "$dir") | cut -d '-' -f2) echo $reponame if [ "$reponame" == "$versionNo" ]; then - reject_docs + reject_docs fi - fi + fi done create_docs fi #reponame=$(echo $(basename "$dir") | cut -d '-' -f2 | cut -b 2-6) -# echo $reponame \ No newline at end of file +# echo $reponame diff --git a/scripts/encoders/encode_CappedSTO.js b/scripts/encoders/encode_CappedSTO.js index 12527a1e1..dc9d054c2 100644 --- a/scripts/encoders/encode_CappedSTO.js +++ b/scripts/encoders/encode_CappedSTO.js @@ -1,10 +1,10 @@ -const Web3 = require('web3'); +const Web3 = require("web3"); -if (typeof web3 !== 'undefined') { - web3 = new Web3(web3.currentProvider); +if (typeof web3 !== "undefined") { + web3 = new Web3(web3.currentProvider); } else { - // set the provider you want from Web3.providers - web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); + // set the provider you want from Web3.providers + web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); } let startTime = process.argv.slice(2)[0]; @@ -13,33 +13,42 @@ let cap = process.argv.slice(2)[2]; let rate = process.argv.slice(2)[3]; let wallet = process.argv.slice(2)[4]; - -let bytesSTO = web3.eth.abi.encodeFunctionCall({ - name: 'configure', - type: 'function', - inputs: [{ - type: 'uint256', - name: '_startTime' - },{ - type: 'uint256', - name: '_endTime' - },{ - type: 'uint256', - name: '_cap' - },{ - type: 'uint256', - name: '_rate' - },{ - type: 'uint8', - name: '_fundRaiseType' - },{ - type: 'address', - name: '_polyToken' - },{ - type: 'address', - name: '_fundsReceiver' - } +let bytesSTO = web3.eth.abi.encodeFunctionCall( + { + name: "configure", + type: "function", + inputs: [ + { + type: "uint256", + name: "_startTime" + }, + { + type: "uint256", + name: "_endTime" + }, + { + type: "uint256", + name: "_cap" + }, + { + type: "uint256", + name: "_rate" + }, + { + type: "uint8", + name: "_fundRaiseType" + }, + { + type: "address", + name: "_polyToken" + }, + { + type: "address", + name: "_fundsReceiver" + } ] - }, [startTime, endTime, web3.utils.toWei(cap, 'ether'), rate,0,0,wallet]); + }, + [startTime, endTime, web3.utils.toWei(cap, "ether"), rate, 0, 0, wallet] +); - console.log(bytesSTO); +console.log(bytesSTO); diff --git a/scripts/gasUsage.sh b/scripts/gasUsage.sh new file mode 100755 index 000000000..a7794d371 --- /dev/null +++ b/scripts/gasUsage.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +mv truffle-config.js truffle-config-bk.js +cp truffle-config-gas.js truffle-config.js + +scripts/test.sh + +mv truffle-config-bk.js truffle-config.js \ No newline at end of file diff --git a/scripts/patch.js b/scripts/patch.js index b1bbbcb3f..602e0ef0b 100644 --- a/scripts/patch.js +++ b/scripts/patch.js @@ -1,25 +1,26 @@ -const fs = require('fs'); -const request = require('request'); -const regex = /node ..\/n(.)*,/gmi; -const regex2 = /truffle test(.)*,/gmi; +const fs = require("fs"); +const request = require("request"); +const regex = /node ..\/n(.)*,/gim; +const regex2 = /truffle test(.)*,/gim; -request('https://raw.githubusercontent.com/maxsam4/solidity-coverage/relative-path/lib/app.js').pipe(fs.createWriteStream('node_modules\\solidity-coverage\\lib\\app.js')); +request("https://raw.githubusercontent.com/maxsam4/solidity-coverage/relative-path/lib/app.js").pipe( + fs.createWriteStream("node_modules\\solidity-coverage\\lib\\app.js") +); -fs.readFile('.solcover.js', 'utf8', function (err,data) { - if (err) { - return console.log(err); - } +fs.readFile(".solcover.js", "utf8", function(err, data) { + if (err) { + return console.log(err); + } - let testCommand = 'truffle test --network coverage'; - fs.readdirSync('./test').forEach(file => { - if(file != 'a_poly_oracle.js' && file != 's_v130_to_v140_upgrade.js') - testCommand = testCommand + ' test\\\\' + file; - }); - testCommand = testCommand + '\','; - let result = data.replace(regex2, testCommand); - result = result.replace(regex, testCommand); + let testCommand = "truffle test --network coverage"; + fs.readdirSync("./test").forEach(file => { + if (file != "a_poly_oracle.js") testCommand = testCommand + " test\\\\" + file; + }); + testCommand = testCommand + "',"; + let result = data.replace(regex2, testCommand); + result = result.replace(regex, testCommand); - fs.writeFile('.solcover.js', result, 'utf8', function (err) { - if (err) return console.log(err); - }); -}); \ No newline at end of file + fs.writeFile(".solcover.js", result, "utf8", function(err) { + if (err) return console.log(err); + }); +}); diff --git a/scripts/test.sh b/scripts/test.sh index 0d8fa4505..4628af274 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -56,8 +56,8 @@ start_testrpc() { --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501209,1000000000000000000000000" ) - if [ "$COVERAGE" = true ] || [ "$TRAVIS_PULL_REQUEST" > 0 ] && [ "$NOT_FORK" != true ]; then - node_modules/.bin/testrpc-sc --gasLimit 0xfffffffffff --port "$testrpc_port" "${accounts[@]}" > /dev/null & + if [ "$COVERAGE" = true ]; then + node --max-old-space-size=3500 node_modules/.bin/testrpc-sc --gasLimit 0xfffffffff --port "$testrpc_port" "${accounts[@]}" > /dev/null & else node_modules/.bin/ganache-cli --gasLimit 8000000 "${accounts[@]}" > /dev/null & fi @@ -88,18 +88,15 @@ else fi fi -if [ "$COVERAGE" = true ] || [ "$TRAVIS_PULL_REQUEST" > 0 ] && [ "$NOT_FORK" != true ]; then +if [ "$COVERAGE" = true ]; then curl -o node_modules/solidity-coverage/lib/app.js https://raw.githubusercontent.com/maxsam4/solidity-coverage/relative-path/lib/app.js + curl -o node_modules/solidity-parser-sc/build/parser.js https://raw.githubusercontent.com/maxsam4/solidity-parser/solidity-0.5/build/parser.js + node --max_old_space_size=3500 node_modules/.bin/solidity-coverage if [ "$CIRCLECI" = true ]; then - rm truffle-config.js - mv truffle-ci.js truffle-config.js - fi - node_modules/.bin/solidity-coverage - if [ "$CIRCLECI" = true ] || [ "$TRAVIS_PULL_REQUEST" > 0 ] && [ "$NOT_FORK" != true ]; then cat coverage/lcov.info | node_modules/.bin/coveralls || echo 'Failed to report coverage to Coveralls' fi else - if [ "$CIRCLECI" = true ]; then # using mocha junit reporter for parallelism in CircleCI + if [ "$CIRCLECI" = true ]; then # using mocha junit reporter for parallelism in CircleCI mkdir test-results mkdir test-results/mocha rm truffle-config.js @@ -108,9 +105,9 @@ else if [ "$CIRCLE_CI_CRON" = true ]; then node_modules/.bin/truffle test `ls test/*.js | circleci tests split --split-by=timings` else - node_modules/.bin/truffle test `find test/*.js ! -name a_poly_oracle.js -and ! -name s_v130_to_v140_upgrade.js | circleci tests split --split-by=timings` + node_modules/.bin/truffle test `find test/*.js ! -name a_poly_oracle.js | circleci tests split --split-by=timings` fi else - node_modules/.bin/truffle test `find test/*.js ! -name a_poly_oracle.js -and ! -name s_v130_to_v140_upgrade.js` + node_modules/.bin/truffle test `find test/*.js ! -name a_poly_oracle.js` fi fi diff --git a/scripts/tokenInfo-v2.js b/scripts/tokenInfo-v2.js index 1d0f9236f..749bb299d 100644 --- a/scripts/tokenInfo-v2.js +++ b/scripts/tokenInfo-v2.js @@ -1,19 +1,32 @@ const Web3 = require("web3"); const web3 = new Web3(new Web3.providers.HttpProvider("https://mainnet.infura.io/")); -var request = require('request-promise') +var request = require("request-promise"); -const securityTokenABI = JSON.parse(require('fs').readFileSync('../build/contracts/SecurityToken.json').toString()).abi; -const generalTransferManagerABI = JSON.parse(require('fs').readFileSync('../build/contracts/GeneralTransferManager.json').toString()).abi; +const securityTokenABI = JSON.parse( + require("fs") + .readFileSync("../build/contracts/SecurityToken.json") + .toString() +).abi; +const generalTransferManagerABI = JSON.parse( + require("fs") + .readFileSync("../build/contracts/GeneralTransferManager.json") + .toString() +).abi; async function getTokens() { const securityTokenRegistryAddress = "0x240f9f86b1465bf1b8eb29bc88cbf65573dfdd97"; const securityTokenRegistryABI = await getABIfromEtherscan(securityTokenRegistryAddress); const securityTokenRegistry = new web3.eth.Contract(securityTokenRegistryABI, securityTokenRegistryAddress); - let logs = await getLogsFromEtherscan(securityTokenRegistry.options.address, 0, 'latest', 'NewSecurityToken(string,string,address,address,uint256,address,bool,uint256)'); + let logs = await getLogsFromEtherscan( + securityTokenRegistry.options.address, + 0, + "latest", + "NewSecurityToken(string,string,address,address,uint256,address,bool,uint256)" + ); console.log(logs.length); for (let i = 0; i < logs.length; i++) { - let tokenAddress = '0x' + logs[i].topics[1].slice(26, 66) + let tokenAddress = "0x" + logs[i].topics[1].slice(26, 66); await getInfo(tokenAddress); } } @@ -23,25 +36,30 @@ async function getInfo(tokenAddress) { console.log("Token - " + tokenAddress); console.log("----------------------"); //console.log("Owner: " + await token.methods.owner().call()); - console.log("Name: " + await token.methods.name().call()); - console.log("Details: " + await token.methods.tokenDetails().call()); - console.log("Symbol: " + await token.methods.symbol().call()); - console.log("Granularity: " + await token.methods.granularity().call()); - console.log("Total Supply: " + await token.methods.totalSupply().call()); - console.log("Transfers Frozen: " + await token.methods.transfersFrozen().call()); - console.log("Minting Frozen: " + await token.methods.mintingFrozen().call()); + console.log("Name: " + (await token.methods.name().call())); + console.log("Details: " + (await token.methods.tokenDetails().call())); + console.log("Symbol: " + (await token.methods.symbol().call())); + console.log("Granularity: " + (await token.methods.granularity().call())); + console.log("Total Supply: " + (await token.methods.totalSupply().call())); + console.log("Transfers Frozen: " + (await token.methods.transfersFrozen().call())); + console.log("Minting Frozen: " + (await token.methods.mintingFrozen().call())); let controllerDisabled = await token.methods.controllerDisabled().call(); if (controllerDisabled) { console.log("Controller disabled: YES"); } else { - console.log("Controller: " + await token.methods.controller().call()); + console.log("Controller: " + (await token.methods.controller().call())); } - console.log("Investors: " + await token.methods.getInvestorCount().call()); - console.log("Latest Checkpoint: " + await token.methods.currentCheckpointId().call()); + console.log("Investors: " + (await token.methods.getInvestorCount().call())); + console.log("Latest Checkpoint: " + (await token.methods.currentCheckpointId().call())); let gtmEventsCount = 0; - let gtmModules = await token.methods.getModulesByName(web3.utils.toHex('GeneralTransferManager')).call(); + let gtmModules = await token.methods.getModulesByName(web3.utils.toHex("GeneralTransferManager")).call(); for (const m of gtmModules) { - let gtmEvents = await getLogsFromEtherscan(m, 9299699, 'latest', 'ModifyWhitelist(address,uint256,address,uint256,uint256,uint256,bool)'); + let gtmEvents = await getLogsFromEtherscan( + m, + 9299699, + "latest", + "ModifyWhitelist(address,uint256,address,uint256,uint256,uint256,bool)" + ); gtmEventsCount += gtmEvents.length; } console.log("Count of GeneralTransferManager Events: " + gtmEventsCount); @@ -69,19 +87,19 @@ async function getModules(type, token) { } async function getLogsFromEtherscan(_address, _fromBlock, _toBlock, _eventSignature) { - let urlDomain = 'api'; + let urlDomain = "api"; const options = { url: `https://${urlDomain}.etherscan.io/api`, qs: { - module: 'logs', - action: 'getLogs', + module: "logs", + action: "getLogs", fromBlock: _fromBlock, toBlock: _toBlock, address: _address, topic0: web3.utils.sha3(_eventSignature), - apikey: 'THM9IUVC2DJJ6J5MTICDE6H1HGQK14X559' + apikey: "THM9IUVC2DJJ6J5MTICDE6H1HGQK14X559" }, - method: 'GET', + method: "GET", json: true }; let data = await request(options); @@ -89,20 +107,20 @@ async function getLogsFromEtherscan(_address, _fromBlock, _toBlock, _eventSignat } async function getABIfromEtherscan(_address) { - let urlDomain = 'api'; + let urlDomain = "api"; const options = { url: `https://${urlDomain}.etherscan.io/api`, qs: { - module: 'contract', - action: 'getabi', + module: "contract", + action: "getabi", address: _address, - apikey: 'THM9IUVC2DJJ6J5MTICDE6H1HGQK14X559' + apikey: "THM9IUVC2DJJ6J5MTICDE6H1HGQK14X559" }, - method: 'GET', + method: "GET", json: true }; let data = await request(options); return JSON.parse(data.result); } -getTokens(); \ No newline at end of file +getTokens(); diff --git a/scripts/wintest.cmd b/scripts/wintest.cmd index e5a2a6a61..26976da18 100644 --- a/scripts/wintest.cmd +++ b/scripts/wintest.cmd @@ -18,7 +18,5 @@ for %%i in (test\*.js) do call :PushTest %%i :PushTest if NOT "%1" == "test\a_poly_oracle.js" ( - if NOT "%1" == "test\s_v130_to_v140_upgrade.js" ( - set var=%var% %1 - ) + set var=%var% %1 ) diff --git a/test/a_poly_oracle.js b/test/a_poly_oracle.js index 6363fe151..025b6ec72 100644 --- a/test/a_poly_oracle.js +++ b/test/a_poly_oracle.js @@ -5,10 +5,10 @@ import { increaseTime } from "./helpers/time"; import { catchRevert } from "./helpers/exceptions"; const Web3 = require("web3"); -const BigNumber = require("bignumber.js"); +let BN = Web3.utils.BN; const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); // Hardcoded development port -contract("PolyOracle", accounts => { +contract("PolyOracle", async (accounts) => { let I_PolyOracle; let owner; const URL = @@ -52,41 +52,43 @@ contract("PolyOracle", accounts => { describe("Scheduling test cases", async () => { it("Should schedule the timing of the call - fails - non owner", async () => { let timeScheduling = [ - latestTime() + duration.minutes(1), - latestTime() + duration.minutes(2), - latestTime() + duration.minutes(3) + await latestTime() + duration.minutes(1), + await latestTime() + duration.minutes(2), + await latestTime() + duration.minutes(3) ]; - await catchRevert(I_PolyOracle.schedulePriceUpdatesFixed(timeScheduling, { from: accounts[1], value: web3.utils.toWei("2") })); + await catchRevert(I_PolyOracle.schedulePriceUpdatesFixed(timeScheduling, { from: accounts[1], value: new BN(web3.utils.toWei("2")) })); }); it("Should schedule the timing of the call - fails - no value", async () => { let timeScheduling = [ - latestTime() + duration.minutes(1), - latestTime() + duration.minutes(2), - latestTime() + duration.minutes(3) + await latestTime() + duration.minutes(1), + await latestTime() + duration.minutes(2), + await latestTime() + duration.minutes(3) ]; await catchRevert(I_PolyOracle.schedulePriceUpdatesFixed(timeScheduling, { from: owner })); }); it("Should schedule the timing of the call - single call", async () => { - let blockNo = latestBlock(); - let tx = await I_PolyOracle.schedulePriceUpdatesFixed([], { from: owner, value: web3.utils.toWei("1") }); - assert.isAtMost(tx.logs[0].args._time.toNumber(), latestTime()); + let blockNo = await latestBlock(); + let tx = await I_PolyOracle.schedulePriceUpdatesFixed([], { from: owner, value: new BN(web3.utils.toWei("1")) }); + assert.isAtMost(tx.logs[0].args._time.toNumber(), await latestTime()); // await increaseTime(50); - const logNewPriceWatcher = await promisifyLogWatch(I_PolyOracle.PriceUpdated({ fromBlock: blockNo }), 1); + const logNewPriceWatcher = (await I_PolyOracle.getPastEvents('PriceUpdated', {filter: {from: blockNo}}))[0]; // const log = await logNewPriceWatcher; assert.equal(logNewPriceWatcher.event, "PriceUpdated", "PriceUpdated not emitted."); assert.isNotNull(logNewPriceWatcher.args._price, "Price returned was null."); assert.equal(logNewPriceWatcher.args._oldPrice.toNumber(), 0); console.log( - "Success! Current price is: " + logNewPriceWatcher.args._price.dividedBy(new BigNumber(10).pow(18)).toNumber() + " USD/POLY" + "Success! Current price is: " + + logNewPriceWatcher.args._price.div(new BN(10).pow(new BN(18))).toNumber() + + " USD/POLY" ); }); it("Should schedule the timing of the call - multiple calls", async () => { - let blockNo = latestBlock(); - let timeScheduling = [latestTime() + duration.seconds(10), latestTime() + duration.seconds(20)]; - let tx = await I_PolyOracle.schedulePriceUpdatesFixed(timeScheduling, { from: owner, value: web3.utils.toWei("1.5") }); + let blockNo = await latestBlock(); + let timeScheduling = [await latestTime() + duration.seconds(10), await latestTime() + duration.seconds(20)]; + let tx = await I_PolyOracle.schedulePriceUpdatesFixed(timeScheduling, { from: owner, value: new BN(web3.utils.toWei("1.5")) }); let event_data = tx.logs; @@ -97,34 +99,34 @@ contract("PolyOracle", accounts => { } // Wait for the callback to be invoked by oraclize and the event to be emitted - const logNewPriceWatcher = promisifyLogWatch(I_PolyOracle.PriceUpdated({ fromBlock: blockNo }), 2); + const logNewPriceWatcher = (await I_PolyOracle.getPastEvents('PriceUpdated', {filter: {from: blockNo}}))[1]; const log = await logNewPriceWatcher; assert.equal(log.event, "PriceUpdated", "PriceUpdated not emitted."); assert.isNotNull(log.args._price, "Price returned was null."); - console.log("Success! Current price is: " + log.args._price.dividedBy(new BigNumber(10).pow(18)).toNumber() + " USD/POLY"); + console.log("Success! Current price is: " + log.args._price.div(new BN(10).pow(new BN(18))).toNumber() + " USD/POLY"); }); it("Should schedule to call using iters - fails", async () => { - await catchRevert(I_PolyOracle.schedulePriceUpdatesRolling(latestTime() + 10, 30, 2, { from: accounts[6] })); + await catchRevert(I_PolyOracle.schedulePriceUpdatesRolling(await latestTime() + 10, 30, 2, { from: accounts[6] })); }); it("Should schedule to call using iters", async () => { - let blockNo = latestBlock(); + let blockNo = await latestBlock(); console.log(`Latest Block number of the local chain:${blockNo}`); - let tx = await I_PolyOracle.schedulePriceUpdatesRolling(latestTime() + 10, 10, 2, { from: owner }); + let tx = await I_PolyOracle.schedulePriceUpdatesRolling(await latestTime() + 10, 10, 2, { from: owner }); let event_data = tx.logs; for (var i = 0; i < event_data.length; i++) { let time = event_data[i].args._time; requestIds.push(event_data[i].args._queryId); console.log(` checking the time for the ${i} index and the scheduling time is ${time}`); - assert.isAtMost(time.toNumber(), latestTime() + (i + 1) * 30); + assert.isAtMost(time.toNumber(), await latestTime() + (i + 1) * 30); } // Wait for the callback to be invoked by oraclize and the event to be emitted - const logNewPriceWatcher = promisifyLogWatch(I_PolyOracle.PriceUpdated({ fromBlock: blockNo }), 2); + const logNewPriceWatcher = (await I_PolyOracle.getPastEvents('PriceUpdated', {filter: {from: blockNo}}))[1]; const log = await logNewPriceWatcher; assert.equal(log.event, "PriceUpdated", "PriceUpdated not emitted."); assert.isNotNull(log.args._price, "Price returned was null."); - console.log("Success! Current price is: " + log.args._price.dividedBy(new BigNumber(10).pow(18)).toNumber() + " USD/POLY"); + console.log("Success! Current price is: " + log.args._price.div(new BN(10).pow(new BN(18))).toNumber() + " USD/POLY"); latestPrice = log.args._price; }); }); @@ -154,26 +156,26 @@ contract("PolyOracle", accounts => { }); it("Should change the sanity bounds manually - fails - bad owner", async () => { - await catchRevert(I_PolyOracle.setSanityBounds(new BigNumber(25).times(new BigNumber(10).pow(16)), { from: accounts[6] })); + await catchRevert(I_PolyOracle.setSanityBounds(new BN(25).mul(new BN(10).pow(16)), { from: accounts[6] })); }); it("Should change the sanity bounds manually", async () => { console.log(JSON.stringify(await I_PolyOracle.sanityBounds.call())); - await I_PolyOracle.setSanityBounds(new BigNumber(25).times(new BigNumber(10).pow(16)), { from: owner }); + await I_PolyOracle.setSanityBounds(new BN(25).mul(new BN(10).pow(16)), { from: owner }); let sanityBounds = await I_PolyOracle.sanityBounds.call(); console.log(JSON.stringify(await I_PolyOracle.sanityBounds.call())); - assert.equal(sanityBounds.toNumber(), new BigNumber(25).times(new BigNumber(10).pow(16)).toNumber()); + assert.equal(sanityBounds.toNumber(), new BN(25).mul(new BN(10).pow(16)).toNumber()); }); it("Should change the gas price manually - fails - bad owner", async () => { - await catchRevert(I_PolyOracle.setGasPrice(new BigNumber(60).times(new BigNumber(10).pow(9)), { from: accounts[6] })); + await catchRevert(I_PolyOracle.setGasPrice(new BN(60).mul(new BN(10).pow(9)), { from: accounts[6] })); }); it("Should change the gas price manually", async () => { - await I_PolyOracle.setGasPrice(new BigNumber(60).times(new BigNumber(10).pow(9)), { from: owner }); - let blockNo = latestBlock(); - let timeScheduling = [latestTime() + duration.seconds(10), latestTime() + duration.seconds(20)]; - let tx = await I_PolyOracle.schedulePriceUpdatesFixed(timeScheduling, { from: owner, value: web3.utils.toWei("2") }); + await I_PolyOracle.setGasPrice(new BN(60).mul(new BN(10).pow(9)), { from: owner }); + let blockNo = await latestBlock(); + let timeScheduling = [await latestTime() + duration.seconds(10), await latestTime() + duration.seconds(20)]; + let tx = await I_PolyOracle.schedulePriceUpdatesFixed(timeScheduling, { from: owner, value: new BN(web3.utils.toWei("2")) }); let event_data = tx.logs; @@ -182,13 +184,14 @@ contract("PolyOracle", accounts => { console.log(` checking the time for the ${i} index and the scheduling time is ${time}`); assert.isAtMost(time.toNumber(), timeScheduling[i]); } - - const logNewPriceWatcher = await promisifyLogWatch(I_PolyOracle.PriceUpdated({ fromBlock: blockNo }), 2); + const logNewPriceWatcher = (await I_PolyOracle.getPastEvents('PriceUpdated', {filter: {from: blockNo}}))[1]; assert.equal(logNewPriceWatcher.event, "PriceUpdated", "PriceUpdated not emitted."); assert.isNotNull(logNewPriceWatcher.args._price, "Price returned was null."); console.log( - "Success! Current price is: " + logNewPriceWatcher.args._price.dividedBy(new BigNumber(10).pow(18)).toNumber() + " USD/POLY" + "Success! Current price is: " + + logNewPriceWatcher.args._price.div(new BN(10).pow(new BN(18))).toNumber() + + " USD/POLY" ); // assert.isTrue(false); }); @@ -243,14 +246,16 @@ contract("PolyOracle", accounts => { }); it("Should schedule the timing of the call - after changes", async () => { - let blockNo = latestBlock(); - let tx = await I_PolyOracle.schedulePriceUpdatesFixed([], { from: owner, value: web3.utils.toWei("1") }); - assert.isAtMost(tx.logs[0].args._time.toNumber(), latestTime()); - const logNewPriceWatcher = await promisifyLogWatch(I_PolyOracle.PriceUpdated({ fromBlock: blockNo }), 1); + let blockNo = await latestBlock(); + let tx = await I_PolyOracle.schedulePriceUpdatesFixed([], { from: owner, value: new BN(web3.utils.toWei("1")) }); + assert.isAtMost(tx.logs[0].args._time.toNumber(), await latestTime()); + const logNewPriceWatcher = (await I_PolyOracle.getPastEvents('PriceUpdated', {filter: {from: blockNo}}))[0]; assert.equal(logNewPriceWatcher.event, "PriceUpdated", "PriceUpdated not emitted."); assert.isNotNull(logNewPriceWatcher.args._price, "Price returned was null."); console.log( - "Success! Current price is: " + logNewPriceWatcher.args._price.dividedBy(new BigNumber(10).pow(18)).toNumber() + " USD/POLY" + "Success! Current price is: " + + logNewPriceWatcher.args._price.div(new BN(10).pow(new BN(18))).toNumber() + + " USD/POLY" ); // assert.isTrue(false); }); diff --git a/test/b_capped_sto.js b/test/b_capped_sto.js index ea4317638..512f10fcd 100644 --- a/test/b_capped_sto.js +++ b/test/b_capped_sto.js @@ -1,25 +1,28 @@ import latestTime from "./helpers/latestTime"; -import { duration, ensureException, promisifyLogWatch, latestBlock } from "./helpers/utils"; +import { duration, ensureException, latestBlock } from "./helpers/utils"; import { takeSnapshot, increaseTime, revertToSnapshot } from "./helpers/time"; import { encodeModuleCall } from "./helpers/encodeCall"; -import { setUpPolymathNetwork, deployGPMAndVerifyed, deployDummySTOAndVerifyed } from "./helpers/createInstances"; +import { setUpPolymathNetwork, deployGPMAndVerifyed, deployCappedSTOAndVerifyed, deployDummySTOAndVerifyed } from "./helpers/createInstances"; import { catchRevert } from "./helpers/exceptions"; const CappedSTOFactory = artifacts.require("./CappedSTOFactory.sol"); +const STFactory = artifacts.require("./STFactory.sol"); const CappedSTO = artifacts.require("./CappedSTO.sol"); const DummySTO = artifacts.require("./DummySTO.sol"); const SecurityToken = artifacts.require("./SecurityToken.sol"); const GeneralTransferManager = artifacts.require("./GeneralTransferManager"); const GeneralPermissionManager = artifacts.require("./GeneralPermissionManager"); +const STGetter = artifacts.require("./STGetter.sol"); const Web3 = require("web3"); -const BigNumber = require("bignumber.js"); +let BN = Web3.utils.BN; +let toBN = Web3.utils.toBN; const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); // Hardcoded development port let ETH = 0; let POLY = 1; let DAI = 2; -contract("CappedSTO", accounts => { +contract("CappedSTO", async (accounts) => { // Accounts Variable declaration let account_polymath; let account_investor1; @@ -53,14 +56,19 @@ contract("CappedSTO", accounts => { let I_STFactory; let I_SecurityToken_ETH; let I_SecurityToken_POLY; + let I_DummySTO; let I_CappedSTO_Array_ETH = []; let I_CappedSTO_Array_POLY = []; - let I_DummySTO; let I_PolyToken; let I_PolymathRegistry; let I_STRProxied; let I_MRProxied; + let I_STRGetter; + let I_STGetter; + let stGetter_eth; + let stGetter_poly; let pauseTime; + let treasury_wallet; // SecurityToken Details for funds raise Type ETH const name = "Team"; @@ -80,15 +88,15 @@ contract("CappedSTO", accounts => { const budget = 0; // Initial fee for ticker registry and security token registry - const initRegFee = web3.utils.toWei("250"); + const initRegFee = new BN(web3.utils.toWei("1000")); // Capped STO details let startTime_ETH1; let endTime_ETH1; let startTime_ETH2; let endTime_ETH2; - const cap = web3.utils.toWei("10000"); - const rate = web3.utils.toWei("1000"); + const cap = new BN(web3.utils.toWei("10000")); + const rate = new BN(web3.utils.toWei("1000")); const E_fundRaiseType = 0; const address_zero = "0x0000000000000000000000000000000000000000"; @@ -97,25 +105,28 @@ contract("CappedSTO", accounts => { let startTime_POLY2; let endTime_POLY2; let blockNo; - const P_cap = web3.utils.toWei("50000"); + const P_cap = new BN(web3.utils.toWei("50000")); const P_fundRaiseType = 1; - const P_rate = web3.utils.toWei("5"); - const cappedSTOSetupCost = web3.utils.toWei("20000", "ether"); - const maxCost = cappedSTOSetupCost; + const P_rate = new BN(web3.utils.toWei("5")); + const cappedSTOSetupCost = new BN(web3.utils.toWei("20000", "ether")); + const cappedSTOSetupCostPOLY = new BN(web3.utils.toWei("80000", "ether")); + const maxCost = cappedSTOSetupCostPOLY; const STOParameters = ["uint256", "uint256", "uint256", "uint256", "uint8[]", "address"]; + let currentTime; + before(async () => { - // Accounts setup + currentTime = new BN(await latestTime()); account_polymath = accounts[0]; account_issuer = accounts[1]; account_investor1 = accounts[4]; account_investor2 = accounts[3]; account_investor3 = accounts[5]; account_fundsReceiver = accounts[2]; + treasury_wallet = accounts[6]; token_owner = account_issuer; let instances = await setUpPolymathNetwork(account_polymath, token_owner); - [ I_PolymathRegistry, I_PolyToken, @@ -127,27 +138,16 @@ contract("CappedSTO", accounts => { I_STFactory, I_SecurityTokenRegistry, I_SecurityTokenRegistryProxy, - I_STRProxied + I_STRProxied, + I_STRGetter, + I_STGetter ] = instances; // STEP 5: Deploy the GeneralDelegateManagerFactory - [I_GeneralPermissionManagerFactory] = await deployGPMAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, 0); - + [I_GeneralPermissionManagerFactory] = await deployGPMAndVerifyed(account_polymath, I_MRProxied, new BN(0)); // STEP 6: Deploy the CappedSTOFactory - I_CappedSTOFactory = await CappedSTOFactory.new(I_PolyToken.address, cappedSTOSetupCost, 0, 0, { from: token_owner }); - - assert.notEqual( - I_CappedSTOFactory.address.valueOf(), - address_zero, - "CappedSTOFactory contract was not deployed" - ); - - // STEP 7: Register the Modules with the ModuleRegistry contract - - // (C) : Register the STOFactory - await I_MRProxied.registerModule(I_CappedSTOFactory.address, { from: account_polymath }); - await I_MRProxied.verifyModule(I_CappedSTOFactory.address, true, { from: account_polymath }); + [I_CappedSTOFactory] = await deployCappedSTOAndVerifyed(account_polymath, I_MRProxied, cappedSTOSetupCost); // Printing all the contract addresses console.log(` @@ -171,75 +171,77 @@ contract("CappedSTO", accounts => { describe("Generate the SecurityToken", async () => { it("Should register the ticker before the generation of the security token", async () => { await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); - let tx = await I_STRProxied.registerTicker(token_owner, symbol, name, { from: token_owner }); + let tx = await I_STRProxied.registerNewTicker(token_owner, symbol, { from: token_owner }); assert.equal(tx.logs[0].args._owner, token_owner); assert.equal(tx.logs[0].args._ticker, symbol); }); it("Should generate the new security token with the same symbol as registered above", async () => { await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); - let _blockNo = latestBlock(); - let tx = await I_STRProxied.generateSecurityToken(name, symbol, tokenDetails, false, { from: token_owner }); + let t = await I_STRGetter.getSTFactoryAddress.call(); + console.log(t); + let foo = await STFactory.at(t); + console.log(await foo.polymathRegistry.call()); + + let tx = await I_STRProxied.generateNewSecurityToken(name, symbol, tokenDetails, false, treasury_wallet, 0, { from: token_owner }); // Verify the successful generation of the security token assert.equal(tx.logs[1].args._ticker, symbol, "SecurityToken doesn't get deployed"); - I_SecurityToken_ETH = SecurityToken.at(tx.logs[1].args._securityTokenAddress); - - const log = await promisifyLogWatch(I_SecurityToken_ETH.ModuleAdded({ from: _blockNo }), 1); - + I_SecurityToken_ETH = await SecurityToken.at(tx.logs[1].args._securityTokenAddress); + stGetter_eth = await STGetter.at(I_SecurityToken_ETH.address); + assert.equal(await stGetter_eth.getTreasuryWallet.call(), treasury_wallet, "Incorrect wallet set") + const log = (await I_SecurityToken_ETH.getPastEvents('ModuleAdded', {filter: {transactionHash: tx.transactionHash}}))[0]; // Verify that GeneralTransferManager module get added successfully or not assert.equal(log.args._types[0].toNumber(), transferManagerKey); assert.equal(web3.utils.hexToString(log.args._name), "GeneralTransferManager"); }); - it("Should intialize the auto attached modules", async () => { - let moduleData = (await I_SecurityToken_ETH.getModulesByType(transferManagerKey))[0]; - I_GeneralTransferManager = GeneralTransferManager.at(moduleData); + it("Should initialize the auto attached modules", async () => { + let moduleData = (await stGetter_eth.getModulesByType(transferManagerKey))[0]; + I_GeneralTransferManager = await GeneralTransferManager.at(moduleData); }); it("Should mint the tokens before attaching the STO", async () => { - await catchRevert( - I_SecurityToken_ETH.mint(address_zero, web3.utils.toWei("1"), { from: token_owner }) - ); + await catchRevert(I_SecurityToken_ETH.issue(address_zero, new BN(new BN(web3.utils.toWei("1"))), "0x0", { from: token_owner })); }); it("Should fail to launch the STO due to security token doesn't have the sufficient POLY", async () => { - let startTime = latestTime() + duration.days(1); + let startTime = await latestTime() + duration.days(1); let endTime = startTime + duration.days(30); - await I_PolyToken.getTokens(cappedSTOSetupCost, token_owner); + await I_PolyToken.getTokens(cappedSTOSetupCostPOLY, token_owner); - let bytesSTO = encodeModuleCall(STOParameters, [startTime, endTime, cap, 0, [E_fundRaiseType], account_fundsReceiver]); + let bytesSTO = encodeModuleCall(STOParameters, [startTime, endTime, cap, new BN(0), [E_fundRaiseType], account_fundsReceiver]); - await catchRevert(I_SecurityToken_ETH.addModule(I_CappedSTOFactory.address, bytesSTO, maxCost, 0, { from: token_owner })); + await catchRevert(I_SecurityToken_ETH.addModule(I_CappedSTOFactory.address, bytesSTO, maxCost, new BN(0), false, { from: token_owner })); }); it("Should fail to launch the STO due to rate is 0", async () => { - let startTime = latestTime() + duration.days(1); + let startTime = await latestTime() + duration.days(1); let endTime = startTime + duration.days(30); - await I_PolyToken.transfer(I_SecurityToken_ETH.address, cappedSTOSetupCost, { from: token_owner }); + await I_PolyToken.transfer(I_SecurityToken_ETH.address, cappedSTOSetupCostPOLY, { from: token_owner }); - let bytesSTO = encodeModuleCall(STOParameters, [startTime, endTime, cap, 0, [E_fundRaiseType], account_fundsReceiver]); + let bytesSTO = encodeModuleCall(STOParameters, [startTime, endTime, cap, new BN(0), [E_fundRaiseType], account_fundsReceiver]); - await catchRevert(I_SecurityToken_ETH.addModule(I_CappedSTOFactory.address, bytesSTO, maxCost, 0, { from: token_owner })); + await catchRevert(I_SecurityToken_ETH.addModule(I_CappedSTOFactory.address, bytesSTO, maxCost, new BN(0), false, { from: token_owner })); }); it("Should fail to launch the STO due funds reciever account 0x", async () => { - let startTime = latestTime() + duration.days(1); + let startTime = await latestTime() + duration.days(1); let endTime = startTime + duration.days(30); let bytesSTO = encodeModuleCall(STOParameters, [startTime, endTime, cap, rate, [E_fundRaiseType], address_zero]); - await catchRevert(I_SecurityToken_ETH.addModule(I_CappedSTOFactory.address, bytesSTO, maxCost, 0, { from: token_owner })); + await catchRevert(I_SecurityToken_ETH.addModule(I_CappedSTOFactory.address, bytesSTO, maxCost, new BN(0), false, { from: token_owner })); }); it("Should fail to launch the STO due to raise type of 0 length", async () => { - let startTime = latestTime() + duration.days(1); + let startTime = await latestTime() + duration.days(1); let endTime = startTime + duration.days(30); let bytesSTO = encodeModuleCall(STOParameters, [startTime, endTime, cap, rate, [], account_fundsReceiver]); - await catchRevert(I_SecurityToken_ETH.addModule(I_CappedSTOFactory.address, bytesSTO, maxCost, 0, { from: token_owner })); + await catchRevert(I_SecurityToken_ETH.addModule(I_CappedSTOFactory.address, bytesSTO, maxCost, new BN(0), false, { from: token_owner })); }); it("Should fail to launch the STO due to startTime > endTime", async () => { @@ -252,26 +254,26 @@ contract("CappedSTO", accounts => { account_fundsReceiver ]); - await catchRevert(I_SecurityToken_ETH.addModule(I_CappedSTOFactory.address, bytesSTO, maxCost, 0, { from: token_owner })); + await catchRevert(I_SecurityToken_ETH.addModule(I_CappedSTOFactory.address, bytesSTO, maxCost, new BN(0), false, { from: token_owner })); }); it("Should fail to launch the STO due to cap is of 0 securityToken", async () => { - let startTime = latestTime() + duration.days(1); + let startTime = await latestTime() + duration.days(1); let endTime = startTime + duration.days(30); - let bytesSTO = encodeModuleCall(STOParameters, [startTime, endTime, 0, rate, [E_fundRaiseType], account_fundsReceiver]); + let bytesSTO = encodeModuleCall(STOParameters, [startTime, endTime, new BN(0), rate, [E_fundRaiseType], account_fundsReceiver]); - await catchRevert(I_SecurityToken_ETH.addModule(I_CappedSTOFactory.address, bytesSTO, maxCost, 0, { from: token_owner })); + await catchRevert(I_SecurityToken_ETH.addModule(I_CappedSTOFactory.address, bytesSTO, maxCost, new BN(0), false, { from: token_owner })); }); - it("Should fail to launch the STO due to different value incompare to getInitFunction", async() => { - let startTime = latestTime() + duration.days(1); + it("Should fail to launch the STO due to different value incompare to getInitFunction", async () => { + let startTime = await latestTime() + duration.days(1); let endTime = startTime + duration.days(30); - let bytesSTO = encodeModuleCall(['uint256', 'uint256', 'uint256'], [startTime, endTime, 0, ]); - await catchRevert(I_SecurityToken_ETH.addModule(I_CappedSTOFactory.address, bytesSTO, maxCost, 0, { from: token_owner })); + let bytesSTO = encodeModuleCall(["uint256", "uint256", "uint256"], [startTime, endTime, 0]); + await catchRevert(I_SecurityToken_ETH.addModule(I_CappedSTOFactory.address, bytesSTO, maxCost, new BN(0), false, { from: token_owner })); }); it("Should successfully attach the STO module to the security token", async () => { - startTime_ETH1 = latestTime() + duration.days(1); + startTime_ETH1 = await latestTime() + duration.days(1); endTime_ETH1 = startTime_ETH1 + duration.days(30); let bytesSTO = encodeModuleCall(STOParameters, [ startTime_ETH1, @@ -281,26 +283,28 @@ contract("CappedSTO", accounts => { [E_fundRaiseType], account_fundsReceiver ]); - const tx = await I_SecurityToken_ETH.addModule(I_CappedSTOFactory.address, bytesSTO, maxCost, 0, { from: token_owner }); + const tx = await I_SecurityToken_ETH.addModule(I_CappedSTOFactory.address, bytesSTO, maxCost, new BN(0), false, { from: token_owner }); assert.equal(tx.logs[3].args._types[0], stoKey, "CappedSTO doesn't get deployed"); assert.equal(web3.utils.hexToString(tx.logs[3].args._name), "CappedSTO", "CappedSTOFactory module was not added"); - I_CappedSTO_Array_ETH.push(CappedSTO.at(tx.logs[3].args._module)); + I_CappedSTO_Array_ETH.push(await CappedSTO.at(tx.logs[3].args._module)); }); - it("Should call the configure function -- fail because of the bad owner", async()=> { + it("Should call the configure function -- fail because of the bad owner", async () => { await catchRevert( - I_CappedSTO_Array_ETH[0].configure(startTime_ETH1, endTime_ETH1, cap, rate, [E_fundRaiseType], account_fundsReceiver, {from: account_polymath }) + I_CappedSTO_Array_ETH[0].configure(startTime_ETH1, endTime_ETH1, cap, rate, [E_fundRaiseType], account_fundsReceiver, { + from: account_polymath + }) ); - }) + }); }); describe("verify the data of STO", async () => { it("Should verify the configuration of the STO", async () => { - assert.equal(await I_CappedSTO_Array_ETH[0].startTime.call(), startTime_ETH1, "STO Configuration doesn't set as expected"); - assert.equal(await I_CappedSTO_Array_ETH[0].endTime.call(), endTime_ETH1, "STO Configuration doesn't set as expected"); - assert.equal((await I_CappedSTO_Array_ETH[0].cap.call()).toNumber(), cap, "STO Configuration doesn't set as expected"); - assert.equal((await I_CappedSTO_Array_ETH[0].rate.call()).toNumber(), rate, "STO Configuration doesn't set as expected"); + assert.equal(await I_CappedSTO_Array_ETH[0].startTime(), startTime_ETH1, "1STO Configuration doesn't set as expected"); + assert.equal(await I_CappedSTO_Array_ETH[0].endTime(), endTime_ETH1, "2STO Configuration doesn't set as expected"); + assert.equal((await I_CappedSTO_Array_ETH[0].cap()).toString(), cap.toString(), "3STO Configuration doesn't set as expected"); + assert.equal((await I_CappedSTO_Array_ETH[0].rate()).toString(), rate.toString(), "4STO Configuration doesn't set as expected"); assert.equal( await I_CappedSTO_Array_ETH[0].fundRaiseTypes.call(E_fundRaiseType), true, @@ -311,90 +315,66 @@ contract("CappedSTO", accounts => { describe("Buy tokens", async () => { it("Should buy the tokens -- failed due to startTime is greater than Current time", async () => { - await catchRevert( - web3.eth.sendTransaction({ - from: account_investor1, - to: I_CappedSTO_Array_ETH[0].address, - value: web3.utils.toWei("1", "ether") - }) - ); - }); - - it("Should buy the tokens -- failed due to invested amount is zero", async () => { - await catchRevert( - web3.eth.sendTransaction({ - from: account_investor1, - to: I_CappedSTO_Array_ETH[0].address, - value: web3.utils.toWei("0", "ether") - }) - ); + await catchRevert(I_CappedSTO_Array_ETH[0].buyTokens(account_investor1, { from: account_investor1, value: new BN(web3.utils.toWei("1", "ether")) })); + await increaseTime(duration.days(1)); }); it("Should buy the tokens -- Failed due to investor is not in the whitelist", async () => { - await catchRevert( - web3.eth.sendTransaction({ - from: account_investor1, - to: I_CappedSTO_Array_ETH[0].address, - value: web3.utils.toWei("1", "ether") - }) - ); - }); + await catchRevert(I_CappedSTO_Array_ETH[0].buyTokens(account_investor1, { from: account_investor1, value: new BN(web3.utils.toWei("1", "ether")) })); - it("Should Buy the tokens", async () => { - blockNo = latestBlock(); - fromTime = latestTime(); - toTime = latestTime() + duration.days(15); + blockNo = await latestBlock(); + fromTime = await latestTime(); + toTime = await latestTime() + duration.days(15); expiryTime = toTime + duration.days(100); P_fromTime = fromTime + duration.days(1); P_toTime = P_fromTime + duration.days(50); P_expiryTime = toTime + duration.days(100); - balanceOfReceiver = await web3.eth.getBalance(account_fundsReceiver); // Add the Investor in to the whitelist - - let tx = await I_GeneralTransferManager.modifyWhitelist(account_investor1, fromTime, toTime, expiryTime, true, { + let tx = await I_GeneralTransferManager.modifyKYCData(account_investor1, fromTime, toTime, expiryTime, { from: account_issuer }); assert.equal(tx.logs[0].args._investor, account_investor1, "Failed in adding the investor in whitelist"); + }); - // Jump time - await increaseTime(duration.days(1)); - // Fallback transaction - await web3.eth.sendTransaction({ + it("Should buy the tokens -- failed due to invested amount is zero", async () => { + await catchRevert(I_CappedSTO_Array_ETH[0].buyTokens(account_investor1, { from: account_investor1, value: new BN(web3.utils.toWei("0", "ether")) })); + }); + + it("Should Buy the tokens", async () => { + + balanceOfReceiver = new BN(await web3.eth.getBalance(account_fundsReceiver)); + + await I_CappedSTO_Array_ETH[0].buyTokens(account_investor1, { from: account_investor1, - to: I_CappedSTO_Array_ETH[0].address, - gas: 2100000, value: web3.utils.toWei("1", "ether") }); - assert.equal((await I_CappedSTO_Array_ETH[0].getRaised.call(ETH)).dividedBy(new BigNumber(10).pow(18)).toNumber(), 1); + assert.equal((await I_CappedSTO_Array_ETH[0].getRaised.call(ETH)).div(new BN(10).pow(new BN(18))).toNumber(), 1); assert.equal(await I_CappedSTO_Array_ETH[0].investorCount.call(), 1); - assert.equal((await I_SecurityToken_ETH.balanceOf(account_investor1)).dividedBy(new BigNumber(10).pow(18)).toNumber(), 1000); - assert.equal((await I_CappedSTO_Array_ETH[0].getTokensSold.call()).dividedBy(new BigNumber(10).pow(18)).toNumber(), 1000); + assert.equal((await I_SecurityToken_ETH.balanceOf(account_investor1)).div(new BN(10).pow(new BN(18))).toNumber(), 1000); + assert.equal((await I_CappedSTO_Array_ETH[0].getTokensSold.call()).div(new BN(10).pow(new BN(18))).toNumber(), 1000); }); it("Verification of the event Token Purchase", async () => { - const log = await promisifyLogWatch(I_CappedSTO_Array_ETH[0].TokenPurchase({ from: blockNo }), 1); - + const log = (await I_CappedSTO_Array_ETH[0].getPastEvents('TokenPurchase', {filter: {from: blockNo}}))[0]; assert.equal(log.args.purchaser, account_investor1, "Wrong address of the investor"); - assert.equal(log.args.amount.dividedBy(new BigNumber(10).pow(18)).toNumber(), 1000, "Wrong No. token get dilivered"); + assert.equal(log.args.amount.div(new BN(10).pow(new BN(18))).toNumber(), 1000, "Wrong No. token get dilivered"); }); - it("Should fail to buy the tokens -- Because fundRaiseType is ETH not POLY", async ()=> { - await I_PolyToken.getTokens(web3.utils.toWei("500"), account_investor1); - await I_PolyToken.approve(I_CappedSTO_Array_ETH[0].address, web3.utils.toWei("500"), {from: account_investor1}); - await catchRevert( - I_CappedSTO_Array_ETH[0].buyTokensWithPoly(web3.utils.toWei("500"), {from: account_investor1}) - ); - }) + it("Should fail to buy the tokens -- Because fundRaiseType is ETH not POLY", async () => { + await I_PolyToken.getTokens(new BN(new BN(web3.utils.toWei("500"))), account_investor1); + await I_PolyToken.approve(I_CappedSTO_Array_ETH[0].address, new BN(new BN(web3.utils.toWei("500"))), { from: account_investor1 }); + await catchRevert(I_CappedSTO_Array_ETH[0].buyTokensWithPoly(new BN(new BN(web3.utils.toWei("500"))), { from: account_investor1 })); + }); it("Should pause the STO -- Failed due to wrong msg.sender", async () => { await catchRevert(I_CappedSTO_Array_ETH[0].pause({ from: account_investor1 })); }); it("Should pause the STO", async () => { - pauseTime = latestTime(); + pauseTime = await latestTime(); let tx = await I_CappedSTO_Array_ETH[0].pause({ from: account_issuer }); assert.isTrue(await I_CappedSTO_Array_ETH[0].paused.call()); }); @@ -405,7 +385,7 @@ contract("CappedSTO", accounts => { from: account_investor1, to: I_CappedSTO_Array_ETH[0].address, gas: 2100000, - value: web3.utils.toWei("1", "ether") + value: new BN(web3.utils.toWei("1", "ether")) }) ); }); @@ -420,72 +400,77 @@ contract("CappedSTO", accounts => { }); it("Should buy the granular unit tokens and refund pending amount", async () => { - await I_SecurityToken_ETH.changeGranularity(10 ** 21, {from: token_owner}); - let tx = await I_GeneralTransferManager.modifyWhitelist( + await I_SecurityToken_ETH.changeGranularity(new BN(10).pow(new BN(21)), { from: token_owner }); + let tx = await I_GeneralTransferManager.modifyKYCData( account_investor2, fromTime, toTime + duration.days(20), expiryTime, - true, { from: account_issuer } ); assert.equal(tx.logs[0].args._investor, account_investor2, "Failed in adding the investor in whitelist"); - const initBalance = BigNumber(await web3.eth.getBalance(account_investor2)); - tx = await I_CappedSTO_Array_ETH[0].buyTokens(account_investor2, {from: account_investor2, value: web3.utils.toWei("1.5", "ether"), gasPrice: 1}); - const finalBalance = BigNumber(await web3.eth.getBalance(account_investor2)); - assert.equal(finalBalance.add(BigNumber(tx.receipt.gasUsed)).add(web3.utils.toWei("1", "ether")).toNumber(), initBalance.toNumber()); - await I_SecurityToken_ETH.changeGranularity(1, {from: token_owner}); - assert.equal((await I_CappedSTO_Array_ETH[0].getRaised.call(ETH)).dividedBy(new BigNumber(10).pow(18)).toNumber(), 2); - - assert.equal((await I_SecurityToken_ETH.balanceOf(account_investor2)).dividedBy(new BigNumber(10).pow(18)).toNumber(), 1000); + let initBalance = new BN(await web3.eth.getBalance(account_investor2)); + tx = await I_CappedSTO_Array_ETH[0].buyTokens(account_investor2, { + from: account_investor2, + value: new BN(web3.utils.toWei("1.5", "ether")), + gasPrice: 1 + }); + let finalBalance = new BN(await web3.eth.getBalance(account_investor2)); + assert.equal( + finalBalance + .add(new BN(tx.receipt.gasUsed)) + .add(new BN(web3.utils.toWei("1", "ether"))) + .toString(), + initBalance.toString() + ); + await I_SecurityToken_ETH.changeGranularity(1, { from: token_owner }); + assert.equal((await I_CappedSTO_Array_ETH[0].getRaised.call(ETH)).div(new BN(10).pow(new BN(18))).toNumber(), 2); + + assert.equal((await I_SecurityToken_ETH.balanceOf(account_investor2)).div(new BN(10).pow(new BN(18))).toNumber(), 1000); }); it("Should restrict to buy tokens after hiting the cap in second tx first tx pass", async () => { - - // Fallback transaction await web3.eth.sendTransaction({ from: account_investor2, to: I_CappedSTO_Array_ETH[0].address, gas: 2100000, - value: web3.utils.toWei("8", "ether") + value: new BN(web3.utils.toWei("8", "ether")) }); - assert.equal((await I_CappedSTO_Array_ETH[0].getRaised.call(ETH)).dividedBy(new BigNumber(10).pow(18)).toNumber(), 10); + assert.equal((await I_CappedSTO_Array_ETH[0].getRaised.call(ETH)).div(new BN(10).pow(new BN(18))).toNumber(), 10); assert.equal(await I_CappedSTO_Array_ETH[0].investorCount.call(), 2); - assert.equal((await I_SecurityToken_ETH.balanceOf(account_investor2)).dividedBy(new BigNumber(10).pow(18)).toNumber(), 9000); - await catchRevert(I_CappedSTO_Array_ETH[0].buyTokens(account_investor2, { value: web3.utils.toWei("81") })); + assert.equal((await I_SecurityToken_ETH.balanceOf(account_investor2)).div(new BN(10).pow(new BN(18))).toNumber(), 9000); + await catchRevert(I_CappedSTO_Array_ETH[0].buyTokens(account_investor2, { value: new BN(web3.utils.toWei("81")) })); }); it("Should fundRaised value equal to the raised value in the funds receiver wallet", async () => { const newBalance = await web3.eth.getBalance(account_fundsReceiver); //console.log("WWWW",newBalance,await I_CappedSTO.fundsRaised.call(),balanceOfReceiver); - let op = new BigNumber(newBalance) - .minus(balanceOfReceiver) - .toNumber(); + let op = new BN(newBalance).sub(balanceOfReceiver); assert.equal( - (await I_CappedSTO_Array_ETH[0].getRaised.call(ETH)).toNumber(), - op, + (await I_CappedSTO_Array_ETH[0].getRaised.call(ETH)).toString(), + op.toString(), "Somewhere raised money get stolen or sent to wrong wallet" ); }); it("Should get the raised amount of ether", async () => { - assert.equal(await I_CappedSTO_Array_ETH[0].getRaised.call(ETH), web3.utils.toWei("10", "ether")); + assert.equal((await I_CappedSTO_Array_ETH[0].getRaised.call(ETH)).toString(), new BN(web3.utils.toWei("10", "ether")).toString()); }); it("Should get the raised amount of poly", async () => { - assert.equal((await I_CappedSTO_Array_ETH[0].getRaised.call(POLY)).toNumber(), web3.utils.toWei("0", "ether")); + assert.equal((await I_CappedSTO_Array_ETH[0].getRaised.call(POLY)).toString(), new BN(web3.utils.toWei("0", "ether")).toString()); }); }); describe("Reclaim poly sent to STO by mistake", async () => { it("Should fail to reclaim POLY because token contract address is 0 address", async () => { - let value = web3.utils.toWei("100", "ether"); + let value = new BN(web3.utils.toWei("100", "ether")); await I_PolyToken.getTokens(value, account_investor1); await I_PolyToken.transfer(I_CappedSTO_Array_ETH[0].address, value, { from: account_investor1 }); @@ -493,30 +478,30 @@ contract("CappedSTO", accounts => { }); it("Should successfully reclaim POLY", async () => { - let initInvestorBalance = await I_PolyToken.balanceOf(account_investor1); - let initOwnerBalance = await I_PolyToken.balanceOf(token_owner); - let initContractBalance = await I_PolyToken.balanceOf(I_CappedSTO_Array_ETH[0].address); - let value = web3.utils.toWei("100", "ether"); + let initInvestorBalance = new BN(await I_PolyToken.balanceOf(account_investor1)); + let initOwnerBalance = new BN(await I_PolyToken.balanceOf(token_owner)); + let initContractBalance = new BN(await I_PolyToken.balanceOf(I_CappedSTO_Array_ETH[0].address)); + let value = new BN(web3.utils.toWei("100", "ether")); await I_PolyToken.getTokens(value, account_investor1); await I_PolyToken.transfer(I_CappedSTO_Array_ETH[0].address, value, { from: account_investor1 }); await I_CappedSTO_Array_ETH[0].reclaimERC20(I_PolyToken.address, { from: token_owner }); assert.equal( - (await I_PolyToken.balanceOf(account_investor1)).toNumber(), - initInvestorBalance.toNumber(), + (await I_PolyToken.balanceOf(account_investor1)).toString(), + initInvestorBalance.toString(), "tokens are not transferred out from investor account" ); assert.equal( - (await I_PolyToken.balanceOf(token_owner)).toNumber(), + (await I_PolyToken.balanceOf(token_owner)).toString(), initOwnerBalance .add(value) .add(initContractBalance) - .toNumber(), + .toString(), "tokens are not added to the owner account" ); assert.equal( - (await I_PolyToken.balanceOf(I_CappedSTO_Array_ETH[0].address)).toNumber(), - 0, + (await I_PolyToken.balanceOf(I_CappedSTO_Array_ETH[0].address)).toString(), + new BN(0).toString(), "tokens are not trandfered out from STO contract" ); }); @@ -524,11 +509,11 @@ contract("CappedSTO", accounts => { describe("Attach second ETH STO module", async () => { it("Should successfully attach the second STO module to the security token", async () => { - startTime_ETH2 = latestTime() + duration.days(1); + startTime_ETH2 = await latestTime() + duration.days(1); endTime_ETH2 = startTime_ETH2 + duration.days(30); - await I_PolyToken.getTokens(cappedSTOSetupCost, token_owner); - await I_PolyToken.transfer(I_SecurityToken_ETH.address, cappedSTOSetupCost, { from: token_owner }); + await I_PolyToken.getTokens(cappedSTOSetupCostPOLY, token_owner); + await I_PolyToken.transfer(I_SecurityToken_ETH.address, cappedSTOSetupCostPOLY, { from: token_owner }); let bytesSTO = encodeModuleCall(STOParameters, [ startTime_ETH2, endTime_ETH2, @@ -537,18 +522,18 @@ contract("CappedSTO", accounts => { [E_fundRaiseType], account_fundsReceiver ]); - const tx = await I_SecurityToken_ETH.addModule(I_CappedSTOFactory.address, bytesSTO, maxCost, 0, { from: token_owner }); + const tx = await I_SecurityToken_ETH.addModule(I_CappedSTOFactory.address, bytesSTO, maxCost, new BN(0), false, { from: token_owner }); assert.equal(tx.logs[3].args._types[0], stoKey, "CappedSTO doesn't get deployed"); assert.equal(web3.utils.hexToString(tx.logs[3].args._name), "CappedSTO", "CappedSTOFactory module was not added"); - I_CappedSTO_Array_ETH.push(CappedSTO.at(tx.logs[3].args._module)); + I_CappedSTO_Array_ETH.push(await CappedSTO.at(tx.logs[3].args._module)); }); it("Should verify the configuration of the STO", async () => { assert.equal(await I_CappedSTO_Array_ETH[1].startTime.call(), startTime_ETH2, "STO Configuration doesn't set as expected"); assert.equal(await I_CappedSTO_Array_ETH[1].endTime.call(), endTime_ETH2, "STO Configuration doesn't set as expected"); - assert.equal((await I_CappedSTO_Array_ETH[1].cap.call()).toNumber(), cap, "STO Configuration doesn't set as expected"); - assert.equal((await I_CappedSTO_Array_ETH[1].rate.call()).toNumber(), rate, "STO Configuration doesn't set as expected"); + assert.equal((await I_CappedSTO_Array_ETH[1].cap.call()).toString(), cap.toString(), "STO Configuration doesn't set as expected"); + assert.equal((await I_CappedSTO_Array_ETH[1].rate.call()).toString(), rate.toString(), "STO Configuration doesn't set as expected"); assert.equal( await I_CappedSTO_Array_ETH[1].fundRaiseTypes.call(E_fundRaiseType), true, @@ -557,9 +542,9 @@ contract("CappedSTO", accounts => { }); it("Should successfully whitelist investor 3", async () => { - balanceOfReceiver = await web3.eth.getBalance(account_fundsReceiver); + balanceOfReceiver = new BN(await web3.eth.getBalance(account_fundsReceiver)); - let tx = await I_GeneralTransferManager.modifyWhitelist(account_investor3, fromTime, toTime, expiryTime, true, { + let tx = await I_GeneralTransferManager.modifyKYCData(account_investor3, fromTime, toTime, expiryTime, { from: account_issuer, gas: 500000 }); @@ -574,7 +559,7 @@ contract("CappedSTO", accounts => { // Buying on behalf of another user should fail await catchRevert( - I_CappedSTO_Array_ETH[1].buyTokens(account_investor3, { from: account_issuer, value: web3.utils.toWei("1", "ether") }) + I_CappedSTO_Array_ETH[1].buyTokens(account_investor3, { from: account_issuer, value: new BN(web3.utils.toWei("1", "ether")) }) ); }); @@ -585,37 +570,36 @@ contract("CappedSTO", accounts => { }); it("Should allow non-matching beneficiary -- failed because it is already active", async () => { - await catchRevert( - I_CappedSTO_Array_ETH[1].changeAllowBeneficialInvestments(true, { from: account_issuer }) - ); + await catchRevert(I_CappedSTO_Array_ETH[1].changeAllowBeneficialInvestments(true, { from: account_issuer })); }); it("Should invest in second STO", async () => { - await I_CappedSTO_Array_ETH[1].buyTokens(account_investor3, { from: account_issuer, value: web3.utils.toWei("1", "ether") }); + await I_CappedSTO_Array_ETH[1].buyTokens(account_investor3, { from: account_issuer, value: new BN(web3.utils.toWei("1", "ether")) }); - assert.equal((await I_CappedSTO_Array_ETH[1].getRaised.call(ETH)).dividedBy(new BigNumber(10).pow(18)).toNumber(), 1); + assert.equal((await I_CappedSTO_Array_ETH[1].getRaised.call(ETH)).div(new BN(10).pow(new BN(18))).toNumber(), 1); assert.equal(await I_CappedSTO_Array_ETH[1].investorCount.call(), 1); - assert.equal((await I_SecurityToken_ETH.balanceOf(account_investor3)).dividedBy(new BigNumber(10).pow(18)).toNumber(), 1000); + assert.equal((await I_SecurityToken_ETH.balanceOf(account_investor3)).div(new BN(10).pow(new BN(18))).toNumber(), 1000); }); }); describe("Test cases for reaching limit number of STO modules", async () => { it("Should successfully attach 10 STO modules", async () => { const MAX_MODULES = 10; - let startTime = latestTime() + duration.days(1); + let startTime = await latestTime() + duration.days(1); let endTime = startTime + duration.days(30); - - await I_PolyToken.getTokens(cappedSTOSetupCost * 19, token_owner); - await I_PolyToken.transfer(I_SecurityToken_ETH.address, cappedSTOSetupCost * 19, { from: token_owner }); + for (var i = 0; i < MAX_MODULES; i++) { + await I_PolyToken.getTokens(new BN(cappedSTOSetupCostPOLY), token_owner); + }; + await I_PolyToken.transfer(I_SecurityToken_ETH.address, new BN(cappedSTOSetupCostPOLY.mul(new BN(MAX_MODULES))), { from: token_owner }); let bytesSTO = encodeModuleCall(STOParameters, [startTime, endTime, cap, rate, [E_fundRaiseType], account_fundsReceiver]); for (var STOIndex = 2; STOIndex < MAX_MODULES; STOIndex++) { - const tx = await I_SecurityToken_ETH.addModule(I_CappedSTOFactory.address, bytesSTO, maxCost, 0, { from: token_owner }); + const tx = await I_SecurityToken_ETH.addModule(I_CappedSTOFactory.address, bytesSTO, maxCost, new BN(0), false, { from: token_owner }); assert.equal(tx.logs[3].args._types[0], stoKey, `Wrong module type added at index ${STOIndex}`); assert.equal(web3.utils.hexToString(tx.logs[3].args._name), "CappedSTO", `Wrong STO module added at index ${STOIndex}`); - I_CappedSTO_Array_ETH.push(CappedSTO.at(tx.logs[3].args._module)); + I_CappedSTO_Array_ETH.push(await CappedSTO.at(tx.logs[3].args._module)); } }); @@ -625,10 +609,10 @@ contract("CappedSTO", accounts => { for (var STOIndex = 2; STOIndex < MAX_MODULES; STOIndex++) { await I_CappedSTO_Array_ETH[STOIndex].buyTokens(account_investor3, { from: account_investor3, - value: web3.utils.toWei("1", "ether") + value: new BN(web3.utils.toWei("1", "ether")) }); assert.equal( - (await I_CappedSTO_Array_ETH[STOIndex].getRaised.call(ETH)).dividedBy(new BigNumber(10).pow(18)).toNumber(), + (await I_CappedSTO_Array_ETH[STOIndex].getRaised.call(ETH)).div(new BN(10).pow(new BN(18))).toNumber(), 1 ); assert.equal(await I_CappedSTO_Array_ETH[STOIndex].investorCount.call(), 1); @@ -640,39 +624,49 @@ contract("CappedSTO", accounts => { describe("Launch a new SecurityToken", async () => { it("POLY: Should register the ticker before the generation of the security token", async () => { await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); - let tx = await I_STRProxied.registerTicker(token_owner, P_symbol, P_name, { from: token_owner }); + let tx = await I_STRProxied.registerNewTicker(token_owner, P_symbol, { from: token_owner }); assert.equal(tx.logs[0].args._owner, token_owner); assert.equal(tx.logs[0].args._ticker, P_symbol); }); + it("Failed to generate the ST - Treasury wallet 0x0 is not allowed", async() => { + await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); + + await catchRevert( + I_STRProxied.generateNewSecurityToken(P_name, P_symbol, P_tokenDetails, false, "0x0000000000000000000000000000000000000000", 0, { from: token_owner }) + ); + }); + it("POLY: Should generate the new security token with the same symbol as registered above", async () => { await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); - let _blockNo = latestBlock(); - let tx = await I_STRProxied.generateSecurityToken(P_name, P_symbol, P_tokenDetails, false, { from: token_owner }); + + let tx = await I_STRProxied.generateNewSecurityToken(P_name, P_symbol, P_tokenDetails, false, treasury_wallet, 0, { from: token_owner }); // Verify the successful generation of the security token assert.equal(tx.logs[1].args._ticker, P_symbol, "SecurityToken doesn't get deployed"); - I_SecurityToken_POLY = SecurityToken.at(tx.logs[1].args._securityTokenAddress); + I_SecurityToken_POLY = await SecurityToken.at(tx.logs[1].args._securityTokenAddress); + stGetter_poly = await STGetter.at(I_SecurityToken_POLY.address); + assert.equal(await stGetter_poly.getTreasuryWallet.call(), treasury_wallet, "Incorrect wallet set") - const log = await promisifyLogWatch(I_SecurityToken_POLY.ModuleAdded({ from: _blockNo }), 1); + const log = (await I_SecurityToken_POLY.getPastEvents('ModuleAdded', {filter: {from: blockNo}}))[0]; // Verify that GeneralTransferManager module get added successfully or not assert.equal(log.args._types[0].toNumber(), transferManagerKey); assert.equal(web3.utils.hexToString(log.args._name), "GeneralTransferManager"); }); - it("POLY: Should intialize the auto attached modules", async () => { - let moduleData = (await I_SecurityToken_POLY.getModulesByType(transferManagerKey))[0]; - I_GeneralTransferManager = GeneralTransferManager.at(moduleData); + it("POLY: Should initialize the auto attached modules", async () => { + let moduleData = (await stGetter_poly.getModulesByType(transferManagerKey))[0]; + I_GeneralTransferManager = await GeneralTransferManager.at(moduleData); }); it("POLY: Should successfully attach the STO module to the security token", async () => { - startTime_POLY1 = latestTime() + duration.days(2); + startTime_POLY1 = await latestTime() + duration.days(2); endTime_POLY1 = startTime_POLY1 + duration.days(30); - await I_PolyToken.getTokens(cappedSTOSetupCost, token_owner); - await I_PolyToken.transfer(I_SecurityToken_POLY.address, cappedSTOSetupCost, { from: token_owner }); + await I_PolyToken.getTokens(cappedSTOSetupCostPOLY, token_owner); + await I_PolyToken.transfer(I_SecurityToken_POLY.address, cappedSTOSetupCostPOLY, { from: token_owner }); let bytesSTO = encodeModuleCall(STOParameters, [ startTime_POLY1, @@ -683,11 +677,11 @@ contract("CappedSTO", accounts => { account_fundsReceiver ]); - const tx = await I_SecurityToken_POLY.addModule(I_CappedSTOFactory.address, bytesSTO, maxCost, 0, { from: token_owner }); + const tx = await I_SecurityToken_POLY.addModule(I_CappedSTOFactory.address, bytesSTO, maxCost, new BN(0), false, { from: token_owner }); assert.equal(tx.logs[3].args._types[0], stoKey, "CappedSTO doesn't get deployed"); assert.equal(web3.utils.hexToString(tx.logs[3].args._name), "CappedSTO", "CappedSTOFactory module was not added"); - I_CappedSTO_Array_POLY.push(CappedSTO.at(tx.logs[3].args._module)); + I_CappedSTO_Array_POLY.push(await CappedSTO.at(tx.logs[3].args._module)); }); }); @@ -696,90 +690,86 @@ contract("CappedSTO", accounts => { assert.equal( (await I_CappedSTO_Array_POLY[0].startTime.call()).toNumber(), startTime_POLY1, - "STO Configuration doesn't set as expected" + "1STO Configuration doesn't set as expected" ); assert.equal( (await I_CappedSTO_Array_POLY[0].endTime.call()).toNumber(), endTime_POLY1, - "STO Configuration doesn't set as expected" + "2STO Configuration doesn't set as expected" ); assert.equal( - (await I_CappedSTO_Array_POLY[0].cap.call()).dividedBy(new BigNumber(10).pow(18)).toNumber(), - BigNumber(P_cap).dividedBy(new BigNumber(10).pow(18)), - "STO Configuration doesn't set as expected" + (await I_CappedSTO_Array_POLY[0].cap.call()).div(new BN(10).pow(new BN(18))).toString(), + new BN(P_cap).div(new BN(10).pow(new BN(18))), + "3STO Configuration doesn't set as expected" ); - assert.equal(await I_CappedSTO_Array_POLY[0].rate.call(), P_rate, "STO Configuration doesn't set as expected"); + assert.equal((await I_CappedSTO_Array_POLY[0].rate.call()).toString(), new BN(P_rate).toString(), "STO Configuration doesn't set as expected"); assert.equal( await I_CappedSTO_Array_POLY[0].fundRaiseTypes.call(P_fundRaiseType), true, - "STO Configuration doesn't set as expected" + "4STO Configuration doesn't set as expected" ); }); }); describe("Buy tokens", async () => { it("Should Buy the tokens", async () => { - await I_PolyToken.getTokens(10000 * Math.pow(10, 18), account_investor1); - blockNo = latestBlock(); + await I_PolyToken.getTokens(new BN(10).pow(new BN(22)), account_investor1); + blockNo = await latestBlock(); assert.equal( - (await I_PolyToken.balanceOf(account_investor1)).dividedBy(new BigNumber(10).pow(18)).toNumber(), + (await I_PolyToken.balanceOf(account_investor1)).div(new BN(10).pow(new BN(18))).toNumber(), 10500, "Tokens are not transfered properly" ); - - let tx = await I_GeneralTransferManager.modifyWhitelist(account_investor1, P_fromTime, P_toTime, P_expiryTime, true, { + let tx = await I_GeneralTransferManager.modifyKYCData(account_investor1, P_fromTime, P_toTime, P_expiryTime, { from: account_issuer, gas: 500000 }); - assert.equal(tx.logs[0].args._investor, account_investor1, "Failed in adding the investor in whitelist"); - // Jump time await increaseTime(duration.days(17)); - - await I_PolyToken.approve(I_CappedSTO_Array_POLY[0].address, 1000 * Math.pow(10, 18), { from: account_investor1 }); + await I_PolyToken.approve(I_CappedSTO_Array_POLY[0].address, new BN(10).pow(new BN(21)), { from: account_investor1 }); // buyTokensWithPoly transaction - await I_CappedSTO_Array_POLY[0].buyTokensWithPoly(1000 * Math.pow(10, 18), { + await I_CappedSTO_Array_POLY[0].buyTokensWithPoly(new BN(10).pow(new BN(21)), { from: account_investor1 }); - - assert.equal((await I_CappedSTO_Array_POLY[0].getRaised.call(POLY)).dividedBy(new BigNumber(10).pow(18)).toNumber(), 1000); + assert.equal((await I_CappedSTO_Array_POLY[0].getRaised.call(POLY)).div(new BN(10).pow(new BN(18))).toNumber(), 1000); assert.equal(await I_CappedSTO_Array_POLY[0].investorCount.call(), 1); assert.equal( - (await I_SecurityToken_POLY.balanceOf(account_investor1)).dividedBy(new BigNumber(10).pow(18)).toNumber(), + (await I_SecurityToken_POLY.balanceOf(account_investor1)).div(new BN(10).pow(new BN(18))).toNumber(), 5000 ); + }); it("Verification of the event Token Purchase", async () => { - const log = await promisifyLogWatch(I_CappedSTO_Array_POLY[0].TokenPurchase({ from: blockNo }), 1); + const log = (await I_CappedSTO_Array_POLY[0].getPastEvents('TokenPurchase', {filter: {from: blockNo}}))[0]; assert.equal(log.args.purchaser, account_investor1, "Wrong address of the investor"); - assert.equal(log.args.amount.dividedBy(new BigNumber(10).pow(18)).toNumber(), 5000, "Wrong No. token get dilivered"); + assert.equal(log.args.amount.div(new BN(10).pow(new BN(18))).toNumber(), 5000, "Wrong No. token get dilivered"); }); - it("Should failed to buy tokens -- because fundraisetype is POLY not ETH", async() => { + it("Should failed to buy tokens -- because fundraisetype is POLY not ETH", async () => { await catchRevert( // Fallback transaction web3.eth.sendTransaction({ from: account_investor1, to: I_CappedSTO_Array_POLY[0].address, gas: 2100000, - value: web3.utils.toWei("2", "ether") + value: new BN(web3.utils.toWei("2", "ether")) }) ); }); - it("Should fail in buying tokens because buying is paused", async() => { + it("Should fail in buying tokens because buying is paused", async () => { await I_CappedSTO_Array_POLY[0].pause({ from: account_issuer }); - await I_PolyToken.approve(I_CappedSTO_Array_POLY[0].address, 1000 * Math.pow(10, 18), { from: account_investor1 }); + await I_PolyToken.approve(I_CappedSTO_Array_POLY[0].address, new BN(10).pow(new BN(21)), { from: account_investor1 }); // buyTokensWithPoly transaction await catchRevert( - I_CappedSTO_Array_POLY[0].buyTokensWithPoly(1000 * Math.pow(10, 18), { + I_CappedSTO_Array_POLY[0].buyTokensWithPoly(new BN(10).pow(new BN(21)), { from: account_investor1, gas: 6000000 }) @@ -788,141 +778,156 @@ contract("CappedSTO", accounts => { }); it("Should buy the granular unit tokens and charge only required POLY", async () => { - await I_SecurityToken_POLY.changeGranularity(10 ** 22, {from: token_owner}); - let tx = await I_GeneralTransferManager.modifyWhitelist( + await I_SecurityToken_POLY.changeGranularity(new BN(10).pow(new BN(22)), { from: token_owner }); + let tx = await I_GeneralTransferManager.modifyKYCData( account_investor2, P_fromTime, P_toTime + duration.days(20), P_expiryTime, - true, { from: account_issuer, gas: 500000 } ); - console.log((await I_SecurityToken_POLY.balanceOf(account_investor2)).dividedBy(new BigNumber(10).pow(18)).toNumber()); + console.log((await I_SecurityToken_POLY.balanceOf(account_investor2)).div(new BN(10).pow(new BN(18))).toNumber()); assert.equal(tx.logs[0].args._investor, account_investor2, "Failed in adding the investor in whitelist"); - await I_PolyToken.getTokens(10000 * Math.pow(10, 18), account_investor2); - await I_PolyToken.approve(I_CappedSTO_Array_POLY[0].address, 9000 * Math.pow(10, 18), { from: account_investor2 }); - const initRaised = (await I_CappedSTO_Array_POLY[0].getRaised.call(POLY)).dividedBy(new BigNumber(10).pow(18)).toNumber(); - tx = await I_CappedSTO_Array_POLY[0].buyTokensWithPoly(3000 * Math.pow(10, 18), { from: account_investor2 }); - await I_SecurityToken_POLY.changeGranularity(1, {from: token_owner}); - assert.equal((await I_CappedSTO_Array_POLY[0].getRaised.call(POLY)).dividedBy(new BigNumber(10).pow(18)).toNumber(), initRaised + 2000); //2000 this call, 1000 earlier - assert.equal((await I_PolyToken.balanceOf(account_investor2)).dividedBy(new BigNumber(10).pow(18)).toNumber(), 8000); + await I_PolyToken.getTokens(new BN(10).pow(new BN(22)), account_investor2); + await I_PolyToken.approve(I_CappedSTO_Array_POLY[0].address, new BN(9000).mul(new BN(10).pow(new BN(18))), { from: account_investor2 }); + const initRaised = (await I_CappedSTO_Array_POLY[0].getRaised.call(POLY)).div(new BN(10).pow(new BN(18))).toNumber(); + tx = await I_CappedSTO_Array_POLY[0].buyTokensWithPoly(new BN(3000).mul(new BN(10).pow(new BN(18))), { from: account_investor2 }); + await I_SecurityToken_POLY.changeGranularity(1, { from: token_owner }); + assert.equal( + (await I_CappedSTO_Array_POLY[0].getRaised.call(POLY)).div(new BN(10).pow(new BN(18))).toNumber(), + initRaised + 2000 + ); //2000 this call, 1000 earlier + assert.equal((await I_PolyToken.balanceOf(account_investor2)).div(new BN(10).pow(new BN(18))).toNumber(), 8000); assert.equal( - (await I_SecurityToken_POLY.balanceOf(account_investor2)).dividedBy(new BigNumber(10).pow(18)).toNumber(), + (await I_SecurityToken_POLY.balanceOf(account_investor2)).div(new BN(10).pow(new BN(18))).toNumber(), 10000 ); }); - it("Should restrict to buy tokens after hiting the cap in second tx first tx pass", async () => { // buyTokensWithPoly transaction - await I_CappedSTO_Array_POLY[0].buyTokensWithPoly(7000 * Math.pow(10, 18), { from: account_investor2 }); + await I_CappedSTO_Array_POLY[0].buyTokensWithPoly(new BN(7000).mul(new BN(10).pow(new BN(18))), { from: account_investor2 }); - assert.equal((await I_CappedSTO_Array_POLY[0].getRaised.call(POLY)).dividedBy(new BigNumber(10).pow(18)).toNumber(), 10000); + assert.equal( + (await I_CappedSTO_Array_POLY[0].getRaised.call(POLY)).div(new BN(10).pow(new BN(18))).toNumber(), + 10000 + ); assert.equal(await I_CappedSTO_Array_POLY[0].investorCount.call(), 2); assert.equal( - (await I_SecurityToken_POLY.balanceOf(account_investor2)).dividedBy(new BigNumber(10).pow(18)).toNumber(), + (await I_SecurityToken_POLY.balanceOf(account_investor2)).div(new BN(10).pow(new BN(18))).toNumber(), 45000 ); - await I_PolyToken.approve(I_CappedSTO_Array_POLY[0].address, 1000 * Math.pow(10, 18), { from: account_investor1 }); - await catchRevert( - I_CappedSTO_Array_POLY[0].buyTokensWithPoly(1000 * Math.pow(10, 18), { from: account_investor1 }) - ); + await I_PolyToken.approve(I_CappedSTO_Array_POLY[0].address, new BN(1000).mul(new BN(10).pow(new BN(18))), { from: account_investor1 }); + await catchRevert(I_CappedSTO_Array_POLY[0].buyTokensWithPoly(new BN(1000).mul(new BN(10).pow(new BN(18))), { from: account_investor1 })); }); it("Should failed at the time of buying the tokens -- Because STO get expired", async () => { await increaseTime(duration.days(31)); // increased beyond the end time of the STO - await I_PolyToken.approve(I_CappedSTO_Array_POLY[0].address, 1000 * Math.pow(10, 18), { from: account_investor1 }); + await I_PolyToken.approve(I_CappedSTO_Array_POLY[0].address, new BN(1000).mul(new BN(10).pow(new BN(18))), { from: account_investor1 }); await catchRevert( - I_CappedSTO_Array_POLY[0].buyTokensWithPoly(1000 * Math.pow(10, 18), { from: account_investor1, gas: 6000000 }) + I_CappedSTO_Array_POLY[0].buyTokensWithPoly(new BN(1000).mul(new BN(10).pow(new BN(18))), { from: account_investor1, gas: 6000000 }) ); }); it("Should fundRaised value equal to the raised value in the funds receiver wallet", async () => { const balanceRaised = await I_PolyToken.balanceOf.call(account_fundsReceiver); assert.equal( - (await I_CappedSTO_Array_POLY[0].getRaised.call(POLY)).toNumber(), - balanceRaised, + (await I_CappedSTO_Array_POLY[0].getRaised.call(POLY)).toString(), + balanceRaised.toString(), "Somewhere raised money get stolen or sent to wrong wallet" ); }); }); + describe("Pricing Test cases for Module Factory", async () => { + it("Should return correct price when price is in poly", async () => { + let newFactory = await CappedSTOFactory.new( + new BN(1000), + I_CappedSTO_Array_POLY[0].address, + I_PolymathRegistry.address, + true, + { from: account_polymath } + ); + assert.equal((await newFactory.setupCostInPoly.call()).toString(), (new BN(1000)).toString()); + assert.equal((await newFactory.setupCost()).toString(), (new BN(1000)).toString()); + }); + }); + describe("Check that we can reclaim ETH and ERC20 tokens from an STO", async () => { //xxx it("should attach a dummy STO", async () => { let I_DummySTOFactory; - [I_DummySTOFactory] = await deployDummySTOAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, 0); + [I_DummySTOFactory] = await deployDummySTOAndVerifyed(account_polymath, I_MRProxied, new BN(0)); const DummySTOParameters = ["uint256", "uint256", "uint256", "string"]; - let startTime = latestTime() + duration.days(1); + let startTime = await latestTime() + duration.days(1); let endTime = startTime + duration.days(30); const cap = web3.utils.toWei("10000"); const dummyBytesSig = encodeModuleCall(DummySTOParameters, [startTime, endTime, cap, "Hello"]); - const tx = await I_SecurityToken_ETH.addModule(I_DummySTOFactory.address, dummyBytesSig, maxCost, 0, { from: token_owner }); + const tx = await I_SecurityToken_ETH.addModule(I_DummySTOFactory.address, dummyBytesSig, maxCost, new BN(0), false, { from: token_owner }); + console.log(tx.logs[2]); assert.equal(tx.logs[2].args._types[0], stoKey, `Wrong module type added`); assert.equal( web3.utils.hexToString(tx.logs[2].args._name), "DummySTO", `Wrong STO module added` ); - I_DummySTO = DummySTO.at(tx.logs[2].args._module); + I_DummySTO = await DummySTO.at(tx.logs[2].args._module); + console.log(I_DummySTO.address); }); it("should send some funds and ERC20 to the DummySTO", async () => { let tx = await web3.eth.sendTransaction({ from: account_investor1, - to: I_DummySTO.address, + to: web3.utils.toChecksumAddress(I_DummySTO.address), gas: 2100000, - value: web3.utils.toWei("1", "ether") + value: web3.utils.toWei("1") }); - let dummyETH = BigNumber(await web3.eth.getBalance(I_DummySTO.address)); - assert.equal(dummyETH.toNumber(), web3.utils.toWei("1", "ether")); - await I_PolyToken.getTokens(web3.utils.toWei("2", "ether"), I_DummySTO.address); - let dummyPOLY = BigNumber(await I_PolyToken.balanceOf(I_DummySTO.address)); - assert.equal(dummyPOLY.toNumber(), web3.utils.toWei("2", "ether")); + let dummyETH = await web3.eth.getBalance(I_DummySTO.address); + assert.equal(dummyETH.toString(), web3.utils.toWei("1")); + await I_PolyToken.getTokens(web3.utils.toWei("2"), I_DummySTO.address); + let dummyPOLY = await I_PolyToken.balanceOf(I_DummySTO.address); + assert.equal(dummyPOLY.toString(), web3.utils.toWei("2")); }); it("should reclaim ETH and ERC20 from STO", async () => { - let initialIssuerETH = BigNumber(await web3.eth.getBalance(token_owner)); - let initialIssuerPOLY = BigNumber(await I_PolyToken.balanceOf(token_owner)); + let initialIssuerETH = await web3.eth.getBalance(token_owner); + let initialIssuerPOLY = await I_PolyToken.balanceOf(token_owner); await catchRevert(I_DummySTO.reclaimERC20(I_PolyToken.address, {from: account_polymath, gasPrice: 0})); await catchRevert(I_DummySTO.reclaimETH( {from: account_polymath, gasPrice: 0})); let tx = await I_DummySTO.reclaimERC20(I_PolyToken.address, {from: token_owner, gasPrice: 0}); let tx2 = await I_DummySTO.reclaimETH({from: token_owner, gasPrice: 0}); - let finalIssuerETH = BigNumber(await web3.eth.getBalance(token_owner)); - let finalIssuerPOLY = BigNumber(await I_PolyToken.balanceOf(token_owner)); - assert.equal(finalIssuerETH.sub(initialIssuerETH).toNumber(), web3.utils.toWei("1", "ether")); - assert.equal(finalIssuerPOLY.sub(initialIssuerPOLY).toNumber(), web3.utils.toWei("2", "ether")); - let dummyETH = BigNumber(await web3.eth.getBalance(I_DummySTO.address)); - assert.equal(dummyETH.toNumber(), 0); - let dummyPOLY = BigNumber(await I_PolyToken.balanceOf(I_DummySTO.address)); - assert.equal(dummyPOLY.toNumber(), 0); + let finalIssuerETH = await web3.eth.getBalance(token_owner); + let finalIssuerPOLY = await I_PolyToken.balanceOf(token_owner); + let ethDifference = parseInt(web3.utils.fromWei(finalIssuerETH.toString())) - parseInt(web3.utils.fromWei(initialIssuerETH.toString())); + let polyDifference = parseInt(web3.utils.fromWei(finalIssuerPOLY.toString())) - parseInt(web3.utils.fromWei(initialIssuerPOLY.toString())); + assert.equal(ethDifference, 1); + assert.equal(polyDifference, 2); + let dummyETH = await web3.eth.getBalance(I_DummySTO.address); + assert.equal(dummyETH.toString(), 0); + let dummyPOLY = await I_PolyToken.balanceOf(I_DummySTO.address); + assert.equal(dummyPOLY.toString(), 0); }); }); - describe("Test cases for the CappedSTOFactory", async () => { it("should get the exact details of the factory", async () => { - assert.equal((await I_CappedSTOFactory.getSetupCost.call()).toNumber(), cappedSTOSetupCost); + assert.equal((await I_CappedSTOFactory.setupCost.call()).toString(), cappedSTOSetupCost.toString()); + assert.equal((await I_CappedSTOFactory.setupCostInPoly.call()).toString(), cappedSTOSetupCostPOLY.toString()); assert.equal((await I_CappedSTOFactory.getTypes.call())[0], 3); - assert.equal(web3.utils.hexToString(await I_CappedSTOFactory.getName.call()), "CappedSTO", "Wrong Module added"); + assert.equal(web3.utils.hexToString(await I_CappedSTOFactory.name.call()), "CappedSTO", "Wrong Module added"); assert.equal( await I_CappedSTOFactory.description.call(), "This smart contract creates a maximum number of tokens (i.e. hard cap) which the total aggregate of tokens acquired by all investors cannot exceed. Security tokens are sent to the investor upon reception of the funds (ETH or POLY), and any security tokens left upon termination of the offering will not be minted.", "Wrong Module added" ); assert.equal(await I_CappedSTOFactory.title.call(), "Capped STO", "Wrong Module added"); - assert.equal( - await I_CappedSTOFactory.getInstructions.call(), - "Initialises a capped STO. Init parameters are _startTime (time STO starts), _endTime (time STO ends), _cap (cap in tokens for STO), _rate (POLY/ETH to token rate), _fundRaiseType (whether you are raising in POLY or ETH), _polyToken (address of POLY token), _fundsReceiver (address which will receive funds)", - "Wrong Module added" - ); let tags = await I_CappedSTOFactory.getTags.call(); assert.equal(web3.utils.hexToString(tags[0]), "Capped"); - assert.equal(await I_CappedSTOFactory.version.call(), "2.1.0"); + assert.equal(await I_CappedSTOFactory.version.call(), "3.0.0"); }); it("Should fail to change the title -- bad owner", async () => { @@ -930,11 +935,11 @@ contract("CappedSTO", accounts => { }); it("Should fail to change the title -- zero length", async () => { - await catchRevert(I_CappedSTOFactory.changeTitle("", { from: token_owner })); + await catchRevert(I_CappedSTOFactory.changeTitle("", { from: account_polymath })); }); it("Should successfully change the title", async () => { - await I_CappedSTOFactory.changeTitle("STO Capped", { from: token_owner }); + await I_CappedSTOFactory.changeTitle("STO Capped", { from: account_polymath }); assert.equal(await I_CappedSTOFactory.title.call(), "STO Capped", "Title doesn't get changed"); }); @@ -943,11 +948,11 @@ contract("CappedSTO", accounts => { }); it("Should fail to change the description -- zero length", async () => { - await catchRevert(I_CappedSTOFactory.changeDescription("", { from: token_owner })); + await catchRevert(I_CappedSTOFactory.changeDescription("", { from: account_polymath })); }); it("Should successfully change the description", async () => { - await I_CappedSTOFactory.changeDescription("It is only a STO", { from: token_owner }); + await I_CappedSTOFactory.changeDescription("It is only a STO", { from: account_polymath }); assert.equal(await I_CappedSTOFactory.description.call(), "It is only a STO", "Description doesn't get changed"); }); @@ -956,17 +961,17 @@ contract("CappedSTO", accounts => { }); it("Should fail to change the name -- zero length", async () => { - await catchRevert(I_CappedSTOFactory.changeName(web3.utils.stringToHex(""), { from: token_owner })); + await catchRevert(I_CappedSTOFactory.changeName(web3.utils.stringToHex(""), { from: account_polymath })); }); it("Should successfully change the name", async () => { - await I_CappedSTOFactory.changeName(web3.utils.stringToHex("STOCapped"), { from: token_owner }); - assert.equal(web3.utils.hexToString(await I_CappedSTOFactory.getName.call()), "STOCapped", "Name doesn't get changed"); + await I_CappedSTOFactory.changeName(web3.utils.stringToHex("STOCapped"), { from: account_polymath }); + assert.equal(web3.utils.hexToString(await I_CappedSTOFactory.name.call()), "STOCapped", "Name doesn't get changed"); }); it("Should successfully change the name", async () => { - await I_CappedSTOFactory.changeName(web3.utils.stringToHex("CappedSTO"), { from: token_owner }); - assert.equal(web3.utils.hexToString(await I_CappedSTOFactory.getName.call()), "CappedSTO", "Name doesn't get changed"); + await I_CappedSTOFactory.changeName(web3.utils.stringToHex("CappedSTO"), { from: account_polymath }); + assert.equal(web3.utils.hexToString(await I_CappedSTOFactory.name.call()), "CappedSTO", "Name doesn't get changed"); }); }); @@ -976,11 +981,11 @@ contract("CappedSTO", accounts => { }); it("Should get the raised amount of ether", async () => { - assert.equal(await I_CappedSTO_Array_POLY[0].getRaised.call(ETH), web3.utils.toWei("0", "ether")); + assert.equal((await I_CappedSTO_Array_POLY[0].getRaised.call(ETH)).toString(), new BN(web3.utils.toWei("0", "ether")).toString()); }); it("Should get the raised amount of poly", async () => { - assert.equal((await I_CappedSTO_Array_POLY[0].getRaised.call(POLY)).toNumber(), web3.utils.toWei("10000", "ether")); + assert.equal((await I_CappedSTO_Array_POLY[0].getRaised.call(POLY)).toString(), new BN(web3.utils.toWei("10000", "ether")).toString()); }); it("Should get the investors", async () => { @@ -989,7 +994,7 @@ contract("CappedSTO", accounts => { it("Should get the listed permissions", async () => { let tx = await I_CappedSTO_Array_POLY[0].getPermissions.call(); - assert.equal(tx.length, 0); + assert.equal(tx.length, 1); }); it("Should get the metrics of the STO", async () => { @@ -1001,11 +1006,11 @@ contract("CappedSTO", accounts => { describe("Attach second POLY STO module", async () => { it("Should successfully attach a second STO to the security token", async () => { - startTime_POLY2 = latestTime() + duration.days(1); + startTime_POLY2 = await latestTime() + duration.days(1); endTime_POLY2 = startTime_POLY2 + duration.days(30); - await I_PolyToken.getTokens(cappedSTOSetupCost, token_owner); - await I_PolyToken.transfer(I_SecurityToken_POLY.address, cappedSTOSetupCost, { from: token_owner }); + await I_PolyToken.getTokens(cappedSTOSetupCostPOLY, token_owner); + await I_PolyToken.transfer(I_SecurityToken_POLY.address, cappedSTOSetupCostPOLY, { from: token_owner }); let bytesSTO = encodeModuleCall(STOParameters, [ startTime_POLY2, @@ -1016,44 +1021,44 @@ contract("CappedSTO", accounts => { account_fundsReceiver ]); - const tx = await I_SecurityToken_POLY.addModule(I_CappedSTOFactory.address, bytesSTO, maxCost, 0, { from: token_owner }); + const tx = await I_SecurityToken_POLY.addModule(I_CappedSTOFactory.address, bytesSTO, maxCost, new BN(0), false, { from: token_owner }); assert.equal(tx.logs[3].args._types[0], stoKey, "CappedSTO doesn't get deployed"); assert.equal(web3.utils.hexToString(tx.logs[3].args._name), "CappedSTO", "CappedSTOFactory module was not added"); - I_CappedSTO_Array_POLY.push(CappedSTO.at(tx.logs[3].args._module)); + I_CappedSTO_Array_POLY.push(await CappedSTO.at(tx.logs[3].args._module)); }); it("Should verify the configuration of the STO", async () => { assert.equal( - (await I_CappedSTO_Array_POLY[1].startTime.call()).toNumber(), - startTime_POLY2, - "STO Configuration doesn't set as expected" + (await I_CappedSTO_Array_POLY[1].startTime.call()).toString(), + startTime_POLY2.toString(), + "1STO Configuration doesn't set as expected" ); assert.equal( - (await I_CappedSTO_Array_POLY[1].endTime.call()).toNumber(), - endTime_POLY2, - "STO Configuration doesn't set as expected" + (await I_CappedSTO_Array_POLY[1].endTime.call()).toString(), + endTime_POLY2.toString(), + "2STO Configuration doesn't set as expected" ); assert.equal( - (await I_CappedSTO_Array_POLY[1].cap.call()).dividedBy(new BigNumber(10).pow(18)).toNumber(), - BigNumber(P_cap).dividedBy(new BigNumber(10).pow(18)), - "STO Configuration doesn't set as expected" + (await I_CappedSTO_Array_POLY[1].cap.call()).div(new BN(10).pow(new BN(18))).toString(), + new BN(P_cap).div(new BN(10).pow(new BN(18))).toString(), + "3STO Configuration doesn't set as expected" ); - assert.equal(await I_CappedSTO_Array_POLY[1].rate.call(), P_rate, "STO Configuration doesn't set as expected"); + assert.equal((await I_CappedSTO_Array_POLY[1].rate.call()).toString(), new BN(P_rate).toString(), "STO Configuration doesn't set as expected"); assert.equal( await I_CappedSTO_Array_POLY[1].fundRaiseTypes.call(P_fundRaiseType), true, - "STO Configuration doesn't set as expected" + "4STO Configuration doesn't set as expected" ); }); it("Should successfully invest in second STO", async () => { - const polyToInvest = 1000; - const stToReceive = (polyToInvest * P_rate)/Math.pow(10, 18); + const polyToInvest = new BN(1000); + const stToReceive = new BN(polyToInvest.mul(new BN(P_rate).div(new BN(10).pow(new BN(18))))); - await I_PolyToken.getTokens(polyToInvest * Math.pow(10, 18), account_investor3); + await I_PolyToken.getTokens(polyToInvest.mul(new BN(10).pow(new BN(18))), account_investor3); - let tx = await I_GeneralTransferManager.modifyWhitelist(account_investor3, P_fromTime, P_toTime, P_expiryTime, true, { + let tx = await I_GeneralTransferManager.modifyKYCData(account_investor3, P_fromTime, P_toTime, P_expiryTime, { from: account_issuer, gas: 500000 }); @@ -1061,25 +1066,25 @@ contract("CappedSTO", accounts => { // Jump time to beyond STO start await increaseTime(duration.days(2)); - await I_PolyToken.approve(I_CappedSTO_Array_POLY[1].address, polyToInvest * Math.pow(10, 18), { from: account_investor3 }); + await I_PolyToken.approve(I_CappedSTO_Array_POLY[1].address, polyToInvest.mul(new BN(10).pow(new BN(18))), { from: account_investor3 }); // buyTokensWithPoly transaction - await I_CappedSTO_Array_POLY[1].buyTokensWithPoly(polyToInvest * Math.pow(10, 18), { + await I_CappedSTO_Array_POLY[1].buyTokensWithPoly(polyToInvest.mul(new BN(10).pow(new BN(18))), { from: account_investor3, gas: 6000000 }); assert.equal( - (await I_CappedSTO_Array_POLY[1].getRaised.call(POLY)).dividedBy(new BigNumber(10).pow(18)).toNumber(), - polyToInvest + (await I_CappedSTO_Array_POLY[1].getRaised.call(POLY)).div(new BN(10).pow(new BN(18))).toString(), + polyToInvest.toString() ); assert.equal(await I_CappedSTO_Array_POLY[1].investorCount.call(), 1); assert.equal( - (await I_SecurityToken_POLY.balanceOf(account_investor3)).dividedBy(new BigNumber(10).pow(18)).toNumber(), - stToReceive + (await I_SecurityToken_POLY.balanceOf(account_investor3)).div(new BN(10).pow(new BN(18))).toString(), + stToReceive.toString() ); - }); + }); }); }); diff --git a/test/c_checkpoints.js b/test/c_checkpoints.js index 830acf600..6160e4316 100644 --- a/test/c_checkpoints.js +++ b/test/c_checkpoints.js @@ -1,16 +1,18 @@ import latestTime from "./helpers/latestTime"; import { duration, ensureException, promisifyLogWatch, latestBlock } from "./helpers/utils"; -import takeSnapshot, { increaseTime, revertToSnapshot } from "./helpers/time"; +import { takeSnapshot, increaseTime, revertToSnapshot } from "./helpers/time"; import { setUpPolymathNetwork } from "./helpers/createInstances"; +import { catchRevert } from "./helpers/exceptions"; const SecurityToken = artifacts.require("./SecurityToken.sol"); const GeneralTransferManager = artifacts.require("./GeneralTransferManager"); +const STGetter = artifacts.require("./STGetter.sol"); const Web3 = require("web3"); -const BigNumber = require("bignumber.js"); +let BN = Web3.utils.BN; const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); // Hardcoded development port -contract("Checkpoints", accounts => { +contract("Checkpoints", async function(accounts) { // Accounts Variable declaration let account_polymath; let account_issuer; @@ -19,14 +21,15 @@ contract("Checkpoints", accounts => { let account_investor2; let account_investor3; let account_investor4; - - // investor Details - let fromTime = latestTime(); - let toTime = latestTime(); - let expiryTime = toTime + duration.days(15); + let account_controller; let message = "Transaction Should Fail!"; + // investor Details + let fromTime; + let toTime; + let expiryTime; + // Contract Instance Declaration let I_GeneralPermissionManagerFactory; let I_SecurityTokenRegistryProxy; @@ -44,6 +47,9 @@ contract("Checkpoints", accounts => { let I_SecurityToken; let I_PolyToken; let I_PolymathRegistry; + let I_STRGetter; + let I_STGetter; + let stGetter; // SecurityToken Details const name = "Team"; @@ -58,15 +64,18 @@ contract("Checkpoints", accounts => { const stoKey = 3; // Initial fee for ticker registry and security token registry - const initRegFee = web3.utils.toWei("250"); + const initRegFee = new BN(web3.utils.toWei("1000")); before(async () => { + fromTime = await latestTime(); + toTime = await latestTime(); + expiryTime = toTime + duration.days(15); // Accounts setup account_polymath = accounts[0]; account_issuer = accounts[1]; token_owner = account_issuer; - + account_controller = accounts[3]; account_investor1 = accounts[6]; account_investor2 = accounts[7]; account_investor3 = accounts[8]; @@ -86,7 +95,9 @@ contract("Checkpoints", accounts => { I_STFactory, I_SecurityTokenRegistry, I_SecurityTokenRegistryProxy, - I_STRProxied + I_STRProxied, + I_STRGetter, + I_STGetter ] = instances; // Printing all the contract addresses @@ -108,54 +119,51 @@ contract("Checkpoints", accounts => { describe("Generate the SecurityToken", async () => { it("Should register the ticker before the generation of the security token", async () => { await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); - let tx = await I_STRProxied.registerTicker(token_owner, symbol, contact, { from: token_owner }); + let tx = await I_STRProxied.registerNewTicker(token_owner, symbol, { from: token_owner }); assert.equal(tx.logs[0].args._owner, token_owner); assert.equal(tx.logs[0].args._ticker, symbol.toUpperCase()); }); it("Should generate the new security token with the same symbol as registered above", async () => { await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); - let _blockNo = latestBlock(); - let tx = await I_STRProxied.generateSecurityToken(name, symbol, tokenDetails, false, { from: token_owner }); + let tx = await I_STRProxied.generateNewSecurityToken(name, symbol, tokenDetails, false, token_owner, 0, { from: token_owner }); // 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 log = await promisifyLogWatch(I_SecurityToken.ModuleAdded({ from: _blockNo }), 1); + I_SecurityToken = await SecurityToken.at(tx.logs[1].args._securityTokenAddress); + stGetter = await STGetter.at(I_SecurityToken.address); + const log = (await I_SecurityToken.getPastEvents('ModuleAdded', {filter: {transactionHash: tx.transactionHash}}))[0]; // Verify that GeneralTransferManager module get added successfully or not assert.equal(log.args._types[0].toNumber(), 2); assert.equal(web3.utils.toAscii(log.args._name).replace(/\u0000/g, ""), "GeneralTransferManager"); }); - it("Should set controller to token owner", async () => { - await I_SecurityToken.setController(token_owner, { from: token_owner }); - }); + it("Should set the controller", async() => { + await I_SecurityToken.setController(account_controller, {from: token_owner}); + }) - it("Should intialize the auto attached modules", async () => { - let moduleData = (await I_SecurityToken.getModulesByType(2))[0]; - I_GeneralTransferManager = GeneralTransferManager.at(moduleData); + it("Should initialize the auto attached modules", async () => { + let moduleData = (await stGetter.getModulesByType(2))[0]; + I_GeneralTransferManager = await GeneralTransferManager.at(moduleData); }); }); describe("Buy tokens using on-chain whitelist", async () => { it("Should Buy the tokens", async () => { // Add the Investor in to the whitelist - - let tx = await I_GeneralTransferManager.modifyWhitelist( + let ltime = new BN(await latestTime()); + let tx = await I_GeneralTransferManager.modifyKYCData( account_investor1, - latestTime(), - latestTime(), - latestTime() + duration.days(10), - false, + ltime, + ltime, + ltime.add(new BN(duration.days(10))), { from: account_issuer, gas: 6000000 } ); - assert.equal( tx.logs[0].args._investor.toLowerCase(), account_investor1.toLowerCase(), @@ -163,20 +171,19 @@ contract("Checkpoints", accounts => { ); // Mint some tokens - await I_SecurityToken.mint(account_investor1, web3.utils.toWei("10", "ether"), { from: token_owner }); + await I_SecurityToken.issue(account_investor1, new BN(web3.utils.toWei("10", "ether")), "0x0", { from: token_owner }); - assert.equal((await I_SecurityToken.balanceOf(account_investor1)).toNumber(), web3.utils.toWei("10", "ether")); + assert.equal((await I_SecurityToken.balanceOf(account_investor1)).toString(), new BN(web3.utils.toWei("10", "ether")).toString()); }); it("Should Buy some more tokens", async () => { // Add the Investor in to the whitelist - - let tx = await I_GeneralTransferManager.modifyWhitelist( + let ltime = new BN(await latestTime()); + let tx = await I_GeneralTransferManager.modifyKYCData( account_investor2, - latestTime(), - latestTime(), - latestTime() + duration.days(10), - false, + ltime, + ltime, + ltime.add(new BN(duration.days(10))), { from: account_issuer, gas: 6000000 @@ -190,18 +197,18 @@ contract("Checkpoints", accounts => { ); // Mint some tokens - await I_SecurityToken.mint(account_investor2, web3.utils.toWei("10", "ether"), { from: token_owner }); + await I_SecurityToken.issue(account_investor2, new BN(web3.utils.toWei("10", "ether")), "0x0", { from: token_owner }); - assert.equal((await I_SecurityToken.balanceOf(account_investor2)).toNumber(), web3.utils.toWei("10", "ether")); + assert.equal((await I_SecurityToken.balanceOf(account_investor2)).toString(), new BN(web3.utils.toWei("10", "ether")).toString()); }); it("Add a new token holder", async () => { - let tx = await I_GeneralTransferManager.modifyWhitelist( + let ltime = new BN(await latestTime()); + let tx = await I_GeneralTransferManager.modifyKYCData( account_investor3, - latestTime(), - latestTime(), - latestTime() + duration.days(10), - false, + ltime, + ltime, + ltime.add(new BN(duration.days(10))), { from: account_issuer, gas: 6000000 @@ -216,9 +223,9 @@ contract("Checkpoints", accounts => { // Add the Investor in to the whitelist // Mint some tokens - await I_SecurityToken.mint(account_investor3, web3.utils.toWei("10", "ether"), { from: token_owner }); + await I_SecurityToken.issue(account_investor3, new BN(web3.utils.toWei("10", "ether")), "0x0", { from: token_owner }); - assert.equal((await I_SecurityToken.balanceOf(account_investor3)).toNumber(), web3.utils.toWei("10", "ether")); + assert.equal((await I_SecurityToken.balanceOf(account_investor3)).toString(), new BN(web3.utils.toWei("10", "ether")).toString()); }); it("Fuzz test balance checkpoints", async () => { @@ -226,10 +233,10 @@ contract("Checkpoints", accounts => { let cps = []; let ts = []; for (let j = 0; j < 10; j++) { - let balance1 = new BigNumber(await I_SecurityToken.balanceOf(account_investor1)); - let balance2 = new BigNumber(await I_SecurityToken.balanceOf(account_investor2)); - let balance3 = new BigNumber(await I_SecurityToken.balanceOf(account_investor3)); - let totalSupply = new BigNumber(await I_SecurityToken.totalSupply()); + let balance1 = new BN(await I_SecurityToken.balanceOf(account_investor1)); + let balance2 = new BN(await I_SecurityToken.balanceOf(account_investor2)); + let balance3 = new BN(await I_SecurityToken.balanceOf(account_investor3)); + let totalSupply = new BN(await I_SecurityToken.totalSupply()); cps.push([balance1, balance2, balance3]); ts.push(totalSupply); console.log( @@ -240,8 +247,10 @@ contract("Checkpoints", accounts => { " TotalSupply: " + JSON.stringify(totalSupply) ); - await I_SecurityToken.createCheckpoint({ from: token_owner }); - let checkpointTimes = await I_SecurityToken.getCheckpointTimes(); + let investorLength = await stGetter.getInvestorCount(); + let tx = await I_SecurityToken.createCheckpoint({ from: token_owner }); + assert.equal((tx.logs[0].args[1]).toString(), investorLength.toString()); + let checkpointTimes = await stGetter.getCheckpointTimes(); assert.equal(checkpointTimes.length, j + 1); console.log("Checkpoint Times: " + checkpointTimes); let txs = Math.floor(Math.random() * 3); @@ -264,21 +273,19 @@ contract("Checkpoints", accounts => { } else { receiver = account_investor3; } - let m = Math.random(); - let amount = new BigNumber(await I_SecurityToken.balanceOf(sender)) - .mul(Math.random().toFixed(10)) - .toFixed(0); - if (m > 0.8) { + let m = Math.floor(Math.random() * 10) + 1; + let amount; + if (m > 8) { console.log("Sending full balance"); - amount = new BigNumber(await I_SecurityToken.balanceOf(sender)); + amount = new BN(await I_SecurityToken.balanceOf(sender)); + } else { + amount = new BN(await I_SecurityToken.balanceOf(sender)).mul(new BN(m)).div(new BN(10)); } console.log("Sender: " + sender + " Receiver: " + receiver + " Amount: " + JSON.stringify(amount)); await I_SecurityToken.transfer(receiver, amount, { from: sender }); } if (Math.random() > 0.5) { - let n = new BigNumber(Math.random().toFixed(10)) - .mul(10 ** 17) - .toFixed(0); + let n = new BN(Math.random().toFixed(10)).mul(new BN(10).pow(new BN(17))); let p = Math.random() * 3; let r = Math.random() * 3; let minter; @@ -290,10 +297,10 @@ contract("Checkpoints", accounts => { minter = account_investor3; } console.log("Minting: " + n.toString() + " to: " + minter); - await I_SecurityToken.mint(minter, n, { from: token_owner }); + await I_SecurityToken.issue(minter, n, "0x0", { from: token_owner }); } if (Math.random() > 0.5) { - let n = new BigNumber(Math.random().toFixed(10)).mul(10 ** 17); + let n = new BN(Math.random().toFixed(10)).mul(new BN(10).pow(new BN(17))); let p = Math.random() * 3; let r = Math.random() * 3; let burner; @@ -304,20 +311,19 @@ contract("Checkpoints", accounts => { } else { burner = account_investor3; } - let burnerBalance = new BigNumber(await I_SecurityToken.balanceOf(burner)); - if (n.gt(burnerBalance.div(2))) { - n = burnerBalance.div(2); + let burnerBalance = new BN(await I_SecurityToken.balanceOf(burner)); + if (n.gt(burnerBalance.div(new BN(2)))) { + n = burnerBalance.div(new BN(2)); } - n = n.toFixed(0); console.log("Burning: " + n.toString() + " from: " + burner); - await I_SecurityToken.forceBurn(burner, n, "", "", { from: token_owner }); + await I_SecurityToken.controllerRedeem(burner, n, "0x0", "0x0", { from: account_controller }); } console.log("Checking Interim..."); for (let k = 0; k < cps.length; k++) { - let balance1 = new BigNumber(await I_SecurityToken.balanceOfAt(account_investor1, k + 1)); - let balance2 = new BigNumber(await I_SecurityToken.balanceOfAt(account_investor2, k + 1)); - let balance3 = new BigNumber(await I_SecurityToken.balanceOfAt(account_investor3, k + 1)); - let totalSupply = new BigNumber(await I_SecurityToken.totalSupplyAt(k + 1)); + let balance1 = new BN(await stGetter.balanceOfAt(account_investor1, k + 1)); + let balance2 = new BN(await stGetter.balanceOfAt(account_investor2, k + 1)); + let balance3 = new BN(await stGetter.balanceOfAt(account_investor3, k + 1)); + let totalSupply = new BN(await stGetter.totalSupplyAt(k + 1)); let balances = [balance1, balance2, balance3]; console.log("Checking TotalSupply: " + totalSupply + " is " + ts[k] + " at checkpoint: " + (k + 1)); assert.isTrue(totalSupply.eq(ts[k])); @@ -330,10 +336,10 @@ contract("Checkpoints", accounts => { } console.log("Checking..."); for (let k = 0; k < cps.length; k++) { - let balance1 = new BigNumber(await I_SecurityToken.balanceOfAt(account_investor1, k + 1)); - let balance2 = new BigNumber(await I_SecurityToken.balanceOfAt(account_investor2, k + 1)); - let balance3 = new BigNumber(await I_SecurityToken.balanceOfAt(account_investor3, k + 1)); - let totalSupply = new BigNumber(await I_SecurityToken.totalSupplyAt(k + 1)); + let balance1 = new BN(await stGetter.balanceOfAt(account_investor1, k + 1)); + let balance2 = new BN(await stGetter.balanceOfAt(account_investor2, k + 1)); + let balance3 = new BN(await stGetter.balanceOfAt(account_investor3, k + 1)); + let totalSupply = new BN(await stGetter.totalSupplyAt(k + 1)); let balances = [balance1, balance2, balance3]; console.log("Checking TotalSupply: " + totalSupply + " is " + ts[k] + " at checkpoint: " + (k + 1)); assert.isTrue(totalSupply.eq(ts[k])); diff --git a/test/d_count_transfer_manager.js b/test/d_count_transfer_manager.js index 3107c4bf7..9654fbb67 100644 --- a/test/d_count_transfer_manager.js +++ b/test/d_count_transfer_manager.js @@ -1,7 +1,7 @@ import latestTime from "./helpers/latestTime"; import { duration, ensureException, promisifyLogWatch, latestBlock } from "./helpers/utils"; -import takeSnapshot, { increaseTime, revertToSnapshot } from "./helpers/time"; -import { encodeModuleCall } from "./helpers/encodeCall"; +import { takeSnapshot, increaseTime, revertToSnapshot } from "./helpers/time"; +import { encodeModuleCall, encodeProxyCall } from "./helpers/encodeCall"; import { setUpPolymathNetwork, deployCountTMAndVerifyed } from "./helpers/createInstances"; import { catchRevert } from "./helpers/exceptions"; @@ -9,12 +9,14 @@ const SecurityToken = artifacts.require("./SecurityToken.sol"); const GeneralTransferManager = artifacts.require("./GeneralTransferManager"); const CountTransferManagerFactory = artifacts.require("./CountTransferManagerFactory.sol"); const CountTransferManager = artifacts.require("./CountTransferManager"); +const MockCountTransferManager = artifacts.require("./MockCountTransferManager"); +const STGetter = artifacts.require("./STGetter.sol"); const Web3 = require("web3"); -const BigNumber = require("bignumber.js"); +let BN = Web3.utils.BN; const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); // Hardcoded development port -contract("CountTransferManager", accounts => { +contract("CountTransferManager", async (accounts) => { // Accounts Variable declaration let account_polymath; let account_issuer; @@ -24,11 +26,6 @@ contract("CountTransferManager", accounts => { let account_investor3; let account_investor4; - // investor Details - let fromTime = latestTime(); - let toTime = latestTime(); - let expiryTime = toTime + duration.days(15); - let message = "Transaction Should Fail!"; // Contract Instance Declaration @@ -40,8 +37,10 @@ contract("CountTransferManager", accounts => { let I_GeneralPermissionManager; let I_CountTransferManager; let I_CountTransferManager2; + let I_CountTransferManager3; let I_GeneralTransferManager; let I_GeneralTransferManager2; + let I_GeneralTransferManager3; let I_ExchangeTransferManager; let I_ModuleRegistry; let I_ModuleRegistryProxy; @@ -52,13 +51,21 @@ contract("CountTransferManager", accounts => { let I_STFactory; let I_SecurityToken; let I_SecurityToken2; + let I_SecurityToken3; let I_PolyToken; let I_PolymathRegistry; + let I_STRGetter; + let I_STGetter; + let I_MockCountTransferManagerLogic; + let stGetter; + let stGetter2; + let stGetter3; // SecurityToken Details const name = "Team"; const symbol = "sap"; const symbol2 = "sapp"; + const symbol3 = "sapp3" const tokenDetails = "This is equity type of issuance"; const decimals = 18; const contact = "team@polymath.network"; @@ -69,13 +76,16 @@ contract("CountTransferManager", accounts => { const stoKey = 3; // Initial fee for ticker registry and security token registry - const initRegFee = web3.utils.toWei("250"); + const initRegFee = new BN(web3.utils.toWei("1000")); // CountTransferManager details const holderCount = 2; // Maximum number of token holders let bytesSTO = encodeModuleCall(["uint256"], [holderCount]); + let currentTime; + before(async () => { + currentTime = new BN(await latestTime()); // Accounts setup account_polymath = accounts[0]; account_issuer = accounts[1]; @@ -101,13 +111,15 @@ contract("CountTransferManager", accounts => { I_STFactory, I_SecurityTokenRegistry, I_SecurityTokenRegistryProxy, - I_STRProxied + I_STRProxied, + I_STRGetter, + I_STGetter ] = instances; // STEP 2: Deploy the CountTransferManager - [I_CountTransferManagerFactory] = await deployCountTMAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, 0); + [I_CountTransferManagerFactory] = await deployCountTMAndVerifyed(account_polymath, I_MRProxied, 0); // STEP 3: Deploy Paid the CountTransferManager - [P_CountTransferManagerFactory] = await deployCountTMAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, web3.utils.toWei("500", "ether")); + [P_CountTransferManagerFactory] = await deployCountTMAndVerifyed(account_polymath, I_MRProxied, new BN(web3.utils.toWei("500", "ether"))); // Printing all the contract addresses console.log(` @@ -129,36 +141,37 @@ contract("CountTransferManager", accounts => { describe("Generate the SecurityToken", async () => { it("Should register the ticker before the generation of the security token", async () => { await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); - let tx = await I_STRProxied.registerTicker(token_owner, symbol, contact, { from: token_owner }); + let tx = await I_STRProxied.registerNewTicker(token_owner, symbol, { from: token_owner }); assert.equal(tx.logs[0].args._owner, token_owner); assert.equal(tx.logs[0].args._ticker, symbol.toUpperCase()); }); it("Should generate the new security token with the same symbol as registered above", async () => { await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); - let _blockNo = latestBlock(); - let tx = await I_STRProxied.generateSecurityToken(name, symbol, tokenDetails, false, { from: token_owner }); + + let tx = await I_STRProxied.generateNewSecurityToken(name, symbol, tokenDetails, false, token_owner, 0, { from: token_owner }); // 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 log = await promisifyLogWatch(I_SecurityToken.ModuleAdded({ from: _blockNo }), 1); + I_SecurityToken = await SecurityToken.at(tx.logs[1].args._securityTokenAddress); + stGetter = await STGetter.at(I_SecurityToken.address); + assert.equal(await stGetter.getTreasuryWallet.call(), token_owner, "Incorrect wallet set"); + const log = (await I_SecurityToken.getPastEvents('ModuleAdded', {filter: {transactionHash: tx.transactionHash}}))[0]; // Verify that GeneralTransferManager module get added successfully or not assert.equal(log.args._types[0].toNumber(), 2); assert.equal(web3.utils.toAscii(log.args._name).replace(/\u0000/g, ""), "GeneralTransferManager"); }); - it("Should intialize the auto attached modules", async () => { - let moduleData = (await I_SecurityToken.getModulesByType(2))[0]; - I_GeneralTransferManager = GeneralTransferManager.at(moduleData); + it("Should initialize the auto attached modules", async () => { + let moduleData = (await stGetter.getModulesByType(2))[0]; + I_GeneralTransferManager = await GeneralTransferManager.at(moduleData); }); it("Should successfully attach the CountTransferManager factory with the security token", async () => { - await I_PolyToken.getTokens(web3.utils.toWei("500", "ether"), token_owner); + await I_PolyToken.getTokens(new BN(web3.utils.toWei("2000", "ether")), token_owner); await catchRevert( - I_SecurityToken.addModule(P_CountTransferManagerFactory.address, bytesSTO, web3.utils.toWei("500", "ether"), 0, { + I_SecurityToken.addModule(P_CountTransferManagerFactory.address, bytesSTO, new BN(web3.utils.toWei("2000", "ether")), new BN(0), false, { from: token_owner }) ); @@ -166,12 +179,13 @@ contract("CountTransferManager", accounts => { it("Should successfully attach the CountTransferManager factory with the security token", async () => { let snapId = await takeSnapshot(); - await I_PolyToken.transfer(I_SecurityToken.address, web3.utils.toWei("500", "ether"), { from: token_owner }); + await I_PolyToken.transfer(I_SecurityToken.address, new BN(web3.utils.toWei("2000", "ether")), { from: token_owner }); const tx = await I_SecurityToken.addModule( P_CountTransferManagerFactory.address, bytesSTO, - web3.utils.toWei("500", "ether"), - 0, + new BN(web3.utils.toWei("2000", "ether")), + new BN(0), + false, { from: token_owner } ); assert.equal(tx.logs[3].args._types[0].toNumber(), transferManagerKey, "CountTransferManagerFactory doesn't get deployed"); @@ -180,19 +194,19 @@ contract("CountTransferManager", accounts => { "CountTransferManager", "CountTransferManagerFactory module was not added" ); - P_CountTransferManager = CountTransferManager.at(tx.logs[3].args._module); + P_CountTransferManager = await CountTransferManager.at(tx.logs[3].args._module); await revertToSnapshot(snapId); }); it("Should successfully attach the CountTransferManager with the security token", async () => { - const tx = await I_SecurityToken.addModule(I_CountTransferManagerFactory.address, bytesSTO, 0, 0, { from: token_owner }); + const tx = await I_SecurityToken.addModule(I_CountTransferManagerFactory.address, bytesSTO, new BN(0), new BN(0), false, { from: token_owner }); assert.equal(tx.logs[2].args._types[0].toNumber(), transferManagerKey, "CountTransferManager doesn't get deployed"); assert.equal( web3.utils.toAscii(tx.logs[2].args._name).replace(/\u0000/g, ""), "CountTransferManager", "CountTransferManager module was not added" ); - I_CountTransferManager = CountTransferManager.at(tx.logs[2].args._module); + I_CountTransferManager = await CountTransferManager.at(tx.logs[2].args._module); }); }); @@ -200,12 +214,11 @@ contract("CountTransferManager", accounts => { it("Should Buy the tokens", async () => { // Add the Investor in to the whitelist - let tx = await I_GeneralTransferManager.modifyWhitelist( + let tx = await I_GeneralTransferManager.modifyKYCData( account_investor1, - latestTime(), - latestTime(), - latestTime() + duration.days(10), - true, + currentTime, + currentTime, + currentTime.add(new BN(duration.days(10))), { from: account_issuer, gas: 500000 @@ -222,20 +235,19 @@ contract("CountTransferManager", accounts => { await increaseTime(5000); // Mint some tokens - await I_SecurityToken.mint(account_investor1, web3.utils.toWei("1", "ether"), { from: token_owner }); + await I_SecurityToken.issue(account_investor1, new BN(web3.utils.toWei("1", "ether")), "0x0", { from: token_owner }); - assert.equal((await I_SecurityToken.balanceOf(account_investor1)).toNumber(), web3.utils.toWei("1", "ether")); + assert.equal((await I_SecurityToken.balanceOf(account_investor1)).toString(), new BN(web3.utils.toWei("1", "ether")).toString()); }); it("Should Buy some more tokens", async () => { // Add the Investor in to the whitelist - let tx = await I_GeneralTransferManager.modifyWhitelist( + let tx = await I_GeneralTransferManager.modifyKYCData( account_investor2, - latestTime(), - latestTime(), - latestTime() + duration.days(10), - true, + currentTime, + currentTime, + currentTime.add(new BN(duration.days(10))), { from: account_issuer, gas: 500000 @@ -249,20 +261,19 @@ contract("CountTransferManager", accounts => { ); // Mint some tokens - await I_SecurityToken.mint(account_investor2, web3.utils.toWei("2", "ether"), { from: token_owner }); + await I_SecurityToken.issue(account_investor2, new BN(web3.utils.toWei("2", "ether")), "0x0", { from: token_owner }); - assert.equal((await I_SecurityToken.balanceOf(account_investor2)).toNumber(), web3.utils.toWei("2", "ether")); + assert.equal((await I_SecurityToken.balanceOf(account_investor2)).toString(), new BN(web3.utils.toWei("2", "ether")).toString()); }); - it("Should able to buy some more tokens (more than 2 hoders) -- because CountTransferManager is paused", async() => { - await I_CountTransferManager.pause({from: account_issuer }); + it("Should able to buy some more tokens (more than 2 hoders) -- because CountTransferManager is paused", async () => { + await I_CountTransferManager.pause({ from: account_issuer }); let snapId = await takeSnapshot(); - let tx = await I_GeneralTransferManager.modifyWhitelist( + let tx = await I_GeneralTransferManager.modifyKYCData( account_investor3, - latestTime(), - latestTime(), - latestTime() + duration.days(10), - true, + currentTime, + currentTime, + currentTime.add(new BN(duration.days(10))), { from: account_issuer, gas: 500000 @@ -275,19 +286,18 @@ contract("CountTransferManager", accounts => { "Failed in adding the investor in whitelist" ); - await I_SecurityToken.mint(account_investor3, web3.utils.toWei("3", "ether"), { from: token_owner }) + await I_SecurityToken.issue(account_investor3, new BN(web3.utils.toWei("3", "ether")), "0x0", { from: token_owner }); await revertToSnapshot(snapId); - }) + }); it("Should fail to buy some more tokens (more than 2 holders)", async () => { - await I_CountTransferManager.unpause({from: account_issuer }); + await I_CountTransferManager.unpause({ from: account_issuer }); // Add the Investor in to the whitelist - let tx = await I_GeneralTransferManager.modifyWhitelist( + let tx = await I_GeneralTransferManager.modifyKYCData( account_investor3, - latestTime(), - latestTime(), - latestTime() + duration.days(10), - true, + currentTime, + currentTime, + currentTime.add(new BN(duration.days(10))), { from: account_issuer, gas: 500000 @@ -300,23 +310,26 @@ contract("CountTransferManager", accounts => { "Failed in adding the investor in whitelist" ); - await catchRevert(I_SecurityToken.mint(account_investor3, web3.utils.toWei("3", "ether"), { from: token_owner })); + await catchRevert(I_SecurityToken.issue(account_investor3, new BN(web3.utils.toWei("3", "ether")), "0x0", { from: token_owner })); + await catchRevert(I_SecurityToken.transfer(account_investor3, new BN(web3.utils.toWei("1", "ether")), { from: account_investor2 })); + let canTransfer = await I_SecurityToken.canTransfer(account_investor3, new BN(web3.utils.toWei("1", "ether")), "0x0", { from: account_investor2 }); + assert.equal(canTransfer[0], "0x50"); //Transfer failure. }); it("Should still be able to add to original token holders", async () => { // Add the Investor in to the whitelist // Mint some tokens - await I_SecurityToken.mint(account_investor2, web3.utils.toWei("2", "ether"), { from: token_owner }); + await I_SecurityToken.issue(account_investor2, new BN(web3.utils.toWei("2", "ether")), "0x0", { from: token_owner }); - assert.equal((await I_SecurityToken.balanceOf(account_investor2)).toNumber(), web3.utils.toWei("4", "ether")); + assert.equal((await I_SecurityToken.balanceOf(account_investor2)).toString(), new BN(web3.utils.toWei("4", "ether")).toString()); }); it("Should still be able to transfer between existing token holders before count change", async () => { // Add the Investor in to the whitelist // Mint some tokens - await I_SecurityToken.transfer(account_investor1, web3.utils.toWei("2", "ether"), { from: account_investor2 }); + await I_SecurityToken.transfer(account_investor1, new BN(web3.utils.toWei("2", "ether")), { from: account_investor2 }); - assert.equal((await I_SecurityToken.balanceOf(account_investor2)).toNumber(), web3.utils.toWei("2", "ether")); + assert.equal((await I_SecurityToken.balanceOf(account_investor2)).toString(), new BN(web3.utils.toWei("2", "ether")).toString()); }); it("Should fail in modifying the holder count", async () => { @@ -334,133 +347,250 @@ contract("CountTransferManager", accounts => { it("Should still be able to transfer between existing token holders after count change", async () => { // Add the Investor in to the whitelist // Mint some tokens - await I_SecurityToken.transfer(account_investor2, web3.utils.toWei("2", "ether"), { from: account_investor1 }); + await I_SecurityToken.transfer(account_investor2, new BN(web3.utils.toWei("2", "ether")), { from: account_investor1 }); - assert.equal((await I_SecurityToken.balanceOf(account_investor2)).toNumber(), web3.utils.toWei("4", "ether")); + assert.equal((await I_SecurityToken.balanceOf(account_investor2)).toString(), new BN(web3.utils.toWei("4", "ether")).toString()); }); it("Should not be able to transfer to a new token holder", async () => { // await I_CountTransferManager.unpause({from: token_owner}); - await catchRevert(I_SecurityToken.transfer(account_investor3, web3.utils.toWei("2", "ether"), { from: account_investor2 })); + await catchRevert(I_SecurityToken.transfer(account_investor3, new BN(web3.utils.toWei("2", "ether")), { from: account_investor2 })); }); it("Should be able to consolidate balances", async () => { - await I_SecurityToken.transfer(account_investor2, web3.utils.toWei("1", "ether"), { from: account_investor1 }); + await I_SecurityToken.transfer(account_investor2, new BN(web3.utils.toWei("1", "ether")), { from: account_investor1 }); }); it("Should get the permission list", async () => { let perm = await I_CountTransferManager.getPermissions.call(); assert.equal(perm.length, 1); }); - describe("Test cases for adding and removing acc holder at the same time", async () => { - it("deploy a new token & auto attach modules", async () => { - - //register ticker and deploy token - await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); - let tx = await I_STRProxied.registerTicker(token_owner, symbol2, contact, { from: token_owner }); - await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); - let _blockNo = latestBlock(); - let tx2 = await I_STRProxied.generateSecurityToken(name, symbol2, tokenDetails, false, { from: token_owner }); - - I_SecurityToken2 = SecurityToken.at(tx2.logs[1].args._securityTokenAddress); - let moduleData = (await I_SecurityToken2.getModulesByType(2))[0]; - I_GeneralTransferManager2 = GeneralTransferManager.at(moduleData); - }); - it("add 3 holders to the token", async () => { - await I_GeneralTransferManager2.modifyWhitelist( - account_investor1, - latestTime(), - latestTime(), - latestTime() + duration.days(10), - true, - { - from: account_issuer, - gas: 500000 - } - ); - await I_GeneralTransferManager2.modifyWhitelist( - account_investor2, - latestTime(), - latestTime(), - latestTime() + duration.days(10), - true, - { - from: account_issuer, - gas: 500000 - } - ); - await I_GeneralTransferManager2.modifyWhitelist( - account_investor3, - latestTime(), - latestTime(), - latestTime() + duration.days(10), - true, - { - from: account_issuer, - gas: 500000 - } - ); - - await I_GeneralTransferManager2.modifyWhitelist( - account_investor4, - latestTime(), - latestTime(), - latestTime() + duration.days(10), - true, - { - from: account_issuer, - gas: 500000 - } - ); - - // Jump time - await increaseTime(5000); - // Add 3 holders to the token - await I_SecurityToken2.mint(account_investor1, web3.utils.toWei("1", "ether"), { from: token_owner }); - await I_SecurityToken2.mint(account_investor2, web3.utils.toWei("1", "ether"), { from: token_owner }); - await I_SecurityToken2.mint(account_investor3, web3.utils.toWei("1", "ether"), { from: token_owner }); - assert.equal((await I_SecurityToken2.balanceOf(account_investor1)).toNumber(), web3.utils.toWei("1", "ether")); - }); - it("Should intialize the auto attached modules", async () => { - let moduleData = (await I_SecurityToken2.getModulesByType(2))[0]; - I_GeneralTransferManager2 = GeneralTransferManager.at(moduleData); - }); - it("Should successfully attach the CountTransferManager factory with the security token", async () => { - await I_PolyToken.getTokens(web3.utils.toWei("500", "ether"), token_owner); - await catchRevert( - I_SecurityToken2.addModule(P_CountTransferManagerFactory.address, bytesSTO, web3.utils.toWei("500", "ether"), 0, { - from: token_owner - }) - ); - }); - it("Should successfully attach the CountTransferManager with the security token and set max holder to 2", async () => { - const tx = await I_SecurityToken2.addModule(I_CountTransferManagerFactory.address, bytesSTO, 0, 0, { from: token_owner }); - assert.equal(tx.logs[2].args._types[0].toNumber(), transferManagerKey, "CountTransferManager doesn't get deployed"); - assert.equal( - web3.utils.toAscii(tx.logs[2].args._name).replace(/\u0000/g, ""), - "CountTransferManager", - "CountTransferManager module was not added" - ); - I_CountTransferManager2 = CountTransferManager.at(tx.logs[2].args._module); - await I_CountTransferManager2.changeHolderCount(2, {from: token_owner}); - console.log('current max holder number is '+ await I_CountTransferManager2.maxHolderCount({from: token_owner})); - }); - it("Should allow add a new token holder while transfer all the tokens at one go", async () => { - let amount = (await I_SecurityToken2.balanceOf(account_investor2)).toNumber(); - let investorCount = await I_SecurityToken2.getInvestorCount({from: account_investor2 }); - console.log("current investor count is " + investorCount); - await I_SecurityToken2.transfer(account_investor4, amount, { from: account_investor2 }); - assert((await I_SecurityToken2.balanceOf(account_investor4)).toNumber(), amount, {from: account_investor2 }); - assert(await I_SecurityToken2.getInvestorCount({from: account_investor2 }), investorCount); - }); - }); + + describe("Test cases for adding and removing acc holder at the same time", async () => { + it("deploy a new token & auto attach modules", async () => { + //register ticker and deploy token + await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); + let tx = await I_STRProxied.registerNewTicker(token_owner, symbol2, { from: token_owner }); + + await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); + + let tx2 = await I_STRProxied.generateNewSecurityToken(name, symbol2, tokenDetails, false, token_owner, 0, { from: token_owner }); + + I_SecurityToken2 = await SecurityToken.at(tx2.logs[1].args._securityTokenAddress); + stGetter2 = await STGetter.at(I_SecurityToken2.address); + let moduleData = (await stGetter2.getModulesByType(2))[0]; + I_GeneralTransferManager2 = await GeneralTransferManager.at(moduleData); + }); + + it("deploy another new token & auto attach modules", async () => { + //register ticker and deploy token + await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); + let tx = await I_STRProxied.registerTicker(token_owner, symbol3, contact, { from: token_owner }); + + await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); + + let tx2 = await I_STRProxied.generateNewSecurityToken(name, symbol3, tokenDetails, false, token_owner, 0, { from: token_owner }); + + I_SecurityToken3 = await SecurityToken.at(tx2.logs[1].args._securityTokenAddress); + stGetter3 = await STGetter.at(I_SecurityToken3.address); + let moduleData = (await stGetter3.getModulesByType(2))[0]; + I_GeneralTransferManager3 = await GeneralTransferManager.at(moduleData); + }); + + it("add 3 holders to the token", async () => { + await I_GeneralTransferManager2.modifyKYCData( + account_investor1, + currentTime, + currentTime, + currentTime.add(new BN(duration.days(10))), + { + from: account_issuer, + gas: 500000 + } + ); + + await I_GeneralTransferManager2.modifyKYCData( + account_investor2, + currentTime, + currentTime, + currentTime.add(new BN(duration.days(10))), + { + from: account_issuer, + gas: 500000 + } + ); + + await I_GeneralTransferManager2.modifyKYCData( + account_investor3, + currentTime, + currentTime, + currentTime.add(new BN(duration.days(10))), + { + from: account_issuer, + gas: 500000 + } + ); + + await I_GeneralTransferManager2.modifyKYCData( + account_investor4, + currentTime, + currentTime, + currentTime.add(new BN(duration.days(10))), + { + from: account_issuer, + gas: 500000 + } + ); + + // Jump time + await increaseTime(5000); + + // Add 3 holders to the token + await I_SecurityToken2.issue(account_investor1, new BN(web3.utils.toWei("1", "ether")), "0x0", { from: token_owner }); + await I_SecurityToken2.issue(account_investor2, new BN(web3.utils.toWei("1", "ether")), "0x0", { from: token_owner }); + await I_SecurityToken2.issue(account_investor3, new BN(web3.utils.toWei("1", "ether")), "0x0", { from: token_owner }); + assert.equal((await I_SecurityToken2.balanceOf(account_investor1)).toString(), new BN(web3.utils.toWei("1", "ether")).toString()); + }); + + it("Should initialize the auto attached modules", async () => { + let moduleData = (await stGetter2.getModulesByType(2))[0]; + I_GeneralTransferManager2 = await GeneralTransferManager.at(moduleData); + }); + + it("Should successfully attach the CountTransferManager factory with the security token", async () => { + await I_PolyToken.getTokens(new BN(web3.utils.toWei("2000", "ether")), token_owner); + await catchRevert( + I_SecurityToken2.addModule(P_CountTransferManagerFactory.address, bytesSTO, new BN(web3.utils.toWei("2000", "ether")), new BN(0), false, { + from: token_owner + }) + ); + }); + + it("Should successfully attach the CountTransferManager with the security token and set max holder to 2", async () => { + const tx = await I_SecurityToken2.addModule(I_CountTransferManagerFactory.address, bytesSTO, new BN(0), new BN(0), false, { from: token_owner }); + assert.equal(tx.logs[2].args._types[0].toNumber(), transferManagerKey, "CountTransferManager doesn't get deployed"); + assert.equal( + web3.utils.toAscii(tx.logs[2].args._name).replace(/\u0000/g, ""), + "CountTransferManager", + "CountTransferManager module was not added" + ); + I_CountTransferManager2 = await CountTransferManager.at(tx.logs[2].args._module); + await I_CountTransferManager2.changeHolderCount(2, { from: token_owner }); + console.log("current max holder number is " + (await I_CountTransferManager2.maxHolderCount({ from: token_owner }))); + }); + + it("Should successfully attach the CountTransferManager with the third security token and set max holder to 2", async () => { + const tx = await I_SecurityToken3.addModule(I_CountTransferManagerFactory.address, bytesSTO, new BN(0), new BN(0), false, { from: token_owner }); + assert.equal(tx.logs[2].args._types[0].toNumber(), transferManagerKey, "CountTransferManager doesn't get deployed"); + assert.equal( + web3.utils.toAscii(tx.logs[2].args._name).replace(/\u0000/g, ""), + "CountTransferManager", + "CountTransferManager module was not added" + ); + I_CountTransferManager3 = await CountTransferManager.at(tx.logs[2].args._module); + await I_CountTransferManager3.changeHolderCount(2, { from: token_owner }); + console.log("current max holder number is " + (await I_CountTransferManager3.maxHolderCount({ from: token_owner }))); + }); + + it("Should upgrade the CTM", async () => { + I_MockCountTransferManagerLogic = await MockCountTransferManager.new("0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", { from: account_polymath }); + let bytesCM = encodeProxyCall(["uint256"], [11]); + await catchRevert( + // Fails as no upgrade available + I_SecurityToken2.upgradeModule(I_CountTransferManager2.address, { from: token_owner }) + ); + await catchRevert( + // Fails due to the same version being used + I_CountTransferManagerFactory.setLogicContract("3.0.0", I_MockCountTransferManagerLogic.address, bytesCM, { from: account_polymath }) + ); + await catchRevert( + // Fails due to the wrong contract being used + I_CountTransferManagerFactory.setLogicContract("4.0.0", "0x0000000000000000000000000000000000000000", bytesCM, { from: account_polymath }) + ); + await catchRevert( + // Fails due to the wrong owner being used + I_CountTransferManagerFactory.setLogicContract("4.0.0", "0x0000000000000000000000000000000000000000", bytesCM, { from: token_owner }) + ); + await I_CountTransferManagerFactory.setLogicContract("4.0.0", I_MockCountTransferManagerLogic.address, bytesCM, { from: account_polymath }); + await catchRevert( + // Fails as upgraded module has been unverified + I_SecurityToken2.upgradeModule(I_CountTransferManager2.address, { from: token_owner }) + ); + let tx = await I_MRProxied.verifyModule(I_CountTransferManagerFactory.address, { from: account_polymath }); + await I_SecurityToken2.upgradeModule(I_CountTransferManager2.address, { from: token_owner }); + let I_MockCountTransferManager = await MockCountTransferManager.at(I_CountTransferManager2.address); + let newValue = await I_MockCountTransferManager.someValue.call(); + assert(newValue.toNumber(), 11); + await I_MockCountTransferManager.newFunction(); + }); + + it("Should modify the upgrade data and upgrade", async () => { + let bytesCM = encodeProxyCall(["uint256"], [12]); + await catchRevert( + // Fails due to the same version being used + I_CountTransferManagerFactory.updateLogicContract(1, "3.0.0", I_MockCountTransferManagerLogic.address, bytesCM, { from: account_polymath }) + ); + await catchRevert( + // Fails due to the wrong contract being used + I_CountTransferManagerFactory.updateLogicContract(1, "4.0.0", "0x0000000000000000000000000000000000000000", bytesCM, { from: account_polymath }) + ); + await catchRevert( + // Fails due to the wrong owner being used + I_CountTransferManagerFactory.updateLogicContract(1, "4.0.0", "0x0000000000000000000000000000000000000000", bytesCM, { from: token_owner }) + ); + await I_CountTransferManagerFactory.updateLogicContract(1, "4.0.0", I_MockCountTransferManagerLogic.address, bytesCM, { from: account_polymath }); + await catchRevert( + // Fails as upgraded module has been unverified + I_SecurityToken3.upgradeModule(I_CountTransferManager3.address, { from: token_owner }) + ); + let tx = await I_MRProxied.verifyModule(I_CountTransferManagerFactory.address, { from: account_polymath }); + await I_SecurityToken3.upgradeModule(I_CountTransferManager3.address, { from: token_owner }); + let I_MockCountTransferManager = await MockCountTransferManager.at(I_CountTransferManager3.address); + let newValue = await I_MockCountTransferManager.someValue.call(); + assert(newValue.toNumber(), 12); + await I_MockCountTransferManager.newFunction(); + + }); + + it("Should upgrade the CTM again", async () => { + let I_MockCountTransferManagerLogic = await MockCountTransferManager.new("0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", { from: account_polymath }); + let bytesCM = encodeProxyCall(["uint256"], [11]); + await catchRevert( + // Fails as no upgrade available + I_SecurityToken2.upgradeModule(I_CountTransferManager2.address, { from: token_owner }) + ); + await catchRevert( + // Fails due to the same version being used + I_CountTransferManagerFactory.setLogicContract("4.0.0", I_MockCountTransferManagerLogic.address, bytesCM, { from: account_polymath }) + ); + await I_CountTransferManagerFactory.setLogicContract("5.0.0", I_MockCountTransferManagerLogic.address, bytesCM, { from: account_polymath }); + await catchRevert( + // Fails due to the same contract being used + I_CountTransferManagerFactory.setLogicContract("6.0.0", I_MockCountTransferManagerLogic.address, bytesCM, { from: account_polymath }) + ); + I_MockCountTransferManagerLogic = await MockCountTransferManager.new("0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", { from: account_polymath }); + await I_CountTransferManagerFactory.setLogicContract("6.0.0", I_MockCountTransferManagerLogic.address, bytesCM, { from: account_polymath }); + await I_MRProxied.verifyModule(I_CountTransferManagerFactory.address, { from: account_polymath }); + await I_SecurityToken2.upgradeModule(I_CountTransferManager2.address, { from: token_owner }); + await I_SecurityToken2.upgradeModule(I_CountTransferManager2.address, { from: token_owner }); + }); + + it("Should allow add a new token holder while transfer all the tokens at one go", async () => { + let amount = await I_SecurityToken2.balanceOf(account_investor2); + let investorCount = await stGetter2.holderCount({ from: account_investor2 }); + console.log("current investor count is " + investorCount); + await I_SecurityToken2.transfer(account_investor4, amount, { from: account_investor2 }); + assert((await I_SecurityToken2.balanceOf(account_investor4)).toString(), amount.toString(), { from: account_investor2 }); + assert(await stGetter2.holderCount({ from: account_investor2 }), investorCount); + }); + }); describe("Test cases for the factory", async () => { it("should get the exact details of the factory", async () => { - assert.equal(await I_CountTransferManagerFactory.getSetupCost.call(), 0); + assert.equal(await I_CountTransferManagerFactory.setupCost.call(), 0); assert.equal((await I_CountTransferManagerFactory.getTypes.call())[0], 2); assert.equal( - web3.utils.toAscii(await I_CountTransferManagerFactory.getName.call()).replace(/\u0000/g, ""), + web3.utils.toAscii(await I_CountTransferManagerFactory.name.call()).replace(/\u0000/g, ""), "CountTransferManager", "Wrong Module added" ); @@ -470,11 +600,6 @@ contract("CountTransferManager", accounts => { "Wrong Module added" ); assert.equal(await I_CountTransferManagerFactory.title.call(), "Count Transfer Manager", "Wrong Module added"); - assert.equal( - await I_CountTransferManagerFactory.getInstructions.call(), - "Allows an issuer to restrict the total number of non-zero token holders", - "Wrong Module added" - ); }); it("Should get the tags of the factory", async () => { @@ -483,78 +608,50 @@ contract("CountTransferManager", accounts => { }); }); - describe("Test cases for the ModuleFactory", async() => { - it("Should successfully change the SetupCost -- fail beacuse of bad owner", async() => { + describe("Test cases for the ModuleFactory", async () => { + it("Should successfully change the SetupCost -- fail beacuse of bad owner", async () => { await catchRevert( - I_CountTransferManagerFactory.changeFactorySetupFee(web3.utils.toWei("500"), {from: account_investor3}) + I_CountTransferManagerFactory.changeSetupCost(new BN(web3.utils.toWei("500")), { from: account_investor3 }) ); }); - it("Should successfully change the setupCost", async() => { - await I_CountTransferManagerFactory.changeFactorySetupFee(web3.utils.toWei("800"), { from: account_polymath }); - assert.equal(await I_CountTransferManagerFactory.getSetupCost.call(), web3.utils.toWei("800")); - }) - - it("Should successfully change the usage fee -- fail beacuse of bad owner", async() => { - await catchRevert( - I_CountTransferManagerFactory.changeFactoryUsageFee(web3.utils.toWei("500"), {from: account_investor3}) - ); + it("Should successfully change the setupCost", async () => { + await I_CountTransferManagerFactory.changeSetupCost(new BN(web3.utils.toWei("800")), { from: account_polymath }); + assert.equal((await I_CountTransferManagerFactory.setupCost.call()).toString(), new BN(web3.utils.toWei("800")).toString()); }); - it("Should successfully change the usage fee", async() => { - await I_CountTransferManagerFactory.changeFactoryUsageFee(web3.utils.toWei("800"), { from: account_polymath }); - assert.equal(await I_CountTransferManagerFactory.usageCost.call(), web3.utils.toWei("800")); - }); - - it("Should successfully change the subscription fee -- fail beacuse of bad owner", async() => { + it("Should successfully change the cost type -- fail beacuse of bad owner", async () => { await catchRevert( - I_CountTransferManagerFactory.changeFactorySubscriptionFee(web3.utils.toWei("500"), {from: account_investor3}) + I_CountTransferManagerFactory.changeCostAndType(new BN(web3.utils.toWei("500")), true, { from: account_investor3 }) ); }); - it("Should successfully change the subscription fee", async() => { - await I_CountTransferManagerFactory.changeFactorySubscriptionFee(web3.utils.toWei("800"), { from: account_polymath }); - assert.equal(await I_CountTransferManagerFactory.monthlySubscriptionCost.call(), web3.utils.toWei("800")); + it("Should successfully change the cost type", async () => { + let snapId = await takeSnapshot(); + let tx = await I_CountTransferManagerFactory.changeCostAndType(new BN(web3.utils.toWei("500")), true, { from: account_polymath }); + assert.equal(tx.logs[0].args[1].toString(), new BN(web3.utils.toWei("500")).toString(), "wrong setup fee in event"); + assert.equal(tx.logs[1].args[1], true, "wrong fee type in event"); + assert.equal((await I_CountTransferManagerFactory.setupCost.call()).toString(), new BN(web3.utils.toWei("500")).toString()); + assert.equal((await I_CountTransferManagerFactory.setupCost.call()).toString(), (await I_CountTransferManagerFactory.setupCostInPoly.call()).toString()); + await revertToSnapshot(snapId); }); - it("Should successfully change the version of the factory -- failed because of bad owner", async() => { - await catchRevert( - I_CountTransferManagerFactory.changeVersion("5.0.0", {from: account_investor3}) - ); - }); + }); - it("Should successfully change the version of the fatory -- failed because of the 0 string", async() => { - await catchRevert( - I_CountTransferManagerFactory.changeVersion("", {from: account_polymath}) - ); + describe("Test case for the changeSTVersionBounds", async () => { + it("Should successfully change the version bounds -- failed because of the non permitted bound type", async () => { + await catchRevert(I_CountTransferManagerFactory.changeSTVersionBounds("middleType", [1, 2, 3], { from: account_polymath })); }); - it("Should successfully change the version of the fatory", async() => { - await I_CountTransferManagerFactory.changeVersion("5.0.0", {from: account_polymath}); - assert.equal(await I_CountTransferManagerFactory.version.call(), "5.0.0"); + it("Should successfully change the version bound --failed because the new version length < 3", async () => { + await catchRevert(I_CountTransferManagerFactory.changeSTVersionBounds("lowerBound", [1, 2], { from: account_polymath })); }); - }) - - describe("Test case for the changeSTVersionBounds", async() => { - it("Should successfully change the version bounds -- failed because of the non permitted bound type", async() => { - await catchRevert( - I_CountTransferManagerFactory.changeSTVersionBounds("middleType", [1,2,3], {from: account_polymath}) - ); - }) - - it("Should successfully change the version bound --failed because the new version length < 3", async()=> { - await catchRevert( - I_CountTransferManagerFactory.changeSTVersionBounds("lowerBound", [1,2], {from: account_polymath}) - ); - }) - it("Should successfully change the version bound", async()=> { - await I_CountTransferManagerFactory.changeSTVersionBounds("lowerBound", [1,2,1], {from: account_polymath}); - await I_CountTransferManagerFactory.changeSTVersionBounds("lowerBound", [1,4,9], {from: account_polymath}); - await catchRevert( - I_CountTransferManagerFactory.changeSTVersionBounds("lowerBound", [1,0,0], {from: account_polymath}) - ); - }) - }) + it("Should successfully change the version bound", async () => { + await I_CountTransferManagerFactory.changeSTVersionBounds("lowerBound", [1, 2, 1], { from: account_polymath }); + await I_CountTransferManagerFactory.changeSTVersionBounds("lowerBound", [1, 0, 9], { from: account_polymath }); + await catchRevert(I_CountTransferManagerFactory.changeSTVersionBounds("lowerBound", [1, 1, 0], { from: account_polymath })); + }); + }); }); }); diff --git a/test/e_erc20_dividends.js b/test/e_erc20_dividends.js index 692916f67..d73c07166 100644 --- a/test/e_erc20_dividends.js +++ b/test/e_erc20_dividends.js @@ -1,6 +1,6 @@ import latestTime from "./helpers/latestTime"; import { duration, promisifyLogWatch, latestBlock } from "./helpers/utils"; -import takeSnapshot, { increaseTime, revertToSnapshot } from "./helpers/time"; +import { takeSnapshot, increaseTime, revertToSnapshot } from "./helpers/time"; import { catchRevert } from "./helpers/exceptions"; import { setUpPolymathNetwork, deployERC20DividendAndVerifyed, deployGPMAndVerifyed } from "./helpers/createInstances"; import { encodeModuleCall } from "./helpers/encodeCall"; @@ -9,12 +9,13 @@ const SecurityToken = artifacts.require("./SecurityToken.sol"); const GeneralTransferManager = artifacts.require("./GeneralTransferManager"); const ERC20DividendCheckpoint = artifacts.require("./ERC20DividendCheckpoint"); const GeneralPermissionManager = artifacts.require("GeneralPermissionManager"); +const STGetter = artifacts.require("./STGetter.sol"); const Web3 = require("web3"); -const BigNumber = require("bignumber.js"); +let BN = Web3.utils.BN; const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); // Hardcoded development port -contract("ERC20DividendCheckpoint", accounts => { +contract("ERC20DividendCheckpoint", async (accounts) => { // Accounts Variable declaration let account_polymath; let account_issuer; @@ -27,11 +28,6 @@ contract("ERC20DividendCheckpoint", accounts => { let account_manager; let account_temp; - // investor Details - let fromTime = latestTime(); - let toTime = latestTime(); - let expiryTime = toTime + duration.days(15); - let message = "Transaction Should Fail!"; let dividendName = "0x546573744469766964656e640000000000000000000000000000000000000000"; @@ -56,6 +52,9 @@ contract("ERC20DividendCheckpoint", accounts => { let I_SecurityToken; let I_PolyToken; let I_PolymathRegistry; + let I_STRGetter; + let I_STGetter; + let stGetter; // SecurityToken Details const name = "Team"; @@ -71,17 +70,20 @@ contract("ERC20DividendCheckpoint", accounts => { const checkpointKey = 4; //Manager details - const managerDetails = "Hello, I am a legit manager"; + const managerDetails = web3.utils.fromAscii("Hello"); // Initial fee for ticker registry and security token registry - const initRegFee = web3.utils.toWei("250"); + const initRegFee = new BN(web3.utils.toWei("1000")); + + const one_address = "0x0000000000000000000000000000000000000001"; + const address_zero = "0x0000000000000000000000000000000000000000"; - const one_address = '0x0000000000000000000000000000000000000001'; + let currentTime; const DividendParameters = ["address"]; before(async () => { - // Accounts setup + currentTime = new BN(await latestTime()); account_polymath = accounts[0]; account_issuer = accounts[1]; @@ -109,11 +111,17 @@ contract("ERC20DividendCheckpoint", accounts => { I_STFactory, I_SecurityTokenRegistry, I_SecurityTokenRegistryProxy, - I_STRProxied + I_STRProxied, + I_STRGetter, + I_STGetter ] = instances; - [P_ERC20DividendCheckpointFactory] = await deployERC20DividendAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, web3.utils.toWei("500", "ether")); - [I_ERC20DividendCheckpointFactory] = await deployERC20DividendAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, 0); + [P_ERC20DividendCheckpointFactory] = await deployERC20DividendAndVerifyed( + account_polymath, + I_MRProxied, + new BN(web3.utils.toWei("500", "ether")) + ); + [I_ERC20DividendCheckpointFactory] = await deployERC20DividendAndVerifyed(account_polymath, I_MRProxied, 0); // Printing all the contract addresses console.log(` @@ -135,36 +143,38 @@ contract("ERC20DividendCheckpoint", accounts => { describe("Generate the SecurityToken", async () => { it("Should register the ticker before the generation of the security token", async () => { await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); - let tx = await I_STRProxied.registerTicker(token_owner, symbol, contact, { from: token_owner }); + let tx = await I_STRProxied.registerNewTicker(token_owner, symbol, { from: token_owner }); assert.equal(tx.logs[0].args._owner, token_owner); assert.equal(tx.logs[0].args._ticker, symbol.toUpperCase()); }); it("Should generate the new security token with the same symbol as registered above", async () => { await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); - let _blockNo = latestBlock(); - let tx = await I_STRProxied.generateSecurityToken(name, symbol, tokenDetails, false, { from: token_owner }); + + let tx = await I_STRProxied.generateNewSecurityToken(name, symbol, tokenDetails, false, token_owner, 0, { from: token_owner }); // 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); + I_SecurityToken = await SecurityToken.at(tx.logs[1].args._securityTokenAddress); + stGetter = await STGetter.at(I_SecurityToken.address); + assert.equal(await stGetter.getTreasuryWallet.call(), token_owner, "Incorrect wallet set") - const log = await promisifyLogWatch(I_SecurityToken.ModuleAdded({ from: _blockNo }), 1); + const log = (await I_SecurityToken.getPastEvents('ModuleAdded', {filter: {transactionHash: tx.transactionHash}}))[0]; // Verify that GeneralTransferManager module get added successfully or not assert.equal(log.args._types[0].toNumber(), 2); assert.equal(web3.utils.toAscii(log.args._name).replace(/\u0000/g, ""), "GeneralTransferManager"); }); - it("Should intialize the auto attached modules", async () => { - let moduleData = (await I_SecurityToken.getModulesByType(2))[0]; - I_GeneralTransferManager = GeneralTransferManager.at(moduleData); + it("Should initialize the auto attached modules", async () => { + let moduleData = (await stGetter.getModulesByType(2))[0]; + I_GeneralTransferManager = await GeneralTransferManager.at(moduleData); }); it("Should successfully attach the ERC20DividendCheckpoint with the security token - fail insufficient payment", async () => { let bytesDividend = encodeModuleCall(DividendParameters, [wallet]); await catchRevert( - I_SecurityToken.addModule(P_ERC20DividendCheckpointFactory.address, bytesDividend, web3.utils.toWei("500", "ether"), 0, { + I_SecurityToken.addModule(P_ERC20DividendCheckpointFactory.address, bytesDividend, new BN(web3.utils.toWei("2000", "ether")), new BN(0), false, { from: token_owner }) ); @@ -172,10 +182,10 @@ contract("ERC20DividendCheckpoint", accounts => { 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 }); + await I_PolyToken.getTokens(new BN(web3.utils.toWei("2000", "ether")), token_owner); + await I_PolyToken.transfer(I_SecurityToken.address, new BN(web3.utils.toWei("2000", "ether")), { from: token_owner }); let bytesDividend = encodeModuleCall(DividendParameters, [wallet]); - const tx = await I_SecurityToken.addModule(P_ERC20DividendCheckpointFactory.address, bytesDividend, web3.utils.toWei("500", "ether"), 0, { + const tx = await I_SecurityToken.addModule(P_ERC20DividendCheckpointFactory.address, bytesDividend, new BN(web3.utils.toWei("2000", "ether")), new BN(0), false, { from: token_owner }); assert.equal(tx.logs[3].args._types[0].toNumber(), checkpointKey, "ERC20DividendCheckpoint doesn't get deployed"); @@ -184,20 +194,21 @@ contract("ERC20DividendCheckpoint", accounts => { "ERC20DividendCheckpoint", "ERC20DividendCheckpoint module was not added" ); - P_ERC20DividendCheckpoint = ERC20DividendCheckpoint.at(tx.logs[3].args._module); + P_ERC20DividendCheckpoint = await ERC20DividendCheckpoint.at(tx.logs[3].args._module); await revertToSnapshot(snapId); }); it("Should successfully attach the ERC20DividendCheckpoint with the security token", async () => { - let bytesDividend = encodeModuleCall(DividendParameters, [wallet]); - const tx = await I_SecurityToken.addModule(I_ERC20DividendCheckpointFactory.address, bytesDividend, 0, 0, { from: token_owner }); + let bytesDividend = encodeModuleCall(DividendParameters, [address_zero]); + const tx = await I_SecurityToken.addModule(I_ERC20DividendCheckpointFactory.address, bytesDividend, new BN(0), new BN(0), false, { from: token_owner }); + console.log(tx.logs[2].args); assert.equal(tx.logs[2].args._types[0].toNumber(), checkpointKey, "ERC20DividendCheckpoint doesn't get deployed"); assert.equal( web3.utils.toAscii(tx.logs[2].args._name).replace(/\u0000/g, ""), "ERC20DividendCheckpoint", "ERC20DividendCheckpoint module was not added" ); - I_ERC20DividendCheckpoint = ERC20DividendCheckpoint.at(tx.logs[2].args._module); + I_ERC20DividendCheckpoint = await ERC20DividendCheckpoint.at(tx.logs[2].args._module); }); }); @@ -205,12 +216,11 @@ contract("ERC20DividendCheckpoint", accounts => { it("Buy some tokens for account_investor1 (1 ETH)", async () => { // Add the Investor in to the whitelist - let tx = await I_GeneralTransferManager.modifyWhitelist( + let tx = await I_GeneralTransferManager.modifyKYCData( account_investor1, - latestTime(), - latestTime(), - latestTime() + duration.days(30), - true, + currentTime, + currentTime, + currentTime.add(new BN(duration.days(30))), { from: account_issuer, gas: 500000 @@ -227,20 +237,19 @@ contract("ERC20DividendCheckpoint", accounts => { await increaseTime(5000); // Mint some tokens - await I_SecurityToken.mint(account_investor1, web3.utils.toWei("1", "ether"), { from: token_owner }); + await I_SecurityToken.issue(account_investor1, new BN(web3.utils.toWei("1", "ether")), "0x0", { from: token_owner }); - assert.equal((await I_SecurityToken.balanceOf(account_investor1)).toNumber(), web3.utils.toWei("1", "ether")); + assert.equal((await I_SecurityToken.balanceOf(account_investor1)).toString(), new BN(web3.utils.toWei("1", "ether")).toString()); }); it("Buy some tokens for account_investor2 (2 ETH)", async () => { // Add the Investor in to the whitelist - let tx = await I_GeneralTransferManager.modifyWhitelist( + let tx = await I_GeneralTransferManager.modifyKYCData( account_investor2, - latestTime(), - latestTime(), - latestTime() + duration.days(30), - true, + currentTime, + currentTime, + currentTime.add(new BN(duration.days(30))), { from: account_issuer, gas: 500000 @@ -254,21 +263,21 @@ contract("ERC20DividendCheckpoint", accounts => { ); // Mint some tokens - await I_SecurityToken.mint(account_investor2, web3.utils.toWei("2", "ether"), { from: token_owner }); + await I_SecurityToken.issue(account_investor2, new BN(web3.utils.toWei("2", "ether")), "0x0", { from: token_owner }); - assert.equal((await I_SecurityToken.balanceOf(account_investor2)).toNumber(), web3.utils.toWei("2", "ether")); + assert.equal((await I_SecurityToken.balanceOf(account_investor2)).toString(), new BN(web3.utils.toWei("2", "ether")).toString()); }); it("Should fail in creating the dividend - incorrect allowance", async () => { - let maturity = latestTime(); - let expiry = latestTime() + duration.days(10); - await I_PolyToken.getTokens(web3.utils.toWei("1.5", "ether"), token_owner); + let maturity = await latestTime(); + let expiry = await latestTime() + duration.days(10); + await I_PolyToken.getTokens(new BN(web3.utils.toWei("1.5", "ether")), token_owner); await catchRevert( I_ERC20DividendCheckpoint.createDividend( maturity, expiry, I_PolyToken.address, - web3.utils.toWei("1.5", "ether"), + new BN(web3.utils.toWei("1.5", "ether")), dividendName, { from: token_owner } ) @@ -276,15 +285,15 @@ contract("ERC20DividendCheckpoint", accounts => { }); it("Should fail in creating the dividend - maturity > expiry", async () => { - 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 }); + let maturity = await latestTime(); + let expiry = await latestTime() - duration.days(10); + await I_PolyToken.approve(I_ERC20DividendCheckpoint.address, new BN(web3.utils.toWei("1.5", "ether")), { from: token_owner }); await catchRevert( I_ERC20DividendCheckpoint.createDividend( maturity, expiry, I_PolyToken.address, - web3.utils.toWei("1.5", "ether"), + new BN(web3.utils.toWei("1.5", "ether")), dividendName, { from: token_owner } ) @@ -292,14 +301,14 @@ contract("ERC20DividendCheckpoint", accounts => { }); it("Should fail in creating the dividend - now > expiry", async () => { - let maturity = latestTime() - duration.days(2); - let expiry = latestTime() - duration.days(1); + let maturity = await latestTime() - duration.days(2); + let expiry = await latestTime() - duration.days(1); await catchRevert( I_ERC20DividendCheckpoint.createDividend( maturity, expiry, I_PolyToken.address, - web3.utils.toWei("1.5", "ether"), + new BN(web3.utils.toWei("1.5", "ether")), dividendName, { from: token_owner } ) @@ -307,32 +316,32 @@ contract("ERC20DividendCheckpoint", accounts => { }); it("Should fail in creating the dividend - bad token", async () => { - let maturity = latestTime(); - let expiry = latestTime() + duration.days(10); + let maturity = await latestTime(); + let expiry = await latestTime() + duration.days(10); await catchRevert( - I_ERC20DividendCheckpoint.createDividend(maturity, expiry, 0, web3.utils.toWei("1.5", "ether"), dividendName, { + I_ERC20DividendCheckpoint.createDividend(maturity, expiry, address_zero, new BN(web3.utils.toWei("1.5", "ether")), dividendName, { from: token_owner }) ); }); it("Should fail in creating the dividend - amount is 0", async () => { - let maturity = latestTime(); - let expiry = latestTime() + duration.days(10); + let maturity = await latestTime(); + let expiry = await latestTime() + duration.days(10); await catchRevert( - I_ERC20DividendCheckpoint.createDividend(maturity, expiry, I_PolyToken.address, 0, dividendName, { from: token_owner }) + I_ERC20DividendCheckpoint.createDividend(maturity, expiry, I_PolyToken.address, new BN(0), dividendName, { from: token_owner }) ); }); it("Create new dividend of POLY tokens", async () => { - let maturity = latestTime() + duration.days(1); - let expiry = latestTime() + duration.days(10); + let maturity = await latestTime() + duration.days(1); + let expiry = await latestTime() + duration.days(10); let tx = await I_ERC20DividendCheckpoint.createDividend( maturity, expiry, I_PolyToken.address, - web3.utils.toWei("1.5", "ether"), + new BN(web3.utils.toWei("1.5", "ether")), dividendName, { from: token_owner } ); @@ -341,52 +350,51 @@ contract("ERC20DividendCheckpoint", accounts => { let data = await I_ERC20DividendCheckpoint.getDividendsData(); assert.equal(data[1][0].toNumber(), maturity, "maturity match"); assert.equal(data[2][0].toNumber(), expiry, "expiry match"); - assert.equal(data[3][0].toNumber(), web3.utils.toWei("1.5", "ether"), "amount match"); + assert.equal(data[3][0].toString(), new BN(web3.utils.toWei("1.5", "ether")).toString(), "amount match"); assert.equal(data[4][0].toNumber(), 0, "claimed match"); assert.equal(data[5][0], dividendName, "dividendName match"); }); 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 }); + await I_SecurityToken.transfer(account_investor2, new BN(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")); + assert.equal((await I_SecurityToken.balanceOf(account_investor2)).toString(), new BN(web3.utils.toWei("3", "ether")).toString()); }); it("Issuer pushes dividends iterating over account holders - dividends proportional to checkpoint - fails maturity in the future", async () => { - await catchRevert(I_ERC20DividendCheckpoint.pushDividendPayment(0, 0, 10, { from: token_owner })); + await catchRevert(I_ERC20DividendCheckpoint.pushDividendPayment(0, new BN(0), 10, { from: token_owner })); }); it("Issuer pushes dividends iterating over account holders - dividends proportional to checkpoint - fails not owner", async () => { // Increase time by 2 day await increaseTime(duration.days(2)); - await catchRevert(I_ERC20DividendCheckpoint.pushDividendPayment(0, 0, 10, { from: account_temp })); + await catchRevert(I_ERC20DividendCheckpoint.pushDividendPayment(0, new BN(0), 10, { from: account_temp })); }); it("Issuer pushes dividends iterating over account holders - dividends proportional to checkpoint - fails wrong index", async () => { - await catchRevert(I_ERC20DividendCheckpoint.pushDividendPayment(2, 0, 10, { from: token_owner })); + await catchRevert(I_ERC20DividendCheckpoint.pushDividendPayment(2, new BN(0), 10, { from: token_owner })); }); it("Issuer pushes dividends iterating over account holders - dividends proportional to checkpoint", async () => { - let investor1Balance = new BigNumber(await I_PolyToken.balanceOf(account_investor1)); - let investor2Balance = new BigNumber(await I_PolyToken.balanceOf(account_investor2)); - await I_ERC20DividendCheckpoint.pushDividendPayment(0, 0, 10, { from: token_owner, gas: 5000000 }); - let investor1BalanceAfter = new BigNumber(await I_PolyToken.balanceOf(account_investor1)); - let investor2BalanceAfter = new BigNumber(await I_PolyToken.balanceOf(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")); + let investor1Balance = new BN(await I_PolyToken.balanceOf(account_investor1)); + let investor2Balance = new BN(await I_PolyToken.balanceOf(account_investor2)); + await I_ERC20DividendCheckpoint.pushDividendPayment(0, new BN(0), 10, { from: token_owner, gas: 5000000 }); + let investor1BalanceAfter = new BN(await I_PolyToken.balanceOf(account_investor1)); + let investor2BalanceAfter = new BN(await I_PolyToken.balanceOf(account_investor2)); + assert.equal(investor1BalanceAfter.sub(investor1Balance).toString(), new BN(web3.utils.toWei("0.5", "ether")).toString()); + assert.equal(investor2BalanceAfter.sub(investor2Balance).toString(), new BN(web3.utils.toWei("1", "ether")).toString()); //Check fully claimed - assert.equal((await I_ERC20DividendCheckpoint.dividends(0))[5].toNumber(), web3.utils.toWei("1.5", "ether")); + assert.equal((await I_ERC20DividendCheckpoint.dividends(0))[5].toString(), new BN(web3.utils.toWei("1.5", "ether")).toString()); }); it("Buy some tokens for account_temp (1 ETH)", async () => { // Add the Investor in to the whitelist - let tx = await I_GeneralTransferManager.modifyWhitelist( + let tx = await I_GeneralTransferManager.modifyKYCData( account_temp, - latestTime(), - latestTime(), - latestTime() + duration.days(20), - true, + currentTime, + currentTime, + currentTime.add(new BN(duration.days(20))), { from: account_issuer, gas: 500000 @@ -396,33 +404,33 @@ contract("ERC20DividendCheckpoint", accounts => { 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 }); + await I_SecurityToken.issue(account_temp, new BN(web3.utils.toWei("1", "ether")), "0x0", { from: token_owner }); - assert.equal((await I_SecurityToken.balanceOf(account_temp)).toNumber(), web3.utils.toWei("1", "ether")); + assert.equal((await I_SecurityToken.balanceOf(account_temp)).toString(), new BN(web3.utils.toWei("1", "ether")).toString()); }); it("Should not allow to create dividend without name", async () => { - let maturity = latestTime() + duration.days(1); - 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 maturity = await latestTime() + duration.days(1); + let expiry = await latestTime() + duration.days(10); + await I_PolyToken.getTokens(new BN(web3.utils.toWei("1.5", "ether")), token_owner); + await I_PolyToken.approve(I_ERC20DividendCheckpoint.address, new BN(web3.utils.toWei("1.5", "ether")), { from: token_owner }); await catchRevert( - I_ERC20DividendCheckpoint.createDividend(maturity, expiry, I_PolyToken.address, web3.utils.toWei("1.5", "ether"), "", { + I_ERC20DividendCheckpoint.createDividend(maturity, expiry, I_PolyToken.address, new BN(web3.utils.toWei("1.5", "ether")), "0x0", { from: token_owner }) ); }); it("Create new dividend", async () => { - let maturity = latestTime() + duration.days(1); - let expiry = latestTime() + duration.days(10); - await I_PolyToken.getTokens(web3.utils.toWei("1.5", "ether"), token_owner); + let maturity = await latestTime() + duration.days(1); + let expiry = await latestTime() + duration.days(10); + await I_PolyToken.getTokens(new BN(web3.utils.toWei("1.5", "ether")), token_owner); // transfer approved in above test let tx = await I_ERC20DividendCheckpoint.createDividend( maturity, expiry, I_PolyToken.address, - web3.utils.toWei("1.5", "ether"), + new BN(web3.utils.toWei("1.5", "ether")), dividendName, { from: token_owner } ); @@ -432,24 +440,24 @@ contract("ERC20DividendCheckpoint", accounts => { it("Issuer pushes dividends iterating over account holders - dividends proportional to checkpoint - fails past expiry", async () => { await increaseTime(duration.days(12)); - await catchRevert(I_ERC20DividendCheckpoint.pushDividendPayment(1, 0, 10, { from: token_owner })); + await catchRevert(I_ERC20DividendCheckpoint.pushDividendPayment(1, new BN(0), 10, { from: token_owner })); }); - it("Issuer pushes dividends iterating over account holders - dividends proportional to checkpoin - fails already reclaimed", async () => { + it("Issuer pushes dividends iterating over account holders - dividends proportional to checkpoint - fails already reclaimed", async () => { + await I_ERC20DividendCheckpoint.changeWallet(wallet, {from: token_owner}); 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")); + assert.equal(tx.logs[0].args._claimedAmount.toString(), new BN(web3.utils.toWei("1.5", "ether")).toString()); await catchRevert(I_ERC20DividendCheckpoint.reclaimDividend(1, { from: token_owner, gas: 500000 })); }); it("Buy some tokens for account_investor3 (7 ETH)", async () => { // Add the Investor in to the whitelist - let tx = await I_GeneralTransferManager.modifyWhitelist( + let tx = await I_GeneralTransferManager.modifyKYCData( account_investor3, - latestTime(), - latestTime(), - latestTime() + duration.days(10), - true, + currentTime, + currentTime, + currentTime.add(new BN(duration.days(100000))), { from: account_issuer, gas: 500000 @@ -463,17 +471,19 @@ contract("ERC20DividendCheckpoint", accounts => { ); // Mint some tokens - await I_SecurityToken.mint(account_investor3, web3.utils.toWei("7", "ether"), { from: token_owner }); + await I_SecurityToken.issue(account_investor3, new BN(web3.utils.toWei("7", "ether")), "0x0", { from: token_owner }); - assert.equal((await I_SecurityToken.balanceOf(account_investor3)).toNumber(), web3.utils.toWei("7", "ether")); + assert.equal((await I_SecurityToken.balanceOf(account_investor3)).toString(), new BN(web3.utils.toWei("7", "ether")).toString()); }); it("Should allow to exclude same number of address as EXCLUDED_ADDRESS_LIMIT", async () => { let limit = await I_ERC20DividendCheckpoint.EXCLUDED_ADDRESS_LIMIT(); - limit = limit.toNumber(); + limit = limit.toNumber() + 42; let addresses = []; addresses.push(account_temp); - while (--limit) addresses.push(limit); + let tempAdd = '0x0000000000000000000000000000000000000000'; + while (--limit > 42) addresses.push(web3.utils.toChecksumAddress(tempAdd.substring(0, 42 - limit.toString().length) + limit)); + //'0x00000000000000000000000000000000000000' + limit)); await I_ERC20DividendCheckpoint.setDefaultExcluded(addresses, { from: token_owner }); let excluded = await I_ERC20DividendCheckpoint.getDefaultExcluded(); assert.equal(excluded[0], account_temp); @@ -489,7 +499,7 @@ contract("ERC20DividendCheckpoint", accounts => { it("Should not allow to exclude 0x0 address", async () => { let addresses = []; addresses.push(account_investor3); - addresses.push(0); + addresses.push(address_zero); await catchRevert(I_ERC20DividendCheckpoint.setDefaultExcluded(addresses, { from: token_owner })); }); @@ -501,24 +511,27 @@ contract("ERC20DividendCheckpoint", accounts => { it("Should not allow to exclude more address than EXCLUDED_ADDRESS_LIMIT", async () => { let limit = await I_ERC20DividendCheckpoint.EXCLUDED_ADDRESS_LIMIT(); - limit = limit.toNumber(); + limit = limit.toNumber() + 42; let addresses = []; addresses.push(account_temp); - while (limit--) addresses.push(limit); + let tempAdd = '0x0000000000000000000000000000000000000000'; + while (limit-- > 42) addresses.push(web3.utils.toChecksumAddress(tempAdd.substring(0, 42 - limit.toString().length) + limit)); + + // while (limit-- > 42) addresses.push(web3.utils.toChecksumAddress('0x00000000000000000000000000000000000000' + limit)); console.log(addresses.length); await catchRevert(I_ERC20DividendCheckpoint.setDefaultExcluded(addresses, { 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 maturity = await latestTime(); + let expiry = await latestTime() + duration.days(10); + await I_PolyToken.getTokens(new BN(web3.utils.toWei("11", "ether")), token_owner); + await I_PolyToken.approve(I_ERC20DividendCheckpoint.address, new BN(web3.utils.toWei("11", "ether")), { from: token_owner }); let tx = await I_ERC20DividendCheckpoint.createDividend( maturity, expiry, I_PolyToken.address, - web3.utils.toWei("10", "ether"), + new BN(web3.utils.toWei("10", "ether")), dividendName, { from: token_owner } ); @@ -533,16 +546,16 @@ contract("ERC20DividendCheckpoint", accounts => { it("should investor 3 claims dividend", async () => { console.log((await I_ERC20DividendCheckpoint.dividends(2))[5].toNumber()); - let investor1Balance = new BigNumber(await I_PolyToken.balanceOf(account_investor1)); - let investor2Balance = new BigNumber(await I_PolyToken.balanceOf(account_investor2)); - let investor3Balance = new BigNumber(await I_PolyToken.balanceOf(account_investor3)); + let investor1Balance = new BN(await I_PolyToken.balanceOf(account_investor1)); + let investor2Balance = new BN(await I_PolyToken.balanceOf(account_investor2)); + let investor3Balance = new BN(await I_PolyToken.balanceOf(account_investor3)); await I_ERC20DividendCheckpoint.pullDividendPayment(2, { from: account_investor3, gasPrice: 0 }); - let investor1BalanceAfter1 = new BigNumber(await I_PolyToken.balanceOf(account_investor1)); - let investor2BalanceAfter1 = new BigNumber(await I_PolyToken.balanceOf(account_investor2)); - let investor3BalanceAfter1 = new BigNumber(await I_PolyToken.balanceOf(account_investor3)); + let investor1BalanceAfter1 = new BN(await I_PolyToken.balanceOf(account_investor1)); + let investor2BalanceAfter1 = new BN(await I_PolyToken.balanceOf(account_investor2)); + let investor3BalanceAfter1 = new BN(await I_PolyToken.balanceOf(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")); + assert.equal(investor3BalanceAfter1.sub(investor3Balance).toString(), new BN(web3.utils.toWei("7", "ether")).toString()); let info = await I_ERC20DividendCheckpoint.getDividendProgress.call(2); console.log(info); assert.equal(info[0][1], account_temp, "account_temp"); @@ -560,22 +573,21 @@ contract("ERC20DividendCheckpoint", accounts => { }); it("should issuer pushes remain", async () => { - console.log((await I_ERC20DividendCheckpoint.dividends(2))[5].toNumber()); - let investor1BalanceAfter1 = new BigNumber(await I_PolyToken.balanceOf(account_investor1)); - let investor2BalanceAfter1 = new BigNumber(await I_PolyToken.balanceOf(account_investor2)); - let investor3BalanceAfter1 = new BigNumber(await I_PolyToken.balanceOf(account_investor3)); - let investorTempBalanceAfter1 = new BigNumber(await I_PolyToken.balanceOf(account_temp)); - await I_ERC20DividendCheckpoint.pushDividendPayment(2, 0, 10, { from: token_owner }); - let investor1BalanceAfter2 = new BigNumber(await I_PolyToken.balanceOf(account_investor1)); - let investor2BalanceAfter2 = new BigNumber(await I_PolyToken.balanceOf(account_investor2)); - let investor3BalanceAfter2 = new BigNumber(await I_PolyToken.balanceOf(account_investor3)); - let investorTempBalanceAfter2 = new BigNumber(await I_PolyToken.balanceOf(account_temp)); + let investor1BalanceAfter1 = new BN(await I_PolyToken.balanceOf(account_investor1)); + let investor2BalanceAfter1 = new BN(await I_PolyToken.balanceOf(account_investor2)); + let investor3BalanceAfter1 = new BN(await I_PolyToken.balanceOf(account_investor3)); + let investorTempBalanceAfter1 = new BN(await I_PolyToken.balanceOf(account_temp)); + await I_ERC20DividendCheckpoint.pushDividendPayment(2, new BN(0), 10, { from: token_owner }); + let investor1BalanceAfter2 = new BN(await I_PolyToken.balanceOf(account_investor1)); + let investor2BalanceAfter2 = new BN(await I_PolyToken.balanceOf(account_investor2)); + let investor3BalanceAfter2 = new BN(await I_PolyToken.balanceOf(account_investor3)); + let investorTempBalanceAfter2 = new BN(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(investor2BalanceAfter2.sub(investor2BalanceAfter1).toString(), new BN(web3.utils.toWei("3", "ether")).toString()); 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("10", "ether")); + assert.equal((await I_ERC20DividendCheckpoint.dividends(2))[5].toString(), new BN(web3.utils.toWei("10", "ether")).toString()); }); it("Delete global exclusion list", async () => { @@ -583,26 +595,26 @@ contract("ERC20DividendCheckpoint", accounts => { }); 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")); + await I_SecurityToken.transfer(account_investor1, new BN(web3.utils.toWei("1", "ether")), { from: account_investor2 }); + assert.equal((await I_SecurityToken.balanceOf(account_investor1)).toString(), new BN(web3.utils.toWei("1", "ether")).toString()); + assert.equal((await I_SecurityToken.balanceOf(account_investor2)).toString(), new BN(web3.utils.toWei("2", "ether")).toString()); + assert.equal((await I_SecurityToken.balanceOf(account_investor3)).toString(), new BN(web3.utils.toWei("7", "ether")).toString()); + assert.equal((await I_SecurityToken.balanceOf(account_temp)).toString(), new BN(web3.utils.toWei("1", "ether")).toString()); }); it("Create another new dividend with explicit checkpoint - fails bad allowance", async () => { - let maturity = latestTime(); - let expiry = latestTime() + duration.days(2); + let maturity = await latestTime(); + let expiry = await 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); + await I_PolyToken.getTokens(new BN(web3.utils.toWei("20", "ether")), token_owner); await catchRevert( I_ERC20DividendCheckpoint.createDividendWithCheckpoint( maturity, expiry, I_PolyToken.address, - web3.utils.toWei("20", "ether"), + new BN(web3.utils.toWei("20", "ether")), 4, dividendName, { from: token_owner } @@ -613,15 +625,15 @@ contract("ERC20DividendCheckpoint", accounts => { it("Create another new dividend with explicit - fails maturity > expiry", async () => { console.log((await I_SecurityToken.currentCheckpointId()).toNumber()); - let maturity = latestTime(); - let expiry = latestTime() - duration.days(10); - await I_PolyToken.approve(I_ERC20DividendCheckpoint.address, web3.utils.toWei("20", "ether"), { from: token_owner }); + let maturity = await latestTime(); + let expiry = await latestTime() - duration.days(10); + await I_PolyToken.approve(I_ERC20DividendCheckpoint.address, new BN(web3.utils.toWei("20", "ether")), { from: token_owner }); await catchRevert( I_ERC20DividendCheckpoint.createDividendWithCheckpoint( maturity, expiry, I_PolyToken.address, - web3.utils.toWei("20", "ether"), + new BN(web3.utils.toWei("20", "ether")), 4, dividendName, { from: token_owner } @@ -632,14 +644,14 @@ contract("ERC20DividendCheckpoint", accounts => { it("Create another new dividend with explicit - fails now > expiry", async () => { console.log((await I_SecurityToken.currentCheckpointId()).toNumber()); - let maturity = latestTime() - duration.days(5); - let expiry = latestTime() - duration.days(2); + let maturity = await latestTime() - duration.days(5); + let expiry = await latestTime() - duration.days(2); await catchRevert( I_ERC20DividendCheckpoint.createDividendWithCheckpoint( maturity, expiry, I_PolyToken.address, - web3.utils.toWei("20", "ether"), + new BN(web3.utils.toWei("20", "ether")), 4, dividendName, { from: token_owner } @@ -648,14 +660,14 @@ contract("ERC20DividendCheckpoint", accounts => { }); it("Create another new dividend with explicit - fails bad checkpoint", async () => { - let maturity = latestTime(); - let expiry = latestTime() + duration.days(2); + let maturity = await latestTime(); + let expiry = await latestTime() + duration.days(2); await catchRevert( I_ERC20DividendCheckpoint.createDividendWithCheckpoint( maturity, expiry, I_PolyToken.address, - web3.utils.toWei("20", "ether"), + new BN(web3.utils.toWei("20", "ether")), 5, dividendName, { from: token_owner } @@ -666,44 +678,43 @@ contract("ERC20DividendCheckpoint", accounts => { it("Set withholding tax of 20% on account_temp and 100% on investor2", async () => { await I_ERC20DividendCheckpoint.setWithholding( [account_temp, account_investor2], - [BigNumber(20 * 10 ** 16), BigNumber(100 * 10 ** 16)], + [new BN(20).mul(new BN(10).pow(new BN(16))), new BN(100).mul(new BN(10).pow(new BN(16)))], { from: token_owner } ); }); it("Should not allow mismatching input lengths", async () => { await catchRevert( - I_ERC20DividendCheckpoint.setWithholding([account_temp], [BigNumber(20 * 10 ** 16), BigNumber(10 * 10 ** 16)], { + I_ERC20DividendCheckpoint.setWithholding([account_temp], [new BN(20).mul(new BN(10).pow(new BN(16))), new BN(10).mul(new BN(10).pow(new BN(16)))], { from: token_owner }) ); }); it("Should not allow withholding greater than limit", async () => { - await catchRevert(I_ERC20DividendCheckpoint.setWithholding([account_temp], [BigNumber(20 * 10 ** 26)], { from: token_owner })); - await catchRevert( - I_ERC20DividendCheckpoint.setWithholdingFixed([account_temp], BigNumber(20 * 10 ** 26), { from: token_owner }), - "" - ); + await catchRevert(I_ERC20DividendCheckpoint.setWithholding([account_temp], [new BN(20).mul(new BN(10).pow(new BN(26)))], { from: token_owner })); + await catchRevert(I_ERC20DividendCheckpoint.setWithholdingFixed([account_temp], new BN(20).mul(new BN(10).pow(new BN(26))), { from: token_owner }), ""); }); it("Should not create dividend with more exclusions than limit", 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 maturity = await latestTime(); + let expiry = await latestTime() + duration.days(10); + await I_PolyToken.getTokens(new BN(web3.utils.toWei("11", "ether")), token_owner); + await I_PolyToken.approve(I_ERC20DividendCheckpoint.address, new BN(web3.utils.toWei("11", "ether")), { from: token_owner }); let limit = await I_ERC20DividendCheckpoint.EXCLUDED_ADDRESS_LIMIT(); - limit = limit.toNumber(); + limit = limit.toNumber() + 42; let addresses = []; addresses.push(account_temp); addresses.push(token_owner); - while (--limit) addresses.push(limit); + let tempAdd = '0x0000000000000000000000000000000000000000'; + while (--limit > 42) addresses.push(web3.utils.toChecksumAddress(tempAdd.substring(0, 42 - limit.toString().length) + limit)); + // while (--limit > 42) addresses.push(web3.utils.toChecksumAddress('0x00000000000000000000000000000000000000' + limit)); await catchRevert( I_ERC20DividendCheckpoint.createDividendWithCheckpointAndExclusions( maturity, expiry, I_PolyToken.address, - web3.utils.toWei("10", "ether"), + new BN(web3.utils.toWei("10", "ether")), 4, addresses, dividendName, @@ -713,15 +724,15 @@ contract("ERC20DividendCheckpoint", accounts => { }); 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); + let maturity = await latestTime(); + let expiry = await latestTime() + duration.days(10); + await I_PolyToken.getTokens(new BN(web3.utils.toWei("11", "ether")), token_owner); //token transfer approved in above test let tx = await I_ERC20DividendCheckpoint.createDividendWithCheckpointAndExclusions( maturity, expiry, I_PolyToken.address, - web3.utils.toWei("10", "ether"), + new BN(web3.utils.toWei("10", "ether")), 4, [account_investor1], dividendName, @@ -731,37 +742,41 @@ contract("ERC20DividendCheckpoint", accounts => { }); it("Should not create new dividend with duplicate exclusion", async () => { - let maturity = latestTime(); - let expiry = latestTime() + duration.days(10); - await I_PolyToken.getTokens(web3.utils.toWei("11", "ether"), token_owner); + let maturity = await latestTime(); + let expiry = await latestTime() + duration.days(10); + await I_PolyToken.getTokens(new BN(web3.utils.toWei("11", "ether")), token_owner); //token transfer approved in above test - await catchRevert(I_ERC20DividendCheckpoint.createDividendWithCheckpointAndExclusions( - maturity, - expiry, - I_PolyToken.address, - web3.utils.toWei("10", "ether"), - 4, - [account_investor1, account_investor1], - dividendName, - { from: token_owner } - )); + await catchRevert( + I_ERC20DividendCheckpoint.createDividendWithCheckpointAndExclusions( + maturity, + expiry, + I_PolyToken.address, + new BN(web3.utils.toWei("10", "ether")), + 4, + [account_investor1, account_investor1], + dividendName, + { from: token_owner } + ) + ); }); it("Should not create new dividend with 0x0 address in exclusion", async () => { - let maturity = latestTime(); - let expiry = latestTime() + duration.days(10); - await I_PolyToken.getTokens(web3.utils.toWei("11", "ether"), token_owner); + let maturity = await latestTime(); + let expiry = await latestTime() + duration.days(10); + await I_PolyToken.getTokens(new BN(web3.utils.toWei("11", "ether")), token_owner); //token transfer approved in above test - await catchRevert(I_ERC20DividendCheckpoint.createDividendWithCheckpointAndExclusions( - maturity, - expiry, - I_PolyToken.address, - web3.utils.toWei("10", "ether"), - 4, - [0], - dividendName, - { from: token_owner } - )); + await catchRevert( + I_ERC20DividendCheckpoint.createDividendWithCheckpointAndExclusions( + maturity, + expiry, + I_PolyToken.address, + new BN(web3.utils.toWei("10", "ether")), + 4, + [address_zero], + dividendName, + { from: token_owner } + ) + ); }); it("Should not allow excluded to pull Dividend Payment", async () => { @@ -795,14 +810,14 @@ contract("ERC20DividendCheckpoint", accounts => { 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("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")); - assert.equal(dividendAmount1[1].toNumber(), web3.utils.toWei("0", "ether")); - assert.equal(dividendAmount2[1].toNumber(), web3.utils.toWei("2", "ether")); - assert.equal(dividendAmount3[1].toNumber(), web3.utils.toWei("0", "ether")); - assert.equal(dividendAmount_temp[1].toNumber(), web3.utils.toWei("0.2", "ether")); + assert.equal(dividendAmount1[0].toString(), new BN(web3.utils.toWei("0", "ether")).toString()); + assert.equal(dividendAmount2[0].toString(), new BN(web3.utils.toWei("2", "ether")).toString()); + assert.equal(dividendAmount3[0].toString(), new BN(web3.utils.toWei("7", "ether")).toString()); + assert.equal(dividendAmount_temp[0].toString(), new BN(web3.utils.toWei("1", "ether")).toString()); + assert.equal(dividendAmount1[1].toString(), new BN(web3.utils.toWei("0", "ether")).toString()); + assert.equal(dividendAmount2[1].toString(), new BN(web3.utils.toWei("2", "ether")).toString()); + assert.equal(dividendAmount3[1].toString(), new BN(web3.utils.toWei("0", "ether")).toString()); + assert.equal(dividendAmount_temp[1].toString(), new BN(web3.utils.toWei("0.2", "ether")).toString()); }); it("Pause and unpause the dividend contract", async () => { @@ -813,50 +828,49 @@ contract("ERC20DividendCheckpoint", accounts => { tx = await I_ERC20DividendCheckpoint.unpause({from: token_owner}); }); - it("Investor 2 claims dividend", async () => { - let investor1Balance = new BigNumber(await I_PolyToken.balanceOf(account_investor1)); - let investor2Balance = new BigNumber(await I_PolyToken.balanceOf(account_investor2)); - let investor3Balance = new BigNumber(await I_PolyToken.balanceOf(account_investor3)); - let tempBalance = new BigNumber(await web3.eth.getBalance(account_temp)); + let investor1Balance = new BN(await I_PolyToken.balanceOf(account_investor1)); + let investor2Balance = new BN(await I_PolyToken.balanceOf(account_investor2)); + let investor3Balance = new BN(await I_PolyToken.balanceOf(account_investor3)); + let tempBalance = new BN(await web3.eth.getBalance(account_temp)); let _blockNo = latestBlock(); let tx = await I_ERC20DividendCheckpoint.pullDividendPayment(3, { from: account_investor2, gasPrice: 0 }); - let investor1BalanceAfter1 = new BigNumber(await I_PolyToken.balanceOf(account_investor1)); - let investor2BalanceAfter1 = new BigNumber(await I_PolyToken.balanceOf(account_investor2)); - let investor3BalanceAfter1 = new BigNumber(await I_PolyToken.balanceOf(account_investor3)); - let tempBalanceAfter1 = new BigNumber(await web3.eth.getBalance(account_temp)); + let investor1BalanceAfter1 = new BN(await I_PolyToken.balanceOf(account_investor1)); + let investor2BalanceAfter1 = new BN(await I_PolyToken.balanceOf(account_investor2)); + let investor3BalanceAfter1 = new BN(await I_PolyToken.balanceOf(account_investor3)); + let tempBalanceAfter1 = new BN(await web3.eth.getBalance(account_temp)); assert.equal(investor1BalanceAfter1.sub(investor1Balance).toNumber(), 0); // Full amount is in withheld tax - assert.equal(investor2BalanceAfter1.sub(investor2Balance).toNumber(), 0); + assert.equal(investor2BalanceAfter1.sub(investor2Balance).toString(), 0); assert.equal(investor3BalanceAfter1.sub(investor3Balance).toNumber(), 0); assert.equal(tempBalanceAfter1.sub(tempBalance).toNumber(), 0); //Check tx contains event... - const log = await promisifyLogWatch(I_ERC20DividendCheckpoint.ERC20DividendClaimed({ from: _blockNo }), 1); + const log = (await I_ERC20DividendCheckpoint.getPastEvents('ERC20DividendClaimed', {filter: {transactionHash: tx.transactionHash}}))[0]; // Verify that GeneralTransferManager module get added successfully or not assert.equal(log.args._payee, account_investor2); - assert.equal(log.args._withheld.toNumber(), web3.utils.toWei("2", "ether")); - assert.equal(log.args._amount.toNumber(), web3.utils.toWei("2", "ether")); + assert.equal(web3.utils.fromWei(log.args._withheld.toString()), 2); + assert.equal(web3.utils.fromWei(log.args._amount.toString()), 2); }); it("Should issuer pushes temp investor - investor1 excluded", async () => { - let investor1BalanceAfter1 = new BigNumber(await I_PolyToken.balanceOf(account_investor1)); - let investor2BalanceAfter1 = new BigNumber(await I_PolyToken.balanceOf(account_investor2)); - let investor3BalanceAfter1 = new BigNumber(await I_PolyToken.balanceOf(account_investor3)); - let tempBalanceAfter1 = new BigNumber(await I_PolyToken.balanceOf(account_temp)); + let investor1BalanceAfter1 = new BN(await I_PolyToken.balanceOf(account_investor1)); + let investor2BalanceAfter1 = new BN(await I_PolyToken.balanceOf(account_investor2)); + let investor3BalanceAfter1 = new BN(await I_PolyToken.balanceOf(account_investor3)); + let tempBalanceAfter1 = new BN(await I_PolyToken.balanceOf(account_temp)); // Issuer can still push payments when contract is paused let tx = await I_ERC20DividendCheckpoint.pause({from: token_owner}); await I_ERC20DividendCheckpoint.pushDividendPaymentToAddresses(3, [account_temp, account_investor1], { from: token_owner }); tx = await I_ERC20DividendCheckpoint.unpause({from: token_owner}); - let investor1BalanceAfter2 = new BigNumber(await I_PolyToken.balanceOf(account_investor1)); - let investor2BalanceAfter2 = new BigNumber(await I_PolyToken.balanceOf(account_investor2)); - let investor3BalanceAfter2 = new BigNumber(await I_PolyToken.balanceOf(account_investor3)); - let tempBalanceAfter2 = new BigNumber(await I_PolyToken.balanceOf(account_temp)); + let investor1BalanceAfter2 = new BN(await I_PolyToken.balanceOf(account_investor1)); + let investor2BalanceAfter2 = new BN(await I_PolyToken.balanceOf(account_investor2)); + let investor3BalanceAfter2 = new BN(await I_PolyToken.balanceOf(account_investor3)); + let tempBalanceAfter2 = new BN(await I_PolyToken.balanceOf(account_temp)); 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("0.8", "ether")); + assert.equal(tempBalanceAfter2.sub(tempBalanceAfter1).toString(), new BN(web3.utils.toWei("0.8", "ether")).toString()); //Check fully claimed - assert.equal((await I_ERC20DividendCheckpoint.dividends(3))[5].toNumber(), web3.utils.toWei("3", "ether")); + assert.equal((await I_ERC20DividendCheckpoint.dividends(3))[5].toString(), new BN(web3.utils.toWei("3", "ether")).toString()); }); it("should calculate dividend after the push dividend payment", async () => { @@ -892,57 +906,57 @@ contract("ERC20DividendCheckpoint", accounts => { console.log(info[2][3]); console.log("Withheld:"); - console.log(info[3][0].toNumber()); - console.log(info[3][1].toNumber()); - console.log(info[3][2].toNumber()); - console.log(info[3][3].toNumber()); + console.log(info[3][0].toString()); + console.log(info[3][1].toString()); + console.log(info[3][2].toString()); + console.log(info[3][3].toString()); console.log("Claimed:"); - console.log(info[4][0].toNumber()); - console.log(info[4][1].toNumber()); - console.log(info[4][2].toNumber()); - console.log(info[4][3].toNumber()); + console.log(info[4][0].toString()); + console.log(info[4][1].toString()); + console.log(info[4][2].toString()); + console.log(info[4][3].toString()); console.log("Balance:"); - console.log(info[5][0].toNumber()); - console.log(info[5][1].toNumber()); - console.log(info[5][2].toNumber()); - console.log(info[5][3].toNumber()); + console.log(info[5][0].toString()); + console.log(info[5][1].toString()); + console.log(info[5][2].toString()); + console.log(info[5][3].toString()); assert.equal(info[0][0], account_investor1, "account match"); assert.equal(info[0][1], account_investor2, "account match"); assert.equal(info[0][2], account_temp, "account match"); assert.equal(info[0][3], account_investor3, "account match"); - assert.equal(info[3][0].toNumber(), 0, "withheld match"); - assert.equal(info[3][1].toNumber(), web3.utils.toWei("2", "ether"), "withheld match"); - assert.equal(info[3][2].toNumber(), web3.utils.toWei("0.2", "ether"), "withheld match"); - assert.equal(info[3][3].toNumber(), 0, "withheld match"); + assert.equal(info[3][0].toString(), 0, "withheld match"); + assert.equal(info[3][1].toString(), web3.utils.toWei("2", "ether"), "withheld match"); + assert.equal(info[3][2].toString(), web3.utils.toWei("0.2", "ether"), "withheld match"); + assert.equal(info[3][3].toString(), 0, "withheld match"); - assert.equal(info[4][0].toNumber(), 0, "excluded"); - assert.equal(info[4][1].toNumber(), web3.utils.toWei("0", "ether"), "claim match"); - assert.equal(info[4][2].toNumber(), web3.utils.toWei("0.8", "ether"), "claim match"); - assert.equal(info[4][3].toNumber(), web3.utils.toWei("7", "ether"), "claim match"); + assert.equal(info[4][0].toString(), 0, "excluded"); + assert.equal(info[4][1].toString(), web3.utils.toWei("0", "ether"), "claim match"); + assert.equal(info[4][2].toString(), web3.utils.toWei("0.8", "ether"), "claim match"); + assert.equal(info[4][3].toString(), web3.utils.toWei("7", "ether"), "claim match"); - assert.equal(info[5][0].toNumber(), (await I_SecurityToken.balanceOfAt(account_investor1, 4)).toNumber(), "balance match"); - assert.equal(info[5][1].toNumber(), (await I_SecurityToken.balanceOfAt(account_investor2, 4)).toNumber(), "balance match"); - assert.equal(info[5][2].toNumber(), (await I_SecurityToken.balanceOfAt(account_temp, 4)).toNumber(), "balance match"); - assert.equal(info[5][3].toNumber(), (await I_SecurityToken.balanceOfAt(account_investor3, 4)).toNumber(), "balance match"); + assert.equal(info[5][0].toString(), (await stGetter.balanceOfAt(account_investor1, new BN(4))).toString(), "balance match"); + assert.equal(info[5][1].toString(), (await stGetter.balanceOfAt(account_investor2, new BN(4))).toString(), "balance match"); + assert.equal(info[5][2].toString(), (await stGetter.balanceOfAt(account_temp, new BN(4))).toString(), "balance match"); + assert.equal(info[5][3].toString(), (await stGetter.balanceOfAt(account_investor3, new BN(4))).toString(), "balance match"); let dividend = await I_ERC20DividendCheckpoint.dividends.call(3); - console.log("totalWithheld: " + dividend[8].toNumber()); - console.log("totalWithheldWithdrawn: " + dividend[9].toNumber()); - assert.equal(dividend[8].toNumber(), web3.utils.toWei("2.2", "ether")); - assert.equal(dividend[9].toNumber(), 0); - let issuerBalance = new BigNumber(await I_PolyToken.balanceOf(wallet)); - await I_ERC20DividendCheckpoint.withdrawWithholding(3, { from: token_owner, gasPrice: 0 }); - let issuerBalanceAfter = new BigNumber(await I_PolyToken.balanceOf(wallet)); - assert.equal(issuerBalanceAfter.sub(issuerBalance).toNumber(), web3.utils.toWei("2.2", "ether")); + console.log("totalWithheld: " + dividend[8].toString()); + console.log("totalWithheldWithdrawn: " + dividend[9].toString()); + assert.equal(dividend[8].toString(), web3.utils.toWei("2.2", "ether")); + assert.equal(dividend[9].toString(), 0); + let issuerBalance = new BN(await I_PolyToken.balanceOf(wallet)); + await I_ERC20DividendCheckpoint.withdrawWithholding(new BN(3), { from: token_owner, gasPrice: 0 }); + let issuerBalanceAfter = new BN(await I_PolyToken.balanceOf(wallet)); + assert.equal(issuerBalanceAfter.sub(issuerBalance).toString(), web3.utils.toWei("2.2", "ether")); dividend = await I_ERC20DividendCheckpoint.dividends.call(3); - console.log("totalWithheld: " + dividend[8].toNumber()); - console.log("totalWithheldWithdrawn: " + dividend[9].toNumber()); - assert.equal(dividend[8].toNumber(), web3.utils.toWei("2.2", "ether")); - assert.equal(dividend[9].toNumber(), web3.utils.toWei("2.2", "ether")); + console.log("totalWithheld: " + dividend[8].toString()); + console.log("totalWithheldWithdrawn: " + dividend[9].toString()); + assert.equal(dividend[8].toString(), web3.utils.toWei("2.2", "ether")); + assert.equal(dividend[9].toString(), web3.utils.toWei("2.2", "ether")); }); it("Issuer changes wallet address", async () => { @@ -969,10 +983,10 @@ contract("ERC20DividendCheckpoint", accounts => { }); it("Issuer is able to reclaim dividend after expiry", async () => { - let tokenOwnerBalance = new BigNumber(await I_PolyToken.balanceOf(wallet)); + let tokenOwnerBalance = new BN(await I_PolyToken.balanceOf(wallet)); await I_ERC20DividendCheckpoint.reclaimDividend(3, { from: token_owner, gasPrice: 0 }); - let tokenOwnerAfter = new BigNumber(await I_PolyToken.balanceOf(wallet)); - assert.equal(tokenOwnerAfter.sub(tokenOwnerBalance).toNumber(), web3.utils.toWei("7", "ether")); + let tokenOwnerAfter = new BN(await I_PolyToken.balanceOf(wallet)); + assert.equal(tokenOwnerAfter.sub(tokenOwnerBalance).toString(), new BN(web3.utils.toWei("7", "ether")).toString()); }); it("Issuer is unable to reclaim already reclaimed dividend", async () => { @@ -999,8 +1013,8 @@ contract("ERC20DividendCheckpoint", accounts => { }); it("should register a delegate", async () => { - [I_GeneralPermissionManagerFactory] = await deployGPMAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, 0); - let tx = await I_SecurityToken.addModule(I_GeneralPermissionManagerFactory.address, "0x", 0, 0, { from: token_owner }); + [I_GeneralPermissionManagerFactory] = await deployGPMAndVerifyed(account_polymath, I_MRProxied, 0); + let tx = await I_SecurityToken.addModule(I_GeneralPermissionManagerFactory.address, "0x", new BN(0), new BN(0), false, { from: token_owner }); assert.equal(tx.logs[2].args._types[0].toNumber(), delegateManagerKey, "General Permission Manager doesn't get deployed"); assert.equal( web3.utils.toAscii(tx.logs[2].args._name).replace(/\u0000/g, ""), @@ -1008,78 +1022,73 @@ contract("ERC20DividendCheckpoint", accounts => { "GeneralPermissionManagerFactory module was not added" ); I_GeneralPermissionManager = await GeneralPermissionManager.at(tx.logs[2].args._module); - tx = await I_GeneralPermissionManager.addDelegate(account_manager, managerDetails, { from: token_owner}); + tx = await I_GeneralPermissionManager.addDelegate(account_manager, managerDetails, { from: token_owner }); assert.equal(tx.logs[0].args._delegate, account_manager); }); it("should not allow manager without permission to set default excluded", async () => { - await catchRevert(I_ERC20DividendCheckpoint.setDefaultExcluded( - [0], - { from: account_manager } - )); + await catchRevert(I_ERC20DividendCheckpoint.setDefaultExcluded([address_zero], { from: account_manager })); }); it("should not allow manager without permission to set withholding", async () => { - await catchRevert(I_ERC20DividendCheckpoint.setWithholding( - [0], - [0], - { from: account_manager } - )); + await catchRevert(I_ERC20DividendCheckpoint.setWithholding([address_zero], [new BN(0)], { from: account_manager })); }); it("should not allow manager without permission to set withholding fixed", async () => { - await catchRevert(I_ERC20DividendCheckpoint.setWithholdingFixed( - [0], - 0, - { from: account_manager } - )); + await catchRevert(I_ERC20DividendCheckpoint.setWithholdingFixed([address_zero], new BN(0), { from: account_manager })); }); it("should not allow manager without permission to create dividend", async () => { - await I_PolyToken.transfer(account_manager, web3.utils.toWei("100", "ether"), { from: token_owner }); - await I_PolyToken.approve(I_ERC20DividendCheckpoint.address, web3.utils.toWei("100", "ether"), { from: account_manager }); - let maturity = latestTime() + duration.days(1); - let expiry = latestTime() + duration.days(10); + await I_PolyToken.transfer(account_manager, new BN(web3.utils.toWei("100", "ether")), { from: token_owner }); + await I_PolyToken.approve(I_ERC20DividendCheckpoint.address, new BN(web3.utils.toWei("100", "ether")), { from: account_manager }); + let maturity = await latestTime() + duration.days(1); + let expiry = await latestTime() + duration.days(10); - await catchRevert(I_ERC20DividendCheckpoint.createDividend( - maturity, - expiry, - I_PolyToken.address, - web3.utils.toWei("1.5", "ether"), - dividendName, - { from: account_manager } - )); + await catchRevert( + I_ERC20DividendCheckpoint.createDividend( + maturity, + expiry, + I_PolyToken.address, + new BN(web3.utils.toWei("1.5", "ether")), + dividendName, + { from: account_manager } + ) + ); }); it("should not allow manager without permission to create dividend with checkpoint", async () => { - let maturity = latestTime() + duration.days(1); - let expiry = latestTime() + duration.days(10); + let maturity = await latestTime() + duration.days(1); + let expiry = await latestTime() + duration.days(10); let checkpointID = await I_SecurityToken.createCheckpoint.call({ from: token_owner }); await I_SecurityToken.createCheckpoint({ from: token_owner }); - await catchRevert(I_ERC20DividendCheckpoint.createDividendWithCheckpoint( - maturity, - expiry, - I_PolyToken.address, - web3.utils.toWei("1.5", "ether"), - checkpointID.toNumber(), - dividendName, - { from: account_manager } - )); + await catchRevert( + I_ERC20DividendCheckpoint.createDividendWithCheckpoint( + maturity, + expiry, + I_PolyToken.address, + new BN(web3.utils.toWei("1.5", "ether")), + checkpointID.toNumber(), + dividendName, + { from: account_manager } + ) + ); }); it("should not allow manager without permission to create dividend with exclusion", async () => { - let maturity = latestTime() + duration.days(1); - let expiry = latestTime() + duration.days(10); - let exclusions = [1]; - await catchRevert(I_ERC20DividendCheckpoint.createDividendWithExclusions( - maturity, - expiry, - I_PolyToken.address, - web3.utils.toWei("1.5", "ether"), - exclusions, - dividendName, - { from: account_manager } - )); + let maturity = await latestTime() + duration.days(1); + let expiry = await latestTime() + duration.days(10); + let exclusions = [one_address]; + await catchRevert( + I_ERC20DividendCheckpoint.createDividendWithExclusions( + maturity, + expiry, + I_PolyToken.address, + new BN(web3.utils.toWei("1.5", "ether")), + exclusions, + dividendName, + { from: account_manager } + ) + ); }); it("should not allow manager without permission to create checkpoint", async () => { @@ -1087,76 +1096,59 @@ contract("ERC20DividendCheckpoint", accounts => { }); it("should not allow manager without permission to create dividend with checkpoint and exclusion", async () => { - let maturity = latestTime() + duration.days(1); - let expiry = latestTime() + duration.days(10); - let exclusions = [1]; + let maturity = await latestTime() + duration.days(1); + let expiry = await latestTime() + duration.days(10); + let exclusions = [one_address]; let checkpointID = await I_SecurityToken.createCheckpoint.call({ from: token_owner }); await I_SecurityToken.createCheckpoint({ from: token_owner }); - await catchRevert(I_ERC20DividendCheckpoint.createDividendWithCheckpointAndExclusions( - maturity, - expiry, - I_PolyToken.address, - web3.utils.toWei("1.5", "ether"), - checkpointID.toNumber(), - exclusions, - dividendName, - { from: account_manager } - )); + await catchRevert( + I_ERC20DividendCheckpoint.createDividendWithCheckpointAndExclusions( + maturity, + expiry, + I_PolyToken.address, + new BN(web3.utils.toWei("1.5", "ether")), + checkpointID.toNumber(), + exclusions, + dividendName, + { from: account_manager } + ) + ); }); it("should give permission to manager", async () => { - await I_GeneralPermissionManager.changePermission( - account_manager, - I_ERC20DividendCheckpoint.address, - "CHECKPOINT", - true, - { from: token_owner } - ); - let tx = await I_GeneralPermissionManager.changePermission( - account_manager, - I_ERC20DividendCheckpoint.address, - "MANAGE", - true, - { from: token_owner } - ); + await I_GeneralPermissionManager.changePermission(account_manager, I_ERC20DividendCheckpoint.address, web3.utils.fromAscii("OPERATOR"), true, { + from: token_owner + }); + let tx = await I_GeneralPermissionManager.changePermission(account_manager, I_ERC20DividendCheckpoint.address, web3.utils.fromAscii("ADMIN"), true, { + from: token_owner + }); assert.equal(tx.logs[0].args._delegate, account_manager); }); it("should allow manager with permission to set default excluded", async () => { - let tx = await I_ERC20DividendCheckpoint.setDefaultExcluded( - [1], - { from: account_manager } - ); + let tx = await I_ERC20DividendCheckpoint.setDefaultExcluded([one_address], { from: account_manager }); assert.equal(tx.logs[0].args._excluded[0], one_address); }); it("should allow manager with permission to set withholding", async () => { - let tx = await I_ERC20DividendCheckpoint.setWithholding( - [0], - [0], - { from: account_manager } - ); + let tx = await I_ERC20DividendCheckpoint.setWithholding([one_address], [new BN(0)], { from: account_manager }); assert.equal(tx.logs[0].args._withholding[0], 0); }); it("should allow manager withpermission to set withholding fixed", async () => { - let tx = await I_ERC20DividendCheckpoint.setWithholdingFixed( - [0], - 0, - { from: account_manager } - ); + let tx = await I_ERC20DividendCheckpoint.setWithholdingFixed([one_address], new BN(0), { from: account_manager }); assert.equal(tx.logs[0].args._withholding, 0); }); it("should allow manager with permission to create dividend", async () => { - let maturity = latestTime() + duration.days(1); - let expiry = latestTime() + duration.days(10); + let maturity = await latestTime() + duration.days(1); + let expiry = await latestTime() + duration.days(10); let tx = await I_ERC20DividendCheckpoint.createDividend( maturity, expiry, I_PolyToken.address, - web3.utils.toWei("1.5", "ether"), + new BN(web3.utils.toWei("1.5", "ether")), dividendName, { from: account_manager } ); @@ -1164,15 +1156,15 @@ contract("ERC20DividendCheckpoint", accounts => { }); it("should allow manager with permission to create dividend with checkpoint", async () => { - let maturity = latestTime() + duration.days(1); - let expiry = latestTime() + duration.days(10); + let maturity = await latestTime() + duration.days(1); + let expiry = await latestTime() + duration.days(10); let checkpointID = await I_SecurityToken.createCheckpoint.call({ from: token_owner }); await I_SecurityToken.createCheckpoint({ from: token_owner }); let tx = await I_ERC20DividendCheckpoint.createDividendWithCheckpoint( maturity, expiry, I_PolyToken.address, - web3.utils.toWei("1.5", "ether"), + new BN(web3.utils.toWei("1.5", "ether")), checkpointID.toNumber(), dividendName, { from: account_manager } @@ -1183,32 +1175,26 @@ contract("ERC20DividendCheckpoint", accounts => { assert.equal(info[0][1], account_investor2, "account match"); assert.equal(info[0][2], account_temp, "account match"); assert.equal(info[0][3], account_investor3, "account match"); - assert.equal(info[1][0].toNumber(), (await I_SecurityToken.balanceOfAt.call(account_investor1, checkpointID)).toNumber(), "balance match"); - assert.equal(info[1][1].toNumber(), (await I_SecurityToken.balanceOfAt.call(account_investor2, checkpointID)).toNumber(), "balance match"); - assert.equal(info[1][2].toNumber(), (await I_SecurityToken.balanceOfAt.call(account_temp, checkpointID)).toNumber(), "balance match"); - assert.equal(info[1][3].toNumber(), (await I_SecurityToken.balanceOfAt.call(account_investor3, checkpointID)).toNumber(), "balance match"); + assert.equal(info[1][0].toString(), (await stGetter.balanceOfAt.call(account_investor1, checkpointID)).toString(), "balance match"); + assert.equal(info[1][1].toString(), (await stGetter.balanceOfAt.call(account_investor2, checkpointID)).toString(), "balance match"); + assert.equal(info[1][2].toString(), (await stGetter.balanceOfAt.call(account_temp, checkpointID)).toString(), "balance match"); + assert.equal(info[1][3].toString(), (await stGetter.balanceOfAt.call(account_investor3, checkpointID)).toString(), "balance match"); assert.equal(info[2][0].toNumber(), 0, "withholding match"); - assert.equal(info[2][1].toNumber(), BigNumber(100 * 10 ** 16).toNumber(), "withholding match"); - assert.equal(info[2][2].toNumber(), BigNumber(20 * 10 ** 16).toNumber(), "withholding match"); + assert.equal(info[2][1].toString(), new BN((100 * 10 ** 16).toString()).toString(), "withholding match"); + assert.equal(info[2][2].toString(), new BN((20 * 10 ** 16).toString()).toString(), "withholding match"); assert.equal(info[2][3].toNumber(), 0, "withholding match"); assert.equal(tx.logs[0].args._checkpointId.toNumber(), checkpointID); }); it("should allow manager with permission to create dividend with exclusion", async () => { - let maturity = latestTime() + duration.days(1); - let expiry = latestTime() + duration.days(10); - - let limit = await I_ERC20DividendCheckpoint.EXCLUDED_ADDRESS_LIMIT(); - limit = limit.toNumber(); - let exclusions = []; - exclusions.push(account_temp); - while (--limit) exclusions.push(limit); - console.log(exclusions.length); + let maturity = (await latestTime()) + duration.days(1); + let expiry = (await latestTime()) + duration.days(10); + let exclusions = [account_temp]; let tx = await I_ERC20DividendCheckpoint.createDividendWithExclusions( maturity, expiry, I_PolyToken.address, - web3.utils.toWei("1.5", "ether"), + new BN(web3.utils.toWei("1.5", "ether")), exclusions, dividendName, { from: account_manager } @@ -1221,46 +1207,63 @@ contract("ERC20DividendCheckpoint", accounts => { }); it("should allow manager with permission to create dividend with checkpoint and exclusion", async () => { - let maturity = latestTime() + duration.days(1); - let expiry = latestTime() + duration.days(10); - let exclusions = [1]; + let maturity = await latestTime() + duration.days(1); + let expiry = await latestTime() + duration.days(10); + let exclusions = [one_address]; let checkpointID = await I_SecurityToken.createCheckpoint.call({ from: token_owner }); await I_SecurityToken.createCheckpoint({ from: token_owner }); let tx = await I_ERC20DividendCheckpoint.createDividendWithCheckpointAndExclusions( maturity, expiry, I_PolyToken.address, - web3.utils.toWei("1.5", "ether"), + new BN(web3.utils.toWei("1.5", "ether")), checkpointID.toNumber(), exclusions, dividendName, { from: account_manager } ); assert.equal(tx.logs[0].args._checkpointId.toNumber(), 10); + console.log(tx.logs[0].args._dividendIndex); + }); + + it("Should fail to update the dividend dates because msg.sender is not authorised", async () => { + // failed because msg.sender is not the owner + await catchRevert( + I_ERC20DividendCheckpoint.updateDividendDates(7, 0, 1, {from: account_polymath}) + ); }); - it("Update maturity and expiry dates on dividend", async () => { - await catchRevert(I_ERC20DividendCheckpoint.updateDividendDates(7, 0, 1, {from: account_polymath})); - let tx = await I_ERC20DividendCheckpoint.updateDividendDates(7, 0, 1, {from: token_owner}); + it("Should fail to update the dates when the dividend get expired", async() => { + let id = await takeSnapshot(); + await increaseTime(duration.days(11)); + await catchRevert( + I_ERC20DividendCheckpoint.updateDividendDates(7, 0, 1, {from: token_owner}) + ); + await revertToSnapshot(id); + }); + + it("Should update the dividend dates", async() => { + let newMaturity = await latestTime() - duration.days(4); + let newExpiry = await latestTime() - duration.days(2); + let tx = await I_ERC20DividendCheckpoint.updateDividendDates(7, newMaturity, newExpiry, {from: token_owner}); let info = await I_ERC20DividendCheckpoint.getDividendData.call(7); - assert.equal(info[1].toNumber(), 0); - assert.equal(info[2].toNumber(), 1); + assert.equal(info[1].toNumber(), newMaturity); + assert.equal(info[2].toNumber(), newExpiry); // Can now reclaim the dividend await I_ERC20DividendCheckpoint.reclaimDividend(7, {from: token_owner}); }); - it("Reclaim ERC20 tokens from the dividend contract", async () => { - let currentDividendBalance = BigNumber(await I_PolyToken.balanceOf.call(I_ERC20DividendCheckpoint.address)); - let currentIssuerBalance = BigNumber(await I_PolyToken.balanceOf.call(token_owner)); + it("Reclaim ERC20 tokens from the dividend contract", async () => { + let currentDividendBalance = await I_PolyToken.balanceOf.call(I_ERC20DividendCheckpoint.address); + let currentIssuerBalance = await I_PolyToken.balanceOf.call(token_owner); await catchRevert(I_ERC20DividendCheckpoint.reclaimERC20(I_PolyToken.address, {from: account_polymath})); let tx = await I_ERC20DividendCheckpoint.reclaimERC20(I_PolyToken.address, {from: token_owner}); assert.equal(await I_PolyToken.balanceOf.call(I_ERC20DividendCheckpoint.address), 0); - let newIssuerBalance = BigNumber(await I_PolyToken.balanceOf.call(token_owner)); - console.log("Reclaimed: " + currentDividendBalance.toNumber()); - assert.equal(newIssuerBalance.sub(currentIssuerBalance).toNumber(), currentDividendBalance.toNumber()); + let newIssuerBalance = await I_PolyToken.balanceOf.call(token_owner); + console.log("Reclaimed: " + currentDividendBalance.toString()); + assert.equal(newIssuerBalance.sub(currentIssuerBalance).toString(), currentDividendBalance.toString()); }); - it("should allow manager with permission to create checkpoint", async () => { let initCheckpointID = await I_SecurityToken.createCheckpoint.call({ from: token_owner }); await I_ERC20DividendCheckpoint.createCheckpoint({ from: account_manager }); @@ -1270,11 +1273,11 @@ contract("ERC20DividendCheckpoint", accounts => { describe("Test cases for the ERC20DividendCheckpointFactory", async () => { it("should get the exact details of the factory", async () => { - assert.equal((await I_ERC20DividendCheckpointFactory.getSetupCost.call()).toNumber(), 0); + assert.equal((await I_ERC20DividendCheckpointFactory.setupCost.call()).toNumber(), 0); assert.equal((await I_ERC20DividendCheckpointFactory.getTypes.call())[0], 4); - assert.equal(await I_ERC20DividendCheckpointFactory.version.call(), "2.1.1"); + assert.equal(await I_ERC20DividendCheckpointFactory.version.call(), "3.0.0"); assert.equal( - web3.utils.toAscii(await I_ERC20DividendCheckpointFactory.getName.call()).replace(/\u0000/g, ""), + web3.utils.toAscii(await I_ERC20DividendCheckpointFactory.name.call()).replace(/\u0000/g, ""), "ERC20DividendCheckpoint", "Wrong Module added" ); @@ -1284,11 +1287,6 @@ contract("ERC20DividendCheckpoint", accounts => { "Wrong Module added" ); assert.equal(await I_ERC20DividendCheckpointFactory.title.call(), "ERC20 Dividend Checkpoint", "Wrong Module added"); - assert.equal( - await I_ERC20DividendCheckpointFactory.getInstructions.call(), - "Create ERC20 dividend to be paid out to token holders based on their balances at dividend creation time", - "Wrong Module added" - ); let tags = await I_ERC20DividendCheckpointFactory.getTags.call(); assert.equal(tags.length, 3); }); diff --git a/test/f_ether_dividends.js b/test/f_ether_dividends.js index 1f7646ce6..374092bd8 100644 --- a/test/f_ether_dividends.js +++ b/test/f_ether_dividends.js @@ -1,6 +1,6 @@ import latestTime from "./helpers/latestTime"; import { duration, ensureException, promisifyLogWatch, latestBlock } from "./helpers/utils"; -import takeSnapshot, { increaseTime, revertToSnapshot } from "./helpers/time"; +import { takeSnapshot, increaseTime, revertToSnapshot } from "./helpers/time"; import { encodeProxyCall } from "./helpers/encodeCall"; import { catchRevert } from "./helpers/exceptions"; import { setUpPolymathNetwork, deployEtherDividendAndVerifyed, deployGPMAndVerifyed } from "./helpers/createInstances"; @@ -10,12 +10,13 @@ const SecurityToken = artifacts.require("./SecurityToken.sol"); const GeneralTransferManager = artifacts.require("./GeneralTransferManager"); const EtherDividendCheckpoint = artifacts.require("./EtherDividendCheckpoint"); const GeneralPermissionManager = artifacts.require("GeneralPermissionManager"); +const STGetter = artifacts.require("./STGetter.sol"); const Web3 = require("web3"); -const BigNumber = require("bignumber.js"); +let BN = Web3.utils.BN; const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); // Hardcoded development port -contract("EtherDividendCheckpoint", accounts => { +contract("EtherDividendCheckpoint", async (accounts) => { // Accounts Variable declaration let account_polymath; let account_issuer; @@ -28,13 +29,10 @@ contract("EtherDividendCheckpoint", accounts => { let account_manager; let account_temp; - // investor Details - let fromTime = latestTime(); - let toTime = latestTime(); - let expiryTime = toTime + duration.days(15); - let message = "Transaction Should Fail!"; let dividendName = "0x546573744469766964656e640000000000000000000000000000000000000000"; + const address_zero = "0x0000000000000000000000000000000000000000"; + const one_address = "0x0000000000000000000000000000000000000001"; // Contract Instance Declaration let I_SecurityTokenRegistryProxy; @@ -56,6 +54,9 @@ contract("EtherDividendCheckpoint", accounts => { let I_PolyToken; let I_MRProxied; let I_PolymathRegistry; + let I_STRGetter; + let I_STGetter; + let stGetter; // SecurityToken Details const name = "Team"; @@ -63,7 +64,7 @@ contract("EtherDividendCheckpoint", accounts => { const tokenDetails = "This is equity type of issuance"; const decimals = 18; const contact = "team@polymath.network"; - const managerDetails = "Hello, I am a legit manager"; + const managerDetails = web3.utils.fromAscii("Hello"); let snapId; // Module key const delegateManagerKey = 1; @@ -73,10 +74,12 @@ contract("EtherDividendCheckpoint", accounts => { const DividendParameters = ["address"]; // Initial fee for ticker registry and security token registry - const initRegFee = web3.utils.toWei("250"); + const initRegFee = new BN(web3.utils.toWei("1000")); + + let currentTime; before(async () => { - // Accounts setup + currentTime = new BN(await latestTime()); account_polymath = accounts[0]; account_issuer = accounts[1]; @@ -104,11 +107,13 @@ contract("EtherDividendCheckpoint", accounts => { I_STFactory, I_SecurityTokenRegistry, I_SecurityTokenRegistryProxy, - I_STRProxied + I_STRProxied, + I_STRGetter, + I_STGetter ] = instances; - [P_EtherDividendCheckpointFactory] = await deployEtherDividendAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, web3.utils.toWei("500", "ether")); - [I_EtherDividendCheckpointFactory] = await deployEtherDividendAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, 0); + [P_EtherDividendCheckpointFactory] = await deployEtherDividendAndVerifyed(account_polymath, I_MRProxied, web3.utils.toWei("500", "ether")); + [I_EtherDividendCheckpointFactory] = await deployEtherDividendAndVerifyed(account_polymath, I_MRProxied, 0); // Printing all the contract addresses console.log(` @@ -131,38 +136,40 @@ contract("EtherDividendCheckpoint", accounts => { describe("Generate the SecurityToken", async () => { it("Should register the ticker before the generation of the security token", async () => { await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); - let tx = await I_STRProxied.registerTicker(token_owner, symbol, contact, { from: token_owner }); + let tx = await I_STRProxied.registerNewTicker(token_owner, symbol, { from: token_owner }); assert.equal(tx.logs[0].args._owner, token_owner); assert.equal(tx.logs[0].args._ticker, symbol.toUpperCase()); }); it("Should generate the new security token with the same symbol as registered above", async () => { await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); - let _blockNo = latestBlock(); - let tx = await I_STRProxied.generateSecurityToken(name, symbol, tokenDetails, false, { from: token_owner }); + + let tx = await I_STRProxied.generateNewSecurityToken(name, symbol, tokenDetails, false, wallet, 0, { from: token_owner }); // 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); + I_SecurityToken = await SecurityToken.at(tx.logs[1].args._securityTokenAddress); + stGetter = await STGetter.at(I_SecurityToken.address); + assert.equal(await stGetter.getTreasuryWallet.call(), wallet, "Incorrect wallet set") - const log = await promisifyLogWatch(I_SecurityToken.ModuleAdded({ from: _blockNo }), 1); + const log = (await I_SecurityToken.getPastEvents('ModuleAdded', {filter: {transactionHash: tx.transactionHash}}))[0]; // Verify that GeneralTransferManager module get added successfully or not assert.equal(log.args._types[0].toNumber(), 2); assert.equal(web3.utils.toAscii(log.args._name).replace(/\u0000/g, ""), "GeneralTransferManager"); }); - it("Should intialize the auto attached modules", async () => { - let moduleData = (await I_SecurityToken.getModulesByType(2))[0]; - I_GeneralTransferManager = GeneralTransferManager.at(moduleData); + it("Should initialize the auto attached modules", async () => { + let moduleData = (await stGetter.getModulesByType(2))[0]; + I_GeneralTransferManager = await GeneralTransferManager.at(moduleData); }); it("Should successfully attach the ERC20DividendCheckpoint with the security token", async () => { - await I_PolyToken.getTokens(web3.utils.toWei("500", "ether"), token_owner); + await I_PolyToken.getTokens(new BN(web3.utils.toWei("2000", "ether")), token_owner); let bytesDividend = encodeModuleCall(DividendParameters, [wallet]); await catchRevert( - I_SecurityToken.addModule(P_EtherDividendCheckpointFactory.address, bytesDividend, web3.utils.toWei("500", "ether"), 0, { + I_SecurityToken.addModule(P_EtherDividendCheckpointFactory.address, bytesDividend, new BN(web3.utils.toWei("2000", "ether")), new BN(0), false, { from: token_owner }) ); @@ -170,9 +177,9 @@ contract("EtherDividendCheckpoint", accounts => { it("Should successfully attach the EtherDividendCheckpoint with the security token", async () => { let snapId = await takeSnapshot(); - await I_PolyToken.transfer(I_SecurityToken.address, web3.utils.toWei("500", "ether"), { from: token_owner }); + await I_PolyToken.transfer(I_SecurityToken.address, new BN(web3.utils.toWei("2000", "ether")), { from: token_owner }); let bytesDividend = encodeModuleCall(DividendParameters, [wallet]); - const tx = await I_SecurityToken.addModule(P_EtherDividendCheckpointFactory.address, bytesDividend, web3.utils.toWei("500", "ether"), 0, { + const tx = await I_SecurityToken.addModule(P_EtherDividendCheckpointFactory.address, bytesDividend, new BN(web3.utils.toWei("2000", "ether")), new BN(0), false, { from: token_owner }); assert.equal(tx.logs[3].args._types[0].toNumber(), checkpointKey, "EtherDividendCheckpoint doesn't get deployed"); @@ -181,20 +188,20 @@ contract("EtherDividendCheckpoint", accounts => { "EtherDividendCheckpoint", "EtherDividendCheckpoint module was not added" ); - P_EtherDividendCheckpoint = EtherDividendCheckpoint.at(tx.logs[3].args._module); + P_EtherDividendCheckpoint = await EtherDividendCheckpoint.at(tx.logs[3].args._module); await revertToSnapshot(snapId); }); it("Should successfully attach the EtherDividendCheckpoint with the security token", async () => { - let bytesDividend = encodeModuleCall(DividendParameters, [wallet]); - const tx = await I_SecurityToken.addModule(I_EtherDividendCheckpointFactory.address, bytesDividend, 0, 0, { from: token_owner }); + let bytesDividend = encodeModuleCall(DividendParameters, [address_zero]); + const tx = await I_SecurityToken.addModule(I_EtherDividendCheckpointFactory.address, bytesDividend, new BN(0), new BN(0), false, { from: token_owner }); assert.equal(tx.logs[2].args._types[0].toNumber(), checkpointKey, "EtherDividendCheckpoint doesn't get deployed"); assert.equal( web3.utils.toAscii(tx.logs[2].args._name).replace(/\u0000/g, ""), "EtherDividendCheckpoint", "EtherDividendCheckpoint module was not added" ); - I_EtherDividendCheckpoint = EtherDividendCheckpoint.at(tx.logs[2].args._module); + I_EtherDividendCheckpoint = await EtherDividendCheckpoint.at(tx.logs[2].args._module); }); }); @@ -202,12 +209,11 @@ contract("EtherDividendCheckpoint", accounts => { it("Buy some tokens for account_investor1 (1 ETH)", async () => { // Add the Investor in to the whitelist - let tx = await I_GeneralTransferManager.modifyWhitelist( + let tx = await I_GeneralTransferManager.modifyKYCData( account_investor1, - latestTime(), - latestTime(), - latestTime() + duration.days(30), - true, + currentTime, + currentTime, + currentTime.add(new BN(duration.days(300000))), { from: account_issuer, gas: 500000 @@ -224,20 +230,19 @@ contract("EtherDividendCheckpoint", accounts => { await increaseTime(5000); // Mint some tokens - await I_SecurityToken.mint(account_investor1, web3.utils.toWei("1", "ether"), { from: token_owner }); + await I_SecurityToken.issue(account_investor1, new BN(web3.utils.toWei("1", "ether")), "0x0", { from: token_owner }); - assert.equal((await I_SecurityToken.balanceOf(account_investor1)).toNumber(), web3.utils.toWei("1", "ether")); + assert.equal((await I_SecurityToken.balanceOf(account_investor1)).toString(), new BN(web3.utils.toWei("1", "ether")).toString()); }); it("Buy some tokens for account_investor2 (2 ETH)", async () => { // Add the Investor in to the whitelist - let tx = await I_GeneralTransferManager.modifyWhitelist( + let tx = await I_GeneralTransferManager.modifyKYCData( account_investor2, - latestTime(), - latestTime(), - latestTime() + duration.days(30), - true, + currentTime, + currentTime, + currentTime.add(new BN(duration.days(3000000))), { from: account_issuer, gas: 500000 @@ -251,95 +256,96 @@ contract("EtherDividendCheckpoint", accounts => { ); // Mint some tokens - await I_SecurityToken.mint(account_investor2, web3.utils.toWei("2", "ether"), { from: token_owner }); + await I_SecurityToken.issue(account_investor2, new BN(web3.utils.toWei("2", "ether")), "0x0", { from: token_owner }); - assert.equal((await I_SecurityToken.balanceOf(account_investor2)).toNumber(), web3.utils.toWei("2", "ether")); + assert.equal((await I_SecurityToken.balanceOf(account_investor2)).toString(), new BN(web3.utils.toWei("2", "ether")).toString()); }); it("Should fail in creating the dividend", async () => { - let maturity = latestTime(); - let expiry = latestTime() + duration.days(10); + let maturity = await latestTime(); + let expiry = await latestTime() + duration.days(10); await catchRevert(I_EtherDividendCheckpoint.createDividend(maturity, expiry, dividendName, { from: token_owner })); }); it("Should fail in creating the dividend", async () => { - let maturity = latestTime(); - let expiry = latestTime() - duration.days(10); + let maturity = await latestTime(); + let expiry = await latestTime() - duration.days(10); await catchRevert( I_EtherDividendCheckpoint.createDividend(maturity, expiry, dividendName, { from: token_owner, - value: web3.utils.toWei("1.5", "ether") + value: new BN(web3.utils.toWei("1.5", "ether")) }) ); }); it("Should fail in creating the dividend", async () => { - let maturity = latestTime() - duration.days(2); - let expiry = latestTime() - duration.days(1); + let maturity = await latestTime() - duration.days(2); + let expiry = await latestTime() - duration.days(1); await catchRevert( I_EtherDividendCheckpoint.createDividend(maturity, expiry, dividendName, { from: token_owner, - value: web3.utils.toWei("1.5", "ether") + value: new BN(web3.utils.toWei("1.5", "ether")) }) ); }); it("Set withholding tax of 20% on investor 2", async () => { - await I_EtherDividendCheckpoint.setWithholdingFixed([account_investor2], BigNumber(20 * 10 ** 16), { from: token_owner }); + await I_EtherDividendCheckpoint.setWithholdingFixed([account_investor2], new BN(web3.utils.toWei("0.2", "ether")), { from: token_owner }); }); it("Should fail in creating the dividend", async () => { - let maturity = latestTime() + duration.days(1); - let expiry = latestTime() + duration.days(10); + let maturity = await latestTime() + duration.days(1); + let expiry = await latestTime() + duration.days(10); await catchRevert( - I_EtherDividendCheckpoint.createDividend(maturity, expiry, "", { + I_EtherDividendCheckpoint.createDividend(maturity, expiry, "0x0", { from: token_owner, - value: web3.utils.toWei("1.5", "ether") + value: new BN(web3.utils.toWei("1.5", "ether")) }) ); }); it("Create new dividend", async () => { - let maturity = latestTime() + duration.days(1); - let expiry = latestTime() + duration.days(10); + let maturity = await latestTime() + duration.days(1); + let expiry = await latestTime() + duration.days(10); let tx = await I_EtherDividendCheckpoint.createDividend(maturity, expiry, dividendName, { from: token_owner, - value: web3.utils.toWei("1.5", "ether") + value: new BN(web3.utils.toWei("1.5", "ether")) }); assert.equal(tx.logs[0].args._checkpointId.toNumber(), 1, "Dividend should be created at checkpoint 1"); assert.equal(tx.logs[0].args._name.toString(), dividendName, "Dividend name incorrect in event"); + console.log("Dividend first :" + tx.logs[0].args._dividendIndex.toNumber()); }); 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 }); + await I_SecurityToken.transfer(account_investor2, new BN(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")); + assert.equal((await I_SecurityToken.balanceOf(account_investor2)).toString(), new BN(web3.utils.toWei("3", "ether")).toString()); }); it("Issuer pushes dividends iterating over account holders - dividends proportional to checkpoint", async () => { - await catchRevert(I_EtherDividendCheckpoint.pushDividendPayment(0, 0, 10, { from: token_owner })); + await catchRevert(I_EtherDividendCheckpoint.pushDividendPayment(0, new BN(0), 10, { from: token_owner })); }); it("Issuer pushes dividends iterating over account holders - dividends proportional to checkpoint", async () => { // Increase time by 2 day await increaseTime(duration.days(2)); - await catchRevert(I_EtherDividendCheckpoint.pushDividendPayment(0, 0, 10, { from: account_temp })); + await catchRevert(I_EtherDividendCheckpoint.pushDividendPayment(0, new BN(0), 10, { from: account_temp })); }); it("Issuer pushes dividends iterating over account holders - dividends proportional to checkpoint", async () => { - await catchRevert(I_EtherDividendCheckpoint.pushDividendPayment(2, 0, 10, { from: token_owner })); + await catchRevert(I_EtherDividendCheckpoint.pushDividendPayment(2, new BN(0), 10, { from: token_owner })); }); it("Issuer pushes dividends iterating over account holders - dividends proportional to checkpoint", async () => { - let investor1Balance = new BigNumber(await web3.eth.getBalance(account_investor1)); - let investor2Balance = new BigNumber(await web3.eth.getBalance(account_investor2)); - await I_EtherDividendCheckpoint.pushDividendPayment(0, 0, 10, { from: token_owner }); - let investor1BalanceAfter = new BigNumber(await web3.eth.getBalance(account_investor1)); - let investor2BalanceAfter = new 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("0.8", "ether")); + let investor1Balance = new BN(await web3.eth.getBalance(account_investor1)); + let investor2Balance = new BN(await web3.eth.getBalance(account_investor2)); + await I_EtherDividendCheckpoint.pushDividendPayment(0, new BN(0), 10, { from: token_owner }); + let investor1BalanceAfter = new BN(await web3.eth.getBalance(account_investor1)); + let investor2BalanceAfter = new BN(await web3.eth.getBalance(account_investor2)); + assert.equal(investor1BalanceAfter.sub(investor1Balance).toString(), new BN(web3.utils.toWei("0.5", "ether")).toString()); + assert.equal(investor2BalanceAfter.sub(investor2Balance).toString(), new BN(web3.utils.toWei("0.8", "ether")).toString()); //Check fully claimed - assert.equal((await I_EtherDividendCheckpoint.dividends(0))[5].toNumber(), web3.utils.toWei("1.5", "ether")); + assert.equal((await I_EtherDividendCheckpoint.dividends(0))[5].toString(), new BN(web3.utils.toWei("1.5", "ether")).toString()); }); it("Should not allow reclaiming withholding tax with incorrect index", async () => { @@ -350,32 +356,31 @@ contract("EtherDividendCheckpoint", accounts => { }); it("Issuer reclaims withholding tax", async () => { - let issuerBalance = new BigNumber(await web3.eth.getBalance(wallet)); + let issuerBalance = new BN(await web3.eth.getBalance(wallet)); await I_EtherDividendCheckpoint.withdrawWithholding(0, { from: token_owner, gasPrice: 0 }); - let issuerBalanceAfter = new BigNumber(await web3.eth.getBalance(wallet)); - assert.equal(issuerBalanceAfter.sub(issuerBalance).toNumber(), web3.utils.toWei("0.2", "ether")); + let issuerBalanceAfter = new BN(await web3.eth.getBalance(wallet)); + assert.equal(issuerBalanceAfter.sub(issuerBalance).toString(), new BN(web3.utils.toWei("0.2", "ether")).toString()); }); it("No more withholding tax to withdraw", async () => { - let issuerBalance = new BigNumber(await web3.eth.getBalance(token_owner)); + let issuerBalance = new BN(await web3.eth.getBalance(token_owner)); await I_EtherDividendCheckpoint.withdrawWithholding(0, { from: token_owner, gasPrice: 0 }); - let issuerBalanceAfter = new BigNumber(await web3.eth.getBalance(token_owner)); - assert.equal(issuerBalanceAfter.sub(issuerBalance).toNumber(), web3.utils.toWei("0", "ether")); + let issuerBalanceAfter = new BN(await web3.eth.getBalance(token_owner)); + assert.equal(issuerBalanceAfter.sub(issuerBalance).toString(), new BN(web3.utils.toWei("0", "ether")).toString()); }); it("Set withholding tax of 100% on investor 2", async () => { - await I_EtherDividendCheckpoint.setWithholdingFixed([account_investor2], BigNumber(100 * 10 ** 16), { from: token_owner }); + await I_EtherDividendCheckpoint.setWithholdingFixed([account_investor2], new BN(100).mul(new BN(10).pow(new BN(16))), { from: token_owner }); }); it("Buy some tokens for account_temp (1 ETH)", async () => { // Add the Investor in to the whitelist - let tx = await I_GeneralTransferManager.modifyWhitelist( + let tx = await I_GeneralTransferManager.modifyKYCData( account_temp, - latestTime(), - latestTime(), - latestTime() + duration.days(20), - true, + currentTime, + currentTime, + currentTime.add(new BN(duration.days(200000))), { from: account_issuer, gas: 500000 @@ -385,48 +390,48 @@ contract("EtherDividendCheckpoint", accounts => { 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 }); + await I_SecurityToken.issue(account_temp, new BN(web3.utils.toWei("1", "ether")), "0x0", { from: token_owner }); - assert.equal((await I_SecurityToken.balanceOf(account_temp)).toNumber(), web3.utils.toWei("1", "ether")); + assert.equal((await I_SecurityToken.balanceOf(account_temp)).toString(), new BN(web3.utils.toWei("1", "ether")).toString()); }); it("Create new dividend", async () => { - let maturity = latestTime() + duration.days(1); - let expiry = latestTime() + duration.days(10); + let maturity = await latestTime() + duration.days(1); + let expiry = await latestTime() + duration.days(10); let tx = await I_EtherDividendCheckpoint.createDividend(maturity, expiry, dividendName, { from: token_owner, - value: web3.utils.toWei("1.5", "ether") + value: new BN(web3.utils.toWei("1.5", "ether")) }); assert.equal(tx.logs[0].args._checkpointId.toNumber(), 2, "Dividend should be created at checkpoint 2"); + console.log("Dividend second :" + tx.logs[0].args._dividendIndex.toNumber()); }); it("Issuer pushes dividends fails due to passed expiry", async () => { await increaseTime(duration.days(12)); - await catchRevert(I_EtherDividendCheckpoint.pushDividendPayment(0, 0, 10, { from: token_owner })); + await catchRevert(I_EtherDividendCheckpoint.pushDividendPayment(0, new BN(0), 10, { from: token_owner })); }); it("Issuer reclaims dividend", async () => { 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")); + assert.equal(tx.logs[0].args._claimedAmount.toString(), new BN(web3.utils.toWei("1.5", "ether")).toString()); await catchRevert(I_EtherDividendCheckpoint.reclaimDividend(1, { from: token_owner, gas: 500000 })); }); it("Still no more withholding tax to withdraw", async () => { - let issuerBalance = new BigNumber(await web3.eth.getBalance(token_owner)); + let issuerBalance = new BN(await web3.eth.getBalance(token_owner)); await I_EtherDividendCheckpoint.withdrawWithholding(0, { from: token_owner, gasPrice: 0 }); - let issuerBalanceAfter = new BigNumber(await web3.eth.getBalance(token_owner)); - assert.equal(issuerBalanceAfter.sub(issuerBalance).toNumber(), web3.utils.toWei("0", "ether")); + let issuerBalanceAfter = new BN(await web3.eth.getBalance(token_owner)); + assert.equal(issuerBalanceAfter.sub(issuerBalance).toString(), new BN(web3.utils.toWei("0", "ether")).toString()); }); it("Buy some tokens for account_investor3 (7 ETH)", async () => { // Add the Investor in to the whitelist - let tx = await I_GeneralTransferManager.modifyWhitelist( + let tx = await I_GeneralTransferManager.modifyKYCData( account_investor3, - latestTime(), - latestTime(), - latestTime() + duration.days(10), - true, + currentTime, + currentTime, + currentTime.add(new BN(duration.days(10000))), { from: account_issuer, gas: 500000 @@ -440,46 +445,47 @@ contract("EtherDividendCheckpoint", accounts => { ); // Mint some tokens - await I_SecurityToken.mint(account_investor3, web3.utils.toWei("7", "ether"), { from: token_owner }); + await I_SecurityToken.issue(account_investor3, new BN(web3.utils.toWei("7", "ether")), "0x0", { from: token_owner }); - assert.equal((await I_SecurityToken.balanceOf(account_investor3)).toNumber(), web3.utils.toWei("7", "ether")); + assert.equal((await I_SecurityToken.balanceOf(account_investor3)).toString(), new BN(web3.utils.toWei("7", "ether")).toString()); }); it("Create another new dividend", async () => { - let maturity = latestTime(); - let expiry = latestTime() + duration.days(10); + let maturity = await latestTime(); + let expiry = await latestTime() + duration.days(10); let tx = await I_EtherDividendCheckpoint.createDividend(maturity, expiry, dividendName, { from: token_owner, - value: web3.utils.toWei("11", "ether") + value: new BN(web3.utils.toWei("11", "ether")) }); assert.equal(tx.logs[0].args._checkpointId.toNumber(), 3, "Dividend should be created at checkpoint 3"); + console.log("Dividend third :" + tx.logs[0].args._dividendIndex.toNumber()); }); it("should investor 3 claims dividend - fails bad index", async () => { - let investor1Balance = new BigNumber(await I_PolyToken.balanceOf(account_investor1)); - let investor2Balance = new BigNumber(await I_PolyToken.balanceOf(account_investor2)); - let investor3Balance = new BigNumber(await I_PolyToken.balanceOf(account_investor3)); + let investor1Balance = new BN(await I_PolyToken.balanceOf(account_investor1)); + let investor2Balance = new BN(await I_PolyToken.balanceOf(account_investor2)); + let investor3Balance = new BN(await I_PolyToken.balanceOf(account_investor3)); await catchRevert(I_EtherDividendCheckpoint.pullDividendPayment(5, { from: account_investor3, gasPrice: 0 })); }); it("Should investor 3 claims dividend", async () => { - let investor1Balance = new BigNumber(await web3.eth.getBalance(account_investor1)); - let investor2Balance = new BigNumber(await web3.eth.getBalance(account_investor2)); - let investor3Balance = new BigNumber(await web3.eth.getBalance(account_investor3)); + let investor1Balance = new BN(await web3.eth.getBalance(account_investor1)); + let investor2Balance = new BN(await web3.eth.getBalance(account_investor2)); + let investor3Balance = new BN(await web3.eth.getBalance(account_investor3)); await I_EtherDividendCheckpoint.pullDividendPayment(2, { from: account_investor3, gasPrice: 0 }); - let investor1BalanceAfter1 = new BigNumber(await web3.eth.getBalance(account_investor1)); - let investor2BalanceAfter1 = new BigNumber(await web3.eth.getBalance(account_investor2)); - let investor3BalanceAfter1 = new BigNumber(await web3.eth.getBalance(account_investor3)); + let investor1BalanceAfter1 = new BN(await web3.eth.getBalance(account_investor1)); + let investor2BalanceAfter1 = new BN(await web3.eth.getBalance(account_investor2)); + let investor3BalanceAfter1 = new BN(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")); + assert.equal(investor3BalanceAfter1.sub(investor3Balance).toString(), new BN(web3.utils.toWei("7", "ether")).toString()); }); it("Still no more withholding tax to withdraw", async () => { - let issuerBalance = new BigNumber(await web3.eth.getBalance(token_owner)); + let issuerBalance = new BN(await web3.eth.getBalance(token_owner)); await I_EtherDividendCheckpoint.withdrawWithholding(0, { from: token_owner, gasPrice: 0 }); - let issuerBalanceAfter = new BigNumber(await web3.eth.getBalance(token_owner)); - assert.equal(issuerBalanceAfter.sub(issuerBalance).toNumber(), web3.utils.toWei("0", "ether")); + let issuerBalanceAfter = new BN(await web3.eth.getBalance(token_owner)); + assert.equal(issuerBalanceAfter.sub(issuerBalance).toString(), new BN(web3.utils.toWei("0", "ether")).toString()); }); it("should investor 3 claims dividend", async () => { @@ -487,38 +493,38 @@ contract("EtherDividendCheckpoint", accounts => { }); it("Issuer pushes remainder", async () => { - let investor1BalanceAfter1 = new BigNumber(await web3.eth.getBalance(account_investor1)); - let investor2BalanceAfter1 = new BigNumber(await web3.eth.getBalance(account_investor2)); - let investor3BalanceAfter1 = new BigNumber(await web3.eth.getBalance(account_investor3)); + let investor1BalanceAfter1 = new BN(await web3.eth.getBalance(account_investor1)); + let investor2BalanceAfter1 = new BN(await web3.eth.getBalance(account_investor2)); + let investor3BalanceAfter1 = new BN(await web3.eth.getBalance(account_investor3)); let _blockNo = latestBlock(); - let tx = await I_EtherDividendCheckpoint.pushDividendPayment(2, 0, 10, { from: token_owner }); - let investor1BalanceAfter2 = new BigNumber(await web3.eth.getBalance(account_investor1)); - let investor2BalanceAfter2 = new BigNumber(await web3.eth.getBalance(account_investor2)); - let investor3BalanceAfter2 = new BigNumber(await web3.eth.getBalance(account_investor3)); - assert.equal(investor1BalanceAfter2.sub(investor1BalanceAfter1).toNumber(), 0); - assert.equal(investor2BalanceAfter2.sub(investor2BalanceAfter1).toNumber(), 0); - assert.equal(investor3BalanceAfter2.sub(investor3BalanceAfter1).toNumber(), 0); + let tx = await I_EtherDividendCheckpoint.pushDividendPayment(2, new BN(0), 10, { from: token_owner }); + let investor1BalanceAfter2 = new BN(await web3.eth.getBalance(account_investor1)); + let investor2BalanceAfter2 = new BN(await web3.eth.getBalance(account_investor2)); + let investor3BalanceAfter2 = new BN(await web3.eth.getBalance(account_investor3)); + assert.equal(investor1BalanceAfter2.sub(investor1BalanceAfter1).toString(), 0); + assert.equal(investor2BalanceAfter2.sub(investor2BalanceAfter1).toString(), 0); + assert.equal(investor3BalanceAfter2.sub(investor3BalanceAfter1).toString(), 0); //Check fully claimed - assert.equal((await I_EtherDividendCheckpoint.dividends(2))[5].toNumber(), web3.utils.toWei("11", "ether")); + assert.equal((await I_EtherDividendCheckpoint.dividends(2))[5].toString(), web3.utils.toWei("11", "ether")); }); it("Issuer withdraws new withholding tax", async () => { - let issuerBalance = new BigNumber(await web3.eth.getBalance(wallet)); + let issuerBalance = new BN(await web3.eth.getBalance(wallet)); await I_EtherDividendCheckpoint.withdrawWithholding(2, { from: token_owner, gasPrice: 0 }); - let issuerBalanceAfter = new BigNumber(await web3.eth.getBalance(wallet)); - assert.equal(issuerBalanceAfter.sub(issuerBalance).toNumber(), web3.utils.toWei("3", "ether")); + let issuerBalanceAfter = new BN(await web3.eth.getBalance(wallet)); + assert.equal(issuerBalanceAfter.sub(issuerBalance).toString(), new BN(web3.utils.toWei("3", "ether")).toString()); }); 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")); + await I_SecurityToken.transfer(account_investor1, new BN(web3.utils.toWei("1", "ether")), { from: account_investor2 }); + assert.equal((await I_SecurityToken.balanceOf(account_investor1)).toString(), new BN(web3.utils.toWei("1", "ether")).toString()); + assert.equal((await I_SecurityToken.balanceOf(account_investor2)).toString(), new BN(web3.utils.toWei("2", "ether")).toString()); + assert.equal((await I_SecurityToken.balanceOf(account_investor3)).toString(), new BN(web3.utils.toWei("7", "ether")).toString()); }); it("Create another new dividend with no value - fails", async () => { - let maturity = latestTime(); - let expiry = latestTime() + duration.days(2); + let maturity = await latestTime(); + let expiry = await latestTime() + duration.days(2); let tx = await I_SecurityToken.createCheckpoint({ from: token_owner }); await catchRevert( I_EtherDividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, 4, dividendName, { from: token_owner, value: 0 }) @@ -526,60 +532,62 @@ contract("EtherDividendCheckpoint", accounts => { }); it("Create another new dividend with explicit", async () => { - let maturity = latestTime(); - let expiry = latestTime() - duration.days(10); + let maturity = await latestTime(); + let expiry = await latestTime() - duration.days(10); await catchRevert( I_EtherDividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, 4, dividendName, { from: token_owner, - value: web3.utils.toWei("11", "ether") + value: new BN(web3.utils.toWei("11", "ether")) }) ); }); it("Create another new dividend with bad expiry - fails", async () => { - let maturity = latestTime() - duration.days(5); - let expiry = latestTime() - duration.days(2); + let maturity = await latestTime() - duration.days(5); + let expiry = await latestTime() - duration.days(2); await catchRevert( I_EtherDividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, 4, dividendName, { from: token_owner, - value: web3.utils.toWei("11", "ether") + value: new BN(web3.utils.toWei("11", "ether")) }) ); }); it("Create another new dividend with bad checkpoint in the future - fails", async () => { - let maturity = latestTime(); - let expiry = latestTime() + duration.days(2); + let maturity = await latestTime(); + let expiry = await latestTime() + duration.days(2); await catchRevert( I_EtherDividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, 5, dividendName, { from: token_owner, - value: web3.utils.toWei("11", "ether") + value: new BN(web3.utils.toWei("11", "ether")) }) ); }); it("Should not create dividend with more exclusions than limit", async () => { - let maturity = latestTime(); - let expiry = latestTime() + duration.days(10); + let maturity = await latestTime(); + let expiry = await latestTime() + duration.days(10); await I_SecurityToken.createCheckpoint({ from: token_owner }); let limit = await I_EtherDividendCheckpoint.EXCLUDED_ADDRESS_LIMIT(); - limit = limit.toNumber(); + limit = limit.toNumber() + 42; let addresses = []; addresses.push(account_temp); addresses.push(token_owner); - while (--limit) addresses.push(limit); + let tempAdd = '0x0000000000000000000000000000000000000000'; + while (--limit > 42) addresses.push(web3.utils.toChecksumAddress(tempAdd.substring(0, 42 - limit.toString().length) + limit)); + // while (--limit > 42) addresses.push(web3.utils.toChecksumAddress('0x00000000000000000000000000000000000000' + limit)); await catchRevert( I_EtherDividendCheckpoint.createDividendWithCheckpointAndExclusions(maturity, expiry, 4, addresses, dividendName, { from: token_owner, - value: web3.utils.toWei("10", "ether") + value: new BN(web3.utils.toWei("10", "ether")) }), "tx -> failed because too many address excluded" ); }); it("Create another new dividend with explicit checkpoint and excluding account_investor1", async () => { - let maturity = latestTime(); - let expiry = latestTime() + duration.days(10); + let maturity = await latestTime(); + let expiry = await latestTime() + duration.days(10); //checkpoint created in above test let tx = await I_EtherDividendCheckpoint.createDividendWithCheckpointAndExclusions( maturity, @@ -587,43 +595,44 @@ contract("EtherDividendCheckpoint", accounts => { 4, [account_investor1], dividendName, - { from: token_owner, value: web3.utils.toWei("10", "ether") } + { from: token_owner, value: new BN(web3.utils.toWei("10", "ether")) } ); assert.equal(tx.logs[0].args._checkpointId.toNumber(), 4, "Dividend should be created at checkpoint 4"); + console.log("Dividend Fourth :" + tx.logs[0].args._dividendIndex.toNumber()); }); it("Should not create new dividend with duplicate exclusion", async () => { - let maturity = latestTime(); - let expiry = latestTime() + duration.days(10); + let maturity = await latestTime(); + let expiry = await latestTime() + duration.days(10); //checkpoint created in above test - await catchRevert(I_EtherDividendCheckpoint.createDividendWithCheckpointAndExclusions( - maturity, - expiry, - 4, - [account_investor1, account_investor1], - dividendName, - { from: token_owner, value: web3.utils.toWei("10", "ether") } - )); + await catchRevert( + I_EtherDividendCheckpoint.createDividendWithCheckpointAndExclusions( + maturity, + expiry, + 4, + [account_investor1, account_investor1], + dividendName, + { from: token_owner, value: new BN(web3.utils.toWei("10", "ether")) } + ) + ); }); it("Should not create new dividend with 0x0 address in exclusion", async () => { - let maturity = latestTime(); - let expiry = latestTime() + duration.days(10); + let maturity = await latestTime(); + let expiry = await latestTime() + duration.days(10); //checkpoint created in above test - await catchRevert(I_EtherDividendCheckpoint.createDividendWithCheckpointAndExclusions( - maturity, - expiry, - 4, - [0], - dividendName, - { from: token_owner, value: web3.utils.toWei("10", "ether") } - )); + await catchRevert( + I_EtherDividendCheckpoint.createDividendWithCheckpointAndExclusions(maturity, expiry, 4, [address_zero], dividendName, { + from: token_owner, + value: new BN(web3.utils.toWei("10", "ether")) + }) + ); }); it("Non-owner pushes investor 1 - fails", async () => { - let investor1Balance = new BigNumber(await I_PolyToken.balanceOf(account_investor1)); - let investor2Balance = new BigNumber(await I_PolyToken.balanceOf(account_investor2)); - let investor3Balance = new BigNumber(await I_PolyToken.balanceOf(account_investor3)); + let investor1Balance = new BN(await I_PolyToken.balanceOf(account_investor1)); + let investor2Balance = new BN(await I_PolyToken.balanceOf(account_investor2)); + let investor3Balance = new BN(await I_PolyToken.balanceOf(account_investor3)); await catchRevert( I_EtherDividendCheckpoint.pushDividendPaymentToAddresses(3, [account_investor2, account_investor1], { from: account_investor2, @@ -633,9 +642,9 @@ contract("EtherDividendCheckpoint", accounts => { }); it("issuer pushes investor 1 with bad dividend index - fails", async () => { - let investor1Balance = new BigNumber(await I_PolyToken.balanceOf(account_investor1)); - let investor2Balance = new BigNumber(await I_PolyToken.balanceOf(account_investor2)); - let investor3Balance = new BigNumber(await I_PolyToken.balanceOf(account_investor3)); + let investor1Balance = new BN(await I_PolyToken.balanceOf(account_investor1)); + let investor2Balance = new BN(await I_PolyToken.balanceOf(account_investor2)); + let investor3Balance = new BN(await I_PolyToken.balanceOf(account_investor3)); await catchRevert( I_EtherDividendCheckpoint.pushDividendPaymentToAddresses(6, [account_investor2, account_investor1], { from: token_owner, @@ -650,48 +659,48 @@ contract("EtherDividendCheckpoint", accounts => { let dividendAmount3 = await I_EtherDividendCheckpoint.calculateDividend.call(3, account_investor3); let dividendAmount_temp = await I_EtherDividendCheckpoint.calculateDividend.call(3, account_temp); //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("2", "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")); + assert.equal(dividendAmount1[0].toString(), new BN(web3.utils.toWei("0", "ether")).toString()); + assert.equal(dividendAmount1[1].toString(), new BN(web3.utils.toWei("0", "ether")).toString()); + assert.equal(dividendAmount2[0].toString(), new BN(web3.utils.toWei("2", "ether")).toString()); + assert.equal(dividendAmount2[1].toString(), new BN(web3.utils.toWei("2", "ether")).toString()); + assert.equal(dividendAmount3[0].toString(), new BN(web3.utils.toWei("7", "ether")).toString()); + assert.equal(dividendAmount3[1].toString(), new BN(web3.utils.toWei("0", "ether")).toString()); + assert.equal(dividendAmount_temp[0].toString(), new BN(web3.utils.toWei("1", "ether")).toString()); + assert.equal(dividendAmount_temp[1].toString(), new BN(web3.utils.toWei("0", "ether")).toString()); }); it("Investor 2 claims dividend", async () => { - let investor1Balance = new BigNumber(await web3.eth.getBalance(account_investor1)); - let investor2Balance = new BigNumber(await web3.eth.getBalance(account_investor2)); - let investor3Balance = new BigNumber(await web3.eth.getBalance(account_investor3)); - let tempBalance = new BigNumber(await web3.eth.getBalance(account_temp)); + let investor1Balance = new BN(await web3.eth.getBalance(account_investor1)); + let investor2Balance = new BN(await web3.eth.getBalance(account_investor2)); + let investor3Balance = new BN(await web3.eth.getBalance(account_investor3)); + let tempBalance = new BN(await web3.eth.getBalance(account_temp)); await I_EtherDividendCheckpoint.pullDividendPayment(3, { from: account_investor2, gasPrice: 0 }); - let investor1BalanceAfter1 = new BigNumber(await web3.eth.getBalance(account_investor1)); - let investor2BalanceAfter1 = new BigNumber(await web3.eth.getBalance(account_investor2)); - let investor3BalanceAfter1 = new BigNumber(await web3.eth.getBalance(account_investor3)); - let tempBalanceAfter1 = new BigNumber(await web3.eth.getBalance(account_temp)); + let investor1BalanceAfter1 = new BN(await web3.eth.getBalance(account_investor1)); + let investor2BalanceAfter1 = new BN(await web3.eth.getBalance(account_investor2)); + let investor3BalanceAfter1 = new BN(await web3.eth.getBalance(account_investor3)); + let tempBalanceAfter1 = new BN(await web3.eth.getBalance(account_temp)); assert.equal(investor1BalanceAfter1.sub(investor1Balance).toNumber(), 0); - assert.equal(investor2BalanceAfter1.sub(investor2Balance).toNumber(), 0); + assert.equal(investor2BalanceAfter1.sub(investor2Balance).toString(), 0); 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 = new BigNumber(await web3.eth.getBalance(account_investor1)); - let investor2BalanceAfter1 = new BigNumber(await web3.eth.getBalance(account_investor2)); - let investor3BalanceAfter1 = new BigNumber(await web3.eth.getBalance(account_investor3)); - let tempBalanceAfter1 = new BigNumber(await web3.eth.getBalance(account_temp)); + let investor1BalanceAfter1 = new BN(await web3.eth.getBalance(account_investor1)); + let investor2BalanceAfter1 = new BN(await web3.eth.getBalance(account_investor2)); + let investor3BalanceAfter1 = new BN(await web3.eth.getBalance(account_investor3)); + let tempBalanceAfter1 = new BN(await web3.eth.getBalance(account_temp)); await I_EtherDividendCheckpoint.pushDividendPaymentToAddresses(3, [account_investor1, account_temp], { from: token_owner }); - let investor1BalanceAfter2 = new BigNumber(await web3.eth.getBalance(account_investor1)); - let investor2BalanceAfter2 = new BigNumber(await web3.eth.getBalance(account_investor2)); - let investor3BalanceAfter2 = new BigNumber(await web3.eth.getBalance(account_investor3)); - let tempBalanceAfter2 = new BigNumber(await web3.eth.getBalance(account_temp)); + let investor1BalanceAfter2 = new BN(await web3.eth.getBalance(account_investor1)); + let investor2BalanceAfter2 = new BN(await web3.eth.getBalance(account_investor2)); + let investor3BalanceAfter2 = new BN(await web3.eth.getBalance(account_investor3)); + let tempBalanceAfter2 = new BN(await web3.eth.getBalance(account_temp)); 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).toString(), new BN(web3.utils.toWei("1", "ether")).toString()); //Check fully claimed - assert.equal((await I_EtherDividendCheckpoint.dividends(3))[5].toNumber(), web3.utils.toWei("3", "ether")); + assert.equal((await I_EtherDividendCheckpoint.dividends(3))[5].toString(), new BN(web3.utils.toWei("3", "ether")).toString()); }); it("should calculate dividend after the push dividend payment", async () => { @@ -711,10 +720,10 @@ contract("EtherDividendCheckpoint", accounts => { }); it("Issuer is able to reclaim dividend after expiry", async () => { - let tokenOwnerBalance = new BigNumber(await web3.eth.getBalance(wallet)); + let tokenOwnerBalance = new BN(await web3.eth.getBalance(wallet)); await I_EtherDividendCheckpoint.reclaimDividend(3, { from: token_owner, gasPrice: 0 }); - let tokenOwnerAfter = new BigNumber(await web3.eth.getBalance(wallet)); - assert.equal(tokenOwnerAfter.sub(tokenOwnerBalance).toNumber(), web3.utils.toWei("7", "ether")); + let tokenOwnerAfter = new BN(await web3.eth.getBalance(wallet)); + assert.equal(tokenOwnerAfter.sub(tokenOwnerBalance).toString(), new BN(web3.utils.toWei("7", "ether")).toString()); }); it("Issuer is able to reclaim dividend after expiry", async () => { @@ -726,12 +735,11 @@ contract("EtherDividendCheckpoint", accounts => { }); it("Assign token balance to an address that can't receive funds", async () => { - let tx = await I_GeneralTransferManager.modifyWhitelist( + let tx = await I_GeneralTransferManager.modifyKYCData( I_PolyToken.address, - latestTime(), - latestTime(), - latestTime() + duration.days(10), - true, + currentTime, + currentTime, + currentTime.add(new BN(duration.days(1000000))), { from: account_issuer, gas: 500000 @@ -740,47 +748,48 @@ contract("EtherDividendCheckpoint", accounts => { // 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")); + await I_SecurityToken.issue(I_PolyToken.address, new BN(web3.utils.toWei("1", "ether")), "0x0", { from: token_owner }); + assert.equal((await I_SecurityToken.balanceOf(account_investor1)).toString(), new BN(web3.utils.toWei("1", "ether")).toString()); + assert.equal((await I_SecurityToken.balanceOf(account_investor2)).toString(), new BN(web3.utils.toWei("2", "ether")).toString()); + assert.equal((await I_SecurityToken.balanceOf(account_investor3)).toString(), new BN(web3.utils.toWei("7", "ether")).toString()); + assert.equal((await I_SecurityToken.balanceOf(account_temp)).toString(), new BN(web3.utils.toWei("1", "ether")).toString()); + assert.equal((await I_SecurityToken.balanceOf(I_PolyToken.address)).toString(), new BN(web3.utils.toWei("1", "ether")).toString()); }); it("Create another new dividend", async () => { - let maturity = latestTime(); - let expiry = latestTime() + duration.days(10); + let maturity = await latestTime(); + let expiry = await latestTime() + duration.days(10); let tx = await I_EtherDividendCheckpoint.createDividendWithExclusions(maturity, expiry, [], dividendName, { from: token_owner, - value: web3.utils.toWei("12", "ether") + value: new BN(web3.utils.toWei("12", "ether")) }); assert.equal(tx.logs[0].args._checkpointId.toNumber(), 6, "Dividend should be created at checkpoint 6"); + console.log("Dividend Fifth :" + tx.logs[0].args._dividendIndex.toNumber()); }); it("Should issuer pushes all dividends", async () => { - let investor1BalanceBefore = new BigNumber(await web3.eth.getBalance(account_investor1)); - let investor2BalanceBefore = new BigNumber(await web3.eth.getBalance(account_investor2)); - let investor3BalanceBefore = new BigNumber(await web3.eth.getBalance(account_investor3)); - let tempBalanceBefore = new BigNumber(await web3.eth.getBalance(account_temp)); - let tokenBalanceBefore = new BigNumber(await web3.eth.getBalance(I_PolyToken.address)); - - await I_EtherDividendCheckpoint.pushDividendPayment(4, 0, 10, { from: token_owner }); - - let investor1BalanceAfter = new BigNumber(await web3.eth.getBalance(account_investor1)); - let investor2BalanceAfter = new BigNumber(await web3.eth.getBalance(account_investor2)); - let investor3BalanceAfter = new BigNumber(await web3.eth.getBalance(account_investor3)); - let tempBalanceAfter = new BigNumber(await web3.eth.getBalance(account_temp)); - let tokenBalanceAfter = new 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(), 0); - 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")); + let investor1BalanceBefore = new BN(await web3.eth.getBalance(account_investor1)); + let investor2BalanceBefore = new BN(await web3.eth.getBalance(account_investor2)); + let investor3BalanceBefore = new BN(await web3.eth.getBalance(account_investor3)); + let tempBalanceBefore = new BN(await web3.eth.getBalance(account_temp)); + let tokenBalanceBefore = new BN(await web3.eth.getBalance(I_PolyToken.address)); + + await I_EtherDividendCheckpoint.pushDividendPayment(4, new BN(0), 10, { from: token_owner }); + + let investor1BalanceAfter = new BN(await web3.eth.getBalance(account_investor1)); + let investor2BalanceAfter = new BN(await web3.eth.getBalance(account_investor2)); + let investor3BalanceAfter = new BN(await web3.eth.getBalance(account_investor3)); + let tempBalanceAfter = new BN(await web3.eth.getBalance(account_temp)); + let tokenBalanceAfter = new BN(await web3.eth.getBalance(I_PolyToken.address)); + + assert.equal(investor1BalanceAfter.sub(investor1BalanceBefore).toString(), new BN(web3.utils.toWei("1", "ether")).toString()); + assert.equal(investor2BalanceAfter.sub(investor2BalanceBefore).toString(), 0); + assert.equal(investor3BalanceAfter.sub(investor3BalanceBefore).toString(), new BN(web3.utils.toWei("7", "ether")).toString()); + assert.equal(tempBalanceAfter.sub(tempBalanceBefore).toString(), new BN(web3.utils.toWei("1", "ether")).toString()); + assert.equal(tokenBalanceAfter.sub(tokenBalanceBefore).toString(), new BN(web3.utils.toWei("0", "ether")).toString()); //Check partially claimed - assert.equal((await I_EtherDividendCheckpoint.dividends(4))[5].toNumber(), web3.utils.toWei("11", "ether")); + assert.equal((await I_EtherDividendCheckpoint.dividends(4))[5].toString(), new BN(web3.utils.toWei("11", "ether")).toString()); }); it("Should give the right dividend index", async () => { @@ -799,8 +808,8 @@ contract("EtherDividendCheckpoint", accounts => { }); it("should registr a delegate", async () => { - [I_GeneralPermissionManagerFactory] = await deployGPMAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, 0); - let tx = await I_SecurityToken.addModule(I_GeneralPermissionManagerFactory.address, "0x", 0, 0, { from: token_owner }); + [I_GeneralPermissionManagerFactory] = await deployGPMAndVerifyed(account_polymath, I_MRProxied, 0); + let tx = await I_SecurityToken.addModule(I_GeneralPermissionManagerFactory.address, "0x", new BN(0), new BN(0), false, { from: token_owner }); assert.equal(tx.logs[2].args._types[0].toNumber(), delegateManagerKey, "General Permission Manager doesn't get deployed"); assert.equal( web3.utils.toAscii(tx.logs[2].args._name).replace(/\u0000/g, ""), @@ -808,65 +817,65 @@ contract("EtherDividendCheckpoint", accounts => { "GeneralPermissionManagerFactory module was not added" ); I_GeneralPermissionManager = await GeneralPermissionManager.at(tx.logs[2].args._module); - tx = await I_GeneralPermissionManager.addDelegate(account_manager, managerDetails, { from: token_owner}); + tx = await I_GeneralPermissionManager.addDelegate(account_manager, managerDetails, { from: token_owner }); assert.equal(tx.logs[0].args._delegate, account_manager); }); it("should not allow manager without permission to create dividend", async () => { - await I_PolyToken.transfer(account_manager, web3.utils.toWei("2", "ether"), { from: token_owner }); - await I_PolyToken.approve(I_EtherDividendCheckpoint.address, web3.utils.toWei("1.5", "ether"), { from: account_manager }); - let maturity = latestTime() + duration.days(1); - let expiry = latestTime() + duration.days(10); + await I_PolyToken.transfer(account_manager, new BN(web3.utils.toWei("2", "ether")), { from: token_owner }); + await I_PolyToken.approve(I_EtherDividendCheckpoint.address, new BN(web3.utils.toWei("1.5", "ether")), { from: account_manager }); + let maturity = await latestTime() + duration.days(1); + let expiry = await latestTime() + duration.days(10); - await catchRevert(I_EtherDividendCheckpoint.createDividend( - maturity, - expiry, - dividendName, - { from: account_manager, value: web3.utils.toWei("12", "ether") } - )); + await catchRevert( + I_EtherDividendCheckpoint.createDividend(maturity, expiry, dividendName, { + from: account_manager, + value: new BN(web3.utils.toWei("12", "ether")) + }) + ); }); it("should not allow manager without permission to create dividend with checkpoint", async () => { - let maturity = latestTime() + duration.days(1); - let expiry = latestTime() + duration.days(10); + let maturity = await latestTime() + duration.days(1); + let expiry = await latestTime() + duration.days(10); let checkpointID = await I_SecurityToken.createCheckpoint.call({ from: token_owner }); await I_SecurityToken.createCheckpoint({ from: token_owner }); - await catchRevert(I_EtherDividendCheckpoint.createDividendWithCheckpoint( - maturity, - expiry, - checkpointID.toNumber(), - dividendName, - { from: account_manager, value: web3.utils.toWei("12", "ether") } - )); + await catchRevert( + I_EtherDividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, checkpointID.toNumber(), dividendName, { + from: account_manager, + value: new BN(web3.utils.toWei("12", "ether")) + }) + ); }); it("should not allow manager without permission to create dividend with exclusion", async () => { - let maturity = latestTime() + duration.days(1); - let expiry = latestTime() + duration.days(10); - let exclusions = [1]; - await catchRevert(I_EtherDividendCheckpoint.createDividendWithExclusions( - maturity, - expiry, - exclusions, - dividendName, - { from: account_manager, value: web3.utils.toWei("12", "ether") } - )); + let maturity = await latestTime() + duration.days(1); + let expiry = await latestTime() + duration.days(10); + let exclusions = [one_address]; + await catchRevert( + I_EtherDividendCheckpoint.createDividendWithExclusions(maturity, expiry, exclusions, dividendName, { + from: account_manager, + value: new BN(web3.utils.toWei("12", "ether")) + }) + ); }); it("should not allow manager without permission to create dividend with checkpoint and exclusion", async () => { - let maturity = latestTime() + duration.days(1); - let expiry = latestTime() + duration.days(10); - let exclusions = [1]; + let maturity = await latestTime() + duration.days(1); + let expiry = await latestTime() + duration.days(10); + let exclusions = [one_address]; let checkpointID = await I_SecurityToken.createCheckpoint.call({ from: token_owner }); await I_SecurityToken.createCheckpoint({ from: token_owner }); - await catchRevert(I_EtherDividendCheckpoint.createDividendWithCheckpointAndExclusions( - maturity, - expiry, - checkpointID.toNumber(), - exclusions, - dividendName, - { from: account_manager, value: web3.utils.toWei("12", "ether") } - )); + await catchRevert( + I_EtherDividendCheckpoint.createDividendWithCheckpointAndExclusions( + maturity, + expiry, + checkpointID.toNumber(), + exclusions, + dividendName, + { from: account_manager, value: new BN(web3.utils.toWei("12", "ether")) } + ) + ); }); it("should not allow manager without permission to create checkpoint", async () => { @@ -874,69 +883,96 @@ contract("EtherDividendCheckpoint", accounts => { }); it("should give permission to manager", async () => { - await I_GeneralPermissionManager.changePermission( - account_manager, - I_EtherDividendCheckpoint.address, - "CHECKPOINT", - true, - { from: token_owner } - ); - let tx = await I_GeneralPermissionManager.changePermission( - account_manager, - I_EtherDividendCheckpoint.address, - "MANAGE", - true, - { from: token_owner } - ); + await I_GeneralPermissionManager.changePermission(account_manager, I_EtherDividendCheckpoint.address, web3.utils.fromAscii("OPERATOR"), true, { + from: token_owner + }); + let tx = await I_GeneralPermissionManager.changePermission(account_manager, I_EtherDividendCheckpoint.address, web3.utils.fromAscii("ADMIN"), true, { + from: token_owner + }); assert.equal(tx.logs[0].args._delegate, account_manager); }); it("should allow manager with permission to create dividend", async () => { - let maturity = latestTime() + duration.days(1); - let expiry = latestTime() + duration.days(10); + let maturity = await latestTime() + duration.days(1); + let expiry = await latestTime() + duration.days(10); - let tx = await I_EtherDividendCheckpoint.createDividend( - maturity, - expiry, - dividendName, - { from: account_manager, value: web3.utils.toWei("12", "ether") } - ); + let tx = await I_EtherDividendCheckpoint.createDividend(maturity, expiry, dividendName, { + from: account_manager, + value: new BN(web3.utils.toWei("12", "ether")) + }); assert.equal(tx.logs[0].args._checkpointId.toNumber(), 9); + console.log("Dividend sixth :" + tx.logs[0].args._dividendIndex.toNumber()); }); it("should allow manager with permission to create dividend with checkpoint", async () => { - let maturity = latestTime() + duration.days(1); - let expiry = latestTime() + duration.days(10); + let maturity = await latestTime() + duration.days(1); + let expiry = await latestTime() + duration.days(10); let checkpointID = await I_SecurityToken.createCheckpoint.call({ from: token_owner }); await I_SecurityToken.createCheckpoint({ from: token_owner }); - let tx = await I_EtherDividendCheckpoint.createDividendWithCheckpoint( - maturity, - expiry, - checkpointID.toNumber(), - dividendName, - { from: account_manager, value: web3.utils.toWei("12", "ether") } - ); + let tx = await I_EtherDividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, checkpointID.toNumber(), dividendName, { + from: account_manager, + value: new BN(web3.utils.toWei("12", "ether")) + }); assert.equal(tx.logs[0].args._checkpointId.toNumber(), 10); + console.log("Dividend seventh :" + tx.logs[0].args._dividendIndex.toNumber()); }); it("should allow manager with permission to create dividend with exclusion", async () => { - let maturity = latestTime() + duration.days(1); - let expiry = latestTime() + duration.days(10); - let exclusions = [1]; - let tx = await I_EtherDividendCheckpoint.createDividendWithExclusions( - maturity, - expiry, - exclusions, - dividendName, - { from: account_manager, value: web3.utils.toWei("12", "ether") } - ); + let maturity = await latestTime() + duration.days(1); + let expiry = await latestTime() + duration.days(10); + let exclusions = [one_address]; + let tx = await I_EtherDividendCheckpoint.createDividendWithExclusions(maturity, expiry, exclusions, dividendName, { + from: account_manager, + value: new BN(web3.utils.toWei("12", "ether")) + }); + console.log("Dividend Eighth :" + tx.logs[0].args._dividendIndex.toNumber()); assert.equal(tx.logs[0].args._checkpointId.toNumber(), 11); + console.log(tx.logs[0].args._dividendIndex.toNumber()); + }); + + it("Should fail to update the dividend dates because msg.sender is not authorised", async () => { + // failed because msg.sender is not the owner + await catchRevert( + I_EtherDividendCheckpoint.updateDividendDates(new BN(7), new BN(0), new BN(1), {from: account_polymath}) + ); + }); + + it("Should fail to update the dates when the dividend get expired", async() => { + let id = await takeSnapshot(); + await increaseTime(duration.days(11)); + await catchRevert( + I_EtherDividendCheckpoint.updateDividendDates(new BN(7), new BN(0), new BN(1), {from: token_owner}) + ); + await revertToSnapshot(id); + }); + + it("Should update the dividend dates", async() => { + let newMaturity = await latestTime() - duration.days(4); + let newExpiry = await latestTime() - duration.days(2); + let tx = await I_EtherDividendCheckpoint.updateDividendDates(new BN(7), newMaturity, newExpiry, {from: token_owner}); + let info = await I_EtherDividendCheckpoint.getDividendData.call(7); + assert.equal(info[1].toNumber(), newMaturity); + assert.equal(info[2].toNumber(), newExpiry); + // Can now reclaim the dividend + await I_EtherDividendCheckpoint.reclaimDividend(new BN(7), {from: token_owner}); + }); + + it("Reclaim ETH from the dividend contract", async () => { + let currentDividendBalance = await web3.eth.getBalance(I_EtherDividendCheckpoint.address); + let currentIssuerBalance = await web3.eth.getBalance(token_owner); + await catchRevert(I_EtherDividendCheckpoint.reclaimETH({from: account_polymath, gasPrice: 0})); + let tx = await I_EtherDividendCheckpoint.reclaimETH({from: token_owner, gasPrice: 0}); + assert.equal(await web3.eth.getBalance(I_EtherDividendCheckpoint.address), 0); + let newIssuerBalance = await web3.eth.getBalance(token_owner); + console.log("Reclaimed: " + currentDividendBalance.toString()); + let balanceDiff = parseInt(web3.utils.fromWei(newIssuerBalance.toString())) - parseInt(web3.utils.fromWei(currentIssuerBalance.toString())) + assert.equal(balanceDiff, parseInt(web3.utils.fromWei(currentDividendBalance.toString()))); }); it("should allow manager with permission to create dividend with checkpoint and exclusion", async () => { - let maturity = latestTime() + duration.days(1); - let expiry = latestTime() + duration.days(10); - let exclusions = [1]; + let maturity = await latestTime() + duration.days(1); + let expiry = await latestTime() + duration.days(10); + let exclusions = [one_address]; let checkpointID = await I_SecurityToken.createCheckpoint.call({ from: token_owner }); await I_SecurityToken.createCheckpoint({ from: token_owner }); let tx = await I_EtherDividendCheckpoint.createDividendWithCheckpointAndExclusions( @@ -945,34 +981,11 @@ contract("EtherDividendCheckpoint", accounts => { checkpointID.toNumber(), exclusions, dividendName, - { from: account_manager, value: web3.utils.toWei("12", "ether") } + { from: account_manager, value: new BN(web3.utils.toWei("12", "ether")) } ); assert.equal(tx.logs[0].args._checkpointId.toNumber(), 12); - console.log(tx.logs[0].args._dividendIndex.toNumber()); - }); - - it("Update maturity and expiry dates on dividend", async () => { - await catchRevert(I_EtherDividendCheckpoint.updateDividendDates(8, 0, 1, {from: account_polymath})); - let tx = await I_EtherDividendCheckpoint.updateDividendDates(8, 0, 1, {from: token_owner}); - let info = await I_EtherDividendCheckpoint.getDividendData.call(8); - assert.equal(info[1].toNumber(), 0); - assert.equal(info[2].toNumber(), 1); - // Can now reclaim the dividend - await I_EtherDividendCheckpoint.reclaimDividend(8, {from: token_owner}); }); - it("Reclaim ETH from the dividend contract", async () => { - let currentDividendBalance = BigNumber(await web3.eth.getBalance(I_EtherDividendCheckpoint.address)); - let currentIssuerBalance = BigNumber(await web3.eth.getBalance(token_owner)); - await catchRevert(I_EtherDividendCheckpoint.reclaimETH({from: account_polymath, gasPrice: 0})); - let tx = await I_EtherDividendCheckpoint.reclaimETH({from: token_owner, gasPrice: 0}); - assert.equal(await web3.eth.getBalance(I_EtherDividendCheckpoint.address), 0); - let newIssuerBalance = BigNumber(await web3.eth.getBalance(token_owner)); - console.log("Reclaimed: " + currentDividendBalance.toNumber()); - assert.equal(newIssuerBalance.sub(currentIssuerBalance).toNumber(), currentDividendBalance.toNumber()); - }); - - it("should allow manager with permission to create checkpoint", async () => { let initCheckpointID = await I_SecurityToken.createCheckpoint.call({ from: token_owner }); await I_EtherDividendCheckpoint.createCheckpoint({ from: account_manager }); @@ -982,11 +995,11 @@ contract("EtherDividendCheckpoint", accounts => { describe("Test cases for the EtherDividendCheckpointFactory", async () => { it("should get the exact details of the factory", async () => { - assert.equal((await I_EtherDividendCheckpointFactory.getSetupCost.call()).toNumber(), 0); + assert.equal((await I_EtherDividendCheckpointFactory.setupCost.call()).toNumber(), 0); assert.equal((await I_EtherDividendCheckpointFactory.getTypes.call())[0], 4); - assert.equal(await I_EtherDividendCheckpointFactory.version.call(), "2.1.1"); + assert.equal(await I_EtherDividendCheckpointFactory.version.call(), "3.0.0"); assert.equal( - web3.utils.toAscii(await I_EtherDividendCheckpointFactory.getName.call()).replace(/\u0000/g, ""), + web3.utils.toAscii(await I_EtherDividendCheckpointFactory.name.call()).replace(/\u0000/g, ""), "EtherDividendCheckpoint", "Wrong Module added" ); @@ -996,11 +1009,6 @@ contract("EtherDividendCheckpoint", accounts => { "Wrong Module added" ); assert.equal(await I_EtherDividendCheckpointFactory.title.call(), "Ether Dividend Checkpoint", "Wrong Module added"); - assert.equal( - await I_EtherDividendCheckpointFactory.getInstructions.call(), - "Create a dividend which will be paid out to token holders proportionally according to their balances at the point the dividend is created", - "Wrong Module added" - ); let tags = await I_EtherDividendCheckpointFactory.getTags.call(); assert.equal(tags.length, 3); }); diff --git a/test/g_general_permission_manager.js b/test/g_general_permission_manager.js index d554891ee..77b2a37b0 100644 --- a/test/g_general_permission_manager.js +++ b/test/g_general_permission_manager.js @@ -1,21 +1,21 @@ -import latestTime from './helpers/latestTime'; -import {signData} from './helpers/signData'; -import { pk } from './helpers/testprivateKey'; -import { duration, promisifyLogWatch, latestBlock } from './helpers/utils'; -import { takeSnapshot, increaseTime, revertToSnapshot } from './helpers/time'; +import latestTime from "./helpers/latestTime"; +import { pk } from "./helpers/testprivateKey"; +import { duration, promisifyLogWatch, latestBlock } from "./helpers/utils"; +import { takeSnapshot, increaseTime, revertToSnapshot } from "./helpers/time"; import { catchRevert } from "./helpers/exceptions"; import { setUpPolymathNetwork, deployGPMAndVerifyed } from "./helpers/createInstances"; -const SecurityToken = artifacts.require('./SecurityToken.sol'); -const GeneralTransferManager = artifacts.require('./GeneralTransferManager'); -const GeneralPermissionManager = artifacts.require('./GeneralPermissionManager'); +const SecurityToken = artifacts.require("./SecurityToken.sol"); +const SecurityTokenRegistryInterface = artifacts.require("./ISecurityTokenRegistry.sol"); +const GeneralTransferManager = artifacts.require("./GeneralTransferManager"); +const GeneralPermissionManager = artifacts.require("./GeneralPermissionManager"); +const STGetter = artifacts.require("./STGetter"); -const Web3 = require('web3'); -const BigNumber = require('bignumber.js'); -const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")) // Hardcoded development port - -contract('GeneralPermissionManager', accounts => { +const Web3 = require("web3"); +const BN = Web3.utils.BN; +const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); // Hardcoded development port +contract("GeneralPermissionManager", async (accounts) => { // Accounts Variable declaration let account_polymath; let account_issuer; @@ -28,10 +28,7 @@ contract('GeneralPermissionManager', accounts => { let account_delegate; let account_delegate2; let account_delegate3; - // investor Details - let fromTime = latestTime(); - let toTime = latestTime(); - let expiryTime = toTime + duration.days(15); + const delegateDetails = web3.utils.fromAscii("I am delegate"); let message = "Transaction Should Fail!"; @@ -54,6 +51,10 @@ contract('GeneralPermissionManager', accounts => { let I_STRProxied; let I_PolyToken; let I_PolymathRegistry; + let I_STRGetter; + let I_STGetter; + let I_SecurityTokenRegistryInterface; + let stGetter; // SecurityToken Details const name = "Team"; @@ -61,7 +62,7 @@ contract('GeneralPermissionManager', accounts => { const tokenDetails = "This is equity type of issuance"; const decimals = 18; const contact = "team@polymath.network"; - const delegateDetails = "Hello I am legit delegate"; + const managerDetails = web3.utils.fromAscii("Hello"); // Module key const delegateManagerKey = 1; @@ -69,10 +70,14 @@ contract('GeneralPermissionManager', accounts => { const stoKey = 3; // Initial fee for ticker registry and security token registry - const initRegFee = web3.utils.toWei("250"); + const initRegFee = new BN(web3.utils.toWei("1000")); + + let currentTime; + const address_zero = "0x0000000000000000000000000000000000000000"; + const one_address = "0x0000000000000000000000000000000000000001"; - before(async() => { - // Accounts setup + before(async () => { + currentTime = new BN(await latestTime()); account_polymath = accounts[0]; account_issuer = accounts[1]; @@ -99,14 +104,16 @@ contract('GeneralPermissionManager', accounts => { I_STFactory, I_SecurityTokenRegistry, I_SecurityTokenRegistryProxy, - I_STRProxied + I_STRProxied, + I_STRGetter, + I_STGetter ] = instances; // STEP 5: Deploy the GeneralDelegateManagerFactory - [I_GeneralPermissionManagerFactory] = await deployGPMAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, 0); + [I_GeneralPermissionManagerFactory] = await deployGPMAndVerifyed(account_polymath, I_MRProxied, 0); // STEP 6: Deploy the GeneralDelegateManagerFactory - [P_GeneralPermissionManagerFactory] = await deployGPMAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, web3.utils.toWei("500")); - + [P_GeneralPermissionManagerFactory] = await deployGPMAndVerifyed(account_polymath, I_MRProxied, new BN(web3.utils.toWei("500"))); + I_SecurityTokenRegistryInterface = await SecurityTokenRegistryInterface.at(I_SecurityTokenRegistryProxy.address); // Printing all the contract addresses console.log(` --------------------- Polymath Network Smart Contracts: --------------------- @@ -127,49 +134,52 @@ contract('GeneralPermissionManager', accounts => { describe("Generate the SecurityToken", async () => { it("Should register the ticker before the generation of the security token", async () => { await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); - let tx = await I_STRProxied.registerTicker(token_owner, symbol, contact, { from: token_owner }); + let tx = await I_STRProxied.registerNewTicker(token_owner, symbol, { from: token_owner }); assert.equal(tx.logs[0].args._owner, token_owner); assert.equal(tx.logs[0].args._ticker, symbol.toUpperCase()); }); it("Should generate the new security token with the same symbol as registered above", async () => { await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); - let _blockNo = latestBlock(); - let tx = await I_STRProxied.generateSecurityToken(name, symbol, tokenDetails, false, { from: token_owner }); + + let tx = await I_STRProxied.generateNewSecurityToken(name, symbol, tokenDetails, false, token_owner, 0, { from: token_owner }); // 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 log = await promisifyLogWatch(I_SecurityToken.ModuleAdded({ from: _blockNo }), 1); + I_SecurityToken = await SecurityToken.at(tx.logs[1].args._securityTokenAddress); + stGetter = await STGetter.at(I_SecurityToken.address); + const log = (await I_SecurityToken.getPastEvents('ModuleAdded', {filter: {transactionHash: tx.transactionHash}}))[0]; // Verify that GeneralTransferManager module get added successfully or not assert.equal(log.args._types[0].toNumber(), 2); assert.equal(web3.utils.toAscii(log.args._name).replace(/\u0000/g, ""), "GeneralTransferManager"); }); - it("Should intialize the auto attached modules", async () => { - let moduleData = (await I_SecurityToken.getModulesByType(2))[0]; - I_GeneralTransferManager = GeneralTransferManager.at(moduleData); + it("Should initialize the auto attached modules", async () => { + let moduleData = (await stGetter.getModulesByType(2))[0]; + I_GeneralTransferManager = await GeneralTransferManager.at(moduleData); }); it("Should successfully attach the General permission manager factory with the security token -- failed because Token is not paid", async () => { let errorThrown = false; - await I_PolyToken.getTokens(web3.utils.toWei("500", "ether"), token_owner); + await I_PolyToken.getTokens(new BN(web3.utils.toWei("2000", "ether")), token_owner); await catchRevert( - I_SecurityToken.addModule(P_GeneralPermissionManagerFactory.address, "0x", web3.utils.toWei("500", "ether"), 0, { from: token_owner }) + I_SecurityToken.addModule(P_GeneralPermissionManagerFactory.address, "0x", new BN(web3.utils.toWei("2000", "ether")), new BN(0), false, { + from: token_owner + }) ); }); it("Should successfully attach the General permission manager factory with the security token", async () => { let snapId = await takeSnapshot(); - await I_PolyToken.transfer(I_SecurityToken.address, web3.utils.toWei("500", "ether"), { from: token_owner }); + await I_PolyToken.transfer(I_SecurityToken.address, new BN(web3.utils.toWei("2000", "ether")), { from: token_owner }); const tx = await I_SecurityToken.addModule( P_GeneralPermissionManagerFactory.address, "0x", - web3.utils.toWei("500", "ether"), - 0, + new BN(web3.utils.toWei("2000", "ether")), + new BN(0), + false, { from: token_owner } ); assert.equal(tx.logs[3].args._types[0].toNumber(), delegateManagerKey, "General Permission Manager doesn't get deployed"); @@ -178,21 +188,20 @@ contract('GeneralPermissionManager', accounts => { "GeneralPermissionManager", "GeneralPermissionManagerFactory module was not added" ); - P_GeneralPermissionManager = GeneralPermissionManager.at(tx.logs[3].args._module); + P_GeneralPermissionManager = await GeneralPermissionManager.at(tx.logs[3].args._module); await revertToSnapshot(snapId); }); it("Should successfully attach the General permission manager factory with the security token", async () => { - const tx = await I_SecurityToken.addModule(I_GeneralPermissionManagerFactory.address, "0x", 0, 0, { from: token_owner }); + const tx = await I_SecurityToken.addModule(I_GeneralPermissionManagerFactory.address, "0x", new BN(0), new BN(0), false, { from: token_owner }); assert.equal(tx.logs[2].args._types[0].toNumber(), delegateManagerKey, "General Permission Manager doesn't get deployed"); assert.equal( web3.utils.toAscii(tx.logs[2].args._name).replace(/\u0000/g, ""), "GeneralPermissionManager", "GeneralPermissionManagerFactory module was not added" ); - I_GeneralPermissionManager = GeneralPermissionManager.at(tx.logs[2].args._module); + I_GeneralPermissionManager = await GeneralPermissionManager.at(tx.logs[2].args._module); }); - }); describe("General Permission Manager test cases", async () => { @@ -201,51 +210,43 @@ contract('GeneralPermissionManager', accounts => { assert.equal(web3.utils.toAscii(tx).replace(/\u0000/g, ""), 0); }); - it("Should fail in adding the delegate -- msg.sender doesn't have permission", async() => { + it("Should fail in adding the delegate -- msg.sender doesn't have permission", async () => { let errorThrown = false; - await catchRevert( - I_GeneralPermissionManager.addDelegate(account_delegate, delegateDetails, { from: account_investor1}) - ); + await catchRevert(I_GeneralPermissionManager.addDelegate(account_delegate, delegateDetails, { from: account_investor1 })); }); - it("Should fail in adding the delegate -- no delegate details provided", async() => { - await catchRevert( - I_GeneralPermissionManager.addDelegate(account_delegate, '', { from: token_owner }) - ); + it("Should fail in adding the delegate -- no delegate details provided", async () => { + await catchRevert(I_GeneralPermissionManager.addDelegate(account_delegate, "0x0", { from: token_owner })); }); - it("Should fail in adding the delegate -- no delegate address provided", async() => { - await catchRevert( - I_GeneralPermissionManager.addDelegate('', delegateDetails, { from: token_owner }) - ); + it("Should fail in adding the delegate -- no delegate address provided", async () => { + await catchRevert(I_GeneralPermissionManager.addDelegate(address_zero, delegateDetails, { from: token_owner })); }); - it("Should fail to remove the delegate -- failed because delegate does not exisit", async() => { - await catchRevert( - I_GeneralPermissionManager.deleteDelegate(account_delegate, { from: token_owner}) - ); + it("Should fail to remove the delegate -- failed because delegate does not exisit", async () => { + await catchRevert(I_GeneralPermissionManager.deleteDelegate(account_delegate, { from: token_owner })); }); - it("Should successfully add the delegate", async() => { - let tx = await I_GeneralPermissionManager.addDelegate(account_delegate, delegateDetails, { from: token_owner}); + it("Should successfully add the delegate", async () => { + let tx = await I_GeneralPermissionManager.addDelegate(account_delegate, delegateDetails, { from: token_owner }); assert.equal(tx.logs[0].args._delegate, account_delegate); }); - it("Should successfully add the delegate -- failed because trying to add the already present delegate", async() => { + it("Should successfully add the delegate -- failed because trying to add the already present delegate", async () => { + await catchRevert(I_GeneralPermissionManager.addDelegate(account_delegate, delegateDetails, { from: token_owner })); + }); + + it("Should fail to provide the permission -- because msg.sender doesn't have permission", async () => { await catchRevert( - I_GeneralPermissionManager.addDelegate(account_delegate, delegateDetails, { from: token_owner}) + I_GeneralPermissionManager.changePermission(account_delegate, I_GeneralTransferManager.address, web3.utils.fromAscii("WHITELIST"), true, { + from: account_investor1 + }) ); - }) - - it("Should fail to provide the permission -- because msg.sender doesn't have permission", async() => { - await catchRevert( - I_GeneralPermissionManager.changePermission(account_delegate, I_GeneralTransferManager.address, "WHITELIST", true, {from: account_investor1}) - ); }); it("Should check the permission", async () => { assert.isFalse( - await I_GeneralPermissionManager.checkPermission.call(account_delegate, I_GeneralTransferManager.address, "WHITELIST") + await I_GeneralPermissionManager.checkPermission.call(account_delegate, I_GeneralTransferManager.address, web3.utils.fromAscii("WHITELIST")) ); }); @@ -253,7 +254,7 @@ contract('GeneralPermissionManager', accounts => { let tx = await I_GeneralPermissionManager.changePermission( account_delegate, I_GeneralTransferManager.address, - "WHITELIST", + web3.utils.fromAscii("WHITELIST"), true, { from: token_owner } ); @@ -262,141 +263,221 @@ contract('GeneralPermissionManager', accounts => { it("Should check the permission", async () => { assert.isTrue( - await I_GeneralPermissionManager.checkPermission.call(account_delegate, I_GeneralTransferManager.address, "WHITELIST") + await I_GeneralPermissionManager.checkPermission.call(account_delegate, I_GeneralTransferManager.address, web3.utils.fromAscii("WHITELIST")) ); }); it("Security token should deny all permission if all permission managers are disabled", async () => { await I_SecurityToken.archiveModule(I_GeneralPermissionManager.address, { from: token_owner }); - assert.isFalse( - await I_SecurityToken.checkPermission.call(account_delegate, I_GeneralTransferManager.address, "WHITELIST") - ); + assert.isFalse(await stGetter.checkPermission.call(account_delegate, I_GeneralTransferManager.address, web3.utils.fromAscii("WHITELIST"))); await I_SecurityToken.unarchiveModule(I_GeneralPermissionManager.address, { from: token_owner }); - assert.isTrue( - await I_SecurityToken.checkPermission.call(account_delegate, I_GeneralTransferManager.address, "WHITELIST") - ); + assert.isTrue(await stGetter.checkPermission.call(account_delegate, I_GeneralTransferManager.address, web3.utils.fromAscii("WHITELIST"))); }); - it("Should fail to remove the delegate -- failed because unauthorized msg.sender", async() => { - await catchRevert( - I_GeneralPermissionManager.deleteDelegate(account_delegate, { from: account_delegate}) - ); + it("Should fail to remove the delegate -- failed because unauthorized msg.sender", async () => { + await catchRevert(I_GeneralPermissionManager.deleteDelegate(account_delegate, { from: account_delegate })); }); - it("Should remove the delegate", async() => { - await I_GeneralPermissionManager.deleteDelegate(account_delegate, { from: token_owner}) + it("Should remove the delegate", async () => { + await I_GeneralPermissionManager.deleteDelegate(account_delegate, { from: token_owner }); }); it("Should check the permission", async () => { assert.isFalse( - await I_GeneralPermissionManager.checkPermission.call(account_delegate, I_GeneralTransferManager.address, "WHITELIST") + await I_GeneralPermissionManager.checkPermission.call(account_delegate, I_GeneralTransferManager.address, web3.utils.fromAscii("WHITELIST")) ); }); - it("Should successfully add the delegate", async() => { - let tx = await I_GeneralPermissionManager.addDelegate(account_delegate, delegateDetails, { from: token_owner}); + it("Should successfully add the delegate", async () => { + let tx = await I_GeneralPermissionManager.addDelegate(account_delegate, delegateDetails, { from: token_owner }); assert.equal(tx.logs[0].args._delegate, account_delegate); }); - it("Should check the delegate details", async() => { - assert.equal(web3.utils.toAscii(await I_GeneralPermissionManager.delegateDetails.call(account_delegate)) - .replace(/\u0000/g, ''), - delegateDetails, - "Wrong delegate address get checked"); + it("Should check the delegate details", async () => { + assert.equal( + web3.utils.toAscii(await I_GeneralPermissionManager.delegateDetails.call(account_delegate)).replace(/\u0000/g, ""), + web3.utils.toAscii(delegateDetails), + "Wrong delegate address get checked" + ); }); it("Should get the permission of the general permission manager contract", async () => { let tx = await I_GeneralPermissionManager.getPermissions.call(); - assert.equal(web3.utils.toAscii(tx[0]).replace(/\u0000/g, ""), "CHANGE_PERMISSION", "Wrong permissions"); - }); - - it("Should return all delegates", async() => { - await I_GeneralPermissionManager.addDelegate(account_delegate2, delegateDetails, { from: token_owner}); + assert.equal(web3.utils.toAscii(tx[0]).replace(/\u0000/g, ""), "ADMIN", "Wrong permissions"); + }); + + it("Should return all delegates", async () => { + assert.equal((await I_SecurityTokenRegistryInterface.getTokensByDelegate.call(account_delegate))[0], I_SecurityToken.address); + assert.equal((await I_SecurityTokenRegistryInterface.getTokensByDelegate.call(account_delegate)).length, 1); + assert.equal((await I_SecurityTokenRegistryInterface.getTokensByDelegate.call(account_delegate2)).length, 0); + await I_GeneralPermissionManager.addDelegate(account_delegate2, delegateDetails, { from: token_owner }); + assert.equal((await I_SecurityTokenRegistryInterface.getTokensByDelegate.call(account_delegate))[0], I_SecurityToken.address); + assert.equal((await I_SecurityTokenRegistryInterface.getTokensByDelegate.call(account_delegate)).length, 1); + assert.equal((await I_SecurityTokenRegistryInterface.getTokensByDelegate.call(account_delegate2))[0], I_SecurityToken.address); + assert.equal((await I_SecurityTokenRegistryInterface.getTokensByDelegate.call(account_delegate2)).length, 1); let tx = await I_GeneralPermissionManager.getAllDelegates.call(); assert.equal(tx.length, 2); - assert.equal(tx[0], account_delegate); + assert.equal(tx[0], account_delegate); assert.equal(tx[1], account_delegate2); }); - it("Should check is delegate for 0x address - failed 0x address is not allowed", async() => { - await catchRevert( - I_GeneralPermissionManager.checkDelegate.call("0x0000000000000000000000000000000000000000000000000") + it("Should create a new token and add some more delegates, then get them", async() => { + await I_PolyToken.getTokens(web3.utils.toWei("500", "ether"), token_owner); + await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); + let tx1 = await I_STRProxied.registerNewTicker(token_owner, "DEL", { from: token_owner }); + assert.equal(tx1.logs[0].args._owner, token_owner); + assert.equal(tx1.logs[0].args._ticker, "DEL"); + + await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); + let _blockNo = latestBlock(); + let tx2 = await I_STRProxied.generateNewSecurityToken(name, "DEL", tokenDetails, false, token_owner, 0, { from: token_owner }); + + // Verify the successful generation of the security token + assert.equal(tx2.logs[1].args._ticker, "DEL", "SecurityToken doesn't get deployed"); + + let I_SecurityToken_DEL = await SecurityToken.at(tx2.logs[1].args._securityTokenAddress); + + const tx = await I_SecurityToken_DEL.addModule(I_GeneralPermissionManagerFactory.address, "0x", 0, 0, false, { from: token_owner }); + assert.equal(tx.logs[2].args._types[0].toNumber(), delegateManagerKey, "General Permission Manager doesn't get deployed"); + assert.equal( + web3.utils.toAscii(tx.logs[2].args._name).replace(/\u0000/g, ""), + "GeneralPermissionManager", + "GeneralPermissionManagerFactory module was not added" ); - }); - it("Should return false when check is delegate - because user is not a delegate", async() => { + let I_GeneralPermissionManager_DEL = await GeneralPermissionManager.at(tx.logs[2].args._module); + await I_GeneralPermissionManager_DEL.addDelegate(account_delegate3, delegateDetails, { from: token_owner}); + + assert.equal((await I_SecurityTokenRegistryInterface.getTokensByDelegate.call(account_delegate))[0], I_SecurityToken.address); + assert.equal((await I_SecurityTokenRegistryInterface.getTokensByDelegate.call(account_delegate2))[0], I_SecurityToken.address); + assert.equal((await I_SecurityTokenRegistryInterface.getTokensByDelegate.call(account_delegate3))[0], I_SecurityToken_DEL.address); + assert.equal((await I_SecurityTokenRegistryInterface.getTokensByDelegate.call(account_delegate)).length, 1); + assert.equal((await I_SecurityTokenRegistryInterface.getTokensByDelegate.call(account_delegate2)).length, 1); + assert.equal((await I_SecurityTokenRegistryInterface.getTokensByDelegate.call(account_delegate3)).length, 1); + await I_GeneralPermissionManager_DEL.addDelegate(account_delegate2, delegateDetails, { from: token_owner}); + assert.equal((await I_SecurityTokenRegistryInterface.getTokensByDelegate.call(account_delegate))[0], I_SecurityToken.address); + assert.equal((await I_SecurityTokenRegistryInterface.getTokensByDelegate.call(account_delegate2))[0], I_SecurityToken.address); + assert.equal((await I_SecurityTokenRegistryInterface.getTokensByDelegate.call(account_delegate2))[1], I_SecurityToken_DEL.address); + assert.equal((await I_SecurityTokenRegistryInterface.getTokensByDelegate.call(account_delegate3))[0], I_SecurityToken_DEL.address); + assert.equal((await I_SecurityTokenRegistryInterface.getTokensByDelegate.call(account_delegate)).length, 1); + assert.equal((await I_SecurityTokenRegistryInterface.getTokensByDelegate.call(account_delegate2)).length, 2); + assert.equal((await I_SecurityTokenRegistryInterface.getTokensByDelegate.call(account_delegate3)).length, 1); + let tx4 = await I_GeneralPermissionManager_DEL.getAllDelegates.call(); + assert.equal(tx4.length, 2); + assert.equal(tx4[0], account_delegate3, account_delegate2); + }); + + it("Should check is delegate for 0x address - failed 0x address is not allowed", async () => { + await catchRevert(I_GeneralPermissionManager.checkDelegate.call(address_zero)); + }); + + it("Should return false when check is delegate - because user is not a delegate", async () => { assert.equal(await I_GeneralPermissionManager.checkDelegate.call(account_investor1), false); }); - it("Should return true when check is delegate - because user is a delegate", async() => { + it("Should return true when check is delegate - because user is a delegate", async () => { assert.equal(await I_GeneralPermissionManager.checkDelegate.call(account_delegate), true); }); - - it("Should successfully provide the permissions in batch -- failed because of array length is 0", async() => { - await I_GeneralPermissionManager.addDelegate(account_delegate3, delegateDetails, { from: token_owner}); + it("Should successfully provide the permissions in batch -- failed because of array length is 0", async () => { + await I_GeneralPermissionManager.addDelegate(account_delegate3, delegateDetails, { from: token_owner }); await catchRevert( - I_GeneralPermissionManager.changePermissionMulti(account_delegate3, [], ["WHITELIST","CHANGE_PERMISSION"], [true, true], {from: token_owner}) + I_GeneralPermissionManager.changePermissionMulti(account_delegate3, [], [web3.utils.fromAscii("ADMIN"), web3.utils.fromAscii("ADMIN")], [true, true], { + from: token_owner + }) ); }); - it("Should successfully provide the permissions in batch -- failed because of perm array length is 0", async() => { + it("Should successfully provide the permissions in batch -- failed because of perm array length is 0", async () => { await catchRevert( - I_GeneralPermissionManager.changePermissionMulti(account_delegate3, [I_GeneralTransferManager.address, I_GeneralPermissionManager.address], [], [true, true], {from: token_owner}) + I_GeneralPermissionManager.changePermissionMulti( + account_delegate3, + [I_GeneralTransferManager.address, I_GeneralPermissionManager.address], + [], + [true, true], + { from: token_owner } + ) ); }); - it("Should successfully provide the permissions in batch -- failed because mismatch in arrays length", async() => { + it("Should successfully provide the permissions in batch -- failed because mismatch in arrays length", async () => { await catchRevert( - I_GeneralPermissionManager.changePermissionMulti(account_delegate3, [I_GeneralTransferManager.address], ["WHITELIST","CHANGE_PERMISSION"], [true, true], {from: token_owner}) + I_GeneralPermissionManager.changePermissionMulti( + account_delegate3, + [I_GeneralTransferManager.address], + [web3.utils.fromAscii("ADMIN"), web3.utils.fromAscii("ADMIN")], + [true, true], + { from: token_owner } + ) ); }); - it("Should successfully provide the permissions in batch -- failed because mismatch in arrays length", async() => { + it("Should successfully provide the permissions in batch -- failed because mismatch in arrays length", async () => { await catchRevert( - I_GeneralPermissionManager.changePermissionMulti(account_delegate3, [I_GeneralTransferManager.address, I_GeneralPermissionManager.address], ["WHITELIST","CHANGE_PERMISSION"], [true], {from: token_owner}) + I_GeneralPermissionManager.changePermissionMulti( + account_delegate3, + [I_GeneralTransferManager.address, I_GeneralPermissionManager.address], + [web3.utils.fromAscii("ADMIN"), web3.utils.fromAscii("ADMIN")], + [true], + { from: token_owner } + ) ); }); - it("Should successfully provide the permissions in batch", async() => { - let tx = await I_GeneralPermissionManager.changePermissionMulti(account_delegate3, [I_GeneralTransferManager.address, I_GeneralPermissionManager.address], ["WHITELIST","CHANGE_PERMISSION"], [true, true], {from: token_owner}); + it("Should successfully provide the permissions in batch", async () => { + let tx = await I_GeneralPermissionManager.changePermissionMulti( + account_delegate3, + [I_GeneralTransferManager.address, I_GeneralPermissionManager.address], + [web3.utils.fromAscii("ADMIN"), web3.utils.fromAscii("ADMIN")], + [true, true], + { from: token_owner } + ); assert.equal(tx.logs[0].args._delegate, account_delegate3); - assert.isTrue(await I_GeneralPermissionManager.checkPermission.call(account_delegate3, I_GeneralTransferManager.address, "WHITELIST")); - assert.isTrue(await I_GeneralPermissionManager.checkPermission.call(account_delegate3, I_GeneralPermissionManager.address, "CHANGE_PERMISSION")); + assert.isTrue( + await I_GeneralPermissionManager.checkPermission.call(account_delegate3, I_GeneralTransferManager.address, web3.utils.fromAscii("ADMIN")) + ); + assert.isTrue( + await I_GeneralPermissionManager.checkPermission.call( + account_delegate3, + I_GeneralPermissionManager.address, + web3.utils.fromAscii("ADMIN") + ) + ); }); - it("Should provide all delegates with specified permission", async() => { - await I_GeneralPermissionManager.changePermission(account_delegate2, I_GeneralTransferManager.address, "WHITELIST", true, {from: token_owner}); - let tx = await I_GeneralPermissionManager.getAllDelegatesWithPerm.call(I_GeneralTransferManager.address, "WHITELIST"); - assert.equal(tx.length, 3); - assert.equal(tx[0], account_delegate); - assert.equal(tx[1], account_delegate2); + it("Should provide all delegates with specified permission", async () => { + await I_GeneralPermissionManager.changePermission(account_delegate2, I_GeneralTransferManager.address, web3.utils.fromAscii("ADMIN"), true, { + from: token_owner + }); + let tx = await I_GeneralPermissionManager.getAllDelegatesWithPerm.call(I_GeneralTransferManager.address, web3.utils.fromAscii("ADMIN")); + assert.equal(tx.length, 2); + assert.equal(tx[0], account_delegate2); + assert.equal(tx[1], account_delegate3); }); - it("Should get all delegates for the permission manager", async() => { - let tx = await I_GeneralPermissionManager.getAllDelegatesWithPerm.call(I_GeneralPermissionManager.address, "CHANGE_PERMISSION"); + it("Should get all delegates for the permission manager", async () => { + let tx = await I_GeneralPermissionManager.getAllDelegatesWithPerm.call(I_GeneralPermissionManager.address, web3.utils.fromAscii("ADMIN")); assert.equal(tx.length, 1); assert.equal(tx[0], account_delegate3); - }) + }); - it("Should return all modules and all permission", async() => { - let tx = await I_GeneralPermissionManager.getAllModulesAndPermsFromTypes.call(account_delegate3, [2,1]); + it("Should return all modules and all permission", async () => { + let tx = await I_GeneralPermissionManager.getAllModulesAndPermsFromTypes.call(account_delegate3, [2, 1]); assert.equal(tx[0][0], I_GeneralTransferManager.address); - assert.equal(tx[1][0], "0x57484954454c4953540000000000000000000000000000000000000000000000"); + assert.equal(web3.utils.hexToUtf8(tx[1][0]), "ADMIN"); assert.equal(tx[0][1], I_GeneralPermissionManager.address); - assert.equal(tx[1][1], "0x4348414e47455f5045524d495353494f4e000000000000000000000000000000"); + assert.equal(web3.utils.hexToUtf8(tx[1][1]), "ADMIN"); }); - }); describe("General Permission Manager Factory test cases", async () => { it("should get the exact details of the factory", async () => { - assert.equal(await I_GeneralPermissionManagerFactory.getSetupCost.call(), 0); + assert.equal(await I_GeneralPermissionManagerFactory.setupCost.call(), 0); assert.equal((await I_GeneralPermissionManagerFactory.getTypes.call())[0], 1); - assert.equal(await I_GeneralPermissionManagerFactory.version.call(), "1.0.0"); + assert.equal(await I_GeneralPermissionManagerFactory.version.call(), "3.0.0"); assert.equal( - web3.utils.toAscii(await I_GeneralPermissionManagerFactory.getName.call()).replace(/\u0000/g, ""), + web3.utils.toAscii(await I_GeneralPermissionManagerFactory.name.call()).replace(/\u0000/g, ""), "GeneralPermissionManager", "Wrong Module added" ); @@ -406,21 +487,12 @@ contract('GeneralPermissionManager', accounts => { "Wrong Module added" ); assert.equal(await I_GeneralPermissionManagerFactory.title.call(), "General Permission Manager", "Wrong Module added"); - assert.equal( - await I_GeneralPermissionManagerFactory.getInstructions.call(), - "Add and remove permissions for the SecurityToken and associated modules. Permission types should be encoded as bytes32 values and attached using withPerm modifier to relevant functions. No initFunction required.", - "Wrong Module added" - ); }); it("Should get the tags of the factory", async () => { let tags = await I_GeneralPermissionManagerFactory.getTags.call(); - assert.equal(tags.length, 0); + assert.equal(web3.utils.toUtf8(tags[0]), "Permission Management"); }); - it("Should ge the version of the factory", async() => { - let version = await I_GeneralPermissionManagerFactory.version.call(); - assert.equal(version, "1.0.0"); - }) }); }); diff --git a/test/h_general_transfer_manager.js b/test/h_general_transfer_manager.js index 76a9d5806..3b156fd97 100644 --- a/test/h_general_transfer_manager.js +++ b/test/h_general_transfer_manager.js @@ -1,23 +1,23 @@ import latestTime from "./helpers/latestTime"; import { duration, promisifyLogWatch, latestBlock } from "./helpers/utils"; -import takeSnapshot, { increaseTime, revertToSnapshot } from "./helpers/time"; -import { signData } from "./helpers/signData"; +import { getSignGTMData, getSignGTMTransferData, getMultiSignGTMData } from "./helpers/signData"; +import { takeSnapshot, increaseTime, revertToSnapshot } from "./helpers/time"; import { pk } from "./helpers/testprivateKey"; import { encodeProxyCall, encodeModuleCall } from "./helpers/encodeCall"; import { catchRevert } from "./helpers/exceptions"; import { setUpPolymathNetwork, deployGPMAndVerifyed, deployDummySTOAndVerifyed, deployGTMAndVerifyed } from "./helpers/createInstances"; - const DummySTO = artifacts.require("./DummySTO.sol"); const SecurityToken = artifacts.require("./SecurityToken.sol"); const GeneralTransferManager = artifacts.require("./GeneralTransferManager"); const GeneralPermissionManager = artifacts.require("./GeneralPermissionManager"); +const STGetter = artifacts.require("./STGetter.sol"); const Web3 = require("web3"); -const BigNumber = require("bignumber.js"); +let BN = Web3.utils.BN; const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); // Hardcoded development port -contract("GeneralTransferManager", accounts => { +contract("GeneralTransferManager", async (accounts) => { // Accounts Variable declaration let account_polymath; let account_issuer; @@ -32,9 +32,9 @@ contract("GeneralTransferManager", accounts => { let account_affiliates2; // investor Details - let fromTime = latestTime(); - let toTime = latestTime(); - let expiryTime = toTime + duration.days(15); + let fromTime; + let toTime; + let expiryTime; let message = "Transaction Should Fail!"; @@ -58,6 +58,9 @@ contract("GeneralTransferManager", accounts => { let I_PolyToken; let I_PolymathRegistry; let P_GeneralTransferManagerFactory; + let I_STRGetter; + let I_STGetter; + let stGetter; // SecurityToken Details const name = "Team"; @@ -72,17 +75,29 @@ contract("GeneralTransferManager", accounts => { const stoKey = 3; // Initial fee for ticker registry and security token registry - const initRegFee = web3.utils.toWei("250"); + const initRegFee = new BN(web3.utils.toWei("1000")); // Dummy STO details - const startTime = latestTime() + duration.seconds(5000); // Start time will be 5000 seconds more than the latest time - const endTime = startTime + duration.days(80); // Add 80 days more - const cap = web3.utils.toWei("10", "ether"); + let startTime; + let endTime; + const cap = new BN(web3.utils.toWei("10", "ether")); const someString = "A string which is not used"; const STOParameters = ["uint256", "uint256", "uint256", "string"]; + let currentTime; + const address_zero = "0x0000000000000000000000000000000000000000"; + const one_address = "0x0000000000000000000000000000000000000001"; + let signer; + let snapid; + before(async () => { - // Accounts setup + currentTime = new BN(await latestTime()); + fromTime = await latestTime(); + toTime = await latestTime(); + expiryTime = toTime + duration.days(15); + startTime = await latestTime() + duration.seconds(5000); // Start time will be 5000 seconds more than the latest time + endTime = startTime + duration.days(80); // Add 80 days more + account_polymath = accounts[0]; account_issuer = accounts[1]; @@ -98,6 +113,12 @@ contract("GeneralTransferManager", accounts => { account_affiliates1 = accounts[3]; account_affiliates2 = accounts[4]; + let oneeth = new BN(web3.utils.toWei("1", "ether")); + signer = web3.eth.accounts.create(); + await web3.eth.personal.importRawKey(signer.privateKey, ""); + await web3.eth.personal.unlockAccount(signer.address, "", 6000); + await web3.eth.sendTransaction({ from: token_owner, to: signer.address, value: oneeth }); + // Step 1: Deploy the genral PM ecosystem let instances = await setUpPolymathNetwork(account_polymath, token_owner); @@ -112,13 +133,15 @@ contract("GeneralTransferManager", accounts => { I_STFactory, I_SecurityTokenRegistry, I_SecurityTokenRegistryProxy, - I_STRProxied + I_STRProxied, + I_STRGetter, + I_STGetter ] = instances; - [I_GeneralPermissionManagerFactory] = await deployGPMAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, 0); - [P_GeneralTransferManagerFactory] = await deployGTMAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, web3.utils.toWei("500")); - [I_DummySTOFactory] = await deployDummySTOAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, 0); - [P_DummySTOFactory] = await deployDummySTOAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, web3.utils.toWei("500")); + [I_GeneralPermissionManagerFactory] = await deployGPMAndVerifyed(account_polymath, I_MRProxied, 0); + [P_GeneralTransferManagerFactory] = await deployGTMAndVerifyed(account_polymath, I_MRProxied, new BN(web3.utils.toWei("500"))); + [I_DummySTOFactory] = await deployDummySTOAndVerifyed(account_polymath, I_MRProxied, 0); + [P_DummySTOFactory] = await deployDummySTOAndVerifyed(account_polymath, I_MRProxied, new BN(web3.utils.toWei("500"))); // Printing all the contract addresses console.log(` @@ -142,183 +165,254 @@ contract("GeneralTransferManager", accounts => { describe("Generate the SecurityToken", async () => { it("Should register the ticker before the generation of the security token", async () => { await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); - let tx = await I_STRProxied.registerTicker(token_owner, symbol, contact, { from: token_owner }); + let tx = await I_STRProxied.registerNewTicker(token_owner, symbol, { from: token_owner }); assert.equal(tx.logs[0].args._owner, token_owner); assert.equal(tx.logs[0].args._ticker, symbol.toUpperCase()); }); it("Should generate the new security token with the same symbol as registered above", async () => { await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); - let _blockNo = latestBlock(); - let tx = await I_STRProxied.generateSecurityToken(name, symbol, tokenDetails, false, { from: token_owner }); + let tx = await I_STRProxied.generateNewSecurityToken(name, symbol, tokenDetails, false, token_owner, 0, { from: token_owner }); // 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 log = await promisifyLogWatch(I_SecurityToken.ModuleAdded({ from: _blockNo }), 1); + I_SecurityToken = await SecurityToken.at(tx.logs[1].args._securityTokenAddress); + stGetter = await STGetter.at(I_SecurityToken.address); + const log = (await I_SecurityToken.getPastEvents('ModuleAdded', {filter: {transactionHash: tx.transactionHash}}))[0]; // Verify that GeneralTransferManager module get added successfully or not assert.equal(log.args._types[0].toNumber(), 2); assert.equal(web3.utils.toAscii(log.args._name).replace(/\u0000/g, ""), "GeneralTransferManager"); }); - it("Should intialize the auto attached modules", async () => { - let moduleData = (await I_SecurityToken.getModulesByType(2))[0]; - I_GeneralTransferManager = GeneralTransferManager.at(moduleData); + it("Should initialize the auto attached modules", async () => { + let moduleData = (await stGetter.getModulesByType(2))[0]; + I_GeneralTransferManager = await GeneralTransferManager.at(moduleData); }); - it("Should attach the paid GTM -- failed because of no tokens", async() => { + it("Should attach the paid GTM -- failed because of no tokens", async () => { await catchRevert( - I_SecurityToken.addModule(P_GeneralTransferManagerFactory.address, "", web3.utils.toWei("500"), 0, {from: account_issuer}) + I_SecurityToken.addModule(P_GeneralTransferManagerFactory.address, "0x0", new BN(web3.utils.toWei("2000")), new BN(0), false, { from: account_issuer }) ); }); - it("Should attach the paid GTM", async() => { + it("Should attach the paid GTM", async () => { + let snap_id = await takeSnapshot(); + await I_PolyToken.getTokens(new BN(web3.utils.toWei("2000")), I_SecurityToken.address); + await I_SecurityToken.addModule(P_GeneralTransferManagerFactory.address, "0x0", new BN(web3.utils.toWei("2000")), new BN(0), false, { + from: account_issuer + }); + await revertToSnapshot(snap_id); + }); + + it("Should add investor flags", async () => { let snap_id = await takeSnapshot(); - await I_PolyToken.getTokens(web3.utils.toWei("500"), I_SecurityToken.address); - await I_SecurityToken.addModule(P_GeneralTransferManagerFactory.address, "", web3.utils.toWei("500"), 0, {from: account_issuer}); + await I_GeneralTransferManager.modifyInvestorFlagMulti([account_investor1, account_investor1, account_investor2], [0, 1, 1], [true, true, true], { from: account_issuer }); + let investors = await I_GeneralTransferManager.getInvestors(0, 1); + assert.equal(investors[0], account_investor1); + assert.equal(investors[1], account_investor2); + let investorCount = await stGetter.getInvestorCount(); + assert.equal(investorCount.toNumber(), 2); + let allInvestorFlags = await I_GeneralTransferManager.getAllInvestorFlags(); + assert.deepEqual(investors, allInvestorFlags[0]); + assert.equal(allInvestorFlags[1][0].toNumber(), 3)//0x000....00011 + assert.equal(allInvestorFlags[1][1].toNumber(), 2)//0x000....00010 + let investorFlags = await I_GeneralTransferManager.getInvestorFlags(allInvestorFlags[0][0]); + assert.equal(investorFlags, 3)//0x000....00011 await revertToSnapshot(snap_id); }); it("Should whitelist the affiliates before the STO attached", async () => { - let tx = await I_GeneralTransferManager.modifyWhitelistMulti( + console.log(`Estimate gas of one Whitelist: + ${await I_GeneralTransferManager.modifyKYCData.estimateGas( + account_affiliates1, + currentTime + currentTime.add(new BN(duration.days(30))), + currentTime + currentTime.add(new BN(duration.days(90))), + currentTime + currentTime.add(new BN(duration.days(965))), + { + from: account_issuer + } + )}` + ); + let fromTime1 = currentTime + currentTime.add(new BN(duration.days(30))); + let fromTime2 = currentTime.add(new BN(duration.days(30))); + let toTime1 = currentTime + currentTime.add(new BN(duration.days(90))); + let toTime2 = currentTime.add(new BN(duration.days(90))); + let expiryTime1 = currentTime + currentTime.add(new BN(duration.days(965))); + let expiryTime2 = currentTime.add(new BN(duration.days(365))); + + let tx = await I_GeneralTransferManager.modifyKYCDataMulti( [account_affiliates1, account_affiliates2], - [latestTime() + duration.days(30), latestTime() + duration.days(30)], - [latestTime() + duration.days(90), latestTime() + duration.days(90)], - [latestTime() + duration.years(1), latestTime() + duration.years(1)], - [false, false], + [fromTime1, fromTime2], + [toTime1, toTime2], + [expiryTime1, expiryTime2], { from: account_issuer, gas: 6000000 } ); + await I_GeneralTransferManager.modifyInvestorFlagMulti([account_affiliates1, account_affiliates2], [1, 1], [true, true], { from: account_issuer }); assert.equal(tx.logs[0].args._investor, account_affiliates1); assert.equal(tx.logs[1].args._investor, account_affiliates2); - assert.deepEqual(await I_GeneralTransferManager.getInvestors.call(), [account_affiliates1, account_affiliates2]); - console.log(await I_GeneralTransferManager.getAllInvestorsData.call()); - console.log(await I_GeneralTransferManager.getInvestorsData.call([account_affiliates1, account_affiliates2])); + assert.deepEqual(await I_GeneralTransferManager.getAllInvestors.call(), [account_affiliates1, account_affiliates2]); + console.log(await I_GeneralTransferManager.getAllKYCData.call()); + let data = await I_GeneralTransferManager.getKYCData.call([account_affiliates1, account_affiliates2]); + assert.equal(data[0][0].toString(), fromTime1); + assert.equal(data[0][1].toString(), fromTime2); + assert.equal(data[1][0].toString(), toTime1); + assert.equal(data[1][1].toString(), toTime2); + assert.equal(data[2][0].toString(), expiryTime1); + assert.equal(data[2][1].toString(), expiryTime2); + assert.equal(await I_GeneralTransferManager.getInvestorFlag(account_affiliates1, 1), true); + assert.equal(await I_GeneralTransferManager.getInvestorFlag(account_affiliates2, 1), true); }); it("Should whitelist lots of addresses and check gas", async () => { let mockInvestors = []; for (let i = 0; i < 50; i++) { - mockInvestors.push("0x1000000000000000000000000000000000000000".substring(0,42-i.toString().length) + i.toString()); + mockInvestors.push("0x1000000000000000000000000000000000000000".substring(0, 42 - i.toString().length) + i.toString()); } let times = range1(50); let bools = rangeB(50); - let tx = await I_GeneralTransferManager.modifyWhitelistMulti( - mockInvestors, - times, - times, - times, - bools, - { - from: account_issuer, - gas: 7900000 - } - ); + let tx = await I_GeneralTransferManager.modifyKYCDataMulti(mockInvestors, times, times, times, { + from: account_issuer + }); console.log("Multi Whitelist x 50: " + tx.receipt.gasUsed); - assert.deepEqual(await I_GeneralTransferManager.getInvestors.call(), [account_affiliates1, account_affiliates2].concat(mockInvestors)); + assert.deepEqual( + await I_GeneralTransferManager.getAllInvestors.call(), + [account_affiliates1, account_affiliates2].concat(mockInvestors) + ); }); it("Should mint the tokens to the affiliates", async () => { - await I_SecurityToken.mintMulti([account_affiliates1, account_affiliates2], [100 * Math.pow(10, 18), 100 * Math.pow(10, 18)], { + console.log(` + Estimate gas cost for minting the tokens: ${await I_SecurityToken.issueMulti.estimateGas([account_affiliates1, account_affiliates2], [new BN(100).mul(new BN(10).pow(new BN(18))), new BN(10).pow(new BN(20))], { + from: account_issuer + })} + `) + await I_SecurityToken.issueMulti([account_affiliates1, account_affiliates2], [new BN(100).mul(new BN(10).pow(new BN(18))), new BN(10).pow(new BN(20))], { from: account_issuer, gas: 6000000 }); - assert.equal((await I_SecurityToken.balanceOf.call(account_affiliates1)).dividedBy(new BigNumber(10).pow(18)).toNumber(), 100); - assert.equal((await I_SecurityToken.balanceOf.call(account_affiliates2)).dividedBy(new BigNumber(10).pow(18)).toNumber(), 100); + assert.equal((await I_SecurityToken.balanceOf.call(account_affiliates1)).div(new BN(10).pow(new BN(18))).toNumber(), 100); + assert.equal((await I_SecurityToken.balanceOf.call(account_affiliates2)).div(new BN(10).pow(new BN(18))).toNumber(), 100); }); - it("Should successfully attach the STO factory with the security token -- failed because of no tokens", async () => { let bytesSTO = encodeModuleCall(STOParameters, [ - latestTime() + duration.seconds(1000), - latestTime() + duration.days(40), + await latestTime() + duration.seconds(1000), + await latestTime() + duration.days(40), cap, someString ]); await catchRevert( - I_SecurityToken.addModule(P_DummySTOFactory.address, bytesSTO, web3.utils.toWei("500"), 0, { from: token_owner }) + I_SecurityToken.addModule(P_DummySTOFactory.address, bytesSTO, new BN(web3.utils.toWei("2000")), new BN(0), false, { from: token_owner }) ); }); it("Should successfully attach the STO factory with the security token", async () => { let snap_id = await takeSnapshot(); let bytesSTO = encodeModuleCall(STOParameters, [ - latestTime() + duration.seconds(1000), - latestTime() + duration.days(40), + await latestTime() + duration.seconds(1000), + await latestTime() + duration.days(40), cap, someString ]); - await I_PolyToken.getTokens(web3.utils.toWei("500"), I_SecurityToken.address); - const tx = await I_SecurityToken.addModule(P_DummySTOFactory.address, bytesSTO, web3.utils.toWei("500"), 0, { from: token_owner }); + await I_PolyToken.getTokens(new BN(web3.utils.toWei("2000")), I_SecurityToken.address); + const tx = await I_SecurityToken.addModule(P_DummySTOFactory.address, bytesSTO, new BN(web3.utils.toWei("2000")), new BN(0), false, { + from: token_owner + }); assert.equal(tx.logs[3].args._types[0].toNumber(), stoKey, "DummySTO doesn't get deployed"); assert.equal( web3.utils.toAscii(tx.logs[3].args._name).replace(/\u0000/g, ""), "DummySTO", "DummySTOFactory module was not added" ); - I_DummySTO = DummySTO.at(tx.logs[3].args._module); + I_DummySTO = await DummySTO.at(tx.logs[3].args._module); await revertToSnapshot(snap_id); }); it("Should successfully attach the STO factory with the security token - invalid data", async () => { - let bytesSTO = encodeModuleCall(['uint256', 'string'], [ - latestTime() + duration.seconds(1000), - someString - ]); - await catchRevert( - I_SecurityToken.addModule(P_DummySTOFactory.address, bytesSTO, 0, 0, { from: token_owner }) - ); + let bytesSTO = encodeModuleCall(["uint256", "string"], [await latestTime() + duration.seconds(1000), someString]); + await catchRevert(I_SecurityToken.addModule(P_DummySTOFactory.address, bytesSTO, new BN(0), new BN(0), false, { from: token_owner })); }); it("Should successfully attach the STO factory with the security token", async () => { let bytesSTO = encodeModuleCall(STOParameters, [ - latestTime() + duration.seconds(1000), - latestTime() + duration.days(40), + await latestTime() + duration.seconds(1000), + await latestTime() + duration.days(40), cap, someString ]); - const tx = await I_SecurityToken.addModule(I_DummySTOFactory.address, bytesSTO, 0, 0, { from: token_owner }); + const tx = await I_SecurityToken.addModule(I_DummySTOFactory.address, bytesSTO, new BN(0), new BN(0), false, { from: token_owner }); assert.equal(tx.logs[2].args._types[0].toNumber(), stoKey, "DummySTO doesn't get deployed"); assert.equal( web3.utils.toAscii(tx.logs[2].args._name).replace(/\u0000/g, ""), "DummySTO", "DummySTOFactory module was not added" ); - I_DummySTO = DummySTO.at(tx.logs[2].args._module); + I_DummySTO = await DummySTO.at(tx.logs[2].args._module); }); it("Should successfully attach the permission manager factory with the security token", async () => { - const tx = await I_SecurityToken.addModule(I_GeneralPermissionManagerFactory.address, 0, 0, 0, { from: token_owner }); + const tx = await I_SecurityToken.addModule(I_GeneralPermissionManagerFactory.address, "0x0", new BN(0), new BN(0), false, { from: token_owner }); assert.equal(tx.logs[2].args._types[0].toNumber(), delegateManagerKey, "GeneralPermissionManager doesn't get deployed"); assert.equal( web3.utils.toAscii(tx.logs[2].args._name).replace(/\u0000/g, ""), "GeneralPermissionManager", "GeneralPermissionManager module was not added" ); - I_GeneralPermissionManager = GeneralPermissionManager.at(tx.logs[2].args._module); + I_GeneralPermissionManager = await GeneralPermissionManager.at(tx.logs[2].args._module); + }); + + it("should have transfer requirements initialized", async () => { + let transferRestrions = await I_GeneralTransferManager.transferRequirements(0); + assert.equal(transferRestrions[0], true); + assert.equal(transferRestrions[1], true); + assert.equal(transferRestrions[2], true); + assert.equal(transferRestrions[3], true); + transferRestrions = await I_GeneralTransferManager.transferRequirements(1); + assert.equal(transferRestrions[0], false); + assert.equal(transferRestrions[1], true); + assert.equal(transferRestrions[2], false); + assert.equal(transferRestrions[3], false); + transferRestrions = await I_GeneralTransferManager.transferRequirements(2); + assert.equal(transferRestrions[0], true); + assert.equal(transferRestrions[1], false); + assert.equal(transferRestrions[2], false); + assert.equal(transferRestrions[3], false); + }); + + it("should not allow unauthorized people to change transfer requirements", async () => { + await catchRevert( + I_GeneralTransferManager.modifyTransferRequirementsMulti( + [0, 1, 2], + [true, false, true], + [true, true, false], + [false, false, false], + [false, false, false], + { from: account_investor1 } + ) + ); + await catchRevert(I_GeneralTransferManager.modifyTransferRequirements(0, false, false, false, false, { from: account_investor1 })); }); }); describe("Buy tokens using on-chain whitelist", async () => { it("Should buy the tokens -- Failed due to investor is not in the whitelist", async () => { - await catchRevert(I_DummySTO.generateTokens(account_investor1, web3.utils.toWei("1", "ether"), { from: token_owner })); + await catchRevert(I_DummySTO.generateTokens(account_investor1, new BN(web3.utils.toWei("1", "ether")), { from: token_owner })); }); it("Should Buy the tokens", async () => { // Add the Investor in to the whitelist - let tx = await I_GeneralTransferManager.modifyWhitelist( + let tx = await I_GeneralTransferManager.modifyKYCData( account_investor1, - latestTime(), - latestTime(), - latestTime() + duration.days(10), - true, + currentTime, + currentTime, + currentTime.add(new BN(duration.days(10))), { from: account_issuer, gas: 6000000 @@ -335,37 +429,38 @@ contract("GeneralTransferManager", accounts => { await increaseTime(5000); // Mint some tokens - await I_DummySTO.generateTokens(account_investor1, web3.utils.toWei("1", "ether"), { from: token_owner }); + console.log( + `Gas usage of minting of tokens: ${await I_DummySTO.generateTokens.estimateGas(account_investor1, new BN(web3.utils.toWei("1", "ether")), { from: token_owner })}` + ) + await I_DummySTO.generateTokens(account_investor1, new BN(web3.utils.toWei("1", "ether")), { from: token_owner }); - assert.equal((await I_SecurityToken.balanceOf(account_investor1)).toNumber(), web3.utils.toWei("1", "ether")); + assert.equal((await I_SecurityToken.balanceOf(account_investor1)).toString(), new BN(web3.utils.toWei("1", "ether")).toString()); }); it("Should fail in buying the token from the STO", async () => { - await catchRevert(I_DummySTO.generateTokens(account_affiliates1, web3.utils.toWei("1", "ether"), { from: token_owner })); + await catchRevert(I_DummySTO.generateTokens(account_affiliates1, new BN(web3.utils.toWei("1", "ether")), { from: token_owner })); }); - it("Should fail in buying the tokens from the STO -- because amount is 0", async() => { - await catchRevert( - I_DummySTO.generateTokens(account_investor1, 0, { from: token_owner }) - ); + it("Should fail in buying the tokens from the STO -- because amount is 0", async () => { + await catchRevert(I_DummySTO.generateTokens(account_investor1, new BN(0), { from: token_owner })); }); - it("Should fail in buying the tokens from the STO -- because STO is paused", async() => { - await I_DummySTO.pause({from: account_issuer }); - await catchRevert(I_DummySTO.generateTokens(account_investor1, web3.utils.toWei("1", "ether"), { from: token_owner })); + it("Should fail in buying the tokens from the STO -- because STO is paused", async () => { + await I_DummySTO.pause({ from: account_issuer }); + await catchRevert(I_DummySTO.generateTokens(account_investor1, new BN(web3.utils.toWei("1", "ether")), { from: token_owner })); // Reverting the changes releated to pause - await I_DummySTO.unpause({from: account_issuer }); + await I_DummySTO.unpause({ from: account_issuer }); }); - it("Should buy more tokens from the STO to investor1", async() => { - await I_DummySTO.generateTokens(account_investor1, web3.utils.toWei("1", "ether"), { from: token_owner }); - assert.equal((await I_SecurityToken.balanceOf(account_investor1)).toNumber(), web3.utils.toWei("2", "ether")); + it("Should buy more tokens from the STO to investor1", async () => { + await I_DummySTO.generateTokens(account_investor1, new BN(web3.utils.toWei("1", "ether")), { from: token_owner }); + assert.equal((await I_SecurityToken.balanceOf(account_investor1)).toString(), new BN(web3.utils.toWei("2", "ether")).toString()); }); it("Should fail in investing the money in STO -- expiry limit reached", async () => { await increaseTime(duration.days(10)); - await catchRevert(I_DummySTO.generateTokens(account_investor1, web3.utils.toWei("1", "ether"), { from: token_owner })); + await catchRevert(I_DummySTO.generateTokens(account_investor1, new BN(web3.utils.toWei("1", "ether")), { from: token_owner })); }); }); @@ -375,17 +470,10 @@ contract("GeneralTransferManager", accounts => { it("Should Buy the tokens", async () => { // Add the Investor in to the whitelist // snap_id = await takeSnapshot(); - let tx = await I_GeneralTransferManager.modifyWhitelist( - account_investor1, - 0, - 0, - latestTime() + duration.days(20), - true, - { - from: account_issuer, - gas: 6000000 - } - ); + let tx = await I_GeneralTransferManager.modifyKYCData(account_investor1, new BN(0), new BN(0), currentTime.add(new BN(duration.days(20))), { + from: account_issuer, + gas: 6000000 + }); assert.equal( tx.logs[0].args._investor.toLowerCase(), @@ -393,12 +481,11 @@ contract("GeneralTransferManager", accounts => { "Failed in adding the investor in whitelist" ); - tx = await I_GeneralTransferManager.modifyWhitelist( + tx = await I_GeneralTransferManager.modifyKYCData( account_investor2, - latestTime(), - latestTime(), - latestTime() + duration.days(20), - true, + currentTime, + currentTime, + currentTime.add(new BN(duration.days(20))), { from: account_issuer, gas: 6000000 @@ -415,85 +502,81 @@ contract("GeneralTransferManager", accounts => { await increaseTime(5000); // Can transfer tokens - await I_SecurityToken.transfer(account_investor2, web3.utils.toWei("1", "ether"), {from: account_investor1}); - assert.equal((await I_SecurityToken.balanceOf(account_investor1)).toNumber(), web3.utils.toWei("1", "ether")); - assert.equal((await I_SecurityToken.balanceOf(account_investor1)).toNumber(), web3.utils.toWei("1", "ether")); + await I_SecurityToken.transfer(account_investor2, new BN(web3.utils.toWei("1", "ether")), { from: account_investor1 }); + assert.equal((await I_SecurityToken.balanceOf(account_investor1)).toString(), new BN(web3.utils.toWei("1", "ether")).toString()); + assert.equal((await I_SecurityToken.balanceOf(account_investor1)).toString(), new BN(web3.utils.toWei("1", "ether")).toString()); }); it("Add a from default and check transfers are disabled then enabled in the future", async () => { - let tx = await I_GeneralTransferManager.changeDefaults(latestTime() + duration.days(5), 0, {from: token_owner}); - await I_SecurityToken.transfer(account_investor1, web3.utils.toWei("1", "ether"), {from: account_investor2}); - await catchRevert(I_SecurityToken.transfer(account_investor2, web3.utils.toWei("1", "ether"), {from: account_investor1})); + let tx = await I_GeneralTransferManager.changeDefaults(currentTime.add(new BN(duration.days(12))), new BN(0), { from: token_owner }); + await I_SecurityToken.transfer(account_investor1, new BN(web3.utils.toWei("1", "ether")), { from: account_investor2 }); + await catchRevert(I_SecurityToken.transfer(account_investor2, new BN(web3.utils.toWei("1", "ether")), { from: account_investor1 })); await increaseTime(duration.days(5)); - await I_SecurityToken.transfer(account_investor2, web3.utils.toWei("1", "ether"), {from: account_investor1}); + await I_SecurityToken.transfer(account_investor2, new BN(web3.utils.toWei("1", "ether")), { from: account_investor1 }); }); it("Add a to default and check transfers are disabled then enabled in the future", async () => { - let tx = await I_GeneralTransferManager.changeDefaults(0, latestTime() + duration.days(5), {from: token_owner}); - await catchRevert(I_SecurityToken.transfer(account_investor1, web3.utils.toWei("1", "ether"), {from: account_investor2})); - await I_SecurityToken.transfer(account_investor2, web3.utils.toWei("1", "ether"), {from: account_investor1}); - await increaseTime(duration.days(5)); - await I_SecurityToken.transfer(account_investor1, web3.utils.toWei("2", "ether"), {from: account_investor2}); + let tx = await I_GeneralTransferManager.changeDefaults(0, currentTime.add(new BN(duration.days(16))), { from: token_owner }); + await catchRevert(I_SecurityToken.transfer(account_investor1, new BN(web3.utils.toWei("1", "ether")), { from: account_investor2 })); + await I_SecurityToken.transfer(account_investor2, new BN(web3.utils.toWei("1", "ether")), { from: account_investor1 }); + await increaseTime(duration.days(2)); + await I_SecurityToken.transfer(account_investor1, new BN(web3.utils.toWei("2", "ether")), { from: account_investor2 }); // revert changes - await I_GeneralTransferManager.modifyWhitelist( - account_investor2, - 0, - 0, - 0, - false, - { - from: account_issuer, - gas: 6000000 - } - ); - await I_GeneralTransferManager.changeDefaults(0, 0, {from: token_owner}); + await I_GeneralTransferManager.modifyKYCData(account_investor2, new BN(0), new BN(0), new BN(0), { + from: account_issuer, + gas: 6000000 + }); + await I_GeneralTransferManager.changeDefaults(0, new BN(0), { from: token_owner }); }); - - - }); describe("Buy tokens using off-chain whitelist", async () => { it("Should buy the tokens -- Failed due to investor is not in the whitelist", async () => { - await catchRevert(I_DummySTO.generateTokens(account_investor2, web3.utils.toWei("1", "ether"), { from: token_owner })); + await catchRevert(I_DummySTO.generateTokens(account_investor2, new BN(web3.utils.toWei("1", "ether")), { from: token_owner })); + }); + + it("Should provide the permission and change the signing address", async () => { + + let log2 = await I_GeneralPermissionManager.addDelegate(signer.address, web3.utils.fromAscii("My details"), { from: token_owner }); + assert.equal(log2.logs[0].args._delegate, signer.address); + + await I_GeneralPermissionManager.changePermission(signer.address, I_GeneralTransferManager.address, web3.utils.fromAscii("OPERATOR"), true, { + from: token_owner + }); + + assert.isTrue( + await I_GeneralPermissionManager.checkPermission.call(signer.address, I_GeneralTransferManager.address, web3.utils.fromAscii("OPERATOR")) + ); }); it("Should buy the tokens -- Failed due to incorrect signature input", async () => { // Add the Investor in to the whitelist //tmAddress, investorAddress, fromTime, toTime, validFrom, validTo, pk - let validFrom = latestTime(); - let validTo = latestTime() + duration.days(5); + let validFrom = await latestTime(); + let validTo = await latestTime() + duration.days(5); let nonce = 5; - const sig = signData( + const sig = getSignGTMData( account_investor2, account_investor2, fromTime, toTime, expiryTime, - true, validFrom, validTo, nonce, - token_owner_pk + signer.privateKey ); - const r = `0x${sig.r.toString("hex")}`; - const s = `0x${sig.s.toString("hex")}`; - const v = sig.v; - await catchRevert( - I_GeneralTransferManager.modifyWhitelistSigned( + I_GeneralTransferManager.modifyKYCDataSigned( account_investor2, fromTime, toTime, expiryTime, - true, validFrom, validTo, nonce, - v, - r, - s, + sig, { from: account_investor2, gas: 6000000 @@ -505,39 +588,31 @@ contract("GeneralTransferManager", accounts => { it("Should buy the tokens -- Failed due to incorrect signature timing", async () => { // Add the Investor in to the whitelist //tmAddress, investorAddress, fromTime, toTime, validFrom, validTo, pk - let validFrom = latestTime() - 100; - let validTo = latestTime() - 1; + let validFrom = await latestTime() - 100; + let validTo = await latestTime() - 1; let nonce = 5; - const sig = signData( + const sig = getSignGTMData( I_GeneralTransferManager.address, account_investor2, fromTime, toTime, expiryTime, - true, validFrom, validTo, nonce, - token_owner_pk + signer.privateKey ); - const r = `0x${sig.r.toString("hex")}`; - const s = `0x${sig.s.toString("hex")}`; - const v = sig.v; - await catchRevert( - I_GeneralTransferManager.modifyWhitelistSigned( + I_GeneralTransferManager.modifyKYCDataSigned( account_investor2, fromTime, toTime, expiryTime, - true, validFrom, validTo, nonce, - v, - r, - s, + sig, { from: account_investor2, gas: 6000000 @@ -549,39 +624,60 @@ contract("GeneralTransferManager", accounts => { it("Should buy the tokens -- Failed due to incorrect signature signer", async () => { // Add the Investor in to the whitelist //tmAddress, investorAddress, fromTime, toTime, validFrom, validTo, pk - let validFrom = latestTime(); - let validTo = latestTime() + 60 * 60; + let validFrom = await latestTime(); + let validTo = await latestTime() + 60 * 60; let nonce = 5; - const sig = signData( - account_investor2, + const sig = getSignGTMData( + I_GeneralTransferManager.address, account_investor2, fromTime, toTime, expiryTime, - true, validFrom, validTo, nonce, "2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501200" ); - const r = `0x${sig.r.toString("hex")}`; - const s = `0x${sig.s.toString("hex")}`; - const v = sig.v; + const sig2 = getMultiSignGTMData( + I_GeneralTransferManager.address, + [account_investor2], + [fromTime], + [toTime], + [expiryTime], + validFrom, + validTo, + nonce, + "2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501200" + ); await catchRevert( - I_GeneralTransferManager.modifyWhitelistSigned( + I_GeneralTransferManager.modifyKYCDataSigned( account_investor2, fromTime, toTime, expiryTime, - true, validFrom, validTo, nonce, - v, - r, - s, + sig, + { + from: account_investor2, + gas: 6000000 + } + ) + ); + + await catchRevert( + I_GeneralTransferManager.modifyKYCDataSignedMulti( + [account_investor2], + [fromTime], + [toTime], + [expiryTime], + validFrom, + validTo, + nonce, + sig2, { from: account_investor2, gas: 6000000 @@ -590,41 +686,228 @@ contract("GeneralTransferManager", accounts => { ); }); - it("Should Buy the tokens", async () => { + it("Should Not Transfer with expired Signed KYC data", async () => { + let nonce = 5; + const sig = getSignGTMTransferData( + I_GeneralTransferManager.address, + [account_investor2], + [currentTime.toNumber()], + [currentTime.toNumber()], + [expiryTime + duration.days(200)], + 1, + 1, + nonce, + signer.privateKey + ); + + // Jump time + await increaseTime(10000); + await catchRevert(I_SecurityToken.transfer(account_investor2, new BN(web3.utils.toWei("1", "ether")), {from: account_investor1})); + await catchRevert(I_SecurityToken.transferWithData(account_investor2, new BN(web3.utils.toWei("1", "ether")), sig, {from: account_investor1})); + }); + + it("Should Transfer with Signed KYC data", async () => { + let snap_id = await takeSnapshot(); // Add the Investor in to the whitelist //tmAddress, investorAddress, fromTime, toTime, validFrom, validTo, pk - let validFrom = latestTime(); - let validTo = latestTime() + duration.days(5); + let validFrom = await latestTime(); + let validTo = await latestTime() + duration.days(5); + let nonce = 5; + const sig = getSignGTMTransferData( + I_GeneralTransferManager.address, + [account_investor2], + [currentTime.toNumber()], + [currentTime.toNumber()], + [expiryTime + duration.days(200)], + validFrom, + validTo, + nonce, + signer.privateKey + ); + + // Jump time + await increaseTime(10000); + await catchRevert(I_SecurityToken.transfer(account_investor2, new BN(web3.utils.toWei("1", "ether")), {from: account_investor1})); + await I_SecurityToken.transferWithData(account_investor2, new BN(web3.utils.toWei("1", "ether")), sig, {from: account_investor1}); + assert.equal((await I_SecurityToken.balanceOf(account_investor2)).toString(), new BN(web3.utils.toWei("1", "ether")).toString()); + //Should transfer even with invalid sig data when kyc not required + await I_SecurityToken.transferWithData(account_investor1, new BN(web3.utils.toWei("1", "ether")), sig, {from: account_investor2}); + await revertToSnapshot(snap_id); + }); + + it("Should not do multiple signed whitelist if sig has expired", async () => { + snapid = await takeSnapshot(); + await I_GeneralTransferManager.modifyKYCDataMulti( + [account_investor1, account_investor2], + [currentTime, currentTime], + [currentTime, currentTime], + [1, 1], + { + from: account_issuer, + gas: 6000000 + } + ); + + let kycData = await I_GeneralTransferManager.getKYCData([account_investor1, account_investor2]); + + assert.equal(new BN(kycData[2][0]).toNumber(), 1, "KYC data not modified correctly"); + assert.equal(new BN(kycData[2][1]).toNumber(), 1, "KYC data not modified correctly"); + let nonce = 5; - const sig = signData( + + let newExpiryTime = new BN(expiryTime).add(new BN(duration.days(200))); + const sig = getMultiSignGTMData( + I_GeneralTransferManager.address, + [account_investor1, account_investor2], + [fromTime, fromTime], + [toTime, toTime], + [newExpiryTime, newExpiryTime], + 1, + 1, + nonce, + signer.privateKey + ); + + await increaseTime(10000); + + + await catchRevert( + I_GeneralTransferManager.modifyKYCDataSignedMulti( + [account_investor1, account_investor2], + [fromTime, fromTime], + [toTime, toTime], + [newExpiryTime, newExpiryTime], + 1, + 1, + nonce, + sig, + { + from: account_investor2, + gas: 6000000 + } + ) + ); + + kycData = await I_GeneralTransferManager.getKYCData([account_investor1, account_investor2]); + + assert.equal(new BN(kycData[2][0]).toNumber(), 1, "KYC data modified incorrectly"); + assert.equal(new BN(kycData[2][1]).toNumber(), 1, "KYC data modified incorrectly"); + }); + + it("Should not do multiple signed whitelist if array length mismatch", async () => { + let validFrom = await latestTime(); + let validTo = await latestTime() + duration.days(5); + let nonce = 5; + + let newExpiryTime = new BN(expiryTime).add(new BN(duration.days(200))); + const sig = getMultiSignGTMData( + I_GeneralTransferManager.address, + [account_investor1, account_investor2], + [fromTime, fromTime], + [toTime, toTime], + [newExpiryTime], + validFrom, + validTo, + nonce, + signer.privateKey + ); + + await increaseTime(10000); + + + await catchRevert( + I_GeneralTransferManager.modifyKYCDataSignedMulti( + [account_investor1, account_investor2], + [fromTime, fromTime], + [toTime, toTime], + [newExpiryTime], + validFrom, + validTo, + nonce, + sig, + { + from: account_investor2, + gas: 6000000 + } + ) + ); + + let kycData = await I_GeneralTransferManager.getKYCData([account_investor1, account_investor2]); + + assert.equal(new BN(kycData[2][0]).toNumber(), 1, "KYC data modified incorrectly"); + assert.equal(new BN(kycData[2][1]).toNumber(), 1, "KYC data modified incorrectly"); + }); + + it("Should do multiple signed whitelist in a signle transaction", async () => { + let validFrom = await latestTime(); + let validTo = await latestTime() + duration.days(5); + let nonce = 5; + + let newExpiryTime = new BN(expiryTime).add(new BN(duration.days(200))); + const sig = getMultiSignGTMData( + I_GeneralTransferManager.address, + [account_investor1, account_investor2], + [fromTime, fromTime], + [toTime, toTime], + [newExpiryTime, newExpiryTime], + validFrom, + validTo, + nonce, + signer.privateKey + ); + + await increaseTime(10000); + + await I_GeneralTransferManager.modifyKYCDataSignedMulti( + [account_investor1, account_investor2], + [fromTime, fromTime], + [toTime, toTime], + [newExpiryTime, newExpiryTime], + validFrom, + validTo, + nonce, + sig, + { + from: account_investor2, + gas: 6000000 + } + ); + + let kycData = await I_GeneralTransferManager.getKYCData([account_investor1, account_investor2]); + + assert.equal(new BN(kycData[2][0]).toString(), newExpiryTime.toString(), "KYC data not modified correctly"); + assert.equal(new BN(kycData[2][1]).toString(), newExpiryTime.toString(), "KYC data not modified correctly"); + + await revertToSnapshot(snapid); + }); + + it("Should Buy the tokens with signers signature", async () => { + // Add the Investor in to the whitelist + //tmAddress, investorAddress, fromTime, toTime, validFrom, validTo, pk + let validFrom = await latestTime(); + let validTo = await latestTime() + duration.days(5); + let nonce = 5; + const sig = getSignGTMData( I_GeneralTransferManager.address, account_investor2, - latestTime(), - latestTime() + duration.days(80), + currentTime.toNumber(), + currentTime.add(new BN(duration.days(100))).toNumber(), expiryTime + duration.days(200), - true, validFrom, validTo, nonce, - token_owner_pk + signer.privateKey ); - const r = `0x${sig.r.toString("hex")}`; - const s = `0x${sig.s.toString("hex")}`; - const v = sig.v; - - let tx = await I_GeneralTransferManager.modifyWhitelistSigned( + let tx = await I_GeneralTransferManager.modifyKYCDataSigned( account_investor2, - latestTime(), - latestTime() + duration.days(80), + currentTime.toNumber(), + currentTime.add(new BN(duration.days(100))).toNumber(), expiryTime + duration.days(200), - true, validFrom, validTo, nonce, - v, - r, - s, + sig, { from: account_investor2, gas: 6000000 @@ -641,118 +924,122 @@ contract("GeneralTransferManager", accounts => { await increaseTime(10000); // Mint some tokens - await I_DummySTO.generateTokens(account_investor2, web3.utils.toWei("1", "ether"), { from: token_owner }); + await I_DummySTO.generateTokens(account_investor2, new BN(web3.utils.toWei("1", "ether")), { from: token_owner }); - assert.equal((await I_SecurityToken.balanceOf(account_investor2)).toNumber(), web3.utils.toWei("1", "ether")); + assert.equal((await I_SecurityToken.balanceOf(account_investor2)).toString(), new BN(web3.utils.toWei("1", "ether")).toString()); }); - it("Should fail if the txn is generated with same nonce", async() => { + it("Should fail if the txn is generated with same nonce", async () => { // Add the Investor in to the whitelist //tmAddress, investorAddress, fromTime, toTime, validFrom, validTo, pk - let validFrom = latestTime(); - let validTo = latestTime() + duration.days(5); + let validFrom = await latestTime(); + let validTo = await latestTime() + duration.days(5); let nonce = 5; - const sig = signData( + const sig = getSignGTMData( I_GeneralTransferManager.address, account_investor2, - latestTime(), - latestTime() + duration.days(80), + currentTime.toNumber(), + currentTime.add(new BN(duration.days(100))).toNumber(), expiryTime + duration.days(200), - true, validFrom, validTo, nonce, - token_owner_pk + signer.privateKey + ); + + await catchRevert( + I_GeneralTransferManager.modifyKYCDataSigned( + account_investor2, + currentTime.toNumber(), + currentTime.add(new BN(duration.days(100))).toNumber(), + expiryTime + duration.days(200), + validFrom, + validTo, + nonce, + sig, + { + from: account_investor2, + gas: 6000000 + } + ) ); + }); - const r = `0x${sig.r.toString("hex")}`; - const s = `0x${sig.s.toString("hex")}`; - const v = sig.v; + it("Should sign with token owner key", async () => { + // Add the Investor in to the whitelist + //tmAddress, investorAddress, fromTime, toTime, validFrom, validTo, pk + let validFrom = await latestTime(); + let validTo = await latestTime() + duration.days(5); + let nonce = 6; + const sig = getSignGTMData( + I_GeneralTransferManager.address, + account_investor2, + currentTime.toNumber(), + currentTime.add(new BN(duration.days(100))).toNumber(), + expiryTime + duration.days(200), + validFrom, + validTo, + nonce, + "0x" + token_owner_pk + ); - await catchRevert(I_GeneralTransferManager.modifyWhitelistSigned( + await I_GeneralTransferManager.modifyKYCDataSigned( account_investor2, - latestTime(), - latestTime() + duration.days(80), + currentTime.toNumber(), + currentTime.add(new BN(duration.days(100))).toNumber(), expiryTime + duration.days(200), - true, validFrom, validTo, nonce, - v, - r, - s, + sig, { from: account_investor2, gas: 6000000 } ) - ); - }) - it("Should fail in changing the signing address", async () => { - await catchRevert(I_GeneralTransferManager.changeSigningAddress(account_polymath, { from: account_investor4 })); }); it("Should get the permission", async () => { let perm = await I_GeneralTransferManager.getPermissions.call(); - assert.equal(web3.utils.toAscii(perm[0]).replace(/\u0000/g, ""), "WHITELIST"); - assert.equal(web3.utils.toAscii(perm[1]).replace(/\u0000/g, ""), "FLAGS"); - }); - - it("Should provide the permission and change the signing address", async() => { - let log = await I_GeneralPermissionManager.addDelegate(account_delegate, "My details", {from: token_owner}); - assert.equal(log.logs[0].args._delegate, account_delegate); - - await I_GeneralPermissionManager.changePermission(account_delegate, I_GeneralTransferManager.address, "FLAGS", true, { - from: token_owner - }); - - assert.isTrue( - await I_GeneralPermissionManager.checkPermission.call(account_delegate, I_GeneralTransferManager.address, "FLAGS") - ); - - let tx = await I_GeneralTransferManager.changeSigningAddress(account_polymath, { from: account_delegate }); - assert.equal(tx.logs[0].args._signingAddress, account_polymath); - }); - - it("Should fail to pull fees as no budget set", async () => { - await catchRevert(I_GeneralTransferManager.takeFee(web3.utils.toWei("1", "ether"), { from: account_polymath })); + assert.equal(web3.utils.toAscii(perm[0]).replace(/\u0000/g, ""), "ADMIN"); }); it("Should set a budget for the GeneralTransferManager", async () => { - await I_SecurityToken.changeModuleBudget(I_GeneralTransferManager.address, 10 * Math.pow(10, 18), true, { from: token_owner }); - - await catchRevert(I_GeneralTransferManager.takeFee(web3.utils.toWei("1", "ether"), { from: token_owner })); - await I_PolyToken.getTokens(10 * Math.pow(10, 18), token_owner); - await I_PolyToken.transfer(I_SecurityToken.address, 10 * Math.pow(10, 18), { from: token_owner }); - }); - - it("Factory owner should pull fees - fails as not permissioned by issuer", async () => { - await catchRevert(I_GeneralTransferManager.takeFee(web3.utils.toWei("1", "ether"), { from: account_delegate })); + await I_SecurityToken.changeModuleBudget(I_GeneralTransferManager.address, new BN(10).pow(new BN(19)), true, { from: token_owner }); + await I_PolyToken.getTokens(new BN(10).pow(new BN(19)), token_owner); + await I_PolyToken.transfer(I_SecurityToken.address, new BN(10).pow(new BN(19)), { from: token_owner }); }); - it("Factory owner should pull fees", async () => { - await I_GeneralPermissionManager.changePermission(account_delegate, I_GeneralTransferManager.address, "FEE_ADMIN", true, { - from: token_owner - }); - let balanceBefore = await I_PolyToken.balanceOf(account_polymath); - await I_GeneralTransferManager.takeFee(web3.utils.toWei("1", "ether"), { from: account_delegate }); - let balanceAfter = await I_PolyToken.balanceOf(account_polymath); - assert.equal(balanceBefore.add(web3.utils.toWei("1", "ether")).toNumber(), balanceAfter.toNumber(), "Fee is transferred"); - }); - - it("Should change the white list transfer variable", async () => { - let tx = await I_GeneralTransferManager.changeAllowAllWhitelistIssuances(true, { from: token_owner }); - assert.isTrue(tx.logs[0].args._allowAllWhitelistIssuances); + it("should allow authorized people to modify transfer requirements", async () => { + await I_GeneralTransferManager.modifyTransferRequirements(0, false, true, false, false, { from: token_owner }); + let transferRestrions = await I_GeneralTransferManager.transferRequirements(0); + assert.equal(transferRestrions[0], false); + assert.equal(transferRestrions[1], true); + assert.equal(transferRestrions[2], false); + assert.equal(transferRestrions[3], false); }); it("should failed in trasfering the tokens", async () => { - let tx = await I_GeneralTransferManager.changeAllowAllWhitelistTransfers(true, { from: token_owner }); + await I_GeneralTransferManager.modifyTransferRequirementsMulti( + [0, 1, 2], + [true, false, true], + [true, true, false], + [false, false, false], + [false, false, false], + { from: token_owner } + ); await I_GeneralTransferManager.pause({ from: token_owner }); - await catchRevert(I_SecurityToken.transfer(account_investor1, web3.utils.toWei("2", "ether"), { from: account_investor2 })); + await catchRevert(I_SecurityToken.transfer(account_investor1, new BN(web3.utils.toWei("2", "ether")), { from: account_investor2 })); }); it("Should change the Issuance address", async () => { + let log = await I_GeneralPermissionManager.addDelegate(account_delegate, web3.utils.fromAscii("My details"), { from: token_owner }); + assert.equal(log.logs[0].args._delegate, account_delegate); + + await I_GeneralPermissionManager.changePermission(account_delegate, I_GeneralTransferManager.address, web3.utils.fromAscii("ADMIN"), true, { + from: token_owner + }); let tx = await I_GeneralTransferManager.changeIssuanceAddress(account_investor2, { from: account_delegate }); assert.equal(tx.logs[0].args._issuanceAddress, account_investor2); }); @@ -771,19 +1058,18 @@ contract("GeneralTransferManager", accounts => { describe("WhiteList that addresses", async () => { it("Should fail in adding the investors in whitelist", async () => { - let fromTime = latestTime(); - let toTime = latestTime() + duration.days(20); + let fromTime = await latestTime(); + let toTime = await latestTime() + duration.days(20); let expiryTime = toTime + duration.days(10); await catchRevert( - I_GeneralTransferManager.modifyWhitelistMulti( + I_GeneralTransferManager.modifyKYCDataMulti( [account_investor3, account_investor4], [fromTime, fromTime], [toTime, toTime], [expiryTime, expiryTime], - [true, true], { - from: account_delegate, + from: account_investor1, gas: 6000000 } ) @@ -791,17 +1077,16 @@ contract("GeneralTransferManager", accounts => { }); it("Should fail in adding the investors in whitelist -- array length mismatch", async () => { - let fromTime = latestTime(); - let toTime = latestTime() + duration.days(20); + let fromTime = await latestTime(); + let toTime = await latestTime() + duration.days(20); let expiryTime = toTime + duration.days(10); await catchRevert( - I_GeneralTransferManager.modifyWhitelistMulti( + I_GeneralTransferManager.modifyKYCDataMulti( [account_investor3, account_investor4], [fromTime], [toTime, toTime], [expiryTime, expiryTime], - [1, 1], { from: account_delegate, gas: 6000000 @@ -811,17 +1096,16 @@ contract("GeneralTransferManager", accounts => { }); it("Should fail in adding the investors in whitelist -- array length mismatch", async () => { - let fromTime = latestTime(); - let toTime = latestTime() + duration.days(20); + let fromTime = await latestTime(); + let toTime = await latestTime() + duration.days(20); let expiryTime = toTime + duration.days(10); await catchRevert( - I_GeneralTransferManager.modifyWhitelistMulti( + I_GeneralTransferManager.modifyKYCDataMulti( [account_investor3, account_investor4], [fromTime, fromTime], [toTime], [expiryTime, expiryTime], - [true, true], { from: account_delegate, gas: 6000000 @@ -831,17 +1115,16 @@ contract("GeneralTransferManager", accounts => { }); it("Should fail in adding the investors in whitelist -- array length mismatch", async () => { - let fromTime = latestTime(); - let toTime = latestTime() + duration.days(20); + let fromTime = await latestTime(); + let toTime = await latestTime() + duration.days(20); let expiryTime = toTime + duration.days(10); await catchRevert( - I_GeneralTransferManager.modifyWhitelistMulti( + I_GeneralTransferManager.modifyKYCDataMulti( [account_investor3, account_investor4], [fromTime, fromTime], [toTime, toTime], [expiryTime], - [true, true], { from: account_delegate, gas: 6000000 @@ -851,16 +1134,15 @@ contract("GeneralTransferManager", accounts => { }); it("Should successfully add the investors in whitelist", async () => { - let fromTime = latestTime(); - let toTime = latestTime() + duration.days(20); + let fromTime = await latestTime(); + let toTime = await latestTime() + duration.days(20); let expiryTime = toTime + duration.days(10); - let tx = await I_GeneralTransferManager.modifyWhitelistMulti( + let tx = await I_GeneralTransferManager.modifyKYCDataMulti( [account_investor3, account_investor4], [fromTime, fromTime], [toTime, toTime], [expiryTime, expiryTime], - [true, true], { from: token_owner, gas: 6000000 @@ -870,12 +1152,110 @@ contract("GeneralTransferManager", accounts => { }); }); + describe("Test cases for the getTokensByPartition", async() => { + + it("Should change the transfer requirements", async() => { + await I_GeneralTransferManager.modifyTransferRequirementsMulti( + [0, 1, 2], + [true, false, true], + [true, true, false], + [true, false, false], + [true, false, false], + { from: token_owner } + ); + }) + + it("Should check the partition balance before changing the canSendAfter & canReceiveAfter", async() => { + assert.equal( + web3.utils.fromWei( + ( + await I_SecurityToken.balanceOf.call(account_investor2) + ).toString() + ), + 1 + ); + assert.equal( + web3.utils.fromWei( + ( + await I_GeneralTransferManager.getTokensByPartition.call(web3.utils.toHex("LOCKED"), account_investor2, new BN(0)) + ).toString() + ), + 0 + ); + assert.equal( + web3.utils.fromWei( + ( + await I_GeneralTransferManager.getTokensByPartition.call(web3.utils.toHex("UNLOCKED"), account_investor2, new BN(0)) + ).toString() + ), + 1 + ); + }); + + it("Should change the canSendAfter and canRecieveAfter of the investor2", async() => { + let canSendAfter = await latestTime() + duration.days(10); + let canRecieveAfter = await latestTime() + duration.days(10); + let expiryTime = await latestTime() + duration.days(100); + + let tx = await I_GeneralTransferManager.modifyKYCData( + account_investor2, + canSendAfter, + canRecieveAfter, + expiryTime, + { + from: token_owner, + gas: 6000000 + } + ); + assert.equal(tx.logs[0].args._investor, account_investor2); + assert.equal( + web3.utils.fromWei( + ( + await I_GeneralTransferManager.getTokensByPartition.call(web3.utils.toHex("LOCKED"), account_investor2, new BN(0)) + ).toString() + ), + 1 + ); + + assert.equal( + web3.utils.fromWei( + ( + await I_GeneralTransferManager.getTokensByPartition.call(web3.utils.toHex("UNLOCKED"), account_investor2, new BN(0)) + ).toString() + ), + 0 + ); + }); + + it("Should check the values of partition balance after the GTM pause", async() => { + await I_GeneralTransferManager.pause({from: token_owner}); + assert.equal( + web3.utils.fromWei( + ( + await I_GeneralTransferManager.getTokensByPartition.call(web3.utils.toHex("LOCKED"), account_investor2, new BN(0)) + ).toString() + ), + 0 + ); + + assert.equal( + web3.utils.fromWei( + ( + await I_GeneralTransferManager.getTokensByPartition.call(web3.utils.toHex("UNLOCKED"), account_investor2, new BN(0)) + ).toString() + ), + 1 + ); + await I_GeneralTransferManager.unpause({from: token_owner}); + }) + }); + describe("General Transfer Manager Factory test cases", async () => { it("Should get the exact details of the factory", async () => { - assert.equal(await I_GeneralTransferManagerFactory.getSetupCost.call(), 0); + assert.equal(await I_GeneralTransferManagerFactory.setupCost.call(), 0); assert.equal((await I_GeneralTransferManagerFactory.getTypes.call())[0], 2); assert.equal( - web3.utils.toAscii(await I_GeneralTransferManagerFactory.getName.call()).replace(/\u0000/g, ""), + web3.utils.toAscii(await I_GeneralTransferManagerFactory.name.call()).replace(/\u0000/g, ""), "GeneralTransferManager", "Wrong Module added" ); @@ -885,12 +1265,7 @@ contract("GeneralTransferManager", accounts => { "Wrong Module added" ); assert.equal(await I_GeneralTransferManagerFactory.title.call(), "General Transfer Manager", "Wrong Module added"); - assert.equal( - await I_GeneralTransferManagerFactory.getInstructions.call(), - "Allows an issuer to maintain a time based whitelist of authorised token holders.Addresses are added via modifyWhitelist and take a fromTime (the time from which they can send tokens) and a toTime (the time from which they can receive tokens). There are additional flags, allowAllWhitelistIssuances, allowAllWhitelistTransfers & allowAllTransfers which allow you to set corresponding contract level behaviour. Init function takes no parameters.", - "Wrong Module added" - ); - assert.equal(await I_GeneralTransferManagerFactory.version.call(), "2.1.0"); + assert.equal(await I_GeneralTransferManagerFactory.version.call(), "3.0.0"); }); it("Should get the tags of the factory", async () => { @@ -901,16 +1276,15 @@ contract("GeneralTransferManager", accounts => { describe("Dummy STO Factory test cases", async () => { it("should get the exact details of the factory", async () => { - assert.equal(await I_DummySTOFactory.getSetupCost.call(), 0); + assert.equal(await I_DummySTOFactory.setupCost.call(), 0); assert.equal((await I_DummySTOFactory.getTypes.call())[0], 3); assert.equal( - web3.utils.toAscii(await I_DummySTOFactory.getName.call()).replace(/\u0000/g, ""), + web3.utils.toAscii(await I_DummySTOFactory.name.call()).replace(/\u0000/g, ""), "DummySTO", "Wrong Module added" ); assert.equal(await I_DummySTOFactory.description.call(), "Dummy STO", "Wrong Module added"); assert.equal(await I_DummySTOFactory.title.call(), "Dummy STO", "Wrong Module added"); - assert.equal(await I_DummySTOFactory.getInstructions.call(), "Dummy STO - you can mint tokens at will", "Wrong Module added"); }); it("Should get the tags of the factory", async () => { @@ -918,19 +1292,15 @@ contract("GeneralTransferManager", accounts => { assert.equal(web3.utils.toAscii(tags[0]).replace(/\u0000/g, ""), "Dummy"); }); - it("Should get the version of factory", async() => { - let version = await I_DummySTOFactory.version.call(); - assert.equal(version, "1.0.0"); - }); }); describe("Test cases for the get functions of the dummy sto", async () => { it("Should get the raised amount of ether", async () => { - assert.equal(await I_DummySTO.getRaised.call(0), web3.utils.toWei("0", "ether")); + assert.equal((await I_DummySTO.getRaised.call(0)).toString(), new BN(web3.utils.toWei("0", "ether")).toString()); }); it("Should get the raised amount of poly", async () => { - assert.equal((await I_DummySTO.getRaised.call(1)).toNumber(), web3.utils.toWei("0", "ether")); + assert.equal((await I_DummySTO.getRaised.call(1)).toString(), new BN(web3.utils.toWei("0", "ether")).toString()); }); it("Should get the investors", async () => { @@ -942,11 +1312,18 @@ contract("GeneralTransferManager", accounts => { assert.equal(web3.utils.toAscii(tx[0]).replace(/\u0000/g, ""), "ADMIN"); }); - it("Should get the amount of tokens sold", async() => { + it("Should get the amount of tokens sold", async () => { assert.equal(await I_DummySTO.getTokensSold.call(), 0); - }) + }); }); }); +function range1(i) { + return i ? range1(i - 1).concat(i) : []; +} +function rangeB(i) { + return i ? rangeB(i - 1).concat(0) : []; +} + function range1(i) {return i?range1(i-1).concat(i):[]} function rangeB(i) {return i?rangeB(i-1).concat(0):[]} diff --git a/test/helpers/contracts/PolyToken.sol b/test/helpers/contracts/PolyToken.sol index e14d5d663..b9863b0c2 100644 --- a/test/helpers/contracts/PolyToken.sol +++ b/test/helpers/contracts/PolyToken.sol @@ -1,11 +1,9 @@ -pragma solidity ^0.4.24; +pragma solidity ^0.5.0; -import "openzeppelin-solidity/contracts/token/ERC20/MintableToken.sol"; +import "openzeppelin-solidity/contracts/token/ERC20/ERC20Mintable.sol"; - -contract PolyToken is MintableToken { - - constructor () public { +contract PolyToken is ERC20Mintable { + constructor() public { } diff --git a/test/helpers/createInstances.js b/test/helpers/createInstances.js index 4335e52b3..036420b0d 100644 --- a/test/helpers/createInstances.js +++ b/test/helpers/createInstances.js @@ -1,44 +1,63 @@ import { encodeProxyCall, encodeModuleCall } from "./encodeCall"; const PolymathRegistry = artifacts.require("./PolymathRegistry.sol"); +const MockOracle = artifacts.require("./MockOracle.sol"); +const StableOracle = artifacts.require("./StableOracle.sol"); const ModuleRegistry = artifacts.require("./ModuleRegistry.sol"); const ModuleRegistryProxy = artifacts.require("./ModuleRegistryProxy.sol"); -const SecurityToken = artifacts.require("./SecurityToken.sol"); const CappedSTOFactory = artifacts.require("./CappedSTOFactory.sol"); +const CappedSTO = artifacts.require("./CappedSTO.sol"); const SecurityTokenRegistryProxy = artifacts.require("./SecurityTokenRegistryProxy.sol"); const SecurityTokenRegistry = artifacts.require("./SecurityTokenRegistry.sol"); const SecurityTokenRegistryMock = artifacts.require("./SecurityTokenRegistryMock.sol"); +const SecurityTokenLogic = artifacts.require("./SecurityToken.sol"); const ERC20DividendCheckpoint = artifacts.require("./ERC20DividendCheckpoint.sol"); const EtherDividendCheckpoint = artifacts.require("./EtherDividendCheckpoint.sol"); const ERC20DividendCheckpointFactory = artifacts.require("./ERC20DividendCheckpointFactory.sol"); const EtherDividendCheckpointFactory = artifacts.require("./EtherDividendCheckpointFactory.sol"); +const ManualApprovalTransferManager = artifacts.require("./ManualApprovalTransferManager.sol"); const ManualApprovalTransferManagerFactory = artifacts.require("./ManualApprovalTransferManagerFactory.sol"); const TrackedRedemptionFactory = artifacts.require("./TrackedRedemptionFactory.sol"); const PercentageTransferManagerFactory = artifacts.require("./PercentageTransferManagerFactory.sol"); +const PercentageTransferManager = artifacts.require("./PercentageTransferManager.sol"); +const BlacklistTransferManager = artifacts.require("./BlacklistTransferManager.sol"); const BlacklistTransferManagerFactory = artifacts.require("./BlacklistTransferManagerFactory.sol"); const ScheduledCheckpointFactory = artifacts.require('./ScheduledCheckpointFactory.sol'); const USDTieredSTOFactory = artifacts.require("./USDTieredSTOFactory.sol"); const USDTieredSTO = artifacts.require("./USDTieredSTO"); -const ManualApprovalTransferManager = artifacts.require("./ManualApprovalTransferManager"); const FeatureRegistry = artifacts.require("./FeatureRegistry.sol"); const STFactory = artifacts.require("./STFactory.sol"); const GeneralTransferManager = artifacts.require("./GeneralTransferManager.sol"); const GeneralTransferManagerFactory = artifacts.require("./GeneralTransferManagerFactory.sol"); +const GeneralPermissionManager = artifacts.require("./GeneralPermissionManager.sol"); const GeneralPermissionManagerFactory = artifacts.require("./GeneralPermissionManagerFactory.sol"); +const CountTransferManager = artifacts.require("./CountTransferManager.sol"); const CountTransferManagerFactory = artifacts.require("./CountTransferManagerFactory.sol"); +const LockUpTransferManager = artifacts.require("./LockUpTransferManager"); const LockUpTransferManagerFactory = artifacts.require("./LockUpTransferManagerFactory"); const PreSaleSTOFactory = artifacts.require("./PreSaleSTOFactory.sol"); -const PolyToken = artifacts.require("./PolyToken.sol"); +const PreSaleSTO = artifacts.require("./PreSaleSTO.sol"); const PolyTokenFaucet = artifacts.require("./PolyTokenFaucet.sol"); const DummySTOFactory = artifacts.require("./DummySTOFactory.sol"); +const DummySTO = artifacts.require("./DummySTO.sol"); const MockBurnFactory = artifacts.require("./MockBurnFactory.sol"); +const STRGetter = artifacts.require("./STRGetter.sol"); const MockWrongTypeFactory = artifacts.require("./MockWrongTypeFactory.sol"); +const STGetter = artifacts.require("./STGetter.sol"); +const DataStoreLogic = artifacts.require('./DataStore.sol'); +const DataStoreFactory = artifacts.require('./DataStoreFactory.sol'); +const SignedTransferManagerFactory = artifacts.require("./SignedTransferManagerFactory"); const VolumeRestrictionTMFactory = artifacts.require("./VolumeRestrictionTMFactory.sol"); const VolumeRestrictionTM = artifacts.require("./VolumeRestrictionTM.sol"); const VestingEscrowWalletFactory = artifacts.require("./VestingEscrowWalletFactory.sol"); const VestingEscrowWallet = artifacts.require("./VestingEscrowWallet.sol"); +const PLCRVotingCheckpointFactory = artifacts.require("./PLCRVotingCheckpointFactory.sol"); +const WeightedVoteCheckpointFactory = artifacts.require("./WeightedVoteCheckpointFactory.sol"); +const PLCRVotingCheckpoint = artifacts.require("./PLCRVotingCheckpoint.sol"); +const WeightedVoteCheckpoint = artifacts.require("./WeightedVoteCheckpoint.sol"); const Web3 = require("web3"); +let BN = Web3.utils.BN; const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); // Hardcoded development port // Contract Instance Declaration @@ -48,15 +67,21 @@ let I_TrackedRedemptionFactory; let I_ScheduledCheckpointFactory; let I_MockBurnFactory; let I_MockWrongTypeBurnFactory; +let I_ManualApprovalTransferManagerLogic; let I_ManualApprovalTransferManagerFactory; -let I_VolumeRestrictionTransferManagerFactory; +let I_LockUpTransferManagerLogic; +let I_LockUpTransferManagerFactory; +let I_PercentageTransferManagerLogic; let I_PercentageTransferManagerFactory; let I_EtherDividendCheckpointLogic; let I_EtherDividendCheckpointFactory; +let I_CountTransferManagerLogic; let I_CountTransferManagerFactory; let I_ERC20DividendCheckpointLogic; let I_ERC20DividendCheckpointFactory; +let I_GeneralPermissionManagerLogic; let I_VolumeRestrictionTMFactory; +let I_WeightedVoteCheckpointFactory; let I_GeneralPermissionManagerFactory; let I_GeneralTransferManagerLogic; let I_GeneralTransferManagerFactory; @@ -64,27 +89,40 @@ let I_VestingEscrowWalletFactory; let I_GeneralTransferManager; let I_VolumeRestrictionTMLogic; let I_ModuleRegistryProxy; +let I_PreSaleSTOLogic; let I_PreSaleSTOFactory; let I_ModuleRegistry; let I_FeatureRegistry; let I_SecurityTokenRegistry; +let I_CappedSTOLogic; let I_CappedSTOFactory; let I_SecurityToken; +let I_DummySTOLogic; let I_DummySTOFactory; let I_PolyToken; let I_STFactory; let I_USDTieredSTOLogic; let I_PolymathRegistry; let I_SecurityTokenRegistryProxy; +let I_BlacklistTransferManagerLogic; let I_BlacklistTransferManagerFactory; let I_VestingEscrowWalletLogic; let I_STRProxied; let I_MRProxied; +let I_STRGetter; +let I_STGetter; +let I_SignedTransferManagerFactory; +let I_USDOracle; +let I_POLYOracle; +let I_StablePOLYOracle; +let I_PLCRVotingCheckpointFactory; +let I_WeightedVoteCheckpointLogic; +let I_PLCRVotingCheckpointLogic; // Initial fee for ticker registry and security token registry -const initRegFee = web3.utils.toWei("250"); +const initRegFee = new BN(web3.utils.toWei("250")); -const STRProxyParameters = ["address", "address", "uint256", "uint256", "address", "address"]; +const STRProxyParameters = ["address", "uint256", "uint256", "address", "address"]; const MRProxyParameters = ["address", "address"]; /// Function use to launch the polymath ecossystem. @@ -109,18 +147,52 @@ export async function setUpPolymathNetwork(account_polymath, token_owner) { await setInPolymathRegistry(account_polymath); // STEP 9: Register the Modules with the ModuleRegistry contract await registerGTM(account_polymath); - let tempArray = new Array(a, b, c, d, e, f); - return mergeReturn(tempArray); + // STEP 10: Add dummy oracles + await addOracles(account_polymath); + + let tempArray = new Array( + I_PolymathRegistry, + I_PolyToken, + I_FeatureRegistry, + I_ModuleRegistry, + I_ModuleRegistryProxy, + I_MRProxied, + I_GeneralTransferManagerFactory, + I_STFactory, + I_SecurityTokenRegistry, + I_SecurityTokenRegistryProxy, + I_STRProxied, + I_STRGetter, + I_STGetter, + I_USDOracle, + I_POLYOracle, + I_StablePOLYOracle + ); + return Promise.all(tempArray); } +export async function addOracles(account_polymath) { + let USDETH = new BN(500).mul(new BN(10).pow(new BN(18))); // 500 USD/ETH + let USDPOLY = new BN(25).mul(new BN(10).pow(new BN(16))); // 0.25 USD/POLY + let StableChange = new BN(10).mul(new BN(10).pow(new BN(16))); // 0.25 USD/POLY + I_USDOracle = await MockOracle.new("0x0000000000000000000000000000000000000000", web3.utils.fromAscii("ETH"), web3.utils.fromAscii("USD"), USDETH, { from: account_polymath }); // 500 dollars per POLY + I_POLYOracle = await MockOracle.new(I_PolyToken.address, web3.utils.fromAscii("POLY"), web3.utils.fromAscii("USD"), USDPOLY, { from: account_polymath }); // 25 cents per POLY + I_StablePOLYOracle = await StableOracle.new(I_POLYOracle.address, StableChange, { from: account_polymath }); + await I_PolymathRegistry.changeAddress("EthUsdOracle", I_USDOracle.address, { from: account_polymath }); + await I_PolymathRegistry.changeAddress("PolyUsdOracle", I_POLYOracle.address, { from: account_polymath }); + await I_PolymathRegistry.changeAddress("StablePolyUsdOracle", I_StablePOLYOracle.address, { from: account_polymath }); +} export async function deployPolyRegistryAndPolyToken(account_polymath, token_owner) { // Step 0: Deploy the PolymathRegistry I_PolymathRegistry = await PolymathRegistry.new({ from: account_polymath }); // Step 1: 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); + I_PolyToken = await PolyTokenFaucet.new({ from: account_polymath }); + + await I_PolyToken.getTokens(new BN(10000).mul(new BN(10).pow(new BN(18))), token_owner); + + await I_PolymathRegistry.changeAddress("PolyToken", I_PolyToken.address, { from: account_polymath }); return new Array(I_PolymathRegistry, I_PolyToken); } @@ -145,7 +217,11 @@ async function deployModuleRegistry(account_polymath) { } async function deployGTMLogic(account_polymath) { - I_GeneralTransferManagerLogic = await GeneralTransferManager.new("0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", { from: account_polymath }); + I_GeneralTransferManagerLogic = await GeneralTransferManager.new( + "0x0000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000", + { from: account_polymath } + ); assert.notEqual( I_GeneralTransferManagerLogic.address.valueOf(), @@ -157,7 +233,9 @@ async function deployGTMLogic(account_polymath) { } async function deployGTM(account_polymath) { - I_GeneralTransferManagerFactory = await GeneralTransferManagerFactory.new(I_PolyToken.address, 0, 0, 0, I_GeneralTransferManagerLogic.address, { from: account_polymath }); + I_GeneralTransferManagerFactory = await GeneralTransferManagerFactory.new(new BN(0), I_GeneralTransferManagerLogic.address, I_PolymathRegistry.address, true, { + from: account_polymath + }); assert.notEqual( I_GeneralTransferManagerFactory.address.valueOf(), @@ -169,11 +247,27 @@ async function deployGTM(account_polymath) { } async function deploySTFactory(account_polymath) { - I_STFactory = await STFactory.new(I_GeneralTransferManagerFactory.address, { from: account_polymath }); + I_STGetter = await STGetter.new({from: account_polymath}); + I_SecurityToken = await SecurityTokenLogic.new({ from: account_polymath }); + console.log("STL - " + I_SecurityToken.address); + let I_DataStoreLogic = await DataStoreLogic.new({ from: account_polymath }); + let I_DataStoreFactory = await DataStoreFactory.new(I_DataStoreLogic.address, { from: account_polymath }); + const tokenInitBytes = { + name: "initialize", + type: "function", + inputs: [ + { + type: "address", + name: "_getterDelegate" + } + ] + }; + let tokenInitBytesCall = web3.eth.abi.encodeFunctionCall(tokenInitBytes, [I_STGetter.address]); + I_STFactory = await STFactory.new(I_PolymathRegistry.address, I_GeneralTransferManagerFactory.address, I_DataStoreFactory.address, "3.0.0", I_SecurityToken.address, tokenInitBytesCall, { from: account_polymath }); assert.notEqual(I_STFactory.address.valueOf(), "0x0000000000000000000000000000000000000000", "STFactory contract was not deployed"); - return new Array(I_STFactory); + return new Array(I_STFactory, I_STGetter); } async function deploySTR(account_polymath) { @@ -187,18 +281,21 @@ async function deploySTR(account_polymath) { // Step 9 (a): Deploy the proxy I_SecurityTokenRegistryProxy = await SecurityTokenRegistryProxy.new({ from: account_polymath }); + I_STRGetter = await STRGetter.new({from: account_polymath}); + let bytesProxy = encodeProxyCall(STRProxyParameters, [ I_PolymathRegistry.address, - I_STFactory.address, initRegFee, initRegFee, - I_PolyToken.address, - account_polymath + account_polymath, + I_STRGetter.address ]); await I_SecurityTokenRegistryProxy.upgradeToAndCall("1.0.0", I_SecurityTokenRegistry.address, bytesProxy, { from: account_polymath }); - I_STRProxied = SecurityTokenRegistry.at(I_SecurityTokenRegistryProxy.address); - - return new Array(I_SecurityTokenRegistry, I_SecurityTokenRegistryProxy, I_STRProxied); + I_STRProxied = await SecurityTokenRegistry.at(I_SecurityTokenRegistryProxy.address); + await I_STRProxied.setProtocolFactory(I_STFactory.address, 3, 0, 0); + await I_STRProxied.setLatestVersion(3, 0, 0); + I_STRGetter = await STRGetter.at(I_SecurityTokenRegistryProxy.address); + return new Array(I_SecurityTokenRegistry, I_SecurityTokenRegistryProxy, I_STRProxied, I_STRGetter); } async function setInPolymathRegistry(account_polymath) { @@ -211,34 +308,36 @@ async function setInPolymathRegistry(account_polymath) { async function registerGTM(account_polymath) { await I_MRProxied.registerModule(I_GeneralTransferManagerFactory.address, { from: account_polymath }); - await I_MRProxied.verifyModule(I_GeneralTransferManagerFactory.address, true, { from: account_polymath }); + await I_MRProxied.verifyModule(I_GeneralTransferManagerFactory.address, { from: account_polymath }); } async function registerAndVerifyByMR(factoryAdrress, owner, mr) { await mr.registerModule(factoryAdrress, { from: owner }); - await mr.verifyModule(factoryAdrress, true, { from: owner }); + await mr.verifyModule(factoryAdrress, { from: owner }); } /// Deploy the TransferManagers -export async function deployGTMAndVerifyed(accountPolymath, MRProxyInstance, polyToken, setupCost) { - I_GeneralTransferManagerFactory = await GeneralTransferManagerFactory.new(polyToken, setupCost, 0, 0, I_GeneralTransferManagerLogic.address, { from: accountPolymath }); +export async function deployGTMAndVerifyed(accountPolymath, MRProxyInstance, setupCost, feeInPoly = false) { + I_GeneralTransferManagerFactory = await GeneralTransferManagerFactory.new(setupCost, I_GeneralTransferManagerLogic.address, I_PolymathRegistry.address, feeInPoly, { + from: accountPolymath + }); assert.notEqual( I_GeneralTransferManagerFactory.address.valueOf(), "0x0000000000000000000000000000000000000000", - "GeneralPermissionManagerFactory contract was not deployed" + "GeneralTransferManagerFactory contract was not deployed" ); // (B) : Register the GeneralDelegateManagerFactory await registerAndVerifyByMR(I_GeneralTransferManagerFactory.address, accountPolymath, MRProxyInstance); - return new Array(I_GeneralTransferManagerFactory); + return Promise.all(new Array(I_GeneralTransferManagerFactory)); } -export async function deployVRTMAndVerifyed(accountPolymath, MRProxyInstance, polyToken, setupCost) { +export async function deployVRTMAndVerifyed(accountPolymath, MRProxyInstance, setupCost, feeInPoly = false) { I_VolumeRestrictionTMLogic = await VolumeRestrictionTM.new("0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", { from: accountPolymath }); - I_VolumeRestrictionTMFactory = await VolumeRestrictionTMFactory.new(polyToken, setupCost, 0, 0, I_VolumeRestrictionTMLogic.address, { from: accountPolymath }); + I_VolumeRestrictionTMFactory = await VolumeRestrictionTMFactory.new(setupCost, I_VolumeRestrictionTMLogic.address, I_PolymathRegistry.address, feeInPoly, { from: accountPolymath }); assert.notEqual( I_VolumeRestrictionTMFactory.address.valueOf(), @@ -251,8 +350,9 @@ export async function deployVRTMAndVerifyed(accountPolymath, MRProxyInstance, po return new Array(I_VolumeRestrictionTMFactory); } -export async function deployCountTMAndVerifyed(accountPolymath, MRProxyInstance, polyToken, setupCost) { - I_CountTransferManagerFactory = await CountTransferManagerFactory.new(polyToken, setupCost, 0, 0, { from: accountPolymath }); +export async function deployCountTMAndVerifyed(accountPolymath, MRProxyInstance, setupCost, feeInPoly = false) { + I_CountTransferManagerLogic = await CountTransferManager.new("0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", { from: accountPolymath }); + I_CountTransferManagerFactory = await CountTransferManagerFactory.new(setupCost, I_CountTransferManagerLogic.address, I_PolymathRegistry.address, feeInPoly, { from: accountPolymath }); assert.notEqual( I_CountTransferManagerFactory.address.valueOf(), @@ -261,11 +361,12 @@ export async function deployCountTMAndVerifyed(accountPolymath, MRProxyInstance, ); await registerAndVerifyByMR(I_CountTransferManagerFactory.address, accountPolymath, MRProxyInstance); - return new Array(I_CountTransferManagerFactory); + return Promise.all(new Array(I_CountTransferManagerFactory)); } -export async function deployManualApprovalTMAndVerifyed(accountPolymath, MRProxyInstance, polyToken, setupCost) { - I_ManualApprovalTransferManagerFactory = await ManualApprovalTransferManagerFactory.new(polyToken, setupCost, 0, 0, { from: accountPolymath }); +export async function deployManualApprovalTMAndVerifyed(accountPolymath, MRProxyInstance, setupCost, feeInPoly = false) { + I_ManualApprovalTransferManagerLogic = await ManualApprovalTransferManager.new("0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", { from: accountPolymath }); + I_ManualApprovalTransferManagerFactory = await ManualApprovalTransferManagerFactory.new(setupCost, ManualApprovalTransferManager.address, I_PolymathRegistry.address, feeInPoly, { from: accountPolymath }); assert.notEqual( I_ManualApprovalTransferManagerFactory.address.valueOf(), "0x0000000000000000000000000000000000000000", @@ -273,11 +374,12 @@ export async function deployManualApprovalTMAndVerifyed(accountPolymath, MRProxy ); await registerAndVerifyByMR(I_ManualApprovalTransferManagerFactory.address, accountPolymath, MRProxyInstance); - return new Array(I_ManualApprovalTransferManagerFactory); + return Promise.all(new Array(I_ManualApprovalTransferManagerFactory)); } -export async function deployPercentageTMAndVerified(accountPolymath, MRProxyInstance, polyToken, setupCost) { - I_PercentageTransferManagerFactory = await PercentageTransferManagerFactory.new(polyToken, setupCost, 0, 0, { from: accountPolymath }); +export async function deployPercentageTMAndVerified(accountPolymath, MRProxyInstance, setupCost, feeInPoly = false) { + I_PercentageTransferManagerLogic = await PercentageTransferManager.new("0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", { from: accountPolymath }); + I_PercentageTransferManagerFactory = await PercentageTransferManagerFactory.new(setupCost, I_PercentageTransferManagerLogic.address, I_PolymathRegistry.address, feeInPoly, { from: accountPolymath }); assert.notEqual( I_PercentageTransferManagerFactory.address.valueOf(), "0x0000000000000000000000000000000000000000", @@ -285,11 +387,12 @@ export async function deployPercentageTMAndVerified(accountPolymath, MRProxyInst ); await registerAndVerifyByMR(I_PercentageTransferManagerFactory.address, accountPolymath, MRProxyInstance); - return new Array(I_PercentageTransferManagerFactory); + return Promise.all(new Array(I_PercentageTransferManagerFactory)); } -export async function deployBlacklistTMAndVerified(accountPolymath, MRProxyInstance, polyToken, setupCost) { - I_BlacklistTransferManagerFactory = await BlacklistTransferManagerFactory.new(polyToken, setupCost, 0, 0, { from: accountPolymath }); +export async function deployBlacklistTMAndVerified(accountPolymath, MRProxyInstance, setupCost, feeInPoly = false) { + I_BlacklistTransferManagerLogic = await BlacklistTransferManager.new("0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", { from: accountPolymath }); + I_BlacklistTransferManagerFactory = await BlacklistTransferManagerFactory.new(setupCost, I_BlacklistTransferManagerLogic.address, I_PolymathRegistry.address, feeInPoly, { from: accountPolymath }); assert.notEqual( I_BlacklistTransferManagerFactory.address.valueOf(), "0x0000000000000000000000000000000000000000", @@ -300,20 +403,23 @@ export async function deployBlacklistTMAndVerified(accountPolymath, MRProxyInsta return new Array(I_BlacklistTransferManagerFactory); } -export async function deployLockupVolumeRTMAndVerified(accountPolymath, MRProxyInstance, polyToken, setupCost) { - I_VolumeRestrictionTransferManagerFactory = await LockUpTransferManagerFactory.new(polyToken, setupCost, 0, 0, { from: accountPolymath }); +export async function deployLockUpTMAndVerified(accountPolymath, MRProxyInstance, setupCost, feeInPoly = false) { + I_LockUpTransferManagerLogic = await LockUpTransferManager.new("0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", { from: accountPolymath }); + I_LockUpTransferManagerFactory = await LockUpTransferManagerFactory.new(setupCost, I_LockUpTransferManagerLogic.address, I_PolymathRegistry.address, feeInPoly, { + from: accountPolymath + }); assert.notEqual( - I_VolumeRestrictionTransferManagerFactory.address.valueOf(), + I_LockUpTransferManagerFactory.address.valueOf(), "0x0000000000000000000000000000000000000000", "LockUpTransferManagerFactory contract was not deployed" ); - await registerAndVerifyByMR(I_VolumeRestrictionTransferManagerFactory.address, accountPolymath, MRProxyInstance); - return new Array(I_VolumeRestrictionTransferManagerFactory); + await registerAndVerifyByMR(I_LockUpTransferManagerFactory.address, accountPolymath, MRProxyInstance); + return Promise.all(new Array(I_LockUpTransferManagerFactory)); } -export async function deployScheduleCheckpointAndVerified(accountPolymath, MRProxyInstance, polyToken, setupCost) { - I_ScheduledCheckpointFactory = await ScheduledCheckpointFactory.new(polyToken, setupCost, 0, 0, { from: accountPolymath }); +export async function deployScheduleCheckpointAndVerified(accountPolymath, MRProxyInstance, setupCost, feeInPoly = false) { + I_ScheduledCheckpointFactory = await ScheduledCheckpointFactory.new(setupCost, I_PolymathRegistry.address, feeInPoly, { from: accountPolymath }); assert.notEqual( I_ScheduledCheckpointFactory.address.valueOf(), "0x0000000000000000000000000000000000000000", @@ -321,13 +427,14 @@ export async function deployScheduleCheckpointAndVerified(accountPolymath, MRPro ); await registerAndVerifyByMR(I_ScheduledCheckpointFactory.address, accountPolymath, MRProxyInstance); - return new Array(I_ScheduledCheckpointFactory); + return Promise.all(new Array(I_ScheduledCheckpointFactory)); } /// Deploy the Permission Manager -export async function deployGPMAndVerifyed(accountPolymath, MRProxyInstance, polyToken, setupCost) { - I_GeneralPermissionManagerFactory = await GeneralPermissionManagerFactory.new(polyToken, setupCost, 0, 0, { from: accountPolymath }); +export async function deployGPMAndVerifyed(accountPolymath, MRProxyInstance, setupCost, feeInPoly = false) { + I_GeneralPermissionManagerLogic = await GeneralPermissionManager.new("0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", { from: accountPolymath }); + I_GeneralPermissionManagerFactory = await GeneralPermissionManagerFactory.new(setupCost, I_GeneralPermissionManagerLogic.address, I_PolymathRegistry.address, feeInPoly, { from: accountPolymath }); assert.notEqual( I_GeneralPermissionManagerFactory.address.valueOf(), @@ -337,14 +444,14 @@ export async function deployGPMAndVerifyed(accountPolymath, MRProxyInstance, pol // (B) : Register the GeneralDelegateManagerFactory await registerAndVerifyByMR(I_GeneralPermissionManagerFactory.address, accountPolymath, MRProxyInstance); - return new Array(I_GeneralPermissionManagerFactory); + return Promise.all(new Array(I_GeneralPermissionManagerFactory)); } - /// Deploy the STO Modules -export async function deployDummySTOAndVerifyed(accountPolymath, MRProxyInstance, polyToken, setupCost) { - I_DummySTOFactory = await DummySTOFactory.new(polyToken, setupCost, 0, 0, { from: accountPolymath }); +export async function deployDummySTOAndVerifyed(accountPolymath, MRProxyInstance, setupCost, feeInPoly = false) { + I_DummySTOLogic = await DummySTO.new("0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", { from: accountPolymath }); + I_DummySTOFactory = await DummySTOFactory.new(setupCost, I_DummySTOLogic.address, I_PolymathRegistry.address, feeInPoly, { from: accountPolymath }); assert.notEqual( I_DummySTOFactory.address.valueOf(), @@ -352,11 +459,12 @@ export async function deployDummySTOAndVerifyed(accountPolymath, MRProxyInstance "DummySTOFactory contract was not deployed" ); await registerAndVerifyByMR(I_DummySTOFactory.address, accountPolymath, MRProxyInstance); - return new Array(I_DummySTOFactory); + return Promise.all(new Array(I_DummySTOFactory)); } -export async function deployCappedSTOAndVerifyed(accountPolymath, MRProxyInstance, polyToken, setupCost) { - I_CappedSTOFactory = await CappedSTOFactory.new(polyToken, setupCost, 0, 0, { from: accountPolymath }); +export async function deployCappedSTOAndVerifyed(accountPolymath, MRProxyInstance, setupCost, feeInPoly = false) { + I_CappedSTOLogic = await CappedSTO.new("0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", { from: accountPolymath }); + I_CappedSTOFactory = await CappedSTOFactory.new(setupCost, I_CappedSTOLogic.address, I_PolymathRegistry.address, feeInPoly, { from: accountPolymath }); assert.notEqual( I_CappedSTOFactory.address.valueOf(), "0x0000000000000000000000000000000000000000", @@ -364,12 +472,12 @@ export async function deployCappedSTOAndVerifyed(accountPolymath, MRProxyInstanc ); await registerAndVerifyByMR(I_CappedSTOFactory.address, accountPolymath, MRProxyInstance); - return new Array(I_CappedSTOFactory); - + return Promise.all(new Array(I_CappedSTOFactory)); } -export async function deployPresaleSTOAndVerified(accountPolymath, MRProxyInstance, polyToken, setupCost) { - I_PreSaleSTOFactory = await PreSaleSTOFactory.new(polyToken, setupCost, 0, 0, { from: accountPolymath }); +export async function deployPresaleSTOAndVerified(accountPolymath, MRProxyInstance, setupCost, feeInPoly = false) { + I_PreSaleSTOLogic = await PreSaleSTO.new("0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", { from: accountPolymath }); + I_PreSaleSTOFactory = await PreSaleSTOFactory.new(setupCost, I_PreSaleSTOLogic.address, I_PolymathRegistry.address, feeInPoly, { from: accountPolymath }); assert.notEqual( I_PreSaleSTOFactory.address.valueOf(), @@ -378,13 +486,17 @@ export async function deployPresaleSTOAndVerified(accountPolymath, MRProxyInstan ); await registerAndVerifyByMR(I_PreSaleSTOFactory.address, accountPolymath, MRProxyInstance); - return new Array(I_PreSaleSTOFactory); + return Promise.all(new Array(I_PreSaleSTOFactory)); } -export async function deployUSDTieredSTOAndVerified(accountPolymath, MRProxyInstance, polyToken, setupCost) { - I_USDTieredSTOLogic = await USDTieredSTO.new("0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", { from: accountPolymath }); +export async function deployUSDTieredSTOAndVerified(accountPolymath, MRProxyInstance, setupCost, feeInPoly = false) { + I_USDTieredSTOLogic = await USDTieredSTO.new( + "0x0000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000", + { from: accountPolymath } + ); - I_USDTieredSTOFactory = await USDTieredSTOFactory.new(polyToken, setupCost, 0, 0, I_USDTieredSTOLogic.address, { from: accountPolymath }); + I_USDTieredSTOFactory = await USDTieredSTOFactory.new(setupCost, I_USDTieredSTOLogic.address, I_PolymathRegistry.address, feeInPoly, { from: accountPolymath }); assert.notEqual( I_USDTieredSTOFactory.address.valueOf(), @@ -393,15 +505,20 @@ export async function deployUSDTieredSTOAndVerified(accountPolymath, MRProxyInst ); await registerAndVerifyByMR(I_USDTieredSTOFactory.address, accountPolymath, MRProxyInstance); - return new Array(I_USDTieredSTOFactory); + return Promise.all(new Array(I_USDTieredSTOFactory)); } - /// Deploy the Dividend Modules -export async function deployERC20DividendAndVerifyed(accountPolymath, MRProxyInstance, polyToken, setupCost) { - I_ERC20DividendCheckpointLogic = await ERC20DividendCheckpoint.new("0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", { from: accountPolymath }); - I_ERC20DividendCheckpointFactory = await ERC20DividendCheckpointFactory.new(polyToken, setupCost, 0, 0, I_ERC20DividendCheckpointLogic.address, { from: accountPolymath }); +export async function deployERC20DividendAndVerifyed(accountPolymath, MRProxyInstance, setupCost, feeInPoly = false) { + I_ERC20DividendCheckpointLogic = await ERC20DividendCheckpoint.new( + "0x0000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000", + { from: accountPolymath } + ); + I_ERC20DividendCheckpointFactory = await ERC20DividendCheckpointFactory.new(setupCost, I_ERC20DividendCheckpointLogic.address, I_PolymathRegistry.address, feeInPoly, { + from: accountPolymath + }); assert.notEqual( I_ERC20DividendCheckpointFactory.address.valueOf(), @@ -409,12 +526,18 @@ export async function deployERC20DividendAndVerifyed(accountPolymath, MRProxyIns "ERC20DividendCheckpointFactory contract was not deployed" ); await registerAndVerifyByMR(I_ERC20DividendCheckpointFactory.address, accountPolymath, MRProxyInstance); - return new Array(I_ERC20DividendCheckpointFactory); + return Promise.all(new Array(I_ERC20DividendCheckpointFactory)); } -export async function deployEtherDividendAndVerifyed(accountPolymath, MRProxyInstance, polyToken, setupCost) { - I_EtherDividendCheckpointLogic = await EtherDividendCheckpoint.new("0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", { from: accountPolymath }); - I_EtherDividendCheckpointFactory = await EtherDividendCheckpointFactory.new(polyToken, setupCost, 0, 0, I_EtherDividendCheckpointLogic.address, { from: accountPolymath }); +export async function deployEtherDividendAndVerifyed(accountPolymath, MRProxyInstance, setupCost, feeInPoly = false) { + I_EtherDividendCheckpointLogic = await EtherDividendCheckpoint.new( + "0x0000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000", + { from: accountPolymath } + ); + I_EtherDividendCheckpointFactory = await EtherDividendCheckpointFactory.new(setupCost, I_EtherDividendCheckpointLogic.address, I_PolymathRegistry.address, feeInPoly, { + from: accountPolymath + }); assert.notEqual( I_EtherDividendCheckpointFactory.address.valueOf(), @@ -423,14 +546,13 @@ export async function deployEtherDividendAndVerifyed(accountPolymath, MRProxyIns ); await registerAndVerifyByMR(I_EtherDividendCheckpointFactory.address, accountPolymath, MRProxyInstance); - return new Array(I_EtherDividendCheckpointFactory); + return Promise.all(new Array(I_EtherDividendCheckpointFactory)); } - /// Deploy the Burn Module -export async function deployRedemptionAndVerifyed(accountPolymath, MRProxyInstance, polyToken, setupCost) { - I_TrackedRedemptionFactory = await TrackedRedemptionFactory.new(polyToken, setupCost, 0, 0, { from: accountPolymath }); +export async function deployRedemptionAndVerifyed(accountPolymath, MRProxyInstance, setupCost, feeInPoly = false) { + I_TrackedRedemptionFactory = await TrackedRedemptionFactory.new(setupCost, I_PolymathRegistry.address, feeInPoly, { from: accountPolymath }); assert.notEqual( I_TrackedRedemptionFactory.address.valueOf(), @@ -439,12 +561,12 @@ export async function deployRedemptionAndVerifyed(accountPolymath, MRProxyInstan ); await registerAndVerifyByMR(I_TrackedRedemptionFactory.address, accountPolymath, MRProxyInstance); - return new Array(I_TrackedRedemptionFactory); + return Promise.all(new Array(I_TrackedRedemptionFactory)); } -export async function deployVestingEscrowWalletAndVerifyed(accountPolymath, MRProxyInstance, polyToken, setupCost) { +export async function deployVestingEscrowWalletAndVerifyed(accountPolymath, MRProxyInstance, setupCost, feeInPoly = false) { I_VestingEscrowWalletLogic = await VestingEscrowWallet.new("0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", { from: accountPolymath }); - I_VestingEscrowWalletFactory = await VestingEscrowWalletFactory.new(polyToken, setupCost, 0, 0, I_VestingEscrowWalletLogic.address, { from: accountPolymath }); + I_VestingEscrowWalletFactory = await VestingEscrowWalletFactory.new(setupCost, I_VestingEscrowWalletLogic.address, I_PolymathRegistry.address, feeInPoly, { from: accountPolymath }); assert.notEqual( I_VestingEscrowWalletFactory.address.valueOf(), @@ -456,8 +578,8 @@ export async function deployVestingEscrowWalletAndVerifyed(accountPolymath, MRPr return new Array(I_VestingEscrowWalletFactory); } -export async function deployMockRedemptionAndVerifyed(accountPolymath, MRProxyInstance, polyToken, setupCost) { - I_MockBurnFactory = await MockBurnFactory.new(polyToken, setupCost, 0, 0, { from: accountPolymath }); +export async function deployMockRedemptionAndVerifyed(accountPolymath, MRProxyInstance, setupCost, feeInPoly = false) { + I_MockBurnFactory = await MockBurnFactory.new(setupCost, I_PolymathRegistry.address, feeInPoly, { from: accountPolymath }); assert.notEqual( I_MockBurnFactory.address.valueOf(), @@ -466,11 +588,11 @@ export async function deployMockRedemptionAndVerifyed(accountPolymath, MRProxyIn ); await registerAndVerifyByMR(I_MockBurnFactory.address, accountPolymath, MRProxyInstance); - return new Array(I_MockBurnFactory); + return Promise.all(new Array(I_MockBurnFactory)); } -export async function deployMockWrongTypeRedemptionAndVerifyed(accountPolymath, MRProxyInstance, polyToken, setupCost) { - I_MockWrongTypeBurnFactory = await MockWrongTypeFactory.new(polyToken, setupCost, 0, 0, { from: accountPolymath }); +export async function deployMockWrongTypeRedemptionAndVerifyed(accountPolymath, MRProxyInstance, setupCost, feeInPoly = false) { + I_MockWrongTypeBurnFactory = await MockWrongTypeFactory.new(setupCost, I_PolymathRegistry.address, feeInPoly, { from: accountPolymath }); assert.notEqual( I_MockWrongTypeBurnFactory.address.valueOf(), @@ -479,16 +601,46 @@ export async function deployMockWrongTypeRedemptionAndVerifyed(accountPolymath, ); await registerAndVerifyByMR(I_MockWrongTypeBurnFactory.address, accountPolymath, MRProxyInstance); - return new Array(I_MockWrongTypeBurnFactory); -} - -/// Helper function -function mergeReturn(returnData) { - let returnArray = new Array(); - for (let i = 0; i < returnData.length; i++) { - for (let j = 0; j < returnData[i].length; j++) { - returnArray.push(returnData[i][j]); - } - } - return returnArray; + return Promise.all(new Array(I_MockWrongTypeBurnFactory)); +} + +export async function deploySignedTMAndVerifyed(accountPolymath, MRProxyInstance, setupCost, feeInPoly = false) { + I_SignedTransferManagerFactory = await SignedTransferManagerFactory.new(setupCost, I_PolymathRegistry.address, feeInPoly, { from: accountPolymath }); + assert.notEqual( + I_SignedTransferManagerFactory.address.valueOf(), + "0x0000000000000000000000000000000000000000", + "SignedTransferManagerFactory contract was not deployed" + ); + + await registerAndVerifyByMR(I_SignedTransferManagerFactory.address, accountPolymath, MRProxyInstance); + return new Array(I_SignedTransferManagerFactory); +} + +// Deploy voting modules + +export async function deployPLCRVoteCheckpoint(accountPolymath, MRProxyInstance, setupCost, feeInPoly = false) { + I_PLCRVotingCheckpointLogic = await PLCRVotingCheckpoint.new("0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", { from: accountPolymath }); + I_PLCRVotingCheckpointFactory = await PLCRVotingCheckpointFactory.new(setupCost, I_PLCRVotingCheckpointLogic.address, I_PolymathRegistry.address, feeInPoly, { from: accountPolymath }); + assert.notEqual( + I_PLCRVotingCheckpointFactory.address.valueOf(), + "0x0000000000000000000000000000000000000000", + "PLCRVotingCheckpointFactory contract was not deployed" + ); + + await registerAndVerifyByMR(I_PLCRVotingCheckpointFactory.address, accountPolymath, MRProxyInstance); + return new Array(I_PLCRVotingCheckpointFactory); +} +// Deploy the voting modules + +export async function deployWeightedVoteCheckpoint(accountPolymath, MRProxyInstance, setupCost, feeInPoly = false) { + I_WeightedVoteCheckpointLogic = await WeightedVoteCheckpoint.new("0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", { from: accountPolymath }); + I_WeightedVoteCheckpointFactory = await WeightedVoteCheckpointFactory.new(setupCost, I_WeightedVoteCheckpointLogic.address, I_PolymathRegistry.address, feeInPoly, { from: accountPolymath }); + assert.notEqual( + I_WeightedVoteCheckpointFactory.address.valueOf(), + "0x0000000000000000000000000000000000000000", + "WeightedVoteCheckpointFactory contract was not deployed" + ); + + await registerAndVerifyByMR(I_WeightedVoteCheckpointFactory.address, accountPolymath, MRProxyInstance); + return new Array(I_WeightedVoteCheckpointFactory); } diff --git a/test/helpers/encodeCall.js b/test/helpers/encodeCall.js index 7aaa9bbf8..a1a58056b 100644 --- a/test/helpers/encodeCall.js +++ b/test/helpers/encodeCall.js @@ -11,3 +11,9 @@ export function encodeModuleCall(parametersType, values) { const params = abi.rawEncode(parametersType, values).toString("hex"); return "0x" + methodId + params; } + +export function encodeCall(methodName, parametersType, values) { + const methodId = abi.methodID(methodName, parametersType).toString("hex"); + const params = abi.rawEncode(parametersType, values).toString("hex"); + return "0x" + methodId + params; +} diff --git a/test/helpers/latestTime.js b/test/helpers/latestTime.js index 38c744969..58671e254 100644 --- a/test/helpers/latestTime.js +++ b/test/helpers/latestTime.js @@ -1,4 +1,9 @@ // Returns the time of the last mined block in seconds -export default function latestTime() { - return web3.eth.getBlock("latest").timestamp; +export default async function latestTime() { + let block = await latestBlock(); + return block.timestamp; +} + +async function latestBlock() { + return web3.eth.getBlock("latest"); } diff --git a/test/helpers/signData.js b/test/helpers/signData.js index ea7476cc3..e6f1ad414 100644 --- a/test/helpers/signData.js +++ b/test/helpers/signData.js @@ -1,21 +1,200 @@ -const ethers = require("ethers"); -const utils = ethers.utils; -const ethUtil = require("ethereumjs-util"); +const Web3 = require("web3"); +//const sigUtil = require('eth-sig-util') +let BN = Web3.utils.BN; -//this, _investor, _fromTime, _toTime, _validTo -function signData(tmAddress, investorAddress, fromTime, toTime, expiryTime, restricted, validFrom, validTo, nonce, pk) { - let packedData = utils - .solidityKeccak256( - ["address", "address", "uint256", "uint256", "uint256", "bool", "uint256", "uint256", "uint256"], - [tmAddress, investorAddress, fromTime, toTime, expiryTime, restricted, validFrom, validTo, nonce] - ) - .slice(2); - packedData = new Buffer(packedData, "hex"); - packedData = Buffer.concat([new Buffer(`\x19Ethereum Signed Message:\n${packedData.length.toString()}`), packedData]); - packedData = web3.sha3(`0x${packedData.toString("hex")}`, { encoding: "hex" }); - return ethUtil.ecsign(new Buffer(packedData.slice(2), "hex"), new Buffer(pk, "hex")); +function getSignSTMData(tmAddress, nonce, validFrom, expiry, fromAddress, toAddress, amount, pk) { + let hash = web3.utils.soliditySha3({ + t: 'address', + v: tmAddress + }, { + t: 'uint256', + v: new BN(nonce) + }, { + t: 'uint256', + v: new BN(validFrom) + }, { + t: 'uint256', + v: new BN(expiry) + }, { + t: 'address', + v: fromAddress + }, { + t: 'address', + v: toAddress + }, { + t: 'uint256', + v: new BN(amount) + }); + let signature = (web3.eth.accounts.sign(hash, pk)).signature; + let data = web3.eth.abi.encodeParameters( + ['address', 'uint256', 'uint256', 'uint256', 'bytes'], + [tmAddress, new BN(nonce).toString(), new BN(validFrom).toString(), new BN(expiry).toString(), signature] + ); + return data; +} + +async function getFreezeIssuanceAck(stAddress, from) { + const typedData = { + types: { + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' } + ], + Acknowledgment: [ + { name: 'text', type: 'string' } + ], + }, + primaryType: 'Acknowledgment', + domain: { + name: 'Polymath', + chainId: 1, + verifyingContract: stAddress + }, + message: { + text: 'I acknowledge that freezing Issuance is a permanent and irrevocable change', + }, + }; + const result = await new Promise((resolve, reject) => { + web3.currentProvider.send( + { + method: 'eth_signTypedData', + params: [from, typedData] + }, + (err, result) => { + if (err) { + return reject(err); + } + resolve(result.result); + } + ); + }); + // console.log('signed by', from); + // const recovered = sigUtil.recoverTypedSignature({ + // data: typedData, + // sig: result + // }) + // console.log('recovered address', recovered); + return result; +} + +async function getDisableControllerAck(stAddress, from) { + const typedData = { + types: { + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' } + ], + Acknowledgment: [ + { name: 'text', type: 'string' } + ], + }, + primaryType: 'Acknowledgment', + domain: { + name: 'Polymath', + chainId: 1, + verifyingContract: stAddress + }, + message: { + text: 'I acknowledge that disabling controller is a permanent and irrevocable change', + }, + }; + const result = await new Promise((resolve, reject) => { + web3.currentProvider.send( + { + method: 'eth_signTypedData', + params: [from, typedData] + }, + (err, result) => { + if (err) { + return reject(err); + } + resolve(result.result); + } + ); + }); + return result; +} + +function getSignGTMData(tmAddress, investorAddress, fromTime, toTime, expiryTime, validFrom, validTo, nonce, pk) { + let hash = web3.utils.soliditySha3({ + t: 'address', + v: tmAddress + }, { + t: 'address', + v: investorAddress + }, { + t: 'uint256', + v: new BN(fromTime) + }, { + t: 'uint256', + v: new BN(toTime) + }, { + t: 'uint256', + v: new BN(expiryTime) + }, { + t: 'uint256', + v: new BN(validFrom) + }, { + t: 'uint256', + v: new BN(validTo) + }, { + t: 'uint256', + v: new BN(nonce) + }); + let signature = (web3.eth.accounts.sign(hash, pk)); + return signature.signature; +} + +function getSignGTMTransferData(tmAddress, investorAddress, fromTime, toTime, expiryTime, validFrom, validTo, nonce, pk) { + let signature = getMultiSignGTMData(tmAddress, investorAddress, fromTime, toTime, expiryTime, validFrom, validTo, nonce, pk); + let packedData = web3.eth.abi.encodeParameters( + ['address[]', 'uint256[]', 'uint256[]', 'uint256[]', 'bytes'], + [investorAddress, fromTime, toTime, expiryTime, signature] + ); + let data = web3.eth.abi.encodeParameters( + ['address', 'uint256', 'uint256', 'uint256', 'bytes'], + [tmAddress, new BN(nonce).toString(), new BN(validFrom).toString(), new BN(validTo).toString(), packedData] + ); + return data; +} + +function getMultiSignGTMData(tmAddress, investorAddress, fromTime, toTime, expiryTime, validFrom, validTo, nonce, pk) { + let hash = web3.utils.soliditySha3({ + t: 'address', + v: tmAddress + }, { + t: 'address[]', + v: investorAddress + }, { + t: 'uint256[]', + v: fromTime + }, { + t: 'uint256[]', + v: toTime + }, { + t: 'uint256[]', + v: expiryTime + }, { + t: 'uint256', + v: new BN(validFrom) + }, { + t: 'uint256', + v: new BN(validTo) + }, { + t: 'uint256', + v: new BN(nonce) + }); + let signature = (web3.eth.accounts.sign(hash, pk)).signature; + return signature; } module.exports = { - signData -}; + getSignSTMData, + getSignGTMData, + getSignGTMTransferData, + getMultiSignGTMData, + getFreezeIssuanceAck, + getDisableControllerAck +}; \ No newline at end of file diff --git a/test/helpers/time.js b/test/helpers/time.js index 3e3362d81..ef9553e95 100644 --- a/test/helpers/time.js +++ b/test/helpers/time.js @@ -2,38 +2,44 @@ // aren’t included within the original RPC specification. // See https://github.com/ethereumjs/testrpc#implemented-methods -function increaseTime(duration) { - const id = Date.now(); - - return new Promise((resolve, reject) => { - web3.currentProvider.sendAsync( - { - jsonrpc: "2.0", - method: "evm_increaseTime", - params: [duration], - id: id - }, - err1 => { - if (err1) return reject(err1); +async function advanceBlock() { + return new Promise((resolve, reject) => { + web3.currentProvider.send({ + jsonrpc: '2.0', + method: 'evm_mine', + }, + (err, result) => { + if (err) { + return reject(err); + } + resolve(result.result); + } + ); + }); +} - web3.currentProvider.sendAsync( - { - jsonrpc: "2.0", - method: "evm_mine", - id: id + 1 - }, - (err2, res) => { - return err2 ? reject(err2) : resolve(res); - } - ); - } - ); - }); +// Increases ganache time by the passed duration in seconds +async function increaseTime(duration) { + await new Promise((resolve, reject) => { + web3.currentProvider.send({ + jsonrpc: '2.0', + method: 'evm_increaseTime', + params: [duration], + }, + (err, result) => { + if (err) { + return reject(err); + } + resolve(result.result); + } + ); + }); + await advanceBlock(); } -export default function takeSnapshot() { +async function takeSnapshot() { return new Promise((resolve, reject) => { - web3.currentProvider.sendAsync( + web3.currentProvider.send( { jsonrpc: "2.0", method: "evm_snapshot", @@ -51,9 +57,28 @@ export default function takeSnapshot() { }); } -function revertToSnapshot(snapShotId) { +async function jumpToTime(timestamp) { + await new Promise( + (resolve, reject) => { + web3.currentProvider.send({ + jsonrpc: '2.0', + method: "evm_mine", + params: [timestamp], + }, + (err, result) => { + if (err) { + return reject(err); + } + resolve(result.result); + } + ); + }); + await advanceBlock(); +} + +async function revertToSnapshot(snapShotId) { return new Promise((resolve, reject) => { - web3.currentProvider.sendAsync( + web3.currentProvider.send( { jsonrpc: "2.0", method: "evm_revert", @@ -71,4 +96,4 @@ function revertToSnapshot(snapShotId) { }); } -export { increaseTime, takeSnapshot, revertToSnapshot }; +export { increaseTime, takeSnapshot, revertToSnapshot, jumpToTime }; diff --git a/test/helpers/utils.js b/test/helpers/utils.js index a2c23c218..b89d8b9ee 100644 --- a/test/helpers/utils.js +++ b/test/helpers/utils.js @@ -49,25 +49,7 @@ export const duration = { } }; -/** - * Helper to wait for log emission. - * @param {Object} _event The event to wait for. - */ -export function promisifyLogWatch(_event, _times) { - return new Promise((resolve, reject) => { - let i = 0; - _event.watch((error, log) => { - if (error !== null) reject(error); - i = i + 1; - console.log("Received event: " + i + " out of: " + _times); - if (i == _times) { - _event.stopWatching(); - resolve(log); - } - }); - }); -} - -export function latestBlock() { - return web3.eth.getBlock("latest").number; +export async function latestBlock() { + let block = await web3.eth.getBlock("latest"); + return block.number; } diff --git a/test/i_Issuance.js b/test/i_Issuance.js index 5cef65baf..0044d8c7e 100644 --- a/test/i_Issuance.js +++ b/test/i_Issuance.js @@ -1,6 +1,6 @@ import latestTime from "./helpers/latestTime"; import { duration, ensureException, promisifyLogWatch, latestBlock } from "./helpers/utils"; -import takeSnapshot, { increaseTime, revertToSnapshot } from "./helpers/time"; +import { takeSnapshot, increaseTime, revertToSnapshot } from "./helpers/time"; import { encodeProxyCall, encodeModuleCall } from "./helpers/encodeCall"; import { setUpPolymathNetwork, deployCappedSTOAndVerifyed, deployGPMAndVerifyed } from "./helpers/createInstances"; @@ -9,12 +9,13 @@ const CappedSTO = artifacts.require("./CappedSTO.sol"); const SecurityToken = artifacts.require("./SecurityToken.sol"); const GeneralTransferManager = artifacts.require("./GeneralTransferManager"); const GeneralPermissionManager = artifacts.require("./GeneralPermissionManager"); +const STGetter = artifacts.require("./STGetter.sol"); const Web3 = require("web3"); -const BigNumber = require("bignumber.js"); +let BN = Web3.utils.BN; const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); // Hardcoded development port -contract("Issuance", accounts => { +contract("Issuance", async (accounts) => { // Accounts Variable declaration let account_polymath; let account_investor1; @@ -26,12 +27,12 @@ contract("Issuance", accounts => { let blockNo; let balanceOfReceiver; let message = "Transaction Should Fail!"; - const TM_Perm = "WHITELIST"; - const delegateDetails = "I am delegate"; + const TM_Perm = web3.utils.fromAscii("ADMIN"); + const delegateDetails = web3.utils.fromAscii("I am delegate"); // investor Details - let fromTime = latestTime(); - let toTime = latestTime() + duration.days(15); - let expiryTime = toTime + duration.days(100); + let fromTime; + let toTime; + let expiryTime; // Contract Instance Declaration let I_GeneralPermissionManagerFactory; @@ -51,6 +52,9 @@ contract("Issuance", accounts => { let I_CappedSTO; let I_PolyToken; let I_PolymathRegistry; + let I_STRGetter; + let I_STGetter; + let stGetter; // SecurityToken Details (Launched ST on the behalf of the issuer) const name = "Demo Token"; @@ -64,23 +68,28 @@ contract("Issuance", accounts => { const stoKey = 3; const budget = 0; const address_zero = "0x0000000000000000000000000000000000000000"; + const one_address = "0x0000000000000000000000000000000000000001"; // Initial fee for ticker registry and security token registry - const initRegFee = web3.utils.toWei("250"); + const initRegFee = new BN(web3.utils.toWei("1000")); // Capped STO details //let startTime; // Start time will be 5000 seconds more than the latest time //let endTime; // Add 30 days more - const cap = web3.utils.toWei("10000"); - const rate = web3.utils.toWei("1000"); + const cap = new BN(web3.utils.toWei("10000")); + const rate = new BN(web3.utils.toWei("1000")); const fundRaiseType = [0]; - const cappedSTOSetupCost = web3.utils.toWei("20000", "ether"); - const maxCost = cappedSTOSetupCost; + const cappedSTOSetupCost = new BN(web3.utils.toWei("20000", "ether")); + const cappedSTOSetupCostPOLY = new BN(web3.utils.toWei("80000", "ether")); + const maxCost = cappedSTOSetupCostPOLY; const STOParameters = ["uint256", "uint256", "uint256", "uint256", "uint8[]", "address"]; const STRProxyParameters = ["address", "address", "uint256", "uint256", "address", "address"]; const MRProxyParameters = ["address", "address"]; before(async () => { + fromTime = await latestTime(); + toTime = await latestTime(); + expiryTime = toTime + duration.days(15); // Accounts setup account_polymath = accounts[0]; account_issuer = accounts[1]; @@ -104,13 +113,15 @@ contract("Issuance", accounts => { I_STFactory, I_SecurityTokenRegistry, I_SecurityTokenRegistryProxy, - I_STRProxied + I_STRProxied, + I_STRGetter, + I_STGetter ] = instances; // STEP 2: Deploy the GeneralDelegateManagerFactory - [I_GeneralPermissionManagerFactory] = await deployGPMAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, 0); + [I_GeneralPermissionManagerFactory] = await deployGPMAndVerifyed(account_polymath, I_MRProxied, 0); // STEP 3: Deploy the CappedSTOFactory - [I_CappedSTOFactory] = await deployCappedSTOAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, 0); + [I_CappedSTOFactory] = await deployCappedSTOAndVerifyed(account_polymath, I_MRProxied, 0); // Printing all the contract addresses console.log(` @@ -132,67 +143,57 @@ contract("Issuance", accounts => { }); describe("Launch SecurityToken & STO on the behalf of the issuer", async () => { - describe("Create securityToken for the issuer by the polymath", async () => { - it("POLYMATH: Should register the ticker before the generation of the security token", async () => { - await I_PolyToken.getTokens(10000 * Math.pow(10, 18), account_polymath); + await I_PolyToken.getTokens(new BN(10000).mul(new BN(10).pow(new BN(18))), account_polymath); await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: account_polymath }); - let tx = await I_STRProxied.registerTicker(account_polymath, symbol, name, { from: account_polymath }); + let tx = await I_STRProxied.registerNewTicker(account_polymath, symbol, { from: account_polymath }); assert.equal(tx.logs[0].args._owner, account_polymath); assert.equal(tx.logs[0].args._ticker, symbol); }); it("POLYMATH: Should generate the new security token with the same symbol as registered above", async () => { await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: account_polymath }); - let _blockNo = latestBlock(); - let tx = await I_STRProxied.generateSecurityToken(name, symbol, tokenDetails, false, { from: account_polymath }); + + let tx = await I_STRProxied.generateNewSecurityToken(name, symbol, tokenDetails, false, account_polymath, 0, { from: account_polymath }); // Verify the successful generation of the security token assert.equal(tx.logs[1].args._ticker, symbol, "SecurityToken doesn't get deployed"); - I_SecurityToken = SecurityToken.at(tx.logs[1].args._securityTokenAddress); + I_SecurityToken = await SecurityToken.at(tx.logs[1].args._securityTokenAddress); + stGetter = await STGetter.at(I_SecurityToken.address); + assert.equal(await stGetter.getTreasuryWallet.call(), account_polymath, "Incorrect wallet set") - const log = await promisifyLogWatch(I_SecurityToken.ModuleAdded({ from: _blockNo }), 1); + const log = (await I_SecurityToken.getPastEvents('ModuleAdded', {filter: {transactionHash: tx.transactionHash}}))[0]; // Verify that GeneralTransferManager module get added successfully or not assert.equal(log.args._types[0].toNumber(), transferManagerKey); assert.equal(web3.utils.toAscii(log.args._name).replace(/\u0000/g, ""), "GeneralTransferManager"); }); - it("POLYMATH: Should intialize the auto attached modules", async () => { - let moduleData = (await I_SecurityToken.getModulesByType(transferManagerKey))[0]; - I_GeneralTransferManager = GeneralTransferManager.at(moduleData); + it("POLYMATH: Should initialize the auto attached modules", async () => { + let moduleData = (await stGetter.getModulesByType(transferManagerKey))[0]; + I_GeneralTransferManager = await GeneralTransferManager.at(moduleData); }); it("POLYMATH: Should successfully attach the STO factory with the security token", async () => { // STEP 4: Deploy the CappedSTOFactory - I_CappedSTOFactory = await CappedSTOFactory.new(I_PolyToken.address, cappedSTOSetupCost, 0, 0, { from: account_polymath }); - - assert.notEqual( - I_CappedSTOFactory.address.valueOf(), - address_zero, - "CappedSTOFactory contract was not deployed" - ); - - // (C) : Register the STOFactory - await I_MRProxied.registerModule(I_CappedSTOFactory.address, { from: account_polymath }); - await I_MRProxied.verifyModule(I_CappedSTOFactory.address, true, { from: account_polymath }); + [I_CappedSTOFactory] = await deployCappedSTOAndVerifyed(account_polymath, I_MRProxied, cappedSTOSetupCost); let bytesSTO = encodeModuleCall(STOParameters, [ - latestTime() + duration.seconds(5000), - latestTime() + duration.days(30), + await latestTime() + duration.seconds(5000), + await latestTime() + duration.days(30), cap, rate, fundRaiseType, account_fundsReceiver ]); - await I_PolyToken.getTokens(cappedSTOSetupCost, account_polymath); - await I_PolyToken.transfer(I_SecurityToken.address, cappedSTOSetupCost, { from: account_polymath }); + await I_PolyToken.getTokens(cappedSTOSetupCostPOLY, account_polymath); + await I_PolyToken.transfer(I_SecurityToken.address, cappedSTOSetupCostPOLY, { from: account_polymath }); - const tx = await I_SecurityToken.addModule(I_CappedSTOFactory.address, bytesSTO, maxCost, 0, { from: account_polymath }); + const tx = await I_SecurityToken.addModule(I_CappedSTOFactory.address, bytesSTO, maxCost, new BN(0), false, { from: account_polymath }); assert.equal(tx.logs[3].args._types[0], stoKey, "CappedSTO doesn't get deployed"); assert.equal( @@ -200,22 +201,21 @@ contract("Issuance", accounts => { "CappedSTO", "CappedSTOFactory module was not added" ); - I_CappedSTO = CappedSTO.at(tx.logs[3].args._module); + I_CappedSTO = await CappedSTO.at(tx.logs[3].args._module); }); }); describe("Transfer Manager operations by the polymath_account", async () => { it("Should modify the whitelist", async () => { - fromTime = latestTime(); - toTime = latestTime() + duration.days(15); + fromTime = await latestTime(); + toTime = await latestTime() + duration.days(15); expiryTime = toTime + duration.days(100); - let tx = await I_GeneralTransferManager.modifyWhitelist( + let tx = await I_GeneralTransferManager.modifyKYCData( account_investor1, fromTime + duration.days(70), toTime + duration.days(90), expiryTime + duration.days(50), - true, { from: account_polymath } @@ -223,17 +223,21 @@ contract("Issuance", accounts => { assert.equal(tx.logs[0].args._investor, account_investor1, "Failed in adding the investor in whitelist"); }); - it("Should add the delegate with permission", async() => { - //First attach a permission manager to the token - await I_SecurityToken.addModule(I_GeneralPermissionManagerFactory.address, "", 0, 0, {from: account_polymath}); - let moduleData = (await I_SecurityToken.getModulesByType(permissionManagerKey))[0]; - I_GeneralPermissionManager = GeneralPermissionManager.at(moduleData); - // Add permission to the deletgate (A regesteration process) - await I_GeneralPermissionManager.addDelegate(account_delegate, delegateDetails, { from: account_polymath}); - // Providing the permission to the delegate - await I_GeneralPermissionManager.changePermission(account_delegate, I_GeneralTransferManager.address, TM_Perm, true, { from: account_polymath }); - - assert.isTrue(await I_GeneralPermissionManager.checkPermission(account_delegate, I_GeneralTransferManager.address, TM_Perm)); + it("Should add the delegate with permission", async () => { + //First attach a permission manager to the token + await I_SecurityToken.addModule(I_GeneralPermissionManagerFactory.address, "0x0", new BN(0), new BN(0), false, { from: account_polymath }); + let moduleData = (await stGetter.getModulesByType(permissionManagerKey))[0]; + I_GeneralPermissionManager = await GeneralPermissionManager.at(moduleData); + // Add permission to the deletgate (A regesteration process) + await I_GeneralPermissionManager.addDelegate(account_delegate, delegateDetails, { from: account_polymath }); + // Providing the permission to the delegate + await I_GeneralPermissionManager.changePermission(account_delegate, I_GeneralTransferManager.address, TM_Perm, true, { + from: account_polymath + }); + + assert.isTrue( + await I_GeneralPermissionManager.checkPermission(account_delegate, I_GeneralTransferManager.address, TM_Perm) + ); }); it("POLYMATH: Should change the ownership of the SecurityToken", async () => { @@ -246,7 +250,7 @@ contract("Issuance", accounts => { describe("Operations on the STO", async () => { it("Should Buy the tokens", async () => { balanceOfReceiver = await web3.eth.getBalance(account_fundsReceiver); - blockNo = latestBlock(); + blockNo = await latestBlock(); // Jump time await increaseTime(5000); // Fallback transaction @@ -254,24 +258,24 @@ contract("Issuance", accounts => { from: account_investor1, to: I_CappedSTO.address, gas: 6100000, - value: web3.utils.toWei("1", "ether") + value: new BN(web3.utils.toWei("1", "ether")) }); - assert.equal((await I_CappedSTO.getRaised.call(0)).dividedBy(new BigNumber(10).pow(18)).toNumber(), 1); + assert.equal((await I_CappedSTO.getRaised.call(0)).div(new BN(10).pow(new BN(18))).toNumber(), 1); assert.equal(await I_CappedSTO.investorCount.call(), 1); - assert.equal((await I_SecurityToken.balanceOf(account_investor1)).dividedBy(new BigNumber(10).pow(18)).toNumber(), 1000); + assert.equal((await I_SecurityToken.balanceOf(account_investor1)).div(new BN(10).pow(new BN(18))).toNumber(), 1000); }); it("Verification of the event Token Purchase", async () => { - const log = await promisifyLogWatch(I_CappedSTO.TokenPurchase({ from: blockNo }), 1); + const log = (await I_CappedSTO.getPastEvents('TokenPurchase', {filter: {from: blockNo}}))[0]; assert.equal(log.args.purchaser, account_investor1, "Wrong address of the investor"); - assert.equal(log.args.amount.dividedBy(new BigNumber(10).pow(18)).toNumber(), 1000, "Wrong No. token get dilivered"); + assert.equal(log.args.amount.div(new BN(10).pow(new BN(18))).toNumber(), 1000, "Wrong No. token get dilivered"); }); it("should add the investor into the whitelist by the delegate", async () => { - let tx = await I_GeneralTransferManager.modifyWhitelist(account_investor2, fromTime, toTime, expiryTime, true, { + let tx = await I_GeneralTransferManager.modifyKYCData(account_investor2, fromTime, toTime, expiryTime, { from: account_delegate, gas: 7000000 }); @@ -283,14 +287,14 @@ contract("Issuance", accounts => { from: account_investor2, to: I_CappedSTO.address, gas: 2100000, - value: web3.utils.toWei("1", "ether") + value: new BN(web3.utils.toWei("1", "ether")) }); - assert.equal((await I_CappedSTO.getRaised.call(0)).dividedBy(new BigNumber(10).pow(18)).toNumber(), 2); + assert.equal((await I_CappedSTO.getRaised.call(0)).div(new BN(10).pow(new BN(18))).toNumber(), 2); assert.equal(await I_CappedSTO.investorCount.call(), 2); - assert.equal((await I_SecurityToken.balanceOf(account_investor2)).dividedBy(new BigNumber(10).pow(18)).toNumber(), 1000); + assert.equal((await I_SecurityToken.balanceOf(account_investor2)).div(new BN(10).pow(new BN(18))).toNumber(), 1000); }); }); }); diff --git a/test/j_manual_approval_transfer_manager.js b/test/j_manual_approval_transfer_manager.js index 985d471ca..344f30cf3 100644 --- a/test/j_manual_approval_transfer_manager.js +++ b/test/j_manual_approval_transfer_manager.js @@ -1,6 +1,6 @@ import latestTime from "./helpers/latestTime"; import { duration, ensureException, promisifyLogWatch, latestBlock } from "./helpers/utils"; -import takeSnapshot, { increaseTime, revertToSnapshot } from "./helpers/time"; +import { takeSnapshot, increaseTime, revertToSnapshot } from "./helpers/time"; import { encodeProxyCall } from "./helpers/encodeCall"; import { catchRevert } from "./helpers/exceptions"; import { setUpPolymathNetwork, deployManualApprovalTMAndVerifyed, deployGPMAndVerifyed, deployCountTMAndVerifyed } from "./helpers/createInstances"; @@ -10,11 +10,16 @@ const GeneralTransferManager = artifacts.require("./GeneralTransferManager"); const ManualApprovalTransferManager = artifacts.require("./ManualApprovalTransferManager"); const CountTransferManager = artifacts.require("./CountTransferManager"); const GeneralPermissionManager = artifacts.require("./GeneralPermissionManager"); +const STGetter = artifacts.require("./STGetter.sol"); const Web3 = require("web3"); -const BigNumber = require("bignumber.js"); +let BN = Web3.utils.BN; const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); // Hardcoded development port +const SUCCESS_CODE = 0x51; +const FAILURE_CODE = 0x50; + + contract("ManualApprovalTransferManager", accounts => { // Accounts Variable declaration let account_polymath; @@ -55,6 +60,9 @@ contract("ManualApprovalTransferManager", accounts => { let I_SecurityToken; let I_PolyToken; let I_PolymathRegistry; + let I_STRGetter; + let I_STGetter; + let stGetter; // SecurityToken Details const name = "Team"; @@ -72,11 +80,12 @@ contract("ManualApprovalTransferManager", accounts => { let approvalTime; // Initial fee for ticker registry and security token registry - const initRegFee = web3.utils.toWei("250"); + const initRegFee = web3.utils.toWei("1000"); const STOParameters = ["uint256", "uint256", "uint256", "uint256", "uint8[]", "address"]; - + let currentTime; before(async () => { + currentTime = new BN(await latestTime()); // Accounts setup account_polymath = accounts[0]; account_issuer = accounts[1]; @@ -103,17 +112,19 @@ contract("ManualApprovalTransferManager", accounts => { I_STFactory, I_SecurityTokenRegistry, I_SecurityTokenRegistryProxy, - I_STRProxied + I_STRProxied, + I_STRGetter, + I_STGetter ] = instances; // STEP 2: Deploy the GeneralDelegateManagerFactory - [I_GeneralPermissionManagerFactory] = await deployGPMAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, 0); + [I_GeneralPermissionManagerFactory] = await deployGPMAndVerifyed(account_polymath, I_MRProxied, 0); // STEP 3: Deploy the ManualApprovalTransferManagerFactory - [I_ManualApprovalTransferManagerFactory] = await deployManualApprovalTMAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, 0); + [I_ManualApprovalTransferManagerFactory] = await deployManualApprovalTMAndVerifyed(account_polymath, I_MRProxied, 0); // STEP 4: Deploy the Paid ManualApprovalTransferManagerFactory - [P_ManualApprovalTransferManagerFactory] = await deployManualApprovalTMAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, web3.utils.toWei("500", "ether")); + [P_ManualApprovalTransferManagerFactory] = await deployManualApprovalTMAndVerifyed(account_polymath, I_MRProxied, web3.utils.toWei("500", "ether")); // STEP 5: Deploy the CountTransferManagerFactory - [I_CountTransferManagerFactory] = await deployCountTMAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, 0); + [I_CountTransferManagerFactory] = await deployCountTMAndVerifyed(account_polymath, I_MRProxied, 0); // Printing all the contract addresses console.log(` @@ -137,31 +148,32 @@ contract("ManualApprovalTransferManager", accounts => { describe("Generate the SecurityToken", async () => { it("Should register the ticker before the generation of the security token", async () => { await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); - let tx = await I_STRProxied.registerTicker(token_owner, symbol, contact, { from: token_owner }); + let tx = await I_STRProxied.registerNewTicker(token_owner, symbol, { from: token_owner }); assert.equal(tx.logs[0].args._owner, token_owner); assert.equal(tx.logs[0].args._ticker, symbol.toUpperCase()); }); it("Should generate the new security token with the same symbol as registered above", async () => { await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); - let _blockNo = latestBlock(); - let tx = await I_STRProxied.generateSecurityToken(name, symbol, tokenDetails, false, { from: token_owner }); + + let tx = await I_STRProxied.generateNewSecurityToken(name, symbol, tokenDetails, false, token_owner, 0, { from: token_owner }); // 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 log = await promisifyLogWatch(I_SecurityToken.ModuleAdded({ from: _blockNo }), 1); + I_SecurityToken = await SecurityToken.at(tx.logs[1].args._securityTokenAddress); + stGetter = await STGetter.at(I_SecurityToken.address); + assert.equal(await stGetter.getTreasuryWallet.call(), token_owner, "Incorrect wallet set"); + const log = (await I_SecurityToken.getPastEvents('ModuleAdded', {filter: {transactionHash: tx.transactionHash}}))[0]; // Verify that GeneralTransferManager module get added successfully or not - assert.equal(log.args._types[0].toNumber(), 2); + assert.equal(log.args._types[0].toString(), 2); assert.equal(web3.utils.toUtf8(log.args._name), "GeneralTransferManager"); }); - it("Should intialize the auto attached modules", async () => { - let moduleData = (await I_SecurityToken.getModulesByType(2))[0]; - I_GeneralTransferManager = GeneralTransferManager.at(moduleData); + it("Should initialize the auto attached modules", async () => { + let moduleData = (await stGetter.getModulesByType(2))[0]; + I_GeneralTransferManager = await GeneralTransferManager.at(moduleData); }); }); @@ -169,12 +181,11 @@ contract("ManualApprovalTransferManager", accounts => { it("Should Buy the tokens", async () => { // Add the Investor in to the whitelist - let tx = await I_GeneralTransferManager.modifyWhitelist( + let tx = await I_GeneralTransferManager.modifyKYCData( account_investor1, - latestTime(), - latestTime(), - latestTime() + duration.days(30), - true, + currentTime, + currentTime, + currentTime.add(new BN(duration.days(30))), { from: account_issuer, gas: 6000000 @@ -189,22 +200,21 @@ contract("ManualApprovalTransferManager", accounts => { // Jump time await increaseTime(5000); - + currentTime = new BN(await latestTime()); // Mint some tokens - await I_SecurityToken.mint(account_investor1, web3.utils.toWei("30", "ether"), { from: token_owner }); + await I_SecurityToken.issue(account_investor1, new BN(web3.utils.toWei("30", "ether")), "0x0", { from: token_owner }); - assert.equal((await I_SecurityToken.balanceOf(account_investor1)).toNumber(), web3.utils.toWei("30", "ether")); + assert.equal((await I_SecurityToken.balanceOf(account_investor1)).toString(), web3.utils.toWei("30", "ether")); }); it("Should Buy some more tokens", async () => { // Add the Investor in to the whitelist - let tx = await I_GeneralTransferManager.modifyWhitelist( + let tx = await I_GeneralTransferManager.modifyKYCData( account_investor2, - latestTime(), - latestTime(), - latestTime() + duration.days(30), - true, + currentTime, + currentTime, + currentTime.add(new BN(duration.days(30))), { from: account_issuer, gas: 6000000 @@ -218,15 +228,15 @@ contract("ManualApprovalTransferManager", accounts => { ); // Mint some tokens - await I_SecurityToken.mint(account_investor2, web3.utils.toWei("10", "ether"), { from: token_owner }); + await I_SecurityToken.issue(account_investor2, new BN(web3.utils.toWei("10", "ether")), "0x0", { from: token_owner }); - assert.equal((await I_SecurityToken.balanceOf(account_investor2)).toNumber(), web3.utils.toWei("10", "ether")); + assert.equal((await I_SecurityToken.balanceOf(account_investor2)).toString(), web3.utils.toWei("10", "ether")); }); it("Should successfully attach the ManualApprovalTransferManager with the security token", async () => { - await I_PolyToken.getTokens(web3.utils.toWei("500", "ether"), token_owner); + await I_PolyToken.getTokens(web3.utils.toWei("2000", "ether"), token_owner); await catchRevert( - I_SecurityToken.addModule(P_ManualApprovalTransferManagerFactory.address, "0x", web3.utils.toWei("500", "ether"), 0, { + I_SecurityToken.addModule(P_ManualApprovalTransferManagerFactory.address, "0x0", web3.utils.toWei("2000", "ether"), 0, false, { from: token_owner }) ); @@ -234,43 +244,43 @@ contract("ManualApprovalTransferManager", accounts => { it("Should successfully attach the General permission manager factory with the security token", async () => { let snapId = await takeSnapshot(); - await I_PolyToken.transfer(I_SecurityToken.address, web3.utils.toWei("500", "ether"), { from: token_owner }); + await I_PolyToken.transfer(I_SecurityToken.address, web3.utils.toWei("2000", "ether"), { from: token_owner }); const tx = await I_SecurityToken.addModule( P_ManualApprovalTransferManagerFactory.address, - "0x", - web3.utils.toWei("500", "ether"), + "0x0", + web3.utils.toWei("2000", "ether"), 0, + false, { from: token_owner } ); - assert.equal(tx.logs[3].args._types[0].toNumber(), transferManagerKey, "Manual Approval Transfer Manager doesn't get deployed"); + assert.equal(tx.logs[3].args._types[0].toString(), transferManagerKey, "Manual Approval Transfer Manager doesn't get deployed"); assert.equal( web3.utils.toAscii(tx.logs[3].args._name).replace(/\u0000/g, ""), "ManualApprovalTransferManager", "ManualApprovalTransferManagerFactory module was not added" ); - P_ManualApprovalTransferManagerFactory = ManualApprovalTransferManager.at(tx.logs[3].args._module); + P_ManualApprovalTransferManagerFactory = await ManualApprovalTransferManager.at(tx.logs[3].args._module); await revertToSnapshot(snapId); }); it("Should successfully attach the ManualApprovalTransferManager with the security token", async () => { - const tx = await I_SecurityToken.addModule(I_ManualApprovalTransferManagerFactory.address, "", 0, 0, { from: token_owner }); - assert.equal(tx.logs[2].args._types[0].toNumber(), transferManagerKey, "ManualApprovalTransferManager doesn't get deployed"); + const tx = await I_SecurityToken.addModule(I_ManualApprovalTransferManagerFactory.address, "0x0", 0, 0, false, { from: token_owner }); + assert.equal(tx.logs[2].args._types[0].toString(), transferManagerKey, "ManualApprovalTransferManager doesn't get deployed"); assert.equal( web3.utils.toUtf8(tx.logs[2].args._name), "ManualApprovalTransferManager", "ManualApprovalTransferManager module was not added" ); - I_ManualApprovalTransferManager = ManualApprovalTransferManager.at(tx.logs[2].args._module); + I_ManualApprovalTransferManager = await ManualApprovalTransferManager.at(tx.logs[2].args._module); }); - //function verifyTransfer(address _from, address _to, uint256 _amount, bool _isTransfer) public returns(Result) { - it("Cannot call verifyTransfer on the TM directly if _isTransfer == true", async () => { + + it("Cannot call executeTransfer on the TM directly", async () => { await catchRevert( - I_ManualApprovalTransferManager.verifyTransfer( + I_ManualApprovalTransferManager.executeTransfer( account_investor4, account_investor4, web3.utils.toWei("2", "ether"), - "", - true, + "0x0", { from: token_owner } ) ); @@ -281,19 +291,17 @@ contract("ManualApprovalTransferManager", accounts => { account_investor4, account_investor4, web3.utils.toWei("2", "ether"), - "", - false, + "0x0", { from: token_owner } ); }); it("Add a new token holder", async () => { - let tx = await I_GeneralTransferManager.modifyWhitelist( + let tx = await I_GeneralTransferManager.modifyKYCData( account_investor3, - latestTime(), - latestTime(), - latestTime() + duration.days(10), - true, + currentTime, + currentTime, + currentTime.add(new BN(duration.days(10))), { from: account_issuer, gas: 6000000 @@ -309,9 +317,9 @@ contract("ManualApprovalTransferManager", accounts => { await I_ManualApprovalTransferManager.pause({from: token_owner}); // Add the Investor in to the whitelist // Mint some tokens - await I_SecurityToken.mint(account_investor3, web3.utils.toWei("10", "ether"), { from: token_owner }); + await I_SecurityToken.issue(account_investor3, new BN(web3.utils.toWei("10", "ether")),"0x0", { from: token_owner }); - assert.equal((await I_SecurityToken.balanceOf(account_investor3)).toNumber(), web3.utils.toWei("10", "ether")); + assert.equal((await I_SecurityToken.balanceOf(account_investor3)).toString(), web3.utils.toWei("10", "ether")); // Unpause at the transferManager level await I_ManualApprovalTransferManager.unpause({from: token_owner}); }); @@ -321,20 +329,7 @@ contract("ManualApprovalTransferManager", accounts => { // Mint some tokens await I_SecurityToken.transfer(account_investor1, web3.utils.toWei("1", "ether"), { from: account_investor2 }); - assert.equal((await I_SecurityToken.balanceOf(account_investor1)).toNumber(), web3.utils.toWei("31", "ether")); - }); - - it("Should fail to add a manual approval because invalid _to address", async () => { - await catchRevert( - I_ManualApprovalTransferManager.addManualApproval( - account_investor1, - "", - web3.utils.toWei("2", "ether"), - latestTime() + duration.days(1), - "DESCRIPTION", - { from: token_owner } - ) - ); + assert.equal((await I_SecurityToken.balanceOf(account_investor1)).toString(), web3.utils.toWei("31", "ether")); }); it("Should fail to add a manual approval because invalid expiry time", async () => { @@ -344,30 +339,30 @@ contract("ManualApprovalTransferManager", accounts => { account_investor4, web3.utils.toWei("2", "ether"), 99999, - "DESCRIPTION", + web3.utils.fromAscii("DESCRIPTION"), { from: token_owner } ) ); }); it("Add a manual approval for a 4th investor & return correct length", async () => { - approvalTime = latestTime() + duration.days(1); + approvalTime = currentTime.add(new BN(duration.days(1))); await I_ManualApprovalTransferManager.addManualApproval( account_investor1, account_investor4, web3.utils.toWei("3", "ether"), approvalTime, - "DESCRIPTION", - { + web3.utils.fromAscii("DESCRIPTION"), + { from: token_owner } ); - assert.equal((await I_ManualApprovalTransferManager.getTotalApprovalsLength.call()).toNumber(), 1); + assert.equal((await I_ManualApprovalTransferManager.getTotalApprovalsLength.call()).toString(), 1); }); it("Should return all approvals correctly", async () => { - console.log("current approval length is " + (await I_ManualApprovalTransferManager.getTotalApprovalsLength.call()).toNumber()); + console.log("current approval length is " + (await I_ManualApprovalTransferManager.getTotalApprovalsLength.call()).toString()); let tx = await I_ManualApprovalTransferManager.getAllApprovals({from: token_owner }); assert.equal(tx[0][0], account_investor1); @@ -376,7 +371,7 @@ contract("ManualApprovalTransferManager", accounts => { console.log("2"); assert.equal(tx[2][0], web3.utils.toWei("3")); console.log("3"); - assert.equal(tx[3][0].toNumber(), approvalTime); + assert.equal(tx[3][0].toString(), approvalTime); console.log("4"); assert.equal(web3.utils.toUtf8(tx[4][0]), "DESCRIPTION"); }) @@ -387,9 +382,9 @@ contract("ManualApprovalTransferManager", accounts => { account_investor1, account_investor4, web3.utils.toWei("5", "ether"), - latestTime() + duration.days(1), - "DESCRIPTION", - { + currentTime.add(new BN(duration.days(1))), + web3.utils.fromAscii("DESCRIPTION"), + { from: token_owner } ) @@ -397,20 +392,28 @@ contract("ManualApprovalTransferManager", accounts => { }) it("Check verifyTransfer without actually transferring", async () => { - let verified = await I_SecurityToken.verifyTransfer.call( - account_investor1, + let verified = await I_SecurityToken.canTransfer.call( account_investor4, web3.utils.toWei("2", "ether"), - "" + "0x0", + { + from: account_investor1 + } ); - console.log(JSON.stringify(verified)); - assert.equal(verified, true); + // console.log(JSON.stringify(verified[0])); + assert.equal(verified[0], SUCCESS_CODE); - verified = await I_SecurityToken.verifyTransfer.call(account_investor1, account_investor4, web3.utils.toWei("4", "ether"), ""); - assert.equal(verified, false); + verified = await I_SecurityToken.canTransfer.call(account_investor4, web3.utils.toWei("4", "ether"), "0x0", { + from: account_investor1 + }); + // console.log(JSON.stringify(verified[0])); + assert.equal(verified[0], FAILURE_CODE); - verified = await I_SecurityToken.verifyTransfer.call(account_investor1, account_investor4, web3.utils.toWei("1", "ether"), ""); - assert.equal(verified, true); + verified = await I_SecurityToken.canTransfer.call(account_investor4, web3.utils.toWei("1", "ether"), "0x0", { + from: account_investor1 + }); + // console.log(JSON.stringify(verified[0])); + assert.equal(verified[0], SUCCESS_CODE); }); it("Should fail to sell the tokens more than the allowance", async() => { @@ -427,14 +430,14 @@ contract("ManualApprovalTransferManager", accounts => { let oldBal4 = await I_SecurityToken.balanceOf.call(account_investor4); await I_SecurityToken.transfer(account_investor4, web3.utils.toWei("1"), {from: account_investor1}); let newBal4 = await I_SecurityToken.balanceOf.call(account_investor4); - assert.equal((newBal4.minus(oldBal4)).dividedBy(new BigNumber(10).pow(18)).toNumber(), 1); + assert.equal((newBal4.sub(oldBal4)).div(new BN(10).pow(new BN(18))).toString(), 1); }); it("Should sell more tokens to investor 4 with in the same day(GTM will give INVALID as investor 4 not in the whitelist)", async() => { let oldBal4 = await I_SecurityToken.balanceOf.call(account_investor4); await I_SecurityToken.transfer(account_investor4, web3.utils.toWei("1"), {from: account_investor1}); let newBal4 = await I_SecurityToken.balanceOf.call(account_investor4); - assert.equal((newBal4.minus(oldBal4)).dividedBy(new BigNumber(10).pow(18)).toNumber(), 1); + assert.equal((newBal4.sub(oldBal4)).div(new BN(10).pow(new BN(18))).toString(), 1); let tx = await I_ManualApprovalTransferManager.getActiveApprovalsToUser.call(account_investor1); @@ -446,6 +449,7 @@ contract("ManualApprovalTransferManager", accounts => { it("Should fail to transact after the approval get expired", async() => { await increaseTime(duration.days(1)); + currentTime = new BN(await latestTime()); await catchRevert( I_SecurityToken.transfer(account_investor4, web3.utils.toWei("1"), {from: account_investor1}) ); @@ -456,11 +460,11 @@ contract("ManualApprovalTransferManager", accounts => { I_ManualApprovalTransferManager.modifyManualApproval( account_investor1, account_investor4, - latestTime() + duration.days(2), + currentTime.add(new BN(duration.days(2))), web3.utils.toWei("5"), - "New Description", + web3.utils.fromAscii("New Description"), false, - { + { from: token_owner } ) @@ -469,17 +473,18 @@ contract("ManualApprovalTransferManager", accounts => { it("Should attach the manual approval for the investor4 again", async() => { assert.equal((await I_ManualApprovalTransferManager.getActiveApprovalsToUser.call(account_investor4))[0].length, 0); + currentTime = new BN(await latestTime()); await I_ManualApprovalTransferManager.addManualApproval( account_investor1, account_investor4, web3.utils.toWei("2", "ether"), - latestTime() + duration.days(1), - "DESCRIPTION", - { + currentTime.add(new BN(duration.days(1))), + web3.utils.fromAscii("DESCRIPTION"), + { from: token_owner } ); - assert.equal((await I_ManualApprovalTransferManager.getTotalApprovalsLength.call()).toNumber(), 1); + assert.equal((await I_ManualApprovalTransferManager.getTotalApprovalsLength.call()).toString(), 1); let data = await I_ManualApprovalTransferManager.approvals.call(0); assert.equal(data[0], account_investor1); assert.equal(data[1], account_investor4); @@ -488,15 +493,16 @@ contract("ManualApprovalTransferManager", accounts => { }); it("Should modify the manual approval expiry time for 4th investor", async () => { - expiryTimeMA = latestTime() + duration.days(3); + currentTime = new BN(await latestTime()); + expiryTimeMA = currentTime.add(new BN(duration.days(3))); let tx = await I_ManualApprovalTransferManager.modifyManualApproval( account_investor1, account_investor4, expiryTimeMA, 0, - "New Description", + web3.utils.fromAscii("New Description"), true, - { + { from: token_owner } ); @@ -505,32 +511,34 @@ contract("ManualApprovalTransferManager", accounts => { assert.equal(data[0], account_investor1); assert.equal(data[1], account_investor4); assert.equal(data[2], web3.utils.toWei("2")); - assert.equal(data[3].toNumber(), expiryTimeMA); + assert.equal(data[3].toString(), expiryTimeMA); assert.equal(web3.utils.toUtf8(data[4]), "New Description"); assert.equal(tx.logs[0].args._from, account_investor1); assert.equal(tx.logs[0].args._to, account_investor4); - assert.equal((tx.logs[0].args._expiryTime).toNumber(), expiryTimeMA); - assert.equal((tx.logs[0].args._allowance).toNumber(), web3.utils.toWei("2")); + assert.equal((tx.logs[0].args._expiryTime).toString(), expiryTimeMA); + assert.equal((tx.logs[0].args._allowance).toString(), web3.utils.toWei("2")); assert.equal(web3.utils.toUtf8(tx.logs[0].args._description), "New Description"); }); it("Should transact after two days", async() => { await increaseTime(2); + currentTime = new BN(await latestTime()); let oldBal4 = await I_SecurityToken.balanceOf.call(account_investor4); await I_SecurityToken.transfer(account_investor4, web3.utils.toWei("1"), {from: account_investor1}); let newBal4 = await I_SecurityToken.balanceOf.call(account_investor4); - assert.equal((newBal4.minus(oldBal4)).dividedBy(new BigNumber(10).pow(18)).toNumber(), 1); + assert.equal((newBal4.sub(oldBal4)).div(new BN(10).pow(new BN(18))).toString(), 1); }); it("Should modify the allowance of the manual approval (increase)", async() => { + currentTime = new BN(await latestTime()); await I_ManualApprovalTransferManager.modifyManualApproval( account_investor1, account_investor4, expiryTimeMA, web3.utils.toWei("4"), - "New Description", + web3.utils.fromAscii("New Description"), true, - { + { from: token_owner } ); @@ -538,27 +546,29 @@ contract("ManualApprovalTransferManager", accounts => { let data = await I_ManualApprovalTransferManager.approvals.call(0); assert.equal(data[0], account_investor1); assert.equal(data[1], account_investor4); - assert.equal(data[2].toNumber(), web3.utils.toWei("5")); - assert.equal(data[3].toNumber(), expiryTimeMA); + assert.equal(data[2].toString(), web3.utils.toWei("5")); + assert.equal(data[3].toString(), expiryTimeMA); assert.equal(web3.utils.toUtf8(data[4]), "New Description"); }); it("Should transact according to new allowance", async() => { + currentTime = new BN(await latestTime()); let oldBal4 = await I_SecurityToken.balanceOf.call(account_investor4); await I_SecurityToken.transfer(account_investor4, web3.utils.toWei("3"), {from: account_investor1}); let newBal4 = await I_SecurityToken.balanceOf.call(account_investor4); - assert.equal((newBal4.minus(oldBal4)).dividedBy(new BigNumber(10).pow(18)).toNumber(), 3); + assert.equal((newBal4.sub(oldBal4)).div(new BN(10).pow(new BN(18))).toString(), 3); }); it("Should decrease the allowance", async() => { + currentTime = new BN(await latestTime()); await I_ManualApprovalTransferManager.modifyManualApproval( account_investor1, account_investor4, expiryTimeMA, web3.utils.toWei("1"), - "New Description", + web3.utils.fromAscii("New Description"), false, - { + { from: token_owner } ); @@ -566,8 +576,8 @@ contract("ManualApprovalTransferManager", accounts => { let data = await I_ManualApprovalTransferManager.approvals.call(0); assert.equal(data[0], account_investor1); assert.equal(data[1], account_investor4); - assert.equal(data[2].toNumber(), web3.utils.toWei("1")); - assert.equal(data[3].toNumber(), expiryTimeMA); + assert.equal(data[2].toString(), web3.utils.toWei("1")); + assert.equal(data[3].toString(), expiryTimeMA); assert.equal(web3.utils.toUtf8(data[4]), "New Description"); }); @@ -578,10 +588,11 @@ contract("ManualApprovalTransferManager", accounts => { }); it("Should successfully transfer the tokens within the allowance limit", async() => { + currentTime = new BN(await latestTime()); let oldBal4 = await I_SecurityToken.balanceOf.call(account_investor4); await I_SecurityToken.transfer(account_investor4, web3.utils.toWei("1"), {from: account_investor1}); let newBal4 = await I_SecurityToken.balanceOf.call(account_investor4); - assert.equal((newBal4.minus(oldBal4)).dividedBy(new BigNumber(10).pow(18)).toNumber(), 1); + assert.equal((newBal4.sub(oldBal4)).div(new BN(10).pow(new BN(18))).toString(), 1); }); it("Should fail to modify because allowance is zero", async() => { @@ -591,9 +602,9 @@ contract("ManualApprovalTransferManager", accounts => { account_investor4, expiryTimeMA, web3.utils.toWei("5"), - "New Description", + web3.utils.fromAscii("New Description"), false, - { + { from: token_owner } ) @@ -624,9 +635,9 @@ contract("ManualApprovalTransferManager", accounts => { [account_investor2,account_investor3], [account_investor3,account_investor4], [web3.utils.toWei("2", "ether"), web3.utils.toWei("2", "ether")], - [latestTime() + duration.days(1),latestTime() + duration.days(1)], - ["DESCRIPTION_1", "DESCRIPTION_2"], - { + [currentTime.add(new BN(duration.days(1))),currentTime.add(new BN(duration.days(1)))], + [web3.utils.fromAscii("DESCRIPTION_1"), web3.utils.fromAscii("DESCRIPTION_2")], + { from: account_investor5 } ) @@ -639,9 +650,9 @@ contract("ManualApprovalTransferManager", accounts => { [account_investor2], [account_investor3,account_investor4], [web3.utils.toWei("2", "ether"), web3.utils.toWei("2", "ether")], - [latestTime() + duration.days(1),latestTime() + duration.days(1)], - ["DESCRIPTION_1", "DESCRIPTION_2"], - { + [currentTime.add(new BN(duration.days(1))),currentTime.add(new BN(duration.days(1)))], + [web3.utils.fromAscii("DESCRIPTION_1"), web3.utils.fromAscii("DESCRIPTION_2")], + { from: token_owner } ) @@ -654,9 +665,9 @@ contract("ManualApprovalTransferManager", accounts => { [account_investor2,account_investor3], [account_investor3,account_investor4], [web3.utils.toWei("2", "ether"), web3.utils.toWei("2", "ether")], - [latestTime() + duration.days(1)], - ["DESCRIPTION_1", "DESCRIPTION_2"], - { + [currentTime.add(new BN(duration.days(1)))], + [web3.utils.fromAscii("DESCRIPTION_1"), web3.utils.fromAscii("DESCRIPTION_2")], + { from: token_owner } ) @@ -669,9 +680,9 @@ contract("ManualApprovalTransferManager", accounts => { [account_investor2,account_investor3], [account_investor3,account_investor4], [web3.utils.toWei("2", "ether")], - [latestTime() + duration.days(1),latestTime() + duration.days(1)], - ["DESCRIPTION_1", "DESCRIPTION_2"], - { + [currentTime.add(new BN(duration.days(1))),currentTime.add(new BN(duration.days(1)))], + [web3.utils.fromAscii("DESCRIPTION_1"), web3.utils.fromAscii("DESCRIPTION_2")], + { from: token_owner } ) @@ -684,9 +695,9 @@ contract("ManualApprovalTransferManager", accounts => { [account_investor2,account_investor3], [account_investor3,account_investor4], [web3.utils.toWei("2", "ether"), web3.utils.toWei("2", "ether")], - [latestTime() + duration.days(1),latestTime() + duration.days(1)], - ["DESCRIPTION_1"], - { + [currentTime.add(new BN(duration.days(1))),currentTime.add(new BN(duration.days(1)))], + [web3.utils.fromAscii("DESCRIPTION_1")], + { from: token_owner } ) @@ -694,14 +705,14 @@ contract("ManualApprovalTransferManager", accounts => { }); it("Add multiple manual approvals", async () => { - let time = latestTime() + duration.days(1); + let time = currentTime.add(new BN(duration.days(1))); await I_ManualApprovalTransferManager.addManualApprovalMulti( [account_investor2,account_investor3], [account_investor3,account_investor4], [web3.utils.toWei("2", "ether"), web3.utils.toWei("2", "ether")], - [time,latestTime() + duration.days(1)], - ["DESCRIPTION_1", "DESCRIPTION_2"], - { + [time,currentTime.add(new BN(duration.days(1)))], + [web3.utils.fromAscii("DESCRIPTION_1"), web3.utils.fromAscii("DESCRIPTION_2")], + { from: token_owner } ); @@ -711,8 +722,8 @@ contract("ManualApprovalTransferManager", accounts => { assert.equal((await I_ManualApprovalTransferManager.getActiveApprovalsToUser.call(account_investor3))[0][1], account_investor3); assert.equal((await I_ManualApprovalTransferManager.getActiveApprovalsToUser.call(account_investor3))[1][0], account_investor3); let approvalDetail = await I_ManualApprovalTransferManager.getApprovalDetails.call(account_investor2, account_investor3); - assert.equal(approvalDetail[0].toNumber(), time); - assert.equal(approvalDetail[1].toNumber(), web3.utils.toWei("2", "ether")); + assert.equal(approvalDetail[0].toString(), time); + assert.equal(approvalDetail[1].toString(), web3.utils.toWei("2", "ether")); assert.equal(web3.utils.toUtf8(approvalDetail[2]), "DESCRIPTION_1"); }); @@ -721,7 +732,7 @@ contract("ManualApprovalTransferManager", accounts => { I_ManualApprovalTransferManager.revokeManualApprovalMulti( [account_investor2,account_investor3], [account_investor3,account_investor4], - { + { from: account_investor5 } ) @@ -733,7 +744,7 @@ contract("ManualApprovalTransferManager", accounts => { I_ManualApprovalTransferManager.revokeManualApprovalMulti( [account_investor2,account_investor3], [account_investor3], - { + { from: token_owner } ) @@ -744,7 +755,7 @@ contract("ManualApprovalTransferManager", accounts => { await I_ManualApprovalTransferManager.revokeManualApprovalMulti( [account_investor2,account_investor3], [account_investor3,account_investor4], - { + { from: token_owner } ); @@ -753,12 +764,12 @@ contract("ManualApprovalTransferManager", accounts => { it("Add a manual approval for a 5th investor from issuance", async () => { await I_ManualApprovalTransferManager.addManualApproval( - "", + "0x0000000000000000000000000000000000000000", account_investor5, web3.utils.toWei("2", "ether"), - latestTime() + duration.days(1), - "DESCRIPTION", - { + currentTime.add(new BN(duration.days(1))), + web3.utils.fromAscii("DESCRIPTION"), + { from: token_owner } ); @@ -779,11 +790,11 @@ contract("ManualApprovalTransferManager", accounts => { [1] ); - const tx = await I_SecurityToken.addModule(I_CountTransferManagerFactory.address, bytesCountTM, 0, 0, { from: token_owner }); - assert.equal(tx.logs[2].args._types[0].toNumber(), transferManagerKey, "CountTransferManager doesn't get deployed"); + const tx = await I_SecurityToken.addModule(I_CountTransferManagerFactory.address, bytesCountTM, 0, 0, false, { from: token_owner }); + assert.equal(tx.logs[2].args._types[0].toString(), transferManagerKey, "CountTransferManager doesn't get deployed"); let name = web3.utils.toUtf8(tx.logs[2].args._name); assert.equal(name, "CountTransferManager", "CountTransferManager module was not added"); - I_CountTransferManager = CountTransferManager.at(tx.logs[2].args._module); + I_CountTransferManager = await CountTransferManager.at(tx.logs[2].args._module); }); it("Should get the permission list", async () => { @@ -799,26 +810,21 @@ contract("ManualApprovalTransferManager", accounts => { describe("ManualApproval Transfer Manager Factory test cases", async () => { it("Should get the exact details of the factory", async () => { - assert.equal(await I_ManualApprovalTransferManagerFactory.getSetupCost.call(), 0); + assert.equal(await I_ManualApprovalTransferManagerFactory.setupCost.call(), 0); assert.equal((await I_ManualApprovalTransferManagerFactory.getTypes.call())[0], 2); - let name = web3.utils.toUtf8(await I_ManualApprovalTransferManagerFactory.getName.call()); + let name = web3.utils.toUtf8(await I_ManualApprovalTransferManagerFactory.name.call()); assert.equal(name, "ManualApprovalTransferManager", "Wrong Module added"); let desc = await I_ManualApprovalTransferManagerFactory.description.call(); assert.equal(desc, "Manage transfers using single approvals", "Wrong Module added"); let title = await I_ManualApprovalTransferManagerFactory.title.call(); assert.equal(title, "Manual Approval Transfer Manager", "Wrong Module added"); - let inst = await I_ManualApprovalTransferManagerFactory.getInstructions.call(); - assert.equal( - inst, - "Allows an issuer to set manual approvals for specific pairs of addresses and amounts. Init function takes no parameters.", - "Wrong Module added" - ); - assert.equal(await I_ManualApprovalTransferManagerFactory.version.call(), "2.1.0"); + assert.equal(await I_ManualApprovalTransferManagerFactory.version.call(), "3.0.0"); }); it("Should get the tags of the factory", async () => { let tags = await I_ManualApprovalTransferManagerFactory.getTags.call(); - assert.equal(web3.utils.toUtf8(tags[0]), "ManualApproval"); + assert.equal(web3.utils.toUtf8(tags[0]), "Manual Approval"); + assert.equal(web3.utils.toUtf8(tags[1]), "Transfer Restriction"); }); }); }); diff --git a/test/k_module_registry.js b/test/k_module_registry.js index ba3d55155..1ceda46d5 100644 --- a/test/k_module_registry.js +++ b/test/k_module_registry.js @@ -3,24 +3,28 @@ import { duration, ensureException, latestBlock } from "./helpers/utils"; import { takeSnapshot, increaseTime, revertToSnapshot } from "./helpers/time"; import { encodeProxyCall, encodeModuleCall } from "./helpers/encodeCall"; import { catchRevert } from "./helpers/exceptions"; -import { setUpPolymathNetwork } from "./helpers/createInstances"; +import {deployCappedSTOAndVerifyed, setUpPolymathNetwork} from "./helpers/createInstances"; const CappedSTOFactory = artifacts.require("./CappedSTOFactory.sol"); +const CappedSTO = artifacts.require("./CappedSTO.sol"); +const DummySTO = artifacts.require("./DummySTO.sol"); const DummySTOFactory = artifacts.require("./DummySTOFactory.sol"); const SecurityToken = artifacts.require("./SecurityToken.sol"); const ModuleRegistryProxy = artifacts.require("./ModuleRegistryProxy.sol"); const ModuleRegistry = artifacts.require("./ModuleRegistry.sol"); const GeneralPermissionManagerFactory = artifacts.require("./GeneralPermissionManagerFactory.sol"); +const GeneralPermissionManager = artifacts.require("./GeneralPermissionManager.sol"); const GeneralTransferManagerFactory = artifacts.require("./GeneralTransferManagerFactory.sol"); const MockFactory = artifacts.require("./MockFactory.sol"); const TestSTOFactory = artifacts.require("./TestSTOFactory.sol"); const ReclaimTokens = artifacts.require("./ReclaimTokens.sol"); +const STGetter = artifacts.require("./STGetter.sol"); const Web3 = require("web3"); -const BigNumber = require("bignumber.js"); +let BN = Web3.utils.BN; const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); // Hardcoded development port -contract("ModuleRegistry", accounts => { +contract("ModuleRegistry", async (accounts) => { // Accounts Variable declaration let account_polymath; let account_investor1; @@ -32,14 +36,12 @@ contract("ModuleRegistry", accounts => { let account_temp; let balanceOfReceiver; - // investor Details - let fromTime = latestTime(); - let toTime = latestTime() + duration.days(15); let ID_snap; let message = "Transaction Should fail!"; // Contract Instance Declaration let I_GeneralPermissionManagerFactory; + let I_GeneralPermissionManagerLogic; let I_GeneralTransferManagerFactory; let I_SecurityTokenRegistryProxy; let I_GeneralPermissionManager; @@ -56,13 +58,16 @@ contract("ModuleRegistry", accounts => { let I_SecurityToken; let I_ReclaimERC20; let I_STRProxied; - let I_CappedSTO; + let I_CappedSTOLogic; let I_PolyToken; let I_MockFactory; let I_TestSTOFactory; let I_DummySTOFactory; let I_PolymathRegistry; let I_SecurityToken2; + let I_STRGetter; + let I_STGetter; + let stGetter; // SecurityToken Details (Launched ST on the behalf of the issuer) const name = "Demo Token"; @@ -76,9 +81,10 @@ contract("ModuleRegistry", accounts => { const stoKey = 3; const budget = 0; const address_zero = "0x0000000000000000000000000000000000000000"; + const one_address = "0x0000000000000000000000000000000000000001"; // Initial fee for ticker registry and security token registry - const initRegFee = web3.utils.toWei("250"); + const initRegFee = new BN(web3.utils.toWei("1000")); // delagate details const delegateDetails = "I am delegate .."; @@ -87,14 +93,16 @@ contract("ModuleRegistry", accounts => { // Capped STO details let startTime; let endTime; - const cap = web3.utils.toWei("10000"); + const cap = new BN(web3.utils.toWei("10000")); const rate = 1000; const fundRaiseType = [0]; const STOParameters = ["uint256", "uint256", "uint256", "uint256", "uint8[]", "address"]; const MRProxyParameters = ["address", "address"]; + let currentTime; + before(async () => { - // Accounts setup + currentTime = new BN(await latestTime()); account_polymath = accounts[0]; account_issuer = accounts[1]; account_investor1 = accounts[9]; @@ -118,7 +126,9 @@ contract("ModuleRegistry", accounts => { I_STFactory, I_SecurityTokenRegistry, I_SecurityTokenRegistryProxy, - I_STRProxied + I_STRProxied, + I_STRGetter, + I_STGetter ] = instances; I_ModuleRegistryProxy = await ModuleRegistryProxy.new({from: account_polymath}); @@ -141,10 +151,7 @@ contract("ModuleRegistry", accounts => { describe("Test the initialize the function", async () => { it("Should successfully update the implementation address -- fail because polymathRegistry address is 0x", async () => { - let bytesProxy = encodeProxyCall(MRProxyParameters, [ - address_zero, - account_polymath - ]); + let bytesProxy = encodeProxyCall(MRProxyParameters, [address_zero, account_polymath]); catchRevert( I_ModuleRegistryProxy.upgradeToAndCall("1.0.0", I_ModuleRegistry.address, bytesProxy, { from: account_polymath @@ -154,10 +161,7 @@ contract("ModuleRegistry", accounts => { }); it("Should successfully update the implementation address -- fail because owner address is 0x", async () => { - let bytesProxy = encodeProxyCall(MRProxyParameters, [ - I_PolymathRegistry.address, - address_zero - ]); + let bytesProxy = encodeProxyCall(MRProxyParameters, [I_PolymathRegistry.address, address_zero]); catchRevert( I_ModuleRegistryProxy.upgradeToAndCall("1.0.0", I_ModuleRegistry.address, bytesProxy, { from: account_polymath @@ -167,10 +171,7 @@ contract("ModuleRegistry", accounts => { }); it("Should successfully update the implementation address -- fail because all params are 0x", async () => { - let bytesProxy = encodeProxyCall(MRProxyParameters, [ - address_zero, - address_zero - ]); + let bytesProxy = encodeProxyCall(MRProxyParameters, [address_zero, address_zero]); catchRevert( I_ModuleRegistryProxy.upgradeToAndCall("1.0.0", I_ModuleRegistry.address, bytesProxy, { from: account_polymath @@ -179,15 +180,12 @@ contract("ModuleRegistry", accounts => { ); }); - it("Should successfully update the implementation address", async() => { - let bytesProxy = encodeProxyCall(MRProxyParameters, [ - I_PolymathRegistry.address, - account_polymath - ]); + it("Should successfully update the implementation address", async () => { + let bytesProxy = encodeProxyCall(MRProxyParameters, [I_PolymathRegistry.address, account_polymath]); await I_ModuleRegistryProxy.upgradeToAndCall("1.0.0", I_ModuleRegistry.address, bytesProxy, { from: account_polymath }); I_MRProxied = await ModuleRegistry.at(I_ModuleRegistryProxy.address); await I_PolymathRegistry.changeAddress("ModuleRegistry", I_ModuleRegistryProxy.address, { from: account_polymath }); - }) + }); }); describe("Test cases for the ModuleRegistry", async () => { @@ -248,11 +246,11 @@ contract("ModuleRegistry", accounts => { assert.equal(tx.logs[0].args._owner, account_polymath, "Should be the right owner"); let _list = await I_MRProxied.getModulesByType(transferManagerKey); - assert.equal(_list.length, 1, "Length should be 1"); - assert.equal(_list[0], I_GeneralTransferManagerFactory.address); + assert.equal(_list.length, 0, "Length should be 0 - unverified"); + // assert.equal(_list[0], I_GeneralTransferManagerFactory.address); - let _reputation = await I_MRProxied.getReputationByFactory(I_GeneralTransferManagerFactory.address); - assert.equal(_reputation.length, 0); + let _reputation = await I_MRProxied.getFactoryDetails(I_GeneralTransferManagerFactory.address); + assert.equal(_reputation[2].length, 0); }); it("Should fail the register the module -- Already registered module", async () => { @@ -260,76 +258,78 @@ contract("ModuleRegistry", accounts => { }); it("Should fail in registering the module-- type = 0", async () => { - I_MockFactory = await MockFactory.new(I_PolyToken.address, 0, 0, 0, { from: account_polymath }); + I_MockFactory = await MockFactory.new(new BN(0), one_address, I_PolymathRegistry.address, true, { from: account_polymath }); catchRevert(I_MRProxied.registerModule(I_MockFactory.address, { from: account_polymath })); }); it("Should fail to register the new module because msg.sender is not the owner of the module", async() => { - I_CappedSTOFactory3 = await CappedSTOFactory.new(I_PolyToken.address, 0, 0, 0, { from: account_temp }); - catchRevert( - I_MRProxied.registerModule(I_CappedSTOFactory3.address, { from: token_owner }) - ); + I_CappedSTOLogic = await CappedSTO.new(address_zero, address_zero, { from: account_polymath }); + I_CappedSTOFactory3 = await CappedSTOFactory.new(new BN(0), I_CappedSTOLogic.address, I_PolymathRegistry.address, true, { from: account_temp }); + console.log(await I_MRProxied.owner()); + catchRevert(I_MRProxied.registerModule(I_CappedSTOFactory3.address, { from: token_owner })); }); - it("Should successfully register the module -- fail because no module type uniqueness", async() => { - await I_MockFactory.changeTypes({from: account_polymath }); - catchRevert( - I_MRProxied.registerModule(I_MockFactory.address, { from: account_polymath }) - ); - }) + it("Should successfully register the module -- fail because no module type uniqueness", async () => { + await I_MockFactory.switchTypes({ from: account_polymath }); + catchRevert(I_MRProxied.registerModule(I_MockFactory.address, { from: account_polymath })); + }); }); describe("Test case for verifyModule", async () => { it("Should fail in calling the verify module. Because msg.sender should be account_polymath", async () => { - await catchRevert(I_MRProxied.verifyModule(I_GeneralTransferManagerFactory.address, true, { from: account_temp })); + await catchRevert(I_MRProxied.verifyModule(I_GeneralTransferManagerFactory.address, { from: account_temp })); }); it("Should successfully verify the module -- true", async () => { - let tx = await I_MRProxied.verifyModule(I_GeneralTransferManagerFactory.address, true, { from: account_polymath }); + let tx = await I_MRProxied.verifyModule(I_GeneralTransferManagerFactory.address, { from: account_polymath }); assert.equal(tx.logs[0].args._moduleFactory, I_GeneralTransferManagerFactory.address, "Failed in verifying the module"); - assert.equal(tx.logs[0].args._verified, true, "Failed in verifying the module"); + let info = await I_MRProxied.getFactoryDetails.call(I_GeneralTransferManagerFactory.address); + let _list = await I_MRProxied.getModulesByType(transferManagerKey); + assert.equal(_list.length, 1, "Length should be 1"); + assert.equal(_list[0], I_GeneralTransferManagerFactory.address); + assert.equal(info[0], true); + assert.equal(info[1], account_polymath); }); it("Should successfully verify the module -- false", async () => { - I_CappedSTOFactory1 = await CappedSTOFactory.new(I_PolyToken.address, 0, 0, 0, { from: account_polymath }); + I_CappedSTOFactory1 = await CappedSTOFactory.new(new BN(0), I_CappedSTOLogic.address, I_PolymathRegistry.address, true, { from: account_polymath }); await I_MRProxied.registerModule(I_CappedSTOFactory1.address, { from: account_polymath }); - let tx = await I_MRProxied.verifyModule(I_CappedSTOFactory1.address, false, { from: account_polymath }); + let tx = await I_MRProxied.unverifyModule(I_CappedSTOFactory1.address, { from: account_polymath }); assert.equal(tx.logs[0].args._moduleFactory, I_CappedSTOFactory1.address, "Failed in verifying the module"); - assert.equal(tx.logs[0].args._verified, false, "Failed in verifying the module"); + let info = await I_MRProxied.getFactoryDetails.call(I_CappedSTOFactory1.address); + assert.equal(info[0], false); }); it("Should fail in verifying the module. Because the module is not registered", async () => { - await catchRevert(I_MRProxied.verifyModule(I_MockFactory.address, true, { from: account_polymath })); + await catchRevert(I_MRProxied.verifyModule(I_MockFactory.address, { from: account_polymath })); }); }); describe("Test cases for the useModule function of the module registry", async () => { it("Deploy the securityToken", async () => { - await I_PolyToken.getTokens(web3.utils.toWei("500"), account_issuer); - await I_PolyToken.approve(I_STRProxied.address, web3.utils.toWei("500"), { from: account_issuer }); - await I_STRProxied.registerTicker(account_issuer, symbol, name, { from: account_issuer }); - let tx = await I_STRProxied.generateSecurityToken(name, symbol, tokenDetails, true, { from: account_issuer }); + await I_PolyToken.getTokens(new BN(web3.utils.toWei("2000")), account_issuer); + await I_PolyToken.approve(I_STRProxied.address, new BN(web3.utils.toWei("2000")), { from: account_issuer }); + await I_STRProxied.registerNewTicker(account_issuer, symbol, { from: account_issuer }); + let tx = await I_STRProxied.generateNewSecurityToken(name, symbol, tokenDetails, true, account_issuer, 0, { from: account_issuer }); assert.equal(tx.logs[1].args._ticker, symbol.toUpperCase()); - I_SecurityToken = SecurityToken.at(tx.logs[1].args._securityTokenAddress); + I_SecurityToken = await SecurityToken.at(tx.logs[1].args._securityTokenAddress); + stGetter = await STGetter.at(I_SecurityToken.address); + assert.equal(await stGetter.getTreasuryWallet.call(), account_issuer, "Incorrect wallet set") }); it("Should fail in adding module. Because module is un-verified", async () => { - startTime = latestTime() + duration.seconds(5000); + startTime = await latestTime() + duration.seconds(5000); endTime = startTime + duration.days(30); let bytesSTO = encodeModuleCall(STOParameters, [startTime, endTime, cap, rate, fundRaiseType, account_fundsReceiver]); - await catchRevert(I_SecurityToken.addModule(I_CappedSTOFactory1.address, bytesSTO, 0, 0, { from: token_owner })); + await catchRevert(I_SecurityToken.addModule(I_CappedSTOFactory1.address, bytesSTO, new BN(0), new BN(0), false, { from: token_owner })); }); it("Should fail to register module because custom modules not allowed", async () => { - I_CappedSTOFactory2 = await CappedSTOFactory.new(I_PolyToken.address, 0, 0, 0, { from: token_owner }); + I_CappedSTOFactory2 = await CappedSTOFactory.new(new BN(0), I_CappedSTOLogic.address, I_PolymathRegistry.address, true, { from: token_owner }); - assert.notEqual( - I_CappedSTOFactory2.address.valueOf(), - address_zero, - "CappedSTOFactory contract was not deployed" - ); + assert.notEqual(I_CappedSTOFactory2.address.valueOf(), address_zero, "CappedSTOFactory contract was not deployed"); await catchRevert(I_MRProxied.registerModule(I_CappedSTOFactory2.address, { from: token_owner })); }); @@ -349,11 +349,11 @@ contract("ModuleRegistry", accounts => { }); it("Should successfully add module because custom modules switched on", async () => { - startTime = latestTime() + duration.seconds(5000); + startTime = await latestTime() + duration.seconds(5000); endTime = startTime + duration.days(30); let bytesSTO = encodeModuleCall(STOParameters, [startTime, endTime, cap, rate, fundRaiseType, account_fundsReceiver]); let tx = await I_MRProxied.registerModule(I_CappedSTOFactory2.address, { from: token_owner }); - tx = await I_SecurityToken.addModule(I_CappedSTOFactory2.address, bytesSTO, 0, 0, { from: token_owner }); + tx = await I_SecurityToken.addModule(I_CappedSTOFactory2.address, bytesSTO, new BN(0), new BN(0), false, { from: token_owner }); assert.equal(tx.logs[2].args._types[0], stoKey, "CappedSTO doesn't get deployed"); assert.equal( @@ -361,83 +361,86 @@ contract("ModuleRegistry", accounts => { "CappedSTO", "CappedSTOFactory module was not added" ); - let _reputation = await I_MRProxied.getReputationByFactory.call(I_CappedSTOFactory2.address); - assert.equal(_reputation.length, 1); + let _reputation = await I_MRProxied.getFactoryDetails.call(I_CappedSTOFactory2.address); + assert.equal(_reputation[2].length, 1); }); - it("Should successfully add module when custom modules switched on -- fail because factory owner is different", async() => { - await I_MRProxied.registerModule(I_CappedSTOFactory3.address, { from: account_temp }) - startTime = latestTime() + duration.seconds(5000); + it("Should successfully add module when custom modules switched on -- fail because factory owner is different", async () => { + await I_MRProxied.registerModule(I_CappedSTOFactory3.address, { from: account_temp }); + startTime = await latestTime() + duration.seconds(5000); endTime = startTime + duration.days(30); let bytesSTO = encodeModuleCall(STOParameters, [startTime, endTime, cap, rate, fundRaiseType, account_fundsReceiver]); - catchRevert( - I_SecurityToken.addModule(I_CappedSTOFactory3.address, bytesSTO, 0, 0, { from: token_owner }) - ); - }) + catchRevert(I_SecurityToken.addModule(I_CappedSTOFactory3.address, bytesSTO, new BN(0), new BN(0), false, { from: token_owner })); + }); it("Should successfully add verified module", async () => { - I_GeneralPermissionManagerFactory = await GeneralPermissionManagerFactory.new(I_PolyToken.address, 0, 0, 0, { + I_GeneralPermissionManagerLogic = await GeneralPermissionManager.new("0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", { from: account_polymath }); + I_GeneralPermissionManagerFactory = await GeneralPermissionManagerFactory.new(new BN(0), I_GeneralPermissionManagerLogic.address, I_PolymathRegistry.address, true, { from: account_polymath }); await I_MRProxied.registerModule(I_GeneralPermissionManagerFactory.address, { from: account_polymath }); - await I_MRProxied.verifyModule(I_GeneralPermissionManagerFactory.address, true, { from: account_polymath }); - let tx = await I_SecurityToken.addModule(I_GeneralPermissionManagerFactory.address, "", 0, 0, { from: token_owner }); + await I_MRProxied.verifyModule(I_GeneralPermissionManagerFactory.address, { from: account_polymath }); + let tx = await I_SecurityToken.addModule(I_GeneralPermissionManagerFactory.address, "0x0", new BN(0), new BN(0), false, { from: token_owner }); assert.equal(tx.logs[2].args._types[0], permissionManagerKey, "module doesn't get deployed"); }); it("Should failed in adding the TestSTOFactory module because not compatible with the current protocol version --lower", async () => { - I_TestSTOFactory = await TestSTOFactory.new(I_PolyToken.address, 0, 0, 0, { from: account_polymath }); + let I_TestSTOFactoryLogic = await DummySTO.new(address_zero, address_zero); + I_TestSTOFactory = await TestSTOFactory.new(new BN(0), I_TestSTOFactoryLogic.address, I_PolymathRegistry.address, true, { from: account_polymath }); await I_MRProxied.registerModule(I_TestSTOFactory.address, { from: account_polymath }); - await I_MRProxied.verifyModule(I_TestSTOFactory.address, true, { from: account_polymath }); + await I_MRProxied.verifyModule(I_TestSTOFactory.address, { from: account_polymath }); // Taking the snapshot the revert the changes from here let id = await takeSnapshot(); - await I_TestSTOFactory.changeSTVersionBounds("lowerBound", [2, 1, 0], { from: account_polymath }); + await I_TestSTOFactory.changeSTVersionBounds("lowerBound", [3, 1, 0], { from: account_polymath }); let _lstVersion = await I_TestSTOFactory.getLowerSTVersionBounds.call(); - assert.equal(_lstVersion[2], 0); + assert.equal(_lstVersion[0], 3); assert.equal(_lstVersion[1], 1); + assert.equal(_lstVersion[2], 0); let bytesData = encodeModuleCall( ["uint256", "uint256", "uint256", "string"], - [latestTime(), latestTime() + duration.days(1), cap, "Test STO"] + [await latestTime(), currentTime.add(new BN(duration.days(1))), cap, "Test STO"] ); - - await catchRevert(I_SecurityToken.addModule(I_TestSTOFactory.address, bytesData, 0, 0, { from: token_owner })); + console.log("I_TestSTOFactory:" +I_TestSTOFactory.address); + await catchRevert(I_SecurityToken.addModule(I_TestSTOFactory.address, bytesData, new BN(0), new BN(0), false, { from: token_owner })); await revertToSnapshot(id); }); it("Should failed in adding the TestSTOFactory module because not compatible with the current protocol version --upper", async () => { - await I_TestSTOFactory.changeSTVersionBounds("upperBound", [0, 0, 1], { from: account_polymath }); + await I_TestSTOFactory.changeSTVersionBounds("upperBound", [0, new BN(0), 1], { from: account_polymath }); let _ustVersion = await I_TestSTOFactory.getUpperSTVersionBounds.call(); assert.equal(_ustVersion[0], 0); assert.equal(_ustVersion[2], 1); - await I_STRProxied.setProtocolVersion(I_STFactory.address, 2, 0, 1); - + await I_STRProxied.setProtocolFactory(I_STFactory.address, 2, new BN(0), 1); + await I_STRProxied.setLatestVersion(2, new BN(0), 1); // Generate the new securityToken let newSymbol = "toro"; - await I_PolyToken.getTokens(web3.utils.toWei("500"), account_issuer); - await I_PolyToken.approve(I_STRProxied.address, web3.utils.toWei("500"), { from: account_issuer }); - await I_STRProxied.registerTicker(account_issuer, newSymbol, name, { from: account_issuer }); - let tx = await I_STRProxied.generateSecurityToken(name, newSymbol, tokenDetails, true, { from: account_issuer }); + await I_PolyToken.getTokens(new BN(web3.utils.toWei("2000")), account_issuer); + await I_PolyToken.approve(I_STRProxied.address, new BN(web3.utils.toWei("2000")), { from: account_issuer }); + await I_STRProxied.registerNewTicker(account_issuer, newSymbol, { from: account_issuer }); + let tx = await I_STRProxied.generateNewSecurityToken(name, newSymbol, tokenDetails, true, account_issuer, 0, { from: account_issuer }); assert.equal(tx.logs[1].args._ticker, newSymbol.toUpperCase()); - I_SecurityToken2 = SecurityToken.at(tx.logs[1].args._securityTokenAddress); - + I_SecurityToken2 = await SecurityToken.at(tx.logs[1].args._securityTokenAddress); + stGetter = await STGetter.at(I_SecurityToken2.address); let bytesData = encodeModuleCall( ["uint256", "uint256", "uint256", "string"], - [latestTime(), latestTime() + duration.days(1), cap, "Test STO"] + [await latestTime(), currentTime.add(new BN(duration.days(1))), cap, "Test STO"] ); - await catchRevert(I_SecurityToken2.addModule(I_TestSTOFactory.address, bytesData, 0, 0, { from: token_owner })); + await catchRevert(I_SecurityToken2.addModule(I_TestSTOFactory.address, bytesData, new BN(0), new BN(0), false, { from: token_owner })); }); }); describe("Test case for the getModulesByTypeAndToken()", async () => { it("Should get the list of available modules when the customModulesAllowed", async () => { let _list = await I_MRProxied.getModulesByTypeAndToken.call(3, I_SecurityToken.address); + console.log(_list); assert.equal(_list[0], I_CappedSTOFactory2.address); }); it("Should get the list of available modules when the customModulesAllowed is not allowed", async () => { await I_FeatureRegistry.setFeatureStatus("customModulesAllowed", false, { from: account_polymath }); let _list = await I_MRProxied.getModulesByTypeAndToken.call(3, I_SecurityToken.address); + console.log(_list); assert.equal(_list.length, 0); }); }); @@ -476,55 +479,52 @@ contract("ModuleRegistry", accounts => { it("Should successfully remove module and delete data if msg.sender is curator", async () => { let snap = await takeSnapshot(); - + console.log("All modules: " + (await I_MRProxied.getModulesByType.call(3))); let sto1 = (await I_MRProxied.getModulesByType.call(3))[0]; - let sto2 = (await I_MRProxied.getModulesByType.call(3))[1]; - let sto3 = (await I_MRProxied.getModulesByType.call(3))[2]; - let sto4 = (await I_MRProxied.getModulesByType.call(3))[3]; + // let sto2 = (await I_MRProxied.getModulesByType.call(3))[1]; + // let sto3 = (await I_MRProxied.getModulesByType.call(3))[2]; + // let sto4 = (await I_MRProxied.getModulesByType.call(3))[3]; - assert.equal(sto1, I_CappedSTOFactory1.address); - assert.equal(sto2, I_CappedSTOFactory2.address); - assert.equal((await I_MRProxied.getModulesByType.call(3)).length, 4); + assert.equal(sto1, I_TestSTOFactory.address); + assert.equal((await I_MRProxied.getModulesByType.call(3)).length, 1); - let tx = await I_MRProxied.removeModule(sto4, { from: account_polymath }); + let tx = await I_MRProxied.removeModule(sto1, { from: account_polymath }); - assert.equal(tx.logs[0].args._moduleFactory, sto4, "Event is not properly emitted for _moduleFactory"); + assert.equal(tx.logs[0].args._moduleFactory, sto1, "Event is not properly emitted for _moduleFactory"); assert.equal(tx.logs[0].args._decisionMaker, account_polymath, "Event is not properly emitted for _decisionMaker"); let sto3_end = (await I_MRProxied.getModulesByType.call(3))[2]; - // re-ordering - assert.equal(sto3_end, sto3); // delete related data - assert.equal(await I_MRProxied.getUintValue.call(web3.utils.soliditySha3("registry", sto4)), 0); - assert.equal(await I_MRProxied.getReputationByFactory.call(sto4), 0); - assert.equal((await I_MRProxied.getModulesByType.call(3)).length, 3); - assert.equal(await I_MRProxied.getBoolValue.call(web3.utils.soliditySha3("verified", sto4)), false); + assert.equal(await I_MRProxied.getUintValue.call(web3.utils.soliditySha3("registry", sto1)), 0); + assert.equal((await I_MRProxied.getFactoryDetails.call(sto1))[1], 0); + assert.equal((await I_MRProxied.getModulesByType.call(3)).length, 0); + assert.equal(await I_MRProxied.getBoolValue.call(web3.utils.soliditySha3("verified", sto1)), false); await revertToSnapshot(snap); }); it("Should successfully remove module and delete data if msg.sender is owner", async () => { - let sto1 = (await I_MRProxied.getModulesByType.call(3))[0]; - let sto2 = (await I_MRProxied.getModulesByType.call(3))[1]; + let sto1 = (await I_MRProxied.getAllModulesByType.call(3))[0]; + let sto2 = (await I_MRProxied.getAllModulesByType.call(3))[1]; assert.equal(sto1, I_CappedSTOFactory1.address); assert.equal(sto2, I_CappedSTOFactory2.address); - assert.equal((await I_MRProxied.getModulesByType.call(3)).length, 4); + assert.equal((await I_MRProxied.getAllModulesByType.call(3)).length, 4); let tx = await I_MRProxied.removeModule(sto2, { from: token_owner }); assert.equal(tx.logs[0].args._moduleFactory, sto2, "Event is not properly emitted for _moduleFactory"); assert.equal(tx.logs[0].args._decisionMaker, token_owner, "Event is not properly emitted for _decisionMaker"); - let sto1_end = (await I_MRProxied.getModulesByType.call(3))[0]; + let sto1_end = (await I_MRProxied.getAllModulesByType.call(3))[0]; // re-ordering assert.equal(sto1_end, sto1); // delete related data assert.equal(await I_MRProxied.getUintValue.call(web3.utils.soliditySha3("registry", sto2)), 0); - assert.equal(await I_MRProxied.getReputationByFactory.call(sto2), 0); - assert.equal((await I_MRProxied.getModulesByType.call(3)).length, 3); + assert.equal((await I_MRProxied.getFactoryDetails.call(sto2))[1], 0); + assert.equal((await I_MRProxied.getAllModulesByType.call(3)).length, 3); assert.equal(await I_MRProxied.getBoolValue.call(web3.utils.soliditySha3("verified", sto2)), false); }); @@ -535,28 +535,23 @@ contract("ModuleRegistry", accounts => { describe("Test cases for IRegistry functionality", async () => { describe("Test cases for reclaiming funds", async () => { - - it("Should successfully reclaim POLY tokens -- fail because token address will be 0x", async() => { - await I_PolyToken.transfer(I_MRProxied.address, web3.utils.toWei("1"), { from: token_owner }); - catchRevert( - I_MRProxied.reclaimERC20("0x000000000000000000000000000000000000000", { from: account_polymath }) - ); + it("Should successfully reclaim POLY tokens -- fail because token address will be 0x", async () => { + await I_PolyToken.transfer(I_MRProxied.address, new BN(web3.utils.toWei("1")), { from: token_owner }); + catchRevert(I_MRProxied.reclaimERC20(address_zero, { from: account_polymath })); }); - it("Should successfully reclaim POLY tokens -- not authorised", async() => { - catchRevert( - I_MRProxied.reclaimERC20(I_PolyToken.address, { from: account_temp }) - ); + it("Should successfully reclaim POLY tokens -- not authorised", async () => { + catchRevert(I_MRProxied.reclaimERC20(I_PolyToken.address, { from: account_temp })); }); it("Should successfully reclaim POLY tokens", async () => { - await I_PolyToken.getTokens(web3.utils.toWei("1"), I_MRProxied.address); + await I_PolyToken.getTokens(new BN(web3.utils.toWei("1")), I_MRProxied.address); let bal1 = await I_PolyToken.balanceOf.call(account_polymath); await I_MRProxied.reclaimERC20(I_PolyToken.address); let bal2 = await I_PolyToken.balanceOf.call(account_polymath); assert.isAtLeast( - bal2.dividedBy(new BigNumber(10).pow(18)).toNumber(), - bal2.dividedBy(new BigNumber(10).pow(18)).toNumber() + bal2.div(new BN(10).pow(new BN(18))).toNumber(), + bal2.div(new BN(10).pow(new BN(18))).toNumber() ); }); }); @@ -583,76 +578,61 @@ contract("ModuleRegistry", accounts => { }); }); - describe("Test cases for the ReclaimTokens contract", async() => { - - it("Should successfully reclaim POLY tokens -- fail because token address will be 0x", async() => { + describe("Test cases for the ReclaimTokens contract", async () => { + it("Should successfully reclaim POLY tokens -- fail because token address will be 0x", async () => { I_ReclaimERC20 = await ReclaimTokens.at(I_FeatureRegistry.address); - await I_PolyToken.transfer(I_ReclaimERC20.address, web3.utils.toWei("1"), { from: token_owner }); - catchRevert( - I_ReclaimERC20.reclaimERC20("0x000000000000000000000000000000000000000", { from: account_polymath }) - ); + await I_PolyToken.transfer(I_ReclaimERC20.address, new BN(web3.utils.toWei("1")), { from: token_owner }); + catchRevert(I_ReclaimERC20.reclaimERC20(address_zero, { from: account_polymath })); }); - it("Should successfully reclaim POLY tokens -- not authorised", async() => { - catchRevert( - I_ReclaimERC20.reclaimERC20(I_PolyToken.address, { from: account_temp }) - ); + it("Should successfully reclaim POLY tokens -- not authorised", async () => { + catchRevert(I_ReclaimERC20.reclaimERC20(I_PolyToken.address, { from: account_temp })); }); it("Should successfully reclaim POLY tokens", async () => { - await I_PolyToken.getTokens(web3.utils.toWei("1"), I_ReclaimERC20.address); + await I_PolyToken.getTokens(new BN(web3.utils.toWei("1")), I_ReclaimERC20.address); let bal1 = await I_PolyToken.balanceOf.call(account_polymath); await I_ReclaimERC20.reclaimERC20(I_PolyToken.address); let bal2 = await I_PolyToken.balanceOf.call(account_polymath); assert.isAtLeast( - bal2.dividedBy(new BigNumber(10).pow(18)).toNumber(), - bal2.dividedBy(new BigNumber(10).pow(18)).toNumber() + bal2.div(new BN(10).pow(new BN(18))).toNumber(), + bal2.div(new BN(10).pow(new BN(18))).toNumber() ); }); - }) - - describe("Test case for the PolymathRegistry", async() => { + }); - it("Should successfully get the address -- fail because key is not exist", async() => { - catchRevert( - I_PolymathRegistry.getAddress("PolyOracle") - ); + describe("Test case for the PolymathRegistry", async () => { + it("Should successfully get the address -- fail because key is not exist", async () => { + catchRevert(I_PolymathRegistry.getAddress("PolyOracle")); }); - it("Should successfully get the address", async() => { + it("Should successfully get the address", async () => { let _moduleR = await I_PolymathRegistry.getAddress("ModuleRegistry"); assert.equal(_moduleR, I_ModuleRegistryProxy.address); - }) - }) - - - describe("Test cases for the transferOwnership", async() => { + }); + }); - it("Should fail to transfer the ownership -- not authorised", async() => { - catchRevert( - I_MRProxied.transferOwnership(account_temp, { from: account_issuer}) - ); + describe("Test cases for the transferOwnership", async () => { + it("Should fail to transfer the ownership -- not authorised", async () => { + catchRevert(I_MRProxied.transferOwnership(account_temp, { from: account_issuer })); }); - it("Should fail to transfer the ownership -- 0x address is not allowed", async() => { - catchRevert( - I_MRProxied.transferOwnership("0x000000000000000000000000000000000000000", { from: account_polymath}) - ); + it("Should fail to transfer the ownership -- 0x address is not allowed", async () => { + catchRevert(I_MRProxied.transferOwnership(address_zero, { from: account_polymath })); }); - it("Should successfully transfer the ownership of the STR", async() => { + it("Should successfully transfer the ownership of the STR", async () => { let tx = await I_MRProxied.transferOwnership(account_temp, { from: account_polymath }); assert.equal(tx.logs[0].args.previousOwner, account_polymath); assert.equal(tx.logs[0].args.newOwner, account_temp); }); - it("New owner has authorisation", async() => { + it("New owner has authorisation", async () => { let tx = await I_MRProxied.transferOwnership(account_polymath, { from: account_temp }); assert.equal(tx.logs[0].args.previousOwner, account_temp); assert.equal(tx.logs[0].args.newOwner, account_polymath); }); - - }) + }); }); }); }); diff --git a/test/l_percentage_transfer_manager.js b/test/l_percentage_transfer_manager.js index e122bdea2..ef3aaca48 100644 --- a/test/l_percentage_transfer_manager.js +++ b/test/l_percentage_transfer_manager.js @@ -1,6 +1,6 @@ import latestTime from "./helpers/latestTime"; import { duration, promisifyLogWatch, latestBlock } from "./helpers/utils"; -import takeSnapshot, { increaseTime, revertToSnapshot } from "./helpers/time"; +import { takeSnapshot, increaseTime, revertToSnapshot } from "./helpers/time"; import { setUpPolymathNetwork, deployGPMAndVerifyed, deployPercentageTMAndVerified } from "./helpers/createInstances"; import { catchRevert } from "./helpers/exceptions"; @@ -8,12 +8,13 @@ const GeneralTransferManager = artifacts.require("./GeneralTransferManager"); const PercentageTransferManager = artifacts.require("./PercentageTransferManager"); const GeneralPermissionManager = artifacts.require("./GeneralPermissionManager"); const SecurityToken = artifacts.require("./SecurityToken.sol"); +const STGetter = artifacts.require("./STGetter.sol"); const Web3 = require("web3"); -const BigNumber = require("bignumber.js"); +let BN = Web3.utils.BN; const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); // Hardcoded development port -contract("PercentageTransferManager", accounts => { +contract("PercentageTransferManager", async (accounts) => { // Accounts Variable declaration let account_polymath; let account_issuer; @@ -24,11 +25,6 @@ contract("PercentageTransferManager", accounts => { let account_investor4; let account_delegate; - // investor Details - let fromTime = latestTime(); - let toTime = latestTime(); - let expiryTime = toTime + duration.days(15); - let message = "Transaction Should Fail!"; // Contract Instance Declaration @@ -50,7 +46,10 @@ contract("PercentageTransferManager", accounts => { let I_STFactory; let I_SecurityToken; let I_PolyToken; - var I_PolymathRegistry; + let I_STRGetter; + let I_PolymathRegistry; + let I_STGetter; + let stGetter; // SecurityToken Details const name = "Team"; @@ -58,7 +57,8 @@ contract("PercentageTransferManager", accounts => { const tokenDetails = "This is equity type of issuance"; const decimals = 18; const contact = "team@polymath.network"; - const delegateDetails = "Hello I am legit delegate"; + const managerDetails = web3.utils.fromAscii("Hello"); + const delegateDetails = web3.utils.fromAscii("I am delegate"); // Module key const delegateManagerKey = 1; @@ -66,26 +66,35 @@ contract("PercentageTransferManager", accounts => { const stoKey = 3; // Initial fee for ticker registry and security token registry - const initRegFee = web3.utils.toWei("250"); + const initRegFee = new BN(web3.utils.toWei("1000")); // PercentageTransferManager details - const holderPercentage = 70 * 10**16; // Maximum number of token holders - - let bytesSTO = web3.eth.abi.encodeFunctionCall({ - name: 'configure', - type: 'function', - inputs: [{ - type: 'uint256', - name: '_maxHolderPercentage' - },{ - type: 'bool', - name: '_allowPrimaryIssuance' - } - ] - }, [holderPercentage, false]); - - before(async() => { - // Accounts setup + const holderPercentage = 70 * 10 ** 16; // Maximum number of token holders + + let bytesSTO = web3.eth.abi.encodeFunctionCall( + { + name: "configure", + type: "function", + inputs: [ + { + type: "uint256", + name: "_maxHolderPercentage" + }, + { + type: "bool", + name: "_allowPrimaryIssuance" + } + ] + }, + [holderPercentage, false] + ); + + let currentTime; + const address_zero = "0x0000000000000000000000000000000000000000"; + const one_address = "0x0000000000000000000000000000000000000001"; + + before(async () => { + currentTime = new BN(await latestTime()); account_polymath = accounts[0]; account_issuer = accounts[1]; @@ -94,6 +103,7 @@ contract("PercentageTransferManager", accounts => { account_investor1 = accounts[7]; account_investor2 = accounts[8]; account_investor3 = accounts[9]; + account_investor4 = accounts[5] account_delegate = accounts[6]; let instances = await setUpPolymathNetwork(account_polymath, token_owner); @@ -109,18 +119,23 @@ contract("PercentageTransferManager", accounts => { I_STFactory, I_SecurityTokenRegistry, I_SecurityTokenRegistryProxy, - I_STRProxied + I_STRProxied, + I_STRGetter, + I_STGetter ] = instances; // STEP 2: Deploy the GeneralDelegateManagerFactory - [I_GeneralPermissionManagerFactory] = await deployGPMAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, 0); + [I_GeneralPermissionManagerFactory] = await deployGPMAndVerifyed(account_polymath, I_MRProxied, 0); // STEP 3(a): Deploy the PercentageTransferManager - [I_PercentageTransferManagerFactory] = await deployPercentageTMAndVerified(account_polymath, I_MRProxied, I_PolyToken.address, 0); + [I_PercentageTransferManagerFactory] = await deployPercentageTMAndVerified(account_polymath, I_MRProxied, 0); // STEP 4(b): Deploy the PercentageTransferManager - [P_PercentageTransferManagerFactory] = await deployPercentageTMAndVerified(account_polymath, I_MRProxied, I_PolyToken.address, web3.utils.toWei("500", "ether")); - + [P_PercentageTransferManagerFactory] = await deployPercentageTMAndVerified( + account_polymath, + I_MRProxied, + new BN(web3.utils.toWei("500", "ether")) + ); // Printing all the contract addresses console.log(` --------------------- Polymath Network Smart Contracts: --------------------- @@ -143,56 +158,55 @@ contract("PercentageTransferManager", accounts => { describe("Generate the SecurityToken", async () => { it("Should register the ticker before the generation of the security token", async () => { await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); - let tx = await I_STRProxied.registerTicker(token_owner, symbol, contact, { from: token_owner }); + let tx = await I_STRProxied.registerNewTicker(token_owner, symbol, { from: token_owner }); assert.equal(tx.logs[0].args._owner, token_owner); assert.equal(tx.logs[0].args._ticker, symbol.toUpperCase()); }); it("Should generate the new security token with the same symbol as registered above", async () => { await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); - let _blockNo = latestBlock(); - let tx = await I_STRProxied.generateSecurityToken(name, symbol, tokenDetails, false, { from: token_owner }); + + let tx = await I_STRProxied.generateNewSecurityToken(name, symbol, tokenDetails, false, token_owner, 0, { from: token_owner }); // 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 log = await promisifyLogWatch(I_SecurityToken.ModuleAdded({ from: _blockNo }), 1); + I_SecurityToken = await SecurityToken.at(tx.logs[1].args._securityTokenAddress); + stGetter = await STGetter.at(I_SecurityToken.address); + assert.equal(await stGetter.getTreasuryWallet.call(), token_owner, "Incorrect wallet set") + const log = (await I_SecurityToken.getPastEvents('ModuleAdded', {filter: {transactionHash: tx.transactionHash}}))[0]; // Verify that GeneralTransferManager module get added successfully or not assert.equal(log.args._types[0].toNumber(), 2); assert.equal(web3.utils.toAscii(log.args._name).replace(/\u0000/g, ""), "GeneralTransferManager"); }); - it("Should intialize the auto attached modules", async () => { - let moduleData = (await I_SecurityToken.getModulesByType(2))[0]; - I_GeneralTransferManager = GeneralTransferManager.at(moduleData); + it("Should initialize the auto attached modules", async () => { + let moduleData = (await stGetter.getModulesByType(2))[0]; + I_GeneralTransferManager = await GeneralTransferManager.at(moduleData); }); it("Should successfully attach the General permission manager factory with the security token", async () => { - const tx = await I_SecurityToken.addModule(I_GeneralPermissionManagerFactory.address, "0x", 0, 0, { from: token_owner }); + const tx = await I_SecurityToken.addModule(I_GeneralPermissionManagerFactory.address, "0x", new BN(0), new BN(0), false, { from: token_owner }); assert.equal(tx.logs[2].args._types[0].toNumber(), delegateManagerKey, "General Permission Manager doesn't get deployed"); assert.equal( web3.utils.toAscii(tx.logs[2].args._name).replace(/\u0000/g, ""), "GeneralPermissionManager", "GeneralPermissionManagerFactory module was not added" ); - I_GeneralPermissionManager = GeneralPermissionManager.at(tx.logs[2].args._module); + I_GeneralPermissionManager = await GeneralPermissionManager.at(tx.logs[2].args._module); }); - }); describe("Buy tokens using on-chain whitelist", async () => { it("Should Buy the tokens", async () => { // Add the Investor in to the whitelist - let tx = await I_GeneralTransferManager.modifyWhitelist( + let tx = await I_GeneralTransferManager.modifyKYCData( account_investor1, - latestTime(), - latestTime(), - latestTime() + duration.days(10), - true, + currentTime, + currentTime, + currentTime.add(new BN(duration.days(10))), { from: account_issuer, gas: 6000000 @@ -209,20 +223,19 @@ contract("PercentageTransferManager", accounts => { await increaseTime(5000); // Mint some tokens - await I_SecurityToken.mint(account_investor1, web3.utils.toWei("1", "ether"), { from: token_owner }); + await I_SecurityToken.issue(account_investor1, new BN(web3.utils.toWei("1", "ether")), "0x0", { from: token_owner }); - assert.equal((await I_SecurityToken.balanceOf(account_investor1)).toNumber(), web3.utils.toWei("1", "ether")); + assert.equal((await I_SecurityToken.balanceOf(account_investor1)).toString(), new BN(web3.utils.toWei("1", "ether")).toString()); }); it("Should Buy some more tokens", async () => { // Add the Investor in to the whitelist - let tx = await I_GeneralTransferManager.modifyWhitelist( + let tx = await I_GeneralTransferManager.modifyKYCData( account_investor2, - latestTime(), - latestTime(), - latestTime() + duration.days(10), - true, + currentTime, + currentTime, + currentTime.add(new BN(duration.days(10))), { from: account_issuer, gas: 6000000 @@ -236,15 +249,15 @@ contract("PercentageTransferManager", accounts => { ); // Mint some tokens - await I_SecurityToken.mint(account_investor2, web3.utils.toWei("1", "ether"), { from: token_owner }); + await I_SecurityToken.issue(account_investor2, new BN(web3.utils.toWei("1", "ether")), "0x0", { from: token_owner }); - assert.equal((await I_SecurityToken.balanceOf(account_investor2)).toNumber(), web3.utils.toWei("1", "ether")); + assert.equal((await I_SecurityToken.balanceOf(account_investor2)).toString(), new BN(web3.utils.toWei("1", "ether")).toString()); }); it("Should successfully attach the PercentageTransferManager factory with the security token - failed payment", async () => { - await I_PolyToken.getTokens(web3.utils.toWei("500", "ether"), token_owner); + await I_PolyToken.getTokens(new BN(web3.utils.toWei("2000", "ether")), token_owner); await catchRevert( - I_SecurityToken.addModule(P_PercentageTransferManagerFactory.address, bytesSTO, web3.utils.toWei("500", "ether"), 0, { + I_SecurityToken.addModule(P_PercentageTransferManagerFactory.address, bytesSTO, new BN(web3.utils.toWei("2000", "ether")), new BN(0), false, { from: token_owner }) ); @@ -252,12 +265,13 @@ contract("PercentageTransferManager", accounts => { it("Should successfully attach the PercentageTransferManager factory with the security token", async () => { let snapId = await takeSnapshot(); - await I_PolyToken.transfer(I_SecurityToken.address, web3.utils.toWei("500", "ether"), { from: token_owner }); + await I_PolyToken.transfer(I_SecurityToken.address, new BN(web3.utils.toWei("2000", "ether")), { from: token_owner }); const tx = await I_SecurityToken.addModule( P_PercentageTransferManagerFactory.address, bytesSTO, - web3.utils.toWei("500", "ether"), - 0, + new BN(web3.utils.toWei("2000", "ether")), + new BN(0), + false, { from: token_owner } ); assert.equal(tx.logs[3].args._types[0].toNumber(), transferManagerKey, "PercentageTransferManagerFactory doesn't get deployed"); @@ -266,28 +280,27 @@ contract("PercentageTransferManager", accounts => { "PercentageTransferManager", "PercentageTransferManagerFactory module was not added" ); - P_PercentageTransferManager = PercentageTransferManager.at(tx.logs[3].args._module); + P_PercentageTransferManager = await PercentageTransferManager.at(tx.logs[3].args._module); await revertToSnapshot(snapId); }); it("Should successfully attach the PercentageTransferManager with the security token", async () => { - const tx = await I_SecurityToken.addModule(I_PercentageTransferManagerFactory.address, bytesSTO, 0, 0, { from: token_owner }); + const tx = await I_SecurityToken.addModule(I_PercentageTransferManagerFactory.address, bytesSTO, new BN(0), new BN(0), false, { from: token_owner }); assert.equal(tx.logs[2].args._types[0].toNumber(), transferManagerKey, "PercentageTransferManager doesn't get deployed"); assert.equal( web3.utils.toAscii(tx.logs[2].args._name).replace(/\u0000/g, ""), "PercentageTransferManager", "PercentageTransferManager module was not added" ); - I_PercentageTransferManager = PercentageTransferManager.at(tx.logs[2].args._module); + I_PercentageTransferManager = await PercentageTransferManager.at(tx.logs[2].args._module); }); it("Add a new token holder", async () => { - let tx = await I_GeneralTransferManager.modifyWhitelist( + let tx = await I_GeneralTransferManager.modifyKYCData( account_investor3, - latestTime(), - latestTime(), - latestTime() + duration.days(10), - true, + currentTime, + currentTime, + currentTime.add(new BN(duration.days(10))), { from: account_issuer, gas: 6000000 @@ -302,9 +315,9 @@ contract("PercentageTransferManager", accounts => { // Add the Investor in to the whitelist // Mint some tokens - await I_SecurityToken.mint(account_investor3, web3.utils.toWei("1", "ether"), { from: token_owner }); + await I_SecurityToken.issue(account_investor3, new BN(web3.utils.toWei("1", "ether")), "0x0", { from: token_owner }); - assert.equal((await I_SecurityToken.balanceOf(account_investor3)).toNumber(), web3.utils.toWei("1", "ether")); + assert.equal((await I_SecurityToken.balanceOf(account_investor3)).toString(), new BN(web3.utils.toWei("1", "ether")).toString()); }); it("Should pause the tranfers at transferManager level", async () => { @@ -314,9 +327,9 @@ contract("PercentageTransferManager", accounts => { it("Should still be able to transfer between existing token holders up to limit", async () => { // Add the Investor in to the whitelist // Mint some tokens - await I_SecurityToken.transfer(account_investor1, web3.utils.toWei("1", "ether"), { from: account_investor2 }); + await I_SecurityToken.transfer(account_investor1, new BN(web3.utils.toWei("1", "ether")), { from: account_investor2 }); - assert.equal((await I_SecurityToken.balanceOf(account_investor1)).toNumber(), web3.utils.toWei("2", "ether")); + assert.equal((await I_SecurityToken.balanceOf(account_investor1)).toString(), new BN(web3.utils.toWei("2", "ether")).toString()); }); it("Should unpause the tranfers at transferManager level", async () => { @@ -324,48 +337,42 @@ contract("PercentageTransferManager", accounts => { }); it("Should not be able to transfer between existing token holders over limit", async () => { - await catchRevert(I_SecurityToken.transfer(account_investor3, web3.utils.toWei("2", "ether"), { from: account_investor1 })); + await catchRevert(I_SecurityToken.transfer(account_investor3, new BN(web3.utils.toWei("2", "ether")), { from: account_investor1 })); }); - it("Should not be able to mint token amount over limit", async() => { - await catchRevert(I_SecurityToken.mint(account_investor3, web3.utils.toWei('100', 'ether'), { from: token_owner })) + it("Should not be able to issue token amount over limit", async () => { + await catchRevert(I_SecurityToken.issue(account_investor3, new BN(web3.utils.toWei("100", "ether")), "0x0", { from: token_owner })); }); - it("Allow unlimited primary issuance and remint", async() => { + it("Allow unlimited primary issuance and remint", async () => { let snapId = await takeSnapshot(); await I_PercentageTransferManager.setAllowPrimaryIssuance(true, { from: token_owner }); - await I_SecurityToken.mint(account_investor3, web3.utils.toWei('100', 'ether'), { from: token_owner }); + await I_SecurityToken.issue(account_investor3, new BN(web3.utils.toWei("100", "ether")), "0x0", { from: token_owner }); // trying to call it again with the same value. should fail - await catchRevert( - I_PercentageTransferManager.setAllowPrimaryIssuance(true, { from: token_owner }) - ) + await catchRevert(I_PercentageTransferManager.setAllowPrimaryIssuance(true, { from: token_owner })); await revertToSnapshot(snapId); }); - it("Should not be able to transfer between existing token holders over limit", async() => { - await catchRevert( - I_SecurityToken.transfer(account_investor3, web3.utils.toWei('2', 'ether'), { from: account_investor1 }) - ) + it("Should not be able to transfer between existing token holders over limit", async () => { + await catchRevert(I_SecurityToken.transfer(account_investor3, new BN(web3.utils.toWei("2", "ether")), { from: account_investor1 })); }); it("Should not be able to modify holder percentage to 100 - Unauthorized msg.sender", async () => { - await catchRevert( - I_PercentageTransferManager.changeHolderPercentage(100 * 10 ** 16, { from: account_delegate }) - ) + await catchRevert(I_PercentageTransferManager.changeHolderPercentage(new BN(10).pow(new BN(18)), { from: account_delegate })); }); - it("Should successfully add the delegate", async() => { - let tx = await I_GeneralPermissionManager.addDelegate(account_delegate, delegateDetails, { from: token_owner}); + it("Should successfully add the delegate", async () => { + let tx = await I_GeneralPermissionManager.addDelegate(account_delegate, delegateDetails, { from: token_owner }); assert.equal(tx.logs[0].args._delegate, account_delegate); }); - it("Should provide the permission", async() => { + it("Should provide the permission", async () => { let tx = await I_GeneralPermissionManager.changePermission( account_delegate, I_PercentageTransferManager.address, - "ADMIN", + web3.utils.fromAscii("ADMIN"), true, - {from: token_owner} + { from: token_owner } ); assert.equal(tx.logs[0].args._delegate, account_delegate); }); @@ -373,32 +380,38 @@ contract("PercentageTransferManager", accounts => { it("Modify holder percentage to 100", async () => { // Add the Investor in to the whitelist // Mint some tokens - await I_PercentageTransferManager.changeHolderPercentage(100 * 10 ** 16, { from: account_delegate }); + await I_PercentageTransferManager.changeHolderPercentage(new BN(10).pow(new BN(18)), { from: account_delegate }); - assert.equal((await I_PercentageTransferManager.maxHolderPercentage()).toNumber(), 100 * 10 ** 16); + assert.equal((await I_PercentageTransferManager.maxHolderPercentage()).toString(), (new BN(10).pow(new BN(18))).toString()); }); it("Should be able to transfer between existing token holders up to limit", async () => { await I_PercentageTransferManager.modifyWhitelist(account_investor3, false, { from: token_owner }); - await I_SecurityToken.transfer(account_investor3, web3.utils.toWei("2", "ether"), { from: account_investor1 }); + await I_SecurityToken.transfer(account_investor3, new BN(web3.utils.toWei("2", "ether")), { from: account_investor1 }); }); - it("Should whitelist in batch --failed because of mismatch in array lengths", async() => { + it("Should whitelist in batch --failed because of mismatch in array lengths", async () => { + let addressArray = [account_investor3, account_investor4]; await catchRevert( - I_PercentageTransferManager.modifyWhitelistMulti([account_investor3, account_investor4], [false], { from: token_owner }) + I_PercentageTransferManager.modifyWhitelistMulti(addressArray, [false], { from: token_owner }) ); - }) + }); - it("Should whitelist in batch", async() => { + it("Should whitelist in batch", async () => { let snapId = await takeSnapshot(); - await I_PercentageTransferManager.modifyWhitelistMulti([account_investor3, account_investor4], [false, true], { from: token_owner }); + let addressArray = []; + addressArray.push(account_investor3); + addressArray.push(account_investor4); + await I_PercentageTransferManager.modifyWhitelistMulti(addressArray, [false, true], { + from: token_owner + }); await revertToSnapshot(snapId); - }) + }); it("Should be able to whitelist address and then transfer regardless of holders", async () => { - await I_PercentageTransferManager.changeHolderPercentage(30 * 10 ** 16, { from: token_owner }); + await I_PercentageTransferManager.changeHolderPercentage(new BN(30).mul(new BN(10).pow(new BN(16))), { from: token_owner }); await I_PercentageTransferManager.modifyWhitelist(account_investor1, true, { from: token_owner }); - await I_SecurityToken.transfer(account_investor1, web3.utils.toWei("2", "ether"), { from: account_investor3 }); + await I_SecurityToken.transfer(account_investor1, new BN(web3.utils.toWei("2", "ether")), { from: account_investor3 }); }); it("Should get the permission", async () => { @@ -409,10 +422,10 @@ contract("PercentageTransferManager", accounts => { describe("Percentage Transfer Manager Factory test cases", async () => { it("Should get the exact details of the factory", async () => { - assert.equal(await I_PercentageTransferManagerFactory.getSetupCost.call(), 0); + assert.equal(await I_PercentageTransferManagerFactory.setupCost.call(), 0); assert.equal((await I_PercentageTransferManagerFactory.getTypes.call())[0], 2); assert.equal( - web3.utils.toAscii(await I_PercentageTransferManagerFactory.getName.call()).replace(/\u0000/g, ""), + web3.utils.toAscii(await I_PercentageTransferManagerFactory.name.call()).replace(/\u0000/g, ""), "PercentageTransferManager", "Wrong Module added" ); @@ -422,12 +435,7 @@ contract("PercentageTransferManager", accounts => { "Wrong Module added" ); assert.equal(await I_PercentageTransferManagerFactory.title.call(), "Percentage Transfer Manager", "Wrong Module added"); - assert.equal( - await I_PercentageTransferManagerFactory.getInstructions.call(), - "Allows an issuer to restrict the total number of non-zero token holders", - "Wrong Module added" - ); - assert.equal(await I_PercentageTransferManagerFactory.version.call(), "1.0.0"); + assert.equal(await I_PercentageTransferManagerFactory.version.call(), "3.0.0"); }); it("Should get the tags of the factory", async () => { diff --git a/test/m_presale_sto.js b/test/m_presale_sto.js index a89932dc4..f6c4ddbba 100644 --- a/test/m_presale_sto.js +++ b/test/m_presale_sto.js @@ -3,18 +3,19 @@ import { duration, ensureException, promisifyLogWatch, latestBlock } from "./hel import { takeSnapshot, increaseTime, revertToSnapshot } from "./helpers/time"; import { encodeProxyCall, encodeModuleCall } from "./helpers/encodeCall"; import { catchRevert } from "./helpers/exceptions"; -import { setUpPolymathNetwork, deployPresaleSTOAndVerified } from "./helpers/createInstances" +import { setUpPolymathNetwork, deployPresaleSTOAndVerified } from "./helpers/createInstances"; const PreSaleSTOFactory = artifacts.require("./PreSaleSTOFactory.sol"); const PreSaleSTO = artifacts.require("./PreSaleSTO.sol"); const SecurityToken = artifacts.require("./SecurityToken.sol"); const GeneralTransferManager = artifacts.require("./GeneralTransferManager"); +const STGetter = artifacts.require("./STGetter.sol"); const Web3 = require("web3"); -const BigNumber = require("bignumber.js"); +let BN = Web3.utils.BN; const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); // Hardcoded development port -contract("PreSaleSTO", accounts => { +contract("PreSaleSTO", async (accounts) => { // Accounts Variable declaration let account_polymath; let account_investor1; @@ -49,6 +50,9 @@ contract("PreSaleSTO", accounts => { let I_PreSaleSTO; let I_PolyToken; let I_PolymathRegistry; + let I_STRGetter; + let I_STGetter; + let stGetter; // SecurityToken Details for funds raise Type ETH const name = "Team"; @@ -68,13 +72,16 @@ contract("PreSaleSTO", accounts => { const budget = 0; // Initial fee for ticker registry and security token registry - const initRegFee = web3.utils.toWei("250"); + const initRegFee = new BN(web3.utils.toWei("1000")); let endTime; const address_zero = "0x0000000000000000000000000000000000000000"; + const one_address = "0x0000000000000000000000000000000000000001"; const STOParameters = ["uint256"]; + let currentTime; + before(async () => { - // Accounts setup + currentTime = new BN(await latestTime()); account_polymath = accounts[0]; account_issuer = accounts[1]; account_investor1 = accounts[4]; @@ -96,13 +103,15 @@ contract("PreSaleSTO", accounts => { I_STFactory, I_SecurityTokenRegistry, I_SecurityTokenRegistryProxy, - I_STRProxied + I_STRProxied, + I_STRGetter, + I_STGetter ] = instances; // STEP 4: Deploy the PreSaleSTOFactory - [I_PreSaleSTOFactory] = await deployPresaleSTOAndVerified(account_polymath, I_MRProxied, I_PolyToken.address, 0); + [I_PreSaleSTOFactory] = await deployPresaleSTOAndVerified(account_polymath, I_MRProxied, 0); // STEP 5: Deploy the paid PresaleSTOFactory - [P_PreSaleSTOFactory] = await deployPresaleSTOAndVerified(account_polymath, I_MRProxied, I_PolyToken.address, 0); + [P_PreSaleSTOFactory] = await deployPresaleSTOAndVerified(account_polymath, I_MRProxied, 0); // Printing all the contract addresses console.log(` @@ -125,45 +134,48 @@ contract("PreSaleSTO", accounts => { describe("Generate the SecurityToken", async () => { it("Should register the ticker before the generation of the security token", async () => { await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); - let tx = await I_STRProxied.registerTicker(token_owner, symbol, name, { from: token_owner }); + let tx = await I_STRProxied.registerNewTicker(token_owner, symbol, { from: token_owner }); assert.equal(tx.logs[0].args._owner, token_owner); assert.equal(tx.logs[0].args._ticker, symbol); }); it("Should generate the new security token with the same symbol as registered above", async () => { await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); - let _blockNo = latestBlock(); - let tx = await I_STRProxied.generateSecurityToken(name, symbol, tokenDetails, false, { from: token_owner }); + + let tx = await I_STRProxied.generateNewSecurityToken(name, symbol, tokenDetails, false, token_owner, 0, { from: token_owner }); // Verify the successful generation of the security token assert.equal(tx.logs[1].args._ticker, symbol, "SecurityToken doesn't get deployed"); - I_SecurityToken = SecurityToken.at(tx.logs[1].args._securityTokenAddress); - - const log = await promisifyLogWatch(I_SecurityToken.ModuleAdded({ from: _blockNo }), 1); + I_SecurityToken = await SecurityToken.at(tx.logs[1].args._securityTokenAddress); + stGetter = await STGetter.at(I_SecurityToken.address); + assert.equal(await stGetter.getTreasuryWallet.call(), token_owner, "Incorrect wallet set") + const log = (await I_SecurityToken.getPastEvents('ModuleAdded', {filter: {transactionHash: tx.transactionHash}}))[0]; // Verify that GeneralTransferManager module get added successfully or not assert.equal(log.args._types[0].toNumber(), transferManagerKey); assert.equal(web3.utils.toAscii(log.args._name).replace(/\u0000/g, ""), "GeneralTransferManager"); }); - it("Should intialize the auto attached modules", async () => { - let moduleData = (await I_SecurityToken.getModulesByType(transferManagerKey))[0]; - I_GeneralTransferManager = GeneralTransferManager.at(moduleData); + it("Should initialize the auto attached modules", async () => { + let moduleData = (await stGetter.getModulesByType(transferManagerKey))[0]; + I_GeneralTransferManager = await GeneralTransferManager.at(moduleData); }); it("Should fail to launch the STO due to endTime is 0", async () => { let bytesSTO = encodeModuleCall(STOParameters, [0]); - await catchRevert(I_SecurityToken.addModule(I_PreSaleSTOFactory.address, bytesSTO, 0, 0, { from: token_owner })); + await catchRevert(I_SecurityToken.addModule(I_PreSaleSTOFactory.address, bytesSTO, new BN(0), new BN(0), false, { from: token_owner })); }); - it("Should successfully attach the Paid STO factory with the security token", async () => { + it("Should successfully attach the Paid STO factory (archived) with the security token", async () => { let snap_id = await takeSnapshot(); - endTime = latestTime() + duration.days(30); // Start time will be 5000 seconds more than the latest time + endTime = await latestTime() + duration.days(30); // Start time will be 5000 seconds more than the latest time let bytesSTO = encodeModuleCall(STOParameters, [endTime]); - await I_PolyToken.getTokens(web3.utils.toWei("500"), I_SecurityToken.address); - const tx = await I_SecurityToken.addModule(P_PreSaleSTOFactory.address, bytesSTO, web3.utils.toWei("500"), 0, { from: token_owner }); + await I_PolyToken.getTokens(new BN(web3.utils.toWei("500")), I_SecurityToken.address); + const tx = await I_SecurityToken.addModule(P_PreSaleSTOFactory.address, bytesSTO, new BN(web3.utils.toWei("500")), new BN(0), false, { + from: token_owner + }); assert.equal(tx.logs[2].args._types[0], stoKey, "PreSaleSTO doesn't get deployed"); assert.equal( @@ -171,23 +183,21 @@ contract("PreSaleSTO", accounts => { "PreSaleSTO", "PreSaleSTOFactory module was not added" ); - I_PreSaleSTO = PreSaleSTO.at(tx.logs[2].args._module); + I_PreSaleSTO = await PreSaleSTO.at(tx.logs[2].args._module); await revertToSnapshot(snap_id); }); it("Should successfully attach the STO factory with the security token -- fail because signature is different", async () => { - endTime = latestTime() + duration.days(30); // Start time will be 5000 seconds more than the latest time + endTime = await latestTime() + duration.days(30); // Start time will be 5000 seconds more than the latest time let bytesSTO = encodeModuleCall(["string"], ["hey"]); - await catchRevert( - I_SecurityToken.addModule(I_PreSaleSTOFactory.address, bytesSTO, 0, 0, { from: token_owner }) - ); + await catchRevert(I_SecurityToken.addModule(I_PreSaleSTOFactory.address, bytesSTO, new BN(0), new BN(0), false, { from: token_owner })); }); - it("Should successfully attach the STO factory with the security token", async () => { - endTime = latestTime() + duration.days(30); // Start time will be 5000 seconds more than the latest time + it("Should successfully attach the STO factory (archived) with the security token", async () => { + endTime = await latestTime() + duration.days(30); // Start time will be 5000 seconds more than the latest time let bytesSTO = encodeModuleCall(STOParameters, [endTime]); - const tx = await I_SecurityToken.addModule(I_PreSaleSTOFactory.address, bytesSTO, 0, 0, { from: token_owner }); + const tx = await I_SecurityToken.addModule(I_PreSaleSTOFactory.address, bytesSTO, new BN(0), new BN(0), true, { from: token_owner }); assert.equal(tx.logs[2].args._types[0], stoKey, "PreSaleSTO doesn't get deployed"); assert.equal( @@ -195,14 +205,16 @@ contract("PreSaleSTO", accounts => { "PreSaleSTO", "PreSaleSTOFactory module was not added" ); - I_PreSaleSTO = PreSaleSTO.at(tx.logs[2].args._module); + I_PreSaleSTO = await PreSaleSTO.at(tx.logs[2].args._module); + let info = await stGetter.getModule.call(I_PreSaleSTO.address); + assert.equal(info[3], true); }); it("Should successfully attach the STO factory with the security token", async () => { - endTime = latestTime() + duration.days(30); // Start time will be 5000 seconds more than the latest time + endTime = await latestTime() + duration.days(30); // Start time will be 5000 seconds more than the latest time let bytesSTO = encodeModuleCall(STOParameters, [endTime]); - const tx = await I_SecurityToken.addModule(I_PreSaleSTOFactory.address, bytesSTO, 0, 0, { from: token_owner }); + const tx = await I_SecurityToken.addModule(I_PreSaleSTOFactory.address, bytesSTO, new BN(0), new BN(0), true, { from: token_owner }); assert.equal(tx.logs[2].args._types[0], stoKey, "PreSaleSTO doesn't get deployed"); assert.equal( @@ -210,7 +222,10 @@ contract("PreSaleSTO", accounts => { "PreSaleSTO", "PreSaleSTOFactory module was not added" ); - I_PreSaleSTO = PreSaleSTO.at(tx.logs[2].args._module); + I_PreSaleSTO = await PreSaleSTO.at(tx.logs[2].args._module); + let info = await stGetter.getModule.call(I_PreSaleSTO.address); + assert.equal(info[3], true); + }); }); @@ -221,22 +236,19 @@ contract("PreSaleSTO", accounts => { it("Should get the permissions", async () => { let perm = await I_PreSaleSTO.getPermissions.call(); - assert.equal(web3.utils.toAscii(perm[0]).replace(/\u0000/g, ""), "PRE_SALE_ADMIN"); + assert.equal(web3.utils.toAscii(perm[0]).replace(/\u0000/g, ""), "ADMIN"); }); }); describe("Buy tokens", async () => { - it("Should allocate the tokens -- failed due to investor not on whitelist", async () => { - await catchRevert(I_PreSaleSTO.allocateTokens(account_investor1, 1000, web3.utils.toWei("1", "ether"), 0)); - }); it("Should Buy the tokens", async () => { - fromTime = latestTime(); + fromTime = await latestTime(); toTime = fromTime + duration.days(100); expiryTime = toTime + duration.days(100); // Add the Investor in to the whitelist - let tx = await I_GeneralTransferManager.modifyWhitelist(account_investor1, fromTime, toTime, expiryTime, true, { + let tx = await I_GeneralTransferManager.modifyKYCData(account_investor1, fromTime, toTime, expiryTime, { from: account_issuer, gas: 6000000 }); @@ -245,39 +257,49 @@ contract("PreSaleSTO", accounts => { // Jump time await increaseTime(duration.days(1)); - await I_PreSaleSTO.allocateTokens(account_investor1, web3.utils.toWei("1", "ether"), web3.utils.toWei("1", "ether"), 0, { + // Fail as module is archived + await catchRevert( + I_PreSaleSTO.allocateTokens(account_investor1, new BN(web3.utils.toWei("1", "ether")), new BN(web3.utils.toWei("1", "ether")), new BN(0), { + from: account_issuer + }) + ); + await I_SecurityToken.unarchiveModule(I_PreSaleSTO.address, {from: token_owner}); + let info = await stGetter.getModule.call(I_PreSaleSTO.address); + assert.equal(info[3], false); + + //Fail as investor is not on whitelist + await catchRevert(I_PreSaleSTO.allocateTokens(account_investor2, 1000, new BN(web3.utils.toWei("1", "ether")), 0)); + await I_PreSaleSTO.allocateTokens(account_investor1, new BN(web3.utils.toWei("1", "ether")), new BN(web3.utils.toWei("1", "ether")), new BN(0), { from: account_issuer }); - - assert.equal((await I_PreSaleSTO.getRaised.call(0)).dividedBy(new BigNumber(10).pow(18)).toNumber(), 1); + assert.equal((await I_PreSaleSTO.getRaised.call(0)).div(new BN(10).pow(new BN(18))).toNumber(), 1); console.log(await I_PreSaleSTO.getNumberInvestors.call()); assert.equal((await I_PreSaleSTO.getNumberInvestors.call()).toNumber(), 1); - // assert.isTrue(false); }); - it("Should allocate the tokens --failed because of amount is 0", async() => { + it("Should allocate the tokens --failed because of amount is 0", async () => { await catchRevert( - I_PreSaleSTO.allocateTokens(account_investor1, 0, web3.utils.toWei("1", "ether"), 0, { + I_PreSaleSTO.allocateTokens(account_investor1, new BN(0), new BN(web3.utils.toWei("1", "ether")), new BN(0), { from: account_issuer }) ); - }) + }); it("Should allocate the tokens -- failed due to msg.sender is not pre sale admin", async () => { await catchRevert( - I_PreSaleSTO.allocateTokens(account_investor1, web3.utils.toWei("1", "ether"), web3.utils.toWei("1", "ether"), 0, { + I_PreSaleSTO.allocateTokens(account_investor1, new BN(web3.utils.toWei("1", "ether")), new BN(web3.utils.toWei("1", "ether")), new BN(0), { from: account_fundsReceiver }) ); }); it("Should allocate tokens to multiple investors", async () => { - fromTime = latestTime(); + fromTime = await latestTime(); toTime = fromTime + duration.days(100); expiryTime = toTime + duration.days(100); // Add the Investor in to the whitelist - let tx1 = await I_GeneralTransferManager.modifyWhitelist(account_investor2, fromTime, toTime, expiryTime, true, { + let tx1 = await I_GeneralTransferManager.modifyKYCData(account_investor2, fromTime, toTime, expiryTime, { from: account_issuer, gas: 6000000 }); @@ -285,7 +307,7 @@ contract("PreSaleSTO", accounts => { assert.equal(tx1.logs[0].args._investor, account_investor2, "Failed in adding the investor in whitelist"); // Add the Investor in to the whitelist - let tx2 = await I_GeneralTransferManager.modifyWhitelist(account_investor3, fromTime, toTime, expiryTime, true, { + let tx2 = await I_GeneralTransferManager.modifyKYCData(account_investor3, fromTime, toTime, expiryTime, { from: account_issuer, gas: 6000000 }); @@ -294,82 +316,84 @@ contract("PreSaleSTO", accounts => { await I_PreSaleSTO.allocateTokensMulti( [account_investor2, account_investor3], - [web3.utils.toWei("1", "ether"), web3.utils.toWei("1", "ether")], + [new BN(web3.utils.toWei("1", "ether")), new BN(web3.utils.toWei("1", "ether"))], [0, 0], - [web3.utils.toWei("1000", "ether"), web3.utils.toWei("1000", "ether")], + [new BN(web3.utils.toWei("1000", "ether")), new BN(web3.utils.toWei("1000", "ether"))], { from: account_issuer } ); - assert.equal((await I_PreSaleSTO.getRaised.call(1)).dividedBy(new BigNumber(10).pow(18)).toNumber(), 2000); + assert.equal((await I_PreSaleSTO.getRaised.call(1)).div(new BN(10).pow(new BN(18))).toNumber(), 2000); assert.equal((await I_PreSaleSTO.getNumberInvestors.call()).toNumber(), 3); }); - it("Should successfully mint multiple tokens -- failed because array mismatch", async() => { + it("Should successfully mint multiple tokens -- failed because array mismatch", async () => { await catchRevert( I_PreSaleSTO.allocateTokensMulti( [account_investor2], - [web3.utils.toWei("1", "ether"), web3.utils.toWei("1", "ether")], + [new BN(web3.utils.toWei("1", "ether")), new BN(web3.utils.toWei("1", "ether"))], [0, 0], - [web3.utils.toWei("1000", "ether"), web3.utils.toWei("1000", "ether")], + [new BN(web3.utils.toWei("1000", "ether")), new BN(web3.utils.toWei("1000", "ether"))], { from: account_issuer } ) ); - }) + }); - it("Should successfully mint multiple tokens -- failed because array mismatch", async() => { + it("Should successfully mint multiple tokens -- failed because array mismatch", async () => { await catchRevert( I_PreSaleSTO.allocateTokensMulti( [account_investor2, account_investor3], - [web3.utils.toWei("1", "ether"), web3.utils.toWei("1", "ether")], + [new BN(web3.utils.toWei("1", "ether")), new BN(web3.utils.toWei("1", "ether"))], [0], - [web3.utils.toWei("1000", "ether"), web3.utils.toWei("1000", "ether")], + [new BN(web3.utils.toWei("1000", "ether")), new BN(web3.utils.toWei("1000", "ether"))], { from: account_issuer } ) ); }); - it("Should successfully mint multiple tokens -- failed because array mismatch", async() => { + it("Should successfully mint multiple tokens -- failed because array mismatch", async () => { await catchRevert( I_PreSaleSTO.allocateTokensMulti( [account_investor2, account_investor3], - [web3.utils.toWei("1", "ether"), web3.utils.toWei("1", "ether")], - [0,0], - [web3.utils.toWei("1000", "ether")], + [new BN(web3.utils.toWei("1", "ether")), new BN(web3.utils.toWei("1", "ether"))], + [0, 0], + [new BN(web3.utils.toWei("1000", "ether"))], { from: account_issuer } ) ); }); - it("Should successfully mint multiple tokens -- failed because array mismatch", async() => { + it("Should successfully mint multiple tokens -- failed because array mismatch", async () => { await catchRevert( I_PreSaleSTO.allocateTokensMulti( [account_investor2, account_investor3], - [web3.utils.toWei("1", "ether"), web3.utils.toWei("1", "ether")], + [new BN(web3.utils.toWei("1", "ether")), new BN(web3.utils.toWei("1", "ether"))], [0], - [web3.utils.toWei("1000", "ether"), web3.utils.toWei("1000", "ether")], + [new BN(web3.utils.toWei("1000", "ether")), new BN(web3.utils.toWei("1000", "ether"))], { from: account_issuer } ) ); }); - it("Should buy some more tokens to previous investor", async() => { - await I_PreSaleSTO.allocateTokens(account_investor1, web3.utils.toWei("1000", "ether"), web3.utils.toWei("1", "ether"), 0, { from: account_issuer }); + it("Should buy some more tokens to previous investor", async () => { + await I_PreSaleSTO.allocateTokens(account_investor1, new BN(web3.utils.toWei("1000", "ether")), new BN(web3.utils.toWei("1", "ether")), new BN(0), { + from: account_issuer + }); // No change in the investor count assert.equal((await I_PreSaleSTO.getNumberInvestors.call()).toNumber(), 3); - }) + }); it("Should failed at the time of buying the tokens -- Because STO has ended", async () => { await increaseTime(duration.days(100)); // increased beyond the end time of the STO await catchRevert( - I_PreSaleSTO.allocateTokens(account_investor1, 1000, web3.utils.toWei("1", "ether"), 0, { from: account_issuer }) + I_PreSaleSTO.allocateTokens(account_investor1, 1000, new BN(web3.utils.toWei("1", "ether")), new BN(0), { from: account_issuer }) ); }); }); describe("Reclaim poly sent to STO by mistake", async () => { it("Should fail to reclaim POLY because token contract address is 0 address", async () => { - let value = web3.utils.toWei("100", "ether"); + let value = new BN(web3.utils.toWei("100", "ether")); await I_PolyToken.getTokens(value, account_investor1); await I_PolyToken.transfer(I_PreSaleSTO.address, value, { from: account_investor1 }); @@ -377,7 +401,7 @@ contract("PreSaleSTO", accounts => { }); it("Should successfully reclaim POLY", async () => { - let value = web3.utils.toWei("100", "ether"); + let value = new BN(web3.utils.toWei("100", "ether")); await I_PolyToken.getTokens(value, account_investor1); let initInvestorBalance = await I_PolyToken.balanceOf(account_investor1); let initOwnerBalance = await I_PolyToken.balanceOf(token_owner); @@ -386,37 +410,37 @@ contract("PreSaleSTO", accounts => { await I_PolyToken.transfer(I_PreSaleSTO.address, value, { from: account_investor1 }); await I_PreSaleSTO.reclaimERC20(I_PolyToken.address, { from: token_owner }); assert.equal( - (await I_PolyToken.balanceOf(account_investor1)).toNumber(), - initInvestorBalance.sub(value).toNumber(), + (await I_PolyToken.balanceOf(account_investor1)).toString(), + initInvestorBalance.sub(value).toString(), "tokens are not transferred out from investor account" ); assert.equal( - (await I_PolyToken.balanceOf(token_owner)).toNumber(), + (await I_PolyToken.balanceOf(token_owner)).toString(), initOwnerBalance .add(value) .add(initContractBalance) - .toNumber(), + .toString(), "tokens are not added to the owner account" ); assert.equal( (await I_PolyToken.balanceOf(I_PreSaleSTO.address)).toNumber(), - 0, + new BN(0).toNumber(), "tokens are not trandfered out from STO contract" ); }); - it("Should get the the tokens sold", async() => { + it("Should get the the tokens sold", async () => { let _tokensSold = await I_PreSaleSTO.getTokensSold.call(); console.log(_tokensSold); - }) + }); }); describe("Test cases for the PresaleSTOFactory", async () => { it("should get the exact details of the factory", async () => { - assert.equal(await I_PreSaleSTOFactory.getSetupCost.call(), 0); + assert.equal(await I_PreSaleSTOFactory.setupCost.call(), 0); assert.equal((await I_PreSaleSTOFactory.getTypes.call())[0], 3); assert.equal( - web3.utils.toAscii(await I_PreSaleSTOFactory.getName.call()).replace(/\u0000/g, ""), + web3.utils.toAscii(await I_PreSaleSTOFactory.name.call()).replace(/\u0000/g, ""), "PreSaleSTO", "Wrong Module added" ); @@ -426,13 +450,8 @@ contract("PreSaleSTO", accounts => { "Wrong Module added" ); assert.equal(await I_PreSaleSTOFactory.title.call(), "PreSale STO", "Wrong Module added"); - assert.equal( - await I_PreSaleSTOFactory.getInstructions.call(), - "Configure and track pre-sale token allocations", - "Wrong Module added" - ); let tags = await I_PreSaleSTOFactory.getTags.call(); - assert.equal(web3.utils.toAscii(tags[0]).replace(/\u0000/g, ""), "Presale"); + assert.equal(web3.utils.toAscii(tags[0]).replace(/\u0000/g, ""), "PreSale"); }); }); }); diff --git a/test/n_security_token_registry.js b/test/n_security_token_registry.js index 4bea414db..3751775d6 100644 --- a/test/n_security_token_registry.js +++ b/test/n_security_token_registry.js @@ -11,13 +11,19 @@ const SecurityTokenRegistryProxy = artifacts.require("./SecurityTokenRegistryPro const SecurityTokenRegistry = artifacts.require("./SecurityTokenRegistry.sol"); const SecurityTokenRegistryMock = artifacts.require("./SecurityTokenRegistryMock.sol"); const STFactory = artifacts.require("./STFactory.sol"); - +const STFactoryV2 = artifacts.require("./STFactory.sol"); // change here +const STRGetter = artifacts.require('./STRGetter.sol'); +const STGetter = artifacts.require("./STGetter.sol"); +const DataStoreLogic = artifacts.require('./DataStore.sol'); +const DataStoreFactory = artifacts.require('./DataStoreFactory.sol'); +const TokenLib = artifacts.require('./TokenLib.sol'); +const SecurityTokenMock = artifacts.require('./SecurityTokenMock.sol'); const Web3 = require("web3"); -const BigNumber = require("bignumber.js"); +let BN = Web3.utils.BN; const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); // Hardcoded development port -contract("SecurityTokenRegistry", accounts => { +contract("SecurityTokenRegistry", async (accounts) => { // Accounts Variable declaration let account_polymath; let account_investor1; @@ -30,9 +36,6 @@ contract("SecurityTokenRegistry", accounts => { let dummy_token; let balanceOfReceiver; - // investor Details - let fromTime = latestTime(); - let toTime = latestTime() + duration.days(100); let ID_snap; const message = "Transaction Should Fail!!"; @@ -59,18 +62,27 @@ contract("SecurityTokenRegistry", accounts => { let I_SecurityTokenRegistryProxy; let I_STRProxied; let I_MRProxied; + let I_STRGetter; + let I_Getter; + let I_STGetter; + let stGetter; + let I_USDOracle; + let I_POLYOracle; + let I_StablePOLYOracle; + let I_TokenLib; // SecurityToken Details (Launched ST on the behalf of the issuer) const name = "Demo Token"; const symbol = "DET"; const tokenDetails = "This is equity type of issuance"; const decimals = 18; - + let treasury_wallet; //Security Token Detials (Version 2) const name2 = "Demo2 Token"; const symbol2 = "DET2"; const tokenDetails2 = "This is equity type of issuance"; const address_zero = "0x0000000000000000000000000000000000000000"; + const one_address = "0x0000000000000000000000000000000000000001"; // Module key const permissionManagerKey = 1; @@ -79,18 +91,26 @@ contract("SecurityTokenRegistry", accounts => { const budget = 0; // Initial fee for ticker registry and security token registry - const initRegFee = web3.utils.toWei("250"); - const newRegFee = web3.utils.toWei("300"); + const initRegFee = new BN(web3.utils.toWei("250")); + const initRegFeePOLY = new BN(web3.utils.toWei("1000")); - const STRProxyParameters = ["address", "address", "uint256", "uint256", "address", "address"]; + const STRProxyParameters = ["address", "uint256", "uint256", "address", "address"]; const STOParameters = ["uint256", "uint256", "uint256", "string"]; // Capped STO details - const cap = web3.utils.toWei("10000"); + const cap = new BN(web3.utils.toWei("10000")); const someString = "Hello string"; + let currentTime; + + function _pack(_major, _minor, _patch) { + let packedVersion =(parseInt(_major) << 16) | (parseInt(_minor) << 8) | parseInt(_patch); + return packedVersion; + } + before(async () => { - // Accounts setup + currentTime = new BN(await latestTime()); + treasury_wallet = accounts[2]; account_polymath = accounts[0]; account_issuer = accounts[1]; account_investor1 = accounts[9]; @@ -114,15 +134,21 @@ contract("SecurityTokenRegistry", accounts => { I_STFactory, I_SecurityTokenRegistry, I_SecurityTokenRegistryProxy, - I_STRProxied + I_STRProxied, + I_STRGetter, + I_STGetter, + I_USDOracle, + I_POLYOracle, + I_StablePOLYOracle ] = instances; // STEP 8: Deploy the CappedSTOFactory - [I_DummySTOFactory] = await deployDummySTOAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, 0); + [I_DummySTOFactory] = await deployDummySTOAndVerifyed(account_polymath, I_MRProxied, 0); // Step 9: Deploy the SecurityTokenRegistry - + console.log(I_SecurityTokenRegistry.address); I_SecurityTokenRegistry = await SecurityTokenRegistry.new({ from: account_polymath }); + console.log(I_SecurityTokenRegistry.address); assert.notEqual( I_SecurityTokenRegistry.address.valueOf(), @@ -132,6 +158,8 @@ contract("SecurityTokenRegistry", accounts => { // Step 9 (a): Deploy the proxy I_SecurityTokenRegistryProxy = await SecurityTokenRegistryProxy.new({ from: account_polymath }); + // Step 10 : Deploy the getter contract + I_STRGetter = await STRGetter.new({ from: account_polymath }); //Step 11: update the registries addresses from the PolymathRegistry contract await I_PolymathRegistry.changeAddress("SecurityTokenRegistry", I_SecurityTokenRegistryProxy.address, { from: account_polymath }); await I_MRProxied.updateFromRegistry({ from: account_polymath }); @@ -157,13 +185,12 @@ contract("SecurityTokenRegistry", accounts => { it("Should successfully update the implementation address -- fail because polymathRegistry address is 0x", async () => { let bytesProxy = encodeProxyCall(STRProxyParameters, [ address_zero, - I_STFactory.address, initRegFee, initRegFee, - I_PolyToken.address, - account_polymath + account_polymath, + I_STRGetter.address ]); - catchRevert( + await catchRevert( I_SecurityTokenRegistryProxy.upgradeToAndCall("1.0.0", I_SecurityTokenRegistry.address, bytesProxy, { from: account_polymath }), @@ -171,84 +198,15 @@ contract("SecurityTokenRegistry", accounts => { ); }); - it("Should successfully update the implementation address -- fail because STFactory address is 0x", async () => { - let bytesProxy = encodeProxyCall(STRProxyParameters, [ - I_PolymathRegistry.address, - address_zero, - initRegFee, - initRegFee, - I_PolyToken.address, - account_polymath - ]); - catchRevert( - I_SecurityTokenRegistryProxy.upgradeToAndCall("1.0.0", I_SecurityTokenRegistry.address, bytesProxy, { - from: account_polymath - }), - "tx-> revert because STFactory address is 0x" - ); - }); - - it("Should successfully update the implementation address -- fail because STLaunch fee is 0", async () => { - let bytesProxy = encodeProxyCall(STRProxyParameters, [ - I_PolymathRegistry.address, - I_STFactory.address, - 0, - initRegFee, - I_PolyToken.address, - account_polymath - ]); - catchRevert( - I_SecurityTokenRegistryProxy.upgradeToAndCall("1.0.0", I_SecurityTokenRegistry.address, bytesProxy, { - from: account_polymath - }), - "tx-> revert because STLaunch fee is 0" - ); - }); - - it("Should successfully update the implementation address -- fail because tickerRegFee fee is 0", async () => { - let bytesProxy = encodeProxyCall(STRProxyParameters, [ - I_PolymathRegistry.address, - I_STFactory.address, - initRegFee, - 0, - I_PolyToken.address, - account_polymath - ]); - catchRevert( - I_SecurityTokenRegistryProxy.upgradeToAndCall("1.0.0", I_SecurityTokenRegistry.address, bytesProxy, { - from: account_polymath - }), - "tx-> revert because tickerRegFee is 0" - ); - }); - - it("Should successfully update the implementation address -- fail because PolyToken address is 0x", async () => { - let bytesProxy = encodeProxyCall(STRProxyParameters, [ - I_PolymathRegistry.address, - I_STFactory.address, - initRegFee, - initRegFee, - address_zero, - account_polymath - ]); - catchRevert( - I_SecurityTokenRegistryProxy.upgradeToAndCall("1.0.0", I_SecurityTokenRegistry.address, bytesProxy, { - from: account_polymath - }), - "tx-> revert because PolyToken address is 0x" - ); - }); - it("Should successfully update the implementation address -- fail because owner address is 0x", async () => { let bytesProxy = encodeProxyCall(STRProxyParameters, [ I_PolymathRegistry.address, - I_STFactory.address, initRegFee, initRegFee, - I_PolyToken.address, - address_zero + address_zero, + I_STRGetter.address ]); - catchRevert( + await catchRevert( I_SecurityTokenRegistryProxy.upgradeToAndCall("1.0.0", I_SecurityTokenRegistry.address, bytesProxy, { from: account_polymath }), @@ -257,15 +215,8 @@ contract("SecurityTokenRegistry", accounts => { }); it("Should successfully update the implementation address -- fail because all params get 0", async () => { - let bytesProxy = encodeProxyCall(STRProxyParameters, [ - address_zero, - address_zero, - 0, - 0, - address_zero, - address_zero - ]); - catchRevert( + let bytesProxy = encodeProxyCall(STRProxyParameters, [address_zero, new BN(0), new BN(0), address_zero, address_zero]); + await catchRevert( I_SecurityTokenRegistryProxy.upgradeToAndCall("1.0.0", I_SecurityTokenRegistry.address, bytesProxy, { from: account_polymath }), @@ -276,16 +227,28 @@ contract("SecurityTokenRegistry", accounts => { it("Should successfully update the implementation address", async () => { let bytesProxy = encodeProxyCall(STRProxyParameters, [ I_PolymathRegistry.address, - I_STFactory.address, initRegFee, initRegFee, - I_PolyToken.address, - account_polymath + account_polymath, + I_STRGetter.address ]); await I_SecurityTokenRegistryProxy.upgradeToAndCall("1.0.0", I_SecurityTokenRegistry.address, bytesProxy, { from: account_polymath }); - I_STRProxied = SecurityTokenRegistry.at(I_SecurityTokenRegistryProxy.address); + I_Getter = await STRGetter.at(I_SecurityTokenRegistryProxy.address); + I_STRProxied = await SecurityTokenRegistry.at(I_SecurityTokenRegistryProxy.address); + await I_STRProxied.setProtocolFactory(I_STFactory.address, 3, 0, 0); + await I_STRProxied.setLatestVersion(3, 0, 0); + + let latestSTF = await I_Getter.getSTFactoryAddress(); + console.log(latestSTF); + assert.equal(await I_Getter.getSTFactoryAddressOfVersion(196608), latestSTF); //196608 is 3.0.0 in packed format + let info = await I_Getter.getLatestProtocolVersion(); + for (let i = 0; i < info.length; i++) { + console.log(info[i].toNumber()); + } + console.log(await I_Getter.getLatestProtocolVersion()); + }); }); @@ -301,124 +264,185 @@ contract("SecurityTokenRegistry", accounts => { assert.equal(polytoken, I_PolyToken.address, "Should be the polytoken address"); let stlaunchFee = await I_STRProxied.getUintValue.call(web3.utils.soliditySha3("stLaunchFee")); - assert.equal(stlaunchFee.toNumber(), initRegFee, "Should be provided reg fee"); + assert.equal(stlaunchFee.toString(), initRegFee.toString(), "Should be provided reg fee"); let tickerRegFee = await I_STRProxied.getUintValue.call(web3.utils.soliditySha3("tickerRegFee")); - assert.equal(tickerRegFee.toNumber(), tickerRegFee, "Should be provided reg fee"); + assert.equal(tickerRegFee.toString(), tickerRegFee.toString(), "Should be provided reg fee"); let polymathRegistry = await I_STRProxied.getAddressValue.call(web3.utils.soliditySha3("polymathRegistry")); assert.equal(polymathRegistry, I_PolymathRegistry.address, "Should be the address of the polymath registry"); + let getterContract = await I_STRProxied.getAddressValue.call(web3.utils.soliditySha3("STRGetter")); + assert.equal(getterContract, I_STRGetter.address, "Should be the address of the getter contract"); + let owner = await I_STRProxied.getAddressValue.call(web3.utils.soliditySha3("owner")); assert.equal(owner, account_polymath, "Should be the address of the registry owner"); }); - it("Can't call the intialize function again", async () => { - catchRevert( + it("Can't call the initialize function again", async () => { + await catchRevert( I_STRProxied.initialize( I_PolymathRegistry.address, - I_STFactory.address, initRegFee, initRegFee, - I_PolyToken.address, - account_polymath + account_polymath, + I_STRGetter.address ), - "tx revert -> Can't call the intialize function again" + "tx revert -> Can't call the initialize function again" ); }); it("Should fail to register ticker if tickerRegFee not approved", async () => { - catchRevert( - I_STRProxied.registerTicker(account_temp, symbol, name, { from: account_temp }), + await catchRevert( + I_STRProxied.registerNewTicker(account_temp, symbol, { from: account_temp }), "tx revert -> POLY allowance not provided for registration fee" ); }); it("Should fail to register ticker if owner is 0x", async () => { - await I_PolyToken.getTokens(initRegFee, account_temp); - await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: account_temp }); + await I_PolyToken.getTokens(initRegFeePOLY, account_temp); + await I_PolyToken.approve(I_STRProxied.address, initRegFeePOLY, { from: account_temp }); - catchRevert( - I_STRProxied.registerTicker(address_zero, symbol, name, { from: account_temp }), + await catchRevert( + I_STRProxied.registerNewTicker(address_zero, symbol, { from: account_temp }), "tx revert -> owner should not be 0x" ); }); it("Should fail to register ticker due to the symbol length is 0", async () => { - catchRevert(I_STRProxied.registerTicker(account_temp, "", name, { from: account_temp }), "tx revert -> Symbol Length is 0"); + await catchRevert(I_STRProxied.registerNewTicker(account_temp, "", { from: account_temp }), "tx revert -> Symbol Length is 0"); }); it("Should fail to register ticker due to the symbol length is greater than 10", async () => { - catchRevert( - I_STRProxied.registerTicker(account_temp, "POLYMATHNET", name, { from: account_temp }), + await catchRevert( + I_STRProxied.registerNewTicker(account_temp, "POLYMATHNET", { from: account_temp }), "tx revert -> Symbol Length is greater than 10" ); }); it("Should register the ticker before the generation of the security token", async () => { - let tx = await I_STRProxied.registerTicker(account_temp, symbol, name, { from: account_temp }); + let tx = await I_STRProxied.registerNewTicker(account_temp, symbol, { from: account_temp }); assert.equal(tx.logs[0].args._owner, account_temp, `Owner should be the ${account_temp}`); assert.equal(tx.logs[0].args._ticker, symbol, `Symbol should be ${symbol}`); + let data = await I_Getter.getTickerDetails.call(symbol); + assert.equal(data[0], account_temp); + // trying to access the function data directly from the STRGetter then it should give all values zero + data = await I_STRGetter.getTickerDetails.call(symbol); + assert.equal(data[0], address_zero); + assert.equal(data[3], ""); }); - it("Should register the ticker when the tickerRegFee is 0", async() => { + it("Should change ticker price based on oracle", async () => { + let snap_Id = await takeSnapshot(); + let origPriceUSD = new BN(web3.utils.toWei("250")); + let origPricePOLY = new BN(web3.utils.toWei("1000")); + let currentRate = await I_POLYOracle.getPrice.call(); + console.log("Current Rate: " + currentRate); + let feesTicker = await I_STRProxied.getFees.call("0x2fcc69711628630fb5a42566c68bd1092bc4aa26826736293969fddcd11cb2d2"); + let feesToken = await I_STRProxied.getFees.call("0xd677304bb45536bb7fdfa6b9e47a3c58fe413f9e8f01474b0a4b9c6e0275baf2"); + assert.equal(feesTicker[0].toString(), origPriceUSD.toString()); + assert.equal(feesTicker[1].toString(), origPricePOLY.toString()); + assert.equal(feesToken[0].toString(), origPriceUSD.toString()); + assert.equal(feesToken[1].toString(), origPricePOLY.toString()); + await I_POLYOracle.changePrice(new BN(27).mul(new BN(10).pow(new BN(16)))); + await I_STRProxied.getFees("0x2fcc69711628630fb5a42566c68bd1092bc4aa26826736293969fddcd11cb2d2"); + feesTicker = await I_STRProxied.getFees.call("0x2fcc69711628630fb5a42566c68bd1092bc4aa26826736293969fddcd11cb2d2"); + feesToken = await I_STRProxied.getFees.call("0xd677304bb45536bb7fdfa6b9e47a3c58fe413f9e8f01474b0a4b9c6e0275baf2"); + // No change as difference is less than 10% + assert.equal(feesTicker[0].toString(), origPriceUSD.toString()); + assert.equal(feesTicker[1].toString(), origPricePOLY.toString()); + assert.equal(feesToken[0].toString(), origPriceUSD.toString()); + assert.equal(feesToken[1].toString(), origPricePOLY.toString()); + await I_POLYOracle.changePrice(new BN(20).mul(new BN(10).pow(new BN(16)))); + await I_STRProxied.getFees("0x2fcc69711628630fb5a42566c68bd1092bc4aa26826736293969fddcd11cb2d2"); + feesTicker = await I_STRProxied.getFees.call("0x2fcc69711628630fb5a42566c68bd1092bc4aa26826736293969fddcd11cb2d2"); + feesToken = await I_STRProxied.getFees.call("0xd677304bb45536bb7fdfa6b9e47a3c58fe413f9e8f01474b0a4b9c6e0275baf2"); + let newPricePOLY = new BN(web3.utils.toWei("1250")); + assert.equal(feesTicker[0].toString(), origPriceUSD.toString()); + assert.equal(feesTicker[1].toString(), newPricePOLY.toString()); + assert.equal(feesToken[0].toString(), origPriceUSD.toString()); + assert.equal(feesToken[1].toString(), newPricePOLY.toString()); + await I_POLYOracle.changePrice(new BN(21).mul(new BN(10).pow(new BN(16)))); + await I_STRProxied.getFees("0x2fcc69711628630fb5a42566c68bd1092bc4aa26826736293969fddcd11cb2d2"); + feesTicker = await I_STRProxied.getFees.call("0x2fcc69711628630fb5a42566c68bd1092bc4aa26826736293969fddcd11cb2d2"); + feesToken = await I_STRProxied.getFees.call("0xd677304bb45536bb7fdfa6b9e47a3c58fe413f9e8f01474b0a4b9c6e0275baf2"); + // No change as difference is less than 10% + assert.equal(feesTicker[0].toString(), origPriceUSD.toString()); + assert.equal(feesTicker[1].toString(), newPricePOLY.toString()); + assert.equal(feesToken[0].toString(), origPriceUSD.toString()); + assert.equal(feesToken[1].toString(), newPricePOLY.toString()); + await I_StablePOLYOracle.changeEvictPercentage(new BN(10).pow(new BN(16))); + await I_STRProxied.getFees("0x2fcc69711628630fb5a42566c68bd1092bc4aa26826736293969fddcd11cb2d2"); + feesTicker = await I_STRProxied.getFees.call("0x2fcc69711628630fb5a42566c68bd1092bc4aa26826736293969fddcd11cb2d2"); + feesToken = await I_STRProxied.getFees.call("0xd677304bb45536bb7fdfa6b9e47a3c58fe413f9e8f01474b0a4b9c6e0275baf2"); + // Change as eviction percentage updated + // newPricePOLY = new BN(web3.utils.toWei("1250")); + //1190.476190476190476190 = 250/0.21 + assert.equal(feesTicker[0].toString(), origPriceUSD.toString()); + assert.equal(feesTicker[1].toString(), "1190476190476190476190"); + assert.equal(feesToken[0].toString(), origPriceUSD.toString()); + assert.equal(feesToken[1].toString(), "1190476190476190476190"); + await revertToSnapshot(snap_Id); + }); + + it("Should register the ticker when the tickerRegFee is 0", async () => { let snap_Id = await takeSnapshot(); await I_STRProxied.changeTickerRegistrationFee(0, { from: account_polymath }); - let tx = await I_STRProxied.registerTicker(account_temp, "ZERO", name, { from: account_temp }); + let tx = await I_STRProxied.registerNewTicker(account_temp, "ZERO", { from: account_temp }); assert.equal(tx.logs[0].args._owner, account_temp, `Owner should be the ${account_temp}`); assert.equal(tx.logs[0].args._ticker, "ZERO", `Symbol should be ZERO`); await revertToSnapshot(snap_Id); - }) + }); it("Should fail to register same symbol again", async () => { // Give POLY to token issuer - await I_PolyToken.getTokens(initRegFee, token_owner); - await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); + await I_PolyToken.getTokens(initRegFeePOLY, token_owner); + await I_PolyToken.approve(I_STRProxied.address, initRegFeePOLY, { from: token_owner }); // Call registration function - catchRevert( - I_STRProxied.registerTicker(token_owner, symbol, name, { from: token_owner }), + await catchRevert( + I_STRProxied.registerNewTicker(token_owner, symbol, { from: token_owner }), "tx revert -> Symbol is already alloted to someone else" ); }); it("Should successfully register pre registerd ticker if expiry is reached", async () => { await increaseTime(5184000 + 100); // 60(5184000) days of expiry + 100 sec for buffer - await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); - let tx = await I_STRProxied.registerTicker(token_owner, symbol, name, { from: token_owner }); + await I_PolyToken.approve(I_STRProxied.address, initRegFeePOLY, { from: token_owner }); + let tx = await I_STRProxied.registerNewTicker(token_owner, symbol, { from: token_owner }); assert.equal(tx.logs[0].args._owner, token_owner, `Owner should be the ${token_owner}`); assert.equal(tx.logs[0].args._ticker, symbol, `Symbol should be ${symbol}`); }); it("Should fail to register ticker if registration is paused", async () => { await I_STRProxied.pause({ from: account_polymath }); - await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); + await I_PolyToken.approve(I_STRProxied.address, initRegFeePOLY, { from: token_owner }); - catchRevert( - I_STRProxied.registerTicker(token_owner, "AAA", name, { from: token_owner }), + await catchRevert( + I_STRProxied.registerNewTicker(token_owner, "AAA", { from: token_owner }), "tx revert -> Registration is paused" ); }); it("Should fail to pause if already paused", async () => { - catchRevert(I_STRProxied.pause({ from: account_polymath }), "tx revert -> Registration is already paused"); + await catchRevert(I_STRProxied.pause({ from: account_polymath }), "tx revert -> Registration is already paused"); }); it("Should successfully register ticker if registration is unpaused", async () => { await I_STRProxied.unpause({ from: account_polymath }); - await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); - let tx = await I_STRProxied.registerTicker(token_owner, "AAA", name, { from: token_owner }); + await I_PolyToken.approve(I_STRProxied.address, initRegFeePOLY, { from: token_owner }); + let tx = await I_STRProxied.registerNewTicker(token_owner, "AAA", { from: token_owner }); assert.equal(tx.logs[0].args._owner, token_owner, `Owner should be the ${token_owner}`); assert.equal(tx.logs[0].args._ticker, "AAA", `Symbol should be AAA`); }); it("Should fail to unpause if already unpaused", async () => { - catchRevert(I_STRProxied.unpause({ from: account_polymath }), "tx revert -> Registration is already unpaused"); + await catchRevert(I_STRProxied.unpause({ from: account_polymath }), "tx revert -> Registration is already unpaused"); }); }); describe("Test cases for the expiry limit", async () => { it("Should fail to set the expiry limit because msg.sender is not owner", async () => { - catchRevert(I_STRProxied.changeExpiryLimit(duration.days(10), { from: account_temp }), "tx revert -> msg.sender is not owner"); + await catchRevert(I_STRProxied.changeExpiryLimit(duration.days(10), { from: account_temp }), "tx revert -> msg.sender is not owner"); }); it("Should successfully set the expiry limit", async () => { @@ -431,7 +455,7 @@ contract("SecurityTokenRegistry", accounts => { }); it("Should fail to set the expiry limit because new expiry limit is lesser than one day", async () => { - catchRevert( + await catchRevert( I_STRProxied.changeExpiryLimit(duration.seconds(5000), { from: account_polymath }), "tx revert -> New expiry limit is lesser than one day" ); @@ -440,14 +464,14 @@ contract("SecurityTokenRegistry", accounts => { describe("Test cases for the getTickerDetails", async () => { it("Should get the details of the symbol", async () => { - let tx = await I_STRProxied.getTickerDetails.call(symbol); + let tx = await I_Getter.getTickerDetails.call(symbol); assert.equal(tx[0], token_owner, "Should equal to the rightful owner of the ticker"); - assert.equal(tx[3], name, `Name of the token should equal to ${name}`); + assert.equal(tx[3], "", "Name of the token should equal be null/empty as it's not stored anymore"); assert.equal(tx[4], false, "Status if the symbol should be undeployed -- false"); }); it("Should get the details of unregistered token", async () => { - let tx = await I_STRProxied.getTickerDetails.call("TORO"); + let tx = await I_Getter.getTickerDetails.call("TORO"); assert.equal(tx[0], address_zero, "Should be 0x as ticker is not exists in the registry"); assert.equal(tx[3], "", "Should be an empty string"); assert.equal(tx[4], false, "Status if the symbol should be undeployed -- false"); @@ -456,30 +480,30 @@ contract("SecurityTokenRegistry", accounts => { describe("Generate SecurityToken", async () => { it("Should get the ticker details successfully and prove the data is not storing in to the logic contract", async () => { - let data = await I_STRProxied.getTickerDetails(symbol, { from: token_owner }); + let data = await I_Getter.getTickerDetails(symbol, { from: token_owner }); assert.equal(data[0], token_owner, "Token owner should be equal"); - assert.equal(data[3], name, "Name of the token should match with the registered symbol infor"); + assert.equal(data[3], "", "Name of the token should equal be null/empty as it's not stored anymore"); assert.equal(data[4], false, "Token is not launched yet so it should return False"); - data = await I_SecurityTokenRegistry.getTickerDetails(symbol, { from: token_owner }); + data = await I_STRGetter.getTickerDetails(symbol, { from: token_owner }); console.log("This is the data from the original securityTokenRegistry contract"); assert.equal(data[0], address_zero, "Token owner should be 0x"); }); it("Should fail to generate new security token if fee not provided", async () => { - await I_PolyToken.approve(I_STRProxied.address, 0, { from: token_owner }); + await I_PolyToken.approve(I_STRProxied.address, new BN(0), { from: token_owner }); - catchRevert( - I_STRProxied.generateSecurityToken(name, symbol, tokenDetails, false, { from: token_owner }), + await catchRevert( + I_STRProxied.generateNewSecurityToken(name, symbol, tokenDetails, false, token_owner, 0, { from: token_owner }), "tx revert -> POLY allowance not provided for registration fee" ); }); it("Should fail to generate token if registration is paused", async () => { await I_STRProxied.pause({ from: account_polymath }); - await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); + await I_PolyToken.approve(I_STRProxied.address, initRegFeePOLY, { from: token_owner }); - catchRevert( - I_STRProxied.generateSecurityToken(name, symbol, tokenDetails, false, { from: token_owner }), + await catchRevert( + I_STRProxied.generateNewSecurityToken(name, symbol, tokenDetails, false, token_owner, 0, { from: token_owner }), "tx revert -> Registration is paused" ); }); @@ -487,36 +511,56 @@ contract("SecurityTokenRegistry", accounts => { it("Should fail to generate the securityToken -- Because ticker length is 0", async () => { await I_STRProxied.unpause({ from: account_polymath }); - catchRevert( - I_STRProxied.generateSecurityToken(name, "", tokenDetails, false, { from: token_owner }), + await catchRevert( + I_STRProxied.generateNewSecurityToken(name, "0x0", tokenDetails, false, token_owner, 0, { from: token_owner }), "tx revert -> Zero ticker length is not allowed" ); }); it("Should fail to generate the securityToken -- Because name length is 0", async () => { - catchRevert( - I_STRProxied.generateSecurityToken("", symbol, tokenDetails, false, { from: token_owner }), + await catchRevert( + I_STRProxied.generateNewSecurityToken("", symbol, tokenDetails, false, token_owner, 0, { from: token_owner }), "tx revert -> 0 name length is not allowed" ); }); + it("Should fail to generate the securityToken -- Because version is not valid", async () => { + await catchRevert( + I_STRProxied.generateNewSecurityToken("", symbol, tokenDetails, false, token_owner, 12356, { from: token_owner }), + "tx revert -> 0 name length is not allowed" + ); + }); + + it("Should fail to generate the securityToken -- Because treasury wallet is 0x0", async () => { + await catchRevert( + I_STRProxied.generateNewSecurityToken(name, symbol, tokenDetails, false, address_zero, 0, { from: token_owner }), + "tx revert -> 0x0 value of treasury wallet is not allowed" + ); + }); + it("Should fail to generate the securityToken -- Because msg.sender is not the rightful owner of the ticker", async () => { - catchRevert( - I_STRProxied.generateSecurityToken(name, symbol, tokenDetails, false, { from: account_temp }), + await catchRevert( + I_STRProxied.generateNewSecurityToken(name, symbol, tokenDetails, false, token_owner, 0, { from: account_temp }), "tx revert -> Because msg.sender is not the rightful owner of the ticker" ); }); it("Should generate the new security token with the same symbol as registered above", async () => { - let _blockNo = latestBlock(); - let tx = await I_STRProxied.generateSecurityToken(name, symbol, tokenDetails, false, { from: token_owner }); + console.log(await I_STRGetter.getSTFactoryAddress()); + let info = await I_STRGetter.getLatestProtocolVersion(); + for (let i = 0; i < info.length; i++) { + console.log(info[i].toNumber()); + } + console.log(await I_STRGetter.getLatestProtocolVersion()); + let tx = await I_STRProxied.generateNewSecurityToken(name, symbol, tokenDetails, false, treasury_wallet, 0, { from: token_owner }); // Verify the successful generation of the security token assert.equal(tx.logs[1].args._ticker, symbol, "SecurityToken doesn't get deployed"); - I_SecurityToken = SecurityToken.at(tx.logs[1].args._securityTokenAddress); - - const log = await promisifyLogWatch(I_SecurityToken.ModuleAdded({ from: _blockNo }), 1); + I_SecurityToken = await SecurityToken.at(tx.logs[1].args._securityTokenAddress); + stGetter = await STGetter.at(I_SecurityToken.address); + assert.equal(await stGetter.getTreasuryWallet.call(), treasury_wallet, "Incorrect wallet set") + const log = (await I_SecurityToken.getPastEvents('ModuleAdded', {filter: {transactionHash: tx.transactionHash}}))[0]; // Verify that GeneralTrasnferManager module get added successfully or not assert.equal(log.args._types[0].toNumber(), transferManagerKey, `Should be equal to the ${transferManagerKey}`); @@ -524,106 +568,195 @@ contract("SecurityTokenRegistry", accounts => { }); it("Should fail to generate the SecurityToken when token is already deployed with the same symbol", async () => { - catchRevert( - I_STRProxied.generateSecurityToken(name, symbol, tokenDetails, false, { from: token_owner }), + await catchRevert( + I_STRProxied.generateNewSecurityToken(name, symbol, tokenDetails, false, treasury_wallet, 0, { from: token_owner }), "tx revert -> Because ticker is already in use" ); }); - it("Should fail to generate the SecurityToken because ticker gets expired", async() => { + it("Should fail to generate the SecurityToken because ticker gets expired", async () => { let snap_Id = await takeSnapshot(); - await I_PolyToken.approve(I_STRProxied.address, web3.utils.toWei("500"), { from: token_owner }); - let tx = await I_STRProxied.registerTicker(token_owner, "CCC", name, { from: token_owner }); + await I_PolyToken.approve(I_STRProxied.address, new BN(web3.utils.toWei("2000")), { from: token_owner }); + let tx = await I_STRProxied.registerNewTicker(token_owner, "CCC", { from: token_owner }); await increaseTime(duration.days(65)); - catchRevert( - I_STRProxied.generateSecurityToken(name, "CCC", tokenDetails, false, { from: token_owner }), + await catchRevert( + I_STRProxied.generateNewSecurityToken(name, "CCC", tokenDetails, false, treasury_wallet, 0, { from: token_owner }), "tx revert -> Because ticker is expired" ); await revertToSnapshot(snap_Id); }); - it("Should generate the SecurityToken when launch fee is 0", async() => { + it("Should generate the SecurityToken when launch fee is 0", async () => { let snap_Id = await takeSnapshot(); await I_STRProxied.changeSecurityLaunchFee(0, { from: account_polymath }); - await I_PolyToken.approve(I_STRProxied.address, web3.utils.toWei("500"), { from: token_owner }); - let tx = await I_STRProxied.registerTicker(token_owner, "CCC", name, { from: token_owner }); - await I_STRProxied.generateSecurityToken(name, "CCC", tokenDetails, false, { from: token_owner }), + await I_STRProxied.changeTickerRegistrationFee(0, { from: account_polymath }); + //await I_PolyToken.approve(I_STRProxied.address, new BN(web3.utils.toWei("2000")), { from: token_owner }); + await I_STRProxied.registerNewTicker(token_owner, "CCC", { from: token_owner }); + await I_STRProxied.generateNewSecurityToken(name, "CCC", tokenDetails, false, treasury_wallet, 0, { from: token_owner }), + await revertToSnapshot(snap_Id); + }); + + it("Should get all created security tokens", async() => { + let snap_Id = await takeSnapshot(); + await I_PolyToken.getTokens(web3.utils.toWei("2000"), account_temp); + await I_PolyToken.approve(I_STRProxied.address, web3.utils.toWei("2000"), { from: account_temp }); + await I_STRProxied.registerNewTicker(account_temp, "TMP", { from: account_temp }); + let tx = await I_STRProxied.generateNewSecurityToken(name, "TMP", tokenDetails, false, account_temp, 0, { from: account_temp }); + + // Verify the successful generation of the security token + assert.equal(tx.logs[1].args._ticker, "TMP", "SecurityToken doesn't get deployed"); + + let securityTokenTmp = await SecurityToken.at(tx.logs[1].args._securityTokenAddress); + + let tokens = await I_Getter.getTokensByOwner.call(token_owner); + assert.equal(tokens.length, 1, "tokens array length error"); + assert.equal(tokens[0], I_SecurityToken.address, "ST address incorrect"); + + let allTokens = await I_Getter.getTokens.call(); + assert.equal(allTokens.length, 2); + assert.equal(allTokens[0], securityTokenTmp.address); + assert.equal(allTokens[1], I_SecurityToken.address); + await revertToSnapshot(snap_Id); }); }); describe("Generate SecurityToken v2", async () => { - it("Should deploy the st version 2", async () => { + it("Should deploy the new ST factory version 2", async () => { + const encodeParams = { + name: "initialize", + type: "function", + inputs: [ + { + type: "address", + name: "_getterDelegate" + } + ] + } // Step 7: Deploy the STFactory contract - - I_STFactory002 = await STFactory.new(I_GeneralTransferManagerFactory.address, { from: account_polymath }); - + I_STGetter = await STGetter.new({from: account_polymath}); + let initializeData = await web3.eth.abi.encodeFunctionCall(encodeParams, [I_STGetter.address]); + let I_DataStoreLogic = await DataStoreLogic.new({ from: account_polymath }); + let I_DataStoreFactory = await DataStoreFactory.new(I_DataStoreLogic.address, { from: account_polymath }); + I_TokenLib = await TokenLib.new(); + await STFactoryV2.link(TokenLib); + await SecurityTokenMock.link(TokenLib); + let SecurityTokenV2Logic = await SecurityTokenMock.new({from: account_polymath}); + I_STFactory002 = await STFactoryV2.new( + I_PolymathRegistry.address, + I_GeneralTransferManagerFactory.address, + I_DataStoreFactory.address, + "3", + SecurityTokenV2Logic.address, + initializeData, + { + from: account_polymath + } + ); + console.log("STF deployed"); assert.notEqual( I_STFactory002.address.valueOf(), address_zero, "STFactory002 contract was not deployed" ); - await I_STRProxied.setProtocolVersion(I_STFactory002.address, 2, 2, 0, { from: account_polymath }); - let _protocol = await I_STRProxied.getProtocolVersion.call(); - assert.equal(_protocol[0], 2); - assert.equal(_protocol[1], 2); + let _protocol = await I_Getter.getLatestProtocolVersion.call(); + assert.equal(_protocol[0], 3); + assert.equal(_protocol[1], 0); assert.equal(_protocol[2], 0); }); it("Should register the ticker before the generation of the security token", async () => { - await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); - let tx = await I_STRProxied.registerTicker(token_owner, symbol2, name2, { from: token_owner }); + await I_PolyToken.approve(I_STRProxied.address, initRegFeePOLY, { from: token_owner }); + let tx = await I_STRProxied.registerNewTicker(token_owner, symbol2, { from: token_owner }); assert.equal(tx.logs[0].args._owner, token_owner, `Token owner should be ${token_owner}`); assert.equal(tx.logs[0].args._ticker, symbol2, `Symbol should be ${symbol2}`); }); - it("Should generate the new security token with version 2", async () => { - await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); - let _blockNo = latestBlock(); - let tx = await I_STRProxied.generateSecurityToken(name2, symbol2, tokenDetails, false, { from: token_owner }); + it("Should change the protocol version", async() => { + await I_STRProxied.setProtocolFactory(I_STFactory002.address, new BN(2), new BN(2), new BN(0), { from: account_polymath }); + let _protocol = await I_Getter.getLatestProtocolVersion.call(); + assert.equal(_protocol[0], 3); + assert.equal(_protocol[1], 0); + assert.equal(_protocol[2], 0); + await I_STRProxied.setLatestVersion(new BN(2), new BN(2), new BN(0), { from: account_polymath }); + _protocol = await I_Getter.getLatestProtocolVersion.call(); + assert.equal(_protocol[0], 2); + assert.equal(_protocol[1], 2); + assert.equal(_protocol[2], 0); + await catchRevert( + I_STRProxied.setProtocolFactory(I_STFactory.address, new BN(3), new BN(0), new BN(0), { from: account_polymath}) + ); + await I_STRProxied.setProtocolFactory(I_STFactory.address, new BN(3), new BN(0), new BN(1), { from: account_polymath}); + await I_STRProxied.setLatestVersion(new BN(3), new BN(0), new BN(0), { from: account_polymath}); + _protocol = await I_Getter.getLatestProtocolVersion.call(); + assert.equal(_protocol[0], 3); + assert.equal(_protocol[1], 0); + assert.equal(_protocol[2], 0); + }); + it("Should fail to generate the securityToken because of invalid version", async() => { + await I_PolyToken.approve(I_STRProxied.address, initRegFeePOLY, { from: token_owner }); + await catchRevert( + I_STRProxied.generateNewSecurityToken(name2, symbol2, tokenDetails, false, token_owner, _pack(1,2,0), { from: token_owner }) + ); + }) + + it("Should generate the new security token with version 2", async () => { + let snapId = await takeSnapshot(); + // Version bounds not checked here as MR is called as non-token + let tx = await I_STRProxied.generateNewSecurityToken(name2, symbol2, tokenDetails, false, token_owner, _pack(2,2,0), { from: token_owner }); + console.log(`Protocol version: ${_pack(2,2,0)}`); // Verify the successful generation of the security token assert.equal(tx.logs[1].args._ticker, symbol2, "SecurityToken doesn't get deployed"); - I_SecurityToken002 = SecurityToken.at(tx.logs[1].args._securityTokenAddress); - let tokens = await I_STRProxied.getTokensByOwner.call(token_owner); - assert.equal(tokens[0], I_SecurityToken.address); - assert.equal(tokens[1], I_SecurityToken002.address); - - const log = await promisifyLogWatch(I_SecurityToken002.ModuleAdded({ from: _blockNo }), 1); + I_SecurityToken002 = await SecurityTokenMock.at(tx.logs[1].args._securityTokenAddress); + let stGetterV2 = await STGetter.at(I_SecurityToken002.address); + let stVersion = await stGetterV2.getVersion.call(); + console.log(stVersion); + assert.equal(stVersion[0], 2); + assert.equal(stVersion[1], 2); + assert.equal(stVersion[2], 0); + const log = (await I_SecurityToken002.getPastEvents('ModuleAdded'))[0]; // Verify that GeneralTransferManager module get added successfully or not assert.equal(log.args._types[0].toNumber(), transferManagerKey); assert.equal(web3.utils.toAscii(log.args._name).replace(/\u0000/g, ""), "GeneralTransferManager"); + await revertToSnapshot(snapId); }); }); describe("Deploy the new SecurityTokenRegistry", async () => { it("Should deploy the new SecurityTokenRegistry contract logic", async () => { I_SecurityTokenRegistryV2 = await SecurityTokenRegistryMock.new({ from: account_polymath }); - assert.notEqual( - I_SecurityTokenRegistryV2.address.valueOf(), - address_zero, - "SecurityTokenRegistry contract was not deployed" - ); + assert.notEqual(I_SecurityTokenRegistryV2.address.valueOf(), address_zero, "SecurityTokenRegistry contract was not deployed"); }); it("Should fail to upgrade the logic contract of the STRProxy -- bad owner", async () => { await I_STRProxied.pause({ from: account_polymath }); - catchRevert( - I_SecurityTokenRegistryProxy.upgradeTo("1.1.0", I_SecurityTokenRegistryV2.address, { from: account_temp }), + const STRProxyConfigParameters = ["uint256"]; + let bytesProxy = encodeModuleCall(STRProxyConfigParameters, [ + 99 + ]); + + await catchRevert( + I_SecurityTokenRegistryProxy.upgradeToAndCall("1.1.0", I_SecurityTokenRegistryV2.address, bytesProxy, { from: account_temp }), "tx revert -> bad owner" ); }); it("Should upgrade the logic contract into the STRProxy", async () => { - await I_SecurityTokenRegistryProxy.upgradeTo("1.1.0", I_SecurityTokenRegistryV2.address, { from: account_polymath }); + const STRProxyConfigParameters = ["uint256"]; + let bytesProxy = encodeModuleCall(STRProxyConfigParameters, [ + 99 + ]); + + await I_SecurityTokenRegistryProxy.upgradeToAndCall("1.1.0", I_SecurityTokenRegistryV2.address, bytesProxy, { from: account_polymath }); I_STRProxied = await SecurityTokenRegistry.at(I_SecurityTokenRegistryProxy.address); assert.isTrue(await I_STRProxied.getBoolValue.call(web3.utils.soliditySha3("paused")), "Paused value should be false"); }); it("Should check the old data persist or not", async () => { - let data = await I_STRProxied.getTickerDetails.call(symbol); + let data = await I_Getter.getTickerDetails.call(symbol); assert.equal(data[0], token_owner, "Should be equal to the token owner address"); assert.equal(data[3], name, "Should be equal to the name of the token that is provided earlier"); assert.isTrue(data[4], "Token status should be deployed == true"); @@ -637,26 +770,26 @@ contract("SecurityTokenRegistry", accounts => { describe("Generate custom tokens", async () => { it("Should fail if msg.sender is not polymath", async () => { - catchRevert( - I_STRProxied.modifySecurityToken("LOGAN", "LOG", account_temp, dummy_token, "I am custom ST", latestTime(), { + await catchRevert( + I_STRProxied.modifyExistingSecurityToken("LOG", account_temp, dummy_token, "I am custom ST", currentTime, { from: account_delegate }), "tx revert -> msg.sender is not polymath account" ); }); - it("Should fail to genrate the custom security token -- ticker length is greater than 10 chars", async() => { - catchRevert( - I_STRProxied.modifySecurityToken("LOGAN", "LOGLOGLOGLOG", account_temp, dummy_token, "I am custom ST", latestTime(), { + it("Should fail to genrate the custom security token -- ticker length is greater than 10 chars", async () => { + await catchRevert( + I_STRProxied.modifyExistingSecurityToken("LOGLOGLOGLOG", account_temp, dummy_token, "I am custom ST", currentTime, { from: account_polymath }), - "tx revert -> msg.sender is not polymath account" + "tx revert -> ticker length is greater than 10 chars" ); - }) + }); it("Should fail to generate the custom security token -- name should not be 0 length ", async () => { - catchRevert( - I_STRProxied.modifySecurityToken("", "LOG", account_temp, dummy_token, "I am custom ST", latestTime(), { + await catchRevert( + I_STRProxied.modifyExistingSecurityToken("LOG", account_temp, dummy_token, "I am custom ST", currentTime, { from: account_polymath }), "tx revert -> name should not be 0 length" @@ -664,8 +797,8 @@ contract("SecurityTokenRegistry", accounts => { }); it("Should fail if ST address is 0 address", async () => { - catchRevert( - I_STRProxied.modifySecurityToken("LOGAN", "LOG", account_temp, 0, "I am custom ST", latestTime(), { + await catchRevert( + I_STRProxied.modifyExistingSecurityToken("LOG", account_temp, address_zero, "I am custom ST", currentTime, { from: account_polymath }), "tx revert -> Security token address is 0" @@ -673,8 +806,8 @@ contract("SecurityTokenRegistry", accounts => { }); it("Should fail if symbol length is 0", async () => { - catchRevert( - I_STRProxied.modifySecurityToken("", "", account_temp, dummy_token, "I am custom ST", latestTime(), { + await catchRevert( + I_STRProxied.modifyExistingSecurityToken("0x0", account_temp, dummy_token, "I am custom ST", currentTime, { from: account_polymath }), "tx revert -> zero length of the symbol is not allowed" @@ -682,71 +815,73 @@ contract("SecurityTokenRegistry", accounts => { }); it("Should fail to generate the custom ST -- deployedAt param is 0", async () => { - catchRevert( - I_STRProxied.modifySecurityToken(name2, symbol2, token_owner, dummy_token, "I am custom ST", 0, { from: account_polymath }), + await catchRevert( + I_STRProxied.modifyExistingSecurityToken(symbol2, token_owner, dummy_token, "I am custom ST", new BN(0), { from: account_polymath }), "tx revert -> because deployedAt param is 0" ); }); it("Should successfully generate custom token", async () => { // Register the new ticker -- Fulfiling the TickerStatus.ON condition - await I_PolyToken.getTokens(web3.utils.toWei("1000"), account_temp); - await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: account_temp }); - let tickersListArray = await I_STRProxied.getTickersByOwner.call(account_temp); + await I_PolyToken.getTokens(new BN(web3.utils.toWei("1000")), account_temp); + await I_PolyToken.approve(I_STRProxied.address, initRegFeePOLY, { from: account_temp }); + let tickersListArray = await I_Getter.getTickersByOwner.call(account_temp); console.log(tickersListArray); - await I_STRProxied.registerTicker(account_temp, "LOG", "LOGAN", { from: account_temp }); - tickersListArray = await I_STRProxied.getTickersByOwner.call(account_temp); + await I_STRProxied.registerNewTicker(account_temp, "LOG", { from: account_temp }); + tickersListArray = await I_Getter.getTickersByOwner.call(account_temp); console.log(tickersListArray); // Generating the ST - let tx = await I_STRProxied.modifySecurityToken("LOGAN", "LOG", account_temp, dummy_token, "I am custom ST", latestTime(), { + let tx = await I_STRProxied.modifyExistingSecurityToken("LOG", account_temp, I_SecurityToken.address, "I am custom ST", currentTime, { from: account_polymath }); - tickersListArray = await I_STRProxied.getTickersByOwner.call(account_temp); + tickersListArray = await I_Getter.getTickersByOwner.call(account_temp); console.log(tickersListArray); assert.equal(tx.logs[1].args._ticker, "LOG", "Symbol should match with the registered symbol"); assert.equal( tx.logs[1].args._securityTokenAddress, - dummy_token, + I_SecurityToken.address, `Address of the SecurityToken should be matched with the input value of addCustomSecurityToken` ); - let symbolDetails = await I_STRProxied.getTickerDetails("LOG"); - assert.equal(symbolDetails[0], account_temp, `Owner of the symbol should be ${account_temp}`); - assert.equal(symbolDetails[3], "LOGAN", `Name of the symbol should be LOGAN`); }); it("Should successfully generate the custom token", async () => { // Fulfilling the TickerStatus.NN condition // - // await catchRevert(I_STRProxied.modifySecurityToken("LOGAN2", "LOG2", account_temp, dummy_token, "I am custom ST", latestTime(), {from: account_polymath})); - // await I_STRProxied.modifyTicker(account_temp, "LOG2", "LOGAN2", latestTime(), latestTime() + duration.days(10), false, {from: account_polymath}); + // await catchRevert(I_STRProxied.modifyExistingSecurityToken("LOG2", account_temp, dummy_token, "I am custom ST", await latestTime(), {from: account_polymath})); + // await I_STRProxied.modifyExistingTicker(account_temp, "LOG2", await latestTime(), currentTime.add(new BN(duration.days(10))), false, {from: account_polymath}); // await increaseTime(duration.days(1)); - let tx = await I_STRProxied.modifySecurityToken("LOGAN2", "LOG2", account_temp, dummy_token, "I am custom ST", latestTime(), { + let tx = await I_STRProxied.modifyExistingSecurityToken("LOG2", account_temp, I_SecurityToken.address, "I am custom ST", currentTime, { from: account_polymath }); assert.equal(tx.logs[1].args._ticker, "LOG2", "Symbol should match with the registered symbol"); assert.equal( tx.logs[1].args._securityTokenAddress, - dummy_token, + I_SecurityToken.address, `Address of the SecurityToken should be matched with the input value of addCustomSecurityToken` ); assert.equal(tx.logs[0].args._owner, account_temp, `Token owner should be ${account_temp}`); assert.equal(tx.logs[0].args._ticker, "LOG2", `Symbol should be LOG2`); - let symbolDetails = await I_STRProxied.getTickerDetails("LOG2"); - assert.equal(symbolDetails[0], account_temp, `Owner of the symbol should be ${account_temp}`); - assert.equal(symbolDetails[3], "LOGAN2", `Name of the symbol should be LOGAN`); }); - it("Should successfully modify the ticker", async() => { + it("Should successfully modify the ticker", async () => { let snap_Id = await takeSnapshot(); - let tx = await I_STRProxied.modifyTicker(account_temp, "LOG2", "LOGAN2", latestTime(), latestTime() + duration.days(60), false, {from: account_polymath}); + let tx = await I_STRProxied.modifyExistingTicker( + account_temp, + "LOG2", + currentTime, + currentTime.add(new BN(duration.days(60))), + false, + { from: account_polymath } + ); await revertToSnapshot(snap_Id); - }) + }); }); describe("Test case for modifyTicker", async () => { it("Should add the custom ticker --failed because of bad owner", async () => { - catchRevert( - I_STRProxied.modifyTicker(token_owner, "ETH", "Ether", latestTime(), latestTime() + duration.days(10), false, { + currentTime = new BN(await latestTime()); + await catchRevert( + I_STRProxied.modifyExistingTicker(token_owner, "ETH", currentTime, currentTime.add(new BN(duration.days(10))), false, { from: account_temp }), "tx revert -> failed beacause of bad owner0" @@ -754,8 +889,8 @@ contract("SecurityTokenRegistry", accounts => { }); it("Should add the custom ticker --fail ticker length should not be 0", async () => { - catchRevert( - I_STRProxied.modifyTicker(token_owner, "", "Ether", latestTime(), latestTime() + duration.days(10), false, { + await catchRevert( + I_STRProxied.modifyExistingTicker(token_owner, "", currentTime, currentTime.add(new BN(duration.days(10))), false, { from: account_polymath }), "tx revert -> failed beacause ticker length should not be 0" @@ -763,8 +898,8 @@ contract("SecurityTokenRegistry", accounts => { }); it("Should add the custom ticker --failed because time should not be 0", async () => { - catchRevert( - I_STRProxied.modifyTicker(token_owner, "ETH", "Ether", 0, latestTime() + duration.days(10), false, { + await catchRevert( + I_STRProxied.modifyExistingTicker(token_owner, "ETH", new BN(0), currentTime.add(new BN(duration.days(10))), false, { from: account_polymath }), "tx revert -> failed because time should not be 0" @@ -772,8 +907,9 @@ contract("SecurityTokenRegistry", accounts => { }); it("Should add the custom ticker --failed because registeration date is greater than the expiryDate", async () => { - catchRevert( - I_STRProxied.modifyTicker(token_owner, "ETH", "Ether", latestTime(), latestTime() - duration.minutes(10), false, { + let ctime = currentTime; + await catchRevert( + I_STRProxied.modifyExistingTicker(token_owner, "ETH", ctime, ctime.sub(new BN(duration.minutes(10))), false, { from: account_polymath }), "tx revert -> failed because registeration date is greater than the expiryDate" @@ -781,13 +917,13 @@ contract("SecurityTokenRegistry", accounts => { }); it("Should add the custom ticker --failed because owner should not be 0x", async () => { - catchRevert( - I_STRProxied.modifyTicker( - "0x000000000000000000000000000000000000000000", + let ctime = currentTime; + await catchRevert( + I_STRProxied.modifyExistingTicker( + address_zero, "ETH", - "Ether", - latestTime(), - latestTime() + duration.minutes(10), + ctime, + ctime.add(new BN(duration.minutes(10))), false, { from: account_polymath } ), @@ -796,12 +932,12 @@ contract("SecurityTokenRegistry", accounts => { }); it("Should add the new custom ticker", async () => { - let tx = await I_STRProxied.modifyTicker( + let ctime = currentTime; + let tx = await I_STRProxied.modifyExistingTicker( account_temp, "ETH", - "Ether", - latestTime(), - latestTime() + duration.minutes(10), + ctime, + ctime.add(new BN(duration.minutes(10))), false, { from: account_polymath } ); @@ -810,12 +946,12 @@ contract("SecurityTokenRegistry", accounts => { }); it("Should change the details of the existing ticker", async () => { - let tx = await I_STRProxied.modifyTicker( + let ctime = currentTime; + let tx = await I_STRProxied.modifyExistingTicker( token_owner, "ETH", - "Ether", - latestTime(), - latestTime() + duration.minutes(10), + ctime, + ctime.add(new BN(duration.minutes(10))), false, { from: account_polymath } ); @@ -824,76 +960,75 @@ contract("SecurityTokenRegistry", accounts => { }); describe("Test cases for the transferTickerOwnership()", async () => { - it("Should able to transfer the ticker ownership -- failed because token is not deployed having the same ticker", async () => { - catchRevert( + it("Should be able to transfer the ticker ownership -- failed because token is not deployed having the same ticker", async () => { + await catchRevert( I_STRProxied.transferTickerOwnership(account_issuer, "ETH", { from: account_temp }), "tx revert -> failed because token is not deployed having the same ticker" ); }); - it("Should able to transfer the ticker ownership -- failed because new owner is 0x", async () => { - await I_SecurityToken002.transferOwnership(account_temp, { from: token_owner }); - catchRevert( - I_STRProxied.transferTickerOwnership("0x00000000000000000000000000000000000000000", symbol2, { from: token_owner }), + it("Should be able to transfer the ticker ownership -- failed because new owner is 0x", async () => { + //await I_SecurityToken002.transferOwnership(account_temp, { from: token_owner }); + await catchRevert( + I_STRProxied.transferTickerOwnership(address_zero, symbol2, { from: token_owner }), "tx revert -> failed because new owner is 0x" ); }); - it("Should able to transfer the ticker ownership -- failed because ticker is of zero length", async () => { - catchRevert( + it("Should be able to transfer the ticker ownership -- failed because ticker is of zero length", async () => { + await catchRevert( I_STRProxied.transferTickerOwnership(account_temp, "", { from: token_owner }), "tx revert -> failed because ticker is of zero length" ); }); - it("Should able to transfer the ticker ownership", async () => { + it("Should be able to transfer the ticker ownership", async () => { let tx = await I_STRProxied.transferTickerOwnership(account_temp, symbol2, { from: token_owner, gas: 5000000 }); assert.equal(tx.logs[0].args._newOwner, account_temp); - let symbolDetails = await I_STRProxied.getTickerDetails.call(symbol2); + let symbolDetails = await I_Getter.getTickerDetails.call(symbol2); assert.equal(symbolDetails[0], account_temp, `Owner of the symbol should be ${account_temp}`); - assert.equal(symbolDetails[3], name2, `Name of the symbol should be ${name2}`); }); }); describe("Test case for the changeSecurityLaunchFee()", async () => { - it("Should able to change the STLaunchFee-- failed because of bad owner", async () => { - catchRevert( - I_STRProxied.changeSecurityLaunchFee(web3.utils.toWei("500"), { from: account_temp }), + it("Should be able to change the STLaunchFee-- failed because of bad owner", async () => { + await catchRevert( + I_STRProxied.changeSecurityLaunchFee(new BN(web3.utils.toWei("500")), { from: account_temp }), "tx revert -> failed because of bad owner" ); }); - it("Should able to change the STLaunchFee-- failed because of putting the same fee", async () => { - catchRevert( + it("Should be able to change the STLaunchFee-- failed because of putting the same fee", async () => { + await catchRevert( I_STRProxied.changeSecurityLaunchFee(initRegFee, { from: account_polymath }), "tx revert -> failed because of putting the same fee" ); }); - it("Should able to change the STLaunchFee", async () => { - let tx = await I_STRProxied.changeSecurityLaunchFee(web3.utils.toWei("500"), { from: account_polymath }); - assert.equal(tx.logs[0].args._newFee, web3.utils.toWei("500")); + it("Should be able to change the STLaunchFee", async () => { + let tx = await I_STRProxied.changeSecurityLaunchFee(new BN(web3.utils.toWei("500")), { from: account_polymath }); + assert.equal(tx.logs[0].args._newFee.toString(), new BN(web3.utils.toWei("500")).toString()); let stLaunchFee = await I_STRProxied.getUintValue(web3.utils.soliditySha3("stLaunchFee")); - assert.equal(stLaunchFee, web3.utils.toWei("500")); + assert.equal(stLaunchFee.toString(), new BN(web3.utils.toWei("500")).toString()); }); }); describe("Test cases for the changeExpiryLimit()", async () => { - it("Should able to change the ExpiryLimit-- failed because of bad owner", async () => { - catchRevert( + it("Should be able to change the ExpiryLimit-- failed because of bad owner", async () => { + await catchRevert( I_STRProxied.changeExpiryLimit(duration.days(15), { from: account_temp }), "tx revert -> failed because of bad owner" ); }); - it("Should able to change the ExpiryLimit-- failed because expirylimit is less than 1 day", async () => { - catchRevert( + it("Should be able to change the ExpiryLimit-- failed because expirylimit is less than 1 day", async () => { + await catchRevert( I_STRProxied.changeExpiryLimit(duration.minutes(50), { from: account_polymath }), "tx revert -> failed because expirylimit is less than 1 day" ); }); - it("Should able to change the ExpiryLimit", async () => { + it("Should be able to change the ExpiryLimit", async () => { let tx = await I_STRProxied.changeExpiryLimit(duration.days(20), { from: account_polymath }); assert.equal(tx.logs[0].args._newExpiry, duration.days(20)); let expiry = await I_STRProxied.getUintValue(web3.utils.soliditySha3("expiryLimit")); @@ -902,54 +1037,88 @@ contract("SecurityTokenRegistry", accounts => { }); describe("Test cases for the changeTickerRegistrationFee()", async () => { - it("Should able to change the TickerRegFee-- failed because of bad owner", async () => { - catchRevert( - I_STRProxied.changeTickerRegistrationFee(web3.utils.toWei("500"), { from: account_temp }), + it("Should be able to change the fee currency-- failed because of bad owner", async () => { + await catchRevert( + I_STRProxied.changeFeesAmountAndCurrency(new BN(web3.utils.toWei("500")), new BN(web3.utils.toWei("100")), false, { from: account_temp }), "tx revert -> failed because of bad owner" ); }); - it("Should able to change the TickerRegFee-- failed because of putting the same fee", async () => { - catchRevert( + it("Should be able to change the fee currency-- failed because of putting the same currency", async () => { + await catchRevert( + I_STRProxied.changeFeesAmountAndCurrency(new BN(web3.utils.toWei("500")), new BN(web3.utils.toWei("100")), false, { from: account_polymath }), + "tx revert -> failed because of putting the same fee" + ); + }); + + it("Should be able to change the fee currency", async () => { + let snapId = await takeSnapshot(); + let tx = await I_STRProxied.changeFeesAmountAndCurrency(new BN(web3.utils.toWei("500")), new BN(web3.utils.toWei("100")), true, { from: account_polymath }); + assert.equal(tx.logs[0].args._newFee.toString(), new BN(web3.utils.toWei("500")).toString(), "wrong ticker fee in event"); + assert.equal(tx.logs[1].args._newFee.toString(), new BN(web3.utils.toWei("100")).toString(), "wrong st fee in event"); + assert.equal(tx.logs[2].args._isFeeInPoly, true, "wrong fee type"); + assert.equal(await I_Getter.getIsFeeInPoly(), true, "is fee in poly not set"); + let tickerRegFee = await I_STRProxied.getUintValue(web3.utils.soliditySha3("tickerRegFee")); + assert.equal(tickerRegFee.toString(), new BN(web3.utils.toWei("500")).toString(), "wrong fee"); + let tickerRegFeePoly = (await I_STRProxied.getFees.call("0x2fcc69711628630fb5a42566c68bd1092bc4aa26826736293969fddcd11cb2d2"))[1]; + assert.equal(tickerRegFeePoly.toString(), tickerRegFee.toString()); + let stFee = await I_STRProxied.getUintValue(web3.utils.soliditySha3("stLaunchFee")); + assert.equal(stFee.toString(), new BN(web3.utils.toWei("100")).toString(), "wrong fee"); + let stFeePoly = (await I_STRProxied.getFees.call("0xd677304bb45536bb7fdfa6b9e47a3c58fe413f9e8f01474b0a4b9c6e0275baf2"))[1]; + assert.equal(stFeePoly.toString(), stFee.toString()); + await revertToSnapshot(snapId); + }); + }); + + describe("Test cases for the changeTickerRegistrationFee()", async () => { + it("Should be able to change the TickerRegFee-- failed because of bad owner", async () => { + await catchRevert( + I_STRProxied.changeTickerRegistrationFee(new BN(web3.utils.toWei("500")), { from: account_temp }), + "tx revert -> failed because of bad owner" + ); + }); + + it("Should be able to change the TickerRegFee-- failed because of putting the same fee", async () => { + await catchRevert( I_STRProxied.changeTickerRegistrationFee(initRegFee, { from: account_polymath }), "tx revert -> failed because of putting the same fee" ); }); - it("Should able to change the TickerRegFee", async () => { - let tx = await I_STRProxied.changeTickerRegistrationFee(web3.utils.toWei("400"), { from: account_polymath }); - assert.equal(tx.logs[0].args._newFee, web3.utils.toWei("400")); + it("Should be able to change the TickerRegFee", async () => { + let tx = await I_STRProxied.changeTickerRegistrationFee(new BN(web3.utils.toWei("400")), { from: account_polymath }); + assert.equal(tx.logs[0].args._newFee.toString(), new BN(web3.utils.toWei("400")).toString()); let tickerRegFee = await I_STRProxied.getUintValue(web3.utils.soliditySha3("tickerRegFee")); - assert.equal(tickerRegFee, web3.utils.toWei("400")); + assert.equal(tickerRegFee.toString(), new BN(web3.utils.toWei("400")).toString()); }); it("Should fail to register the ticker with the old fee", async () => { - await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); - catchRevert( - I_STRProxied.registerTicker(token_owner, "POLY", "Polymath", { from: token_owner }), + await I_PolyToken.approve(I_STRProxied.address, initRegFeePOLY, { from: token_owner }); + await catchRevert( + I_STRProxied.registerNewTicker(token_owner, "POLY", { from: token_owner }), "tx revert -> failed because of ticker registeration fee gets change" ); }); it("Should register the ticker with the new fee", async () => { - await I_PolyToken.getTokens(web3.utils.toWei("1000"), token_owner); - await I_PolyToken.approve(I_STRProxied.address, web3.utils.toWei("500"), { from: token_owner }); - let tx = await I_STRProxied.registerTicker(token_owner, "POLY", "Polymath", { from: token_owner }); + await I_PolyToken.getTokens(new BN(web3.utils.toWei("1600")), token_owner); + await I_PolyToken.approve(I_STRProxied.address, new BN(web3.utils.toWei("2000")), { from: token_owner }); + let tx = await I_STRProxied.registerNewTicker(token_owner, "POLY", { from: token_owner }); assert.equal(tx.logs[0].args._owner, token_owner, `Token owner should be ${token_owner}`); assert.equal(tx.logs[0].args._ticker, "POLY", `Symbol should be POLY`); }); it("Should fail to launch the securityToken with the old launch fee", async () => { - await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); - catchRevert( - I_STRProxied.generateSecurityToken("Polymath", "POLY", tokenDetails, false, { from: token_owner }), + await I_PolyToken.approve(I_STRProxied.address, initRegFeePOLY, { from: token_owner }); + await catchRevert( + I_STRProxied.generateNewSecurityToken("Polymath", "POLY", tokenDetails, false, token_owner, 0, { from: token_owner }), "tx revert -> failed because of old launch fee" ); }); it("Should launch the the securityToken", async () => { - await I_PolyToken.approve(I_STRProxied.address, web3.utils.toWei("500"), { from: token_owner }); - let tx = await I_STRProxied.generateSecurityToken("Polymath", "POLY", tokenDetails, false, { from: token_owner }); + await I_PolyToken.approve(I_STRProxied.address, new BN(web3.utils.toWei("2000")), { from: token_owner }); + let tx = await I_STRProxied.generateNewSecurityToken("Polymath", "POLY", tokenDetails, false, token_owner, 0, { from: token_owner }); // Verify the successful generation of the security token assert.equal(tx.logs[1].args._ticker, "POLY", "SecurityToken doesn't get deployed"); @@ -966,7 +1135,7 @@ contract("SecurityTokenRegistry", accounts => { it("Should change the polytoken address -- failed because of 0x address", async () => { catchRevert( - I_STRProxied.updatePolyTokenAddress("0x0000000000000000000000000000000000000000000", { from: account_polymath }), + I_STRProxied.updatePolyTokenAddress(address_zero, { from: account_polymath }), "tx revert -> failed because 0x address" ); }); @@ -979,23 +1148,83 @@ contract("SecurityTokenRegistry", accounts => { }); }); + describe("Test case for refreshing a security token", async () => { + it("Should fail if msg.sender is not ST owner", async () => { + await catchRevert( + I_STRProxied.refreshSecurityToken("refreshedToken", symbol, "refreshedToken", true, token_owner, { + from: account_delegate + }) + ); + }); + + it("Should fail if ticker is not deployed", async () => { + await catchRevert( + I_STRProxied.refreshSecurityToken("refreshedToken", "LOGLOG3", "refreshedToken", true, token_owner, { + from: token_owner + }) + ); + }); + + it("Should fail if name is 0 length", async () => { + await catchRevert( + I_STRProxied.refreshSecurityToken("", symbol, "refreshedToken", true, token_owner, { + from: token_owner + }) + ); + }); + + it("Should fail if null treasurey wallet", async () => { + await catchRevert( + I_STRProxied.refreshSecurityToken("refreshedToken", symbol, "refreshedToken", true, address_zero, { + from: token_owner + }) + ); + }); + + it("Should fail if transfers not frozen", async () => { + await catchRevert( + I_STRProxied.refreshSecurityToken("refreshedToken", symbol, "refreshedToken", true, token_owner, { + from: token_owner + }) + ); + }); + + it("Should refresh security token", async () => { + let snapid = await takeSnapshot(); + let oldStAddress = await I_Getter.getSecurityTokenAddress(symbol); + let oldSt = await SecurityToken.at(oldStAddress); + await oldSt.freezeTransfers({ from:token_owner }); + let tx = await I_STRProxied.refreshSecurityToken("refreshedToken", symbol, "refreshedToken", true, token_owner, { + from: token_owner + }); + assert.equal(tx.logs[1].args._ticker, symbol, "SecurityToken not deployed"); + let newStAddress = await I_Getter.getSecurityTokenAddress(symbol); + let securityTokenTmp = tx.logs[1].args._securityTokenAddress; + assert.equal(newStAddress, securityTokenTmp, "ST address not updated"); + let newST = await SecurityToken.at(newStAddress); + assert.notEqual(oldStAddress, newStAddress, "new ST not generated"); + assert.equal(await newST.name(), "refreshedToken", "ST not deployed properly"); + await revertToSnapshot(snapid); + }); + }); + describe("Test cases for getters", async () => { it("Should get the security token address", async () => { - let address = await I_STRProxied.getSecurityTokenAddress.call(symbol); + let address = await I_Getter.getSecurityTokenAddress.call(symbol); assert.equal(address, I_SecurityToken.address); }); it("Should get the security token data", async () => { - let data = await I_STRProxied.getSecurityTokenData.call(I_SecurityToken.address); - assert.equal(data[0], symbol); + let data = await I_Getter.getSecurityTokenData.call(I_SecurityToken.address); + assert.equal(data[0], "LOG2"); assert.equal(data[1], token_owner); }); it("Should get the tickers by owner", async () => { - let tickersList = await I_STRProxied.getTickersByOwner.call(token_owner); + let tickersList = await I_Getter.getTickersByOwner.call(token_owner); console.log(tickersList); assert.equal(tickersList.length, 4); - let tickersListArray = await I_STRProxied.getTickersByOwner.call(account_temp); + let tickersListArray = await I_Getter.getTickersByOwner.call(account_temp); console.log(tickersListArray); assert.equal(tickersListArray.length, 3); }); @@ -1003,14 +1232,14 @@ contract("SecurityTokenRegistry", accounts => { describe("Test case for the Removing the ticker", async () => { it("Should remove the ticker from the polymath ecosystem -- bad owner", async () => { - catchRevert( + await catchRevert( I_STRProxied.removeTicker(symbol2, { from: account_investor1 }), "tx revert -> failed because msg.sender should be account_polymath" ); }); it("Should remove the ticker from the polymath ecosystem -- fail because ticker doesn't exist in the ecosystem", async () => { - catchRevert( + await catchRevert( I_STRProxied.removeTicker("HOLA", { from: account_polymath }), "tx revert -> failed because ticker doesn't exist in the polymath ecosystem" ); @@ -1024,84 +1253,76 @@ contract("SecurityTokenRegistry", accounts => { describe(" Test cases of the registerTicker", async () => { it("Should register the ticker 1", async () => { - await I_PolyToken.getTokens(web3.utils.toWei("1000"), account_temp); - await I_PolyToken.approve(I_STRProxied.address, web3.utils.toWei("1000"), { from: account_temp }); - let tx = await I_STRProxied.registerTicker(account_temp, "TOK1", "", { from: account_temp }); + await I_PolyToken.getTokens(new BN(web3.utils.toWei("1600")), account_temp); + await I_PolyToken.approve(I_STRProxied.address, new BN(web3.utils.toWei("1600")), { from: account_temp }); + let tx = await I_STRProxied.registerNewTicker(account_temp, "TOK1", { from: account_temp }); assert.equal(tx.logs[0].args._owner, account_temp, `Owner should be the ${account_temp}`); assert.equal(tx.logs[0].args._ticker, "TOK1", `Symbol should be TOK1`); - console.log((await I_STRProxied.getTickersByOwner.call(account_temp)).map(x => web3.utils.toUtf8(x))); + console.log((await I_Getter.getTickersByOwner.call(account_temp)).map(x => web3.utils.toUtf8(x))); }); it("Should register the ticker 2", async () => { - await I_PolyToken.getTokens(web3.utils.toWei("1000"), account_temp); - await I_PolyToken.approve(I_STRProxied.address, web3.utils.toWei("1000"), { from: account_temp }); - let tx = await I_STRProxied.registerTicker(account_temp, "TOK2", "", { from: account_temp }); + await I_PolyToken.getTokens(new BN(web3.utils.toWei("1600")), account_temp); + await I_PolyToken.approve(I_STRProxied.address, new BN(web3.utils.toWei("1600")), { from: account_temp }); + let tx = await I_STRProxied.registerNewTicker(account_temp, "TOK2", { from: account_temp }); assert.equal(tx.logs[0].args._owner, account_temp, `Owner should be the ${account_temp}`); assert.equal(tx.logs[0].args._ticker, "TOK2", `Symbol should be TOK2`); - console.log((await I_STRProxied.getTickersByOwner.call(account_temp)).map(x => web3.utils.toUtf8(x))); + console.log((await I_Getter.getTickersByOwner.call(account_temp)).map(x => web3.utils.toUtf8(x))); }); it("Should register the ticker 3", async () => { - await I_PolyToken.getTokens(web3.utils.toWei("1000"), account_temp); - await I_PolyToken.approve(I_STRProxied.address, web3.utils.toWei("1000"), { from: account_temp }); - let tx = await I_STRProxied.registerTicker(account_temp, "TOK3", "", { from: account_temp }); + await I_PolyToken.getTokens(new BN(web3.utils.toWei("1600")), account_temp); + await I_PolyToken.approve(I_STRProxied.address, new BN(web3.utils.toWei("1600")), { from: account_temp }); + let tx = await I_STRProxied.registerNewTicker(account_temp, "TOK3", { from: account_temp }); assert.equal(tx.logs[0].args._owner, account_temp, `Owner should be the ${account_temp}`); assert.equal(tx.logs[0].args._ticker, "TOK3", `Symbol should be TOK3`); - console.log((await I_STRProxied.getTickersByOwner.call(account_temp)).map(x => web3.utils.toUtf8(x))); + console.log((await I_Getter.getTickersByOwner.call(account_temp)).map(x => web3.utils.toUtf8(x))); }); it("Should successfully remove the ticker 2", async () => { let tx = await I_STRProxied.removeTicker("TOK2", { from: account_polymath }); assert.equal(tx.logs[0].args._ticker, "TOK2", "Ticker doesn't get deleted successfully"); - console.log((await I_STRProxied.getTickersByOwner.call(account_temp)).map(x => web3.utils.toUtf8(x))); + console.log((await I_Getter.getTickersByOwner.call(account_temp)).map(x => web3.utils.toUtf8(x))); }); it("Should modify ticker 1", async () => { - let tx = await I_STRProxied.modifyTicker( + currentTime = new BN(await latestTime()); + let tx = await I_STRProxied.modifyExistingTicker( account_temp, "TOK1", - "TOKEN 1", - latestTime(), - latestTime() + duration.minutes(10), + currentTime, + currentTime.add(new BN(duration.minutes(10))), false, { from: account_polymath } ); assert.equal(tx.logs[0].args._owner, account_temp, `Should be equal to the ${account_temp}`); assert.equal(tx.logs[0].args._ticker, "TOK1", "Should be equal to TOK1"); - assert.equal(tx.logs[0].args._name, "TOKEN 1", "Should be equal to TOKEN 1"); - console.log((await I_STRProxied.getTickersByOwner.call(account_temp)).map(x => web3.utils.toUtf8(x))); + console.log((await I_Getter.getTickersByOwner.call(account_temp)).map(x => web3.utils.toUtf8(x))); }); it("Should modify ticker 3", async () => { - let tx = await I_STRProxied.modifyTicker( + let tx = await I_STRProxied.modifyExistingTicker( account_temp, "TOK3", - "TOKEN 3", - latestTime(), - latestTime() + duration.minutes(10), + currentTime, + currentTime.add(new BN(duration.minutes(10))), false, { from: account_polymath } ); assert.equal(tx.logs[0].args._owner, account_temp, `Should be equal to the ${account_temp}`); assert.equal(tx.logs[0].args._ticker, "TOK3", "Should be equal to TOK3"); - assert.equal(tx.logs[0].args._name, "TOKEN 3", "Should be equal to TOKEN 3"); - console.log((await I_STRProxied.getTickersByOwner.call(account_temp)).map(x => web3.utils.toUtf8(x))); + console.log((await I_Getter.getTickersByOwner.call(account_temp)).map(x => web3.utils.toUtf8(x))); }); }); describe("Test cases for IRegistry functionality", async () => { describe("Test cases for reclaiming funds", async () => { - - it("Should successfully reclaim POLY tokens -- fail because token address will be 0x", async() => { - I_PolyToken.transfer(I_STRProxied.address, web3.utils.toWei("1"), { from: token_owner }); - catchRevert( - I_STRProxied.reclaimERC20("0x000000000000000000000000000000000000000", { from: account_polymath }) - ); + it("Should successfully reclaim POLY tokens -- fail because token address will be 0x", async () => { + I_PolyToken.transfer(I_STRProxied.address, new BN(web3.utils.toWei("1")), { from: token_owner }); + await catchRevert(I_STRProxied.reclaimERC20(address_zero, { from: account_polymath })); }); - it("Should successfully reclaim POLY tokens -- not authorised", async() => { - catchRevert( - I_STRProxied.reclaimERC20(I_PolyToken.address, { from: account_temp }) - ); + it("Should successfully reclaim POLY tokens -- not authorised", async () => { + await catchRevert(I_STRProxied.reclaimERC20(I_PolyToken.address, { from: account_temp })); }); it("Should successfully reclaim POLY tokens", async () => { @@ -1109,15 +1330,15 @@ contract("SecurityTokenRegistry", accounts => { await I_STRProxied.reclaimERC20(I_PolyToken.address, { from: account_polymath }); let bal2 = await I_PolyToken.balanceOf.call(account_polymath); assert.isAtLeast( - bal2.dividedBy(new BigNumber(10).pow(18)).toNumber(), - bal2.dividedBy(new BigNumber(10).pow(18)).toNumber() + bal2.div(new BN(10).pow(new BN(18))).toNumber(), + bal2.div(new BN(10).pow(new BN(18))).toNumber() ); }); }); describe("Test cases for pausing the contract", async () => { it("Should fail to pause if msg.sender is not owner", async () => { - catchRevert(I_STRProxied.pause({ from: account_temp }), "tx revert -> msg.sender should be account_polymath"); + await catchRevert(I_STRProxied.pause({ from: account_temp }), "tx revert -> msg.sender should be account_polymath"); }); it("Should successfully pause the contract", async () => { @@ -1127,7 +1348,7 @@ contract("SecurityTokenRegistry", accounts => { }); it("Should fail to unpause if msg.sender is not owner", async () => { - catchRevert(I_STRProxied.unpause({ from: account_temp }), "tx revert -> msg.sender should be account_polymath"); + await catchRevert(I_STRProxied.unpause({ from: account_temp }), "tx revert -> msg.sender should be account_polymath"); }); it("Should successfully unpause the contract", async () => { @@ -1137,56 +1358,43 @@ contract("SecurityTokenRegistry", accounts => { }); }); - describe("Test cases for the setProtocolVersion", async() => { - - it("Should successfully change the protocolVersion -- failed because of bad owner", async() => { - catchRevert( - I_STRProxied.setProtocolVersion(accounts[8], 5, 6, 7, { from: account_temp }) - ); + describe("Test cases for the setProtocolVersion", async () => { + it("Should successfully change the protocolVersion -- failed because of bad owner", async () => { + await catchRevert(I_STRProxied.setProtocolFactory(accounts[8], 5, 6, 7, { from: account_temp })); }); - - it("Should successfully change the protocolVersion -- failed because factory address is 0x", async() => { - catchRevert( - I_STRProxied.setProtocolVersion("0x000000000000000000000000000000000000000", 5, 6, 7, { from: account_polymath }) + + it("Should successfully change the protocolVersion -- failed because factory address is 0x", async () => { + await catchRevert( + I_STRProxied.setProtocolFactory(address_zero, 5, 6, 7, { from: account_polymath }) ); }); - - it("Should successfully change the protocolVersion -- not a valid vesrion", async() => { - catchRevert( - I_STRProxied.setProtocolVersion(accounts[8], 0, 0, 0, { from: account_polymath }) - ); + + it("Should successfully change the protocolVersion -- not a valid version", async () => { + await catchRevert(I_STRProxied.setLatestVersion(new BN(0), new BN(0), new BN(0), { from: account_polymath })); }); - it("Should successfully change the protocolVersion -- fail in second attempt because of invalid version", async() => { + it("Should successfully change the protocolVersion -- fail in second attempt because of invalid version", async () => { let snap_Id = await takeSnapshot(); - await I_STRProxied.setProtocolVersion(accounts[8], 2, 3, 1, {from: account_polymath }); - await catchRevert( - I_STRProxied.setProtocolVersion(accounts[8], 1, 3, 1, {from: account_polymath }) - ); + await I_STRProxied.setProtocolFactory(accounts[8], 3, 1, 1, { from: account_polymath }); + await catchRevert(I_STRProxied.setLatestVersion(1, 3, 1, { from: account_polymath })); await revertToSnapshot(snap_Id); }); - }); - describe("Test cases for the transferOwnership", async() => { - - it("Should fail to transfer the ownership -- not authorised", async() => { - catchRevert( - I_STRProxied.transferOwnership(account_temp, { from: account_issuer}) - ); + describe("Test cases for the transferOwnership", async () => { + it("Should fail to transfer the ownership -- not authorised", async () => { + await catchRevert(I_STRProxied.transferOwnership(account_temp, { from: account_issuer })); }); - it("Should fail to transfer the ownership -- 0x address is not allowed", async() => { - catchRevert( - I_STRProxied.transferOwnership("0x000000000000000000000000000000000000000", { from: account_polymath}) - ); + it("Should fail to transfer the ownership -- 0x address is not allowed", async () => { + await catchRevert(I_STRProxied.transferOwnership(address_zero, { from: account_polymath })); }); - it("Should successfully transfer the ownership of the STR", async() => { + it("Should successfully transfer the ownership of the STR", async () => { let tx = await I_STRProxied.transferOwnership(account_temp, { from: account_polymath }); assert.equal(tx.logs[0].args.previousOwner, account_polymath); assert.equal(tx.logs[0].args.newOwner, account_temp); }); - }) + }); }); }); diff --git a/test/o_security_token.js b/test/o_security_token.js index 84aed05f3..3cee1f442 100644 --- a/test/o_security_token.js +++ b/test/o_security_token.js @@ -1,6 +1,7 @@ import latestTime from "./helpers/latestTime"; import { duration, ensureException, promisifyLogWatch, latestBlock } from "./helpers/utils"; -import takeSnapshot, { increaseTime, revertToSnapshot } from "./helpers/time"; +import { getFreezeIssuanceAck, getDisableControllerAck } from "./helpers/signData"; +import { takeSnapshot, increaseTime, revertToSnapshot } from "./helpers/time"; import { encodeProxyCall, encodeModuleCall } from "./helpers/encodeCall"; import { catchRevert } from "./helpers/exceptions"; import { @@ -8,26 +9,33 @@ import { deployGPMAndVerifyed, deployCappedSTOAndVerifyed, deployMockRedemptionAndVerifyed, - deployMockWrongTypeRedemptionAndVerifyed - } from "./helpers/createInstances"; + deployMockWrongTypeRedemptionAndVerifyed, + deployLockUpTMAndVerified +} from "./helpers/createInstances"; +const MockSecurityTokenLogic = artifacts.require("./MockSecurityTokenLogic.sol"); +const MockSTGetter = artifacts.require("./MockSTGetter.sol"); const CappedSTOFactory = artifacts.require("./CappedSTOFactory.sol"); const CappedSTO = artifacts.require("./CappedSTO.sol"); const SecurityToken = artifacts.require("./SecurityToken.sol"); const GeneralTransferManager = artifacts.require("./GeneralTransferManager"); const GeneralPermissionManager = artifacts.require("./GeneralPermissionManager"); const MockRedemptionManager = artifacts.require("./MockRedemptionManager.sol"); +const LockUpTransferManager = artifacts.require("./LockUpTransferManager.sol"); +const STGetter = artifacts.require("./STGetter.sol"); const Web3 = require("web3"); -const BigNumber = require("bignumber.js"); +let BN = Web3.utils.BN; const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); // Hardcoded development port -contract("SecurityToken", accounts => { +contract("SecurityToken", async (accounts) => { // Accounts Variable declaration let account_polymath; let account_investor1; let account_issuer; let token_owner; + let disableControllerAckHash; + let freezeIssuanceAckHash; let account_investor2; let account_investor3; let account_affiliate1; @@ -36,7 +44,8 @@ contract("SecurityToken", accounts => { let account_delegate; let account_temp; let account_controller; - let address_zero = "0x0000000000000000000000000000000000000000"; + const address_zero = "0x0000000000000000000000000000000000000000"; + const one_address = "0x0000000000000000000000000000000000000001"; let balanceOfReceiver; // investor Details @@ -46,9 +55,16 @@ contract("SecurityToken", accounts => { let ID_snap; const message = "Transaction Should Fail!!"; + const uri = "https://www.gogl.bts.fly"; + const docHash = web3.utils.utf8ToHex("hello"); + + const empty_hash = "0x0000000000000000000000000000000000000000000000000000000000000000"; + // Contract Instance Declaration let I_GeneralPermissionManagerFactory; + let I_LockUpTransferManagerFactory; + let I_LockUpTransferManager; let I_SecurityTokenRegistryProxy; let I_GeneralTransferManagerFactory; let I_GeneralPermissionManager; @@ -60,6 +76,7 @@ contract("SecurityToken", accounts => { let I_CappedSTOFactory; let I_STFactory; let I_SecurityToken; + let I_SecurityToken2; let I_STRProxied; let I_MRProxied; let I_CappedSTO; @@ -67,6 +84,10 @@ contract("SecurityToken", accounts => { let I_PolymathRegistry; let I_MockRedemptionManagerFactory; let I_MockRedemptionManager; + let I_STRGetter; + let I_STGetter; + let I_STGetter2; + let stGetter; // SecurityToken Details (Launched ST on the behalf of the issuer) const name = "Demo Token"; @@ -82,25 +103,32 @@ contract("SecurityToken", accounts => { const budget = 0; // Initial fee for ticker registry and security token registry - const initRegFee = web3.utils.toWei("250"); + const initRegFee = new BN(web3.utils.toWei("1000")); // delagate details - const delegateDetails = "I am delegate .."; - const TM_Perm = "FLAGS"; - const TM_Perm_Whitelist = "WHITELIST"; + const delegateDetails = web3.utils.fromAscii("I am delegate .."); + const TM_Perm = web3.utils.fromAscii("ADMIN"); + const TM_Perm_Whitelist = web3.utils.fromAscii("ADMIN"); // Capped STO details let startTime; let endTime; - const cap = web3.utils.toWei("10000"); - const rate = web3.utils.toWei("1000"); + const cap = new BN(web3.utils.toWei("10000")); + const rate = new BN(web3.utils.toWei("1000")); const fundRaiseType = [0]; - const cappedSTOSetupCost = web3.utils.toWei("20000", "ether"); - const maxCost = cappedSTOSetupCost; + const cappedSTOSetupCost = new BN(web3.utils.toWei("20000", "ether")); + const cappedSTOSetupCostPOLY = new BN(web3.utils.toWei("80000", "ether")); + const maxCost = cappedSTOSetupCostPOLY; const STOParameters = ["uint256", "uint256", "uint256", "uint256", "uint8[]", "address"]; + let currentTime; + + async function readStorage(contractAddress, slot) { + return await web3.eth.getStorageAt(contractAddress, slot); + } + before(async () => { - // Accounts setup + currentTime = new BN(await latestTime()); account_polymath = accounts[0]; account_issuer = accounts[1]; account_affiliate1 = accounts[2]; @@ -129,13 +157,17 @@ contract("SecurityToken", accounts => { I_STFactory, I_SecurityTokenRegistry, I_SecurityTokenRegistryProxy, - I_STRProxied + I_STRProxied, + I_STRGetter, + I_STGetter ] = instances; // STEP 2: Deploy the GeneralDelegateManagerFactory - [I_GeneralPermissionManagerFactory] = await deployGPMAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, 0); + [I_GeneralPermissionManagerFactory] = await deployGPMAndVerifyed(account_polymath, I_MRProxied, 0); // STEP 3: Deploy the CappedSTOFactory - [I_CappedSTOFactory] = await deployCappedSTOAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, cappedSTOSetupCost); + [I_CappedSTOFactory] = await deployCappedSTOAndVerifyed(account_polymath, I_MRProxied, cappedSTOSetupCost); + // STEP 4(c): Deploy the LockUpVolumeRestrictionTMFactory + [I_LockUpTransferManagerFactory] = await deployLockUpTMAndVerified(account_polymath, I_MRProxied, 0); // Printing all the contract addresses console.log(` @@ -159,205 +191,311 @@ contract("SecurityToken", accounts => { describe("Generate the SecurityToken", async () => { it("Should register the ticker before the generation of the security token", async () => { await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); - let tx = await I_STRProxied.registerTicker(token_owner, symbol, name, { from: token_owner }); + let tx = await I_STRProxied.registerNewTicker(token_owner, symbol, { from: token_owner }); assert.equal(tx.logs[0].args._owner, token_owner); assert.equal(tx.logs[0].args._ticker, symbol); }); it("Should generate the new security token with the same symbol as registered above", async () => { await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); - let _blockNo = latestBlock(); - let tx = await I_STRProxied.generateSecurityToken(name, symbol, tokenDetails, false, { from: token_owner }); + + let tx = await I_STRProxied.generateNewSecurityToken(name, symbol, tokenDetails, false, token_owner, 0, { from: token_owner }); // Verify the successful generation of the security token + for (let i = 0; i < tx.logs.length; i++) { + console.log("LOGS: " + i); + console.log(tx.logs[i]); + } assert.equal(tx.logs[1].args._ticker, symbol, "SecurityToken doesn't get deployed"); - I_SecurityToken = SecurityToken.at(tx.logs[1].args._securityTokenAddress); - - const log = await promisifyLogWatch(I_SecurityToken.ModuleAdded({ from: _blockNo }), 1); + I_SecurityToken = await SecurityToken.at(tx.logs[1].args._securityTokenAddress); + stGetter = await STGetter.at(I_SecurityToken.address); + assert.equal(await stGetter.getTreasuryWallet.call(), token_owner, "Incorrect wallet set") + const log = (await I_SecurityToken.getPastEvents('ModuleAdded', {filter: {transactionHash: tx.transactionHash}}))[0]; // Verify that GeneralTransferManager module get added successfully or not - console.log(log.args); assert.equal(log.args._types[0].toNumber(), transferManagerKey); assert.equal(web3.utils.toUtf8(log.args._name), "GeneralTransferManager"); + assert.equal(await I_SecurityToken.owner.call(), token_owner); + assert.equal(await I_SecurityToken.initialized.call(), true); + }); + + it("Should not allow unauthorized address to change name", async() => { + await catchRevert(I_SecurityToken.changeName("new token name")); }); - it("Should intialize the auto attached modules", async () => { - let moduleData = (await I_SecurityToken.getModulesByType(transferManagerKey))[0]; - I_GeneralTransferManager = GeneralTransferManager.at(moduleData); + it("Should not allow 0 length name", async() => { + await catchRevert(I_SecurityToken.changeName("", { from: token_owner })); + }); + + it("Should allow authorized address to change name", async() => { + let snapId = await takeSnapshot(); + await I_SecurityToken.changeName("new token name", { from: token_owner }); + assert.equal((await I_SecurityToken.name()).replace(/\u0000/g, ""), "new token name"); + await revertToSnapshot(snapId); + }); + + it("Should initialize the auto attached modules", async () => { + let moduleData = (await stGetter.getModulesByType(transferManagerKey))[0]; + I_GeneralTransferManager = await GeneralTransferManager.at(moduleData); assert.notEqual(I_GeneralTransferManager.address.valueOf(), address_zero, "GeneralTransferManager contract was not deployed"); }); + it("Should failed to change the treasury wallet address -- because of wrong owner", async() => { + await catchRevert( + I_SecurityToken.changeTreasuryWallet(account_fundsReceiver, {from: account_temp}) + ) + }); + + it("Should successfully change the treasury wallet address", async() => { + await I_SecurityToken.changeTreasuryWallet(account_fundsReceiver, {from: token_owner}); + assert.equal(await stGetter.getTreasuryWallet.call(), account_fundsReceiver, "Incorrect wallet set") + }); + it("Should mint the tokens before attaching the STO -- fail only be called by the owner", async () => { - let fromTime = latestTime(); - let toTime = fromTime + duration.days(100); - let expiryTime = toTime + duration.days(100); + currentTime = new BN(await latestTime()); + let toTime = new BN(currentTime.add(new BN(duration.days(100)))); + let expiryTime = new BN(toTime.add(new BN(duration.days(100)))); - let tx = await I_GeneralTransferManager.modifyWhitelist(account_affiliate1, fromTime, toTime, expiryTime, true, { + let tx = await I_GeneralTransferManager.modifyKYCData(account_affiliate1, currentTime, currentTime, expiryTime, { from: token_owner, gas: 6000000 }); assert.equal(tx.logs[0].args._investor, account_affiliate1, "Failed in adding the investor in whitelist"); - await catchRevert(I_SecurityToken.mint(account_investor1, 100 * Math.pow(10, 18), { from: account_delegate })); + await catchRevert(I_SecurityToken.issue(account_investor1, new BN(100).mul(new BN(10).pow(new BN(18))), "0x0", { from: account_delegate })); }); - it("Should mint the tokens before attaching the STO", async () => { - await I_SecurityToken.mint(account_affiliate1, 100 * Math.pow(10, 18), { from: token_owner, gas: 500000 }); + it("Should issue the tokens before attaching the STO", async () => { + await I_SecurityToken.issue(account_affiliate1, new BN(100).mul(new BN(10).pow(new BN(18))), "0x0", { from: token_owner }); let balance = await I_SecurityToken.balanceOf(account_affiliate1); - assert.equal(balance.dividedBy(new BigNumber(10).pow(18)).toNumber(), 100); + assert.equal(balance.div(new BN(10).pow(new BN(18))).toNumber(), 100); }); - it("Should mint the multi tokens before attaching the STO -- fail only be called by the owner", async () => { - let fromTime = latestTime(); - let toTime = fromTime + duration.days(100); - let expiryTime = toTime + duration.days(100); + it("Should issue the multi tokens before attaching the STO -- fail only be called by the owner", async () => { + currentTime = new BN(await latestTime()); + let toTime = new BN(currentTime.add(new BN(duration.days(100)))); + let expiryTime = new BN(toTime.add(new BN(duration.days(100)))); - let tx = await I_GeneralTransferManager.modifyWhitelist(account_affiliate2, fromTime, toTime, expiryTime, true, { + let tx = await I_GeneralTransferManager.modifyKYCData(account_affiliate2, currentTime, currentTime, expiryTime, { from: token_owner, gas: 6000000 }); assert.equal(tx.logs[0].args._investor, account_affiliate2, "Failed in adding the investor in whitelist"); await catchRevert( - I_SecurityToken.mintMulti([account_affiliate1, account_affiliate2], [100 * Math.pow(10, 18), 110 * Math.pow(10, 18)], { + I_SecurityToken.issueMulti([account_affiliate1, account_affiliate2], [new BN(100).mul(new BN(10).pow(new BN(18))), new BN(110).mul(new BN(10).pow(new BN(18)))], { from: account_delegate, gas: 500000 }) ); }); - it("Should mintMulti", async () => { + it("Should check the balance of the locked tokens", async() => { + console.log(`\t Total balance: ${web3.utils.fromWei((await I_SecurityToken.balanceOf.call(account_affiliate1)).toString())}`); + console.log(`\t Locked balance: ${web3.utils.fromWei((await I_SecurityToken.balanceOfByPartition.call(web3.utils.utf8ToHex(`LOCKED`), account_affiliate1)).toString())}`); + console.log(`\t Unlocked balance: ${web3.utils.fromWei((await I_SecurityToken.balanceOfByPartition.call(web3.utils.utf8ToHex(`UNLOCKED`), account_affiliate1)).toString())}`); + assert.equal( + web3.utils.fromWei((await I_SecurityToken.balanceOfByPartition.call(web3.utils.utf8ToHex(`LOCKED`), account_affiliate1)).toString()), + 0 + ); + assert.equal( + web3.utils.fromWei((await I_SecurityToken.balanceOfByPartition.call(web3.utils.utf8ToHex(`UNLOCKED`), account_affiliate1)).toString()), + web3.utils.fromWei((await I_SecurityToken.balanceOf.call(account_affiliate1)).toString()) + ); + console.log(`\t Wrong partition: ${web3.utils.fromWei((await I_SecurityToken.balanceOfByPartition.call(web3.utils.toHex(`OCKED`), account_affiliate1)).toString())}`); + assert.equal( + web3.utils.fromWei((await I_SecurityToken.balanceOfByPartition.call(web3.utils.toHex(`OCKED`), account_affiliate1)).toString()), + 0 + ); + }); + + + it("Should fail due to array length mismatch", async () => { await catchRevert( - I_SecurityToken.mintMulti([account_affiliate1, account_affiliate2], [100 * Math.pow(10, 18)], { + I_SecurityToken.issueMulti([account_affiliate1, account_affiliate2], [new BN(100).mul(new BN(10).pow(new BN(18)))], { from: token_owner, gas: 500000 }) ); }); - it("Should mint the tokens for multiple afiliated investors before attaching the STO", async () => { - await I_SecurityToken.mintMulti([account_affiliate1, account_affiliate2], [100 * Math.pow(10, 18), 110 * Math.pow(10, 18)], { - from: token_owner, - gas: 500000 + it("Should mint to lots of addresses and check gas", async () => { + let id = await takeSnapshot(); + await I_GeneralTransferManager.modifyTransferRequirementsMulti( + [0, 1, 2], + [false, false, false], + [false, false, false], + [false, false, false], + [false, false, false], + { from: token_owner } + ); + let id2 = await takeSnapshot(); + let mockInvestors = []; + let mockAmount = []; + for (let i = 0; i < 40; i++) { + mockInvestors.push("0x1000000000000000000000000000000000000000".substring(0, 42 - i.toString().length) + i.toString()); + mockAmount.push(new BN(10).pow(new BN(18))); + } + + let tx = await I_SecurityToken.issueMulti(mockInvestors, mockAmount, { + from: token_owner }); - let balance1 = await I_SecurityToken.balanceOf(account_affiliate1); - assert.equal(balance1.dividedBy(new BigNumber(10).pow(18)).toNumber(), 200); - let balance2 = await I_SecurityToken.balanceOf(account_affiliate2); - assert.equal(balance2.dividedBy(new BigNumber(10).pow(18)).toNumber(), 110); - }); - it("Should finish the minting -- fail because feature is not activated", async () => { - await catchRevert(I_SecurityToken.freezeMinting({ from: token_owner })); + console.log("Cost for issuing to 40 addresses without checkpoint: " + tx.receipt.gasUsed); + await revertToSnapshot(id2); + + await I_SecurityToken.createCheckpoint({ from: token_owner }); + + tx = await I_SecurityToken.issueMulti(mockInvestors, mockAmount, { + from: token_owner + }); + + console.log("Cost for issuing to 40 addresses with checkpoint: " + tx.receipt.gasUsed); + await revertToSnapshot(id); }); - it("Should finish the minting -- fail to activate the feature because msg.sender is not polymath", async () => { - await catchRevert(I_FeatureRegistry.setFeatureStatus("freezeMintingAllowed", true, { from: token_owner })); + it("Should issue the tokens for multiple afiliated investors before attaching the STO", async () => { + await I_SecurityToken.issueMulti([account_affiliate1, account_affiliate2], [new BN(100).mul(new BN(10).pow(new BN(18))), new BN(110).mul(new BN(10).pow(new BN(18)))], { + from: token_owner + }); + let balance1 = await I_SecurityToken.balanceOf(account_affiliate1); + assert.equal(balance1.div(new BN(10).pow(new BN(18))).toNumber(), 200); + let balance2 = await I_SecurityToken.balanceOf(account_affiliate2); + assert.equal(balance2.div(new BN(10).pow(new BN(18))).toNumber(), 110); + }); - it("Should finish the minting -- successfully activate the feature", async () => { - await catchRevert(I_FeatureRegistry.setFeatureStatus("freezeMintingAllowed", false, { from: account_polymath })); + it("Should ST be issuable", async() => { + assert.isTrue(await stGetter.isIssuable.call()); + }) - assert.equal(false, await I_FeatureRegistry.getFeatureStatus("freezeMintingAllowed", { from: account_temp })); - await I_FeatureRegistry.setFeatureStatus("freezeMintingAllowed", true, { from: account_polymath }); - assert.equal(true, await I_FeatureRegistry.getFeatureStatus("freezeMintingAllowed", { from: account_temp })); - await catchRevert(I_FeatureRegistry.setFeatureStatus("freezeMintingAllowed", true, { from: account_polymath })); + it("Should finish the minting -- fail because owner didn't sign correct acknowledegement", async () => { + let trueButOutOfPlaceAcknowledegement = web3.utils.utf8ToHex( + "F O'Brien is the best!" + ); + await catchRevert(I_SecurityToken.freezeIssuance(trueButOutOfPlaceAcknowledegement, { from: token_owner })); }); - it("Should finish the minting -- fail because msg.sender is not the owner", async () => { - await catchRevert(I_SecurityToken.freezeMinting({ from: account_temp })); + // solidity-coverage uses an older version of testrpc that does not support eth_signTypedData. It is required to signt he acknowledgement + process.env.COVERAGE ? it.skip : it("Should finish the minting -- fail because msg.sender is not the owner", async () => { + freezeIssuanceAckHash = await getFreezeIssuanceAck(I_SecurityToken.address, token_owner); + await catchRevert(I_SecurityToken.freezeIssuance(freezeIssuanceAckHash, { from: account_temp })); }); - it("Should finish minting & restrict the further minting", async () => { + // solidity-coverage uses an older version of testrpc that does not support eth_signTypedData. It is required to signt he acknowledgement + process.env.COVERAGE ? it.skip : it("Should finish minting & restrict the further minting", async () => { let id = await takeSnapshot(); - await I_SecurityToken.freezeMinting({ from: token_owner }); - - await catchRevert(I_SecurityToken.mint(account_affiliate1, 100 * Math.pow(10, 18), { from: token_owner, gas: 500000 })); + await I_SecurityToken.freezeIssuance(freezeIssuanceAckHash, { from: token_owner }); + assert.isFalse(await stGetter.isIssuable.call()); + await catchRevert(I_SecurityToken.issue(account_affiliate1, new BN(100).mul(new BN(10).pow(new BN(18))), "0x0", { from: token_owner, gas: 500000 })); await revertToSnapshot(id); }); it("Should fail to attach the STO factory because not enough poly in contract", async () => { - startTime = latestTime() + duration.seconds(5000); + startTime = await latestTime() + duration.seconds(5000); endTime = startTime + duration.days(30); let bytesSTO = encodeModuleCall(STOParameters, [startTime, endTime, cap, rate, fundRaiseType, account_fundsReceiver]); - - await catchRevert(I_SecurityToken.addModule(I_CappedSTOFactory.address, bytesSTO, maxCost, 0, { from: token_owner })); + await catchRevert(I_SecurityToken.addModule(I_CappedSTOFactory.address, bytesSTO, maxCost, new BN(0), false, { from: token_owner })); }); it("Should fail to attach the STO factory because max cost too small", async () => { - startTime = latestTime() + duration.seconds(5000); + startTime = await latestTime() + duration.seconds(5000); endTime = startTime + duration.days(30); let bytesSTO = encodeModuleCall(STOParameters, [startTime, endTime, cap, rate, fundRaiseType, account_fundsReceiver]); - await I_PolyToken.getTokens(cappedSTOSetupCost, token_owner); - await I_PolyToken.transfer(I_SecurityToken.address, cappedSTOSetupCost, { from: token_owner }); + await I_PolyToken.getTokens(cappedSTOSetupCostPOLY, token_owner); + await I_PolyToken.transfer(I_SecurityToken.address, cappedSTOSetupCostPOLY, { from: token_owner }); await catchRevert( - I_SecurityToken.addModule(I_CappedSTOFactory.address, bytesSTO, web3.utils.toWei("1000", "ether"), 0, { from: token_owner }) + I_SecurityToken.addModule(I_CappedSTOFactory.address, bytesSTO, new BN(web3.utils.toWei("1000", "ether")), new BN(0), false, { from: token_owner }) ); }); + it("Should successfully add module with label", async () => { + let snapId = await takeSnapshot(); + startTime = await latestTime() + duration.seconds(5000); + endTime = startTime + duration.days(30); + let bytesSTO = encodeModuleCall(STOParameters, [startTime, endTime, cap, rate, fundRaiseType, account_fundsReceiver]); + + await I_PolyToken.getTokens(cappedSTOSetupCostPOLY, token_owner); + await I_PolyToken.transfer(I_SecurityToken.address, cappedSTOSetupCostPOLY, { from: token_owner }); + console.log("0"); + const tx = await I_SecurityToken.addModuleWithLabel(I_CappedSTOFactory.address, bytesSTO, maxCost, new BN(0), web3.utils.fromAscii("stofactory"), false, { + from: token_owner + }); + assert.equal(tx.logs[3].args._types[0], stoKey, "CappedSTO doesn't get deployed"); + assert.equal(web3.utils.toUtf8(tx.logs[3].args._name), "CappedSTO", "CappedSTOFactory module was not added"); + console.log("module label is .. " + web3.utils.toAscii(tx.logs[3].args._label)); + assert(web3.utils.toAscii(tx.logs[3].args._label), "stofactory", "label doesnt match"); + I_CappedSTO = await CappedSTO.at(tx.logs[3].args._module); + await revertToSnapshot(snapId); + }); + it("Should successfully attach the STO factory with the security token", async () => { - startTime = latestTime() + duration.seconds(5000); + startTime = await latestTime() + duration.seconds(5000); endTime = startTime + duration.days(30); let bytesSTO = encodeModuleCall(STOParameters, [startTime, endTime, cap, rate, fundRaiseType, account_fundsReceiver]); - await I_PolyToken.getTokens(cappedSTOSetupCost, token_owner); - await I_PolyToken.transfer(I_SecurityToken.address, cappedSTOSetupCost, { from: token_owner }); + await I_PolyToken.getTokens(cappedSTOSetupCostPOLY, token_owner); + await I_PolyToken.transfer(I_SecurityToken.address, cappedSTOSetupCostPOLY, { from: token_owner }); - const tx = await I_SecurityToken.addModule(I_CappedSTOFactory.address, bytesSTO, maxCost, 0, { from: token_owner }); + const tx = await I_SecurityToken.addModule(I_CappedSTOFactory.address, bytesSTO, maxCost, new BN(0), false, { from: token_owner }); assert.equal(tx.logs[3].args._types[0], stoKey, "CappedSTO doesn't get deployed"); assert.equal(web3.utils.toUtf8(tx.logs[3].args._name), "CappedSTO", "CappedSTOFactory module was not added"); - I_CappedSTO = CappedSTO.at(tx.logs[3].args._module); + I_CappedSTO = await CappedSTO.at(tx.logs[3].args._module); }); - it("Should successfully mint tokens while STO attached", async () => { - await I_SecurityToken.mint(account_affiliate1, 100 * Math.pow(10, 18), { from: token_owner, gas: 500000 }); + it("Should successfully issue tokens while STO attached", async () => { + await I_SecurityToken.issue(account_affiliate1, new BN(100).mul(new BN(10).pow(new BN(18))), "0x0", { from: token_owner }); + let balance = await I_SecurityToken.balanceOf(account_affiliate1); - assert.equal(balance.dividedBy(new BigNumber(10).pow(18)).toNumber(), 300); + assert.equal(balance.div(new BN(10).pow(new BN(18))).toNumber(), 300); }); - it("Should fail to mint tokens while STO attached after freezeMinting called", async () => { + // solidity-coverage uses an older version of testrpc that does not support eth_signTypedData. It is required to signt he acknowledgement + process.env.COVERAGE ? it.skip : it("Should fail to issue tokens while STO attached after freezeMinting called", async () => { let id = await takeSnapshot(); - await I_SecurityToken.freezeMinting({ from: token_owner }); + await I_SecurityToken.freezeIssuance(freezeIssuanceAckHash, { from: token_owner }); - await catchRevert(I_SecurityToken.mint(account_affiliate1, 100 * Math.pow(10, 18), { from: token_owner, gas: 500000 })); + await catchRevert(I_SecurityToken.issue(account_affiliate1, new BN(100).mul(new BN(10).pow(new BN(18))), "0x0", { from: token_owner })); await revertToSnapshot(id); }); }); describe("Module related functions", async () => { - it("Should get the modules of the securityToken by index", async () => { - let moduleData = await I_SecurityToken.getModule.call(I_CappedSTO.address); + it(" Should get the modules of the securityToken by name", async () => { + let moduleData = await stGetter.getModule.call(I_CappedSTO.address); assert.equal(web3.utils.toAscii(moduleData[0]).replace(/\u0000/g, ""), "CappedSTO"); assert.equal(moduleData[1], I_CappedSTO.address); assert.equal(moduleData[2], I_CappedSTOFactory.address); assert.equal(moduleData[3], false); assert.equal(moduleData[4][0], 3); + assert.equal(moduleData[5], 0x0000000000000000000000000000000000000000); }); it("Should get the modules of the securityToken by index (not added into the security token yet)", async () => { - let moduleData = await I_SecurityToken.getModule.call(token_owner); + let moduleData = await stGetter.getModule.call(token_owner); assert.equal(web3.utils.toAscii(moduleData[0]).replace(/\u0000/g, ""), ""); assert.equal(moduleData[1], address_zero); }); it("Should get the modules of the securityToken by name", async () => { - let moduleList = await I_SecurityToken.getModulesByName.call("CappedSTO"); + let moduleList = await stGetter.getModulesByName.call(web3.utils.fromAscii("CappedSTO")); assert.isTrue(moduleList.length == 1, "Only one STO"); - let moduleData = await I_SecurityToken.getModule.call(moduleList[0]); + let moduleData = await stGetter.getModule.call(moduleList[0]); assert.equal(web3.utils.toAscii(moduleData[0]).replace(/\u0000/g, ""), "CappedSTO"); assert.equal(moduleData[1], I_CappedSTO.address); }); it("Should get the modules of the securityToken by name (not added into the security token yet)", async () => { - let moduleData = await I_SecurityToken.getModulesByName.call("GeneralPermissionManager"); - assert.isTrue(moduleData.length == 0, "No Permission Manager"); + let moduleData = await stGetter.getModulesByName.call(web3.utils.fromAscii("GeneralPermissionManager")); + assert.isTrue(moduleData.length == new BN(0), "No Permission Manager"); }); it("Should get the modules of the securityToken by name (not added into the security token yet)", async () => { - let moduleData = await I_SecurityToken.getModulesByName.call("CountTransferManager"); - assert.isTrue(moduleData.length == 0, "No Permission Manager"); + let moduleData = await stGetter.getModulesByName.call(web3.utils.fromAscii("CountTransferManager")); + assert.isTrue(moduleData.length == new BN(0), "No Permission Manager"); }); it("Should fail in updating the token details", async () => { @@ -370,7 +508,7 @@ contract("SecurityToken", accounts => { }); it("Should successfully remove the general transfer manager module from the securityToken -- fails msg.sender should be Owner", async () => { - await catchRevert(I_SecurityToken.removeModule(I_GeneralTransferManager.address, { from: token_owner })); + await catchRevert(I_SecurityToken.removeModule(I_GeneralTransferManager.address, { from: account_delegate })); }); it("Should fail to remove the module - module not archived", async () => { @@ -378,7 +516,7 @@ contract("SecurityToken", accounts => { }); it("Should fail to remove the module - incorrect address", async () => { - await catchRevert(I_SecurityToken.removeModule(0, { from: token_owner })); + await catchRevert(I_SecurityToken.removeModule(address_zero, { from: token_owner })); }); it("Should successfully remove the general transfer manager module from the securityToken", async () => { @@ -387,48 +525,44 @@ contract("SecurityToken", accounts => { let tx = await I_SecurityToken.removeModule(I_GeneralTransferManager.address, { from: token_owner }); assert.equal(tx.logs[0].args._types[0], transferManagerKey); assert.equal(tx.logs[0].args._module, I_GeneralTransferManager.address); - await I_SecurityToken.mint(account_investor1, web3.utils.toWei("500"), {from: token_owner}); - await I_SecurityToken.transfer(account_investor2, web3.utils.toWei("200"), {from: account_investor1 }); - assert.equal((await I_SecurityToken.balanceOf(account_investor2)).dividedBy(new BigNumber(10).pow(18)).toNumber(), 200); + await revertToSnapshot(key); }); - it("Should successfully remove the module from the middle of the names mapping", async() => { + it("Should successfully remove the module from the middle of the names mapping", async () => { let snap_Id = await takeSnapshot(); let D_GPM, D_GPM_1, D_GPM_2; let FactoryInstances; let GPMAddress = new Array(); - [D_GPM] = await deployGPMAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, 0); - [D_GPM_1] = await deployGPMAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, 0); - [D_GPM_2] = await deployGPMAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, 0); + [D_GPM] = await deployGPMAndVerifyed(account_polymath, I_MRProxied, 0); + [D_GPM_1] = await deployGPMAndVerifyed(account_polymath, I_MRProxied, 0); + [D_GPM_2] = await deployGPMAndVerifyed(account_polymath, I_MRProxied, 0); FactoryInstances = [D_GPM, D_GPM_1, D_GPM_2]; // Adding module in the ST for (let i = 0; i < FactoryInstances.length; i++) { - let tx = await I_SecurityToken.addModule(FactoryInstances[i].address, "", 0, 0, {from: token_owner }); - assert.equal(tx.logs[2].args._types[0], permissionManagerKey, "fail in adding the GPM") + let tx = await I_SecurityToken.addModule(FactoryInstances[i].address, "0x0", new BN(0), new BN(0), false, { from: token_owner }); + assert.equal(tx.logs[2].args._types[0], permissionManagerKey, "fail in adding the GPM"); GPMAddress.push(tx.logs[2].args._module); } // Archive the one of the module - await I_SecurityToken.archiveModule(GPMAddress[0], {from: token_owner}); + await I_SecurityToken.archiveModule(GPMAddress[0], { from: token_owner }); // Remove the module - let tx = await I_SecurityToken.removeModule(GPMAddress[0], {from: token_owner}); + let tx = await I_SecurityToken.removeModule(GPMAddress[0], { from: token_owner }); assert.equal(tx.logs[0].args._types[0], permissionManagerKey); assert.equal(tx.logs[0].args._module, GPMAddress[0]); await revertToSnapshot(snap_Id); }); - it("Should successfully archive the module first and fail during achiving the module again", async() => { + it("Should successfully archive the module first and fail during achiving the module again", async () => { let key = await takeSnapshot(); await I_SecurityToken.archiveModule(I_GeneralTransferManager.address, { from: token_owner }); - await catchRevert( - I_SecurityToken.archiveModule(I_GeneralTransferManager.address, { from: token_owner }) - ); + await catchRevert(I_SecurityToken.archiveModule(I_GeneralTransferManager.address, { from: token_owner })); await revertToSnapshot(key); }); it("Should verify the revertion of snapshot works properly", async () => { - let moduleData = await I_SecurityToken.getModule.call(I_GeneralTransferManager.address); + let moduleData = await stGetter.getModule.call(I_GeneralTransferManager.address); assert.equal(web3.utils.toAscii(moduleData[0]).replace(/\u0000/g, ""), "GeneralTransferManager"); assert.equal(moduleData[1], I_GeneralTransferManager.address); }); @@ -437,26 +571,22 @@ contract("SecurityToken", accounts => { let tx = await I_SecurityToken.archiveModule(I_GeneralTransferManager.address, { from: token_owner }); assert.equal(tx.logs[0].args._types[0], transferManagerKey); assert.equal(tx.logs[0].args._module, I_GeneralTransferManager.address); - let moduleData = await I_SecurityToken.getModule.call(I_GeneralTransferManager.address); + let moduleData = await stGetter.getModule.call(I_GeneralTransferManager.address); assert.equal(web3.utils.toAscii(moduleData[0]).replace(/\u0000/g, ""), "GeneralTransferManager"); assert.equal(moduleData[1], I_GeneralTransferManager.address); assert.equal(moduleData[2], I_GeneralTransferManagerFactory.address); assert.equal(moduleData[3], true); }); - it("Should successfully mint tokens while GTM archived", async () => { - let key = await takeSnapshot(); - await I_SecurityToken.mint(1, 100 * Math.pow(10, 18), { from: token_owner, gas: 500000 }); - let balance = await I_SecurityToken.balanceOf(1); - assert.equal(balance.dividedBy(new BigNumber(10).pow(18)).toNumber(), 100); - await revertToSnapshot(key); + it("Should fail to issue (or transfer) tokens while all TM are archived archived", async () => { + await catchRevert(I_SecurityToken.issue(one_address, new BN(100).mul(new BN(10).pow(new BN(18))), "0x0", { from: token_owner })); }); it("Should successfully unarchive the general transfer manager module from the securityToken", async () => { let tx = await I_SecurityToken.unarchiveModule(I_GeneralTransferManager.address, { from: token_owner }); assert.equal(tx.logs[0].args._types[0], transferManagerKey); assert.equal(tx.logs[0].args._module, I_GeneralTransferManager.address); - let moduleData = await I_SecurityToken.getModule.call(I_GeneralTransferManager.address); + let moduleData = await stGetter.getModule.call(I_GeneralTransferManager.address); assert.equal(web3.utils.toAscii(moduleData[0]).replace(/\u0000/g, ""), "GeneralTransferManager"); assert.equal(moduleData[1], I_GeneralTransferManager.address); assert.equal(moduleData[2], I_GeneralTransferManagerFactory.address); @@ -464,48 +594,42 @@ contract("SecurityToken", accounts => { }); it("Should successfully unarchive the general transfer manager module from the securityToken -- fail because module is already unarchived", async () => { - await catchRevert( - I_SecurityToken.unarchiveModule(I_GeneralTransferManager.address, { from: token_owner }) - ); + await catchRevert(I_SecurityToken.unarchiveModule(I_GeneralTransferManager.address, { from: token_owner })); }); - it("Should successfully archive the module -- fail because module is not existed", async() => { - await catchRevert( - I_SecurityToken.archiveModule(I_GeneralPermissionManagerFactory.address, { from: token_owner }) - ); - }) + it("Should successfully archive the module -- fail because module is not existed", async () => { + await catchRevert(I_SecurityToken.archiveModule(I_GeneralPermissionManagerFactory.address, { from: token_owner })); + }); - it("Should fail to mint tokens while GTM unarchived", async () => { - await catchRevert(I_SecurityToken.mint(1, 100 * Math.pow(10, 18), { from: token_owner, gas: 500000 })); + it("Should fail to issue tokens while GTM unarchived", async () => { + await catchRevert(I_SecurityToken.issue(one_address, new BN(100).mul(new BN(10).pow(new BN(18))), "0x0", { from: token_owner, gas: 500000 })); }); it("Should change the budget of the module - fail incorrect address", async () => { - await catchRevert(I_SecurityToken.changeModuleBudget(0, 100 * Math.pow(10, 18), true, { from: token_owner })); + await catchRevert(I_SecurityToken.changeModuleBudget(address_zero, new BN(100).mul(new BN(10).pow(new BN(18))), true, { from: token_owner })); }); it("Should change the budget of the module", async () => { let budget = await I_PolyToken.allowance.call(I_SecurityToken.address, I_CappedSTO.address); - let increaseAmount = 100 * Math.pow(10, 18); + let increaseAmount = new BN(100).mul(new BN(10).pow(new BN(18))); let tx = await I_SecurityToken.changeModuleBudget(I_CappedSTO.address, increaseAmount, true, { from: token_owner }); assert.equal(tx.logs[1].args._moduleTypes[0], stoKey); assert.equal(tx.logs[1].args._module, I_CappedSTO.address); - assert.equal(tx.logs[1].args._budget.toNumber(), budget.plus(increaseAmount).toNumber()); + assert.equal(tx.logs[1].args._budget.toString(), budget.add(increaseAmount).toString()); }); - it("Should change the budget of the module (decrease it)", async() => { + it("Should change the budget of the module (decrease it)", async () => { let budget = await I_PolyToken.allowance.call(I_SecurityToken.address, I_CappedSTO.address); - let decreaseAmount = 100 * Math.pow(10, 18); + let decreaseAmount = new BN(100).mul(new BN(10).pow(new BN(18))); let tx = await I_SecurityToken.changeModuleBudget(I_CappedSTO.address, decreaseAmount, false, { from: token_owner }); assert.equal(tx.logs[1].args._moduleTypes[0], stoKey); assert.equal(tx.logs[1].args._module, I_CappedSTO.address); - assert.equal(tx.logs[1].args._budget.toNumber(), budget.minus(decreaseAmount).toNumber()); + assert.equal(tx.logs[1].args._budget.toString(), budget.sub(decreaseAmount).toString()); }); - it("Should fail to get the total supply -- because checkpoint id is greater than present", async() => { - await catchRevert( - I_SecurityToken.totalSupplyAt.call(50) - ); - }) + it("Should fail to get the total supply -- because checkpoint id is greater than present", async () => { + await catchRevert(stGetter.totalSupplyAt.call(50)); + }); }); describe("General Transfer manager Related test cases", async () => { @@ -513,11 +637,11 @@ contract("SecurityToken", accounts => { balanceOfReceiver = await web3.eth.getBalance(account_fundsReceiver); // Add the Investor in to the whitelist - fromTime = latestTime(); - toTime = fromTime + duration.days(100); + fromTime = await latestTime(); + toTime = fromTime; expiryTime = toTime + duration.days(100); - let tx = await I_GeneralTransferManager.modifyWhitelist(account_investor1, fromTime, toTime, expiryTime, true, { + let tx = await I_GeneralTransferManager.modifyKYCData(account_investor1, fromTime, toTime, expiryTime, { from: token_owner, gas: 6000000 }); @@ -530,25 +654,29 @@ contract("SecurityToken", accounts => { from: account_investor1, to: I_CappedSTO.address, gas: 2100000, - value: web3.utils.toWei("1", "ether") + value: new BN(web3.utils.toWei("1", "ether")) }); console.log("AFTER"); - assert.equal((await I_CappedSTO.getRaised.call(0)).dividedBy(new BigNumber(10).pow(18)).toNumber(), 1); + assert.equal((await I_CappedSTO.getRaised.call(0)).div(new BN(10).pow(new BN(18))).toNumber(), 1); assert.equal(await I_CappedSTO.investorCount.call(), 1); - assert.equal((await I_SecurityToken.balanceOf(account_investor1)).dividedBy(new BigNumber(10).pow(18)).toNumber(), 1000); + assert.equal((await I_SecurityToken.balanceOf(account_investor1)).div(new BN(10).pow(new BN(18))).toNumber(), 1000); }); it("Should Fail in transferring the token from one whitelist investor 1 to non whitelist investor 2", async () => { - await catchRevert(I_SecurityToken.transfer(account_investor2, 10 * Math.pow(10, 18), { from: account_investor1 })); + let _canTransfer = await I_SecurityToken.canTransfer.call(account_investor2, new BN(10).mul(new BN(10).pow(new BN(18))), "0x0", {from: account_investor1}); + + assert.equal(_canTransfer[0], 0x50); + + await catchRevert(I_SecurityToken.transfer(account_investor2, new BN(10).mul(new BN(10).pow(new BN(18))), { from: account_investor1 })); }); it("Should fail to provide the permission to the delegate to change the transfer bools -- Bad owner", async () => { // Add permission to the deletgate (A regesteration process) - await I_SecurityToken.addModule(I_GeneralPermissionManagerFactory.address, "", 0, 0, { from: token_owner }); - let moduleData = (await I_SecurityToken.getModulesByType(permissionManagerKey))[0]; - I_GeneralPermissionManager = GeneralPermissionManager.at(moduleData); + await I_SecurityToken.addModule(I_GeneralPermissionManagerFactory.address, "0x0", new BN(0), new BN(0), false, { from: token_owner }); + let moduleData = (await stGetter.getModulesByType(permissionManagerKey))[0]; + I_GeneralPermissionManager = await GeneralPermissionManager.at(moduleData); await catchRevert(I_GeneralPermissionManager.addDelegate(account_delegate, delegateDetails, { from: account_temp })); }); @@ -562,112 +690,264 @@ contract("SecurityToken", accounts => { }); }); - it("Should activate the bool allowAllTransfer", async () => { + it("Should activate allow All Transfer", async () => { ID_snap = await takeSnapshot(); - let tx = await I_GeneralTransferManager.changeAllowAllTransfers(true, { from: account_delegate }); - - assert.isTrue(tx.logs[0].args._allowAllTransfers, "AllowTransfer variable is not successfully updated"); + await I_GeneralTransferManager.modifyTransferRequirementsMulti( + [0, 1, 2], + [false, false, false], + [false, false, false], + [false, false, false], + [false, false, false], + { from: account_delegate } + ); + for (let i = 0; i < 3; i++) { + let transferRestrions = await I_GeneralTransferManager.transferRequirements(i); + assert.equal(transferRestrions[0], false); + assert.equal(transferRestrions[1], false); + assert.equal(transferRestrions[2], false); + assert.equal(transferRestrions[3], false); + } }); it("Should fail to send tokens with the wrong granularity", async () => { - await catchRevert(I_SecurityToken.transfer(accounts[7], Math.pow(10, 17), { from: account_investor1 })); + await catchRevert(I_SecurityToken.transfer(accounts[7], new BN(10).pow(new BN(17)), { from: account_investor1 })); }); - it("Should adjust granularity", async () => { + it("Should not allow 0 granularity", async () => { await catchRevert(I_SecurityToken.changeGranularity(0, { from: token_owner })); }); it("Should adjust granularity", async () => { - await I_SecurityToken.changeGranularity(Math.pow(10, 17), { from: token_owner }); - await I_SecurityToken.transfer(accounts[7], Math.pow(10, 17), { from: account_investor1, gas: 2500000 }); - await I_SecurityToken.transfer(account_investor1, Math.pow(10, 17), { from: accounts[7], gas: 2500000 }); + await I_SecurityToken.changeGranularity(new BN(10).pow(new BN(17)), { from: token_owner }); + await I_SecurityToken.transfer(accounts[7], new BN(10).pow(new BN(17)), { from: account_investor1, gas: 2500000 }); + await I_SecurityToken.transfer(account_investor1, new BN(10).pow(new BN(17)), { from: accounts[7], gas: 2500000 }); + }); + + it("Should not allow unauthorized address to change data store", async () => { + await catchRevert(I_SecurityToken.changeDataStore(one_address, { from: account_polymath })); + }); + + it("Should not allow 0x0 address as data store", async () => { + await catchRevert(I_SecurityToken.changeDataStore(address_zero, { from: token_owner })); + }); + + it("Should change data store", async () => { + let ds = await I_SecurityToken.dataStore(); + await I_SecurityToken.changeDataStore(one_address, { from: token_owner }); + assert.equal(one_address, await I_SecurityToken.dataStore()); + await I_SecurityToken.changeDataStore(ds, { from: token_owner }); }); it("Should transfer from whitelist investor to non-whitelist investor in first tx and in 2nd tx non-whitelist to non-whitelist transfer", async () => { - await I_SecurityToken.transfer(accounts[7], 10 * Math.pow(10, 18), { from: account_investor1, gas: 2500000 }); + await I_SecurityToken.transfer(accounts[7], new BN(10).mul(new BN(10).pow(new BN(18))), { from: account_investor1, gas: 2500000 }); assert.equal( - (await I_SecurityToken.balanceOf(accounts[7])).dividedBy(new BigNumber(10).pow(18)).toNumber(), + (await I_SecurityToken.balanceOf(accounts[7])).div(new BN(10).pow(new BN(18))).toNumber(), 10, "Transfer doesn't take place properly" ); - await I_SecurityToken.transfer(account_temp, 5 * Math.pow(10, 18), { from: accounts[7], gas: 2500000 }); + await I_SecurityToken.transfer(account_temp, new BN(5).mul(new BN(10).pow(new BN(18))), { from: accounts[7], gas: 2500000 }); assert.equal( - (await I_SecurityToken.balanceOf(account_temp)).dividedBy(new BigNumber(10).pow(18)).toNumber(), + (await I_SecurityToken.balanceOf(account_temp)).div(new BN(10).pow(new BN(18))).toNumber(), 5, "Transfer doesn't take place properly" ); await revertToSnapshot(ID_snap); }); - it("Should bool allowAllTransfer value is false", async () => { - assert.isFalse(await I_GeneralTransferManager.allowAllTransfers.call(), "reverting of snapshot doesn't works properly"); - }); - - it("Should change the bool allowAllWhitelistTransfers to true", async () => { + it("Should activate allow All Whitelist Transfers", async () => { ID_snap = await takeSnapshot(); - let tx = await I_GeneralTransferManager.changeAllowAllWhitelistTransfers(true, { from: account_delegate }); + await I_GeneralTransferManager.modifyTransferRequirementsMulti( + [0, 1, 2], + [true, false, true], + [true, true, false], + [false, false, false], + [false, false, false], + { from: account_delegate } + ); + let transferRestrions = await I_GeneralTransferManager.transferRequirements(0); + assert.equal(transferRestrions[0], true); + assert.equal(transferRestrions[1], true); + assert.equal(transferRestrions[2], false); + assert.equal(transferRestrions[3], false); + }); + + it("Should upgrade token logic and getter", async () => { + let mockSTGetter = await MockSTGetter.new({from: account_polymath}); + let mockSecurityTokenLogic = await MockSecurityTokenLogic.new("", "", 0, {from: account_polymath}); + console.log("STL1: " + mockSecurityTokenLogic.address); + const tokenUpgradeBytes = { + name: "upgrade", + type: "function", + inputs: [ + { + type: "address", + name: "_getterDelegate" + }, + { + type: "uint256", + name: "_upgrade" + } + ] + }; + let tokenUpgradeBytesCall = web3.eth.abi.encodeFunctionCall(tokenUpgradeBytes, [mockSTGetter.address, 10]); + + const tokenInitBytes = { + name: "initialize", + type: "function", + inputs: [ + { + type: "address", + name: "_getterDelegate" + }, + { + type: "uint256", + name: "_someValue" + } + ] + }; + let tokenInitBytesCall = web3.eth.abi.encodeFunctionCall(tokenInitBytes, [mockSTGetter.address, 9]); + + await I_STFactory.setLogicContract("3.0.1", mockSecurityTokenLogic.address, tokenInitBytesCall, tokenUpgradeBytesCall, {from: account_polymath}); + // NB - the mockSecurityTokenLogic sets its internal version to 3.0.0 not 3.0.1 + let tx = await I_SecurityToken.upgradeToken({from: token_owner, gas: 7000000}); + assert.equal(tx.logs[0].args._major, 3); + assert.equal(tx.logs[0].args._minor, 0); + assert.equal(tx.logs[0].args._patch, 0); + let newToken = await MockSecurityTokenLogic.at(I_SecurityToken.address); + let newGetter = await MockSTGetter.at(I_SecurityToken.address); + tx = await newToken.newFunction(11); + assert.equal(tx.logs[0].args._upgrade, 11); + tx = await newGetter.newGetter(12); + assert.equal(tx.logs[0].args._upgrade, 12); + console.log((await newToken.someValue.call())); + assert.equal((await newToken.someValue.call()).toNumber(), 10); + }); + + it("Should update token logic and getter", async () => { + let mockSTGetter = await MockSTGetter.new({from: account_polymath}); + let mockSecurityTokenLogic = await MockSecurityTokenLogic.new("", "", 0, {from: account_polymath}); + console.log("STL2: " + mockSecurityTokenLogic.address); + const tokenUpgradeBytes = { + name: "upgrade", + type: "function", + inputs: [ + { + type: "address", + name: "_getterDelegate" + }, + { + type: "uint256", + name: "_upgrade" + } + ] + }; + let tokenUpgradeBytesCall = web3.eth.abi.encodeFunctionCall(tokenUpgradeBytes, [mockSTGetter.address, 12]); + + const tokenInitBytes = { + name: "initialize", + type: "function", + inputs: [ + { + type: "address", + name: "_getterDelegate" + }, + { + type: "uint256", + name: "_someValue" + } + ] + }; + let tokenInitBytesCall = web3.eth.abi.encodeFunctionCall(tokenInitBytes, [mockSTGetter.address, 11]); + + await I_STFactory.updateLogicContract(2, "3.0.1", mockSecurityTokenLogic.address, tokenInitBytesCall, tokenUpgradeBytesCall, {from: account_polymath}); + // assert.equal(0,1); + }); + + it("Should deploy new upgraded token", async () => { + + const symbolUpgrade = "DETU"; + const nameUpgrade = "Demo Upgrade"; + await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); + let tx = await I_STRProxied.registerTicker(token_owner, symbolUpgrade, nameUpgrade, { from: token_owner }); + assert.equal(tx.logs[0].args._owner, token_owner); + assert.equal(tx.logs[0].args._ticker, symbolUpgrade); + + await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); + let tokenTx = await I_STRProxied.generateNewSecurityToken(name, symbolUpgrade, tokenDetails, false, token_owner, 0, { from: token_owner }); + // Verify the successful generation of the security token + for (let i = 0; i < tokenTx.logs.length; i++) { + console.log("LOGS: " + i); + console.log(tx.logs[i]); + } + assert.equal(tokenTx.logs[1].args._ticker, symbolUpgrade, "SecurityToken doesn't get deployed"); + + I_SecurityToken2 = await MockSecurityTokenLogic.at(tokenTx.logs[1].args._securityTokenAddress); + I_STGetter2 = await MockSTGetter.at(I_SecurityToken2.address); + assert.equal(await I_STGetter2.getTreasuryWallet.call(), token_owner, "Incorrect wallet set") + assert.equal(await I_SecurityToken2.owner.call(), token_owner); + assert.equal(await I_SecurityToken2.initialized.call(), true); + assert.equal((await I_SecurityToken2.someValue.call()).toNumber(), 11); - assert.isTrue(tx.logs[0].args._allowAllWhitelistTransfers, "allowAllWhitelistTransfers variable is not successfully updated"); }); it("Should transfer from whitelist investor1 to whitelist investor 2", async () => { - let tx = await I_GeneralTransferManager.modifyWhitelist(account_investor2, fromTime, toTime, expiryTime, true, { + let tx = await I_GeneralTransferManager.modifyKYCData(account_investor2, fromTime, toTime, expiryTime, { from: token_owner, gas: 500000 }); assert.equal(tx.logs[0].args._investor, account_investor2, "Failed in adding the investor in whitelist"); - await I_SecurityToken.transfer(account_investor2, 10 * Math.pow(10, 18), { from: account_investor1, gas: 2500000 }); + await I_SecurityToken.transfer(account_investor2, new BN(10).mul(new BN(10).pow(new BN(18))), { from: account_investor1, gas: 2500000 }); assert.equal( - (await I_SecurityToken.balanceOf(account_investor2)).dividedBy(new BigNumber(10).pow(18)).toNumber(), + (await I_SecurityToken.balanceOf(account_investor2)).div(new BN(10).pow(new BN(18))).toNumber(), 10, "Transfer doesn't take place properly" ); }); it("Should transfer from whitelist investor1 to whitelist investor 2 -- value = 0", async () => { - let tx = await I_SecurityToken.transfer(account_investor2, 0, { from: account_investor1, gas: 2500000 }); + let tx = await I_SecurityToken.transfer(account_investor2, new BN(0), { from: account_investor1, gas: 2500000 }); assert.equal(tx.logs[0].args.value.toNumber(), 0); }); it("Should transferFrom from one investor to other", async () => { - await I_SecurityToken.approve(account_investor1, 2 * Math.pow(10, 18), { from: account_investor2 }); - let tx = await I_GeneralTransferManager.modifyWhitelist(account_investor3, fromTime, toTime, expiryTime, true, { + await I_SecurityToken.approve(account_investor1, new BN(2).mul(new BN(10).pow(new BN(18))), { from: account_investor2 }); + let tx = await I_GeneralTransferManager.modifyKYCData(account_investor3, fromTime, toTime, expiryTime, { from: token_owner, gas: 500000 }); assert.equal(tx.logs[0].args._investor, account_investor3, "Failed in adding the investor in whitelist"); - let log = await I_SecurityToken.transferFrom(account_investor2, account_investor3, 2 * Math.pow(10, 18), { + let log = await I_SecurityToken.transferFrom(account_investor2, account_investor3, new BN(2).mul(new BN(10).pow(new BN(18))), { from: account_investor1 }); - assert.equal(log.logs[0].args.value.toNumber(), 2 * Math.pow(10, 18)); + assert.equal(log.logs[0].args.value.toString(), new BN(2).mul(new BN(10).pow(new BN(18))).toString()); }); it("Should Fail in trasferring from whitelist investor1 to non-whitelist investor", async () => { - await catchRevert(I_SecurityToken.transfer(account_temp, 10 * Math.pow(10, 18), { from: account_investor1, gas: 2500000 })); + await catchRevert(I_SecurityToken.transfer(account_temp, new BN(10).mul(new BN(10).pow(new BN(18))), { from: account_investor1, gas: 2500000 })); await revertToSnapshot(ID_snap); }); - it("Should successfully mint tokens while STO attached", async () => { - await I_SecurityToken.mint(account_affiliate1, 100 * Math.pow(10, 18), { from: token_owner, gas: 500000 }); + it("Should successfully issue tokens while STO attached", async () => { + await I_SecurityToken.issue(account_affiliate1, new BN(100).mul(new BN(10).pow(new BN(18))), "0x0", { from: token_owner }); let balance = await I_SecurityToken.balanceOf(account_affiliate1); - assert.equal(balance.dividedBy(new BigNumber(10).pow(18)).toNumber(), 400); + assert.equal(balance.div(new BN(10).pow(new BN(18))).toNumber(), 400); }); - it("Should mint the tokens for multiple afiliated investors while STO attached", async () => { - await I_SecurityToken.mintMulti([account_affiliate1, account_affiliate2], [100 * Math.pow(10, 18), 110 * Math.pow(10, 18)], { - from: token_owner, - gas: 500000 + it("Should issue the tokens for multiple afiliated investors while STO attached", async () => { + await I_SecurityToken.issueMulti([account_affiliate1, account_affiliate2], [new BN(100).mul(new BN(10).pow(new BN(18))), new BN(110).mul(new BN(10).pow(new BN(18)))], { + from: token_owner }); let balance1 = await I_SecurityToken.balanceOf(account_affiliate1); - assert.equal(balance1.dividedBy(new BigNumber(10).pow(18)).toNumber(), 500); + assert.equal(balance1.div(new BN(10).pow(new BN(18))).toNumber(), 500); let balance2 = await I_SecurityToken.balanceOf(account_affiliate2); - assert.equal(balance2.dividedBy(new BigNumber(10).pow(18)).toNumber(), 220); + assert.equal(balance2.div(new BN(10).pow(new BN(18))).toNumber(), 220); + }); it("Should provide more permissions to the delegate", async () => { @@ -682,7 +962,7 @@ contract("SecurityToken", accounts => { }); it("Should add the investor in the whitelist by the delegate", async () => { - let tx = await I_GeneralTransferManager.modifyWhitelist(account_temp, fromTime, toTime, expiryTime, true, { + let tx = await I_GeneralTransferManager.modifyKYCData(account_temp, fromTime, toTime, expiryTime, { from: account_delegate, gas: 6000000 }); @@ -696,33 +976,34 @@ contract("SecurityToken", accounts => { from: account_temp, to: I_CappedSTO.address, gas: 2100000, - value: web3.utils.toWei("1", "ether") + value: new BN(web3.utils.toWei("1", "ether")) }); - assert.equal((await I_CappedSTO.getRaised.call(0)).dividedBy(new BigNumber(10).pow(18)).toNumber(), 2); + assert.equal((await I_CappedSTO.getRaised.call(0)).div(new BN(10).pow(new BN(18))).toNumber(), 2); assert.equal(await I_CappedSTO.investorCount.call(), 2); - assert.equal((await I_SecurityToken.balanceOf(account_investor1)).dividedBy(new BigNumber(10).pow(18)).toNumber(), 1000); + assert.equal((await I_SecurityToken.balanceOf(account_investor1)).div(new BN(10).pow(new BN(18))).toNumber(), 1000); }); - it("STO should fail to mint tokens after minting is frozen", async () => { + // solidity-coverage uses an older version of testrpc that does not support eth_signTypedData. It is required to signt he acknowledgement + process.env.COVERAGE ? it.skip : it("STO should fail to issue tokens after minting is frozen", async () => { let id = await takeSnapshot(); - await I_SecurityToken.freezeMinting({ from: token_owner }); + await I_SecurityToken.freezeIssuance(freezeIssuanceAckHash, { from: token_owner }); await catchRevert( web3.eth.sendTransaction({ from: account_temp, to: I_CappedSTO.address, gas: 2100000, - value: web3.utils.toWei("1", "ether") + value: new BN(web3.utils.toWei("1", "ether")) }) ); await revertToSnapshot(id); }); it("Should remove investor from the whitelist by the delegate", async () => { - let tx = await I_GeneralTransferManager.modifyWhitelist(account_temp, 0, 0, 0, true, { + let tx = await I_GeneralTransferManager.modifyKYCData(account_temp, new BN(0), new BN(0), new BN(0), { from: account_delegate, gas: 6000000 }); @@ -736,7 +1017,7 @@ contract("SecurityToken", accounts => { from: account_temp, to: I_CappedSTO.address, gas: 2100000, - value: web3.utils.toWei("1", "ether") + value: new BN(web3.utils.toWei("1", "ether")) }) ); }); @@ -751,7 +1032,7 @@ contract("SecurityToken", accounts => { }); it("Should fail in buying to tokens", async () => { - let tx = await I_GeneralTransferManager.modifyWhitelist(account_temp, fromTime, toTime, expiryTime, true, { + let tx = await I_GeneralTransferManager.modifyKYCData(account_temp, fromTime, toTime, expiryTime, { from: account_delegate, gas: 6000000 }); @@ -763,16 +1044,13 @@ contract("SecurityToken", accounts => { from: account_temp, to: I_CappedSTO.address, gas: 2100000, - value: web3.utils.toWei("1", "ether") + value: new BN(web3.utils.toWei("1", "ether")) }) ); }); it("Should fail in trasfering the tokens from one user to another", async () => { - await I_GeneralTransferManager.changeAllowAllWhitelistTransfers(true, { from: token_owner }); - console.log(await I_SecurityToken.balanceOf(account_investor1)); - - await catchRevert(I_SecurityToken.transfer(account_investor1, web3.utils.toWei("1", "ether"), { from: account_temp })); + await catchRevert(I_SecurityToken.transfer(account_investor1, new BN(web3.utils.toWei("1", "ether")), { from: account_temp })); }); it("Should unfreeze all the transfers", async () => { @@ -785,13 +1063,13 @@ contract("SecurityToken", accounts => { }); it("Should able to transfers the tokens from one user to another", async () => { - await I_SecurityToken.transfer(account_investor1, web3.utils.toWei("1", "ether"), { from: account_temp }); + await I_SecurityToken.transfer(account_investor1, new BN(web3.utils.toWei("1", "ether")), { from: account_temp }); }); it("Should check that the list of investors is correct", async () => { // Hardcode list of expected accounts based on transfers above - let investors = await I_SecurityToken.getInvestors(); + let investors = await stGetter.getInvestors.call(); let expectedAccounts = [account_affiliate1, account_affiliate2, account_investor1, account_temp]; for (let i = 0; i < expectedAccounts.length; i++) { assert.equal(investors[i], expectedAccounts[i]); @@ -828,38 +1106,51 @@ contract("SecurityToken", accounts => { assert.equal(account_controller, controller, "Status not set correctly"); }); + it("Should ST be the controllable", async() => { + assert.isTrue(await I_SecurityToken.isControllable.call()); + }); + it("Should force burn the tokens - value too high", async () => { - await I_GeneralTransferManager.changeAllowAllBurnTransfers(true, { from: token_owner }); - let currentInvestorCount = await I_SecurityToken.getInvestorCount.call(); + await I_GeneralTransferManager.modifyTransferRequirementsMulti( + [0, 1, 2], + [true, false, false], + [true, true, false], + [true, false, false], + [true, false, false], + { from: account_delegate } + ); let currentBalance = await I_SecurityToken.balanceOf(account_temp); await catchRevert( - I_SecurityToken.forceBurn(account_temp, currentBalance + web3.utils.toWei("500", "ether"), "", "", { + I_SecurityToken.controllerRedeem(account_temp, currentBalance + new BN(web3.utils.toWei("500", "ether")), "0x0", "0x0", { from: account_controller }) ); }); it("Should force burn the tokens - wrong caller", async () => { - await I_GeneralTransferManager.changeAllowAllBurnTransfers(true, { from: token_owner }); - let currentInvestorCount = await I_SecurityToken.getInvestorCount.call(); let currentBalance = await I_SecurityToken.balanceOf(account_temp); - await catchRevert(I_SecurityToken.forceBurn(account_temp, currentBalance, "", "", { from: token_owner })); + let investors = await stGetter.getInvestors.call(); + for (let i = 0; i < investors.length; i++) { + console.log(investors[i]); + console.log(web3.utils.fromWei((await I_SecurityToken.balanceOf(investors[i])).toString())); + } + await catchRevert(I_SecurityToken.controllerRedeem(account_temp, currentBalance, "0x0", "0x0", { from: token_owner })); }); it("Should burn the tokens", async () => { - let currentInvestorCount = await I_SecurityToken.getInvestorCount.call(); + let currentInvestorCount = await I_SecurityToken.holderCount.call(); let currentBalance = await I_SecurityToken.balanceOf(account_temp); - // console.log(currentInvestorCount.toString(), currentBalance.toString()); - let tx = await I_SecurityToken.forceBurn(account_temp, currentBalance, "", "", { from: account_controller }); - // console.log(tx.logs[0].args._value.toNumber(), currentBalance.toNumber()); - assert.equal(tx.logs[0].args._value.toNumber(), currentBalance.toNumber()); - let newInvestorCount = await I_SecurityToken.getInvestorCount.call(); + let investors = await stGetter.getInvestors.call(); + let tx = await I_SecurityToken.controllerRedeem(account_temp, currentBalance, "0x0", "0x0", { from: account_controller }); + // console.log(tx.logs[1].args._value.toNumber(), currentBalance.toNumber()); + assert.equal(tx.logs[1].args._value.toString(), currentBalance.toString()); + let newInvestorCount = await I_SecurityToken.holderCount.call(); // console.log(newInvestorCount.toString()); assert.equal(newInvestorCount.toNumber() + 1, currentInvestorCount.toNumber(), "Investor count drops by one"); }); it("Should use getInvestorsAt to determine balances now", async () => { await I_SecurityToken.createCheckpoint({ from: token_owner }); - let investors = await I_SecurityToken.getInvestorsAt.call(1); + let investors = await stGetter.getInvestorsAt.call(1); console.log("Filtered investors:" + investors); let expectedAccounts = [account_affiliate1, account_affiliate2, account_investor1]; for (let i = 0; i < expectedAccounts.length; i++) { @@ -871,272 +1162,1060 @@ contract("SecurityToken", accounts => { it("Should prune investor length test #2", async () => { let balance = await I_SecurityToken.balanceOf(account_affiliate2); let balance2 = await I_SecurityToken.balanceOf(account_investor1); - await I_SecurityToken.transfer(account_affiliate1, balance, { from: account_affiliate2}); - await I_SecurityToken.transfer(account_affiliate1, balance2, { from: account_investor1}); + await I_SecurityToken.transfer(account_affiliate1, balance, { from: account_affiliate2 }); + await I_SecurityToken.transfer(account_affiliate1, balance2, { from: account_investor1 }); await I_SecurityToken.createCheckpoint({ from: token_owner }); - let investors = await I_SecurityToken.getInvestors.call(); + let investors = await stGetter.getInvestors.call(); console.log("All investors:" + investors); let expectedAccounts = [account_affiliate1, account_affiliate2, account_investor1, account_temp]; for (let i = 0; i < expectedAccounts.length; i++) { assert.equal(investors[i], expectedAccounts[i]); } assert.equal(investors.length, 4); - investors = await I_SecurityToken.getInvestorsAt.call(2); + investors = await stGetter.getInvestorsAt.call(2); console.log("Filtered investors:" + investors); expectedAccounts = [account_affiliate1]; for (let i = 0; i < expectedAccounts.length; i++) { assert.equal(investors[i], expectedAccounts[i]); } assert.equal(investors.length, 1); - await I_SecurityToken.transfer(account_affiliate2, balance, { from: account_affiliate1}); - await I_SecurityToken.transfer(account_investor1, balance2, { from: account_affiliate1}); + await I_SecurityToken.transfer(account_affiliate2, balance, { from: account_affiliate1 }); + await I_SecurityToken.transfer(account_investor1, balance2, { from: account_affiliate1 }); }); it("Should get filtered investors", async () => { - let investors = await I_SecurityToken.getInvestors.call(); + let investors = await stGetter.getInvestors.call(); console.log("All Investors: " + investors); - let filteredInvestors = await I_SecurityToken.iterateInvestors.call(0, 1); - console.log("Filtered Investors (0, 1): " + filteredInvestors); + let filteredInvestors = await stGetter.iterateInvestors.call(0, 0); + console.log("Filtered Investors (0, 0): " + filteredInvestors); assert.equal(filteredInvestors[0], investors[0]); assert.equal(filteredInvestors.length, 1); - filteredInvestors = await I_SecurityToken.iterateInvestors.call(2, 4); - console.log("Filtered Investors (2, 4): " + filteredInvestors); + filteredInvestors = await stGetter.iterateInvestors.call(2, 3); + console.log("Filtered Investors (2, 3): " + filteredInvestors); assert.equal(filteredInvestors[0], investors[2]); assert.equal(filteredInvestors[1], investors[3]); assert.equal(filteredInvestors.length, 2); - filteredInvestors = await I_SecurityToken.iterateInvestors.call(0, 4); - console.log("Filtered Investors (0, 4): " + filteredInvestors); + filteredInvestors = await stGetter.iterateInvestors.call(0, 3); + console.log("Filtered Investors (0, 3): " + filteredInvestors); assert.equal(filteredInvestors[0], investors[0]); assert.equal(filteredInvestors[1], investors[1]); assert.equal(filteredInvestors[2], investors[2]); assert.equal(filteredInvestors[3], investors[3]); assert.equal(filteredInvestors.length, 4); - await catchRevert( - I_SecurityToken.iterateInvestors(0, 5) - ); }); it("Should check the balance of investor at checkpoint", async () => { - await catchRevert(I_SecurityToken.balanceOfAt(account_investor1, 5)); + await catchRevert(stGetter.balanceOfAt(account_investor1, 5)); }); it("Should check the balance of investor at checkpoint", async () => { - let balance = await I_SecurityToken.balanceOfAt(account_investor1, 0); + let balance = await stGetter.balanceOfAt(account_investor1, 0); assert.equal(balance.toNumber(), 0); }); }); - describe("Test cases for the Mock TrackedRedeemption", async() => { - - it("Should add the tracked redeemption module successfully", async() => { - [I_MockRedemptionManagerFactory] = await deployMockRedemptionAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, 0); - let tx = await I_SecurityToken.addModule(I_MockRedemptionManagerFactory.address, "", 0, 0, {from: token_owner }); + describe("Test cases for the Mock TrackedRedeemption", async () => { + it("Should add the tracked redeemption module successfully", async () => { + [I_MockRedemptionManagerFactory] = await deployMockRedemptionAndVerifyed(account_polymath, I_MRProxied, 0); + let tx = await I_SecurityToken.addModule(I_MockRedemptionManagerFactory.address, "0x0", new BN(0), new BN(0), false, { from: token_owner }); assert.equal(tx.logs[2].args._types[0], burnKey, "fail in adding the burn manager"); - I_MockRedemptionManager = MockRedemptionManager.at(tx.logs[2].args._module); + I_MockRedemptionManager = await MockRedemptionManager.at(tx.logs[2].args._module); // adding the burn module into the GTM - tx = await I_GeneralTransferManager.modifyWhitelist( + currentTime = new BN(await latestTime()); + tx = await I_GeneralTransferManager.modifyKYCData( I_MockRedemptionManager.address, - latestTime(), - latestTime() + duration.seconds(2), - latestTime() + duration.days(50), - true, + currentTime, + currentTime, + currentTime.add(new BN(duration.days(50))), { - from: account_delegate, - gas: 6000000 + from: account_delegate, + gas: 6000000 } ); assert.equal(tx.logs[0].args._investor, I_MockRedemptionManager.address, "Failed in adding the investor in whitelist"); }); - it("Should successfully burn tokens", async() => { - await I_GeneralTransferManager.changeAllowAllWhitelistTransfers(false, {from: token_owner}); + it("Should successfully burn tokens", async () => { // Minting some tokens - await I_SecurityToken.mint(account_investor1, web3.utils.toWei("1000"), {from: token_owner}); + await I_SecurityToken.issue(account_investor1, new BN(web3.utils.toWei("1000")), "0x0", { from: token_owner }); // Provide approval to trnafer the tokens to Module - await I_SecurityToken.approve(I_MockRedemptionManager.address, web3.utils.toWei("500"), {from: account_investor1}); - // Allow all whitelist transfer - await I_GeneralTransferManager.changeAllowAllWhitelistTransfers(true, {from: token_owner}); + await I_SecurityToken.approve(I_MockRedemptionManager.address, new BN(web3.utils.toWei("500")), { from: account_investor1 }); // Transfer the tokens to module (Burn) - await I_MockRedemptionManager.transferToRedeem(web3.utils.toWei("500"), { from: account_investor1}); + await I_MockRedemptionManager.transferToRedeem(new BN(web3.utils.toWei("500")), { from: account_investor1 }); // Redeem tokens - let tx = await I_MockRedemptionManager.redeemTokenByOwner(web3.utils.toWei("250"), {from: account_investor1}); + let tx = await I_MockRedemptionManager.redeemTokenByOwner(new BN(web3.utils.toWei("250")), { from: account_investor1 }); assert.equal(tx.logs[0].args._investor, account_investor1, "Burn tokens of wrong owner"); - assert.equal((tx.logs[0].args._value).dividedBy(new BigNumber(10).pow(18)).toNumber(), 250); + assert.equal(tx.logs[0].args._value.div(new BN(10).pow(new BN(18))).toNumber(), 250); }); - it("Should fail to burn the tokens because module get archived", async() => { - await I_SecurityToken.archiveModule(I_MockRedemptionManager.address, {from: token_owner}); - await catchRevert( - I_MockRedemptionManager.redeemTokenByOwner(web3.utils.toWei("250"), {from: account_investor1}) - ); - }) + it("Should fail to burn the tokens because module get archived", async () => { + await I_SecurityToken.archiveModule(I_MockRedemptionManager.address, { from: token_owner }); + console.log(await stGetter.getModule.call(I_MockRedemptionManager.address)); + await catchRevert(I_MockRedemptionManager.redeemTokenByOwner(new BN(web3.utils.toWei("250")), { from: account_investor1 })); + }); - it("Should successfully fail in calling the burn functions", async() => { - [I_MockRedemptionManagerFactory] = await deployMockWrongTypeRedemptionAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, 0); - let tx = await I_SecurityToken.addModule(I_MockRedemptionManagerFactory.address, "", 0, 0, {from: token_owner }); - I_MockRedemptionManager = MockRedemptionManager.at(tx.logs[2].args._module); + it("Should successfully fail in calling the burn functions", async () => { + [I_MockRedemptionManagerFactory] = await deployMockWrongTypeRedemptionAndVerifyed(account_polymath, I_MRProxied, 0); + let tx = await I_SecurityToken.addModule(I_MockRedemptionManagerFactory.address, "0x0", new BN(0), new BN(0), false, { from: token_owner }); + let I_MockRedemptionManagerWrong = await MockRedemptionManager.at(tx.logs[2].args._module); - // adding the burn module into the GTM - tx = await I_GeneralTransferManager.modifyWhitelist( - I_MockRedemptionManager.address, - latestTime(), - latestTime() + duration.seconds(2), - latestTime() + duration.days(50), - true, + // adding the burn module into the GTM + currentTime = new BN(await latestTime()); + tx = await I_GeneralTransferManager.modifyKYCData( + I_MockRedemptionManagerWrong.address, + 1, + 1, + currentTime.add(new BN(duration.days(50))), { - from: account_delegate, - gas: 6000000 + from: account_delegate, + gas: 6000000 } ); - assert.equal(tx.logs[0].args._investor, I_MockRedemptionManager.address, "Failed in adding the investor in whitelist"); + assert.equal(tx.logs[0].args._investor, I_MockRedemptionManagerWrong.address, "Failed in adding the investor in whitelist"); // Provide approval to trnafer the tokens to Module - await I_SecurityToken.approve(I_MockRedemptionManager.address, web3.utils.toWei("500"), {from: account_investor1}); + await I_SecurityToken.approve(I_MockRedemptionManagerWrong.address, new BN(web3.utils.toWei("500")), { from: account_investor1 }); // Transfer the tokens to module (Burn) - await I_MockRedemptionManager.transferToRedeem(web3.utils.toWei("500"), { from: account_investor1}); + await I_MockRedemptionManagerWrong.transferToRedeem(new BN(web3.utils.toWei("500")), { from: account_investor1 }); await catchRevert( // Redeem tokens - I_MockRedemptionManager.redeemTokenByOwner(web3.utils.toWei("250"), {from: account_investor1}) + I_MockRedemptionManagerWrong.redeemTokenByOwner(new BN(web3.utils.toWei("250")), { from: account_investor1 }) ); }); - - }) + }); describe("Withdraw Poly", async () => { - it("Should successfully withdraw the poly -- failed because of zero address of token", async() => { - await catchRevert(I_SecurityToken.withdrawERC20("0x00000000000000000000000000000000000000000", web3.utils.toWei("20000", "ether"), { from: account_temp })); - }) + it("Should successfully withdraw the poly -- failed because of zero address of token", async () => { + await catchRevert( + I_SecurityToken.withdrawERC20(address_zero, new BN(web3.utils.toWei("20000", "ether")), { + from: account_temp + }) + ); + }); it("Should successfully withdraw the poly", async () => { - await catchRevert(I_SecurityToken.withdrawERC20(I_PolyToken.address, web3.utils.toWei("20000", "ether"), { from: account_temp })); + await catchRevert( + I_SecurityToken.withdrawERC20(I_PolyToken.address, new BN(web3.utils.toWei("20000", "ether")), { from: account_temp }) + ); }); it("Should successfully withdraw the poly", async () => { let balanceBefore = await I_PolyToken.balanceOf(token_owner); - await I_SecurityToken.withdrawERC20(I_PolyToken.address, web3.utils.toWei("20000", "ether"), { from: token_owner }); + let stBalance = await I_PolyToken.balanceOf(I_SecurityToken.address); + await I_SecurityToken.withdrawERC20(I_PolyToken.address, new BN(stBalance), { from: token_owner }); let balanceAfter = await I_PolyToken.balanceOf(token_owner); assert.equal( - BigNumber(balanceAfter) - .sub(new BigNumber(balanceBefore)) - .toNumber(), - web3.utils.toWei("20000", "ether") + BN(balanceAfter) + .sub(new BN(balanceBefore)) + .toString(), + stBalance.toString() ); }); it("Should successfully withdraw the poly", async () => { - await catchRevert(I_SecurityToken.withdrawERC20(I_PolyToken.address, web3.utils.toWei("10", "ether"), { from: token_owner })); + await catchRevert(I_SecurityToken.withdrawERC20(I_PolyToken.address, new BN(web3.utils.toWei("10", "ether")), { from: token_owner })); }); }); describe("Force Transfer", async () => { - it("Should fail to forceTransfer because not approved controller", async () => { + it("Should fail to controllerTransfer because not approved controller", async () => { await catchRevert( - I_SecurityToken.forceTransfer(account_investor1, account_investor2, web3.utils.toWei("10", "ether"), "", "reason", { + I_SecurityToken.controllerTransfer(account_investor1, account_investor2, new BN(web3.utils.toWei("10", "ether")), "0x0", web3.utils.fromAscii("reason"), { from: account_investor1 }) ); }); - it("Should fail to forceTransfer because insufficient balance", async () => { + it("Should fail to controllerTransfer because insufficient balance", async () => { await catchRevert( - I_SecurityToken.forceTransfer(account_investor2, account_investor1, web3.utils.toWei("10", "ether"), "", "reason", { + I_SecurityToken.controllerTransfer(account_investor2, account_investor1, new BN(web3.utils.toWei("10", "ether")), "0x0", web3.utils.fromAscii("reason"), { from: account_controller }) ); }); - it("Should fail to forceTransfer because recipient is zero address", async () => { + it("Should fail to controllerTransfer because recipient is zero address", async () => { await catchRevert( - I_SecurityToken.forceTransfer(account_investor1, address_zero, web3.utils.toWei("10", "ether"), "", "reason", { + I_SecurityToken.controllerTransfer(account_investor1, address_zero, new BN(web3.utils.toWei("10", "ether")), "0x0", web3.utils.fromAscii("reason"), { from: account_controller }) ); }); - it("Should successfully forceTransfer", async () => { + it("Should successfully controllerTransfer", async () => { let sender = account_investor1; let receiver = account_investor2; - let start_investorCount = await I_SecurityToken.getInvestorCount.call(); + let start_investorCount = await I_SecurityToken.holderCount.call(); let start_balInv1 = await I_SecurityToken.balanceOf.call(account_investor1); let start_balInv2 = await I_SecurityToken.balanceOf.call(account_investor2); - let tx = await I_SecurityToken.forceTransfer( + let tx = await I_SecurityToken.controllerTransfer( account_investor1, account_investor2, - web3.utils.toWei("10", "ether"), - "", - "reason", + new BN(web3.utils.toWei("10", "ether")), + "0x0", + web3.utils.fromAscii("reason"), { from: account_controller } ); - let end_investorCount = await I_SecurityToken.getInvestorCount.call(); + let end_investorCount = await I_SecurityToken.holderCount.call(); let end_balInv1 = await I_SecurityToken.balanceOf.call(account_investor1); let end_balInv2 = await I_SecurityToken.balanceOf.call(account_investor2); - assert.equal(start_investorCount.add(1).toNumber(), end_investorCount.toNumber(), "Investor count not changed"); + assert.equal(start_investorCount.add(new BN(1)).toNumber(), end_investorCount.toNumber(), "Investor count not changed"); assert.equal( - start_balInv1.sub(web3.utils.toWei("10", "ether")).toNumber(), - end_balInv1.toNumber(), + start_balInv1.sub(new BN(web3.utils.toWei("10", "ether"))).toString(), + end_balInv1.toString(), "Investor balance not changed" ); assert.equal( - start_balInv2.add(web3.utils.toWei("10", "ether")).toNumber(), - end_balInv2.toNumber(), + start_balInv2.add(new BN(web3.utils.toWei("10", "ether"))).toString(), + end_balInv2.toString(), "Investor balance not changed" ); - console.log(tx.logs[0].args); - console.log(tx.logs[1].args); - assert.equal(account_controller, tx.logs[0].args._controller, "Event not emitted as expected"); - assert.equal(account_investor1, tx.logs[0].args._from, "Event not emitted as expected"); - assert.equal(account_investor2, tx.logs[0].args._to, "Event not emitted as expected"); - assert.equal(web3.utils.toWei("10", "ether"), tx.logs[0].args._value, "Event not emitted as expected"); - console.log(tx.logs[0].args._verifyTransfer); - assert.equal(false, tx.logs[0].args._verifyTransfer, "Event not emitted as expected"); - assert.equal("reason", web3.utils.hexToUtf8(tx.logs[0].args._data), "Event not emitted as expected"); - - assert.equal(account_investor1, tx.logs[1].args.from, "Event not emitted as expected"); - assert.equal(account_investor2, tx.logs[1].args.to, "Event not emitted as expected"); - assert.equal(web3.utils.toWei("10", "ether"), tx.logs[1].args.value, "Event not emitted as expected"); - }); - - it("Should fail to freeze controller functionality because not owner", async () => { - await catchRevert(I_SecurityToken.disableController({ from: account_investor1 })); + let eventForceTransfer = tx.logs[1]; + let eventTransfer = tx.logs[0]; + assert.equal(account_controller, eventForceTransfer.args._controller, "Event not emitted as expected"); + assert.equal(account_investor1, eventForceTransfer.args._from, "Event not emitted as expected"); + assert.equal(account_investor2, eventForceTransfer.args._to, "Event not emitted as expected"); + assert.equal(new BN(web3.utils.toWei("10", "ether")).toString(), eventForceTransfer.args._value.toString(), "Event not emitted as expected"); + assert.equal("reason", web3.utils.hexToUtf8(eventForceTransfer.args._operatorData), "Event not emitted as expected"); + assert.equal(account_investor1, eventTransfer.args.from, "Event not emitted as expected"); + assert.equal(account_investor2, eventTransfer.args.to, "Event not emitted as expected"); + assert.equal(new BN(web3.utils.toWei("10", "ether")).toString(), eventTransfer.args.value.toString(), "Event not emitted as expected"); + }); + + it("Should fail to freeze controller functionality because proper acknowledgement not signed by owner", async () => { + let trueButOutOfPlaceAcknowledegement = web3.utils.utf8ToHex( + "F O'Brien is the best!" + ); + await catchRevert(I_SecurityToken.disableController(trueButOutOfPlaceAcknowledegement, { from: token_owner })); }); - it("Should fail to freeze controller functionality because disableControllerAllowed not activated", async () => { - await catchRevert(I_SecurityToken.disableController({ from: token_owner })); + // solidity-coverage uses an old version of testrpc that does not support eth_signTypedData. It is required to sign he acknowledgement + process.env.COVERAGE ? it.skip : it("Should fail to freeze controller functionality because not owner", async () => { + disableControllerAckHash = await getDisableControllerAck(I_SecurityToken.address, token_owner); + await catchRevert(I_SecurityToken.disableController(disableControllerAckHash, { from: account_investor1 })); }); - it("Should successfully freeze controller functionality", async () => { - let tx1 = await I_FeatureRegistry.setFeatureStatus("disableControllerAllowed", true, { from: account_polymath }); - - // check event - assert.equal("disableControllerAllowed", tx1.logs[0].args._nameKey, "Event not emitted as expected"); - assert.equal(true, tx1.logs[0].args._newStatus, "Event not emitted as expected"); - - let tx2 = await I_SecurityToken.disableController({ from: token_owner }); - + // solidity-coverage uses an old version of testrpc that does not support eth_signTypedData. It is required to sign he acknowledgement + process.env.COVERAGE ? it.skip : it("Should successfully freeze controller functionality", async () => { + await I_SecurityToken.disableController(disableControllerAckHash, { from: token_owner }); // check state assert.equal(address_zero, await I_SecurityToken.controller.call(), "State not changed"); assert.equal(true, await I_SecurityToken.controllerDisabled.call(), "State not changed"); + assert.isFalse(await I_SecurityToken.isControllable.call()); }); - it("Should fail to freeze controller functionality because already frozen", async () => { - await catchRevert(I_SecurityToken.disableController({ from: token_owner })); + // solidity-coverage uses an old version of testrpc that does not support eth_signTypedData. It is required to sign he acknowledgement + process.env.COVERAGE ? it.skip : it("Should fail to freeze controller functionality because already frozen", async () => { + await catchRevert(I_SecurityToken.disableController(disableControllerAckHash, { from: token_owner })); }); - it("Should fail to set controller because controller functionality frozen", async () => { + // solidity-coverage uses an old version of testrpc that does not support eth_signTypedData. It is required to sign he acknowledgement + process.env.COVERAGE ? it.skip : it("Should fail to set controller because controller functionality frozen", async () => { await catchRevert(I_SecurityToken.setController(account_controller, { from: token_owner })); }); - it("Should fail to forceTransfer because controller functionality frozen", async () => { + // solidity-coverage uses an old version of testrpc that does not support eth_signTypedData. It is required to sign he acknowledgement + process.env.COVERAGE ? it.skip : it("Should fail to controllerTransfer because controller functionality frozen", async () => { await catchRevert( - I_SecurityToken.forceTransfer(account_investor1, account_investor2, web3.utils.toWei("10", "ether"), "", "reason", { + I_SecurityToken.controllerTransfer(account_investor1, account_investor2, new BN(web3.utils.toWei("10", "ether")), "0x0", web3.utils.fromAscii("reason"), { from: account_controller }) ); }); + + }); + + async function balanceOf(account) { + console.log(` + ${account} total balance: ${web3.utils.fromWei(await I_SecurityToken.balanceOf(account))} + ${account} Locked balance: ${web3.utils.fromWei(await I_SecurityToken.balanceOfByPartition(web3.utils.toHex("LOCKED"), account))} + ${account} Unlocked balance: ${web3.utils.fromWei(await I_SecurityToken.balanceOfByPartition(web3.utils.toHex("UNLOCKED"), account))} + `); + } + + describe("Test cases for the partition functions -- ERC1410", async() => { + + it("Set the transfer requirements", async() => { + await I_GeneralTransferManager.modifyTransferRequirementsMulti( + [0, 1, 2], + [true, false, true], + [true, true, false], + [true, false, false], + [true, false, false], + { from: account_delegate } + ); + }); + + it("Should Successfully transfer tokens by the partition", async() => { + await balanceOf(account_investor1); + await balanceOf(account_investor2); + await balanceOf(account_investor3); + await balanceOf(account_affiliate1); + await balanceOf(account_affiliate2); + + fromTime = await latestTime(); + toTime = fromTime; + expiryTime = toTime + duration.days(100); + + let tx = await I_GeneralTransferManager.modifyKYCData(account_investor1, fromTime, toTime, expiryTime, { + from: token_owner, + gas: 6000000 + }); + assert.equal(tx.logs[0].args._investor, account_investor1, "Failed in adding the investor in whitelist"); + + tx = await I_GeneralTransferManager.modifyKYCData(account_investor2, fromTime, toTime, expiryTime, { + from: token_owner, + gas: 6000000 + }); + assert.equal(tx.logs[0].args._investor, account_investor2, "Failed in adding the investor in whitelist"); + + await increaseTime(5); + + let data = await I_SecurityToken.canTransferByPartition.call( + account_investor1, + account_investor2, + web3.utils.toHex("LOCKED"), + new BN(web3.utils.toWei("15")), + "0x0" + ); + + assert.equal(data[0], 0x50); + assert.equal(web3.utils.hexToUtf8(data[1]), ""); + assert.equal(web3.utils.hexToUtf8(data[2]), ""); + + await catchRevert( + I_SecurityToken.transferByPartition( + web3.utils.toHex("LOCKED"), + account_investor2, + new BN(web3.utils.toWei("15")), + "0x0", + { + from: account_investor1 + } + ) + ) + + assert.equal( + web3.utils.hexToUtf8( + await I_SecurityToken.transferByPartition.call( + web3.utils.toHex("UNLOCKED"), + account_investor2, + new BN(web3.utils.toWei("15")), + "0x0", + { + from: account_investor1 + } + ) + ), + "UNLOCKED" + ); + + data = await I_SecurityToken.canTransferByPartition.call( + account_investor1, + account_investor2, + web3.utils.toHex("UNLOCKED"), + new BN(web3.utils.toWei("15")), + "0x0" + ); + + assert.equal(data[0], 0x51); + assert.equal(web3.utils.hexToUtf8(data[1]), ""); + assert.equal(web3.utils.hexToUtf8(data[2]), "UNLOCKED"); + + tx = await I_SecurityToken.transferByPartition( + web3.utils.toHex("UNLOCKED"), + account_investor2, + new BN(web3.utils.toWei("15")), + "0x0", + { + from: account_investor1 + } + ); + assert.equal(web3.utils.hexToUtf8(tx.logs[1].args._fromPartition), "UNLOCKED"); + assert.equal(tx.logs[1].args._operator, "0x0000000000000000000000000000000000000000"); + }); + + it("Should authorize the operator", async() => { + await I_SecurityToken.authorizeOperator(account_delegate, {from: account_investor1}); + assert.isTrue(await stGetter.isOperator.call(account_delegate, account_investor1)); + }); + + it("Should fail to call operatorTransferByPartition-- not a valid partition", async() => { + await catchRevert( + I_SecurityToken.operatorTransferByPartition( + web3.utils.toHex("LOCKED"), + account_investor1, + account_investor2, + new BN(web3.utils.toWei('14')), + "0x0", + web3.utils.toHex("Valid transfer from the operator"), + { + from: account_delegate + } + ) + ); + }); + + it("Should fail to call operatorTransferByPartition-- not a valid operator", async() => { + await catchRevert( + I_SecurityToken.operatorTransferByPartition( + web3.utils.toHex("UNLOCKED"), + account_investor1, + account_investor2, + new BN(web3.utils.toWei('14')), + "0x0", + web3.utils.toHex("Valid transfer from the operator"), + { + from: account_affiliate1 + } + ) + ); + }); + + it("Should fail to call operatorTransferByPartition-- not a valid operatorData", async() => { + await catchRevert( + I_SecurityToken.operatorTransferByPartition( + web3.utils.toHex("UNLOCKED"), + account_investor1, + account_investor2, + new BN(web3.utils.toWei('14')), + "0x0", + web3.utils.toHex(""), + { + from: account_delegate + } + ) + ); + }); + + it("Should successfully execute operatorTransferByPartition", async() => { + let unlockedBalanceOf2InvestorBefore = web3.utils.fromWei(await I_SecurityToken.balanceOfByPartition(web3.utils.toHex("UNLOCKED"), account_investor2)); + let tx = await I_SecurityToken.operatorTransferByPartition( + web3.utils.toHex("UNLOCKED"), + account_investor1, + account_investor2, + new BN(web3.utils.toWei('14')), + "0x0", + web3.utils.toHex("Valid transfer from the operator"), + { + from: account_delegate + } + ); + assert.equal(web3.utils.hexToUtf8(tx.logs[1].args._fromPartition), "UNLOCKED"); + assert.equal(tx.logs[1].args._operator, account_delegate); + let unlockedBalanceOf2InvestorAfter = web3.utils.fromWei(await I_SecurityToken.balanceOfByPartition(web3.utils.toHex("UNLOCKED"), account_investor2)); + assert.equal(unlockedBalanceOf2InvestorAfter - unlockedBalanceOf2InvestorBefore, 14); + }); + + it("Should revoke operator", async() => { + await I_SecurityToken.revokeOperator(account_delegate, {from: account_investor1}); + assert.isFalse(await stGetter.isOperator.call(account_delegate, account_investor1)); + }); + + it("Should fail to transfer by operator -- not a valid operator", async() => { + await catchRevert( + I_SecurityToken.operatorTransferByPartition( + web3.utils.toHex("UNLOCKED"), + account_investor1, + account_investor2, + new BN(web3.utils.toWei('20')), + "0x0", + web3.utils.toHex("Valid transfer from the operator"), + { + from: account_delegate + } + ) + ); + }); + + it("Should fail to execute authorizeOperatorByPartition successfully for invalid partition", async() => { + await catchRevert( + I_SecurityToken.authorizeOperatorByPartition(web3.utils.toHex("LOCKED"), account_delegate, {from: account_investor1}) + ); + }); + + it("Should execute authorizeOperatorByPartition successfully", async() => { + await I_SecurityToken.authorizeOperatorByPartition(web3.utils.toHex("UNLOCKED"), account_delegate, {from: account_investor1}); + assert.isTrue(await stGetter.isOperatorForPartition(web3.utils.toHex("UNLOCKED"), account_delegate, account_investor1)); + }); + + it("Should successfully transfer the tokens by operator", async() => { + let unlockedBalanceOf2InvestorBefore = web3.utils.fromWei(await I_SecurityToken.balanceOfByPartition(web3.utils.toHex("UNLOCKED"), account_investor2)); + let tx = await I_SecurityToken.operatorTransferByPartition( + web3.utils.toHex("UNLOCKED"), + account_investor1, + account_investor2, + new BN(web3.utils.toWei('5')), + "0x0", + web3.utils.toHex("Valid transfer from the operator"), + { + from: account_delegate + } + ) + assert.equal(web3.utils.hexToUtf8(tx.logs[1].args._fromPartition), "UNLOCKED"); + assert.equal(tx.logs[1].args._operator, account_delegate); + let unlockedBalanceOf2InvestorAfter = web3.utils.fromWei(await I_SecurityToken.balanceOfByPartition(web3.utils.toHex("UNLOCKED"), account_investor2)); + assert.equal(unlockedBalanceOf2InvestorAfter - unlockedBalanceOf2InvestorBefore, 5); + }); + + it("Should successfully execute revokeOperatorByPartition successfully", async() => { + await I_SecurityToken.revokeOperatorByPartition(web3.utils.toHex("UNLOCKED"), account_delegate, {from: account_investor1}); + assert.isFalse(await stGetter.isOperatorForPartition(web3.utils.toHex("UNLOCKED"), account_delegate, account_investor1)); + }); + + it("Should fail to issue to tokens according to partition -- invalid partition", async() => { + await catchRevert ( + I_SecurityToken.issueByPartition( + web3.utils.toHex("LOCKED"), + account_investor1, + new BN(web3.utils.toWei('100')), + "0x0", + { + from: token_owner + } + ) + ); + }); + + it("Should fail to issue to tokens according to partition -- invalid token owner", async() => { + await catchRevert ( + I_SecurityToken.issueByPartition( + web3.utils.toHex("UNLOCKED"), + account_investor1, + new BN(web3.utils.toWei('100')), + "0x0", + { + from: account_affiliate1 + } + ) + ); + }); + + it("Should successfullly issue the tokens according to partition", async() => { + let beforeTotalSupply = await I_SecurityToken.totalSupply.call(); + let beforeUnlockedBalance = await I_SecurityToken.balanceOfByPartition.call(web3.utils.toHex("UNLOCKED"), account_investor1); + let beforeBalance = await I_SecurityToken.balanceOf.call(account_investor1); + await I_SecurityToken.issueByPartition( + web3.utils.toHex("UNLOCKED"), + account_investor1, + new BN(web3.utils.toWei('100')), + "0x0", + { + from: token_owner + } + ); + let afterTotalSupply = await I_SecurityToken.totalSupply.call(); + let afterUnlockedBalance = await I_SecurityToken.balanceOfByPartition.call(web3.utils.toHex("UNLOCKED"), account_investor1); + let afterBalance = await I_SecurityToken.balanceOf.call(account_investor1); + assert.equal(web3.utils.fromWei(afterTotalSupply.sub(beforeTotalSupply)), 100); + assert.equal(web3.utils.fromWei(afterUnlockedBalance.sub(beforeUnlockedBalance)), 100); + assert.equal(web3.utils.fromWei(afterBalance.sub(beforeBalance)), 100); + }); + + it("Should execute authorizeOperatorByPartition successfully", async() => { + await I_SecurityToken.authorizeOperatorByPartition(web3.utils.toHex("UNLOCKED"), account_delegate, {from: account_investor1}); + assert.isTrue(await stGetter.isOperatorForPartition(web3.utils.toHex("UNLOCKED"), account_delegate, account_investor1)); + }); + + it("Should fail to redeem tokens as per partition -- incorrect msg.sender", async() => { + await catchRevert( + I_SecurityToken.redeemByPartition( + web3.utils.toHex("UNLOCKED"), + web3.utils.toWei("10"), + "0x0", + { + from: account_investor1 + } + ) + ); + }); + + it("Should failed to redeem tokens by partition -- because not sufficient allowance", async() => { + await I_SecurityToken.unarchiveModule(I_MockRedemptionManager.address, {from: token_owner}); + await catchRevert( + I_MockRedemptionManager.redeemTokensByPartition(new BN(web3.utils.toWei("10")), web3.utils.toHex("LOCKED"), "0x0", {from: account_investor1}) + ); + }) + + it("should failed to redeem tokens by partition -- because invalid partition", async() => { + await I_SecurityToken.approve(I_MockRedemptionManager.address, new BN(web3.utils.toWei("50")), {from: account_investor1}); + await I_MockRedemptionManager.transferToRedeem(new BN(web3.utils.toWei("50")), {from: account_investor1}); + + // failed because of invalid partition + await catchRevert( + I_MockRedemptionManager.redeemTokensByPartition(new BN(web3.utils.toWei("10")), web3.utils.toHex("LOCKED"), "0x0", {from: account_investor1}) + ); + }); + + it("Should successfully redeem tokens by partition", async() => { + let beforeTotalSupply = await I_SecurityToken.totalSupply.call(); + + let tx = await I_MockRedemptionManager.redeemTokensByPartition(new BN(web3.utils.toWei("10")), web3.utils.toHex("UNLOCKED"), "0x0", {from: account_investor1}); + assert.equal(web3.utils.hexToUtf8(tx.logs[0].args._partition), "UNLOCKED"); + assert.equal(tx.logs[0].args._operator, "0x0000000000000000000000000000000000000000"); + assert.equal(tx.logs[0].args._investor, account_investor1); + + let afterTotalSupply = await I_SecurityToken.totalSupply.call(); + assert.equal(web3.utils.fromWei(beforeTotalSupply.sub(afterTotalSupply)), 10); + }); + + it("Should failed to call operatorRedeemByPartition -- msg.sender is not authorised", async() => { + await catchRevert( + I_SecurityToken.operatorRedeemByPartition( + web3.utils.toHex("UNLOCKED"), + account_investor1, + web3.utils.toWei("10"), + "0x0", + web3.utils.toHex("Valid call from the operator"), + { + from: account_delegate + } + ) + ); + }); + + it("Should fail when partition is not valid", async() => { + await I_SecurityToken.authorizeOperator(I_MockRedemptionManager.address, {from: account_investor1}); + await I_MockRedemptionManager.operatorTransferToRedeem( + web3.utils.toWei("20"), + web3.utils.toHex("UNLOCKED"), + "0x0", + web3.utils.toHex("Valid call from the operator"), + { + from: account_investor1 + } + ); + + await catchRevert( + I_MockRedemptionManager.operatorRedeemTokensByPartition( + web3.utils.toWei("20"), + web3.utils.toHex("LOCKED"), + "0x0", + web3.utils.toHex("Valid call from the operator"), + { + from: account_investor1 + } + ) + ); + }); + + it("Should successfully redeem tokens by operator", async() => { + let beforeTotalSupply = await I_SecurityToken.totalSupply.call(); + let tx = await I_MockRedemptionManager.operatorRedeemTokensByPartition( + web3.utils.toWei("10"), + web3.utils.toHex("UNLOCKED"), + "0x0", + web3.utils.toHex("Valid call from the operator"), + { + from: account_investor1 + } + ); + assert.equal(web3.utils.hexToUtf8(tx.logs[0].args._partition), "UNLOCKED"); + assert.equal(tx.logs[0].args._operator, I_MockRedemptionManager.address); + assert.equal(tx.logs[0].args._investor, account_investor1); + + let afterTotalSupply = await I_SecurityToken.totalSupply.call(); + assert.equal(web3.utils.fromWei(beforeTotalSupply.sub(afterTotalSupply)), 10); + }); + + it("Should get the partitions of the secuirtyToken", async() => { + let partitions = await I_STGetter.partitionsOf.call(account_investor1); + console.log(`Partitions of the investor 1: ${web3.utils.hexToUtf8(partitions[0])}`); + assert.equal("UNLOCKED", web3.utils.hexToUtf8(partitions[0])); + assert.equal("LOCKED", web3.utils.hexToUtf8(partitions[1])); + partitions = await I_STGetter.partitionsOf.call(account_investor2); + console.log(`Partitions of the investor 2: ${web3.utils.hexToUtf8(partitions[0])}`); + }); + }); + + describe("Test cases for the storage", async() => { + + it("Test the storage values of the ERC20 vairables", async() => { + let investors = await stGetter.getInvestors.call(); + + console.log("Verifying the balances of the Addresses"); + let index; + let key; + let newKey = new Array(); + + function encodeUint(data) { + return web3.eth.abi.encodeParameter('uint256', data); + } + + for (let i = 0; i < investors.length; i++) { + index = encodeUint(0); + key = web3.eth.abi.encodeParameter('address', investors[i]) + var tempKey = key + index.substring(2); + newKey.push(encodeUint(web3.utils.sha3(tempKey, {"encoding": "hex"}))); + } + + assert.equal( + web3.utils.fromWei((await I_SecurityToken.balanceOf.call(investors[0])).toString()), + web3.utils.fromWei((web3.utils.toBN(await readStorage(I_SecurityToken.address, newKey[0]))).toString()) + ) + console.log(` + Balances from the contract: ${web3.utils.fromWei((await I_SecurityToken.balanceOf.call(investors[0])).toString())} + Balances from the storage: ${web3.utils.fromWei((web3.utils.toBN(await readStorage(I_SecurityToken.address, newKey[0]))).toString())} + `) + + assert.equal( + web3.utils.fromWei((await I_SecurityToken.balanceOf.call(investors[1])).toString()), + web3.utils.fromWei((web3.utils.toBN(await readStorage(I_SecurityToken.address, newKey[1]))).toString()) + ); + console.log(` + Balances from the contract: ${web3.utils.fromWei((await I_SecurityToken.balanceOf.call(investors[1])).toString())} + Balances from the storage: ${web3.utils.fromWei((web3.utils.toBN(await readStorage(I_SecurityToken.address, newKey[1]))).toString())} + `) + + assert.equal( + web3.utils.fromWei((await I_SecurityToken.balanceOf.call(investors[2])).toString()), + web3.utils.fromWei((web3.utils.toBN(await readStorage(I_SecurityToken.address, newKey[2]))).toString()) + ); + console.log(` + Balances from the contract: ${web3.utils.fromWei((await I_SecurityToken.balanceOf.call(investors[2])).toString())} + Balances from the storage: ${web3.utils.fromWei((web3.utils.toBN(await readStorage(I_SecurityToken.address, newKey[2]))).toString())} + `) + assert.equal( + web3.utils.fromWei((await I_SecurityToken.balanceOf.call(investors[3])).toString()), + web3.utils.fromWei((web3.utils.toBN(await readStorage(I_SecurityToken.address, newKey[3]))).toString()) + ) + console.log(` + Balances from the contract: ${web3.utils.fromWei((await I_SecurityToken.balanceOf.call(investors[3])).toString())} + Balances from the storage: ${web3.utils.fromWei((web3.utils.toBN(await readStorage(I_SecurityToken.address, newKey[3]))).toString())} + `) + assert.equal( + web3.utils.fromWei((await I_SecurityToken.totalSupply.call()).toString()), + web3.utils.fromWei((web3.utils.toBN(await readStorage(I_SecurityToken.address, 2))).toString()) + ); + console.log(` + TotalSupply from contract: ${web3.utils.fromWei((await I_SecurityToken.totalSupply.call()).toString())} + TotalSupply from the storage: ${web3.utils.fromWei((web3.utils.toBN(await readStorage(I_SecurityToken.address, 2))).toString())} + `); + assert.equal( + await I_SecurityToken.name.call(), + (web3.utils.toAscii(await readStorage(I_SecurityToken.address, 6)).replace(/\u0000/g, "")).replace(/\u0014/g, "") + ) + console.log(` + Name of the ST: ${await I_SecurityToken.name.call()} + Name of the ST from the storage: ${web3.utils.toUtf8(await readStorage(I_SecurityToken.address, 6))} + `); + assert.equal( + await I_SecurityToken.symbol.call(), + (web3.utils.toUtf8(await readStorage(I_SecurityToken.address, 7)).replace(/\u0000/g, "")).replace(/\u0006/g, "") + ); + console.log(` + Symbol of the ST: ${await I_SecurityToken.symbol.call()} + Symbol of the ST from the storage: ${web3.utils.toUtf8(await readStorage(I_SecurityToken.address, 7))} + `); + + console.log(` + Address of the owner: ${await I_SecurityToken.owner.call()} + Address of the owner from the storage: ${(await readStorage(I_SecurityToken.address, 4)).substring(0, 42)} + `) + assert.equal( + await I_SecurityToken.owner.call(), + web3.utils.toChecksumAddress((await readStorage(I_SecurityToken.address, 4)).substring(0, 42)) + ); + + }); + + it("Verify the storage of the STStorage", async() => { + + console.log(` + Controller address from the contract: ${await stGetter.controller.call()} + decimals from the contract: ${await stGetter.decimals.call()} + controller address from the storage + uint8 decimals: ${await readStorage(I_SecurityToken.address, 8)} + `) + + // Controller address is packed with decimals so if controller address is 0x0, only decimals will be returned from read storage. + assert.oneOf( + await readStorage(I_SecurityToken.address, 8), + [ + (await stGetter.controller.call()).toLowerCase() + "12", + "0x12" // When controller address = 0x0, web3 converts 0x00000..000012 to 0x12 + ] + ); + + console.log(` + PolymathRegistry address from the contract: ${await stGetter.polymathRegistry.call()} + PolymathRegistry address from the storage: ${await readStorage(I_SecurityToken.address, 9)} + `) + + assert.equal( + await stGetter.polymathRegistry.call(), + web3.utils.toChecksumAddress(await readStorage(I_SecurityToken.address, 9)) + ); + console.log(` + ModuleRegistry address from the contract: ${await stGetter.moduleRegistry.call()} + ModuleRegistry address from the storage: ${await readStorage(I_SecurityToken.address, 10)} + `) + + assert.equal( + await stGetter.moduleRegistry.call(), + web3.utils.toChecksumAddress(await readStorage(I_SecurityToken.address, 10)) + ); + + console.log(` + SecurityTokenRegistry address from the contract: ${await stGetter.securityTokenRegistry.call()} + SecurityTokenRegistry address from the storage: ${await readStorage(I_SecurityToken.address, 11)} + `) + + assert.equal( + await stGetter.securityTokenRegistry.call(), + web3.utils.toChecksumAddress(await readStorage(I_SecurityToken.address, 11)) + ); + + console.log(` + PolyToken address from the contract: ${await stGetter.polyToken.call()} + PolyToken address from the storage: ${await readStorage(I_SecurityToken.address, 12)} + `) + + assert.equal( + await stGetter.polyToken.call(), + web3.utils.toChecksumAddress(await readStorage(I_SecurityToken.address, 12)) + ); + + console.log(` + Delegate address from the contract: ${await stGetter.getterDelegate.call()} + Delegate address from the storage: ${await readStorage(I_SecurityToken.address, 13)} + `) + + assert.equal( + await stGetter.getterDelegate.call(), + web3.utils.toChecksumAddress(await readStorage(I_SecurityToken.address, 13)) + ); + + console.log(` + Datastore address from the contract: ${await stGetter.dataStore.call()} + Datastore address from the storage: ${await readStorage(I_SecurityToken.address, 14)} + `) + + assert.equal( + await stGetter.dataStore.call(), + web3.utils.toChecksumAddress(await readStorage(I_SecurityToken.address, 14)) + ); + + console.log(` + Granularity value from the contract: ${await stGetter.granularity.call()} + Granularity value from the storage: ${(web3.utils.toBN(await readStorage(I_SecurityToken.address, 15))).toString()} + `) + + assert.equal( + web3.utils.fromWei(await stGetter.granularity.call()), + web3.utils.fromWei((web3.utils.toBN(await readStorage(I_SecurityToken.address, 15))).toString()) + ); + + console.log(` + Current checkpoint ID from the contract: ${await stGetter.currentCheckpointId.call()} + Current checkpoint ID from the storage: ${(web3.utils.toBN(await readStorage(I_SecurityToken.address, 16))).toString()} + `) + assert.equal( + await stGetter.currentCheckpointId.call(), + (web3.utils.toBN(await readStorage(I_SecurityToken.address, 16))).toString() + ); + + console.log(` + TokenDetails from the contract: ${await stGetter.tokenDetails.call()} + TokenDetails from the storage: ${(web3.utils.toUtf8((await readStorage(I_SecurityToken.address, 17)).substring(0, 60)))} + `) + assert.equal( + await stGetter.tokenDetails.call(), + (web3.utils.toUtf8((await readStorage(I_SecurityToken.address, 17)).substring(0, 60))).replace(/\u0000/g, "") + ); + + }); + + }); + + describe(`Test cases for the ERC1643 contract\n`, async () => { + + describe(`Test cases for the setDocument() function of the ERC1643\n`, async() => { + + it("\tShould failed in executing the setDocument() function because msg.sender is not authorised\n", async() => { + await catchRevert( + I_SecurityToken.setDocument(web3.utils.utf8ToHex("doc1"), "https://www.gogl.bts.fly", "0x0", {from: account_temp}) + ); + }); + + it("\tShould failed to set a document details as name is empty\n", async() => { + await catchRevert( + I_SecurityToken.setDocument(web3.utils.utf8ToHex(""), "https://www.gogl.bts.fly", "0x0", {from: token_owner}) + ); + }); + + it("\tShould failed to set a document details as URI is empty\n", async() => { + await catchRevert( + I_SecurityToken.setDocument(web3.utils.utf8ToHex("doc1"), "", "0x0", {from: token_owner}) + ); + }); + + it("\tShould sucessfully add the document details in the `_documents` mapping and change the length of the `_docsNames`\n", async() => { + let tx = await I_SecurityToken.setDocument(web3.utils.utf8ToHex("doc1"), uri, docHash, {from: token_owner}); + assert.equal(web3.utils.toUtf8(tx.logs[0].args._name), "doc1"); + assert.equal(tx.logs[0].args._uri, uri); + assert.equal(web3.utils.toUtf8(tx.logs[0].args._documentHash), web3.utils.toUtf8(docHash)); + assert.equal((await stGetter.getAllDocuments.call()).length, 1); + }); + + it("\tShould successfully add the new document and allow the empty docHash to be added in the `Document` structure\n", async() => { + let tx = await I_SecurityToken.setDocument(web3.utils.utf8ToHex("doc2"), uri, "0x0", {from: token_owner}); + assert.equal(web3.utils.toUtf8(tx.logs[0].args._name), "doc2"); + assert.equal(tx.logs[0].args._uri, uri); + assert.equal(tx.logs[0].args._documentHash, empty_hash); + assert.equal((await stGetter.getAllDocuments.call()).length, 2); + }); + + it("\tShould successfully update the existing document and length of `_docsNames` should remain unaffected\n", async() => { + let tx = await I_SecurityToken.setDocument(web3.utils.utf8ToHex("doc2"), "https://www.bts.l", "0x0", {from: token_owner}); + assert.equal(web3.utils.toUtf8(tx.logs[0].args._name), "doc2"); + assert.equal(tx.logs[0].args._uri, "https://www.bts.l"); + assert.equal(tx.logs[0].args._documentHash, empty_hash); + assert.equal((await stGetter.getAllDocuments.call()).length, 2); + }); + + describe("Test cases for the getters functions\n", async()=> { + + it("\tShould get the details of existed document\n", async() => { + let doc1Details = await stGetter.getDocument.call(web3.utils.utf8ToHex("doc1")); + assert.equal(doc1Details[0], uri); + assert.equal(web3.utils.toUtf8(doc1Details[1]), web3.utils.toUtf8(docHash)); + assert.closeTo(doc1Details[2].toNumber(), await latestTime(), 2); + + let doc2Details = await stGetter.getDocument.call(web3.utils.utf8ToHex("doc2")); + assert.equal(doc2Details[0], "https://www.bts.l"); + assert.equal(doc2Details[1], empty_hash); + assert.closeTo(doc2Details[2].toNumber(), await latestTime(), 2); + }); + + it("\tShould get the details of the non-existed document it means every value should be zero\n", async() => { + let doc3Details = await stGetter.getDocument.call(web3.utils.utf8ToHex("doc3")); + assert.equal(doc3Details[0], ""); + assert.equal(web3.utils.toUtf8(doc3Details[1]), ""); + assert.equal(doc3Details[2], 0); + }); + + it("\tShould get all the documents present in the contract\n", async() => { + let allDocs = await stGetter.getAllDocuments.call() + assert.equal(allDocs.length, 2); + assert.equal(web3.utils.toUtf8(allDocs[0]), "doc1"); + assert.equal(web3.utils.toUtf8(allDocs[1]), "doc2"); + }); + }) + }); + + describe("Test cases for the removeDocument()\n", async() => { + + it("\tShould failed to remove document because msg.sender is not authorised\n", async() => { + await catchRevert( + I_SecurityToken.removeDocument(web3.utils.utf8ToHex("doc2"), {from: account_temp}) + ); + }); + + it("\tShould failed to remove the document that is not existed in the contract\n", async() => { + await catchRevert( + I_SecurityToken.removeDocument(web3.utils.utf8ToHex("doc3"), {from: token_owner}) + ); + }); + + it("\tShould succssfully remove the document from the contract which is present in the last index of the `_docsName` and check the params of the `DocumentRemoved` event\n", async() => { + // first add the new document + await I_SecurityToken.setDocument(web3.utils.utf8ToHex("doc3"), "https://www.bts.l", "0x0", {from: token_owner}); + // as this will be last in the array so remove this + let tx = await I_SecurityToken.removeDocument(web3.utils.utf8ToHex("doc3"), {from: token_owner}); + assert.equal(web3.utils.toUtf8(tx.logs[0].args._name), "doc3"); + assert.equal(tx.logs[0].args._uri, "https://www.bts.l"); + assert.equal(tx.logs[0].args._documentHash, empty_hash); + assert.equal((await stGetter.getAllDocuments.call()).length, 2); + + // remove the document that is not last in the `docsName` array + tx = await I_SecurityToken.removeDocument(web3.utils.utf8ToHex("doc1"), {from: token_owner}); + assert.equal(web3.utils.toUtf8(tx.logs[0].args._name), "doc1"); + assert.equal(tx.logs[0].args._uri, uri); + assert.equal(web3.utils.toUtf8(tx.logs[0].args._documentHash), web3.utils.toUtf8(docHash)); + assert.equal((await stGetter.getAllDocuments.call()).length, 1); + }); + + it("\t Should delete the doc to validate the #17 issue problem", async() => { + let tx = await I_SecurityToken.removeDocument(web3.utils.utf8ToHex("doc2"), {from: token_owner}); + assert.equal(web3.utils.toUtf8(tx.logs[0].args._name), "doc2"); + assert.equal(tx.logs[0].args._uri, "https://www.bts.l"); + assert.equal(web3.utils.toUtf8(tx.logs[0].args._documentHash), ''); + assert.equal((await stGetter.getAllDocuments.call()).length, 0); + }); + + describe("Test cases for the getters functions\n", async()=> { + + it("\tShould get the details of the non-existed (earlier was present but get removed ) document it means every value should be zero\n", async() => { + let doc1Details = await stGetter.getDocument.call(web3.utils.utf8ToHex("doc1")); + assert.equal(doc1Details[0], ""); + assert.equal(web3.utils.toUtf8(doc1Details[1]), ""); + assert.equal(doc1Details[2], 0); + }); + + it("\tShould get all the documents present in the contract which should be 1\n", async() => { + // add one doc before the getter call + await I_SecurityToken.setDocument(web3.utils.utf8ToHex("doc4"), "https://www.bts.l", docHash, {from: token_owner}) + let allDocs = await stGetter.getAllDocuments.call() + assert.equal(allDocs.length, 1); + assert.equal(web3.utils.toUtf8(allDocs[0]), "doc4"); + }); + }); + + describe("Test cases for the returnPartition", async() => { + // It will work once the balanceOfByPartition function fixed added + it.skip("Should add the lockup Transfer manager and create a lockup for investor 1", async() => { + + console.log(web3.utils.fromWei(await I_SecurityToken.balanceOf.call(account_investor1))); + console.log(web3.utils.fromWei(await I_SecurityToken.balanceOfByPartition.call(web3.utils.toHex("UNLOCKED"),account_investor1))); + console.log(web3.utils.fromWei(await I_SecurityToken.balanceOf.call(account_investor2))); + + const tx = await I_SecurityToken.addModule(I_LockUpTransferManagerFactory.address, "0x", 0, 0, false, { from: token_owner }); + assert.equal(tx.logs[2].args._types[0].toString(), transferManagerKey, "LockUpVolumeRestrictionTMFactory doesn't get deployed"); + assert.equal( + web3.utils.toAscii(tx.logs[2].args._name) + .replace(/\u0000/g, ''), + "LockUpTransferManager", + "LockUpTransferManager module was not added" + ); + I_LockUpTransferManager = await LockUpTransferManager.at(tx.logs[2].args._module); + let currentTime = new BN(await latestTime()); + await I_LockUpTransferManager.addNewLockUpToUser( + account_investor2, + new BN(web3.utils.toWei("1000")), + currentTime.add(new BN(duration.seconds(1))), + new BN(duration.seconds(400000)), + new BN(duration.seconds(100000)), + web3.utils.fromAscii("a_lockup"), + { + from: token_owner + } + ); + + // transfer balance of Unlocked partition of invesotor 1 to 2 + await increaseTime(10); + + console.log(`UNLOCKED balance - ${web3.utils.fromWei(await I_SecurityToken.balanceOfByPartition.call(web3.utils.toHex("UNLOCKED"),account_investor2))}`); + console.log(`Locked Balance - ${web3.utils.fromWei(await I_SecurityToken.balanceOfByPartition.call(web3.utils.toHex("LOCKED"),account_investor2))}`); + + let partition = await I_SecurityToken.transferByPartition.call( + web3.utils.toHex("UNLOCKED"), + account_investor2, + new BN(web3.utils.toWei("500")), + "0x0", + { + from: account_investor1 + } + ); + console.log(`UNLOCKED balance - ${web3.utils.fromWei(await I_SecurityToken.balanceOfByPartition.call(web3.utils.toHex("UNLOCKED"),account_investor2))}`); + console.log(`Locked Balance - ${web3.utils.fromWei(await I_SecurityToken.balanceOfByPartition.call(web3.utils.toHex("LOCKED"),account_investor2))}`); + assert.equal(web3.utils.hexToUtf8(partition), "LOCKED"); + }); + }) + }) }); }); diff --git a/test/p_usd_tiered_sto.js b/test/p_usd_tiered_sto.js index ee5838d0a..d5aefc327 100644 --- a/test/p_usd_tiered_sto.js +++ b/test/p_usd_tiered_sto.js @@ -10,17 +10,20 @@ const MockOracle = artifacts.require("./MockOracle.sol"); const SecurityToken = artifacts.require("./SecurityToken.sol"); const GeneralTransferManager = artifacts.require("./GeneralTransferManager"); const PolyTokenFaucet = artifacts.require("./PolyTokenFaucet.sol"); +const STGetter = artifacts.require("./STGetter.sol"); const Web3 = require("web3"); -const BigNumber = require("bignumber.js"); +let BN = Web3.utils.BN; const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); // Hardcoded development port -contract("USDTieredSTO", accounts => { +contract("USDTieredSTO", async (accounts) => { + let e18; + let e16; // Accounts Variable declaration let POLYMATH; let ISSUER; let WALLET; - let RESERVEWALLET; + let TREASURYWALLET; let INVESTOR1; let INVESTOR2; let INVESTOR3; @@ -32,6 +35,8 @@ contract("USDTieredSTO", accounts => { let ETH = 0; let POLY = 1; let DAI = 2; + let oldEthRate; + let oldPolyRate; let MESSAGE = "Transaction Should Fail!"; const GAS_PRICE = 0; @@ -59,6 +64,9 @@ contract("USDTieredSTO", accounts => { let I_DaiToken; let I_PolymathRegistry; let P_USDTieredSTOFactory; + let I_STRGetter; + let I_STGetter; + let stGetter; // SecurityToken Details for funds raise Type ETH const NAME = "Team"; @@ -71,14 +79,15 @@ contract("USDTieredSTO", accounts => { const STOKEY = 3; let snapId; const address_zero = "0x0000000000000000000000000000000000000000"; + const one_address = "0x0000000000000000000000000000000000000001"; // Initial fee for ticker registry and security token registry - const REGFEE = web3.utils.toWei("250"); + let REGFEE; const STOSetupCost = 0; // MockOracle USD prices - const USDETH = new BigNumber(500).mul(10 ** 18); // 500 USD/ETH - const USDPOLY = new BigNumber(25).mul(10 ** 16); // 0.25 USD/POLY + let USDETH; // 500 USD/ETH + let USDPOLY; // 0.25 USD/POLY // STO Configuration Arrays let _startTime = []; @@ -91,7 +100,7 @@ contract("USDTieredSTO", accounts => { let _minimumInvestmentUSD = []; let _fundRaiseTypes = []; let _wallet = []; - let _reserveWallet = []; + let _treasuryWallet = []; let _usdToken = []; /* function configure( @@ -154,7 +163,7 @@ contract("USDTieredSTO", accounts => { }, { type: "address", - name: "_reserveWallet" + name: "_treasuryWallet" }, { type: "address[]", @@ -165,39 +174,47 @@ contract("USDTieredSTO", accounts => { async function convert(_stoID, _tier, _discount, _currencyFrom, _currencyTo, _amount) { let USDTOKEN; - if (_discount) USDTOKEN = ((await I_USDTieredSTO_Array[_stoID].tiers.call(_tier))[1]); - else USDTOKEN = ((await I_USDTieredSTO_Array[_stoID].tiers.call(_tier))[0]); + _amount = new BN(_amount); + if (_discount) USDTOKEN = (await I_USDTieredSTO_Array[_stoID].tiers.call(_tier))[1]; + else USDTOKEN = (await I_USDTieredSTO_Array[_stoID].tiers.call(_tier))[0]; + USDTOKEN = new BN(USDTOKEN); if (_currencyFrom == "TOKEN") { - let tokenToUSD = _amount - .div(10 ** 18) - .mul(USDTOKEN.div(10 ** 18)) - .mul(10 ** 18); // TOKEN * USD/TOKEN = USD + let tokenToUSD = new BN(_amount) + .mul(USDTOKEN) + .div(e18); if (_currencyTo == "USD") return tokenToUSD; if (_currencyTo == "ETH") { - return await I_USDTieredSTO_Array[_stoID].convertFromUSD(ETH, tokenToUSD); + return await I_USDTieredSTO_Array[_stoID].convertFromUSD.call(ETH, tokenToUSD); } else if (_currencyTo == "POLY") { - return await I_USDTieredSTO_Array[_stoID].convertFromUSD(POLY, tokenToUSD); + return await I_USDTieredSTO_Array[_stoID].convertFromUSD.call(POLY, tokenToUSD); } } if (_currencyFrom == "USD") { - if (_currencyTo == "TOKEN") return _amount.div(USDTOKEN).mul(10 ** 18); // USD / USD/TOKEN = TOKEN + if (_currencyTo == "TOKEN") return _amount.div(USDTOKEN).mul(e18); // USD / USD/TOKEN = TOKEN if (_currencyTo == "ETH" || _currencyTo == "POLY") - return await I_USDTieredSTO_Array[_stoID].convertFromUSD(_currencyTo == "ETH" ? ETH : POLY, _amount); + return await I_USDTieredSTO_Array[_stoID].convertFromUSD.call(_currencyTo == "ETH" ? ETH : POLY, _amount); } if (_currencyFrom == "ETH" || _currencyFrom == "POLY") { - let ethToUSD = await I_USDTieredSTO_Array[_stoID].convertToUSD(_currencyTo == "ETH" ? ETH : POLY, _amount); + let ethToUSD = await I_USDTieredSTO_Array[_stoID].convertToUSD.call(_currencyTo == "ETH" ? ETH : POLY, _amount); if (_currencyTo == "USD") return ethToUSD; - if (_currencyTo == "TOKEN") return ethToUSD.div(USDTOKEN).mul(10 ** 18); // USD / USD/TOKEN = TOKEN + if (_currencyTo == "TOKEN") return ethToUSD.div(USDTOKEN).mul(e18); // USD / USD/TOKEN = TOKEN } return 0; } + let currentTime; + before(async () => { - // Accounts setup + e18 = new BN(10).pow(new BN(18)); + e16 = new BN(10).pow(new BN(16)); + currentTime = new BN(await latestTime()); + REGFEE = new BN(web3.utils.toWei("1000")); + USDETH = new BN(500).mul(new BN(10).pow(new BN(18))); // 500 USD/ETH + USDPOLY = new BN(25).mul(new BN(10).pow(new BN(16))); // 0.25 USD/POLY POLYMATH = accounts[0]; ISSUER = accounts[1]; WALLET = accounts[2]; - RESERVEWALLET = WALLET; + TREASURYWALLET = WALLET; ACCREDITED1 = accounts[3]; ACCREDITED2 = accounts[4]; NONACCREDITED1 = accounts[5]; @@ -220,19 +237,22 @@ contract("USDTieredSTO", accounts => { I_STFactory, I_SecurityTokenRegistry, I_SecurityTokenRegistryProxy, - I_STRProxied + I_STRProxied, + I_STRGetter, + I_STGetter, + I_STGetter ] = instances; I_DaiToken = await PolyTokenFaucet.new({from: POLYMATH}); // STEP 4: Deploy the GeneralDelegateManagerFactory - [I_GeneralPermissionManagerFactory] = await deployGPMAndVerifyed(POLYMATH, I_MRProxied, I_PolyToken.address, 0); + [I_GeneralPermissionManagerFactory] = await deployGPMAndVerifyed(POLYMATH, I_MRProxied, 0); // STEP 5: Deploy the USDTieredSTOFactory - [I_USDTieredSTOFactory] = await deployUSDTieredSTOAndVerified(POLYMATH, I_MRProxied, I_PolyToken.address, STOSetupCost); - [P_USDTieredSTOFactory] = await deployUSDTieredSTOAndVerified(POLYMATH, I_MRProxied, I_PolyToken.address, web3.utils.toWei("500")); + [I_USDTieredSTOFactory] = await deployUSDTieredSTOAndVerified(POLYMATH, I_MRProxied, STOSetupCost); + [P_USDTieredSTOFactory] = await deployUSDTieredSTOAndVerified(POLYMATH, I_MRProxied, new BN(web3.utils.toWei("500"))); // Step 12: Deploy & Register Mock Oracles - I_USDOracle = await MockOracle.new(0, "ETH", "USD", USDETH, { from: POLYMATH }); // 500 dollars per POLY - I_POLYOracle = await MockOracle.new(I_PolyToken.address, "POLY", "USD", USDPOLY, { from: POLYMATH }); // 25 cents per POLY + I_USDOracle = await MockOracle.new(address_zero, web3.utils.fromAscii("ETH"), web3.utils.fromAscii("USD"), USDETH, { from: POLYMATH }); // 500 dollars per POLY + I_POLYOracle = await MockOracle.new(I_PolyToken.address, web3.utils.fromAscii("POLY"), web3.utils.fromAscii("USD"), USDPOLY, { from: POLYMATH }); // 25 cents per POLY await I_PolymathRegistry.changeAddress("EthUsdOracle", I_USDOracle.address, { from: POLYMATH }); await I_PolymathRegistry.changeAddress("PolyUsdOracle", I_POLYOracle.address, { from: POLYMATH }); @@ -259,7 +279,7 @@ contract("USDTieredSTO", accounts => { it("Should register the ticker before the generation of the security token", async () => { await I_PolyToken.getTokens(REGFEE, ISSUER); await I_PolyToken.approve(I_STRProxied.address, REGFEE, { from: ISSUER }); - let tx = await I_STRProxied.registerTicker(ISSUER, SYMBOL, NAME, { from: ISSUER }); + let tx = await I_STRProxied.registerNewTicker(ISSUER, SYMBOL, { from: ISSUER }); assert.equal(tx.logs[0].args._owner, ISSUER); assert.equal(tx.logs[0].args._ticker, SYMBOL); }); @@ -267,22 +287,23 @@ contract("USDTieredSTO", accounts => { it("Should generate the new security token with the same symbol as registered above", async () => { await I_PolyToken.getTokens(REGFEE, ISSUER); await I_PolyToken.approve(I_STRProxied.address, REGFEE, { from: ISSUER }); - let _blockNo = latestBlock(); - let tx = await I_STRProxied.generateSecurityToken(NAME, SYMBOL, TOKENDETAILS, true, { from: ISSUER }); - assert.equal(tx.logs[1].args._ticker, SYMBOL, "SecurityToken doesn't get deployed"); - I_SecurityToken = SecurityToken.at(tx.logs[1].args._securityTokenAddress); + let tx = await I_STRProxied.generateNewSecurityToken(NAME, SYMBOL, TOKENDETAILS, true, ISSUER, 0, { from: ISSUER }); + assert.equal(tx.logs[1].args._ticker, SYMBOL, "SecurityToken doesn't get deployed"); - const log = await promisifyLogWatch(I_SecurityToken.ModuleAdded({ from: _blockNo }), 1); + I_SecurityToken = await SecurityToken.at(tx.logs[1].args._securityTokenAddress); + stGetter = await STGetter.at(I_SecurityToken.address); + assert.equal(await stGetter.getTreasuryWallet.call(), ISSUER, "Incorrect wallet set") + const log = (await I_SecurityToken.getPastEvents('ModuleAdded', {filter: {transactionHash: tx.transactionHash}}))[0]; // Verify that GeneralTransferManager module get added successfully or not - assert.equal(log.args._types[0].toNumber(), TMKEY); + assert.equal(log.args._types[0].toString(), TMKEY); assert.equal(web3.utils.hexToString(log.args._name), "GeneralTransferManager"); }); - it("Should intialize the auto attached modules", async () => { - let moduleData = (await I_SecurityToken.getModulesByType(TMKEY))[0]; - I_GeneralTransferManager = GeneralTransferManager.at(moduleData); + it("Should initialize the auto attached modules", async () => { + let moduleData = (await stGetter.getModulesByType(TMKEY))[0]; + I_GeneralTransferManager = await GeneralTransferManager.at(moduleData); }); }); @@ -290,17 +311,17 @@ contract("USDTieredSTO", accounts => { it("Should successfully attach the first STO module to the security token", async () => { let stoId = 0; // No discount - _startTime.push(latestTime() + duration.days(2)); - _endTime.push(_startTime[stoId] + duration.days(100)); - _ratePerTier.push([BigNumber(10 * 10 ** 16), BigNumber(15 * 10 ** 16)]); // [ 0.10 USD/Token, 0.15 USD/Token ] - _ratePerTierDiscountPoly.push([BigNumber(10 * 10 ** 16), BigNumber(15 * 10 ** 16)]); // [ 0.10 USD/Token, 0.15 USD/Token ] - _tokensPerTierTotal.push([BigNumber(100000000).mul(new BigNumber(10 ** 18)), BigNumber(200000000).mul(new BigNumber(10 ** 18))]); // [ 100m Token, 200m Token ] - _tokensPerTierDiscountPoly.push([BigNumber(0), BigNumber(0)]); // [ 0, 0 ] - _nonAccreditedLimitUSD.push(new BigNumber(10000).mul(new BigNumber(10 ** 18))); // 10k USD - _minimumInvestmentUSD.push(new BigNumber(5 * 10 ** 18)); // 5 USD + _startTime.push(new BN(currentTime).add(new BN(duration.days(2)))); + _endTime.push(_startTime[stoId].add(new BN(duration.days(100)))); + _ratePerTier.push([new BN(10).mul(e16), new BN(15).mul(e16)]); // [ 0.10 USD/Token, 0.15 USD/Token ] + _ratePerTierDiscountPoly.push([new BN(10).mul(e16), new BN(15).mul(e16)]); // [ 0.10 USD/Token, 0.15 USD/Token ] + _tokensPerTierTotal.push([new BN(100000000).mul(new BN(e18)), new BN(200000000).mul(new BN(e18))]); // [ 100m Token, 200m Token ] + _tokensPerTierDiscountPoly.push([new BN(0), new BN(0)]); // [ new BN(0), 0 ] + _nonAccreditedLimitUSD.push(new BN(10000).mul(new BN(e18))); // 10k USD + _minimumInvestmentUSD.push(new BN(5).mul(e18)); // 5 USD _fundRaiseTypes.push([0, 1, 2]); _wallet.push(WALLET); - _reserveWallet.push(RESERVEWALLET); + _treasuryWallet.push(TREASURYWALLET); _usdToken.push([I_DaiToken.address]); let config = [ @@ -314,67 +335,67 @@ contract("USDTieredSTO", accounts => { _minimumInvestmentUSD[stoId], _fundRaiseTypes[stoId], _wallet[stoId], - _reserveWallet[stoId], + _treasuryWallet[stoId], _usdToken[stoId] ]; let bytesSTO = web3.eth.abi.encodeFunctionCall(functionSignature, config); - let tx = await I_SecurityToken.addModule(I_USDTieredSTOFactory.address, bytesSTO, 0, 0, { from: ISSUER, gasPrice: GAS_PRICE }); + let tx = await I_SecurityToken.addModule(I_USDTieredSTOFactory.address, bytesSTO, new BN(0), new BN(0), false, { from: ISSUER, gasPrice: GAS_PRICE }); console.log(" Gas addModule: ".grey + tx.receipt.gasUsed.toString().grey); assert.equal(tx.logs[2].args._types[0], STOKEY, "USDTieredSTO doesn't get deployed"); assert.equal(web3.utils.hexToString(tx.logs[2].args._name), "USDTieredSTO", "USDTieredSTOFactory module was not added"); - I_USDTieredSTO_Array.push(USDTieredSTO.at(tx.logs[2].args._module)); + I_USDTieredSTO_Array.push(await USDTieredSTO.at(tx.logs[2].args._module)); - assert.equal((await I_USDTieredSTO_Array[stoId].startTime.call()).toNumber(), _startTime[stoId], "Incorrect _startTime in config"); - assert.equal(await I_USDTieredSTO_Array[stoId].endTime.call(), _endTime[stoId], "Incorrect _endTime in config"); + assert.equal((await I_USDTieredSTO_Array[stoId].startTime.call()).toString(), _startTime[stoId].toString(), "Incorrect _startTime in config"); + assert.equal((await I_USDTieredSTO_Array[stoId].endTime.call()).toString(), _endTime[stoId].toString(), "Incorrect _endTime in config"); for (var i = 0; i < _ratePerTier[stoId].length; i++) { assert.equal( - (await I_USDTieredSTO_Array[stoId].tiers.call(i))[0].toNumber(), - _ratePerTier[stoId][i].toNumber(), + (await I_USDTieredSTO_Array[stoId].tiers.call(i))[0].toString(), + _ratePerTier[stoId][i].toString(), "Incorrect _ratePerTier in config" ); assert.equal( - (await I_USDTieredSTO_Array[stoId].tiers.call(i))[1].toNumber(), - _ratePerTierDiscountPoly[stoId][i].toNumber(), + (await I_USDTieredSTO_Array[stoId].tiers.call(i))[1].toString(), + _ratePerTierDiscountPoly[stoId][i].toString(), "Incorrect _ratePerTierDiscountPoly in config" ); assert.equal( - (await I_USDTieredSTO_Array[stoId].tiers.call(i))[2].toNumber(), - _tokensPerTierTotal[stoId][i].toNumber(), + (await I_USDTieredSTO_Array[stoId].tiers.call(i))[2].toString(), + _tokensPerTierTotal[stoId][i].toString(), "Incorrect _tokensPerTierTotal in config" ); assert.equal( - (await I_USDTieredSTO_Array[stoId].tiers.call(i))[3].toNumber(), - _tokensPerTierDiscountPoly[stoId][i].toNumber(), + (await I_USDTieredSTO_Array[stoId].tiers.call(i))[3].toString(), + _tokensPerTierDiscountPoly[stoId][i].toString(), "Incorrect _tokensPerTierDiscountPoly in config" ); } assert.equal( - (await I_USDTieredSTO_Array[stoId].nonAccreditedLimitUSD.call()).toNumber(), - _nonAccreditedLimitUSD[stoId].toNumber(), + (await I_USDTieredSTO_Array[stoId].nonAccreditedLimitUSD.call()).toString(), + _nonAccreditedLimitUSD[stoId].toString(), "Incorrect _nonAccreditedLimitUSD in config" ); assert.equal( - (await I_USDTieredSTO_Array[stoId].minimumInvestmentUSD.call()).toNumber(), - _minimumInvestmentUSD[stoId].toNumber(), + (await I_USDTieredSTO_Array[stoId].minimumInvestmentUSD.call()).toString(), + _minimumInvestmentUSD[stoId].toString(), "Incorrect _minimumInvestmentUSD in config" ); assert.equal(await I_USDTieredSTO_Array[stoId].wallet.call(), _wallet[stoId], "Incorrect _wallet in config"); assert.equal( - await I_USDTieredSTO_Array[stoId].reserveWallet.call(), - _reserveWallet[stoId], - "Incorrect _reserveWallet in config" + await I_USDTieredSTO_Array[stoId].treasuryWallet.call(), + _treasuryWallet[stoId], + "Incorrect _treasuryWallet in config" ); - assert.equal(await I_USDTieredSTO_Array[stoId].usdTokens.call(0), _usdToken[stoId][0], "Incorrect _usdToken in config"); + assert.equal((await I_USDTieredSTO_Array[stoId].getUsdTokens())[0], _usdToken[stoId][0], "Incorrect _usdToken in config"); assert.equal( await I_USDTieredSTO_Array[stoId].getNumberOfTiers(), _tokensPerTierTotal[stoId].length, "Incorrect number of tiers" ); - assert.equal((await I_USDTieredSTO_Array[stoId].getPermissions()).length, 0, "Incorrect number of permissions"); + assert.equal((await I_USDTieredSTO_Array[stoId].getPermissions()).length, new BN(2), "Incorrect number of permissions"); }); - it("Should attach the paid STO factory -- failed because of no tokens", async() => { + it("Should attach the paid STO factory -- failed because of no tokens", async () => { let stoId = 0; // No discount let config = [ _startTime[stoId], @@ -387,17 +408,20 @@ contract("USDTieredSTO", accounts => { _minimumInvestmentUSD[stoId], _fundRaiseTypes[stoId], _wallet[stoId], - _reserveWallet[stoId], + _treasuryWallet[stoId], _usdToken[stoId] ]; let bytesSTO = web3.eth.abi.encodeFunctionCall(functionSignature, config); await catchRevert( - I_SecurityToken.addModule(P_USDTieredSTOFactory.address, bytesSTO, web3.utils.toWei("500"), 0, { from: ISSUER, gasPrice: GAS_PRICE }) + I_SecurityToken.addModule(P_USDTieredSTOFactory.address, bytesSTO, new BN(web3.utils.toWei("2000")), new BN(0), false, { + from: ISSUER, + gasPrice: GAS_PRICE + }) ); }); - it("Should attach the paid STO factory", async() => { + it("Should attach the paid STO factory", async () => { let snapId = await takeSnapshot(); let stoId = 0; // No discount let config = [ @@ -411,13 +435,16 @@ contract("USDTieredSTO", accounts => { _minimumInvestmentUSD[stoId], _fundRaiseTypes[stoId], _wallet[stoId], - _reserveWallet[stoId], + _treasuryWallet[stoId], _usdToken[stoId] ]; let bytesSTO = web3.eth.abi.encodeFunctionCall(functionSignature, config); - await I_PolyToken.getTokens(web3.utils.toWei("500"), I_SecurityToken.address); - let tx = await I_SecurityToken.addModule(P_USDTieredSTOFactory.address, bytesSTO, web3.utils.toWei("500"), 0, { from: ISSUER, gasPrice: GAS_PRICE }); + await I_PolyToken.getTokens(new BN(web3.utils.toWei("2000")), I_SecurityToken.address); + let tx = await I_SecurityToken.addModule(P_USDTieredSTOFactory.address, bytesSTO, new BN(web3.utils.toWei("2000")), new BN(0), false, { + from: ISSUER, + gasPrice: GAS_PRICE + }); await revertToSnapshot(snapId); }); @@ -429,63 +456,61 @@ contract("USDTieredSTO", accounts => { }); it("Should allow non-matching beneficiary -- failed because it is already active", async () => { - await catchRevert( - I_USDTieredSTO_Array[0].changeAllowBeneficialInvestments(true, { from: ISSUER }) - ); + await catchRevert(I_USDTieredSTO_Array[0].changeAllowBeneficialInvestments(true, { from: ISSUER })); await revertToSnapshot(snapId); }); - it("Should successfully call the modifyTimes before starting the STO -- fail because of bad owner", async() => { + it("Should successfully call the modifyTimes before starting the STO -- fail because of bad owner", async () => { await catchRevert( - I_USDTieredSTO_Array[0].modifyTimes(latestTime() + duration.days(15), latestTime() + duration.days(55), { from: POLYMATH }) + I_USDTieredSTO_Array[0].modifyTimes(new BN(currentTime).add(new BN(duration.days(15))), new BN(currentTime).add(new BN(duration.days(55))), { from: POLYMATH }) ); - }) + }); - it("Should successfully call the modifyTimes before starting the STO", async() => { + it("Should successfully call the modifyTimes before starting the STO", async () => { let snapId = await takeSnapshot(); - let _startTime = latestTime() + duration.days(15); - let _endTime = latestTime() + duration.days(55) + let _startTime = new BN(currentTime).add(new BN(duration.days(15))); + let _endTime = new BN(currentTime).add(new BN(duration.days(55))); await I_USDTieredSTO_Array[0].modifyTimes(_startTime, _endTime, { from: ISSUER }); - assert.equal(await I_USDTieredSTO_Array[0].startTime.call(), _startTime, "Incorrect _startTime in config"); - assert.equal(await I_USDTieredSTO_Array[0].endTime.call(), _endTime, "Incorrect _endTime in config"); + assert.equal((await I_USDTieredSTO_Array[0].startTime.call()).toString(), _startTime.toString(), "Incorrect _startTime in config"); + assert.equal((await I_USDTieredSTO_Array[0].endTime.call()).toString(), _endTime.toString(), "Incorrect _endTime in config"); await revertToSnapshot(snapId); }); it("Should successfully attach the second STO module to the security token", async () => { let stoId = 1; // No discount - _startTime.push(latestTime() + duration.days(2)); - _endTime.push(_startTime[stoId] + duration.days(100)); + _startTime.push(new BN(currentTime).add(new BN(duration.days(2)))); + _endTime.push(new BN(_startTime[stoId]).add(new BN(currentTime).add(new BN(duration.days(100))))); _ratePerTier.push([ - BigNumber(10 * 10 ** 16), - BigNumber(15 * 10 ** 16), - BigNumber(15 * 10 ** 16), - BigNumber(15 * 10 ** 16), - BigNumber(15 * 10 ** 16), - BigNumber(15 * 10 ** 16) + new BN(10).mul(e16), + new BN(15).mul(e16), + new BN(15).mul(e16), + new BN(15).mul(e16), + new BN(15).mul(e16), + new BN(15).mul(e16) ]); _ratePerTierDiscountPoly.push([ - BigNumber(10 * 10 ** 16), - BigNumber(15 * 10 ** 16), - BigNumber(15 * 10 ** 16), - BigNumber(15 * 10 ** 16), - BigNumber(15 * 10 ** 16), - BigNumber(15 * 10 ** 16) + new BN(10).mul(e16), + new BN(15).mul(e16), + new BN(15).mul(e16), + new BN(15).mul(e16), + new BN(15).mul(e16), + new BN(15).mul(e16) ]); _tokensPerTierTotal.push([ - BigNumber(5 * 10 ** 18), - BigNumber(10 * 10 ** 18), - BigNumber(10 * 10 ** 18), - BigNumber(10 * 10 ** 18), - BigNumber(10 * 10 ** 18), - BigNumber(50 * 10 ** 18) + new BN(5).mul(e18), + new BN(10).mul(e18), + new BN(10).mul(e18), + new BN(10).mul(e18), + new BN(10).mul(e18), + new BN(50).mul(e18) ]); - _tokensPerTierDiscountPoly.push([BigNumber(0), BigNumber(0), BigNumber(0), BigNumber(0), BigNumber(0), BigNumber(0)]); - _nonAccreditedLimitUSD.push(new BigNumber(10000).mul(new BigNumber(10 ** 18))); - _minimumInvestmentUSD.push(new BigNumber(0)); + _tokensPerTierDiscountPoly.push([new BN(0), new BN(0), new BN(0), new BN(0), new BN(0), new BN(0)]); + _nonAccreditedLimitUSD.push(new BN(10000).mul(new BN(e18))); + _minimumInvestmentUSD.push(new BN(0)); _fundRaiseTypes.push([0, 1, 2]); _wallet.push(WALLET); - _reserveWallet.push(RESERVEWALLET); + _treasuryWallet.push(TREASURYWALLET); _usdToken.push([I_DaiToken.address]); let config = [ @@ -499,82 +524,80 @@ contract("USDTieredSTO", accounts => { _minimumInvestmentUSD[stoId], _fundRaiseTypes[stoId], _wallet[stoId], - _reserveWallet[stoId], + _treasuryWallet[stoId], _usdToken[stoId] ]; let bytesSTO = web3.eth.abi.encodeFunctionCall(functionSignature, config); - let tx = await I_SecurityToken.addModule(I_USDTieredSTOFactory.address, bytesSTO, 0, 0, { from: ISSUER, gasPrice: GAS_PRICE }); + let tx = await I_SecurityToken.addModule(I_USDTieredSTOFactory.address, bytesSTO, new BN(0), new BN(0), false, { from: ISSUER, gasPrice: GAS_PRICE }); console.log(" Gas addModule: ".grey + tx.receipt.gasUsed.toString().grey); assert.equal(tx.logs[2].args._types[0], STOKEY, "USDTieredSTO doesn't get deployed"); assert.equal(web3.utils.hexToString(tx.logs[2].args._name), "USDTieredSTO", "USDTieredSTOFactory module was not added"); - I_USDTieredSTO_Array.push(USDTieredSTO.at(tx.logs[2].args._module)); + I_USDTieredSTO_Array.push(await USDTieredSTO.at(tx.logs[2].args._module)); - assert.equal(await I_USDTieredSTO_Array[stoId].startTime.call(), _startTime[stoId], "Incorrect _startTime in config"); - assert.equal(await I_USDTieredSTO_Array[stoId].endTime.call(), _endTime[stoId], "Incorrect _endTime in config"); + assert.equal((await I_USDTieredSTO_Array[stoId].startTime.call()).toString(), _startTime[stoId].toString(), "Incorrect _startTime in config"); + assert.equal((await I_USDTieredSTO_Array[stoId].endTime.call()).toString(), _endTime[stoId].toString(), "Incorrect _endTime in config"); for (var i = 0; i < _ratePerTier[stoId].length; i++) { assert.equal( - (await I_USDTieredSTO_Array[stoId].tiers.call(i))[0].toNumber(), - _ratePerTier[stoId][i].toNumber(), + (await I_USDTieredSTO_Array[stoId].tiers.call(i))[0].toString(), + _ratePerTier[stoId][i].toString(), "Incorrect _ratePerTier in config" ); assert.equal( - (await I_USDTieredSTO_Array[stoId].tiers.call(i))[1].toNumber(), - _ratePerTierDiscountPoly[stoId][i].toNumber(), + (await I_USDTieredSTO_Array[stoId].tiers.call(i))[1].toString(), + _ratePerTierDiscountPoly[stoId][i].toString(), "Incorrect _ratePerTierDiscountPoly in config" ); assert.equal( - (await I_USDTieredSTO_Array[stoId].tiers.call(i))[2].toNumber(), - _tokensPerTierTotal[stoId][i].toNumber(), + (await I_USDTieredSTO_Array[stoId].tiers.call(i))[2].toString(), + _tokensPerTierTotal[stoId][i].toString(), "Incorrect _tokensPerTierTotal in config" ); assert.equal( - (await I_USDTieredSTO_Array[stoId].tiers.call(i))[3].toNumber(), - _tokensPerTierDiscountPoly[stoId][i].toNumber(), + (await I_USDTieredSTO_Array[stoId].tiers.call(i))[3].toString(), + _tokensPerTierDiscountPoly[stoId][i].toString(), "Incorrect _tokensPerTierDiscountPoly in config" ); } assert.equal( - (await I_USDTieredSTO_Array[stoId].nonAccreditedLimitUSD.call()).toNumber(), - _nonAccreditedLimitUSD[stoId].toNumber(), + (await I_USDTieredSTO_Array[stoId].nonAccreditedLimitUSD.call()).toString(), + _nonAccreditedLimitUSD[stoId].toString(), "Incorrect _nonAccreditedLimitUSD in config" ); assert.equal( - (await I_USDTieredSTO_Array[stoId].minimumInvestmentUSD.call()).toNumber(), - _minimumInvestmentUSD[stoId].toNumber(), + (await I_USDTieredSTO_Array[stoId].minimumInvestmentUSD.call()).toString(), + _minimumInvestmentUSD[stoId].toString(), "Incorrect _minimumInvestmentUSD in config" ); assert.equal(await I_USDTieredSTO_Array[stoId].wallet.call(), _wallet[stoId], "Incorrect _wallet in config"); assert.equal( - await I_USDTieredSTO_Array[stoId].reserveWallet.call(), - _reserveWallet[stoId], - "Incorrect _reserveWallet in config" + await I_USDTieredSTO_Array[stoId].treasuryWallet.call(), + _treasuryWallet[stoId], + "Incorrect _treasuryWallet in config" ); - assert.equal(await I_USDTieredSTO_Array[stoId].usdTokens.call(0), _usdToken[stoId][0], "Incorrect _usdToken in config"); + assert.equal((await I_USDTieredSTO_Array[stoId].getUsdTokens())[0], _usdToken[stoId][0], "Incorrect _usdToken in config"); assert.equal( await I_USDTieredSTO_Array[stoId].getNumberOfTiers(), _tokensPerTierTotal[stoId].length, "Incorrect number of tiers" ); - assert.equal((await I_USDTieredSTO_Array[stoId].getPermissions()).length, 0, "Incorrect number of permissions"); + assert.equal((await I_USDTieredSTO_Array[stoId].getPermissions()).length, new BN(2), "Incorrect number of permissions"); }); it("Should successfully attach the third STO module to the security token", async () => { let stoId = 2; // Poly discount - - _startTime.push(latestTime() + duration.days(2)); - _endTime.push(_startTime[stoId] + duration.days(100)); - _ratePerTier.push([BigNumber(1 * 10 ** 18), BigNumber(1.5 * 10 ** 18)]); // [ 1 USD/Token, 1.5 USD/Token ] - _ratePerTierDiscountPoly.push([BigNumber(0.5 * 10 ** 18), BigNumber(1 * 10 ** 18)]); // [ 0.5 USD/Token, 1.5 USD/Token ] - _tokensPerTierTotal.push([BigNumber(100 * 10 ** 18), BigNumber(50 * 10 ** 18)]); // [ 100 Token, 50 Token ] - _tokensPerTierDiscountPoly.push([BigNumber(100 * 10 ** 18), BigNumber(25 * 10 ** 18)]); // [ 100 Token, 25 Token ] - _nonAccreditedLimitUSD.push(new BigNumber(25 * 10 ** 18)); // [ 25 USD ] - _minimumInvestmentUSD.push(new BigNumber(5)); + _startTime.push(new BN(currentTime).add(new BN(duration.days(2)))); + _endTime.push(new BN(_startTime[stoId]).add(new BN(currentTime).add(new BN(duration.days(100))))); + _ratePerTier.push([new BN(1).mul(e18), new BN(150).mul(e16)]); // [ 1 USD/Token, 1.5 USD/Token ] + _ratePerTierDiscountPoly.push([new BN(50).mul(e16), new BN(1).mul(e18)]); // [ 0.5 USD/Token, 1.5 USD/Token ] + _tokensPerTierTotal.push([new BN(100).mul(e18), new BN(50).mul(e18)]); // [ 100 Token, 50 Token ] + _tokensPerTierDiscountPoly.push([new BN(100).mul(e18), new BN(25).mul(e18)]); // [ 100 Token, 25 Token ] + _nonAccreditedLimitUSD.push(new BN(25).mul(e18)); // [ 25 USD ] + _minimumInvestmentUSD.push(new BN(5)); _fundRaiseTypes.push([0, 1, 2]); _wallet.push(WALLET); - _reserveWallet.push(RESERVEWALLET); + _treasuryWallet.push(TREASURYWALLET); _usdToken.push([I_DaiToken.address]); - let config = [ _startTime[stoId], _endTime[stoId], @@ -586,32 +609,31 @@ contract("USDTieredSTO", accounts => { _minimumInvestmentUSD[stoId], _fundRaiseTypes[stoId], _wallet[stoId], - _reserveWallet[stoId], + _treasuryWallet[stoId], _usdToken[stoId] ]; - let bytesSTO = web3.eth.abi.encodeFunctionCall(functionSignature, config); - let tx = await I_SecurityToken.addModule(I_USDTieredSTOFactory.address, bytesSTO, 0, 0, { from: ISSUER, gasPrice: GAS_PRICE }); + let tx = await I_SecurityToken.addModule(I_USDTieredSTOFactory.address, bytesSTO, new BN(0), new BN(0), false, { from: ISSUER, gasPrice: GAS_PRICE }); console.log(" Gas addModule: ".grey + tx.receipt.gasUsed.toString().grey); assert.equal(tx.logs[2].args._types[0], STOKEY, "USDTieredSTO doesn't get deployed"); assert.equal(web3.utils.hexToString(tx.logs[2].args._name), "USDTieredSTO", "USDTieredSTOFactory module was not added"); - I_USDTieredSTO_Array.push(USDTieredSTO.at(tx.logs[2].args._module)); + I_USDTieredSTO_Array.push(await USDTieredSTO.at(tx.logs[2].args._module)); }); it("Should successfully attach the fourth STO module to the security token", async () => { let stoId = 3; - _startTime.push(latestTime() + duration.days(0.1)); - _endTime.push(_startTime[stoId] + duration.days(0.1)); - _ratePerTier.push([BigNumber(10 * 10 ** 16), BigNumber(15 * 10 ** 16)]); - _ratePerTierDiscountPoly.push([BigNumber(10 * 10 ** 16), BigNumber(12 * 10 ** 16)]); - _tokensPerTierTotal.push([BigNumber(100 * 10 ** 18), BigNumber(200 * 10 ** 18)]); - _tokensPerTierDiscountPoly.push([BigNumber(0), BigNumber(50 * 10 ** 18)]); - _nonAccreditedLimitUSD.push(new BigNumber(10000).mul(new BigNumber(10 ** 18))); - _minimumInvestmentUSD.push(new BigNumber(0)); + _startTime.push(new BN(currentTime).add(new BN(duration.days(0.1)))); + _endTime.push(new BN(_startTime[stoId]).add(new BN(currentTime).add(new BN(duration.days(0.1))))); + _ratePerTier.push([new BN(10).mul(e16), new BN(15).mul(e16)]); + _ratePerTierDiscountPoly.push([new BN(10).mul(e16), new BN(12).mul(e16)]); + _tokensPerTierTotal.push([new BN(100).mul(e18), new BN(200).mul( e18)]); + _tokensPerTierDiscountPoly.push([new BN(0), new BN(50).mul( e18)]); + _nonAccreditedLimitUSD.push(new BN(10000).mul(new BN(e18))); + _minimumInvestmentUSD.push(new BN(0)); _fundRaiseTypes.push([0, 1, 2]); _wallet.push(WALLET); - _reserveWallet.push(RESERVEWALLET); + _treasuryWallet.push(TREASURYWALLET); _usdToken.push([I_DaiToken.address]); let config = [ @@ -625,34 +647,32 @@ contract("USDTieredSTO", accounts => { _minimumInvestmentUSD[stoId], _fundRaiseTypes[stoId], _wallet[stoId], - _reserveWallet[stoId], + _treasuryWallet[stoId], _usdToken[stoId] ]; let bytesSTO = web3.eth.abi.encodeFunctionCall(functionSignature, config); - let tx = await I_SecurityToken.addModule(I_USDTieredSTOFactory.address, bytesSTO, 0, 0, { from: ISSUER, gasPrice: GAS_PRICE }); + let tx = await I_SecurityToken.addModule(I_USDTieredSTOFactory.address, bytesSTO, new BN(0), new BN(0), false, { from: ISSUER, gasPrice: GAS_PRICE }); console.log(" Gas addModule: ".grey + tx.receipt.gasUsed.toString().grey); assert.equal(tx.logs[2].args._types[0], STOKEY, "USDTieredSTO doesn't get deployed"); assert.equal(web3.utils.hexToString(tx.logs[2].args._name), "USDTieredSTO", "USDTieredSTOFactory module was not added"); - I_USDTieredSTO_Array.push(USDTieredSTO.at(tx.logs[2].args._module)); - let tokens = await I_USDTieredSTO_Array[I_USDTieredSTO_Array.length - 1].getUsdTokens.call(); - assert.equal(tokens[0], I_DaiToken.address, "USD Tokens should match"); + I_USDTieredSTO_Array.push(await USDTieredSTO.at(tx.logs[2].args._module)); }); it("Should successfully attach the fifth STO module to the security token", async () => { let stoId = 4; // Non-divisible tokens - _startTime.push(latestTime() + duration.days(2)); - _endTime.push(_startTime[stoId] + duration.days(100)); - _ratePerTier.push([BigNumber(1 * 10 ** 18), BigNumber(1.5 * 10 ** 18)]); // [ 1 USD/Token, 1.5 USD/Token ] - _ratePerTierDiscountPoly.push([BigNumber(0.5 * 10 ** 18), BigNumber(1 * 10 ** 18)]); // [ 0.5 USD/Token, 1.5 USD/Token ] - _tokensPerTierTotal.push([BigNumber(100 * 10 ** 18), BigNumber(50 * 10 ** 18)]); // [ 100 Token, 50 Token ] - _tokensPerTierDiscountPoly.push([BigNumber(100 * 10 ** 18), BigNumber(25 * 10 ** 18)]); // [ 100 Token, 25 Token ] - _nonAccreditedLimitUSD.push(BigNumber(25 * 10 ** 18)); // [ 25 USD ] - _minimumInvestmentUSD.push(BigNumber(5)); + _startTime.push(new BN(currentTime).add(new BN(duration.days(2)))); + _endTime.push(new BN(_startTime[stoId]).add(new BN(currentTime).add(new BN(duration.days(100))))); + _ratePerTier.push([new BN(1).mul(e18), new BN(150).mul(e16)]); // [ 1 USD/Token, 1.5 USD/Token ] + _ratePerTierDiscountPoly.push([new BN(50).mul(e16), new BN(1).mul(e18)]); // [ 0.5 USD/Token, 1.5 USD/Token ] + _tokensPerTierTotal.push([new BN(100).mul(e18), new BN(50).mul( e18)]); // [ 100 Token, 50 Token ] + _tokensPerTierDiscountPoly.push([new BN(100).mul(e18), new BN(25).mul(e18)]); // [ 100 Token, 25 Token ] + _nonAccreditedLimitUSD.push(new BN(25).mul(e18)); // [ 25 USD ] + _minimumInvestmentUSD.push(new BN(5)); _fundRaiseTypes.push([0, 1, 2]); _wallet.push(WALLET); - _reserveWallet.push(RESERVEWALLET); + _treasuryWallet.push(TREASURYWALLET); _usdToken.push([I_DaiToken.address]); let config = [ @@ -666,32 +686,35 @@ contract("USDTieredSTO", accounts => { _minimumInvestmentUSD[stoId], _fundRaiseTypes[stoId], _wallet[stoId], - _reserveWallet[stoId], + _treasuryWallet[stoId], _usdToken[stoId] ]; let bytesSTO = web3.eth.abi.encodeFunctionCall(functionSignature, config); - let tx = await I_SecurityToken.addModule(I_USDTieredSTOFactory.address, bytesSTO, 0, 0, { from: ISSUER, gasPrice: GAS_PRICE }); + let tx = await I_SecurityToken.addModule(I_USDTieredSTOFactory.address, bytesSTO, 0, 0, false, { from: ISSUER, gasPrice: GAS_PRICE }); console.log(" Gas addModule: ".grey + tx.receipt.gasUsed.toString().grey); assert.equal(tx.logs[2].args._types[0], STOKEY, "USDTieredSTO doesn't get deployed"); assert.equal(web3.utils.hexToString(tx.logs[2].args._name), "USDTieredSTO", "USDTieredSTOFactory module was not added"); - I_USDTieredSTO_Array.push(USDTieredSTO.at(tx.logs[2].args._module)); + I_USDTieredSTO_Array.push(await USDTieredSTO.at(tx.logs[2].args._module)); + // console.log(I_USDTieredSTO_Array[I_USDTieredSTO_Array.length - 1]); + let tokens = await I_USDTieredSTO_Array[I_USDTieredSTO_Array.length - 1].getUsdTokens.call(); + assert.equal(tokens[0], I_DaiToken.address, "USD Tokens should match"); }); it("Should successfully attach the sixth STO module to the security token", async () => { - let stoId = 5; // Non-divisible tokens with bad granularity in tiers - - _startTime.push(latestTime() + duration.days(2)); - _endTime.push(_startTime[stoId] + duration.days(100)); - _ratePerTier.push([BigNumber(1 * 10 ** 18), BigNumber(1 * 10 ** 18)]); // [ 1 USD/Token, 1 USD/Token ] - _ratePerTierDiscountPoly.push([BigNumber(1 * 10 ** 18), BigNumber(1 * 10 ** 18)]); // [ 1 USD/Token, 1 USD/Token ] - _tokensPerTierTotal.push([BigNumber(1001 * 10 ** 17), BigNumber(50 * 10 ** 18)]); // [ 100.1 Token, 50 Token ] - _tokensPerTierDiscountPoly.push([BigNumber(0), BigNumber(0)]); // [ 0 Token, 0 Token ] - _nonAccreditedLimitUSD.push(BigNumber(25 * 10 ** 18)); // [ 25 USD ] - _minimumInvestmentUSD.push(BigNumber(5)); + let stoId = 5; // Non-divisible token with invalid tier + + _startTime.push(new BN(currentTime).add(new BN(duration.days(2)))); + _endTime.push(new BN(_startTime[stoId]).add(new BN(currentTime).add(new BN(duration.days(100))))); + _ratePerTier.push([new BN(1).mul(e18), new BN(1).mul(e18)]); // [ 1 USD/Token, 1 USD/Token ] + _ratePerTierDiscountPoly.push([new BN(1).mul(e18), new BN(1).mul(e18)]); // [ 1 USD/Token, 1 USD/Token ] + _tokensPerTierTotal.push([new BN(10010).mul(e16), new BN(50).mul(e18)]); // [ 100.1 Token, 50 Token ] + _tokensPerTierDiscountPoly.push([new BN(0), new BN(0)]); // [ 0 Token, 0 Token ] + _nonAccreditedLimitUSD.push(new BN(25).mul(e18)); // [ 25 USD ] + _minimumInvestmentUSD.push(new BN(5)); _fundRaiseTypes.push([0, 1, 2]); _wallet.push(WALLET); - _reserveWallet.push(RESERVEWALLET); + _treasuryWallet.push(TREASURYWALLET); _usdToken.push([I_DaiToken.address]); let config = [ @@ -705,16 +728,19 @@ contract("USDTieredSTO", accounts => { _minimumInvestmentUSD[stoId], _fundRaiseTypes[stoId], _wallet[stoId], - _reserveWallet[stoId], + _treasuryWallet[stoId], _usdToken[stoId] ]; let bytesSTO = web3.eth.abi.encodeFunctionCall(functionSignature, config); - let tx = await I_SecurityToken.addModule(I_USDTieredSTOFactory.address, bytesSTO, 0, 0, { from: ISSUER, gasPrice: GAS_PRICE }); + let tx = await I_SecurityToken.addModule(I_USDTieredSTOFactory.address, bytesSTO, 0, 0, false, { from: ISSUER, gasPrice: GAS_PRICE }); console.log(" Gas addModule: ".grey + tx.receipt.gasUsed.toString().grey); assert.equal(tx.logs[2].args._types[0], STOKEY, "USDTieredSTO doesn't get deployed"); assert.equal(web3.utils.hexToString(tx.logs[2].args._name), "USDTieredSTO", "USDTieredSTOFactory module was not added"); - I_USDTieredSTO_Array.push(USDTieredSTO.at(tx.logs[2].args._module)); + I_USDTieredSTO_Array.push(await USDTieredSTO.at(tx.logs[2].args._module)); + // console.log(I_USDTieredSTO_Array[I_USDTieredSTO_Array.length - 1]); + let tokens = await I_USDTieredSTO_Array[I_USDTieredSTO_Array.length - 1].getUsdTokens.call(); + assert.equal(tokens[0], I_DaiToken.address, "USD Tokens should match"); }); it("Should fail because rates and tier array of different length", async () => { @@ -736,7 +762,7 @@ contract("USDTieredSTO", accounts => { _minimumInvestmentUSD[stoId], _fundRaiseTypes[stoId], _wallet[stoId], - _reserveWallet[stoId], + _treasuryWallet[stoId], _usdToken[stoId] ], [ @@ -750,7 +776,7 @@ contract("USDTieredSTO", accounts => { _minimumInvestmentUSD[stoId], _fundRaiseTypes[stoId], _wallet[stoId], - _reserveWallet[stoId], + _treasuryWallet[stoId], _usdToken[stoId] ], [ @@ -764,7 +790,7 @@ contract("USDTieredSTO", accounts => { _minimumInvestmentUSD[stoId], _fundRaiseTypes[stoId], _wallet[stoId], - _reserveWallet[stoId], + _treasuryWallet[stoId], _usdToken[stoId] ], [ @@ -778,21 +804,21 @@ contract("USDTieredSTO", accounts => { _minimumInvestmentUSD[stoId], _fundRaiseTypes[stoId], _wallet[stoId], - _reserveWallet[stoId], + _treasuryWallet[stoId], _usdToken[stoId] ] ]; for (var i = 0; i < config.length; i++) { let bytesSTO = web3.eth.abi.encodeFunctionCall(functionSignature, config[i]); - await catchRevert(I_SecurityToken.addModule(I_USDTieredSTOFactory.address, bytesSTO, 0, 0, { from: ISSUER })); + await catchRevert(I_SecurityToken.addModule(I_USDTieredSTOFactory.address, bytesSTO, new BN(0), new BN(0), false, { from: ISSUER })); } }); it("Should fail because rate of token should be greater than 0", async () => { let stoId = 0; - let ratePerTier = [BigNumber(10 * 10 ** 16), BigNumber(0)]; + let ratePerTier = [new BN(10).mul(e16), new BN(0)]; let config = [ _startTime[stoId], _endTime[stoId], @@ -804,12 +830,12 @@ contract("USDTieredSTO", accounts => { _minimumInvestmentUSD[stoId], _fundRaiseTypes[stoId], _wallet[stoId], - _reserveWallet[stoId], + _treasuryWallet[stoId], _usdToken[stoId] ]; let bytesSTO = web3.eth.abi.encodeFunctionCall(functionSignature, config); - await catchRevert(I_SecurityToken.addModule(I_USDTieredSTOFactory.address, bytesSTO, 0, 0, { from: ISSUER })); + await catchRevert(I_SecurityToken.addModule(I_USDTieredSTOFactory.address, bytesSTO, new BN(0), new BN(0), false, { from: ISSUER })); }); it("Should fail because Zero address is not permitted for wallet", async () => { @@ -827,42 +853,19 @@ contract("USDTieredSTO", accounts => { _minimumInvestmentUSD[stoId], _fundRaiseTypes[stoId], wallet, - _reserveWallet[stoId], + _treasuryWallet[stoId], _usdToken[stoId] ]; let bytesSTO = web3.eth.abi.encodeFunctionCall(functionSignature, config); - await catchRevert(I_SecurityToken.addModule(I_USDTieredSTOFactory.address, bytesSTO, 0, 0, { from: ISSUER })); - }); - - it("Should fail because Zero address is not permitted for reserveWallet", async () => { - let stoId = 0; - - let reserveWallet = address_zero; - let config = [ - _startTime[stoId], - _endTime[stoId], - _ratePerTier[stoId], - _ratePerTierDiscountPoly[stoId], - _tokensPerTierTotal[stoId], - _tokensPerTierDiscountPoly[stoId], - _nonAccreditedLimitUSD[stoId], - _minimumInvestmentUSD[stoId], - _fundRaiseTypes[stoId], - _wallet[stoId], - reserveWallet, - _usdToken[stoId] - ]; - let bytesSTO = web3.eth.abi.encodeFunctionCall(functionSignature, config); - - await catchRevert(I_SecurityToken.addModule(I_USDTieredSTOFactory.address, bytesSTO, 0, 0, { from: ISSUER })); + await catchRevert(I_SecurityToken.addModule(I_USDTieredSTOFactory.address, bytesSTO, new BN(0), new BN(0), false, { from: ISSUER })); }); it("Should fail because end time before start time", async () => { let stoId = 0; - let startTime = latestTime() + duration.days(35); - let endTime = latestTime() + duration.days(1); + let startTime = await latestTime() + duration.days(35); + let endTime = await latestTime() + duration.days(1); let config = [ startTime, endTime, @@ -874,18 +877,18 @@ contract("USDTieredSTO", accounts => { _minimumInvestmentUSD[stoId], _fundRaiseTypes[stoId], _wallet[stoId], - _reserveWallet[stoId], + _treasuryWallet[stoId], _usdToken[stoId] ]; let bytesSTO = web3.eth.abi.encodeFunctionCall(functionSignature, config); - await catchRevert(I_SecurityToken.addModule(I_USDTieredSTOFactory.address, bytesSTO, 0, 0, { from: ISSUER })); + await catchRevert(I_SecurityToken.addModule(I_USDTieredSTOFactory.address, bytesSTO, new BN(0), new BN(0), false, { from: ISSUER })); }); it("Should fail because start time is in the past", async () => { let stoId = 0; - let startTime = latestTime() - duration.days(35); + let startTime = await latestTime() - duration.days(35); let endTime = startTime + duration.days(50); let config = [ startTime, @@ -898,16 +901,50 @@ contract("USDTieredSTO", accounts => { _minimumInvestmentUSD[stoId], _fundRaiseTypes[stoId], _wallet[stoId], - _reserveWallet[stoId], + _treasuryWallet[stoId], _usdToken[stoId] ]; let bytesSTO = web3.eth.abi.encodeFunctionCall(functionSignature, config); - await catchRevert(I_SecurityToken.addModule(I_USDTieredSTOFactory.address, bytesSTO, 0, 0, { from: ISSUER })); + await catchRevert(I_SecurityToken.addModule(I_USDTieredSTOFactory.address, bytesSTO, new BN(0), new BN(0), false, { from: ISSUER })); }); }); describe("Test modifying configuration", async () => { + it("Should not allow unauthorized address to change oracle address", async () => { + let stoId = 3; + await catchRevert(I_USDTieredSTO_Array[stoId].modifyOracle(ETH, address_zero, { from: ACCREDITED1 })); + }); + + it("Should not allow to change oracle address for currencies other than ETH and POLY", async () => { + let stoId = 3; + await catchRevert(I_USDTieredSTO_Array[stoId].modifyOracle(DAI, address_zero, { from: ISSUER })); + }); + + it("Should allow to change oracle address for ETH", async () => { + let stoId = 3; + oldEthRate = await I_USDTieredSTO_Array[stoId].getRate.call(ETH); + let I_USDOracle2 = await MockOracle.new(address_zero, web3.utils.fromAscii("ETH"), web3.utils.fromAscii("USD"), e18, { from: POLYMATH }); + await I_USDTieredSTO_Array[stoId].modifyOracle(ETH, I_USDOracle2.address, { from: ISSUER }); + assert.equal((await I_USDTieredSTO_Array[stoId].getRate.call(ETH)).toString(), e18.toString()); + }); + + it("Should allow to change oracle address for POLY", async () => { + let stoId = 3; + oldPolyRate = await I_USDTieredSTO_Array[stoId].getRate.call(POLY); + let I_POLYOracle2 = await MockOracle.new(I_PolyToken.address, web3.utils.fromAscii("POLY"), web3.utils.fromAscii("USD"), e18, { from: POLYMATH }); + await I_USDTieredSTO_Array[stoId].modifyOracle(POLY, I_POLYOracle2.address, { from: ISSUER }); + assert.equal((await I_USDTieredSTO_Array[stoId].getRate.call(POLY)).toString(), e18.toString()); + }); + + it("Should use official oracles when custom oracle is set to 0x0", async () => { + let stoId = 3; + await I_USDTieredSTO_Array[stoId].modifyOracle(ETH, address_zero, { from: ISSUER }); + await I_USDTieredSTO_Array[stoId].modifyOracle(POLY, address_zero, { from: ISSUER }); + assert.equal((await I_USDTieredSTO_Array[stoId].getRate.call(ETH)).toString(), oldEthRate.toString()); + assert.equal((await I_USDTieredSTO_Array[stoId].getRate.call(POLY)).toString(), oldPolyRate.toString()); + }); + it("Should successfully change config before startTime - funding", async () => { let stoId = 3; await I_USDTieredSTO_Array[stoId].modifyFunding([0], { from: ISSUER }); @@ -926,57 +963,57 @@ contract("USDTieredSTO", accounts => { it("Should successfully change config before startTime - limits and tiers, times, addresses", async () => { let stoId = 3; - await I_USDTieredSTO_Array[stoId].modifyLimits(new BigNumber(1 * 10 ** 18), BigNumber(15 * 10 ** 18), { from: ISSUER }); + await I_USDTieredSTO_Array[stoId].modifyLimits(new BN(1).mul(e18), new BN(15).mul(e18), { from: ISSUER }); assert.equal( - (await I_USDTieredSTO_Array[stoId].minimumInvestmentUSD.call()).toNumber(), - BigNumber(15 * 10 ** 18).toNumber(), + (await I_USDTieredSTO_Array[stoId].minimumInvestmentUSD.call()).toString(), + new BN(15).mul(e18).toString(), "STO Configuration doesn't set as expected" ); assert.equal( - (await I_USDTieredSTO_Array[stoId].nonAccreditedLimitUSD.call()).toNumber(), - BigNumber(1 * 10 ** 18).toNumber(), + (await I_USDTieredSTO_Array[stoId].nonAccreditedLimitUSD.call()).toString(), + new BN(1).mul(e18).toString(), "STO Configuration doesn't set as expected" ); await I_USDTieredSTO_Array[stoId].modifyTiers( - [BigNumber(15 * 10 ** 18)], - [BigNumber(13 * 10 ** 18)], - [BigNumber(15 * 10 ** 20)], - [BigNumber(15 * 10 ** 20)], + [new BN(15).mul(e18)], + [new BN(13).mul(e18)], + [new BN(1500).mul(e18)], + [new BN(1500).mul(e18)], { from: ISSUER } ); assert.equal( - (await I_USDTieredSTO_Array[stoId].tiers.call(0))[0].toNumber(), - BigNumber(15 * 10 ** 18).toNumber(), + (await I_USDTieredSTO_Array[stoId].tiers.call(0))[0].toString(), + new BN(15).mul(e18).toString(), "STO Configuration doesn't set as expected" ); assert.equal( - (await I_USDTieredSTO_Array[stoId].tiers.call(0))[1].toNumber(), - BigNumber(13 * 10 ** 18).toNumber(), + (await I_USDTieredSTO_Array[stoId].tiers.call(0))[1].toString(), + new BN(13).mul(e18).toString(), "STO Configuration doesn't set as expected" ); assert.equal( - (await I_USDTieredSTO_Array[stoId].tiers.call(0))[2], - BigNumber(15 * 10 ** 20).toNumber(), + (await I_USDTieredSTO_Array[stoId].tiers.call(0))[2].toString(), + new BN(1500).mul(e18).toString(), "STO Configuration doesn't set as expected" ); assert.equal( - (await I_USDTieredSTO_Array[stoId].tiers.call(0))[3], - BigNumber(15 * 10 ** 20).toNumber(), + (await I_USDTieredSTO_Array[stoId].tiers.call(0))[3].toString(), + new BN(1500).mul(e18).toString(), "STO Configuration doesn't set as expected" ); - let tempTime1 = latestTime() + duration.days(0.1); - let tempTime2 = latestTime() + duration.days(0.2); - - await I_USDTieredSTO_Array[stoId].modifyTimes(tempTime1, tempTime2, { from: ISSUER }); - assert.equal(await I_USDTieredSTO_Array[stoId].startTime.call(), tempTime1, "STO Configuration doesn't set as expected"); - assert.equal(await I_USDTieredSTO_Array[stoId].endTime.call(), tempTime2, "STO Configuration doesn't set as expected"); + let tempTime1 = new BN(currentTime).add(new BN(duration.days(0.1))); + let tempTime2 = new BN(currentTime).add(new BN(duration.days(0.2))); + await I_USDTieredSTO_Array[stoId].modifyTimes(new BN(tempTime1), new BN(tempTime2), { from: ISSUER }); + assert.equal((await I_USDTieredSTO_Array[stoId].startTime.call()).toString(), tempTime1.toString(), "STO Configuration doesn't set as expected"); + assert.equal((await I_USDTieredSTO_Array[stoId].endTime.call()).toString(), tempTime2.toString(), "STO Configuration doesn't set as expected"); + console.log("HERE"); await I_USDTieredSTO_Array[stoId].modifyAddresses( "0x0000000000000000000000000400000000000000", - "0x0000000000000000000003000000000000000000", - [0x0000000000000000000003000000000000057a00], + "0x0000000000000000000000000000000000000000", + [accounts[3]], { from: ISSUER } ); assert.equal( @@ -985,15 +1022,17 @@ contract("USDTieredSTO", accounts => { "STO Configuration doesn't set as expected" ); assert.equal( - await I_USDTieredSTO_Array[stoId].reserveWallet.call(), - "0x0000000000000000000003000000000000000000", + await I_USDTieredSTO_Array[stoId].treasuryWallet.call(), + "0x0000000000000000000000000000000000000000", "STO Configuration doesn't set as expected" ); - assert.equal( - await I_USDTieredSTO_Array[stoId].usdTokens.call(0), - "0x0000000000000000000003000000000000057a00", - "STO Configuration doesn't set as expected" + await I_USDTieredSTO_Array[stoId].modifyAddresses( + "0x0000000000000000000000000400000000000000", + TREASURYWALLET, + [accounts[3]], + { from: ISSUER } ); + assert.equal((await I_USDTieredSTO_Array[stoId].getUsdTokens())[0], accounts[3], "STO Configuration doesn't set as expected"); }); it("Should fail to change config after endTime", async () => { @@ -1004,22 +1043,20 @@ contract("USDTieredSTO", accounts => { await catchRevert(I_USDTieredSTO_Array[stoId].modifyFunding([0, 1], { from: ISSUER })); - await catchRevert( - I_USDTieredSTO_Array[stoId].modifyLimits(new BigNumber(15 * 10 ** 18), BigNumber(1 * 10 ** 18), { from: ISSUER }) - ); + await catchRevert(I_USDTieredSTO_Array[stoId].modifyLimits(new BN(15).mul(e18), new BN(1).mul(e18), { from: ISSUER })); await catchRevert( I_USDTieredSTO_Array[stoId].modifyTiers( - [BigNumber(15 * 10 ** 18)], - [BigNumber(13 * 10 ** 18)], - [BigNumber(15 * 10 ** 20)], - [BigNumber(15 * 10 ** 20)], + [new BN(15).mul(e18)], + [new BN(13).mul(e18)], + [new BN(1500).mul(e18)], + [new BN(1500).mul(e18)], { from: ISSUER } ) ); - let tempTime1 = latestTime() + duration.days(1); - let tempTime2 = latestTime() + duration.days(3); + let tempTime1 = await latestTime() + duration.days(1); + let tempTime2 = await latestTime() + duration.days(3); await catchRevert(I_USDTieredSTO_Array[stoId].modifyTimes(tempTime1, tempTime2, { from: ISSUER })); @@ -1035,28 +1072,26 @@ contract("USDTieredSTO", accounts => { assert.equal(await I_USDTieredSTO_Array[stoId].isOpen(), false, "STO is not showing correct status"); // Whitelist - let fromTime = latestTime(); - let toTime = latestTime() + duration.days(15); + let fromTime = await latestTime(); + let toTime = await latestTime() + duration.days(15); let expiryTime = toTime + duration.days(100); let whitelisted = true; - await I_GeneralTransferManager.modifyWhitelist(ACCREDITED1, fromTime, toTime, expiryTime, whitelisted, { from: ISSUER }); - await I_GeneralTransferManager.modifyWhitelist(NONACCREDITED1, fromTime, toTime, expiryTime, whitelisted, { from: ISSUER }); + await I_GeneralTransferManager.modifyKYCData(ACCREDITED1, fromTime, toTime, expiryTime, { from: ISSUER }); + await I_GeneralTransferManager.modifyInvestorFlag(ACCREDITED1, 0, true, { from: ISSUER }); //set as Accredited + await I_GeneralTransferManager.modifyKYCData(NONACCREDITED1, fromTime, toTime, expiryTime, { from: ISSUER }); // // Advance time to after STO start // await increaseTime(duration.days(3)); - // Set as accredited - await I_USDTieredSTO_Array[stoId].changeAccredited([ACCREDITED1], [true], { from: ISSUER }); - // Prep for investments - let investment_ETH = web3.utils.toWei("1", "ether"); // Invest 1 ETH - let investment_POLY = web3.utils.toWei("10000", "ether"); // Invest 10000 POLY + let investment_ETH = new BN(web3.utils.toWei("1", "ether")); // Invest 1 ETH + let investment_POLY = new BN(web3.utils.toWei("10000", "ether")); // Invest 10000 POLY await I_PolyToken.getTokens(investment_POLY, NONACCREDITED1); await I_PolyToken.approve(I_USDTieredSTO_Array[stoId].address, investment_POLY, { from: NONACCREDITED1 }); await I_PolyToken.getTokens(investment_POLY, ACCREDITED1); await I_PolyToken.approve(I_USDTieredSTO_Array[stoId].address, investment_POLY, { from: ACCREDITED1 }); - let investment_DAI = web3.utils.toWei("500", "ether"); // Invest 10000 POLY + let investment_DAI = new BN(web3.utils.toWei("500", "ether")); // Invest 10000 POLY await I_DaiToken.getTokens(investment_DAI, NONACCREDITED1); await I_DaiToken.approve(I_USDTieredSTO_Array[stoId].address, investment_DAI, { from: NONACCREDITED1 }); await I_DaiToken.getTokens(investment_DAI, ACCREDITED1); @@ -1081,28 +1116,28 @@ contract("USDTieredSTO", accounts => { let snapId = await takeSnapshot(); // // Whitelist - // let fromTime = latestTime(); - // let toTime = latestTime() + duration.days(15); + // let fromTime = await latestTime(); + // let toTime = await latestTime() + duration.days(15); // let expiryTime = toTime + duration.days(100); // let whitelisted = true; // - // await I_GeneralTransferManager.modifyWhitelist(ACCREDITED1, fromTime, toTime, expiryTime, whitelisted,{ from: ISSUER }); - // await I_GeneralTransferManager.modifyWhitelist(NONACCREDITED1, fromTime, toTime, expiryTime, whitelisted,{ from: ISSUER }); + // await I_GeneralTransferManager.modifyKYCData(ACCREDITED1, fromTime, toTime, expiryTime, whitelisted,{ from: ISSUER }); + // await I_GeneralTransferManager.modifyKYCData(NONACCREDITED1, fromTime, toTime, expiryTime, whitelisted,{ from: ISSUER }); // Advance time to after STO start await increaseTime(duration.days(3)); // Set as accredited - await I_USDTieredSTO_Array[stoId].changeAccredited([ACCREDITED1], [true], { from: ISSUER }); + await I_GeneralTransferManager.modifyInvestorFlag(ACCREDITED1, 0, true, { from: ISSUER }); // Prep for investments - let investment_ETH = web3.utils.toWei("1", "ether"); // Invest 1 ETH - let investment_POLY = web3.utils.toWei("10000", "ether"); // Invest 10000 POLY + let investment_ETH = new BN(web3.utils.toWei("1", "ether")); // Invest 1 ETH + let investment_POLY = new BN(web3.utils.toWei("10000", "ether")); // Invest 10000 POLY await I_PolyToken.getTokens(investment_POLY, NONACCREDITED1); await I_PolyToken.approve(I_USDTieredSTO_Array[stoId].address, investment_POLY, { from: NONACCREDITED1 }); await I_PolyToken.getTokens(investment_POLY, ACCREDITED1); await I_PolyToken.approve(I_USDTieredSTO_Array[stoId].address, investment_POLY, { from: ACCREDITED1 }); - let investment_DAI = web3.utils.toWei("500", "ether"); // Invest 10000 POLY + let investment_DAI = new BN(web3.utils.toWei("500", "ether")); // Invest 10000 POLY await I_DaiToken.getTokens(investment_DAI, NONACCREDITED1); await I_DaiToken.approve(I_USDTieredSTO_Array[stoId].address, investment_DAI, { from: NONACCREDITED1 }); await I_DaiToken.getTokens(investment_DAI, ACCREDITED1); @@ -1135,21 +1170,19 @@ contract("USDTieredSTO", accounts => { let snapId = await takeSnapshot(); // Whitelist - let fromTime = latestTime(); - let toTime = latestTime() + duration.days(15); + let fromTime = await latestTime(); + let toTime = await latestTime() + duration.days(15); let expiryTime = toTime + duration.days(100); let whitelisted = true; - await I_GeneralTransferManager.modifyWhitelist(ACCREDITED1, fromTime, toTime, expiryTime, whitelisted, { from: ISSUER }); - await I_GeneralTransferManager.modifyWhitelist(NONACCREDITED1, fromTime, toTime, expiryTime, whitelisted, { from: ISSUER }); + await I_GeneralTransferManager.modifyKYCData(ACCREDITED1, fromTime, toTime, expiryTime, { from: ISSUER }); + await I_GeneralTransferManager.modifyInvestorFlag(ACCREDITED1, 0, true, { from: ISSUER }); + await I_GeneralTransferManager.modifyKYCData(NONACCREDITED1, fromTime, toTime, expiryTime, { from: ISSUER }); // Advance time to after STO start await increaseTime(duration.days(3)); - // Set as accredited - await I_USDTieredSTO_Array[stoId].changeAccredited([ACCREDITED1], [true], { from: ISSUER }); - - let investment_USD = new BigNumber(2).mul(10 ** 18); + let investment_USD = new BN(2).mul(e18); let investment_ETH = await convert(stoId, tierId, false, "USD", "ETH", investment_USD); let investment_POLY = await convert(stoId, tierId, false, "USD", "POLY", investment_USD); let investment_DAI = investment_USD; @@ -1190,33 +1223,31 @@ contract("USDTieredSTO", accounts => { let snapId = await takeSnapshot(); // Whitelist - let fromTime = latestTime(); - let toTime = latestTime() + duration.days(15); + let fromTime = await latestTime(); + let toTime = await latestTime() + duration.days(15); let expiryTime = toTime + duration.days(100); let whitelisted = true; - await I_GeneralTransferManager.modifyWhitelist(ACCREDITED1, fromTime, toTime, expiryTime, whitelisted, { from: ISSUER }); - await I_GeneralTransferManager.modifyWhitelist(NONACCREDITED1, fromTime, toTime, expiryTime, whitelisted, { from: ISSUER }); + await I_GeneralTransferManager.modifyKYCData(ACCREDITED1, fromTime, toTime, expiryTime, { from: ISSUER }); + await I_GeneralTransferManager.modifyInvestorFlag(ACCREDITED1, 0, true, { from: ISSUER }); + await I_GeneralTransferManager.modifyKYCData(NONACCREDITED1, fromTime, toTime, expiryTime, { from: ISSUER }); // Advance time to after STO start await increaseTime(duration.days(3)); - // Set as accredited - await I_USDTieredSTO_Array[stoId].changeAccredited([ACCREDITED1], [true], { from: ISSUER }); - // Pause the STO await I_USDTieredSTO_Array[stoId].pause({ from: ISSUER }); assert.equal(await I_USDTieredSTO_Array[stoId].paused.call(), true, "STO did not pause successfully"); // Prep for investments - let investment_ETH = web3.utils.toWei("1", "ether"); // Invest 1 ETH - let investment_POLY = web3.utils.toWei("10000", "ether"); // Invest 10000 POLY + let investment_ETH = new BN(web3.utils.toWei("1", "ether")); // Invest 1 ETH + let investment_POLY = new BN(web3.utils.toWei("10000", "ether")); // Invest 10000 POLY await I_PolyToken.getTokens(investment_POLY, NONACCREDITED1); await I_PolyToken.approve(I_USDTieredSTO_Array[stoId].address, investment_POLY, { from: NONACCREDITED1 }); await I_PolyToken.getTokens(investment_POLY, ACCREDITED1); await I_PolyToken.approve(I_USDTieredSTO_Array[stoId].address, investment_POLY, { from: ACCREDITED1 }); - let investment_DAI = web3.utils.toWei("500", "ether"); // Invest 10000 POLY + let investment_DAI = new BN(web3.utils.toWei("500", "ether")); // Invest 10000 POLY await I_DaiToken.getTokens(investment_DAI, NONACCREDITED1); await I_DaiToken.approve(I_USDTieredSTO_Array[stoId].address, investment_DAI, { from: NONACCREDITED1 }); await I_DaiToken.getTokens(investment_DAI, ACCREDITED1); @@ -1260,20 +1291,18 @@ contract("USDTieredSTO", accounts => { let snapId = await takeSnapshot(); // Whitelist - let fromTime = latestTime(); - let toTime = latestTime() + duration.days(15); - let expiryTime = toTime + duration.days(100); + let fromTime = new BN(await latestTime()); + let toTime = fromTime.add(new BN(duration.days(15))); + let expiryTime = toTime.add(new BN(duration.days(100))); let whitelisted = true; - await I_GeneralTransferManager.modifyWhitelist(ACCREDITED1, fromTime, toTime, expiryTime, whitelisted, { from: ISSUER }); - await I_GeneralTransferManager.modifyWhitelist(NONACCREDITED1, fromTime, toTime, expiryTime, whitelisted, { from: ISSUER }); + await I_GeneralTransferManager.modifyKYCData(ACCREDITED1, fromTime, toTime, expiryTime, { from: ISSUER }); + await I_GeneralTransferManager.modifyInvestorFlag(ACCREDITED1, 0, true, { from: ISSUER }); + await I_GeneralTransferManager.modifyKYCData(NONACCREDITED1, fromTime, toTime, expiryTime, { from: ISSUER }); // Advance time to after STO start await increaseTime(duration.days(3)); - // Set as accredited - await I_USDTieredSTO_Array[stoId].changeAccredited([ACCREDITED1], [true], { from: ISSUER }); - // Prep for investments let investment_DAI = web3.utils.toWei("500", "ether"); // Invest 10000 POLY await I_DaiToken.getTokens(investment_DAI, NONACCREDITED1); @@ -1285,8 +1314,8 @@ contract("USDTieredSTO", accounts => { await I_USDTieredSTO_Array[stoId].buyWithUSD(NONACCREDITED1, investment_DAI, I_DaiToken.address, { from: NONACCREDITED1 }); // Change Stable coin address - let I_DaiToken2 = await PolyTokenFaucet.new({from: POLYMATH}); - await I_USDTieredSTO_Array[stoId].modifyAddresses(WALLET, RESERVEWALLET, [I_DaiToken2.address], { from: ISSUER }); + let I_DaiToken2 = await PolyTokenFaucet.new(); + await I_USDTieredSTO_Array[stoId].modifyAddresses(WALLET, TREASURYWALLET, [I_DaiToken2.address], { from: ISSUER }); // NONACCREDITED DAI await catchRevert(I_USDTieredSTO_Array[stoId].buyWithUSD(NONACCREDITED1, investment_DAI, I_DaiToken.address, { from: NONACCREDITED1 })); @@ -1295,7 +1324,7 @@ contract("USDTieredSTO", accounts => { await catchRevert(I_USDTieredSTO_Array[stoId].buyWithUSD(ACCREDITED1, investment_DAI, I_DaiToken.address, { from: ACCREDITED1 })); // Revert stable coin address - await I_USDTieredSTO_Array[stoId].modifyAddresses(WALLET, RESERVEWALLET, [I_DaiToken.address], { from: ISSUER }); + await I_USDTieredSTO_Array[stoId].modifyAddresses(WALLET, TREASURYWALLET, [I_DaiToken.address], { from: ISSUER }); // Make sure buying works again await I_USDTieredSTO_Array[stoId].buyWithUSD(ACCREDITED1, investment_DAI, I_DaiToken.address, { from: ACCREDITED1 }); @@ -1308,30 +1337,28 @@ contract("USDTieredSTO", accounts => { let snapId = await takeSnapshot(); // Whitelist - let fromTime = latestTime(); - let toTime = latestTime() + duration.days(15); + let fromTime = await latestTime(); + let toTime = await latestTime() + duration.days(15); let expiryTime = toTime + duration.days(100); let whitelisted = true; - await I_GeneralTransferManager.modifyWhitelist(ACCREDITED1, fromTime, toTime, expiryTime, whitelisted, { from: ISSUER }); - await I_GeneralTransferManager.modifyWhitelist(NONACCREDITED1, fromTime, toTime, expiryTime, whitelisted, { from: ISSUER }); + await I_GeneralTransferManager.modifyKYCData(ACCREDITED1, fromTime, toTime, expiryTime, { from: ISSUER }); + await I_GeneralTransferManager.modifyInvestorFlag(ACCREDITED1, 0, true, { from: ISSUER }); + await I_GeneralTransferManager.modifyKYCData(NONACCREDITED1, fromTime, toTime, expiryTime, { from: ISSUER }); // Advance time to after STO end await increaseTime(duration.days(3)); assert.equal(await I_USDTieredSTO_Array[stoId].isOpen(), false, "STO is not showing correct status"); - // Set as accredited - await I_USDTieredSTO_Array[stoId].changeAccredited([ACCREDITED1], [true], { from: ISSUER }); - // Prep for investments - let investment_ETH = web3.utils.toWei("1", "ether"); // Invest 1 ETH - let investment_POLY = web3.utils.toWei("10000", "ether"); // Invest 10000 POLY + let investment_ETH = new BN(web3.utils.toWei("1", "ether")); // Invest 1 ETH + let investment_POLY = new BN(web3.utils.toWei("10000", "ether")); // Invest 10000 POLY await I_PolyToken.getTokens(investment_POLY, NONACCREDITED1); await I_PolyToken.approve(I_USDTieredSTO_Array[stoId].address, investment_POLY, { from: NONACCREDITED1 }); await I_PolyToken.getTokens(investment_POLY, ACCREDITED1); await I_PolyToken.approve(I_USDTieredSTO_Array[stoId].address, investment_POLY, { from: ACCREDITED1 }); - let investment_DAI = web3.utils.toWei("500", "ether"); // Invest 10000 POLY + let investment_DAI = new BN(web3.utils.toWei("500", "ether")); // Invest 10000 POLY await I_DaiToken.getTokens(investment_DAI, NONACCREDITED1); await I_DaiToken.approve(I_USDTieredSTO_Array[stoId].address, investment_DAI, { from: NONACCREDITED1 }); await I_DaiToken.getTokens(investment_DAI, ACCREDITED1); @@ -1363,23 +1390,24 @@ contract("USDTieredSTO", accounts => { let snapId = await takeSnapshot(); // Whitelist - let fromTime = latestTime(); - let toTime = latestTime(); + let fromTime = await latestTime(); + let toTime = await latestTime(); let expiryTime = toTime + duration.days(100); let whitelisted = true; - await I_GeneralTransferManager.modifyWhitelist(ACCREDITED1, fromTime, toTime, expiryTime, whitelisted, { from: ISSUER }); - await I_GeneralTransferManager.modifyWhitelist(NONACCREDITED1, fromTime, toTime, expiryTime, whitelisted, { from: ISSUER }); - await I_GeneralTransferManager.modifyWhitelist(RESERVEWALLET, fromTime, toTime, expiryTime, whitelisted, { from: ISSUER }); + await I_GeneralTransferManager.modifyKYCData(ACCREDITED1, fromTime, toTime, expiryTime, { from: ISSUER }); + await I_GeneralTransferManager.modifyInvestorFlag(ACCREDITED1, 0, true, { from: ISSUER }); + await I_GeneralTransferManager.modifyKYCData(NONACCREDITED1, fromTime, toTime, expiryTime, { from: ISSUER }); + await I_GeneralTransferManager.modifyKYCData(TREASURYWALLET, fromTime, toTime, expiryTime, { from: ISSUER }); // Advance time to after STO start await increaseTime(duration.days(3)); - // Set as accredited - await I_USDTieredSTO_Array[stoId].changeAccredited([ACCREDITED1], [true], { from: ISSUER }); - // Finalize STO + let preBalance = await I_SecurityToken.balanceOf.call(TREASURYWALLET); await I_USDTieredSTO_Array[stoId].finalize({ from: ISSUER }); + let postBalance = await I_SecurityToken.balanceOf.call(TREASURYWALLET); + assert.isAbove(parseInt(postBalance.toString()), parseInt(preBalance.toString())); assert.equal(await I_USDTieredSTO_Array[stoId].isFinalized.call(), true, "STO has not been finalized"); assert.equal(await I_USDTieredSTO_Array[stoId].isOpen(), false, "STO is not showing correct status"); @@ -1387,13 +1415,13 @@ contract("USDTieredSTO", accounts => { await catchRevert(I_USDTieredSTO_Array[stoId].finalize({ from: ISSUER })); // Prep for investments - let investment_ETH = web3.utils.toWei("1", "ether"); // Invest 1 ETH - let investment_POLY = web3.utils.toWei("10000", "ether"); // Invest 10000 POLY + let investment_ETH = new BN(web3.utils.toWei("1", "ether")); // Invest 1 ETH + let investment_POLY = new BN(web3.utils.toWei("10000", "ether")); // Invest 10000 POLY await I_PolyToken.getTokens(investment_POLY, NONACCREDITED1); await I_PolyToken.approve(I_USDTieredSTO_Array[stoId].address, investment_POLY, { from: NONACCREDITED1 }); await I_PolyToken.getTokens(investment_POLY, ACCREDITED1); await I_PolyToken.approve(I_USDTieredSTO_Array[stoId].address, investment_POLY, { from: ACCREDITED1 }); - let investment_DAI = web3.utils.toWei("500", "ether"); // Invest 10000 POLY + let investment_DAI = new BN(web3.utils.toWei("500", "ether")); // Invest 10000 POLY await I_DaiToken.getTokens(investment_DAI, NONACCREDITED1); await I_DaiToken.approve(I_USDTieredSTO_Array[stoId].address, investment_DAI, { from: NONACCREDITED1 }); await I_DaiToken.getTokens(investment_DAI, ACCREDITED1); @@ -1431,61 +1459,32 @@ contract("USDTieredSTO", accounts => { it("should whitelist ACCREDITED1 and NONACCREDITED1", async () => { let stoId = 0; - let fromTime = latestTime(); - let toTime = latestTime() + duration.days(15); + let fromTime = await latestTime(); + let toTime = await latestTime() + duration.days(15); let expiryTime = toTime + duration.days(100); let whitelisted = true; - const tx1 = await I_GeneralTransferManager.modifyWhitelist(NONACCREDITED1, fromTime, toTime, expiryTime, whitelisted, { + const tx1 = await I_GeneralTransferManager.modifyKYCData(NONACCREDITED1, fromTime, toTime, expiryTime, { from: ISSUER }); assert.equal(tx1.logs[0].args._investor, NONACCREDITED1, "Failed in adding the investor in whitelist"); - const tx2 = await I_GeneralTransferManager.modifyWhitelist(ACCREDITED1, fromTime, toTime, expiryTime, whitelisted, { + const tx2 = await I_GeneralTransferManager.modifyKYCData(ACCREDITED1, fromTime, toTime, expiryTime, { from: ISSUER }); + await I_GeneralTransferManager.modifyInvestorFlag(ACCREDITED1, 0, true, { from: ISSUER }); assert.equal(tx2.logs[0].args._investor, ACCREDITED1, "Failed in adding the investor in whitelist"); }); - it("should successfully modify accredited addresses for first STO", async () => { + it("should successfully modify accredited addresses for the STOs", async () => { let stoId = 0; - let investorStatus = await I_USDTieredSTO_Array[stoId].investors.call(NONACCREDITED1); - let status1 = investorStatus[0].toNumber(); - assert.equal(status1, 0, "Initial accreditation is set to true"); - - await I_USDTieredSTO_Array[stoId].changeAccredited([NONACCREDITED1], [true], { from: ISSUER }); - investorStatus = await I_USDTieredSTO_Array[stoId].investors.call(NONACCREDITED1); - let status2 = investorStatus[0].toNumber(); - assert.equal(status2, 1, "Failed to set single address"); - - await I_USDTieredSTO_Array[stoId].changeAccredited([NONACCREDITED1, ACCREDITED1], [false, true], { from: ISSUER }); - investorStatus = await I_USDTieredSTO_Array[stoId].investors.call(NONACCREDITED1); - let status3 = investorStatus[0].toNumber(); - assert.equal(status3, 0, "Failed to set multiple addresses"); - investorStatus = await I_USDTieredSTO_Array[stoId].investors.call(ACCREDITED1); - let status4 = investorStatus[0].toNumber(); - assert.equal(status4, 1, "Failed to set multiple addresses"); - let totalStatus = await I_USDTieredSTO_Array[stoId].getAccreditedData.call(); - + console.log(totalStatus); assert.equal(totalStatus[0][0], NONACCREDITED1, "Account match"); assert.equal(totalStatus[0][1], ACCREDITED1, "Account match"); assert.equal(totalStatus[1][0], false, "Account match"); assert.equal(totalStatus[1][1], true, "Account match"); assert.equal(totalStatus[2][0].toNumber(), 0, "override match"); assert.equal(totalStatus[2][1].toNumber(), 0, "override match"); - await catchRevert(I_USDTieredSTO_Array[stoId].changeAccredited([NONACCREDITED1, ACCREDITED1], [true], { from: ISSUER })); - }); - - it("should successfully modify accredited addresses for second STO", async () => { - let stoId = 1; - - await I_USDTieredSTO_Array[stoId].changeAccredited([NONACCREDITED1, ACCREDITED1], [false, true], { from: ISSUER }); - let investorStatus = await I_USDTieredSTO_Array[stoId].investors.call(NONACCREDITED1); - let status1 = investorStatus[0].toNumber(); - investorStatus = await I_USDTieredSTO_Array[stoId].investors.call(ACCREDITED1); - let status2 = investorStatus[0].toNumber(); - assert.equal(status1, 0, "Failed to set multiple address"); - assert.equal(status2, 1, "Failed to set multiple address"); }); }); @@ -1494,23 +1493,23 @@ contract("USDTieredSTO", accounts => { let stoId = 0; let tierId = 0; - let investment_Token = new BigNumber(50).mul(10 ** 18); + let investment_Token = new BN(50).mul(e18); let investment_USD = await convert(stoId, tierId, false, "TOKEN", "USD", investment_Token); let investment_ETH = await convert(stoId, tierId, false, "TOKEN", "ETH", investment_Token); let investment_POLY = await convert(stoId, tierId, false, "TOKEN", "POLY", investment_Token); let init_TokenSupply = await I_SecurityToken.totalSupply(); let init_InvestorTokenBal = await I_SecurityToken.balanceOf(NONACCREDITED1); - let init_InvestorETHBal = new BigNumber(await web3.eth.getBalance(NONACCREDITED1)); + let init_InvestorETHBal = new BN(await web3.eth.getBalance(NONACCREDITED1)); let init_InvestorPOLYBal = await I_PolyToken.balanceOf(NONACCREDITED1); let init_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); - let init_STOETHBal = new BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let init_STOETHBal = new BN(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); let init_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); let init_RaisedUSD = await I_USDTieredSTO_Array[stoId].fundsRaisedUSD.call(); let init_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); let init_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); let init_RaisedDAI = await I_USDTieredSTO_Array[stoId].fundsRaised.call(DAI); - let init_WalletETHBal = new BigNumber(await web3.eth.getBalance(WALLET)); + let init_WalletETHBal = new BN(await web3.eth.getBalance(WALLET)); let init_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET); let tx1 = await web3.eth.sendTransaction({ @@ -1518,101 +1517,101 @@ contract("USDTieredSTO", accounts => { to: I_USDTieredSTO_Array[stoId].address, value: investment_ETH, gasPrice: GAS_PRICE, - gas: 1000000 + gas: 7000000 }); - let gasCost1 = new BigNumber(GAS_PRICE).mul(tx1.gasUsed); - console.log(" Gas fallback purchase: ".grey + tx1.gasUsed.toString().grey); + let gasCost1 = new BN(GAS_PRICE).mul(new BN(tx1.gasUsed)); + console.log(" Gas fallback purchase: ".grey + new BN(tx1.gasUsed).toString().grey); let final_TokenSupply = await I_SecurityToken.totalSupply(); let final_InvestorTokenBal = await I_SecurityToken.balanceOf(NONACCREDITED1); - let final_InvestorETHBal = new BigNumber(await web3.eth.getBalance(NONACCREDITED1)); + let final_InvestorETHBal = new BN(await web3.eth.getBalance(NONACCREDITED1)); let final_InvestorPOLYBal = await I_PolyToken.balanceOf(NONACCREDITED1); let final_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); - let final_STOETHBal = new BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let final_STOETHBal = new BN(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); let final_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); let final_RaisedUSD = await I_USDTieredSTO_Array[stoId].fundsRaisedUSD.call(); let final_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); let final_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); let final_RaisedDAI = await I_USDTieredSTO_Array[stoId].fundsRaised.call(DAI); - let final_WalletETHBal = new BigNumber(await web3.eth.getBalance(WALLET)); + let final_WalletETHBal = new BN(await web3.eth.getBalance(WALLET)); let final_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET); assert.equal( - final_TokenSupply.toNumber(), - init_TokenSupply.add(investment_Token).toNumber(), + final_TokenSupply.toString(), + init_TokenSupply.add(investment_Token).toString(), "Token Supply not changed as expected" ); assert.equal( - final_InvestorTokenBal.toNumber(), - init_InvestorTokenBal.add(investment_Token).toNumber(), + final_InvestorTokenBal.toString(), + init_InvestorTokenBal.add(investment_Token).toString(), "Investor Token Balance not changed as expected" ); assert.equal( - final_InvestorETHBal.toNumber(), + final_InvestorETHBal.toString(), init_InvestorETHBal .sub(gasCost1) .sub(investment_ETH) - .toNumber(), + .toString(), "Investor ETH Balance not changed as expected" ); assert.equal( - final_InvestorPOLYBal.toNumber(), - init_InvestorPOLYBal.toNumber(), + final_InvestorPOLYBal.toString(), + init_InvestorPOLYBal.toString(), "Investor POLY Balance not changed as expected" ); assert.equal( - final_STOTokenSold.toNumber(), - init_STOTokenSold.add(investment_Token).toNumber(), + final_STOTokenSold.toString(), + init_STOTokenSold.add(investment_Token).toString(), "STO Token Sold not changed as expected" ); - assert.equal(final_STOETHBal.toNumber(), init_STOETHBal.toNumber(), "STO ETH Balance not changed as expected"); - assert.equal(final_STOPOLYBal.toNumber(), init_STOPOLYBal.toNumber(), "STO POLY Balance not changed as expected"); - assert.equal(final_RaisedUSD.toNumber(), init_RaisedUSD.add(investment_USD).toNumber(), "Raised USD not changed as expected"); - assert.equal(final_RaisedETH.toNumber(), init_RaisedETH.add(investment_ETH).toNumber(), "Raised ETH not changed as expected"); - assert.equal(final_RaisedPOLY.toNumber(), init_RaisedPOLY.toNumber(), "Raised POLY not changed as expected"); - assert.equal(final_RaisedDAI.toNumber(), init_RaisedDAI.toNumber(), "Raised POLY not changed as expected"); + assert.equal(final_STOETHBal.toString(), init_STOETHBal.toString(), "STO ETH Balance not changed as expected"); + assert.equal(final_STOPOLYBal.toString(), init_STOPOLYBal.toString(), "STO POLY Balance not changed as expected"); + assert.equal(final_RaisedUSD.toString(), init_RaisedUSD.add(investment_USD).toString(), "Raised USD not changed as expected"); + assert.equal(final_RaisedETH.toString(), init_RaisedETH.add(investment_ETH).toString(), "Raised ETH not changed as expected"); + assert.equal(final_RaisedPOLY.toString(), init_RaisedPOLY.toString(), "Raised POLY not changed as expected"); + assert.equal(final_RaisedDAI.toString(), init_RaisedDAI.toString(), "Raised POLY not changed as expected"); assert.equal( - final_WalletETHBal.toNumber(), - init_WalletETHBal.add(investment_ETH).toNumber(), + final_WalletETHBal.toString(), + init_WalletETHBal.add(investment_ETH).toString(), "Wallet ETH Balance not changed as expected" ); - assert.equal(final_WalletPOLYBal.toNumber(), init_WalletPOLYBal.toNumber(), "Wallet POLY Balance not changed as expected"); + assert.equal(final_WalletPOLYBal.toString(), init_WalletPOLYBal.toString(), "Wallet POLY Balance not changed as expected"); // Additional checks on getters - assert.equal((await I_USDTieredSTO_Array[stoId].investorCount.call()).toNumber(), 1, "Investor count not changed as expected"); + assert.equal((await I_USDTieredSTO_Array[stoId].investorCount.call()).toString(), 1, "Investor count not changed as expected"); assert.equal( - (await I_USDTieredSTO_Array[stoId].getTokensSold()).toNumber(), - investment_Token.toNumber(), + (await I_USDTieredSTO_Array[stoId].getTokensSold()).toString(), + investment_Token.toString(), "getTokensSold not changed as expected" ); assert.equal( - (await I_USDTieredSTO_Array[stoId].getTokensMinted()).toNumber(), - investment_Token.toNumber(), + (await I_USDTieredSTO_Array[stoId].getTokensMinted()).toString(), + investment_Token.toString(), "getTokensMinted not changed as expected" ); assert.equal( - (await I_USDTieredSTO_Array[stoId].getTokensSoldFor(ETH)).toNumber(), - investment_Token.toNumber(), + (await I_USDTieredSTO_Array[stoId].getTokensSoldFor(ETH)).toString(), + investment_Token.toString(), "getTokensSoldForETH not changed as expected" ); assert.equal( - (await I_USDTieredSTO_Array[stoId].getTokensSoldFor(POLY)).toNumber(), - 0, + (await I_USDTieredSTO_Array[stoId].getTokensSoldFor(POLY)).toString(), + new BN(0), "getTokensSoldForPOLY not changed as expected" ); assert.equal( - (await I_USDTieredSTO_Array[stoId].investorInvestedUSD.call(NONACCREDITED1)).toNumber(), - investment_USD.toNumber(), + (await I_USDTieredSTO_Array[stoId].investorInvestedUSD.call(NONACCREDITED1)).toString(), + investment_USD.toString(), "investorInvestedUSD not changed as expected" ); assert.equal( - (await I_USDTieredSTO_Array[stoId].investorInvested.call(NONACCREDITED1, ETH)).toNumber(), - investment_ETH.toNumber(), + (await I_USDTieredSTO_Array[stoId].investorInvested.call(NONACCREDITED1, ETH)).toString(), + investment_ETH.toString(), "investorInvestedETH not changed as expected" ); assert.equal( - (await I_USDTieredSTO_Array[stoId].investorInvested.call(NONACCREDITED1, POLY)).toNumber(), - 0, + (await I_USDTieredSTO_Array[stoId].investorInvested.call(NONACCREDITED1, POLY)).toString(), + new BN(0), "investorInvestedPOLY not changed as expected" ); }); @@ -1621,22 +1620,22 @@ contract("USDTieredSTO", accounts => { let stoId = 0; let tierId = 0; - let investment_Token = new BigNumber(50).mul(10 ** 18); + let investment_Token = new BN(50).mul(e18); let investment_USD = await convert(stoId, tierId, false, "TOKEN", "USD", investment_Token); let investment_ETH = await convert(stoId, tierId, false, "TOKEN", "ETH", investment_Token); let investment_POLY = await convert(stoId, tierId, false, "TOKEN", "POLY", investment_Token); let init_TokenSupply = await I_SecurityToken.totalSupply(); let init_InvestorTokenBal = await I_SecurityToken.balanceOf(NONACCREDITED1); - let init_InvestorETHBal = new BigNumber(await web3.eth.getBalance(NONACCREDITED1)); + let init_InvestorETHBal = new BN(await web3.eth.getBalance(NONACCREDITED1)); let init_InvestorPOLYBal = await I_PolyToken.balanceOf(NONACCREDITED1); let init_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); - let init_STOETHBal = new BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let init_STOETHBal = new BN(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); let init_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); let init_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); let init_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); let init_RaisedDAI = await I_USDTieredSTO_Array[stoId].fundsRaised.call(DAI); - let init_WalletETHBal = new BigNumber(await web3.eth.getBalance(WALLET)); + let init_WalletETHBal = new BN(await web3.eth.getBalance(WALLET)); let init_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET); let tx1 = await I_USDTieredSTO_Array[stoId].buyWithETH(NONACCREDITED1, { @@ -1644,68 +1643,68 @@ contract("USDTieredSTO", accounts => { value: investment_ETH, gasPrice: GAS_PRICE }); - let gasCost1 = new BigNumber(GAS_PRICE).mul(tx1.receipt.gasUsed); - console.log(" Gas buyWithETH: ".grey + tx1.receipt.gasUsed.toString().grey); + let gasCost1 = new BN(GAS_PRICE).mul(new BN(tx1.receipt.gasUsed)); + console.log(" Gas buyWithETH: ".grey + new BN(tx1.receipt.gasUsed).toString().grey); let final_TokenSupply = await I_SecurityToken.totalSupply(); let final_InvestorTokenBal = await I_SecurityToken.balanceOf(NONACCREDITED1); - let final_InvestorETHBal = new BigNumber(await web3.eth.getBalance(NONACCREDITED1)); + let final_InvestorETHBal = new BN(await web3.eth.getBalance(NONACCREDITED1)); let final_InvestorPOLYBal = await I_PolyToken.balanceOf(NONACCREDITED1); let final_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); - let final_STOETHBal = new BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let final_STOETHBal = new BN(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); let final_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); let final_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); let final_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); let final_RaisedDAI = await I_USDTieredSTO_Array[stoId].fundsRaised.call(DAI); - let final_WalletETHBal = new BigNumber(await web3.eth.getBalance(WALLET)); + let final_WalletETHBal = new BN(await web3.eth.getBalance(WALLET)); let final_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET); assert.equal( - final_TokenSupply.toNumber(), - init_TokenSupply.add(investment_Token).toNumber(), + final_TokenSupply.toString(), + init_TokenSupply.add(investment_Token).toString(), "Token Supply not changed as expected" ); assert.equal( - final_InvestorTokenBal.toNumber(), - init_InvestorTokenBal.add(investment_Token).toNumber(), + final_InvestorTokenBal.toString(), + init_InvestorTokenBal.add(investment_Token).toString(), "Investor Token Balance not changed as expected" ); assert.equal( - final_InvestorETHBal.toNumber(), + final_InvestorETHBal.toString(), init_InvestorETHBal .sub(gasCost1) .sub(investment_ETH) - .toNumber(), + .toString(), "Investor ETH Balance not changed as expected" ); assert.equal( - final_InvestorPOLYBal.toNumber(), - init_InvestorPOLYBal.toNumber(), + final_InvestorPOLYBal.toString(), + init_InvestorPOLYBal.toString(), "Investor POLY Balance not changed as expected" ); assert.equal( - final_STOTokenSold.toNumber(), - init_STOTokenSold.add(investment_Token).toNumber(), + final_STOTokenSold.toString(), + init_STOTokenSold.add(investment_Token).toString(), "STO Token Sold not changed as expected" ); - assert.equal(final_STOETHBal.toNumber(), init_STOETHBal.toNumber(), "STO ETH Balance not changed as expected"); - assert.equal(final_STOPOLYBal.toNumber(), init_STOPOLYBal.toNumber(), "STO POLY Balance not changed as expected"); - assert.equal(final_RaisedETH.toNumber(), init_RaisedETH.add(investment_ETH).toNumber(), "Raised ETH not changed as expected"); - assert.equal(final_RaisedPOLY.toNumber(), init_RaisedPOLY.toNumber(), "Raised POLY not changed as expected"); - assert.equal(final_RaisedDAI.toNumber(), init_RaisedDAI.toNumber(), "Raised DAI not changed as expected"); + assert.equal(final_STOETHBal.toString(), init_STOETHBal.toString(), "STO ETH Balance not changed as expected"); + assert.equal(final_STOPOLYBal.toString(), init_STOPOLYBal.toString(), "STO POLY Balance not changed as expected"); + assert.equal(final_RaisedETH.toString(), init_RaisedETH.add(investment_ETH).toString(), "Raised ETH not changed as expected"); + assert.equal(final_RaisedPOLY.toString(), init_RaisedPOLY.toString(), "Raised POLY not changed as expected"); + assert.equal(final_RaisedDAI.toString(), init_RaisedDAI.toString(), "Raised DAI not changed as expected"); assert.equal( - final_WalletETHBal.toNumber(), - init_WalletETHBal.add(investment_ETH).toNumber(), + final_WalletETHBal.toString(), + init_WalletETHBal.add(investment_ETH).toString(), "Wallet ETH Balance not changed as expected" ); - assert.equal(final_WalletPOLYBal.toNumber(), init_WalletPOLYBal.toNumber(), "Wallet POLY Balance not changed as expected"); + assert.equal(final_WalletPOLYBal.toString(), init_WalletPOLYBal.toString(), "Wallet POLY Balance not changed as expected"); }); it("should successfully buy using buyWithPOLY at tier 0 for NONACCREDITED1", async () => { let stoId = 0; let tierId = 0; - let investment_Token = new BigNumber(50).mul(10 ** 18); + let investment_Token = new BN(50).mul(e18); let investment_USD = await convert(stoId, tierId, false, "TOKEN", "USD", investment_Token); let investment_ETH = await convert(stoId, tierId, false, "TOKEN", "ETH", investment_Token); let investment_POLY = await convert(stoId, tierId, false, "TOKEN", "POLY", investment_Token); @@ -1715,15 +1714,15 @@ contract("USDTieredSTO", accounts => { let init_TokenSupply = await I_SecurityToken.totalSupply(); let init_InvestorTokenBal = await I_SecurityToken.balanceOf(NONACCREDITED1); - let init_InvestorETHBal = new BigNumber(await web3.eth.getBalance(NONACCREDITED1)); + let init_InvestorETHBal = new BN(await web3.eth.getBalance(NONACCREDITED1)); let init_InvestorPOLYBal = await I_PolyToken.balanceOf(NONACCREDITED1); let init_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); - let init_STOETHBal = new BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let init_STOETHBal = new BN(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); let init_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); let init_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); let init_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); let init_RaisedDAI = await I_USDTieredSTO_Array[stoId].fundsRaised.call(DAI); - let init_WalletETHBal = new BigNumber(await web3.eth.getBalance(WALLET)); + let init_WalletETHBal = new BN(await web3.eth.getBalance(WALLET)); let init_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET); // Buy With POLY @@ -1731,60 +1730,60 @@ contract("USDTieredSTO", accounts => { from: NONACCREDITED1, gasPrice: GAS_PRICE }); - let gasCost2 = new BigNumber(GAS_PRICE).mul(tx2.receipt.gasUsed); - console.log(" Gas buyWithPOLY: ".grey + tx2.receipt.gasUsed.toString().grey); + let gasCost2 = new BN(GAS_PRICE).mul(new BN(tx2.receipt.gasUsed)); + console.log(" Gas buyWithPOLY: ".grey + new BN(tx2.receipt.gasUsed).toString().grey); let final_TokenSupply = await I_SecurityToken.totalSupply(); let final_InvestorTokenBal = await I_SecurityToken.balanceOf(NONACCREDITED1); - let final_InvestorETHBal = new BigNumber(await web3.eth.getBalance(NONACCREDITED1)); + let final_InvestorETHBal = new BN(await web3.eth.getBalance(NONACCREDITED1)); let final_InvestorPOLYBal = await I_PolyToken.balanceOf(NONACCREDITED1); let final_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); - let final_STOETHBal = new BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let final_STOETHBal = new BN(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); let final_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); let final_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); let final_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); let final_RaisedDAI = await I_USDTieredSTO_Array[stoId].fundsRaised.call(DAI); - let final_WalletETHBal = new BigNumber(await web3.eth.getBalance(WALLET)); + let final_WalletETHBal = new BN(await web3.eth.getBalance(WALLET)); let final_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET); assert.equal( - final_TokenSupply.toNumber(), - init_TokenSupply.add(investment_Token).toNumber(), + final_TokenSupply.toString(), + init_TokenSupply.add(investment_Token).toString(), "Token Supply not changed as expected" ); assert.equal( - final_InvestorTokenBal.toNumber(), - init_InvestorTokenBal.add(investment_Token).toNumber(), + final_InvestorTokenBal.toString(), + init_InvestorTokenBal.add(investment_Token).toString(), "Investor Token Balance not changed as expected" ); assert.equal( - final_InvestorETHBal.toNumber(), - init_InvestorETHBal.sub(gasCost2).toNumber(), + final_InvestorETHBal.toString(), + init_InvestorETHBal.sub(gasCost2).toString(), "Investor ETH Balance not changed as expected" ); assert.equal( - final_InvestorPOLYBal.toNumber(), - init_InvestorPOLYBal.sub(investment_POLY).toNumber(), + final_InvestorPOLYBal.toString(), + init_InvestorPOLYBal.sub(investment_POLY).toString(), "Investor POLY Balance not changed as expected" ); assert.equal( - final_STOTokenSold.toNumber(), - init_STOTokenSold.add(investment_Token).toNumber(), + final_STOTokenSold.toString(), + init_STOTokenSold.add(investment_Token).toString(), "STO Token Sold not changed as expected" ); - assert.equal(final_STOETHBal.toNumber(), init_STOETHBal.toNumber(), "STO ETH Balance not changed as expected"); - assert.equal(final_STOPOLYBal.toNumber(), init_STOPOLYBal.toNumber(), "STO POLY Balance not changed as expected"); - assert.equal(final_RaisedETH.toNumber(), init_RaisedETH.toNumber(), "Raised ETH not changed as expected"); + assert.equal(final_STOETHBal.toString(), init_STOETHBal.toString(), "STO ETH Balance not changed as expected"); + assert.equal(final_STOPOLYBal.toString(), init_STOPOLYBal.toString(), "STO POLY Balance not changed as expected"); + assert.equal(final_RaisedETH.toString(), init_RaisedETH.toString(), "Raised ETH not changed as expected"); assert.equal( - final_RaisedPOLY.toNumber(), - init_RaisedPOLY.add(investment_POLY).toNumber(), + final_RaisedPOLY.toString(), + init_RaisedPOLY.add(investment_POLY).toString(), "Raised POLY not changed as expected" ); - assert.equal(final_RaisedDAI.toNumber(), init_RaisedDAI.toNumber(), "Raised POLY not changed as expected"); - assert.equal(final_WalletETHBal.toNumber(), init_WalletETHBal.toNumber(), "Wallet ETH Balance not changed as expected"); + assert.equal(final_RaisedDAI.toString(), init_RaisedDAI.toString(), "Raised POLY not changed as expected"); + assert.equal(final_WalletETHBal.toString(), init_WalletETHBal.toString(), "Wallet ETH Balance not changed as expected"); assert.equal( - final_WalletPOLYBal.toNumber(), - init_WalletPOLYBal.add(investment_POLY).toNumber(), + final_WalletPOLYBal.toString(), + init_WalletPOLYBal.add(investment_POLY).toString(), "Wallet POLY Balance not changed as expected" ); }); @@ -1793,7 +1792,7 @@ contract("USDTieredSTO", accounts => { let stoId = 0; let tierId = 0; - let investment_Token = new BigNumber(50).mul(10 ** 18); + let investment_Token = new BN(50).mul(e18); let investment_USD = await convert(stoId, tierId, false, "TOKEN", "USD", investment_Token); let investment_ETH = await convert(stoId, tierId, false, "TOKEN", "ETH", investment_Token); let investment_POLY = await convert(stoId, tierId, false, "TOKEN", "POLY", investment_Token); @@ -1804,16 +1803,16 @@ contract("USDTieredSTO", accounts => { let init_TokenSupply = await I_SecurityToken.totalSupply(); let init_InvestorTokenBal = await I_SecurityToken.balanceOf(NONACCREDITED1); - let init_InvestorETHBal = new BigNumber(await web3.eth.getBalance(NONACCREDITED1)); + let init_InvestorETHBal = new BN(await web3.eth.getBalance(NONACCREDITED1)); let init_InvestorPOLYBal = await I_PolyToken.balanceOf(NONACCREDITED1); let init_InvestorDAIBal = await I_DaiToken.balanceOf(NONACCREDITED1); let init_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); - let init_STOETHBal = new BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let init_STOETHBal = new BN(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); let init_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); let init_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); let init_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); let init_RaisedDAI = await I_USDTieredSTO_Array[stoId].fundsRaised.call(DAI); - let init_WalletETHBal = new BigNumber(await web3.eth.getBalance(WALLET)); + let init_WalletETHBal = new BN(await web3.eth.getBalance(WALLET)); let init_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET); let init_WalletDAIBal = await I_DaiToken.balanceOf(WALLET); @@ -1822,69 +1821,69 @@ contract("USDTieredSTO", accounts => { from: NONACCREDITED1, gasPrice: GAS_PRICE }); - let gasCost2 = new BigNumber(GAS_PRICE).mul(tx2.receipt.gasUsed); - console.log(" Gas buyWithUSD: ".grey + tx2.receipt.gasUsed.toString().grey); + let gasCost2 = new BN(GAS_PRICE).mul(new BN(tx2.receipt.gasUsed)); + console.log(" Gas buyWithUSD: ".grey + new BN(tx2.receipt.gasUsed).toString().grey); let final_TokenSupply = await I_SecurityToken.totalSupply(); let final_InvestorTokenBal = await I_SecurityToken.balanceOf(NONACCREDITED1); - let final_InvestorETHBal = new BigNumber(await web3.eth.getBalance(NONACCREDITED1)); + let final_InvestorETHBal = new BN(await web3.eth.getBalance(NONACCREDITED1)); let final_InvestorPOLYBal = await I_PolyToken.balanceOf(NONACCREDITED1); let final_InvestorDAIBal = await I_DaiToken.balanceOf(NONACCREDITED1); let final_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); - let final_STOETHBal = new BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let final_STOETHBal = new BN(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); let final_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); let final_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); let final_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); let final_RaisedDAI = await I_USDTieredSTO_Array[stoId].fundsRaised.call(DAI); - let final_WalletETHBal = new BigNumber(await web3.eth.getBalance(WALLET)); + let final_WalletETHBal = new BN(await web3.eth.getBalance(WALLET)); let final_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET); let final_WalletDAIBal = await I_DaiToken.balanceOf(WALLET); assert.equal( - final_TokenSupply.toNumber(), - init_TokenSupply.add(investment_Token).toNumber(), + final_TokenSupply.toString(), + init_TokenSupply.add(investment_Token).toString(), "Token Supply not changed as expected" ); assert.equal( - final_InvestorTokenBal.toNumber(), - init_InvestorTokenBal.add(investment_Token).toNumber(), + final_InvestorTokenBal.toString(), + init_InvestorTokenBal.add(investment_Token).toString(), "Investor Token Balance not changed as expected" ); assert.equal( - final_InvestorETHBal.toNumber(), - init_InvestorETHBal.sub(gasCost2).toNumber(), + final_InvestorETHBal.toString(), + init_InvestorETHBal.sub(gasCost2).toString(), "Investor ETH Balance not changed as expected" ); assert.equal( - final_InvestorPOLYBal.toNumber(), - init_InvestorPOLYBal.toNumber(), + final_InvestorPOLYBal.toString(), + init_InvestorPOLYBal.toString(), "Investor POLY Balance not changed as expected" ); assert.equal( - final_InvestorDAIBal.toNumber(), - init_InvestorDAIBal.sub(investment_DAI).toNumber(), + final_InvestorDAIBal.toString(), + init_InvestorDAIBal.sub(investment_DAI).toString(), "Investor DAI Balance not changed as expected" ); assert.equal( - final_STOTokenSold.toNumber(), - init_STOTokenSold.add(investment_Token).toNumber(), + final_STOTokenSold.toString(), + init_STOTokenSold.add(investment_Token).toString(), "STO Token Sold not changed as expected" ); - assert.equal(final_STOETHBal.toNumber(), init_STOETHBal.toNumber(), "STO ETH Balance not changed as expected"); - assert.equal(final_STOPOLYBal.toNumber(), init_STOPOLYBal.toNumber(), "STO POLY Balance not changed as expected"); - assert.equal(final_RaisedETH.toNumber(), init_RaisedETH.toNumber(), "Raised ETH not changed as expected"); - assert.equal(final_RaisedPOLY.toNumber(), init_RaisedPOLY.toNumber(), "Raised POLY not changed as expected"); - assert.equal(final_RaisedDAI.toNumber(), init_RaisedDAI.add(investment_DAI).toNumber(), "Raised POLY not changed as expected"); - assert.equal(final_WalletETHBal.toNumber(), init_WalletETHBal.toNumber(), "Wallet ETH Balance not changed as expected"); - assert.equal(final_WalletPOLYBal.toNumber(), init_WalletPOLYBal.toNumber(), "Wallet POLY Balance not changed as expected"); + assert.equal(final_STOETHBal.toString(), init_STOETHBal.toString(), "STO ETH Balance not changed as expected"); + assert.equal(final_STOPOLYBal.toString(), init_STOPOLYBal.toString(), "STO POLY Balance not changed as expected"); + assert.equal(final_RaisedETH.toString(), init_RaisedETH.toString(), "Raised ETH not changed as expected"); + assert.equal(final_RaisedPOLY.toString(), init_RaisedPOLY.toString(), "Raised POLY not changed as expected"); + assert.equal(final_RaisedDAI.toString(), init_RaisedDAI.add(investment_DAI).toString(), "Raised POLY not changed as expected"); + assert.equal(final_WalletETHBal.toString(), init_WalletETHBal.toString(), "Wallet ETH Balance not changed as expected"); + assert.equal(final_WalletPOLYBal.toString(), init_WalletPOLYBal.toString(), "Wallet POLY Balance not changed as expected"); assert.equal( - final_WalletDAIBal.toNumber(), - init_WalletDAIBal.add(investment_DAI).toNumber(), + final_WalletDAIBal.toString(), + init_WalletDAIBal.add(investment_DAI).toString(), "Wallet DAI Balance not changed as expected" ); assert.equal( - (await I_USDTieredSTO_Array[stoId].stableCoinsRaised.call(I_DaiToken.address)).toNumber(), - investment_DAI.toNumber(), + (await I_USDTieredSTO_Array[stoId].stableCoinsRaised.call(I_DaiToken.address)).toString(), + investment_DAI.toString(), "DAI Raised not changed as expected" ); }); @@ -1893,23 +1892,21 @@ contract("USDTieredSTO", accounts => { let stoId = 0; let tierId = 0; - await I_USDTieredSTO_Array[stoId].changeAccredited([ACCREDITED1], [true], { from: ISSUER }); - - let investment_Token = new BigNumber(50).mul(10 ** 18); + let investment_Token = new BN(50).mul(e18); let investment_USD = await convert(stoId, tierId, false, "TOKEN", "USD", investment_Token); let investment_ETH = await convert(stoId, tierId, false, "TOKEN", "ETH", investment_Token); let investment_POLY = await convert(stoId, tierId, false, "TOKEN", "POLY", investment_Token); let init_TokenSupply = await I_SecurityToken.totalSupply(); let init_InvestorTokenBal = await I_SecurityToken.balanceOf(ACCREDITED1); - let init_InvestorETHBal = new BigNumber(await web3.eth.getBalance(ACCREDITED1)); + let init_InvestorETHBal = new BN(await web3.eth.getBalance(ACCREDITED1)); let init_InvestorPOLYBal = await I_PolyToken.balanceOf(ACCREDITED1); let init_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); - let init_STOETHBal = new BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let init_STOETHBal = new BN(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); let init_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); let init_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); let init_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); - let init_WalletETHBal = new BigNumber(await web3.eth.getBalance(WALLET)); + let init_WalletETHBal = new BN(await web3.eth.getBalance(WALLET)); let init_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET); let tx1 = await web3.eth.sendTransaction({ @@ -1917,82 +1914,82 @@ contract("USDTieredSTO", accounts => { to: I_USDTieredSTO_Array[stoId].address, value: investment_ETH, gasPrice: GAS_PRICE, - gas: 1000000 + gas: 7000000 }); - let gasCost1 = new BigNumber(GAS_PRICE).mul(tx1.gasUsed); - console.log(" Gas fallback purchase: ".grey + tx1.gasUsed.toString().grey); + let gasCost1 = new BN(GAS_PRICE).mul(new BN(tx1.gasUsed)); + console.log(" Gas fallback purchase: ".grey + new BN(tx1.gasUsed).toString().grey); let final_TokenSupply = await I_SecurityToken.totalSupply(); let final_InvestorTokenBal = await I_SecurityToken.balanceOf(ACCREDITED1); - let final_InvestorETHBal = new BigNumber(await web3.eth.getBalance(ACCREDITED1)); + let final_InvestorETHBal = new BN(await web3.eth.getBalance(ACCREDITED1)); let final_InvestorPOLYBal = await I_PolyToken.balanceOf(ACCREDITED1); let final_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); - let final_STOETHBal = new BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let final_STOETHBal = new BN(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); let final_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); let final_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); let final_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); - let final_WalletETHBal = new BigNumber(await web3.eth.getBalance(WALLET)); + let final_WalletETHBal = new BN(await web3.eth.getBalance(WALLET)); let final_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET); assert.equal( - final_TokenSupply.toNumber(), - init_TokenSupply.add(investment_Token).toNumber(), + final_TokenSupply.toString(), + init_TokenSupply.add(investment_Token).toString(), "Token Supply not changed as expected" ); assert.equal( - final_InvestorTokenBal.toNumber(), - init_InvestorTokenBal.add(investment_Token).toNumber(), + final_InvestorTokenBal.toString(), + init_InvestorTokenBal.add(investment_Token).toString(), "Investor Token Balance not changed as expected" ); assert.equal( - final_InvestorETHBal.toNumber(), + final_InvestorETHBal.toString(), init_InvestorETHBal .sub(gasCost1) .sub(investment_ETH) - .toNumber(), + .toString(), "Investor ETH Balance not changed as expected" ); assert.equal( - final_InvestorPOLYBal.toNumber(), - init_InvestorPOLYBal.toNumber(), + final_InvestorPOLYBal.toString(), + init_InvestorPOLYBal.toString(), "Investor POLY Balance not changed as expected" ); assert.equal( - final_STOTokenSold.toNumber(), - init_STOTokenSold.add(investment_Token).toNumber(), + final_STOTokenSold.toString(), + init_STOTokenSold.add(investment_Token).toString(), "STO Token Sold not changed as expected" ); - assert.equal(final_STOETHBal.toNumber(), init_STOETHBal.toNumber(), "STO ETH Balance not changed as expected"); - assert.equal(final_STOPOLYBal.toNumber(), init_STOPOLYBal.toNumber(), "STO POLY Balance not changed as expected"); - assert.equal(final_RaisedETH.toNumber(), init_RaisedETH.add(investment_ETH).toNumber(), "Raised ETH not changed as expected"); - assert.equal(final_RaisedPOLY.toNumber(), init_RaisedPOLY.toNumber(), "Raised POLY not changed as expected"); + assert.equal(final_STOETHBal.toString(), init_STOETHBal.toString(), "STO ETH Balance not changed as expected"); + assert.equal(final_STOPOLYBal.toString(), init_STOPOLYBal.toString(), "STO POLY Balance not changed as expected"); + assert.equal(final_RaisedETH.toString(), init_RaisedETH.add(investment_ETH).toString(), "Raised ETH not changed as expected"); + assert.equal(final_RaisedPOLY.toString(), init_RaisedPOLY.toString(), "Raised POLY not changed as expected"); assert.equal( - final_WalletETHBal.toNumber(), - init_WalletETHBal.add(investment_ETH).toNumber(), + final_WalletETHBal.toString(), + init_WalletETHBal.add(investment_ETH).toString(), "Wallet ETH Balance not changed as expected" ); - assert.equal(final_WalletPOLYBal.toNumber(), init_WalletPOLYBal.toNumber(), "Wallet POLY Balance not changed as expected"); + assert.equal(final_WalletPOLYBal.toString(), init_WalletPOLYBal.toString(), "Wallet POLY Balance not changed as expected"); }); it("should successfully buy using buyWithETH at tier 0 for ACCREDITED1", async () => { let stoId = 0; let tierId = 0; - let investment_Token = new BigNumber(50).mul(10 ** 18); + let investment_Token = new BN(50).mul(e18); let investment_USD = await convert(stoId, tierId, false, "TOKEN", "USD", investment_Token); let investment_ETH = await convert(stoId, tierId, false, "TOKEN", "ETH", investment_Token); let investment_POLY = await convert(stoId, tierId, false, "TOKEN", "POLY", investment_Token); let init_TokenSupply = await I_SecurityToken.totalSupply(); let init_InvestorTokenBal = await I_SecurityToken.balanceOf(ACCREDITED1); - let init_InvestorETHBal = new BigNumber(await web3.eth.getBalance(ACCREDITED1)); + let init_InvestorETHBal = new BN(await web3.eth.getBalance(ACCREDITED1)); let init_InvestorPOLYBal = await I_PolyToken.balanceOf(ACCREDITED1); let init_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); - let init_STOETHBal = new BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let init_STOETHBal = new BN(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); let init_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); let init_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); let init_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); - let init_WalletETHBal = new BigNumber(await web3.eth.getBalance(WALLET)); + let init_WalletETHBal = new BN(await web3.eth.getBalance(WALLET)); let init_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET); let tx1 = await I_USDTieredSTO_Array[stoId].buyWithETH(ACCREDITED1, { @@ -2000,66 +1997,66 @@ contract("USDTieredSTO", accounts => { value: investment_ETH, gasPrice: GAS_PRICE }); - let gasCost1 = new BigNumber(GAS_PRICE).mul(tx1.receipt.gasUsed); - console.log(" Gas buyWithETH: ".grey + tx1.receipt.gasUsed.toString().grey); + let gasCost1 = new BN(GAS_PRICE).mul(new BN(tx1.receipt.gasUsed)); + console.log(" Gas buyWithETH: ".grey + new BN(tx1.receipt.gasUsed).toString().grey); let final_TokenSupply = await I_SecurityToken.totalSupply(); let final_InvestorTokenBal = await I_SecurityToken.balanceOf(ACCREDITED1); - let final_InvestorETHBal = new BigNumber(await web3.eth.getBalance(ACCREDITED1)); + let final_InvestorETHBal = new BN(await web3.eth.getBalance(ACCREDITED1)); let final_InvestorPOLYBal = await I_PolyToken.balanceOf(ACCREDITED1); let final_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); - let final_STOETHBal = new BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let final_STOETHBal = new BN(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); let final_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); let final_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); let final_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); - let final_WalletETHBal = new BigNumber(await web3.eth.getBalance(WALLET)); + let final_WalletETHBal = new BN(await web3.eth.getBalance(WALLET)); let final_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET); assert.equal( - final_TokenSupply.toNumber(), - init_TokenSupply.add(investment_Token).toNumber(), + final_TokenSupply.toString(), + init_TokenSupply.add(investment_Token).toString(), "Token Supply not changed as expected" ); assert.equal( - final_InvestorTokenBal.toNumber(), - init_InvestorTokenBal.add(investment_Token).toNumber(), + final_InvestorTokenBal.toString(), + init_InvestorTokenBal.add(investment_Token).toString(), "Investor Token Balance not changed as expected" ); assert.equal( - final_InvestorETHBal.toNumber(), + final_InvestorETHBal.toString(), init_InvestorETHBal .sub(gasCost1) .sub(investment_ETH) - .toNumber(), + .toString(), "Investor ETH Balance not changed as expected" ); assert.equal( - final_InvestorPOLYBal.toNumber(), - init_InvestorPOLYBal.toNumber(), + final_InvestorPOLYBal.toString(), + init_InvestorPOLYBal.toString(), "Investor POLY Balance not changed as expected" ); assert.equal( - final_STOTokenSold.toNumber(), - init_STOTokenSold.add(investment_Token).toNumber(), + final_STOTokenSold.toString(), + init_STOTokenSold.add(investment_Token).toString(), "STO Token Sold not changed as expected" ); - assert.equal(final_STOETHBal.toNumber(), init_STOETHBal.toNumber(), "STO ETH Balance not changed as expected"); - assert.equal(final_STOPOLYBal.toNumber(), init_STOPOLYBal.toNumber(), "STO POLY Balance not changed as expected"); - assert.equal(final_RaisedETH.toNumber(), init_RaisedETH.add(investment_ETH).toNumber(), "Raised ETH not changed as expected"); - assert.equal(final_RaisedPOLY.toNumber(), init_RaisedPOLY.toNumber(), "Raised POLY not changed as expected"); + assert.equal(final_STOETHBal.toString(), init_STOETHBal.toString(), "STO ETH Balance not changed as expected"); + assert.equal(final_STOPOLYBal.toString(), init_STOPOLYBal.toString(), "STO POLY Balance not changed as expected"); + assert.equal(final_RaisedETH.toString(), init_RaisedETH.add(investment_ETH).toString(), "Raised ETH not changed as expected"); + assert.equal(final_RaisedPOLY.toString(), init_RaisedPOLY.toString(), "Raised POLY not changed as expected"); assert.equal( - final_WalletETHBal.toNumber(), - init_WalletETHBal.add(investment_ETH).toNumber(), + final_WalletETHBal.toString(), + init_WalletETHBal.add(investment_ETH).toString(), "Wallet ETH Balance not changed as expected" ); - assert.equal(final_WalletPOLYBal.toNumber(), init_WalletPOLYBal.toNumber(), "Wallet POLY Balance not changed as expected"); + assert.equal(final_WalletPOLYBal.toString(), init_WalletPOLYBal.toString(), "Wallet POLY Balance not changed as expected"); }); it("should successfully buy using buyWithPOLY at tier 0 for ACCREDITED1", async () => { let stoId = 0; let tierId = 0; - let investment_Token = new BigNumber(50).mul(10 ** 18); + let investment_Token = new BN(50).mul(e18); let investment_USD = await convert(stoId, tierId, false, "TOKEN", "USD", investment_Token); let investment_ETH = await convert(stoId, tierId, false, "TOKEN", "ETH", investment_Token); let investment_POLY = await convert(stoId, tierId, false, "TOKEN", "POLY", investment_Token); @@ -2078,14 +2075,14 @@ contract("USDTieredSTO", accounts => { let init_TokenSupply = await I_SecurityToken.totalSupply(); let init_InvestorTokenBal = await I_SecurityToken.balanceOf(ACCREDITED1); - let init_InvestorETHBal = new BigNumber(await web3.eth.getBalance(ACCREDITED1)); + let init_InvestorETHBal = new BN(await web3.eth.getBalance(ACCREDITED1)); let init_InvestorPOLYBal = await I_PolyToken.balanceOf(ACCREDITED1); let init_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); - let init_STOETHBal = new BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let init_STOETHBal = new BN(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); let init_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); let init_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); let init_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); - let init_WalletETHBal = new BigNumber(await web3.eth.getBalance(WALLET)); + let init_WalletETHBal = new BN(await web3.eth.getBalance(WALLET)); let init_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET); // Buy With POLY @@ -2093,96 +2090,96 @@ contract("USDTieredSTO", accounts => { from: ACCREDITED1, gasPrice: GAS_PRICE }); - let gasCost2 = new BigNumber(GAS_PRICE).mul(tx2.receipt.gasUsed); - console.log(" Gas buyWithPOLY: ".grey + tx2.receipt.gasUsed.toString().grey); + let gasCost2 = new BN(GAS_PRICE).mul(new BN(tx2.receipt.gasUsed)); + console.log(" Gas buyWithPOLY: ".grey + new BN(tx2.receipt.gasUsed).toString().grey); let final_TokenSupply = await I_SecurityToken.totalSupply(); let final_InvestorTokenBal = await I_SecurityToken.balanceOf(ACCREDITED1); - let final_InvestorETHBal = new BigNumber(await web3.eth.getBalance(ACCREDITED1)); + let final_InvestorETHBal = new BN(await web3.eth.getBalance(ACCREDITED1)); let final_InvestorPOLYBal = await I_PolyToken.balanceOf(ACCREDITED1); let final_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); - let final_STOETHBal = new BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let final_STOETHBal = new BN(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); let final_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); let final_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); let final_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); - let final_WalletETHBal = new BigNumber(await web3.eth.getBalance(WALLET)); + let final_WalletETHBal = new BN(await web3.eth.getBalance(WALLET)); let final_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET); assert.equal( - final_TokenSupply.toNumber(), - init_TokenSupply.add(investment_Token).toNumber(), + final_TokenSupply.toString(), + init_TokenSupply.add(investment_Token).toString(), "Token Supply not changed as expected" ); assert.equal( - final_InvestorTokenBal.toNumber(), - init_InvestorTokenBal.add(investment_Token).toNumber(), + final_InvestorTokenBal.toString(), + init_InvestorTokenBal.add(investment_Token).toString(), "Investor Token Balance not changed as expected" ); assert.equal( - final_InvestorETHBal.toNumber(), - init_InvestorETHBal.sub(gasCost2).toNumber(), + final_InvestorETHBal.toString(), + init_InvestorETHBal.sub(gasCost2).toString(), "Investor ETH Balance not changed as expected" ); assert.equal( - final_InvestorPOLYBal.toNumber(), - init_InvestorPOLYBal.sub(investment_POLY).toNumber(), + final_InvestorPOLYBal.toString(), + init_InvestorPOLYBal.sub(investment_POLY).toString(), "Investor POLY Balance not changed as expected" ); assert.equal( - final_STOTokenSold.toNumber(), - init_STOTokenSold.add(investment_Token).toNumber(), + final_STOTokenSold.toString(), + init_STOTokenSold.add(investment_Token).toString(), "STO Token Sold not changed as expected" ); - assert.equal(final_STOETHBal.toNumber(), init_STOETHBal.toNumber(), "STO ETH Balance not changed as expected"); - assert.equal(final_STOPOLYBal.toNumber(), init_STOPOLYBal.toNumber(), "STO POLY Balance not changed as expected"); - assert.equal(final_RaisedETH.toNumber(), init_RaisedETH.toNumber(), "Raised ETH not changed as expected"); + assert.equal(final_STOETHBal.toString(), init_STOETHBal.toString(), "STO ETH Balance not changed as expected"); + assert.equal(final_STOPOLYBal.toString(), init_STOPOLYBal.toString(), "STO POLY Balance not changed as expected"); + assert.equal(final_RaisedETH.toString(), init_RaisedETH.toString(), "Raised ETH not changed as expected"); assert.equal( - final_RaisedPOLY.toNumber(), - init_RaisedPOLY.add(investment_POLY).toNumber(), + final_RaisedPOLY.toString(), + init_RaisedPOLY.add(investment_POLY).toString(), "Raised POLY not changed as expected" ); - assert.equal(final_WalletETHBal.toNumber(), init_WalletETHBal.toNumber(), "Wallet ETH Balance not changed as expected"); + assert.equal(final_WalletETHBal.toString(), init_WalletETHBal.toString(), "Wallet ETH Balance not changed as expected"); assert.equal( - final_WalletPOLYBal.toNumber(), - init_WalletPOLYBal.add(investment_POLY).toNumber(), + final_WalletPOLYBal.toString(), + init_WalletPOLYBal.add(investment_POLY).toString(), "Wallet POLY Balance not changed as expected" ); // Additional checks on getters - assert.equal((await I_USDTieredSTO_Array[stoId].investorCount.call()).toNumber(), 2, "Investor count not changed as expected"); + assert.equal((await I_USDTieredSTO_Array[stoId].investorCount.call()).toString(), 2, "Investor count not changed as expected"); assert.equal( - (await I_USDTieredSTO_Array[stoId].getTokensSold()).toNumber(), - init_getTokensSold.add(investment_Token).toNumber(), + (await I_USDTieredSTO_Array[stoId].getTokensSold()).toString(), + init_getTokensSold.add(investment_Token).toString(), "getTokensSold not changed as expected" ); assert.equal( - (await I_USDTieredSTO_Array[stoId].getTokensMinted()).toNumber(), - init_getTokensMinted.add(investment_Token).toNumber(), + (await I_USDTieredSTO_Array[stoId].getTokensMinted()).toString(), + init_getTokensMinted.add(investment_Token).toString(), "getTokensMinted not changed as expected" ); assert.equal( - (await I_USDTieredSTO_Array[stoId].getTokensSoldFor(ETH)).toNumber(), - init_getTokensSoldForETH.toNumber(), + (await I_USDTieredSTO_Array[stoId].getTokensSoldFor(ETH)).toString(), + init_getTokensSoldForETH.toString(), "getTokensSoldForETH not changed as expected" ); assert.equal( - (await I_USDTieredSTO_Array[stoId].getTokensSoldFor(POLY)).toNumber(), - init_getTokensSoldForPOLY.add(investment_Token).toNumber(), + (await I_USDTieredSTO_Array[stoId].getTokensSoldFor(POLY)).toString(), + init_getTokensSoldForPOLY.add(investment_Token).toString(), "getTokensSoldForPOLY not changed as expected" ); assert.equal( - (await I_USDTieredSTO_Array[stoId].investorInvestedUSD.call(ACCREDITED1)).toNumber(), - init_investorInvestedUSD.add(investment_USD).toNumber(), + (await I_USDTieredSTO_Array[stoId].investorInvestedUSD.call(ACCREDITED1)).toString(), + init_investorInvestedUSD.add(investment_USD).toString(), "investorInvestedUSD not changed as expected" ); assert.equal( - (await I_USDTieredSTO_Array[stoId].investorInvested.call(ACCREDITED1, ETH)).toNumber(), - init_investorInvestedETH.toNumber(), + (await I_USDTieredSTO_Array[stoId].investorInvested.call(ACCREDITED1, ETH)).toString(), + init_investorInvestedETH.toString(), "investorInvestedETH not changed as expected" ); assert.equal( - (await I_USDTieredSTO_Array[stoId].investorInvested.call(ACCREDITED1, POLY)).toNumber(), - init_investorInvestedPOLY.add(investment_POLY).toNumber(), + (await I_USDTieredSTO_Array[stoId].investorInvested.call(ACCREDITED1, POLY)).toString(), + init_investorInvestedPOLY.add(investment_POLY).toString(), "investorInvestedPOLY not changed as expected" ); }); @@ -2190,29 +2187,27 @@ contract("USDTieredSTO", accounts => { it("should successfully modify NONACCREDITED cap for NONACCREDITED1", async () => { let stoId = 0; let tierId = 0; - console.log("Current investment: " + (await I_USDTieredSTO_Array[stoId].investorInvestedUSD.call(NONACCREDITED1)).toNumber()); - await I_USDTieredSTO_Array[stoId].changeNonAccreditedLimit([NONACCREDITED1], [_nonAccreditedLimitUSD[stoId].div(2)], { + console.log("Current investment: " + (await I_USDTieredSTO_Array[stoId].investorInvestedUSD.call(NONACCREDITED1)).toString()); + await I_USDTieredSTO_Array[stoId].changeNonAccreditedLimit([NONACCREDITED1], [_nonAccreditedLimitUSD[stoId].div(new BN(2))], { from: ISSUER }); - let investorStatus = await I_USDTieredSTO_Array[stoId].investors.call(NONACCREDITED1); - console.log("Current limit: " + investorStatus[2].toNumber()); + let investorLimit = await I_USDTieredSTO_Array[stoId].nonAccreditedLimitUSDOverride.call(NONACCREDITED1); + console.log("Current limit: " + investorLimit.toString()); let totalStatus = await I_USDTieredSTO_Array[stoId].getAccreditedData.call(); assert.equal(totalStatus[0][0], NONACCREDITED1, "Account match"); assert.equal(totalStatus[0][1], ACCREDITED1, "Account match"); assert.equal(totalStatus[1][0], false, "Account match"); assert.equal(totalStatus[1][1], true, "Account match"); - assert.equal(totalStatus[2][0].toNumber(), _nonAccreditedLimitUSD[stoId].div(2), "override match"); - assert.equal(totalStatus[2][1].toNumber(), 0, "override match"); - + assert.equal(totalStatus[2][0].toString(), _nonAccreditedLimitUSD[stoId].div(new BN(2)), "override match"); + assert.equal(totalStatus[2][1].toString(), 0, "override match"); }); it("should successfully buy a partial amount and refund balance when reaching NONACCREDITED cap", async () => { let stoId = 0; let tierId = 0; - let investorStatus = await I_USDTieredSTO_Array[stoId].investors.call(NONACCREDITED1); - let investment_USD = investorStatus[2];//await I_USDTieredSTO_Array[stoId].nonAccreditedLimitUSDOverride(NONACCREDITED1); //_nonAccreditedLimitUSD[stoId]; + let investment_USD = await I_USDTieredSTO_Array[stoId].nonAccreditedLimitUSDOverride.call(NONACCREDITED1);//await I_USDTieredSTO_Array[stoId].nonAccreditedLimitUSDOverride(NONACCREDITED1); //_nonAccreditedLimitUSD[stoId]; let investment_Token = await convert(stoId, tierId, false, "USD", "TOKEN", investment_USD); let investment_ETH = await convert(stoId, tierId, false, "USD", "ETH", investment_USD); let investment_POLY = await convert(stoId, tierId, false, "USD", "POLY", investment_USD); @@ -2222,20 +2217,20 @@ contract("USDTieredSTO", accounts => { let refund_ETH = await convert(stoId, tierId, false, "USD", "ETH", refund_USD); let refund_POLY = await convert(stoId, tierId, false, "USD", "POLY", refund_USD); - console.log("Expected refund in tokens: " + refund_Token.toNumber()); + console.log("Expected refund in tokens: " + refund_Token.toString()); let snap = await takeSnapshot(); let init_TokenSupply = await I_SecurityToken.totalSupply(); let init_InvestorTokenBal = await I_SecurityToken.balanceOf(NONACCREDITED1); - let init_InvestorETHBal = new BigNumber(await web3.eth.getBalance(NONACCREDITED1)); + let init_InvestorETHBal = new BN(await web3.eth.getBalance(NONACCREDITED1)); let init_InvestorPOLYBal = await I_PolyToken.balanceOf(NONACCREDITED1); let init_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); - let init_STOETHBal = new BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let init_STOETHBal = new BN(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); let init_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); let init_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); let init_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); - let init_WalletETHBal = new BigNumber(await web3.eth.getBalance(WALLET)); + let init_WalletETHBal = new BN(await web3.eth.getBalance(WALLET)); let init_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET); // Buy with ETH @@ -2244,79 +2239,79 @@ contract("USDTieredSTO", accounts => { value: investment_ETH, gasPrice: GAS_PRICE }); - let gasCost1 = new BigNumber(GAS_PRICE).mul(tx1.receipt.gasUsed); - console.log(" Gas buyWithETH: ".grey + tx1.receipt.gasUsed.toString().grey); + let gasCost1 = new BN(GAS_PRICE).mul(new BN(tx1.receipt.gasUsed)); + console.log(" Gas buyWithETH: ".grey + new BN(tx1.receipt.gasUsed).toString().grey); let final_TokenSupply = await I_SecurityToken.totalSupply(); let final_InvestorTokenBal = await I_SecurityToken.balanceOf(NONACCREDITED1); - let final_InvestorETHBal = new BigNumber(await web3.eth.getBalance(NONACCREDITED1)); + let final_InvestorETHBal = new BN(await web3.eth.getBalance(NONACCREDITED1)); let final_InvestorPOLYBal = await I_PolyToken.balanceOf(NONACCREDITED1); let final_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); - let final_STOETHBal = new BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let final_STOETHBal = new BN(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); let final_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); let final_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); let final_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); - let final_WalletETHBal = new BigNumber(await web3.eth.getBalance(WALLET)); + let final_WalletETHBal = new BN(await web3.eth.getBalance(WALLET)); let final_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET); assert.equal( - final_TokenSupply.toNumber(), + final_TokenSupply.toString(), init_TokenSupply .add(investment_Token) .sub(refund_Token) - .toNumber(), + .toString(), "Token Supply not changed as expected" ); assert.equal( - final_InvestorTokenBal.toNumber(), + final_InvestorTokenBal.toString(), init_InvestorTokenBal .add(investment_Token) .sub(refund_Token) - .toNumber(), + .toString(), "Investor Token Balance not changed as expected" ); assert.equal( - final_InvestorETHBal.toNumber(), + final_InvestorETHBal.toString(), init_InvestorETHBal .sub(gasCost1) .sub(investment_ETH) .add(refund_ETH) - .toNumber(), + .toString(), "Investor ETH Balance not changed as expected" ); assert.equal( - final_InvestorPOLYBal.toNumber(), - init_InvestorPOLYBal.toNumber(), + final_InvestorPOLYBal.toString(), + init_InvestorPOLYBal.toString(), "Investor POLY Balance not changed as expected" ); assert.equal( - final_STOTokenSold.toNumber(), + final_STOTokenSold.toString(), init_STOTokenSold .add(investment_Token) .sub(refund_Token) - .toNumber(), + .toString(), "STO Token Sold not changed as expected" ); - assert.equal(final_STOETHBal.toNumber(), init_STOETHBal.toNumber(), "STO ETH Balance not changed as expected"); - assert.equal(final_STOPOLYBal.toNumber(), init_STOPOLYBal.toNumber(), "STO POLY Balance not changed as expected"); + assert.equal(final_STOETHBal.toString(), init_STOETHBal.toString(), "STO ETH Balance not changed as expected"); + assert.equal(final_STOPOLYBal.toString(), init_STOPOLYBal.toString(), "STO POLY Balance not changed as expected"); assert.equal( - final_RaisedETH.toNumber(), + final_RaisedETH.toString(), init_RaisedETH .add(investment_ETH) .sub(refund_ETH) - .toNumber(), + .toString(), "Raised ETH not changed as expected" ); - assert.equal(final_RaisedPOLY.toNumber(), init_RaisedPOLY.toNumber(), "Raised POLY not changed as expected"); + assert.equal(final_RaisedPOLY.toString(), init_RaisedPOLY.toString(), "Raised POLY not changed as expected"); assert.equal( - final_WalletETHBal.toNumber(), + final_WalletETHBal.toString(), init_WalletETHBal .add(investment_ETH) .sub(refund_ETH) - .toNumber(), + .toString(), "Wallet ETH Balance not changed as expected" ); - assert.equal(final_WalletPOLYBal.toNumber(), init_WalletPOLYBal.toNumber(), "Wallet POLY Balance not changed as expected"); + assert.equal(final_WalletPOLYBal.toString(), init_WalletPOLYBal.toString(), "Wallet POLY Balance not changed as expected"); await revertToSnapshot(snap); @@ -2325,14 +2320,14 @@ contract("USDTieredSTO", accounts => { init_TokenSupply = await I_SecurityToken.totalSupply(); init_InvestorTokenBal = await I_SecurityToken.balanceOf(NONACCREDITED1); - init_InvestorETHBal = new BigNumber(await web3.eth.getBalance(NONACCREDITED1)); + init_InvestorETHBal = new BN(await web3.eth.getBalance(NONACCREDITED1)); init_InvestorPOLYBal = await I_PolyToken.balanceOf(NONACCREDITED1); init_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); - init_STOETHBal = new BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + init_STOETHBal = new BN(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); init_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); init_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); init_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); - init_WalletETHBal = new BigNumber(await web3.eth.getBalance(WALLET)); + init_WalletETHBal = new BN(await web3.eth.getBalance(WALLET)); init_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET); // Buy With POLY @@ -2340,76 +2335,76 @@ contract("USDTieredSTO", accounts => { from: NONACCREDITED1, gasPrice: GAS_PRICE }); - let gasCost2 = new BigNumber(GAS_PRICE).mul(tx2.receipt.gasUsed); - console.log(" Gas buyWithPOLY: ".grey + tx2.receipt.gasUsed.toString().grey); + let gasCost2 = new BN(GAS_PRICE).mul(new BN(tx2.receipt.gasUsed)); + console.log(" Gas buyWithPOLY: ".grey + new BN(tx2.receipt.gasUsed).toString().grey); final_TokenSupply = await I_SecurityToken.totalSupply(); final_InvestorTokenBal = await I_SecurityToken.balanceOf(NONACCREDITED1); - final_InvestorETHBal = new BigNumber(await web3.eth.getBalance(NONACCREDITED1)); + final_InvestorETHBal = new BN(await web3.eth.getBalance(NONACCREDITED1)); final_InvestorPOLYBal = await I_PolyToken.balanceOf(NONACCREDITED1); final_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); - final_STOETHBal = new BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + final_STOETHBal = new BN(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); final_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); final_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); final_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); - final_WalletETHBal = new BigNumber(await web3.eth.getBalance(WALLET)); + final_WalletETHBal = new BN(await web3.eth.getBalance(WALLET)); final_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET); assert.equal( - final_TokenSupply.toNumber(), + final_TokenSupply.toString(), init_TokenSupply .add(investment_Token) .sub(refund_Token) - .toNumber(), + .toString(), "Token Supply not changed as expected" ); assert.equal( - final_InvestorTokenBal.toNumber(), + final_InvestorTokenBal.toString(), init_InvestorTokenBal .add(investment_Token) .sub(refund_Token) - .toNumber(), + .toString(), "Investor Token Balance not changed as expected" ); assert.equal( - final_InvestorETHBal.toNumber(), - init_InvestorETHBal.sub(gasCost2).toNumber(), + final_InvestorETHBal.toString(), + init_InvestorETHBal.sub(gasCost2).toString(), "Investor ETH Balance not changed as expected" ); assert.equal( - final_InvestorPOLYBal.toNumber(), + final_InvestorPOLYBal.toString(), init_InvestorPOLYBal .sub(investment_POLY) .add(refund_POLY) - .toNumber(), + .toString(), "Investor POLY Balance not changed as expected" ); assert.equal( - final_STOTokenSold.toNumber(), + final_STOTokenSold.toString(), init_STOTokenSold .add(investment_Token) .sub(refund_Token) - .toNumber(), + .toString(), "STO Token Sold not changed as expected" ); - assert.equal(final_STOETHBal.toNumber(), init_STOETHBal.toNumber(), "STO ETH Balance not changed as expected"); - assert.equal(final_STOPOLYBal.toNumber(), init_STOPOLYBal.toNumber(), "STO POLY Balance not changed as expected"); - assert.equal(final_RaisedETH.toNumber(), init_RaisedETH.toNumber(), "Raised ETH not changed as expected"); + assert.equal(final_STOETHBal.toString(), init_STOETHBal.toString(), "STO ETH Balance not changed as expected"); + assert.equal(final_STOPOLYBal.toString(), init_STOPOLYBal.toString(), "STO POLY Balance not changed as expected"); + assert.equal(final_RaisedETH.toString(), init_RaisedETH.toString(), "Raised ETH not changed as expected"); assert.equal( - final_RaisedPOLY.toNumber(), + final_RaisedPOLY.toString(), init_RaisedPOLY .add(investment_POLY) .sub(refund_POLY) - .toNumber(), + .toString(), "Raised POLY not changed as expected" ); - assert.equal(final_WalletETHBal.toNumber(), init_WalletETHBal.toNumber(), "Wallet ETH Balance not changed as expected"); + assert.equal(final_WalletETHBal.toString(), init_WalletETHBal.toString(), "Wallet ETH Balance not changed as expected"); assert.equal( - final_WalletPOLYBal.toNumber(), + final_WalletPOLYBal.toString(), init_WalletPOLYBal .add(investment_POLY) .sub(refund_POLY) - .toNumber(), + .toString(), "Wallet POLY Balance not changed as expected" ); }); @@ -2418,7 +2413,7 @@ contract("USDTieredSTO", accounts => { let stoId = 0; let tierId = 0; - let investment_Token = new BigNumber(50).mul(10 ** 18); + let investment_Token = new BN(50).mul(e18); let investment_USD = await convert(stoId, tierId, false, "TOKEN", "USD", investment_Token); let investment_ETH = await convert(stoId, tierId, false, "TOKEN", "ETH", investment_Token); let investment_POLY = await convert(stoId, tierId, false, "TOKEN", "POLY", investment_Token); @@ -2442,16 +2437,16 @@ contract("USDTieredSTO", accounts => { let tierId; // set new exchange rates - let high_USDETH = new BigNumber(1000).mul(10 ** 18); // 1000 USD per ETH - let high_USDPOLY = new BigNumber(50).mul(10 ** 16); // 0.5 USD per POLY - let low_USDETH = new BigNumber(250).mul(10 ** 18); // 250 USD per ETH - let low_USDPOLY = new BigNumber(20).mul(10 ** 16); // 0.2 USD per POLY + let high_USDETH = new BN(1000).mul(e18); // 1000 USD per ETH + let high_USDPOLY = new BN(50).mul(e16); // 0.5 USD per POLY + let low_USDETH = new BN(250).mul(e18); // 250 USD per ETH + let low_USDPOLY = new BN(20).mul(e16); // 0.2 USD per POLY - let investment_USD = new BigNumber(web3.utils.toWei("50")); // USD - let investment_ETH_high = investment_USD.div(high_USDETH).mul(10 ** 18); // USD / USD/ETH = ETH - let investment_POLY_high = investment_USD.div(high_USDPOLY).mul(10 ** 18); // USD / USD/POLY = POLY - let investment_ETH_low = investment_USD.div(low_USDETH).mul(10 ** 18); // USD / USD/ETH = ETH - let investment_POLY_low = investment_USD.div(low_USDPOLY).mul(10 ** 18); // USD / USD/POLY = POLY + let investment_USD = new BN(web3.utils.toWei("50")); // USD + let investment_ETH_high = investment_USD.div(high_USDETH).mul(e18); // USD / USD/ETH = ETH + let investment_POLY_high = investment_USD.div(high_USDPOLY).mul(e18); // USD / USD/POLY = POLY + let investment_ETH_low = investment_USD.div(low_USDETH).mul(e18); // USD / USD/ETH = ETH + let investment_POLY_low = investment_USD.div(low_USDPOLY).mul(e18); // USD / USD/POLY = POLY await I_PolyToken.getTokens(investment_POLY_low, NONACCREDITED1); await I_PolyToken.approve(I_USDTieredSTO_Array[stoId].address, investment_POLY_low, { from: NONACCREDITED1 }); @@ -2507,12 +2502,12 @@ contract("USDTieredSTO", accounts => { let endTier = 1; assert.equal( - (await I_USDTieredSTO_Array[stoId].currentTier.call()).toNumber(), + (await I_USDTieredSTO_Array[stoId].currentTier.call()).toString(), startTier, "currentTier not changed as expected" ); - let delta_Token = new BigNumber(5).mul(10 ** 18); + let delta_Token = new BN(5).mul(e18); let ethTier0 = await convert(stoId, startTier, false, "TOKEN", "ETH", delta_Token); let ethTier1 = await convert(stoId, endTier, false, "TOKEN", "ETH", delta_Token); @@ -2522,14 +2517,14 @@ contract("USDTieredSTO", accounts => { // Process investment let init_TokenSupply = await I_SecurityToken.totalSupply(); let init_InvestorTokenBal = await I_SecurityToken.balanceOf(NONACCREDITED1); - let init_InvestorETHBal = new BigNumber(await web3.eth.getBalance(NONACCREDITED1)); + let init_InvestorETHBal = new BN(await web3.eth.getBalance(NONACCREDITED1)); let init_InvestorPOLYBal = await I_PolyToken.balanceOf(NONACCREDITED1); let init_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); - let init_STOETHBal = new BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let init_STOETHBal = new BN(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); let init_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); let init_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); let init_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); - let init_WalletETHBal = new BigNumber(await web3.eth.getBalance(WALLET)); + let init_WalletETHBal = new BN(await web3.eth.getBalance(WALLET)); let init_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET); let tx1 = await I_USDTieredSTO_Array[stoId].buyWithETH(NONACCREDITED1, { @@ -2537,62 +2532,62 @@ contract("USDTieredSTO", accounts => { value: investment_ETH, gasPrice: GAS_PRICE }); - let gasCost1 = new BigNumber(GAS_PRICE).mul(tx1.receipt.gasUsed); - console.log(" Gas buyWithETH: ".grey + tx1.receipt.gasUsed.toString().grey); + let gasCost1 = new BN(GAS_PRICE).mul(new BN(tx1.receipt.gasUsed)); + console.log(" Gas buyWithETH: ".grey + new BN(tx1.receipt.gasUsed).toString().grey); let final_TokenSupply = await I_SecurityToken.totalSupply(); let final_InvestorTokenBal = await I_SecurityToken.balanceOf(NONACCREDITED1); - let final_InvestorETHBal = new BigNumber(await web3.eth.getBalance(NONACCREDITED1)); + let final_InvestorETHBal = new BN(await web3.eth.getBalance(NONACCREDITED1)); let final_InvestorPOLYBal = await I_PolyToken.balanceOf(NONACCREDITED1); let final_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); - let final_STOETHBal = new BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let final_STOETHBal = new BN(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); let final_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); let final_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); let final_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); - let final_WalletETHBal = new BigNumber(await web3.eth.getBalance(WALLET)); + let final_WalletETHBal = new BN(await web3.eth.getBalance(WALLET)); let final_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET); assert.equal( - final_TokenSupply.toNumber(), - init_TokenSupply.add(investment_Token).toNumber(), + final_TokenSupply.toString(), + init_TokenSupply.add(investment_Token).toString(), "Token Supply not changed as expected" ); assert.equal( - final_InvestorTokenBal.toNumber(), - init_InvestorTokenBal.add(investment_Token).toNumber(), + final_InvestorTokenBal.toString(), + init_InvestorTokenBal.add(investment_Token).toString(), "Investor Token Balance not changed as expected" ); assert.equal( - final_InvestorETHBal.toNumber(), + final_InvestorETHBal.toString(), init_InvestorETHBal .sub(gasCost1) .sub(investment_ETH) - .toNumber(), + .toString(), "Investor ETH Balance not changed as expected" ); assert.equal( - final_InvestorPOLYBal.toNumber(), - init_InvestorPOLYBal.toNumber(), + final_InvestorPOLYBal.toString(), + init_InvestorPOLYBal.toString(), "Investor POLY Balance not changed as expected" ); assert.equal( - final_STOTokenSold.toNumber(), - init_STOTokenSold.add(investment_Token).toNumber(), + final_STOTokenSold.toString(), + init_STOTokenSold.add(investment_Token).toString(), "STO Token Sold not changed as expected" ); - assert.equal(final_STOETHBal.toNumber(), init_STOETHBal.toNumber(), "STO ETH Balance not changed as expected"); - assert.equal(final_STOPOLYBal.toNumber(), init_STOPOLYBal.toNumber(), "STO POLY Balance not changed as expected"); - assert.equal(final_RaisedETH.toNumber(), init_RaisedETH.add(investment_ETH).toNumber(), "Raised ETH not changed as expected"); - assert.equal(final_RaisedPOLY.toNumber(), init_RaisedPOLY.toNumber(), "Raised POLY not changed as expected"); + assert.equal(final_STOETHBal.toString(), init_STOETHBal.toString(), "STO ETH Balance not changed as expected"); + assert.equal(final_STOPOLYBal.toString(), init_STOPOLYBal.toString(), "STO POLY Balance not changed as expected"); + assert.equal(final_RaisedETH.toString(), init_RaisedETH.add(investment_ETH).toString(), "Raised ETH not changed as expected"); + assert.equal(final_RaisedPOLY.toString(), init_RaisedPOLY.toString(), "Raised POLY not changed as expected"); assert.equal( - final_WalletETHBal.toNumber(), - init_WalletETHBal.add(investment_ETH).toNumber(), + final_WalletETHBal.toString(), + init_WalletETHBal.add(investment_ETH).toString(), "Wallet ETH Balance not changed as expected" ); - assert.equal(final_WalletPOLYBal.toNumber(), init_WalletPOLYBal.toNumber(), "Wallet POLY Balance not changed as expected"); + assert.equal(final_WalletPOLYBal.toString(), init_WalletPOLYBal.toString(), "Wallet POLY Balance not changed as expected"); // Additional Checks - assert.equal((await I_USDTieredSTO_Array[stoId].currentTier.call()).toNumber(), endTier, "currentTier not changed as expected"); + assert.equal((await I_USDTieredSTO_Array[stoId].currentTier.call()).toString(), endTier, "currentTier not changed as expected"); }); it("should successfully buy across tiers for NONACCREDITED POLY", async () => { @@ -2601,12 +2596,12 @@ contract("USDTieredSTO", accounts => { let endTier = 2; assert.equal( - (await I_USDTieredSTO_Array[stoId].currentTier.call()).toNumber(), + (await I_USDTieredSTO_Array[stoId].currentTier.call()).toString(), startTier, "currentTier not changed as expected" ); - let delta_Token = new BigNumber(5).mul(10 ** 18); // Token + let delta_Token = new BN(5).mul(e18); // Token let polyTier0 = await convert(stoId, startTier, false, "TOKEN", "POLY", delta_Token); let polyTier1 = await convert(stoId, endTier, false, "TOKEN", "POLY", delta_Token); @@ -2619,77 +2614,77 @@ contract("USDTieredSTO", accounts => { // Process investment let init_TokenSupply = await I_SecurityToken.totalSupply(); let init_InvestorTokenBal = await I_SecurityToken.balanceOf(NONACCREDITED1); - let init_InvestorETHBal = new BigNumber(await web3.eth.getBalance(NONACCREDITED1)); + let init_InvestorETHBal = new BN(await web3.eth.getBalance(NONACCREDITED1)); let init_InvestorPOLYBal = await I_PolyToken.balanceOf(NONACCREDITED1); let init_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); - let init_STOETHBal = new BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let init_STOETHBal = new BN(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); let init_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); let init_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); let init_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); - let init_WalletETHBal = new BigNumber(await web3.eth.getBalance(WALLET)); + let init_WalletETHBal = new BN(await web3.eth.getBalance(WALLET)); let init_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET); let tx2 = await I_USDTieredSTO_Array[stoId].buyWithPOLY(NONACCREDITED1, investment_POLY, { from: NONACCREDITED1, gasPrice: GAS_PRICE }); - let gasCost2 = new BigNumber(GAS_PRICE).mul(tx2.receipt.gasUsed); - console.log(" Gas buyWithPOLY: ".grey + tx2.receipt.gasUsed.toString().grey); + let gasCost2 = new BN(GAS_PRICE).mul(new BN(tx2.receipt.gasUsed)); + console.log(" Gas buyWithPOLY: ".grey + new BN(tx2.receipt.gasUsed).toString().grey); let final_TokenSupply = await I_SecurityToken.totalSupply(); let final_InvestorTokenBal = await I_SecurityToken.balanceOf(NONACCREDITED1); - let final_InvestorETHBal = new BigNumber(await web3.eth.getBalance(NONACCREDITED1)); + let final_InvestorETHBal = new BN(await web3.eth.getBalance(NONACCREDITED1)); let final_InvestorPOLYBal = await I_PolyToken.balanceOf(NONACCREDITED1); let final_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); - let final_STOETHBal = new BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let final_STOETHBal = new BN(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); let final_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); let final_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); let final_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); - let final_WalletETHBal = new BigNumber(await web3.eth.getBalance(WALLET)); + let final_WalletETHBal = new BN(await web3.eth.getBalance(WALLET)); let final_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET); assert.equal( - final_TokenSupply.toNumber(), - init_TokenSupply.add(investment_Token).toNumber(), + final_TokenSupply.toString(), + init_TokenSupply.add(investment_Token).toString(), "Token Supply not changed as expected" ); assert.equal( - final_InvestorTokenBal.toNumber(), - init_InvestorTokenBal.add(investment_Token).toNumber(), + final_InvestorTokenBal.toString(), + init_InvestorTokenBal.add(investment_Token).toString(), "Investor Token Balance not changed as expected" ); assert.equal( - final_InvestorETHBal.toNumber(), - init_InvestorETHBal.sub(gasCost2).toNumber(), + final_InvestorETHBal.toString(), + init_InvestorETHBal.sub(gasCost2).toString(), "Investor ETH Balance not changed as expected" ); assert.equal( - final_InvestorPOLYBal.toNumber(), - init_InvestorPOLYBal.sub(investment_POLY).toNumber(), + final_InvestorPOLYBal.toString(), + init_InvestorPOLYBal.sub(investment_POLY).toString(), "Investor POLY Balance not changed as expected" ); assert.equal( - final_STOTokenSold.toNumber(), - init_STOTokenSold.add(investment_Token).toNumber(), + final_STOTokenSold.toString(), + init_STOTokenSold.add(investment_Token).toString(), "STO Token Sold not changed as expected" ); - assert.equal(final_STOETHBal.toNumber(), init_STOETHBal.toNumber(), "STO ETH Balance not changed as expected"); - assert.equal(final_STOPOLYBal.toNumber(), init_STOPOLYBal.toNumber(), "STO POLY Balance not changed as expected"); - assert.equal(final_RaisedETH.toNumber(), init_RaisedETH.toNumber(), "Raised ETH not changed as expected"); + assert.equal(final_STOETHBal.toString(), init_STOETHBal.toString(), "STO ETH Balance not changed as expected"); + assert.equal(final_STOPOLYBal.toString(), init_STOPOLYBal.toString(), "STO POLY Balance not changed as expected"); + assert.equal(final_RaisedETH.toString(), init_RaisedETH.toString(), "Raised ETH not changed as expected"); assert.equal( - final_RaisedPOLY.toNumber(), - init_RaisedPOLY.add(investment_POLY).toNumber(), + final_RaisedPOLY.toString(), + init_RaisedPOLY.add(investment_POLY).toString(), "Raised POLY not changed as expected" ); - assert.equal(final_WalletETHBal.toNumber(), init_WalletETHBal.toNumber(), "Wallet ETH Balance not changed as expected"); + assert.equal(final_WalletETHBal.toString(), init_WalletETHBal.toString(), "Wallet ETH Balance not changed as expected"); assert.equal( - final_WalletPOLYBal.toNumber(), - init_WalletPOLYBal.add(investment_POLY).toNumber(), + final_WalletPOLYBal.toString(), + init_WalletPOLYBal.add(investment_POLY).toString(), "Wallet POLY Balance not changed as expected" ); // Additional Checks - assert.equal((await I_USDTieredSTO_Array[stoId].currentTier.call()).toNumber(), endTier, "currentTier not changed as expected"); + assert.equal((await I_USDTieredSTO_Array[stoId].currentTier.call()).toString(), endTier, "currentTier not changed as expected"); }); it("should successfully buy across tiers for ACCREDITED ETH", async () => { @@ -2697,15 +2692,13 @@ contract("USDTieredSTO", accounts => { let startTier = 2; let endTier = 3; - await I_USDTieredSTO_Array[stoId].changeAccredited([ACCREDITED1], [true], { from: ISSUER }); - assert.equal( - (await I_USDTieredSTO_Array[stoId].currentTier.call()).toNumber(), + (await I_USDTieredSTO_Array[stoId].currentTier.call()).toString(), startTier, "currentTier not changed as expected" ); - let delta_Token = new BigNumber(5).mul(10 ** 18); // Token + let delta_Token = new BN(5).mul(e18); // Token let ethTier0 = await convert(stoId, startTier, false, "TOKEN", "ETH", delta_Token); let ethTier1 = await convert(stoId, endTier, false, "TOKEN", "ETH", delta_Token); @@ -2715,14 +2708,14 @@ contract("USDTieredSTO", accounts => { // Process investment let init_TokenSupply = await I_SecurityToken.totalSupply(); let init_InvestorTokenBal = await I_SecurityToken.balanceOf(ACCREDITED1); - let init_InvestorETHBal = new BigNumber(await web3.eth.getBalance(ACCREDITED1)); + let init_InvestorETHBal = new BN(await web3.eth.getBalance(ACCREDITED1)); let init_InvestorPOLYBal = await I_PolyToken.balanceOf(ACCREDITED1); let init_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); - let init_STOETHBal = new BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let init_STOETHBal = new BN(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); let init_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); let init_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); let init_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); - let init_WalletETHBal = new BigNumber(await web3.eth.getBalance(WALLET)); + let init_WalletETHBal = new BN(await web3.eth.getBalance(WALLET)); let init_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET); let tx1 = await I_USDTieredSTO_Array[stoId].buyWithETH(ACCREDITED1, { @@ -2730,62 +2723,62 @@ contract("USDTieredSTO", accounts => { value: investment_ETH, gasPrice: GAS_PRICE }); - let gasCost1 = new BigNumber(GAS_PRICE).mul(tx1.receipt.gasUsed); - console.log(" Gas buyWithETH: ".grey + tx1.receipt.gasUsed.toString().grey); + let gasCost1 = new BN(GAS_PRICE).mul(new BN(tx1.receipt.gasUsed)); + console.log(" Gas buyWithETH: ".grey + new BN(tx1.receipt.gasUsed).toString().grey); let final_TokenSupply = await I_SecurityToken.totalSupply(); let final_InvestorTokenBal = await I_SecurityToken.balanceOf(ACCREDITED1); - let final_InvestorETHBal = new BigNumber(await web3.eth.getBalance(ACCREDITED1)); + let final_InvestorETHBal = new BN(await web3.eth.getBalance(ACCREDITED1)); let final_InvestorPOLYBal = await I_PolyToken.balanceOf(ACCREDITED1); let final_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); - let final_STOETHBal = new BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let final_STOETHBal = new BN(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); let final_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); let final_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); let final_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); - let final_WalletETHBal = new BigNumber(await web3.eth.getBalance(WALLET)); + let final_WalletETHBal = new BN(await web3.eth.getBalance(WALLET)); let final_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET); assert.equal( - final_TokenSupply.toNumber(), - init_TokenSupply.add(investment_Token).toNumber(), + final_TokenSupply.toString(), + init_TokenSupply.add(investment_Token).toString(), "Token Supply not changed as expected" ); assert.equal( - final_InvestorTokenBal.toNumber(), - init_InvestorTokenBal.add(investment_Token).toNumber(), + final_InvestorTokenBal.toString(), + init_InvestorTokenBal.add(investment_Token).toString(), "Investor Token Balance not changed as expected" ); assert.equal( - final_InvestorETHBal.toNumber(), + final_InvestorETHBal.toString(), init_InvestorETHBal .sub(gasCost1) .sub(investment_ETH) - .toNumber(), + .toString(), "Investor ETH Balance not changed as expected" ); assert.equal( - final_InvestorPOLYBal.toNumber(), - init_InvestorPOLYBal.toNumber(), + final_InvestorPOLYBal.toString(), + init_InvestorPOLYBal.toString(), "Investor POLY Balance not changed as expected" ); assert.equal( - final_STOTokenSold.toNumber(), - init_STOTokenSold.add(investment_Token).toNumber(), + final_STOTokenSold.toString(), + init_STOTokenSold.add(investment_Token).toString(), "STO Token Sold not changed as expected" ); - assert.equal(final_STOETHBal.toNumber(), init_STOETHBal.toNumber(), "STO ETH Balance not changed as expected"); - assert.equal(final_STOPOLYBal.toNumber(), init_STOPOLYBal.toNumber(), "STO POLY Balance not changed as expected"); - assert.equal(final_RaisedETH.toNumber(), init_RaisedETH.add(investment_ETH).toNumber(), "Raised ETH not changed as expected"); - assert.equal(final_RaisedPOLY.toNumber(), init_RaisedPOLY.toNumber(), "Raised POLY not changed as expected"); + assert.equal(final_STOETHBal.toString(), init_STOETHBal.toString(), "STO ETH Balance not changed as expected"); + assert.equal(final_STOPOLYBal.toString(), init_STOPOLYBal.toString(), "STO POLY Balance not changed as expected"); + assert.equal(final_RaisedETH.toString(), init_RaisedETH.add(investment_ETH).toString(), "Raised ETH not changed as expected"); + assert.equal(final_RaisedPOLY.toString(), init_RaisedPOLY.toString(), "Raised POLY not changed as expected"); assert.equal( - final_WalletETHBal.toNumber(), - init_WalletETHBal.add(investment_ETH).toNumber(), + final_WalletETHBal.toString(), + init_WalletETHBal.add(investment_ETH).toString(), "Wallet ETH Balance not changed as expected" ); - assert.equal(final_WalletPOLYBal.toNumber(), init_WalletPOLYBal.toNumber(), "Wallet POLY Balance not changed as expected"); + assert.equal(final_WalletPOLYBal.toString(), init_WalletPOLYBal.toString(), "Wallet POLY Balance not changed as expected"); // Additional Checks - assert.equal((await I_USDTieredSTO_Array[stoId].currentTier.call()).toNumber(), endTier, "currentTier not changed as expected"); + assert.equal((await I_USDTieredSTO_Array[stoId].currentTier.call()).toString(), endTier, "currentTier not changed as expected"); }); it("should successfully buy across tiers for ACCREDITED DAI", async () => { @@ -2794,12 +2787,12 @@ contract("USDTieredSTO", accounts => { let endTier = 4; assert.equal( - (await I_USDTieredSTO_Array[stoId].currentTier.call()).toNumber(), + (await I_USDTieredSTO_Array[stoId].currentTier.call()).toString(), startTier, "currentTier not changed as expected" ); - let delta_Token = new BigNumber(5).mul(10 ** 18); // Token + let delta_Token = new BN(5).mul(e18); // Token let daiTier0 = await convert(stoId, startTier, false, "TOKEN", "USD", delta_Token); let daiTier1 = await convert(stoId, endTier, false, "TOKEN", "USD", delta_Token); @@ -2812,83 +2805,83 @@ contract("USDTieredSTO", accounts => { // Process investment let init_TokenSupply = await I_SecurityToken.totalSupply(); let init_InvestorTokenBal = await I_SecurityToken.balanceOf(ACCREDITED1); - let init_InvestorETHBal = new BigNumber(await web3.eth.getBalance(ACCREDITED1)); + let init_InvestorETHBal = new BN(await web3.eth.getBalance(ACCREDITED1)); let init_InvestorPOLYBal = await I_PolyToken.balanceOf(ACCREDITED1); let init_InvestorDAIBal = await I_DaiToken.balanceOf(ACCREDITED1); let init_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); - let init_STOETHBal = new BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let init_STOETHBal = new BN(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); let init_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); let init_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); let init_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); let init_RaisedDAI = await I_USDTieredSTO_Array[stoId].fundsRaised.call(DAI); - let init_WalletETHBal = new BigNumber(await web3.eth.getBalance(WALLET)); + let init_WalletETHBal = new BN(await web3.eth.getBalance(WALLET)); let init_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET); let init_WalletDAIBal = await I_DaiToken.balanceOf(WALLET); let tx2 = await I_USDTieredSTO_Array[stoId].buyWithUSD(ACCREDITED1, investment_DAI, I_DaiToken.address, { from: ACCREDITED1, gasPrice: GAS_PRICE }); - let gasCost2 = new BigNumber(GAS_PRICE).mul(tx2.receipt.gasUsed); - console.log(" Gas buyWithUSD: ".grey + tx2.receipt.gasUsed.toString().grey); + let gasCost2 = new BN(GAS_PRICE).mul(new BN(tx2.receipt.gasUsed)); + console.log(" Gas buyWithUSD: ".grey + new BN(tx2.receipt.gasUsed).toString().grey); let final_TokenSupply = await I_SecurityToken.totalSupply(); let final_InvestorTokenBal = await I_SecurityToken.balanceOf(ACCREDITED1); - let final_InvestorETHBal = new BigNumber(await web3.eth.getBalance(ACCREDITED1)); + let final_InvestorETHBal = new BN(await web3.eth.getBalance(ACCREDITED1)); let final_InvestorPOLYBal = await I_PolyToken.balanceOf(ACCREDITED1); let final_InvestorDAIBal = await I_DaiToken.balanceOf(ACCREDITED1); let final_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); - let final_STOETHBal = new BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let final_STOETHBal = new BN(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); let final_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); let final_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); let final_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); let final_RaisedDAI = await I_USDTieredSTO_Array[stoId].fundsRaised.call(DAI); - let final_WalletETHBal = new BigNumber(await web3.eth.getBalance(WALLET)); + let final_WalletETHBal = new BN(await web3.eth.getBalance(WALLET)); let final_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET); let final_WalletDAIBal = await I_DaiToken.balanceOf(WALLET); assert.equal( - final_TokenSupply.toNumber(), - init_TokenSupply.add(investment_Token).toNumber(), + final_TokenSupply.toString(), + init_TokenSupply.add(investment_Token).toString(), "Token Supply not changed as expected" ); assert.equal( - final_InvestorTokenBal.toNumber(), - init_InvestorTokenBal.add(investment_Token).toNumber(), + final_InvestorTokenBal.toString(), + init_InvestorTokenBal.add(investment_Token).toString(), "Investor Token Balance not changed as expected" ); assert.equal( - final_InvestorETHBal.toNumber(), - init_InvestorETHBal.sub(gasCost2).toNumber(), + final_InvestorETHBal.toString(), + init_InvestorETHBal.sub(gasCost2).toString(), "Investor ETH Balance not changed as expected" ); assert.equal( - final_InvestorPOLYBal.toNumber(), - init_InvestorPOLYBal.toNumber(), + final_InvestorPOLYBal.toString(), + init_InvestorPOLYBal.toString(), "Investor POLY Balance not changed as expected" ); assert.equal( - final_InvestorDAIBal.toNumber(), - init_InvestorDAIBal.sub(investment_DAI).toNumber(), + final_InvestorDAIBal.toString(), + init_InvestorDAIBal.sub(investment_DAI).toString(), "Investor POLY Balance not changed as expected" ); assert.equal( - final_STOTokenSold.toNumber(), - init_STOTokenSold.add(investment_Token).toNumber(), + final_STOTokenSold.toString(), + init_STOTokenSold.add(investment_Token).toString(), "STO Token Sold not changed as expected" ); - assert.equal(final_STOETHBal.toNumber(), init_STOETHBal.toNumber(), "STO ETH Balance not changed as expected"); - assert.equal(final_STOPOLYBal.toNumber(), init_STOPOLYBal.toNumber(), "STO POLY Balance not changed as expected"); - assert.equal(final_RaisedETH.toNumber(), init_RaisedETH.toNumber(), "Raised ETH not changed as expected"); - assert.equal(final_RaisedPOLY.toNumber(), init_RaisedPOLY.toNumber(), "Raised POLY not changed as expected"); - assert.equal(final_RaisedDAI.toNumber(), init_RaisedDAI.add(investment_DAI).toNumber(), "Raised DAI not changed as expected"); - assert.equal(final_WalletETHBal.toNumber(), init_WalletETHBal.toNumber(), "Wallet ETH Balance not changed as expected"); - assert.equal(final_WalletPOLYBal.toNumber(), init_WalletPOLYBal.toNumber(), "Wallet POLY Balance not changed as expected"); + assert.equal(final_STOETHBal.toString(), init_STOETHBal.toString(), "STO ETH Balance not changed as expected"); + assert.equal(final_STOPOLYBal.toString(), init_STOPOLYBal.toString(), "STO POLY Balance not changed as expected"); + assert.equal(final_RaisedETH.toString(), init_RaisedETH.toString(), "Raised ETH not changed as expected"); + assert.equal(final_RaisedPOLY.toString(), init_RaisedPOLY.toString(), "Raised POLY not changed as expected"); + assert.equal(final_RaisedDAI.toString(), init_RaisedDAI.add(investment_DAI).toString(), "Raised DAI not changed as expected"); + assert.equal(final_WalletETHBal.toString(), init_WalletETHBal.toString(), "Wallet ETH Balance not changed as expected"); + assert.equal(final_WalletPOLYBal.toString(), init_WalletPOLYBal.toString(), "Wallet POLY Balance not changed as expected"); assert.equal( - final_WalletDAIBal.toNumber(), - init_WalletDAIBal.add(investment_DAI).toNumber(), + final_WalletDAIBal.toString(), + init_WalletDAIBal.add(investment_DAI).toString(), "Wallet POLY Balance not changed as expected" ); // Additional Checks - assert.equal((await I_USDTieredSTO_Array[stoId].currentTier.call()).toNumber(), endTier, "currentTier not changed as expected"); + assert.equal((await I_USDTieredSTO_Array[stoId].currentTier.call()).toString(), endTier, "currentTier not changed as expected"); }); it("should successfully buy across tiers for ACCREDITED POLY", async () => { @@ -2897,12 +2890,12 @@ contract("USDTieredSTO", accounts => { let endTier = 5; assert.equal( - (await I_USDTieredSTO_Array[stoId].currentTier.call()).toNumber(), + (await I_USDTieredSTO_Array[stoId].currentTier.call()).toString(), startTier, "currentTier not changed as expected" ); - let delta_Token = new BigNumber(5).mul(10 ** 18); // Token + let delta_Token = new BN(5).mul(e18); // Token let polyTier0 = await convert(stoId, startTier, false, "TOKEN", "POLY", delta_Token); let polyTier1 = await convert(stoId, endTier, false, "TOKEN", "POLY", delta_Token); @@ -2915,77 +2908,77 @@ contract("USDTieredSTO", accounts => { // Process investment let init_TokenSupply = await I_SecurityToken.totalSupply(); let init_InvestorTokenBal = await I_SecurityToken.balanceOf(ACCREDITED1); - let init_InvestorETHBal = new BigNumber(await web3.eth.getBalance(ACCREDITED1)); + let init_InvestorETHBal = new BN(await web3.eth.getBalance(ACCREDITED1)); let init_InvestorPOLYBal = await I_PolyToken.balanceOf(ACCREDITED1); let init_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); - let init_STOETHBal = new BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let init_STOETHBal = new BN(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); let init_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); let init_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); let init_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); - let init_WalletETHBal = new BigNumber(await web3.eth.getBalance(WALLET)); + let init_WalletETHBal = new BN(await web3.eth.getBalance(WALLET)); let init_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET); let tx2 = await I_USDTieredSTO_Array[stoId].buyWithPOLY(ACCREDITED1, investment_POLY, { from: ACCREDITED1, gasPrice: GAS_PRICE }); - let gasCost2 = new BigNumber(GAS_PRICE).mul(tx2.receipt.gasUsed); - console.log(" Gas buyWithPOLY: ".grey + tx2.receipt.gasUsed.toString().grey); + let gasCost2 = new BN(GAS_PRICE).mul(new BN(tx2.receipt.gasUsed)); + console.log(" Gas buyWithPOLY: ".grey + new BN(tx2.receipt.gasUsed).toString().grey); let final_TokenSupply = await I_SecurityToken.totalSupply(); let final_InvestorTokenBal = await I_SecurityToken.balanceOf(ACCREDITED1); - let final_InvestorETHBal = new BigNumber(await web3.eth.getBalance(ACCREDITED1)); + let final_InvestorETHBal = new BN(await web3.eth.getBalance(ACCREDITED1)); let final_InvestorPOLYBal = await I_PolyToken.balanceOf(ACCREDITED1); let final_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); - let final_STOETHBal = new BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let final_STOETHBal = new BN(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); let final_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); let final_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); let final_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); - let final_WalletETHBal = new BigNumber(await web3.eth.getBalance(WALLET)); + let final_WalletETHBal = new BN(await web3.eth.getBalance(WALLET)); let final_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET); assert.equal( - final_TokenSupply.toNumber(), - init_TokenSupply.add(investment_Token).toNumber(), + final_TokenSupply.toString(), + init_TokenSupply.add(investment_Token).toString(), "Token Supply not changed as expected" ); assert.equal( - final_InvestorTokenBal.toNumber(), - init_InvestorTokenBal.add(investment_Token).toNumber(), + final_InvestorTokenBal.toString(), + init_InvestorTokenBal.add(investment_Token).toString(), "Investor Token Balance not changed as expected" ); assert.equal( - final_InvestorETHBal.toNumber(), - init_InvestorETHBal.sub(gasCost2).toNumber(), + final_InvestorETHBal.toString(), + init_InvestorETHBal.sub(gasCost2).toString(), "Investor ETH Balance not changed as expected" ); assert.equal( - final_InvestorPOLYBal.toNumber(), - init_InvestorPOLYBal.sub(investment_POLY).toNumber(), + final_InvestorPOLYBal.toString(), + init_InvestorPOLYBal.sub(investment_POLY).toString(), "Investor POLY Balance not changed as expected" ); assert.equal( - final_STOTokenSold.toNumber(), - init_STOTokenSold.add(investment_Token).toNumber(), + final_STOTokenSold.toString(), + init_STOTokenSold.add(investment_Token).toString(), "STO Token Sold not changed as expected" ); - assert.equal(final_STOETHBal.toNumber(), init_STOETHBal.toNumber(), "STO ETH Balance not changed as expected"); - assert.equal(final_STOPOLYBal.toNumber(), init_STOPOLYBal.toNumber(), "STO POLY Balance not changed as expected"); - assert.equal(final_RaisedETH.toNumber(), init_RaisedETH.toNumber(), "Raised ETH not changed as expected"); + assert.equal(final_STOETHBal.toString(), init_STOETHBal.toString(), "STO ETH Balance not changed as expected"); + assert.equal(final_STOPOLYBal.toString(), init_STOPOLYBal.toString(), "STO POLY Balance not changed as expected"); + assert.equal(final_RaisedETH.toString(), init_RaisedETH.toString(), "Raised ETH not changed as expected"); assert.equal( - final_RaisedPOLY.toNumber(), - init_RaisedPOLY.add(investment_POLY).toNumber(), + final_RaisedPOLY.toString(), + init_RaisedPOLY.add(investment_POLY).toString(), "Raised POLY not changed as expected" ); - assert.equal(final_WalletETHBal.toNumber(), init_WalletETHBal.toNumber(), "Wallet ETH Balance not changed as expected"); + assert.equal(final_WalletETHBal.toString(), init_WalletETHBal.toString(), "Wallet ETH Balance not changed as expected"); assert.equal( - final_WalletPOLYBal.toNumber(), - init_WalletPOLYBal.add(investment_POLY).toNumber(), + final_WalletPOLYBal.toString(), + init_WalletPOLYBal.add(investment_POLY).toString(), "Wallet POLY Balance not changed as expected" ); // Additional Checks - assert.equal((await I_USDTieredSTO_Array[stoId].currentTier.call()).toNumber(), endTier, "currentTier not changed as expected"); + assert.equal((await I_USDTieredSTO_Array[stoId].currentTier.call()).toString(), endTier, "currentTier not changed as expected"); }); it("should buy out the rest of the sto", async () => { @@ -2993,9 +2986,9 @@ contract("USDTieredSTO", accounts => { let tierId = 5; let minted = (await I_USDTieredSTO_Array[stoId].tiers.call(tierId))[4]; - console.log(minted.toNumber() + ":" + _tokensPerTierTotal[stoId][tierId]); + console.log(minted.toString() + ":" + _tokensPerTierTotal[stoId][tierId]); let investment_Token = _tokensPerTierTotal[stoId][tierId].sub(minted); - console.log(investment_Token.toNumber()); + console.log(investment_Token.toString()); let investment_ETH = await convert(stoId, tierId, false, "TOKEN", "ETH", investment_Token); let init_TokenSupply = await I_SecurityToken.totalSupply(); @@ -3014,28 +3007,28 @@ contract("USDTieredSTO", accounts => { let final_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); assert.equal( - final_TokenSupply.toNumber(), - init_TokenSupply.add(investment_Token).toNumber(), + final_TokenSupply.toString(), + init_TokenSupply.add(investment_Token).toString(), "Token Supply not changed as expected" ); assert.equal( - final_InvestorTokenBal.toNumber(), - init_InvestorTokenBal.add(investment_Token).toNumber(), + final_InvestorTokenBal.toString(), + init_InvestorTokenBal.add(investment_Token).toString(), "Investor Token Balance not changed as expected" ); assert.equal( - final_STOTokenSold.toNumber(), - init_STOTokenSold.add(investment_Token).toNumber(), + final_STOTokenSold.toString(), + init_STOTokenSold.add(investment_Token).toString(), "STO Token Sold not changed as expected" ); - // assert.equal((await I_USDTieredSTO_Array[1].getTokensMinted()).toNumber(), _tokensPerTierTotal[1].reduce((a, b) => a + b, 0).toNumber(), "STO Token Sold not changed as expected"); + // assert.equal((await I_USDTieredSTO_Array[1].getTokensMinted()).toString(), _tokensPerTierTotal[1].reduce((a, b) => a + b, 0).toString(), "STO Token Sold not changed as expected"); }); it("should fail and revert when all tiers sold out", async () => { let stoId = 1; let tierId = 4; - let investment_Token = new BigNumber(5).mul(10 ** 18); + let investment_Token = new BN(5).mul(e18); let investment_USD = await convert(stoId, tierId, false, "TOKEN", "USD", investment_Token); let investment_ETH = await convert(stoId, tierId, false, "TOKEN", "ETH", investment_Token); let investment_POLY = await convert(stoId, tierId, false, "TOKEN", "POLY", investment_Token); @@ -3091,16 +3084,16 @@ contract("USDTieredSTO", accounts => { let tierId = 4; // set new exchange rates - let high_USDETH = new BigNumber(1000).mul(10 ** 18); // 1000 USD per ETH - let high_USDPOLY = new BigNumber(50).mul(10 ** 16); // 0.5 USD per POLY - let low_USDETH = new BigNumber(250).mul(10 ** 18); // 250 USD per ETH - let low_USDPOLY = new BigNumber(20).mul(10 ** 16); // 0.2 USD per POLY + let high_USDETH = new BN(1000).mul(e18); // 1000 USD per ETH + let high_USDPOLY = new BN(50).mul(e16); // 0.5 USD per POLY + let low_USDETH = new BN(250).mul(e18); // 250 USD per ETH + let low_USDPOLY = new BN(20).mul(e16); // 0.2 USD per POLY - let investment_USD = new BigNumber(web3.utils.toWei("50")); // USD - let investment_ETH_high = investment_USD.div(high_USDETH).mul(10 ** 18); // USD / USD/ETH = ETH - let investment_POLY_high = investment_USD.div(high_USDPOLY).mul(10 ** 18); // USD / USD/POLY = POLY - let investment_ETH_low = investment_USD.div(low_USDETH).mul(10 ** 18); // USD / USD/ETH = ETH - let investment_POLY_low = investment_USD.div(low_USDPOLY).mul(10 ** 18); // USD / USD/POLY = POLY + let investment_USD = new BN(web3.utils.toWei("50")); // USD + let investment_ETH_high = investment_USD.div(high_USDETH).mul(e18); // USD / USD/ETH = ETH + let investment_POLY_high = investment_USD.div(high_USDPOLY).mul(e18); // USD / USD/POLY = POLY + let investment_ETH_low = investment_USD.div(low_USDETH).mul(e18); // USD / USD/ETH = ETH + let investment_POLY_low = investment_USD.div(low_USDPOLY).mul(e18); // USD / USD/POLY = POLY await I_PolyToken.getTokens(investment_POLY_low, NONACCREDITED1); await I_PolyToken.approve(I_USDTieredSTO_Array[stoId].address, investment_POLY_low, { from: NONACCREDITED1 }); @@ -3182,22 +3175,22 @@ contract("USDTieredSTO", accounts => { let stoId = 2; let tierId = 0; - let investment_Token = new BigNumber(5).mul(10 ** 18); + let investment_Token = new BN(5).mul(e18); let investment_USD = await convert(stoId, tierId, false, "TOKEN", "USD", investment_Token); let investment_ETH = await convert(stoId, tierId, false, "TOKEN", "ETH", investment_Token); let investment_POLY = await convert(stoId, tierId, false, "TOKEN", "POLY", investment_Token); let init_TokenSupply = await I_SecurityToken.totalSupply(); let init_InvestorTokenBal = await I_SecurityToken.balanceOf(NONACCREDITED1); - let init_InvestorETHBal = new BigNumber(await web3.eth.getBalance(NONACCREDITED1)); + let init_InvestorETHBal = new BN(await web3.eth.getBalance(NONACCREDITED1)); let init_InvestorPOLYBal = await I_PolyToken.balanceOf(NONACCREDITED1); let init_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); - let init_STOETHBal = new BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let init_STOETHBal = new BN(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); let init_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); let init_RaisedUSD = await I_USDTieredSTO_Array[stoId].fundsRaisedUSD.call(); let init_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); let init_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); - let init_WalletETHBal = new BigNumber(await web3.eth.getBalance(WALLET)); + let init_WalletETHBal = new BN(await web3.eth.getBalance(WALLET)); let init_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET); let tx1 = await web3.eth.sendTransaction({ @@ -3205,99 +3198,99 @@ contract("USDTieredSTO", accounts => { to: I_USDTieredSTO_Array[stoId].address, value: investment_ETH, gasPrice: GAS_PRICE, - gas: 1000000 + gas: 7000000 }); - let gasCost1 = new BigNumber(GAS_PRICE).mul(tx1.gasUsed); - console.log(" Gas fallback purchase: ".grey + tx1.gasUsed.toString().grey); + let gasCost1 = new BN(GAS_PRICE).mul(new BN(tx1.gasUsed)); + console.log(" Gas fallback purchase: ".grey + new BN(tx1.gasUsed).toString().grey); let final_TokenSupply = await I_SecurityToken.totalSupply(); let final_InvestorTokenBal = await I_SecurityToken.balanceOf(NONACCREDITED1); - let final_InvestorETHBal = new BigNumber(await web3.eth.getBalance(NONACCREDITED1)); + let final_InvestorETHBal = new BN(await web3.eth.getBalance(NONACCREDITED1)); let final_InvestorPOLYBal = await I_PolyToken.balanceOf(NONACCREDITED1); let final_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); - let final_STOETHBal = new BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let final_STOETHBal = new BN(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); let final_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); let final_RaisedUSD = await I_USDTieredSTO_Array[stoId].fundsRaisedUSD.call(); let final_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); let final_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); - let final_WalletETHBal = new BigNumber(await web3.eth.getBalance(WALLET)); + let final_WalletETHBal = new BN(await web3.eth.getBalance(WALLET)); let final_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET); assert.equal( - final_TokenSupply.toNumber(), - init_TokenSupply.add(investment_Token).toNumber(), + final_TokenSupply.toString(), + init_TokenSupply.add(investment_Token).toString(), "Token Supply not changed as expected" ); assert.equal( - final_InvestorTokenBal.toNumber(), - init_InvestorTokenBal.add(investment_Token).toNumber(), + final_InvestorTokenBal.toString(), + init_InvestorTokenBal.add(investment_Token).toString(), "Investor Token Balance not changed as expected" ); assert.equal( - final_InvestorETHBal.toNumber(), + final_InvestorETHBal.toString(), init_InvestorETHBal .sub(gasCost1) .sub(investment_ETH) - .toNumber(), + .toString(), "Investor ETH Balance not changed as expected" ); assert.equal( - final_InvestorPOLYBal.toNumber(), - init_InvestorPOLYBal.toNumber(), + final_InvestorPOLYBal.toString(), + init_InvestorPOLYBal.toString(), "Investor POLY Balance not changed as expected" ); assert.equal( - final_STOTokenSold.toNumber(), - init_STOTokenSold.add(investment_Token).toNumber(), + final_STOTokenSold.toString(), + init_STOTokenSold.add(investment_Token).toString(), "STO Token Sold not changed as expected" ); - assert.equal(final_STOETHBal.toNumber(), init_STOETHBal.toNumber(), "STO ETH Balance not changed as expected"); - assert.equal(final_STOPOLYBal.toNumber(), init_STOPOLYBal.toNumber(), "STO POLY Balance not changed as expected"); - assert.equal(final_RaisedUSD.toNumber(), init_RaisedUSD.add(investment_USD).toNumber(), "Raised USD not changed as expected"); - assert.equal(final_RaisedETH.toNumber(), init_RaisedETH.add(investment_ETH).toNumber(), "Raised ETH not changed as expected"); - assert.equal(final_RaisedPOLY.toNumber(), init_RaisedPOLY.toNumber(), "Raised POLY not changed as expected"); + assert.equal(final_STOETHBal.toString(), init_STOETHBal.toString(), "STO ETH Balance not changed as expected"); + assert.equal(final_STOPOLYBal.toString(), init_STOPOLYBal.toString(), "STO POLY Balance not changed as expected"); + assert.equal(final_RaisedUSD.toString(), init_RaisedUSD.add(investment_USD).toString(), "Raised USD not changed as expected"); + assert.equal(final_RaisedETH.toString(), init_RaisedETH.add(investment_ETH).toString(), "Raised ETH not changed as expected"); + assert.equal(final_RaisedPOLY.toString(), init_RaisedPOLY.toString(), "Raised POLY not changed as expected"); assert.equal( - final_WalletETHBal.toNumber(), - init_WalletETHBal.add(investment_ETH).toNumber(), + final_WalletETHBal.toString(), + init_WalletETHBal.add(investment_ETH).toString(), "Wallet ETH Balance not changed as expected" ); - assert.equal(final_WalletPOLYBal.toNumber(), init_WalletPOLYBal.toNumber(), "Wallet POLY Balance not changed as expected"); + assert.equal(final_WalletPOLYBal.toString(), init_WalletPOLYBal.toString(), "Wallet POLY Balance not changed as expected"); // Additional checks on getters - assert.equal((await I_USDTieredSTO_Array[stoId].investorCount.call()).toNumber(), 1, "Investor count not changed as expected"); + assert.equal((await I_USDTieredSTO_Array[stoId].investorCount.call()).toString(), 1, "Investor count not changed as expected"); assert.equal( - (await I_USDTieredSTO_Array[stoId].getTokensSold()).toNumber(), - investment_Token.toNumber(), + (await I_USDTieredSTO_Array[stoId].getTokensSold()).toString(), + investment_Token.toString(), "getTokensSold not changed as expected" ); assert.equal( - (await I_USDTieredSTO_Array[stoId].getTokensMinted()).toNumber(), - investment_Token.toNumber(), + (await I_USDTieredSTO_Array[stoId].getTokensMinted()).toString(), + investment_Token.toString(), "getTokensMinted not changed as expected" ); assert.equal( - (await I_USDTieredSTO_Array[stoId].getTokensSoldFor(ETH)).toNumber(), - investment_Token.toNumber(), + (await I_USDTieredSTO_Array[stoId].getTokensSoldFor(ETH)).toString(), + investment_Token.toString(), "getTokensSoldForETH not changed as expected" ); assert.equal( - (await I_USDTieredSTO_Array[stoId].getTokensSoldFor(POLY)).toNumber(), - 0, + (await I_USDTieredSTO_Array[stoId].getTokensSoldFor(POLY)).toString(), + new BN(0), "getTokensSoldForPOLY not changed as expected" ); assert.equal( - (await I_USDTieredSTO_Array[stoId].investorInvestedUSD.call(NONACCREDITED1)).toNumber(), - investment_USD.toNumber(), + (await I_USDTieredSTO_Array[stoId].investorInvestedUSD.call(NONACCREDITED1)).toString(), + investment_USD.toString(), "investorInvestedUSD not changed as expected" ); assert.equal( - (await I_USDTieredSTO_Array[stoId].investorInvested.call(NONACCREDITED1, ETH)).toNumber(), - investment_ETH.toNumber(), + (await I_USDTieredSTO_Array[stoId].investorInvested.call(NONACCREDITED1, ETH)).toString(), + investment_ETH.toString(), "investorInvestedETH not changed as expected" ); assert.equal( - (await I_USDTieredSTO_Array[stoId].investorInvested.call(NONACCREDITED1, POLY)).toNumber(), - 0, + (await I_USDTieredSTO_Array[stoId].investorInvested.call(NONACCREDITED1, POLY)).toString(), + new BN(0), "investorInvestedPOLY not changed as expected" ); }); @@ -3306,21 +3299,21 @@ contract("USDTieredSTO", accounts => { let stoId = 2; let tierId = 0; - let investment_Token = new BigNumber(5).mul(10 ** 18); + let investment_Token = new BN(5).mul(e18); let investment_USD = await convert(stoId, tierId, false, "TOKEN", "USD", investment_Token); let investment_ETH = await convert(stoId, tierId, false, "TOKEN", "ETH", investment_Token); let investment_POLY = await convert(stoId, tierId, false, "TOKEN", "POLY", investment_Token); let init_TokenSupply = await I_SecurityToken.totalSupply(); let init_InvestorTokenBal = await I_SecurityToken.balanceOf(NONACCREDITED1); - let init_InvestorETHBal = new BigNumber(await web3.eth.getBalance(NONACCREDITED1)); + let init_InvestorETHBal = new BN(await web3.eth.getBalance(NONACCREDITED1)); let init_InvestorPOLYBal = await I_PolyToken.balanceOf(NONACCREDITED1); let init_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); - let init_STOETHBal = new BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let init_STOETHBal = new BN(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); let init_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); let init_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); let init_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); - let init_WalletETHBal = new BigNumber(await web3.eth.getBalance(WALLET)); + let init_WalletETHBal = new BN(await web3.eth.getBalance(WALLET)); let init_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET); let tx1 = await I_USDTieredSTO_Array[stoId].buyWithETH(NONACCREDITED1, { @@ -3328,66 +3321,66 @@ contract("USDTieredSTO", accounts => { value: investment_ETH, gasPrice: GAS_PRICE }); - let gasCost1 = new BigNumber(GAS_PRICE).mul(tx1.receipt.gasUsed); - console.log(" Gas buyWithETH: ".grey + tx1.receipt.gasUsed.toString().grey); + let gasCost1 = new BN(GAS_PRICE).mul(new BN(tx1.receipt.gasUsed)); + console.log(" Gas buyWithETH: ".grey + new BN(tx1.receipt.gasUsed).toString().grey); let final_TokenSupply = await I_SecurityToken.totalSupply(); let final_InvestorTokenBal = await I_SecurityToken.balanceOf(NONACCREDITED1); - let final_InvestorETHBal = new BigNumber(await web3.eth.getBalance(NONACCREDITED1)); + let final_InvestorETHBal = new BN(await web3.eth.getBalance(NONACCREDITED1)); let final_InvestorPOLYBal = await I_PolyToken.balanceOf(NONACCREDITED1); let final_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); - let final_STOETHBal = new BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let final_STOETHBal = new BN(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); let final_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); let final_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); let final_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); - let final_WalletETHBal = new BigNumber(await web3.eth.getBalance(WALLET)); + let final_WalletETHBal = new BN(await web3.eth.getBalance(WALLET)); let final_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET); assert.equal( - final_TokenSupply.toNumber(), - init_TokenSupply.add(investment_Token).toNumber(), + final_TokenSupply.toString(), + init_TokenSupply.add(investment_Token).toString(), "Token Supply not changed as expected" ); assert.equal( - final_InvestorTokenBal.toNumber(), - init_InvestorTokenBal.add(investment_Token).toNumber(), + final_InvestorTokenBal.toString(), + init_InvestorTokenBal.add(investment_Token).toString(), "Investor Token Balance not changed as expected" ); assert.equal( - final_InvestorETHBal.toNumber(), + final_InvestorETHBal.toString(), init_InvestorETHBal .sub(gasCost1) .sub(investment_ETH) - .toNumber(), + .toString(), "Investor ETH Balance not changed as expected" ); assert.equal( - final_InvestorPOLYBal.toNumber(), - init_InvestorPOLYBal.toNumber(), + final_InvestorPOLYBal.toString(), + init_InvestorPOLYBal.toString(), "Investor POLY Balance not changed as expected" ); assert.equal( - final_STOTokenSold.toNumber(), - init_STOTokenSold.add(investment_Token).toNumber(), + final_STOTokenSold.toString(), + init_STOTokenSold.add(investment_Token).toString(), "STO Token Sold not changed as expected" ); - assert.equal(final_STOETHBal.toNumber(), init_STOETHBal.toNumber(), "STO ETH Balance not changed as expected"); - assert.equal(final_STOPOLYBal.toNumber(), init_STOPOLYBal.toNumber(), "STO POLY Balance not changed as expected"); - assert.equal(final_RaisedETH.toNumber(), init_RaisedETH.add(investment_ETH).toNumber(), "Raised ETH not changed as expected"); - assert.equal(final_RaisedPOLY.toNumber(), init_RaisedPOLY.toNumber(), "Raised POLY not changed as expected"); + assert.equal(final_STOETHBal.toString(), init_STOETHBal.toString(), "STO ETH Balance not changed as expected"); + assert.equal(final_STOPOLYBal.toString(), init_STOPOLYBal.toString(), "STO POLY Balance not changed as expected"); + assert.equal(final_RaisedETH.toString(), init_RaisedETH.add(investment_ETH).toString(), "Raised ETH not changed as expected"); + assert.equal(final_RaisedPOLY.toString(), init_RaisedPOLY.toString(), "Raised POLY not changed as expected"); assert.equal( - final_WalletETHBal.toNumber(), - init_WalletETHBal.add(investment_ETH).toNumber(), + final_WalletETHBal.toString(), + init_WalletETHBal.add(investment_ETH).toString(), "Wallet ETH Balance not changed as expected" ); - assert.equal(final_WalletPOLYBal.toNumber(), init_WalletPOLYBal.toNumber(), "Wallet POLY Balance not changed as expected"); + assert.equal(final_WalletPOLYBal.toString(), init_WalletPOLYBal.toString(), "Wallet POLY Balance not changed as expected"); }); it("should successfully buy using buyWithPOLY at tier 0 for NONACCREDITED1", async () => { let stoId = 2; let tierId = 0; - let investment_Token = new BigNumber(5).mul(10 ** 18); + let investment_Token = new BN(5).mul(e18); let investment_USD = await convert(stoId, tierId, true, "TOKEN", "USD", investment_Token); let investment_ETH = await convert(stoId, tierId, true, "TOKEN", "ETH", investment_Token); let investment_POLY = await convert(stoId, tierId, true, "TOKEN", "POLY", investment_Token); @@ -3397,14 +3390,14 @@ contract("USDTieredSTO", accounts => { let init_TokenSupply = await I_SecurityToken.totalSupply(); let init_InvestorTokenBal = await I_SecurityToken.balanceOf(NONACCREDITED1); - let init_InvestorETHBal = new BigNumber(await web3.eth.getBalance(NONACCREDITED1)); + let init_InvestorETHBal = new BN(await web3.eth.getBalance(NONACCREDITED1)); let init_InvestorPOLYBal = await I_PolyToken.balanceOf(NONACCREDITED1); let init_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); - let init_STOETHBal = new BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let init_STOETHBal = new BN(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); let init_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); let init_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); let init_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); - let init_WalletETHBal = new BigNumber(await web3.eth.getBalance(WALLET)); + let init_WalletETHBal = new BN(await web3.eth.getBalance(WALLET)); let init_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET); // Buy With POLY @@ -3412,58 +3405,58 @@ contract("USDTieredSTO", accounts => { from: NONACCREDITED1, gasPrice: GAS_PRICE }); - let gasCost2 = new BigNumber(GAS_PRICE).mul(tx2.receipt.gasUsed); - console.log(" Gas buyWithPOLY: ".grey + tx2.receipt.gasUsed.toString().grey); + let gasCost2 = new BN(GAS_PRICE).mul(new BN(tx2.receipt.gasUsed)); + console.log(" Gas buyWithPOLY: ".grey + new BN(tx2.receipt.gasUsed).toString().grey); let final_TokenSupply = await I_SecurityToken.totalSupply(); let final_InvestorTokenBal = await I_SecurityToken.balanceOf(NONACCREDITED1); - let final_InvestorETHBal = new BigNumber(await web3.eth.getBalance(NONACCREDITED1)); + let final_InvestorETHBal = new BN(await web3.eth.getBalance(NONACCREDITED1)); let final_InvestorPOLYBal = await I_PolyToken.balanceOf(NONACCREDITED1); let final_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); - let final_STOETHBal = new BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let final_STOETHBal = new BN(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); let final_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); let final_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); let final_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); - let final_WalletETHBal = new BigNumber(await web3.eth.getBalance(WALLET)); + let final_WalletETHBal = new BN(await web3.eth.getBalance(WALLET)); let final_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET); assert.equal( - final_TokenSupply.toNumber(), - init_TokenSupply.add(investment_Token).toNumber(), + final_TokenSupply.toString(), + init_TokenSupply.add(investment_Token).toString(), "Token Supply not changed as expected" ); assert.equal( - final_InvestorTokenBal.toNumber(), - init_InvestorTokenBal.add(investment_Token).toNumber(), + final_InvestorTokenBal.toString(), + init_InvestorTokenBal.add(investment_Token).toString(), "Investor Token Balance not changed as expected" ); assert.equal( - final_InvestorETHBal.toNumber(), - init_InvestorETHBal.sub(gasCost2).toNumber(), + final_InvestorETHBal.toString(), + init_InvestorETHBal.sub(gasCost2).toString(), "Investor ETH Balance not changed as expected" ); assert.equal( - final_InvestorPOLYBal.toNumber(), - init_InvestorPOLYBal.sub(investment_POLY).toNumber(), + final_InvestorPOLYBal.toString(), + init_InvestorPOLYBal.sub(investment_POLY).toString(), "Investor POLY Balance not changed as expected" ); assert.equal( - final_STOTokenSold.toNumber(), - init_STOTokenSold.add(investment_Token).toNumber(), + final_STOTokenSold.toString(), + init_STOTokenSold.add(investment_Token).toString(), "STO Token Sold not changed as expected" ); - assert.equal(final_STOETHBal.toNumber(), init_STOETHBal.toNumber(), "STO ETH Balance not changed as expected"); - assert.equal(final_STOPOLYBal.toNumber(), init_STOPOLYBal.toNumber(), "STO POLY Balance not changed as expected"); - assert.equal(final_RaisedETH.toNumber(), init_RaisedETH.toNumber(), "Raised ETH not changed as expected"); + assert.equal(final_STOETHBal.toString(), init_STOETHBal.toString(), "STO ETH Balance not changed as expected"); + assert.equal(final_STOPOLYBal.toString(), init_STOPOLYBal.toString(), "STO POLY Balance not changed as expected"); + assert.equal(final_RaisedETH.toString(), init_RaisedETH.toString(), "Raised ETH not changed as expected"); assert.equal( - final_RaisedPOLY.toNumber(), - init_RaisedPOLY.add(investment_POLY).toNumber(), + final_RaisedPOLY.toString(), + init_RaisedPOLY.add(investment_POLY).toString(), "Raised POLY not changed as expected" ); - assert.equal(final_WalletETHBal.toNumber(), init_WalletETHBal.toNumber(), "Wallet ETH Balance not changed as expected"); + assert.equal(final_WalletETHBal.toString(), init_WalletETHBal.toString(), "Wallet ETH Balance not changed as expected"); assert.equal( - final_WalletPOLYBal.toNumber(), - init_WalletPOLYBal.add(investment_POLY).toNumber(), + final_WalletPOLYBal.toString(), + init_WalletPOLYBal.add(investment_POLY).toString(), "Wallet POLY Balance not changed as expected" ); }); @@ -3472,23 +3465,21 @@ contract("USDTieredSTO", accounts => { let stoId = 2; let tierId = 0; - await I_USDTieredSTO_Array[stoId].changeAccredited([ACCREDITED1], [true], { from: ISSUER }); - - let investment_Token = new BigNumber(5).mul(10 ** 18); + let investment_Token = new BN(5).mul(e18); let investment_USD = await convert(stoId, tierId, false, "TOKEN", "USD", investment_Token); let investment_ETH = await convert(stoId, tierId, false, "TOKEN", "ETH", investment_Token); let investment_POLY = await convert(stoId, tierId, false, "TOKEN", "POLY", investment_Token); let init_TokenSupply = await I_SecurityToken.totalSupply(); let init_InvestorTokenBal = await I_SecurityToken.balanceOf(ACCREDITED1); - let init_InvestorETHBal = new BigNumber(await web3.eth.getBalance(ACCREDITED1)); + let init_InvestorETHBal = new BN(await web3.eth.getBalance(ACCREDITED1)); let init_InvestorPOLYBal = await I_PolyToken.balanceOf(ACCREDITED1); let init_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); - let init_STOETHBal = new BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let init_STOETHBal = new BN(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); let init_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); let init_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); let init_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); - let init_WalletETHBal = new BigNumber(await web3.eth.getBalance(WALLET)); + let init_WalletETHBal = new BN(await web3.eth.getBalance(WALLET)); let init_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET); let tx1 = await web3.eth.sendTransaction({ @@ -3496,82 +3487,82 @@ contract("USDTieredSTO", accounts => { to: I_USDTieredSTO_Array[stoId].address, value: investment_ETH, gasPrice: GAS_PRICE, - gas: 1000000 + gas: 7000000 }); - let gasCost1 = new BigNumber(GAS_PRICE).mul(tx1.gasUsed); - console.log(" Gas fallback purchase: ".grey + tx1.gasUsed.toString().grey); + let gasCost1 = new BN(GAS_PRICE).mul(new BN(tx1.gasUsed)); + console.log(" Gas fallback purchase: ".grey + new BN(tx1.gasUsed).toString().grey); let final_TokenSupply = await I_SecurityToken.totalSupply(); let final_InvestorTokenBal = await I_SecurityToken.balanceOf(ACCREDITED1); - let final_InvestorETHBal = new BigNumber(await web3.eth.getBalance(ACCREDITED1)); + let final_InvestorETHBal = new BN(await web3.eth.getBalance(ACCREDITED1)); let final_InvestorPOLYBal = await I_PolyToken.balanceOf(ACCREDITED1); let final_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); - let final_STOETHBal = new BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let final_STOETHBal = new BN(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); let final_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); let final_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); let final_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); - let final_WalletETHBal = new BigNumber(await web3.eth.getBalance(WALLET)); + let final_WalletETHBal = new BN(await web3.eth.getBalance(WALLET)); let final_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET); assert.equal( - final_TokenSupply.toNumber(), - init_TokenSupply.add(investment_Token).toNumber(), + final_TokenSupply.toString(), + init_TokenSupply.add(investment_Token).toString(), "Token Supply not changed as expected" ); assert.equal( - final_InvestorTokenBal.toNumber(), - init_InvestorTokenBal.add(investment_Token).toNumber(), + final_InvestorTokenBal.toString(), + init_InvestorTokenBal.add(investment_Token).toString(), "Investor Token Balance not changed as expected" ); assert.equal( - final_InvestorETHBal.toNumber(), + final_InvestorETHBal.toString(), init_InvestorETHBal .sub(gasCost1) .sub(investment_ETH) - .toNumber(), + .toString(), "Investor ETH Balance not changed as expected" ); assert.equal( - final_InvestorPOLYBal.toNumber(), - init_InvestorPOLYBal.toNumber(), + final_InvestorPOLYBal.toString(), + init_InvestorPOLYBal.toString(), "Investor POLY Balance not changed as expected" ); assert.equal( - final_STOTokenSold.toNumber(), - init_STOTokenSold.add(investment_Token).toNumber(), + final_STOTokenSold.toString(), + init_STOTokenSold.add(investment_Token).toString(), "STO Token Sold not changed as expected" ); - assert.equal(final_STOETHBal.toNumber(), init_STOETHBal.toNumber(), "STO ETH Balance not changed as expected"); - assert.equal(final_STOPOLYBal.toNumber(), init_STOPOLYBal.toNumber(), "STO POLY Balance not changed as expected"); - assert.equal(final_RaisedETH.toNumber(), init_RaisedETH.add(investment_ETH).toNumber(), "Raised ETH not changed as expected"); - assert.equal(final_RaisedPOLY.toNumber(), init_RaisedPOLY.toNumber(), "Raised POLY not changed as expected"); + assert.equal(final_STOETHBal.toString(), init_STOETHBal.toString(), "STO ETH Balance not changed as expected"); + assert.equal(final_STOPOLYBal.toString(), init_STOPOLYBal.toString(), "STO POLY Balance not changed as expected"); + assert.equal(final_RaisedETH.toString(), init_RaisedETH.add(investment_ETH).toString(), "Raised ETH not changed as expected"); + assert.equal(final_RaisedPOLY.toString(), init_RaisedPOLY.toString(), "Raised POLY not changed as expected"); assert.equal( - final_WalletETHBal.toNumber(), - init_WalletETHBal.add(investment_ETH).toNumber(), + final_WalletETHBal.toString(), + init_WalletETHBal.add(investment_ETH).toString(), "Wallet ETH Balance not changed as expected" ); - assert.equal(final_WalletPOLYBal.toNumber(), init_WalletPOLYBal.toNumber(), "Wallet POLY Balance not changed as expected"); + assert.equal(final_WalletPOLYBal.toString(), init_WalletPOLYBal.toString(), "Wallet POLY Balance not changed as expected"); }); it("should successfully buy using buyWithETH at tier 0 for ACCREDITED1", async () => { let stoId = 2; let tierId = 0; - let investment_Token = new BigNumber(5).mul(10 ** 18); + let investment_Token = new BN(5).mul(e18); let investment_USD = await convert(stoId, tierId, false, "TOKEN", "USD", investment_Token); let investment_ETH = await convert(stoId, tierId, false, "TOKEN", "ETH", investment_Token); let investment_POLY = await convert(stoId, tierId, false, "TOKEN", "POLY", investment_Token); let init_TokenSupply = await I_SecurityToken.totalSupply(); let init_InvestorTokenBal = await I_SecurityToken.balanceOf(ACCREDITED1); - let init_InvestorETHBal = new BigNumber(await web3.eth.getBalance(ACCREDITED1)); + let init_InvestorETHBal = new BN(await web3.eth.getBalance(ACCREDITED1)); let init_InvestorPOLYBal = await I_PolyToken.balanceOf(ACCREDITED1); let init_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); - let init_STOETHBal = new BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let init_STOETHBal = new BN(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); let init_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); let init_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); let init_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); - let init_WalletETHBal = new BigNumber(await web3.eth.getBalance(WALLET)); + let init_WalletETHBal = new BN(await web3.eth.getBalance(WALLET)); let init_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET); let tx1 = await I_USDTieredSTO_Array[stoId].buyWithETH(ACCREDITED1, { @@ -3579,66 +3570,66 @@ contract("USDTieredSTO", accounts => { value: investment_ETH, gasPrice: GAS_PRICE }); - let gasCost1 = new BigNumber(GAS_PRICE).mul(tx1.receipt.gasUsed); - console.log(" Gas buyWithETH: ".grey + tx1.receipt.gasUsed.toString().grey); + let gasCost1 = new BN(GAS_PRICE).mul(new BN(tx1.receipt.gasUsed)); + console.log(" Gas buyWithETH: ".grey + new BN(tx1.receipt.gasUsed).toString().grey); let final_TokenSupply = await I_SecurityToken.totalSupply(); let final_InvestorTokenBal = await I_SecurityToken.balanceOf(ACCREDITED1); - let final_InvestorETHBal = new BigNumber(await web3.eth.getBalance(ACCREDITED1)); + let final_InvestorETHBal = new BN(await web3.eth.getBalance(ACCREDITED1)); let final_InvestorPOLYBal = await I_PolyToken.balanceOf(ACCREDITED1); let final_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); - let final_STOETHBal = new BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let final_STOETHBal = new BN(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); let final_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); let final_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); let final_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); - let final_WalletETHBal = new BigNumber(await web3.eth.getBalance(WALLET)); + let final_WalletETHBal = new BN(await web3.eth.getBalance(WALLET)); let final_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET); assert.equal( - final_TokenSupply.toNumber(), - init_TokenSupply.add(investment_Token).toNumber(), + final_TokenSupply.toString(), + init_TokenSupply.add(investment_Token).toString(), "Token Supply not changed as expected" ); assert.equal( - final_InvestorTokenBal.toNumber(), - init_InvestorTokenBal.add(investment_Token).toNumber(), + final_InvestorTokenBal.toString(), + init_InvestorTokenBal.add(investment_Token).toString(), "Investor Token Balance not changed as expected" ); assert.equal( - final_InvestorETHBal.toNumber(), + final_InvestorETHBal.toString(), init_InvestorETHBal .sub(gasCost1) .sub(investment_ETH) - .toNumber(), + .toString(), "Investor ETH Balance not changed as expected" ); assert.equal( - final_InvestorPOLYBal.toNumber(), - init_InvestorPOLYBal.toNumber(), + final_InvestorPOLYBal.toString(), + init_InvestorPOLYBal.toString(), "Investor POLY Balance not changed as expected" ); assert.equal( - final_STOTokenSold.toNumber(), - init_STOTokenSold.add(investment_Token).toNumber(), + final_STOTokenSold.toString(), + init_STOTokenSold.add(investment_Token).toString(), "STO Token Sold not changed as expected" ); - assert.equal(final_STOETHBal.toNumber(), init_STOETHBal.toNumber(), "STO ETH Balance not changed as expected"); - assert.equal(final_STOPOLYBal.toNumber(), init_STOPOLYBal.toNumber(), "STO POLY Balance not changed as expected"); - assert.equal(final_RaisedETH.toNumber(), init_RaisedETH.add(investment_ETH).toNumber(), "Raised ETH not changed as expected"); - assert.equal(final_RaisedPOLY.toNumber(), init_RaisedPOLY.toNumber(), "Raised POLY not changed as expected"); + assert.equal(final_STOETHBal.toString(), init_STOETHBal.toString(), "STO ETH Balance not changed as expected"); + assert.equal(final_STOPOLYBal.toString(), init_STOPOLYBal.toString(), "STO POLY Balance not changed as expected"); + assert.equal(final_RaisedETH.toString(), init_RaisedETH.add(investment_ETH).toString(), "Raised ETH not changed as expected"); + assert.equal(final_RaisedPOLY.toString(), init_RaisedPOLY.toString(), "Raised POLY not changed as expected"); assert.equal( - final_WalletETHBal.toNumber(), - init_WalletETHBal.add(investment_ETH).toNumber(), + final_WalletETHBal.toString(), + init_WalletETHBal.add(investment_ETH).toString(), "Wallet ETH Balance not changed as expected" ); - assert.equal(final_WalletPOLYBal.toNumber(), init_WalletPOLYBal.toNumber(), "Wallet POLY Balance not changed as expected"); + assert.equal(final_WalletPOLYBal.toString(), init_WalletPOLYBal.toString(), "Wallet POLY Balance not changed as expected"); }); it("should successfully buy using buyWithPOLY at tier 0 for ACCREDITED1", async () => { let stoId = 2; let tierId = 0; - let investment_Token = new BigNumber(5).mul(10 ** 18); + let investment_Token = new BN(5).mul(e18); let investment_USD = await convert(stoId, tierId, true, "TOKEN", "USD", investment_Token); let investment_ETH = await convert(stoId, tierId, true, "TOKEN", "ETH", investment_Token); let investment_POLY = await convert(stoId, tierId, true, "TOKEN", "POLY", investment_Token); @@ -3657,14 +3648,14 @@ contract("USDTieredSTO", accounts => { let init_TokenSupply = await I_SecurityToken.totalSupply(); let init_InvestorTokenBal = await I_SecurityToken.balanceOf(ACCREDITED1); - let init_InvestorETHBal = new BigNumber(await web3.eth.getBalance(ACCREDITED1)); + let init_InvestorETHBal = new BN(await web3.eth.getBalance(ACCREDITED1)); let init_InvestorPOLYBal = await I_PolyToken.balanceOf(ACCREDITED1); let init_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); - let init_STOETHBal = new BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let init_STOETHBal = new BN(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); let init_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); let init_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); let init_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); - let init_WalletETHBal = new BigNumber(await web3.eth.getBalance(WALLET)); + let init_WalletETHBal = new BN(await web3.eth.getBalance(WALLET)); let init_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET); // Buy With POLY @@ -3672,96 +3663,96 @@ contract("USDTieredSTO", accounts => { from: ACCREDITED1, gasPrice: GAS_PRICE }); - let gasCost2 = new BigNumber(GAS_PRICE).mul(tx2.receipt.gasUsed); - console.log(" Gas buyWithPOLY: ".grey + tx2.receipt.gasUsed.toString().grey); + let gasCost2 = new BN(GAS_PRICE).mul(new BN(tx2.receipt.gasUsed)); + console.log(" Gas buyWithPOLY: ".grey + new BN(tx2.receipt.gasUsed).toString().grey); let final_TokenSupply = await I_SecurityToken.totalSupply(); let final_InvestorTokenBal = await I_SecurityToken.balanceOf(ACCREDITED1); - let final_InvestorETHBal = new BigNumber(await web3.eth.getBalance(ACCREDITED1)); + let final_InvestorETHBal = new BN(await web3.eth.getBalance(ACCREDITED1)); let final_InvestorPOLYBal = await I_PolyToken.balanceOf(ACCREDITED1); let final_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); - let final_STOETHBal = new BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let final_STOETHBal = new BN(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); let final_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); let final_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); let final_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); - let final_WalletETHBal = new BigNumber(await web3.eth.getBalance(WALLET)); + let final_WalletETHBal = new BN(await web3.eth.getBalance(WALLET)); let final_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET); assert.equal( - final_TokenSupply.toNumber(), - init_TokenSupply.add(investment_Token).toNumber(), + final_TokenSupply.toString(), + init_TokenSupply.add(investment_Token).toString(), "Token Supply not changed as expected" ); assert.equal( - final_InvestorTokenBal.toNumber(), - init_InvestorTokenBal.add(investment_Token).toNumber(), + final_InvestorTokenBal.toString(), + init_InvestorTokenBal.add(investment_Token).toString(), "Investor Token Balance not changed as expected" ); assert.equal( - final_InvestorETHBal.toNumber(), - init_InvestorETHBal.sub(gasCost2).toNumber(), + final_InvestorETHBal.toString(), + init_InvestorETHBal.sub(gasCost2).toString(), "Investor ETH Balance not changed as expected" ); assert.equal( - final_InvestorPOLYBal.toNumber(), - init_InvestorPOLYBal.sub(investment_POLY).toNumber(), + final_InvestorPOLYBal.toString(), + init_InvestorPOLYBal.sub(investment_POLY).toString(), "Investor POLY Balance not changed as expected" ); assert.equal( - final_STOTokenSold.toNumber(), - init_STOTokenSold.add(investment_Token).toNumber(), + final_STOTokenSold.toString(), + init_STOTokenSold.add(investment_Token).toString(), "STO Token Sold not changed as expected" ); - assert.equal(final_STOETHBal.toNumber(), init_STOETHBal.toNumber(), "STO ETH Balance not changed as expected"); - assert.equal(final_STOPOLYBal.toNumber(), init_STOPOLYBal.toNumber(), "STO POLY Balance not changed as expected"); - assert.equal(final_RaisedETH.toNumber(), init_RaisedETH.toNumber(), "Raised ETH not changed as expected"); + assert.equal(final_STOETHBal.toString(), init_STOETHBal.toString(), "STO ETH Balance not changed as expected"); + assert.equal(final_STOPOLYBal.toString(), init_STOPOLYBal.toString(), "STO POLY Balance not changed as expected"); + assert.equal(final_RaisedETH.toString(), init_RaisedETH.toString(), "Raised ETH not changed as expected"); assert.equal( - final_RaisedPOLY.toNumber(), - init_RaisedPOLY.add(investment_POLY).toNumber(), + final_RaisedPOLY.toString(), + init_RaisedPOLY.add(investment_POLY).toString(), "Raised POLY not changed as expected" ); - assert.equal(final_WalletETHBal.toNumber(), init_WalletETHBal.toNumber(), "Wallet ETH Balance not changed as expected"); + assert.equal(final_WalletETHBal.toString(), init_WalletETHBal.toString(), "Wallet ETH Balance not changed as expected"); assert.equal( - final_WalletPOLYBal.toNumber(), - init_WalletPOLYBal.add(investment_POLY).toNumber(), + final_WalletPOLYBal.toString(), + init_WalletPOLYBal.add(investment_POLY).toString(), "Wallet POLY Balance not changed as expected" ); // Additional checks on getters - assert.equal((await I_USDTieredSTO_Array[stoId].investorCount.call()).toNumber(), 2, "Investor count not changed as expected"); + assert.equal((await I_USDTieredSTO_Array[stoId].investorCount.call()).toString(), 2, "Investor count not changed as expected"); assert.equal( - (await I_USDTieredSTO_Array[stoId].getTokensSold()).toNumber(), - init_getTokensSold.add(investment_Token).toNumber(), + (await I_USDTieredSTO_Array[stoId].getTokensSold()).toString(), + init_getTokensSold.add(investment_Token).toString(), "getTokensSold not changed as expected" ); assert.equal( - (await I_USDTieredSTO_Array[stoId].getTokensMinted()).toNumber(), - init_getTokensMinted.add(investment_Token).toNumber(), + (await I_USDTieredSTO_Array[stoId].getTokensMinted()).toString(), + init_getTokensMinted.add(investment_Token).toString(), "getTokensMinted not changed as expected" ); assert.equal( - (await I_USDTieredSTO_Array[stoId].getTokensSoldFor(ETH)).toNumber(), - init_getTokensSoldForETH.toNumber(), + (await I_USDTieredSTO_Array[stoId].getTokensSoldFor(ETH)).toString(), + init_getTokensSoldForETH.toString(), "getTokensSoldForETH not changed as expected" ); assert.equal( - (await I_USDTieredSTO_Array[stoId].getTokensSoldFor(POLY)).toNumber(), - init_getTokensSoldForPOLY.add(investment_Token).toNumber(), + (await I_USDTieredSTO_Array[stoId].getTokensSoldFor(POLY)).toString(), + init_getTokensSoldForPOLY.add(investment_Token).toString(), "getTokensSoldForPOLY not changed as expected" ); assert.equal( - (await I_USDTieredSTO_Array[stoId].investorInvestedUSD.call(ACCREDITED1)).toNumber(), - init_investorInvestedUSD.add(investment_USD).toNumber(), + (await I_USDTieredSTO_Array[stoId].investorInvestedUSD.call(ACCREDITED1)).toString(), + init_investorInvestedUSD.add(investment_USD).toString(), "investorInvestedUSD not changed as expected" ); assert.equal( - (await I_USDTieredSTO_Array[stoId].investorInvested.call(ACCREDITED1, ETH)).toNumber(), - init_investorInvestedETH.toNumber(), + (await I_USDTieredSTO_Array[stoId].investorInvested.call(ACCREDITED1, ETH)).toString(), + init_investorInvestedETH.toString(), "investorInvestedETH not changed as expected" ); assert.equal( - (await I_USDTieredSTO_Array[stoId].investorInvested.call(ACCREDITED1, POLY)).toNumber(), - init_investorInvestedPOLY.add(investment_POLY).toNumber(), + (await I_USDTieredSTO_Array[stoId].investorInvested.call(ACCREDITED1, POLY)).toString(), + init_investorInvestedPOLY.add(investment_POLY).toString(), "investorInvestedPOLY not changed as expected" ); }); @@ -3785,14 +3776,14 @@ contract("USDTieredSTO", accounts => { let init_TokenSupply = await I_SecurityToken.totalSupply(); let init_InvestorTokenBal = await I_SecurityToken.balanceOf(NONACCREDITED1); - let init_InvestorETHBal = new BigNumber(await web3.eth.getBalance(NONACCREDITED1)); + let init_InvestorETHBal = new BN(await web3.eth.getBalance(NONACCREDITED1)); let init_InvestorPOLYBal = await I_PolyToken.balanceOf(NONACCREDITED1); let init_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); - let init_STOETHBal = new BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let init_STOETHBal = new BN(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); let init_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); let init_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); let init_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); - let init_WalletETHBal = new BigNumber(await web3.eth.getBalance(WALLET)); + let init_WalletETHBal = new BN(await web3.eth.getBalance(WALLET)); let init_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET); // Buy With POLY @@ -3800,89 +3791,88 @@ contract("USDTieredSTO", accounts => { from: NONACCREDITED1, gasPrice: GAS_PRICE }); - let gasCost2 = new BigNumber(GAS_PRICE).mul(tx2.receipt.gasUsed); - console.log(" Gas buyWithPOLY: ".grey + tx2.receipt.gasUsed.toString().grey); + let gasCost2 = new BN(GAS_PRICE).mul(new BN(tx2.receipt.gasUsed)); + console.log(" Gas buyWithPOLY: ".grey + new BN(tx2.receipt.gasUsed).toString().grey); let final_TokenSupply = await I_SecurityToken.totalSupply(); let final_InvestorTokenBal = await I_SecurityToken.balanceOf(NONACCREDITED1); - let final_InvestorETHBal = new BigNumber(await web3.eth.getBalance(NONACCREDITED1)); + let final_InvestorETHBal = new BN(await web3.eth.getBalance(NONACCREDITED1)); let final_InvestorPOLYBal = await I_PolyToken.balanceOf(NONACCREDITED1); let final_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); - let final_STOETHBal = new BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let final_STOETHBal = new BN(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); let final_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); let final_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); let final_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); - let final_WalletETHBal = new BigNumber(await web3.eth.getBalance(WALLET)); + let final_WalletETHBal = new BN(await web3.eth.getBalance(WALLET)); let final_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET); assert.equal( - final_TokenSupply.toNumber(), + final_TokenSupply.toString(), init_TokenSupply .add(investment_Token) .sub(refund_Token) - .toNumber(), + .toString(), "Token Supply not changed as expected" ); assert.equal( - final_InvestorTokenBal.toNumber(), + final_InvestorTokenBal.toString(), init_InvestorTokenBal .add(investment_Token) .sub(refund_Token) - .toNumber(), + .toString(), "Investor Token Balance not changed as expected" ); assert.equal( - final_InvestorETHBal.toNumber(), - init_InvestorETHBal.sub(gasCost2).toNumber(), + final_InvestorETHBal.toString(), + init_InvestorETHBal.sub(gasCost2).toString(), "Investor ETH Balance not changed as expected" ); assert.equal( - final_InvestorPOLYBal.toNumber(), + final_InvestorPOLYBal.toString(), init_InvestorPOLYBal .sub(investment_POLY) .add(refund_POLY) - .toNumber(), + .toString(), "Investor POLY Balance not changed as expected" ); assert.equal( - final_STOTokenSold.toNumber(), + final_STOTokenSold.toString(), init_STOTokenSold .add(investment_Token) .sub(refund_Token) - .toNumber(), + .toString(), "STO Token Sold not changed as expected" ); - assert.equal(final_STOETHBal.toNumber(), init_STOETHBal.toNumber(), "STO ETH Balance not changed as expected"); - assert.equal(final_STOPOLYBal.toNumber(), init_STOPOLYBal.toNumber(), "STO POLY Balance not changed as expected"); - assert.equal(final_RaisedETH.toNumber(), init_RaisedETH.toNumber(), "Raised ETH not changed as expected"); + assert.equal(final_STOETHBal.toString(), init_STOETHBal.toString(), "STO ETH Balance not changed as expected"); + assert.equal(final_STOPOLYBal.toString(), init_STOPOLYBal.toString(), "STO POLY Balance not changed as expected"); + assert.equal(final_RaisedETH.toString(), init_RaisedETH.toString(), "Raised ETH not changed as expected"); assert.equal( - final_RaisedPOLY.toNumber(), + final_RaisedPOLY.toString(), init_RaisedPOLY .add(investment_POLY) .sub(refund_POLY) - .toNumber(), + .toString(), "Raised POLY not changed as expected" ); - assert.equal(final_WalletETHBal.toNumber(), init_WalletETHBal.toNumber(), "Wallet ETH Balance not changed as expected"); + assert.equal(final_WalletETHBal.toString(), init_WalletETHBal.toString(), "Wallet ETH Balance not changed as expected"); assert.equal( - final_WalletPOLYBal.toNumber(), + final_WalletPOLYBal.toString(), init_WalletPOLYBal .add(investment_POLY) .sub(refund_POLY) - .toNumber(), + .toString(), "Wallet POLY Balance not changed as expected" ); }); it("should successfully buy a granular amount and refund balance when buying indivisible token with POLY", async () => { - await I_SecurityToken.changeGranularity(10 ** 18, {from: ISSUER}); + await I_SecurityToken.changeGranularity(e18, { from: ISSUER }); let stoId = 4; let tierId = 0; - await I_USDTieredSTO_Array[stoId].changeAccredited([ACCREDITED1], [true], { from: ISSUER }); - let investment_Tokens = (new BigNumber(10.5)).mul(10 ** 18); + let investment_Tokens = new BN(1050).mul(e16); let investment_POLY = await convert(stoId, tierId, true, "TOKEN", "POLY", investment_Tokens); - let refund_Tokens = (new BigNumber(0.5)).mul(10 ** 18); + let refund_Tokens = new BN(50).mul(e16); let refund_POLY = await convert(stoId, tierId, true, "TOKEN", "POLY", refund_Tokens); await I_PolyToken.getTokens(investment_POLY, ACCREDITED1); @@ -3890,297 +3880,295 @@ contract("USDTieredSTO", accounts => { let init_TokenSupply = await I_SecurityToken.totalSupply(); let init_InvestorTokenBal = await I_SecurityToken.balanceOf(ACCREDITED1); - let init_InvestorETHBal = BigNumber(await web3.eth.getBalance(ACCREDITED1)); + let init_InvestorETHBal = new BN(await web3.eth.getBalance(ACCREDITED1)); let init_InvestorPOLYBal = await I_PolyToken.balanceOf(ACCREDITED1); let init_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); - let init_STOETHBal = BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let init_STOETHBal = new BN(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); let init_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); let init_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); let init_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); - let init_WalletETHBal = BigNumber(await web3.eth.getBalance(WALLET)); + let init_WalletETHBal = new BN(await web3.eth.getBalance(WALLET)); let init_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET); - let tokensToMint = (await I_USDTieredSTO_Array[stoId].buyTokensView(ACCREDITED1, investment_POLY,POLY))[2]; + let tokensToMint = (await I_USDTieredSTO_Array[stoId].buyWithPOLY.call(ACCREDITED1, investment_POLY, {from: ACCREDITED1}))[2]; // Buy With POLY let tx2 = await I_USDTieredSTO_Array[stoId].buyWithPOLY(ACCREDITED1, investment_POLY, { from: ACCREDITED1, gasPrice: GAS_PRICE }); - let gasCost2 = BigNumber(GAS_PRICE).mul(tx2.receipt.gasUsed); - console.log(" Gas buyWithPOLY: ".grey + tx2.receipt.gasUsed.toString().grey); + let gasCost2 = new BN(GAS_PRICE).mul(new BN(tx2.receipt.gasUsed)); + console.log(" Gas buyWithPOLY: ".grey + new BN(tx2.receipt.gasUsed).toString().grey); let final_TokenSupply = await I_SecurityToken.totalSupply(); let final_InvestorTokenBal = await I_SecurityToken.balanceOf(ACCREDITED1); - let final_InvestorETHBal = BigNumber(await web3.eth.getBalance(ACCREDITED1)); + let final_InvestorETHBal = new BN(await web3.eth.getBalance(ACCREDITED1)); let final_InvestorPOLYBal = await I_PolyToken.balanceOf(ACCREDITED1); let final_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); - let final_STOETHBal = BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let final_STOETHBal = new BN(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); let final_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); let final_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); let final_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); - let final_WalletETHBal = BigNumber(await web3.eth.getBalance(WALLET)); + let final_WalletETHBal = new BN(await web3.eth.getBalance(WALLET)); let final_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET); assert.equal( - final_TokenSupply.toNumber(), + final_TokenSupply.toString(), init_TokenSupply .add(investment_Tokens) .sub(refund_Tokens) - .toNumber(), + .toString(), "Token Supply not changed as expected" ); + assert.equal(tokensToMint.toString(), investment_Tokens.sub(refund_Tokens).toString(), "View function returned incorrect data"); assert.equal( - tokensToMint.toNumber(), - investment_Tokens.sub(refund_Tokens).toNumber(), - "View function returned incorrect data" - ); - assert.equal( - final_InvestorTokenBal.toNumber(), + final_InvestorTokenBal.toString(), init_InvestorTokenBal .add(investment_Tokens) .sub(refund_Tokens) - .toNumber(), + .toString(), "Investor Token Balance not changed as expected" ); assert.equal( - final_InvestorETHBal.toNumber(), - init_InvestorETHBal.sub(gasCost2).toNumber(), + final_InvestorETHBal.toString(), + init_InvestorETHBal.sub(gasCost2).toString(), "Investor ETH Balance not changed as expected" ); assert.equal( - final_InvestorPOLYBal.toNumber(), + final_InvestorPOLYBal.toString(), init_InvestorPOLYBal .sub(investment_POLY) .add(refund_POLY) - .toNumber(), + .toString(), "Investor POLY Balance not changed as expected" ); assert.equal( - final_STOTokenSold.toNumber(), + final_STOTokenSold.toString(), init_STOTokenSold .add(investment_Tokens) .sub(refund_Tokens) - .toNumber(), + .toString(), "STO Token Sold not changed as expected" ); - assert.equal(final_STOETHBal.toNumber(), init_STOETHBal.toNumber(), "STO ETH Balance not changed as expected"); - assert.equal(final_STOPOLYBal.toNumber(), init_STOPOLYBal.toNumber(), "STO POLY Balance not changed as expected"); - assert.equal(final_RaisedETH.toNumber(), init_RaisedETH.toNumber(), "Raised ETH not changed as expected"); + assert.equal(final_STOETHBal.toString(), init_STOETHBal.toString(), "STO ETH Balance not changed as expected"); + assert.equal(final_STOPOLYBal.toString(), init_STOPOLYBal.toString(), "STO POLY Balance not changed as expected"); + assert.equal(final_RaisedETH.toString(), init_RaisedETH.toString(), "Raised ETH not changed as expected"); assert.equal( - final_RaisedPOLY.toNumber(), + final_RaisedPOLY.toString(), init_RaisedPOLY .add(investment_POLY) .sub(refund_POLY) - .toNumber(), + .toString(), "Raised POLY not changed as expected" ); - assert.equal(final_WalletETHBal.toNumber(), init_WalletETHBal.toNumber(), "Wallet ETH Balance not changed as expected"); + assert.equal(final_WalletETHBal.toString(), init_WalletETHBal.toString(), "Wallet ETH Balance not changed as expected"); assert.equal( - final_WalletPOLYBal.toNumber(), + final_WalletPOLYBal.toString(), init_WalletPOLYBal .add(investment_POLY) .sub(refund_POLY) - .toNumber(), + .toString(), "Wallet POLY Balance not changed as expected" ); - await I_SecurityToken.changeGranularity(1, {from: ISSUER}); + await I_SecurityToken.changeGranularity(1, { from: ISSUER }); }); - it("should successfully buy a granular amount when buying indivisible token accross tiers with invalid tier limit", async () => { + it("should successfully buy a granular amount when buying indivisible token with illegal tier limits", async () => { + await I_SecurityToken.changeGranularity(e18, { from: ISSUER }); let stoId = 5; let tierId = 0; - await I_USDTieredSTO_Array[stoId].changeAccredited([ACCREDITED1], [true], { from: ISSUER }); - let investment_Tokens = (new BigNumber(110)).mul(10 ** 18); + let investment_Tokens = new BN(110).mul(e18); let investment_POLY = await convert(stoId, tierId, false, "TOKEN", "POLY", investment_Tokens); - let refund_Tokens = new BigNumber(0); - let refund_POLY = await convert(stoId, tierId, true, "TOKEN", "POLY", refund_Tokens); + let refund_Tokens = new BN(0); + let refund_POLY = await convert(stoId, tierId, false, "TOKEN", "POLY", refund_Tokens); await I_PolyToken.getTokens(investment_POLY, ACCREDITED1); await I_PolyToken.approve(I_USDTieredSTO_Array[stoId].address, investment_POLY, { from: ACCREDITED1 }); let init_TokenSupply = await I_SecurityToken.totalSupply(); let init_InvestorTokenBal = await I_SecurityToken.balanceOf(ACCREDITED1); - let init_InvestorETHBal = BigNumber(await web3.eth.getBalance(ACCREDITED1)); + let init_InvestorETHBal = new BN(await web3.eth.getBalance(ACCREDITED1)); let init_InvestorPOLYBal = await I_PolyToken.balanceOf(ACCREDITED1); let init_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); - let init_STOETHBal = BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let init_STOETHBal = new BN(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); let init_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); let init_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); let init_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); - let init_WalletETHBal = BigNumber(await web3.eth.getBalance(WALLET)); + let init_WalletETHBal = new BN(await web3.eth.getBalance(WALLET)); let init_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET); - let tokensToMint = (await I_USDTieredSTO_Array[stoId].buyTokensView(ACCREDITED1, investment_POLY,POLY))[2]; + let tokensToMint = (await I_USDTieredSTO_Array[stoId].buyWithPOLY.call(ACCREDITED1, investment_POLY, {from: ACCREDITED1}))[2]; // Buy With POLY let tx2 = await I_USDTieredSTO_Array[stoId].buyWithPOLY(ACCREDITED1, investment_POLY, { from: ACCREDITED1, gasPrice: GAS_PRICE }); - let gasCost2 = BigNumber(GAS_PRICE).mul(tx2.receipt.gasUsed); - console.log(" Gas buyWithPOLY: ".grey + tx2.receipt.gasUsed.toString().grey); + let gasCost2 = new BN(GAS_PRICE).mul(new BN(tx2.receipt.gasUsed)); + console.log(" Gas buyWithPOLY: ".grey + new BN(tx2.receipt.gasUsed).toString().grey); let final_TokenSupply = await I_SecurityToken.totalSupply(); let final_InvestorTokenBal = await I_SecurityToken.balanceOf(ACCREDITED1); - let final_InvestorETHBal = BigNumber(await web3.eth.getBalance(ACCREDITED1)); + let final_InvestorETHBal = new BN(await web3.eth.getBalance(ACCREDITED1)); let final_InvestorPOLYBal = await I_PolyToken.balanceOf(ACCREDITED1); let final_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); - let final_STOETHBal = BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let final_STOETHBal = new BN(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); let final_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); let final_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); let final_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); - let final_WalletETHBal = BigNumber(await web3.eth.getBalance(WALLET)); + let final_WalletETHBal = new BN(await web3.eth.getBalance(WALLET)); let final_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET); assert.equal( - final_TokenSupply.toNumber(), + final_TokenSupply.toString(), init_TokenSupply .add(investment_Tokens) .sub(refund_Tokens) - .toNumber(), + .toString(), "Token Supply not changed as expected" ); + assert.equal(tokensToMint.toString(), investment_Tokens.sub(refund_Tokens).toString(), "View function returned incorrect data"); assert.equal( - tokensToMint.toNumber(), - investment_Tokens.sub(refund_Tokens).toNumber(), - "View function returned incorrect data" - ); - assert.equal( - final_InvestorTokenBal.toNumber(), + final_InvestorTokenBal.toString(), init_InvestorTokenBal .add(investment_Tokens) .sub(refund_Tokens) - .toNumber(), + .toString(), "Investor Token Balance not changed as expected" ); assert.equal( - final_InvestorETHBal.toNumber(), - init_InvestorETHBal.sub(gasCost2).toNumber(), + final_InvestorETHBal.toString(), + init_InvestorETHBal.sub(gasCost2).toString(), "Investor ETH Balance not changed as expected" ); assert.equal( - final_InvestorPOLYBal.toNumber(), + final_InvestorPOLYBal.toString(), init_InvestorPOLYBal .sub(investment_POLY) .add(refund_POLY) - .toNumber(), + .toString(), "Investor POLY Balance not changed as expected" ); assert.equal( - final_STOTokenSold.toNumber(), + final_STOTokenSold.toString(), init_STOTokenSold .add(investment_Tokens) .sub(refund_Tokens) - .toNumber(), + .toString(), "STO Token Sold not changed as expected" ); - assert.equal(final_STOETHBal.toNumber(), init_STOETHBal.toNumber(), "STO ETH Balance not changed as expected"); - assert.equal(final_STOPOLYBal.toNumber(), init_STOPOLYBal.toNumber(), "STO POLY Balance not changed as expected"); - assert.equal(final_RaisedETH.toNumber(), init_RaisedETH.toNumber(), "Raised ETH not changed as expected"); + assert.equal(final_STOETHBal.toString(), init_STOETHBal.toString(), "STO ETH Balance not changed as expected"); + assert.equal(final_STOPOLYBal.toString(), init_STOPOLYBal.toString(), "STO POLY Balance not changed as expected"); + assert.equal(final_RaisedETH.toString(), init_RaisedETH.toString(), "Raised ETH not changed as expected"); assert.equal( - final_RaisedPOLY.toNumber(), + final_RaisedPOLY.toString(), init_RaisedPOLY .add(investment_POLY) .sub(refund_POLY) - .toNumber(), + .toString(), "Raised POLY not changed as expected" ); - assert.equal(final_WalletETHBal.toNumber(), init_WalletETHBal.toNumber(), "Wallet ETH Balance not changed as expected"); + assert.equal(final_WalletETHBal.toString(), init_WalletETHBal.toString(), "Wallet ETH Balance not changed as expected"); assert.equal( - final_WalletPOLYBal.toNumber(), + final_WalletPOLYBal.toString(), init_WalletPOLYBal .add(investment_POLY) .sub(refund_POLY) - .toNumber(), + .toString(), "Wallet POLY Balance not changed as expected" ); - await I_SecurityToken.changeGranularity(1, {from: ISSUER}); + await I_SecurityToken.changeGranularity(1, { from: ISSUER }); }); it("should successfully buy a granular amount and refund balance when buying indivisible token with ETH", async () => { - await I_SecurityToken.changeGranularity(10**18, {from: ISSUER}); + await I_SecurityToken.changeGranularity(e18, { from: ISSUER }); let stoId = 4; let tierId = 0; - let investment_Tokens = BigNumber(10.5).mul(10**18); + let investment_Tokens = new BN(1050).mul(e16); let investment_ETH = await convert(stoId, tierId, false, "TOKEN", "ETH", investment_Tokens); - let refund_Tokens = BigNumber(0.5).mul(10**18); + let refund_Tokens = new BN(50).mul(e16); let refund_ETH = await convert(stoId, tierId, false, "TOKEN", "ETH", refund_Tokens); let init_TokenSupply = await I_SecurityToken.totalSupply(); let init_InvestorTokenBal = await I_SecurityToken.balanceOf(ACCREDITED1); - let init_InvestorETHBal = BigNumber(await web3.eth.getBalance(ACCREDITED1)); + let init_InvestorETHBal = new BN(await web3.eth.getBalance(ACCREDITED1)); let init_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); - let init_STOETHBal = BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let init_STOETHBal = new BN(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); let init_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); let init_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); let init_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); - // Buy With ETH let tx2 = await I_USDTieredSTO_Array[stoId].buyWithETH(ACCREDITED1, { from: ACCREDITED1, gasPrice: GAS_PRICE, value: investment_ETH }); - let gasCost2 = BigNumber(GAS_PRICE).mul(tx2.receipt.gasUsed); - console.log(" Gas buyWithETH: ".grey + tx2.receipt.gasUsed.toString().grey); + let gasCost2 = new BN(GAS_PRICE).mul(new BN(tx2.receipt.gasUsed)); + console.log(" Gas buyWithETH: ".grey + new BN(tx2.receipt.gasUsed).toString().grey); let final_TokenSupply = await I_SecurityToken.totalSupply(); let final_InvestorTokenBal = await I_SecurityToken.balanceOf(ACCREDITED1); - let final_InvestorETHBal = BigNumber(await web3.eth.getBalance(ACCREDITED1)); + let final_InvestorETHBal = new BN(await web3.eth.getBalance(ACCREDITED1)); let final_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); - let final_STOETHBal = BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let final_STOETHBal = new BN(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); let final_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); let final_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); let final_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); assert.equal( - final_TokenSupply.toNumber(), + final_TokenSupply.toString(), init_TokenSupply .add(investment_Tokens) .sub(refund_Tokens) - .toNumber(), + .toString(), "Token Supply not changed as expected" ); assert.equal( - final_InvestorTokenBal.toNumber(), + final_InvestorTokenBal.toString(), init_InvestorTokenBal .add(investment_Tokens) .sub(refund_Tokens) - .toNumber(), + .toString(), "Investor Token Balance not changed as expected" ); assert.equal( - final_InvestorETHBal.toNumber(), - init_InvestorETHBal.sub(investment_ETH).sub(gasCost2).add(refund_ETH).toNumber(), + final_InvestorETHBal.toString(), + init_InvestorETHBal + .sub(investment_ETH) + .sub(gasCost2) + .add(refund_ETH) + .toString(), "Investor ETH Balance not changed as expected" ); assert.equal( - final_STOTokenSold.toNumber(), + final_STOTokenSold.toString(), init_STOTokenSold .add(investment_Tokens) .sub(refund_Tokens) - .toNumber(), + .toString(), "STO Token Sold not changed as expected" ); - assert.equal(final_STOETHBal.toNumber(), init_STOETHBal.toNumber(), "STO ETH Balance not changed as expected"); - assert.equal(final_STOPOLYBal.toNumber(), init_STOPOLYBal.toNumber(), "STO POLY Balance not changed as expected"); - assert.equal(final_RaisedETH.toNumber(), init_RaisedETH.add(investment_ETH).sub(refund_ETH).toNumber(), "Raised ETH not changed as expected"); + assert.equal(final_STOETHBal.toString(), init_STOETHBal.toString(), "STO ETH Balance not changed as expected"); + assert.equal(final_STOPOLYBal.toString(), init_STOPOLYBal.toString(), "STO POLY Balance not changed as expected"); assert.equal( - final_RaisedPOLY.toNumber(), - init_RaisedPOLY, - "Raised POLY not changed as expected" + final_RaisedETH.toString(), + init_RaisedETH + .add(investment_ETH) + .sub(refund_ETH) + .toString(), + "Raised ETH not changed as expected" ); - await I_SecurityToken.changeGranularity(1, {from: ISSUER}); + assert.equal(final_RaisedPOLY.toString(), init_RaisedPOLY, "Raised POLY not changed as expected"); + await I_SecurityToken.changeGranularity(1, { from: ISSUER }); }); it("should fail and revert when NONACCREDITED cap reached", async () => { let stoId = 2; let tierId = 0; - let investment_Token = new BigNumber(5).mul(10 ** 18); + let investment_Token = new BN(5).mul(e18); let investment_USD = await convert(stoId, tierId, true, "TOKEN", "USD", investment_Token); let investment_ETH = await convert(stoId, tierId, true, "TOKEN", "ETH", investment_Token); let investment_POLY = await convert(stoId, tierId, true, "TOKEN", "POLY", investment_Token); @@ -4204,25 +4192,28 @@ contract("USDTieredSTO", accounts => { it("should fail when rate set my contract is too low", async () => { let stoId = 4; let tierId = 0; - await I_USDTieredSTO_Array[stoId].changeAccredited([ACCREDITED1], [true], { from: ISSUER }); - let investment_Tokens = new BigNumber(10 ** 18); + let investment_Tokens = new BN(e18); let investment_POLY = await convert(stoId, tierId, true, "TOKEN", "POLY", investment_Tokens); let investment_ETH = await convert(stoId, tierId, true, "TOKEN", "ETH", investment_Tokens); - const minTokens = new BigNumber(10 ** 20); + const minTokens = new BN(1000).mul(e18); await I_PolyToken.getTokens(investment_POLY, ACCREDITED1); await I_PolyToken.approve(I_USDTieredSTO_Array[stoId].address, investment_POLY, { from: ACCREDITED1 }); // Buy With POLY - await catchRevert(I_USDTieredSTO_Array[stoId].buyWithPOLYRateLimited(ACCREDITED1, investment_POLY, minTokens, { - from: ACCREDITED1, - gasPrice: GAS_PRICE - })); - await catchRevert(I_USDTieredSTO_Array[stoId].buyWithETHRateLimited(ACCREDITED1, minTokens, { - from: ACCREDITED1, - gasPrice: GAS_PRICE, - value: investment_ETH - })); + await catchRevert( + I_USDTieredSTO_Array[stoId].buyWithPOLYRateLimited(ACCREDITED1, investment_POLY, minTokens, { + from: ACCREDITED1, + gasPrice: GAS_PRICE + }) + ); + await catchRevert( + I_USDTieredSTO_Array[stoId].buyWithETHRateLimited(ACCREDITED1, minTokens, { + from: ACCREDITED1, + gasPrice: GAS_PRICE, + value: investment_ETH + }) + ); }); it("should fail and revert despite oracle price change when NONACCREDITED cap reached", async () => { @@ -4230,18 +4221,18 @@ contract("USDTieredSTO", accounts => { let tierId = 0; // set new exchange rates - let high_USDETH = new BigNumber(1000).mul(10 ** 18); // 1000 USD per ETH - let high_USDPOLY = new BigNumber(50).mul(10 ** 16); // 0.5 USD per POLY - let low_USDETH = new BigNumber(250).mul(10 ** 18); // 250 USD per ETH - let low_USDPOLY = new BigNumber(20).mul(10 ** 16); // 0.2 USD per POLY + let high_USDETH = new BN(1000).mul(e18); // 1000 USD per ETH + let high_USDPOLY = new BN(50).mul(e16); // 0.5 USD per POLY + let low_USDETH = new BN(250).mul(e18); // 250 USD per ETH + let low_USDPOLY = new BN(20).mul(e16); // 0.2 USD per POLY - let investment_Token = new BigNumber(5).mul(10 ** 18); + let investment_Token = new BN(5).mul(e18); let investment_USD = await convert(stoId, tierId, true, "TOKEN", "USD", investment_Token); - let investment_ETH_high = investment_USD.div(high_USDETH).mul(10 ** 18); // USD / USD/ETH = ETH - let investment_POLY_high = investment_USD.div(high_USDPOLY).mul(10 ** 18); // USD / USD/POLY = POLY - let investment_ETH_low = investment_USD.div(low_USDETH).mul(10 ** 18); // USD / USD/ETH = ETH - let investment_POLY_low = investment_USD.div(low_USDPOLY).mul(10 ** 18); // USD / USD/POLY = POLY + let investment_ETH_high = investment_USD.div(high_USDETH).mul(e18); // USD / USD/ETH = ETH + let investment_POLY_high = investment_USD.div(high_USDPOLY).mul(e18); // USD / USD/POLY = POLY + let investment_ETH_low = investment_USD.div(low_USDETH).mul(e18); // USD / USD/ETH = ETH + let investment_POLY_low = investment_USD.div(low_USDPOLY).mul(e18); // USD / USD/POLY = POLY await I_PolyToken.getTokens(investment_POLY_low, NONACCREDITED1); await I_PolyToken.approve(I_USDTieredSTO_Array[stoId].address, investment_POLY_low, { from: NONACCREDITED1 }); @@ -4297,12 +4288,12 @@ contract("USDTieredSTO", accounts => { let endTier = 1; assert.equal( - (await I_USDTieredSTO_Array[stoId].currentTier.call()).toNumber(), + (await I_USDTieredSTO_Array[stoId].currentTier.call()).toString(), startTier, "currentTier not changed as expected" ); - let delta_Token = new BigNumber(5).mul(10 ** 18); // Token + let delta_Token = new BN(5).mul(e18); // Token let polyTier0 = await convert(stoId, startTier, true, "TOKEN", "POLY", delta_Token); let polyTier1 = await convert(stoId, endTier, true, "TOKEN", "POLY", delta_Token); let investment_Token = delta_Token.add(delta_Token); // 10 Token @@ -4321,7 +4312,7 @@ contract("USDTieredSTO", accounts => { let Tier0Token = (await I_USDTieredSTO_Array[stoId].tiers.call(startTier))[2]; let Tier0Minted = (await I_USDTieredSTO_Array[stoId].tiers.call(startTier))[4]; - assert.equal(Tier0Minted.toNumber(), Tier0Token.sub(delta_Token).toNumber()); + assert.equal(Tier0Minted.toString(), Tier0Token.sub(delta_Token).toString()); await I_PolyToken.getTokens(investment_POLY, ACCREDITED1); await I_PolyToken.approve(I_USDTieredSTO_Array[stoId].address, investment_POLY, { from: ACCREDITED1 }); @@ -4329,87 +4320,87 @@ contract("USDTieredSTO", accounts => { // Process investment let init_TokenSupply = await I_SecurityToken.totalSupply(); let init_InvestorTokenBal = await I_SecurityToken.balanceOf(ACCREDITED1); - let init_InvestorETHBal = new BigNumber(await web3.eth.getBalance(ACCREDITED1)); + let init_InvestorETHBal = new BN(await web3.eth.getBalance(ACCREDITED1)); let init_InvestorPOLYBal = await I_PolyToken.balanceOf(ACCREDITED1); let init_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); - let init_STOETHBal = new BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let init_STOETHBal = new BN(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); let init_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); let init_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); let init_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); - let init_WalletETHBal = new BigNumber(await web3.eth.getBalance(WALLET)); + let init_WalletETHBal = new BN(await web3.eth.getBalance(WALLET)); let init_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET); let tx2 = await I_USDTieredSTO_Array[stoId].buyWithPOLY(ACCREDITED1, investment_POLY, { from: ACCREDITED1, gasPrice: GAS_PRICE }); - let gasCost2 = new BigNumber(GAS_PRICE).mul(tx2.receipt.gasUsed); - console.log(" Gas buyWithPOLY: ".grey + tx2.receipt.gasUsed.toString().grey); + let gasCost2 = new BN(GAS_PRICE).mul(new BN(tx2.receipt.gasUsed)); + console.log(" Gas buyWithPOLY: ".grey + new BN(tx2.receipt.gasUsed).toString().grey); let final_TokenSupply = await I_SecurityToken.totalSupply(); let final_InvestorTokenBal = await I_SecurityToken.balanceOf(ACCREDITED1); - let final_InvestorETHBal = new BigNumber(await web3.eth.getBalance(ACCREDITED1)); + let final_InvestorETHBal = new BN(await web3.eth.getBalance(ACCREDITED1)); let final_InvestorPOLYBal = await I_PolyToken.balanceOf(ACCREDITED1); let final_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); - let final_STOETHBal = new BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let final_STOETHBal = new BN(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); let final_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); let final_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); let final_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); - let final_WalletETHBal = new BigNumber(await web3.eth.getBalance(WALLET)); + let final_WalletETHBal = new BN(await web3.eth.getBalance(WALLET)); let final_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET); assert.equal( - final_TokenSupply.toNumber(), - init_TokenSupply.add(investment_Token).toNumber(), + final_TokenSupply.toString(), + init_TokenSupply.add(investment_Token).toString(), "Token Supply not changed as expected" ); assert.equal( - final_InvestorTokenBal.toNumber(), - init_InvestorTokenBal.add(investment_Token).toNumber(), + final_InvestorTokenBal.toString(), + init_InvestorTokenBal.add(investment_Token).toString(), "Investor Token Balance not changed as expected" ); assert.equal( - final_InvestorETHBal.toNumber(), - init_InvestorETHBal.sub(gasCost2).toNumber(), + final_InvestorETHBal.toString(), + init_InvestorETHBal.sub(gasCost2).toString(), "Investor ETH Balance not changed as expected" ); assert.equal( - final_InvestorPOLYBal.toNumber(), - init_InvestorPOLYBal.sub(investment_POLY).toNumber(), + final_InvestorPOLYBal.toString(), + init_InvestorPOLYBal.sub(investment_POLY).toString(), "Investor POLY Balance not changed as expected" ); assert.equal( - final_STOTokenSold.toNumber(), - init_STOTokenSold.add(investment_Token).toNumber(), + final_STOTokenSold.toString(), + init_STOTokenSold.add(investment_Token).toString(), "STO Token Sold not changed as expected" ); - assert.equal(final_STOETHBal.toNumber(), init_STOETHBal.toNumber(), "STO ETH Balance not changed as expected"); - assert.equal(final_STOPOLYBal.toNumber(), init_STOPOLYBal.toNumber(), "STO POLY Balance not changed as expected"); - assert.equal(final_RaisedETH.toNumber(), init_RaisedETH.toNumber(), "Raised ETH not changed as expected"); + assert.equal(final_STOETHBal.toString(), init_STOETHBal.toString(), "STO ETH Balance not changed as expected"); + assert.equal(final_STOPOLYBal.toString(), init_STOPOLYBal.toString(), "STO POLY Balance not changed as expected"); + assert.equal(final_RaisedETH.toString(), init_RaisedETH.toString(), "Raised ETH not changed as expected"); assert.equal( - final_RaisedPOLY.toNumber(), - init_RaisedPOLY.add(investment_POLY).toNumber(), + final_RaisedPOLY.toString(), + init_RaisedPOLY.add(investment_POLY).toString(), "Raised POLY not changed as expected" ); - assert.equal(final_WalletETHBal.toNumber(), init_WalletETHBal.toNumber(), "Wallet ETH Balance not changed as expected"); + assert.equal(final_WalletETHBal.toString(), init_WalletETHBal.toString(), "Wallet ETH Balance not changed as expected"); assert.equal( - final_WalletPOLYBal.toNumber(), - init_WalletPOLYBal.add(investment_POLY).toNumber(), + final_WalletPOLYBal.toString(), + init_WalletPOLYBal.add(investment_POLY).toString(), "Wallet POLY Balance not changed as expected" ); // Additional Checks - assert.equal((await I_USDTieredSTO_Array[stoId].currentTier.call()).toNumber(), endTier, "currentTier not changed as expected"); + assert.equal((await I_USDTieredSTO_Array[stoId].currentTier.call()).toString(), endTier, "currentTier not changed as expected"); }); it("should successfully buy across the discount cap", async () => { let stoId = 2; let tierId = 1; - let discount_Token = new BigNumber(20).mul(10 ** 18); + let discount_Token = new BN(20).mul(e18); let discount_POLY = await convert(stoId, tierId, true, "TOKEN", "POLY", discount_Token); - let regular_Token = new BigNumber(10).mul(10 ** 18); + let regular_Token = new BN(10).mul(e18); let regular_POLY = await convert(stoId, tierId, false, "TOKEN", "POLY", regular_Token); let investment_Token = discount_Token.add(regular_Token); @@ -4420,14 +4411,14 @@ contract("USDTieredSTO", accounts => { let init_TokenSupply = await I_SecurityToken.totalSupply(); let init_InvestorTokenBal = await I_SecurityToken.balanceOf(ACCREDITED1); - let init_InvestorETHBal = new BigNumber(await web3.eth.getBalance(ACCREDITED1)); + let init_InvestorETHBal = new BN(await web3.eth.getBalance(ACCREDITED1)); let init_InvestorPOLYBal = await I_PolyToken.balanceOf(ACCREDITED1); let init_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); - let init_STOETHBal = new BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let init_STOETHBal = new BN(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); let init_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); let init_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); let init_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); - let init_WalletETHBal = new BigNumber(await web3.eth.getBalance(WALLET)); + let init_WalletETHBal = new BN(await web3.eth.getBalance(WALLET)); let init_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET); // Buy With POLY @@ -4435,58 +4426,58 @@ contract("USDTieredSTO", accounts => { from: ACCREDITED1, gasPrice: GAS_PRICE }); - let gasCost2 = new BigNumber(GAS_PRICE).mul(tx2.receipt.gasUsed); - console.log(" Gas buyWithPOLY: ".grey + tx2.receipt.gasUsed.toString().grey); + let gasCost2 = new BN(GAS_PRICE).mul(new BN(tx2.receipt.gasUsed)); + console.log(" Gas buyWithPOLY: ".grey + new BN(tx2.receipt.gasUsed).toString().grey); let final_TokenSupply = await I_SecurityToken.totalSupply(); let final_InvestorTokenBal = await I_SecurityToken.balanceOf(ACCREDITED1); - let final_InvestorETHBal = new BigNumber(await web3.eth.getBalance(ACCREDITED1)); + let final_InvestorETHBal = new BN(await web3.eth.getBalance(ACCREDITED1)); let final_InvestorPOLYBal = await I_PolyToken.balanceOf(ACCREDITED1); let final_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); - let final_STOETHBal = new BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let final_STOETHBal = new BN(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); let final_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); let final_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); let final_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); - let final_WalletETHBal = new BigNumber(await web3.eth.getBalance(WALLET)); + let final_WalletETHBal = new BN(await web3.eth.getBalance(WALLET)); let final_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET); assert.equal( - final_TokenSupply.toNumber(), - init_TokenSupply.add(investment_Token).toNumber(), + final_TokenSupply.toString(), + init_TokenSupply.add(investment_Token).toString(), "Token Supply not changed as expected" ); assert.equal( - final_InvestorTokenBal.toNumber(), - init_InvestorTokenBal.add(investment_Token).toNumber(), + final_InvestorTokenBal.toString(), + init_InvestorTokenBal.add(investment_Token).toString(), "Investor Token Balance not changed as expected" ); assert.equal( - final_InvestorETHBal.toNumber(), - init_InvestorETHBal.sub(gasCost2).toNumber(), + final_InvestorETHBal.toString(), + init_InvestorETHBal.sub(gasCost2).toString(), "Investor ETH Balance not changed as expected" ); assert.equal( - final_InvestorPOLYBal.toNumber(), - init_InvestorPOLYBal.sub(investment_POLY).toNumber(), + final_InvestorPOLYBal.toString(), + init_InvestorPOLYBal.sub(investment_POLY).toString(), "Investor POLY Balance not changed as expected" ); assert.equal( - final_STOTokenSold.toNumber(), - init_STOTokenSold.add(investment_Token).toNumber(), + final_STOTokenSold.toString(), + init_STOTokenSold.add(investment_Token).toString(), "STO Token Sold not changed as expected" ); - assert.equal(final_STOETHBal.toNumber(), init_STOETHBal.toNumber(), "STO ETH Balance not changed as expected"); - assert.equal(final_STOPOLYBal.toNumber(), init_STOPOLYBal.toNumber(), "STO POLY Balance not changed as expected"); - assert.equal(final_RaisedETH.toNumber(), init_RaisedETH.toNumber(), "Raised ETH not changed as expected"); + assert.equal(final_STOETHBal.toString(), init_STOETHBal.toString(), "STO ETH Balance not changed as expected"); + assert.equal(final_STOPOLYBal.toString(), init_STOPOLYBal.toString(), "STO POLY Balance not changed as expected"); + assert.equal(final_RaisedETH.toString(), init_RaisedETH.toString(), "Raised ETH not changed as expected"); assert.equal( - final_RaisedPOLY.toNumber(), - init_RaisedPOLY.add(investment_POLY).toNumber(), + final_RaisedPOLY.toString(), + init_RaisedPOLY.add(investment_POLY).toString(), "Raised POLY not changed as expected" ); - assert.equal(final_WalletETHBal.toNumber(), init_WalletETHBal.toNumber(), "Wallet ETH Balance not changed as expected"); + assert.equal(final_WalletETHBal.toString(), init_WalletETHBal.toString(), "Wallet ETH Balance not changed as expected"); assert.equal( - final_WalletPOLYBal.toNumber(), - init_WalletPOLYBal.add(investment_POLY).toNumber(), + final_WalletPOLYBal.toString(), + init_WalletPOLYBal.add(investment_POLY).toString(), "Wallet POLY Balance not changed as expected" ); }); @@ -4511,26 +4502,26 @@ contract("USDTieredSTO", accounts => { from: ACCREDITED1, gasPrice: GAS_PRICE }); - let gasCost2 = new BigNumber(GAS_PRICE).mul(tx2.receipt.gasUsed); - console.log(" Gas buyWithPOLY: ".grey + tx2.receipt.gasUsed.toString().grey); + let gasCost2 = new BN(GAS_PRICE).mul(new BN(tx2.receipt.gasUsed)); + console.log(" Gas buyWithPOLY: ".grey + new BN(tx2.receipt.gasUsed).toString().grey); let final_TokenSupply = await I_SecurityToken.totalSupply(); let final_InvestorTokenBal = await I_SecurityToken.balanceOf(ACCREDITED1); let final_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); assert.equal( - final_TokenSupply.toNumber(), - init_TokenSupply.add(investment_Token).toNumber(), + final_TokenSupply.toString(), + init_TokenSupply.add(investment_Token).toString(), "Token Supply not changed as expected" ); assert.equal( - final_InvestorTokenBal.toNumber(), - init_InvestorTokenBal.add(investment_Token).toNumber(), + final_InvestorTokenBal.toString(), + init_InvestorTokenBal.add(investment_Token).toString(), "Investor Token Balance not changed as expected" ); assert.equal( - final_STOTokenSold.toNumber(), - init_STOTokenSold.add(investment_Token).toNumber(), + final_STOTokenSold.toString(), + init_STOTokenSold.add(investment_Token).toString(), "STO Token Sold not changed as expected" ); }); @@ -4539,7 +4530,7 @@ contract("USDTieredSTO", accounts => { let stoId = 2; let tierId = 1; - let investment_Token = new BigNumber(5).mul(10 ** 18); + let investment_Token = new BN(5).mul(e18); let investment_USD = await convert(stoId, tierId, false, "TOKEN", "USD", investment_Token); let investment_ETH = await convert(stoId, tierId, false, "TOKEN", "ETH", investment_Token); let investment_POLY = await convert(stoId, tierId, false, "TOKEN", "POLY", investment_Token); @@ -4579,18 +4570,18 @@ contract("USDTieredSTO", accounts => { let tierId = 1; // set new exchange rates - let high_USDETH = new BigNumber(1000).mul(10 ** 18); // 1000 USD per ETH - let high_USDPOLY = new BigNumber(50).mul(10 ** 16); // 0.5 USD per POLY - let low_USDETH = new BigNumber(250).mul(10 ** 18); // 250 USD per ETH - let low_USDPOLY = new BigNumber(20).mul(10 ** 16); // 0.2 USD per POLY + let high_USDETH = new BN(1000).mul(e18); // 1000 USD per ETH + let high_USDPOLY = new BN(50).mul(e16); // 0.5 USD per POLY + let low_USDETH = new BN(250).mul(e18); // 250 USD per ETH + let low_USDPOLY = new BN(20).mul(e16); // 0.2 USD per POLY - let investment_Token = new BigNumber(5).mul(10 ** 18); + let investment_Token = new BN(5).mul(e18); let investment_USD = await convert(stoId, tierId, true, "TOKEN", "USD", investment_Token); - let investment_ETH_high = investment_USD.div(high_USDETH).mul(10 ** 18); // USD / USD/ETH = ETH - let investment_POLY_high = investment_USD.div(high_USDPOLY).mul(10 ** 18); // USD / USD/POLY = POLY - let investment_ETH_low = investment_USD.div(low_USDETH).mul(10 ** 18); // USD / USD/ETH = ETH - let investment_POLY_low = investment_USD.div(low_USDPOLY).mul(10 ** 18); // USD / USD/POLY = POLY + let investment_ETH_high = investment_USD.div(high_USDETH).mul(e18); // USD / USD/ETH = ETH + let investment_POLY_high = investment_USD.div(high_USDPOLY).mul(e18); // USD / USD/POLY = POLY + let investment_ETH_low = investment_USD.div(low_USDETH).mul(e18); // USD / USD/ETH = ETH + let investment_POLY_low = investment_USD.div(low_USDPOLY).mul(e18); // USD / USD/POLY = POLY await I_PolyToken.getTokens(investment_POLY_low, NONACCREDITED1); await I_PolyToken.approve(I_USDTieredSTO_Array[stoId].address, investment_POLY_low, { from: NONACCREDITED1 }); @@ -4665,57 +4656,62 @@ contract("USDTieredSTO", accounts => { await I_USDOracle.changePrice(USDETH, { from: POLYMATH }); await I_POLYOracle.changePrice(USDPOLY, { from: POLYMATH }); }); - }); describe("Test getter functions", async () => { describe("Generic", async () => { it("should get the right number of investors", async () => { assert.equal( - (await I_USDTieredSTO_Array[0].investorCount.call()).toNumber(), - (await I_USDTieredSTO_Array[0].investorCount()).toNumber(), + (await I_USDTieredSTO_Array[0].investorCount.call()).toString(), + (await I_USDTieredSTO_Array[0].investorCount()).toString(), "Investor count not changed as expected" ); assert.equal( - (await I_USDTieredSTO_Array[1].investorCount.call()).toNumber(), - (await I_USDTieredSTO_Array[1].investorCount()).toNumber(), + (await I_USDTieredSTO_Array[1].investorCount.call()).toString(), + (await I_USDTieredSTO_Array[1].investorCount()).toString(), "Investor count not changed as expected" ); assert.equal( - (await I_USDTieredSTO_Array[2].investorCount.call()).toNumber(), - (await I_USDTieredSTO_Array[2].investorCount()).toNumber(), + (await I_USDTieredSTO_Array[2].investorCount.call()).toString(), + (await I_USDTieredSTO_Array[2].investorCount()).toString(), "Investor count not changed as expected" ); }); it("should get the right amounts invested", async () => { assert.equal( - (await I_USDTieredSTO_Array[0].fundsRaised.call(ETH)).toNumber(), - (await I_USDTieredSTO_Array[0].getRaised(0)).toNumber(), + (await I_USDTieredSTO_Array[0].fundsRaised.call(ETH)).toString(), + (await I_USDTieredSTO_Array[0].getRaised(0)).toString(), "getRaisedEther not changed as expected" ); assert.equal( - (await I_USDTieredSTO_Array[0].fundsRaised.call(POLY)).toNumber(), - (await I_USDTieredSTO_Array[0].getRaised(1)).toNumber(), + (await I_USDTieredSTO_Array[0].fundsRaised.call(POLY)).toString(), + (await I_USDTieredSTO_Array[0].getRaised(1)).toString(), "getRaisedPOLY not changed as expected" ); assert.equal( - (await I_USDTieredSTO_Array[0].fundsRaisedUSD.call()).toNumber(), - (await I_USDTieredSTO_Array[0].fundsRaisedUSD()).toNumber(), + (await I_USDTieredSTO_Array[0].fundsRaisedUSD.call()).toString(), + (await I_USDTieredSTO_Array[0].fundsRaisedUSD()).toString(), "fundsRaisedUSD not changed as expected" ); }); it("should return minted tokens in a tier", async () => { - let totalMinted = (await I_USDTieredSTO_Array[0].getTokensSoldByTier.call(0)).toNumber(); + let totalMinted = (await I_USDTieredSTO_Array[0].getTokensSoldByTier.call(0)).toString(); let individualMinted = await I_USDTieredSTO_Array[0].getTokensMintedByTier.call(0); - assert.equal(totalMinted, individualMinted[0].add(individualMinted[1]).add(individualMinted[2]).toNumber()); + assert.equal( + totalMinted, + individualMinted[0] + .add(individualMinted[1]) + .add(individualMinted[2]) + .toString() + ); }); it("should return correct tokens sold in token details", async () => { - let tokensSold = (await I_USDTieredSTO_Array[0].getTokensSold.call()).toNumber(); + let tokensSold = (await I_USDTieredSTO_Array[0].getTokensSold.call()).toString(); let tokenDetails = await I_USDTieredSTO_Array[0].getSTODetails.call(); - assert.equal(tokensSold, tokenDetails[7].toNumber()); + assert.equal(tokensSold, tokenDetails[7].toString()); }); }); @@ -4728,27 +4724,27 @@ contract("USDTieredSTO", accounts => { it("should get the right conversion for ETH to USD", async () => { // 20 ETH to 10000 USD - let ethInWei = new BigNumber(web3.utils.toWei("20", "ether")); - let usdInWei = await I_USDTieredSTO_Array[0].convertToUSD(ETH, ethInWei); + let ethInWei = new BN(web3.utils.toWei("20", "ether")); + let usdInWei = await I_USDTieredSTO_Array[0].convertToUSD.call(ETH, ethInWei); assert.equal( - usdInWei.div(10 ** 18).toNumber(), + usdInWei.div(e18).toString(), ethInWei - .div(10 ** 18) - .mul(USDETH.div(10 ** 18)) - .toNumber() + .div(e18) + .mul(USDETH.div(e18)) + .toString() ); }); it("should get the right conversion for POLY to USD", async () => { // 40000 POLY to 10000 USD - let polyInWei = new BigNumber(web3.utils.toWei("40000", "ether")); - let usdInWei = await I_USDTieredSTO_Array[0].convertToUSD(POLY, polyInWei); + let polyInWei = new BN(web3.utils.toWei("40000", "ether")); + let usdInWei = await I_USDTieredSTO_Array[0].convertToUSD.call(POLY, polyInWei); assert.equal( - usdInWei.div(10 ** 18).toNumber(), + usdInWei.toString(), polyInWei - .div(10 ** 18) - .mul(USDPOLY.div(10 ** 18)) - .toNumber() + .mul(USDPOLY) + .div(e18) + .toString() ); }); }); @@ -4756,27 +4752,24 @@ contract("USDTieredSTO", accounts => { describe("convertFromUSD", async () => { it("should get the right conversion for USD to ETH", async () => { // 10000 USD to 20 ETH - let usdInWei = new BigNumber(web3.utils.toWei("10000", "ether")); - let ethInWei = await I_USDTieredSTO_Array[0].convertFromUSD(ETH, usdInWei); + let usdInWei = new BN(web3.utils.toWei("10000", "ether")); + let ethInWei = await I_USDTieredSTO_Array[0].convertFromUSD.call(ETH, usdInWei); assert.equal( - ethInWei.div(10 ** 18).toNumber(), + ethInWei.div(e18).toString(), usdInWei - .div(10 ** 18) - .div(USDETH.div(10 ** 18)) - .toNumber() + .div(e18) + .div(USDETH.div(e18)) + .toString() ); }); it("should get the right conversion for USD to POLY", async () => { // 10000 USD to 40000 POLY - let usdInWei = new BigNumber(web3.utils.toWei("10000", "ether")); - let polyInWei = await I_USDTieredSTO_Array[0].convertFromUSD(POLY, usdInWei); + let usdInWei = new BN(web3.utils.toWei("10000", "ether")); + let polyInWei = await I_USDTieredSTO_Array[0].convertFromUSD.call(POLY, usdInWei); assert.equal( - polyInWei.div(10 ** 18).toNumber(), - usdInWei - .div(10 ** 18) - .div(USDPOLY.div(10 ** 18)) - .toNumber() + polyInWei.toString(), + usdInWei.mul(e18).div(USDPOLY).toString() ); }); }); @@ -4784,20 +4777,22 @@ contract("USDTieredSTO", accounts => { describe("Test cases for the USDTieredSTOFactory", async () => { it("should get the exact details of the factory", async () => { - assert.equal((await I_USDTieredSTOFactory.getSetupCost.call()).toNumber(), STOSetupCost); + assert.equal((await I_USDTieredSTOFactory.setupCost.call()).toString(), STOSetupCost); assert.equal((await I_USDTieredSTOFactory.getTypes.call())[0], 3); - assert.equal(web3.utils.hexToString(await I_USDTieredSTOFactory.getName.call()), "USDTieredSTO", "Wrong Module added"); - assert.equal(await I_USDTieredSTOFactory.description.call(), - "It allows both accredited and non-accredited investors to contribute into the STO. Non-accredited investors will be capped at a maximum investment limit (as a default or specific to their jurisdiction). Tokens will be sold according to tiers sequentially & each tier has its own price and volume of tokens to sell. Upon receipt of funds (ETH, POLY or DAI), security tokens will automatically transfer to investor’s wallet address", - "Wrong Module added"); + assert.equal(web3.utils.hexToString(await I_USDTieredSTOFactory.name.call()), "USDTieredSTO", "Wrong Module added"); + assert.equal( + await I_USDTieredSTOFactory.description.call(), + "It allows both accredited and non-accredited investors to contribute into the STO. Non-accredited investors will be capped at a maximum investment limit (as a default or specific to their jurisdiction). Tokens will be sold according to tiers sequentially & each tier has its own price and volume of tokens to sell. Upon receipt of funds (ETH, POLY or DAI), security tokens will automatically transfer to investor’s wallet address", + "Wrong Module added" + ); assert.equal(await I_USDTieredSTOFactory.title.call(), "USD Tiered STO", "Wrong Module added"); - assert.equal(await I_USDTieredSTOFactory.getInstructions.call(), "Initialises a USD tiered STO.", "Wrong Module added"); - assert.equal(await I_USDTieredSTOFactory.version.call(), "2.1.0"); + assert.equal(await I_USDTieredSTOFactory.version.call(), "3.0.0"); let tags = await I_USDTieredSTOFactory.getTags.call(); - assert.equal(web3.utils.hexToString(tags[0]), "USD"); - assert.equal(web3.utils.hexToString(tags[1]), "Tiered"); + assert.equal(web3.utils.hexToString(tags[0]), "Tiered"); + assert.equal(web3.utils.hexToString(tags[1]), "ETH"); assert.equal(web3.utils.hexToString(tags[2]), "POLY"); - assert.equal(web3.utils.hexToString(tags[3]), "ETH"); + assert.equal(web3.utils.hexToString(tags[3]), "USD"); + assert.equal(web3.utils.hexToString(tags[4]), "STO"); }); }); }); diff --git a/test/q_usd_tiered_sto_sim.js b/test/q_usd_tiered_sto_sim.js index 2b3062574..e51bf4ac6 100644 --- a/test/q_usd_tiered_sto_sim.js +++ b/test/q_usd_tiered_sto_sim.js @@ -12,19 +12,20 @@ const SecurityToken = artifacts.require("./SecurityToken.sol"); const GeneralTransferManager = artifacts.require("./GeneralTransferManager"); const GeneralPermissionManager = artifacts.require("./GeneralPermissionManager"); const PolyTokenFaucet = artifacts.require("./PolyTokenFaucet.sol"); +const STGetter = artifacts.require("./STGetter.sol"); const Web3 = require("web3"); -const BigNumber = require("bignumber.js"); +let BN = Web3.utils.BN; const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); // Hardcoded development port -const TOLERANCE = 2; // Allow balances to be off by 2 WEI for rounding purposes +//const TOLERANCE = 2; // Allow balances to be off by 2 WEI for rounding purposes -contract("USDTieredSTO Sim", accounts => { +contract("USDTieredSTO Sim", async (accounts) => { // Accounts Variable declaration let POLYMATH; let ISSUER; let WALLET; - let RESERVEWALLET; + let TREASURYWALLET; let INVESTOR1; let ACCREDITED1; let ACCREDITED2; @@ -57,6 +58,9 @@ contract("USDTieredSTO Sim", accounts => { let I_PolyToken; let I_DaiToken; let I_PolymathRegistry; + let I_STRGetter; + let I_STGetter; + let stGetter; // SecurityToken Details for funds raise Type ETH const NAME = "Team"; @@ -69,12 +73,12 @@ contract("USDTieredSTO Sim", accounts => { const STOKEY = 3; // Initial fee for ticker registry and security token registry - const REGFEE = web3.utils.toWei("250"); + const REGFEE = new BN(web3.utils.toWei("1000")); const STOSetupCost = 0; // MockOracle USD prices - const USDETH = new BigNumber(500).mul(10 ** 18); // 500 USD/ETH - const USDPOLY = new BigNumber(25).mul(10 ** 16); // 0.25 USD/POLY + const USDETH = new BN(500).mul(new BN(10).pow(new BN(18))); // 500 USD/ETH + const USDPOLY = new BN(25).mul(new BN(10).pow(new BN(16))); // 0.25 USD/POLY // STO Configuration Arrays let _startTime = []; @@ -87,9 +91,12 @@ contract("USDTieredSTO Sim", accounts => { let _minimumInvestmentUSD = []; let _fundRaiseTypes = []; let _wallet = []; - let _reserveWallet = []; + let _treasuryWallet = []; let _usdToken = []; + const address_zero = "0x0000000000000000000000000000000000000000"; + const one_address = "0x0000000000000000000000000000000000000001"; + /* function configure( uint256 _startTime, uint256 _endTime, @@ -101,7 +108,7 @@ contract("USDTieredSTO Sim", accounts => { uint256 _minimumInvestmentUSD, uint8[] _fundRaiseTypes, address _wallet, - address _reserveWallet, + address _treasuryWallet, address _usdToken ) */ const functionSignature = { @@ -150,7 +157,7 @@ contract("USDTieredSTO Sim", accounts => { }, { type: "address", - name: "_reserveWallet" + name: "_treasuryWallet" }, { type: "address[]", @@ -160,15 +167,27 @@ contract("USDTieredSTO Sim", accounts => { }; function getRandomInt(min, max) { - return Math.floor(Math.random() * (max - min + 1)) + min; + let random = Math.floor(Math.random() * 10 ** 10); + return new BN(random).mul(new BN(max).add(new BN(1)).sub(new BN(min))).div(new BN(10).pow(new BN(10))); + } + + function minBN(a, b) { + if (a.lt(b)) + return a; + else + return b; } + let currentTime; + let e18 = new BN(10).pow(new BN(18)); + let e16 = new BN(10).pow(new BN(16)); + before(async () => { - // Accounts setup + currentTime = new BN(await latestTime()); POLYMATH = accounts[0]; ISSUER = accounts[1]; WALLET = accounts[2]; - RESERVEWALLET = WALLET; + TREASURYWALLET = WALLET; ACCREDITED1 = accounts[3]; ACCREDITED2 = accounts[4]; NONACCREDITED1 = accounts[5]; @@ -193,17 +212,19 @@ contract("USDTieredSTO Sim", accounts => { I_STFactory, I_SecurityTokenRegistry, I_SecurityTokenRegistryProxy, - I_STRProxied + I_STRProxied, + I_STRGetter, + I_STGetter ] = instances; - I_DaiToken = await PolyTokenFaucet.new({from: POLYMATH}); + I_DaiToken = await PolyTokenFaucet.new({ from: POLYMATH }); - // STEP 5: Deploy the USDTieredSTOFactory - [I_USDTieredSTOFactory] = await deployUSDTieredSTOAndVerified(POLYMATH, I_MRProxied, I_PolyToken.address, STOSetupCost); + // STEP 5: Deploy the USDTieredSTOFactory + [I_USDTieredSTOFactory] = await deployUSDTieredSTOAndVerified(POLYMATH, I_MRProxied, STOSetupCost); // Step 12: Deploy & Register Mock Oracles - I_USDOracle = await MockOracle.new(0, "ETH", "USD", USDETH, { from: POLYMATH }); // 500 dollars per POLY - I_POLYOracle = await MockOracle.new(I_PolyToken.address, "POLY", "USD", USDPOLY, { from: POLYMATH }); // 25 cents per POLY + I_USDOracle = await MockOracle.new(address_zero, web3.utils.fromAscii("ETH"), web3.utils.fromAscii("USD"), USDETH, { from: POLYMATH }); // 500 dollars per POLY + I_POLYOracle = await MockOracle.new(I_PolyToken.address, web3.utils.fromAscii("POLY"), web3.utils.fromAscii("USD"), USDPOLY, { from: POLYMATH }); // 25 cents per POLY await I_PolymathRegistry.changeAddress("EthUsdOracle", I_USDOracle.address, { from: POLYMATH }); await I_PolymathRegistry.changeAddress("PolyUsdOracle", I_POLYOracle.address, { from: POLYMATH }); @@ -231,7 +252,7 @@ contract("USDTieredSTO Sim", accounts => { it("Should register the ticker before the generation of the security token", async () => { await I_PolyToken.getTokens(REGFEE, ISSUER); await I_PolyToken.approve(I_STRProxied.address, REGFEE, { from: ISSUER }); - let tx = await I_STRProxied.registerTicker(ISSUER, SYMBOL, NAME, { from: ISSUER }); + let tx = await I_STRProxied.registerNewTicker(ISSUER, SYMBOL, { from: ISSUER }); assert.equal(tx.logs[0].args._owner, ISSUER); assert.equal(tx.logs[0].args._ticker, SYMBOL); }); @@ -239,38 +260,39 @@ contract("USDTieredSTO Sim", accounts => { it("Should generate the new security token with the same symbol as registered above", async () => { await I_PolyToken.getTokens(REGFEE, ISSUER); await I_PolyToken.approve(I_STRProxied.address, REGFEE, { from: ISSUER }); - let _blockNo = latestBlock(); - let tx = await I_STRProxied.generateSecurityToken(NAME, SYMBOL, TOKENDETAILS, true, { from: ISSUER }); - assert.equal(tx.logs[1].args._ticker, SYMBOL, "SecurityToken doesn't get deployed"); - I_SecurityToken = SecurityToken.at(tx.logs[1].args._securityTokenAddress); + let tx = await I_STRProxied.generateNewSecurityToken(NAME, SYMBOL, TOKENDETAILS, true, ISSUER, 0, { from: ISSUER }); + assert.equal(tx.logs[1].args._ticker, SYMBOL, "SecurityToken doesn't get deployed"); - const log = await promisifyLogWatch(I_SecurityToken.ModuleAdded({ from: _blockNo }), 1); + I_SecurityToken = await SecurityToken.at(tx.logs[1].args._securityTokenAddress); + stGetter = await STGetter.at(I_SecurityToken.address); + assert.equal(await stGetter.getTreasuryWallet.call(), ISSUER, "Incorrect wallet set"); + const log = (await I_SecurityToken.getPastEvents('ModuleAdded', {filter: {transactionHash: tx.transactionHash}}))[0]; // Verify that GeneralTransferManager module get added successfully or not assert.equal(log.args._types[0].toNumber(), TMKEY); assert.equal(web3.utils.hexToString(log.args._name), "GeneralTransferManager"); }); - it("Should intialize the auto attached modules", async () => { - let moduleData = (await I_SecurityToken.getModulesByType(TMKEY))[0]; - I_GeneralTransferManager = GeneralTransferManager.at(moduleData); + it("Should initialize the auto attached modules", async () => { + let moduleData = (await stGetter.getModulesByType(TMKEY))[0]; + I_GeneralTransferManager = await GeneralTransferManager.at(moduleData); }); it("Should successfully attach the first STO module to the security token", async () => { let stoId = 0; - _startTime.push(latestTime() + duration.days(2)); - _endTime.push(_startTime[stoId] + duration.days(100)); - _ratePerTier.push([BigNumber(0.05 * 10 ** 18), BigNumber(0.13 * 10 ** 18), BigNumber(0.17 * 10 ** 18)]); // [ 0.05 USD/Token, 0.10 USD/Token, 0.15 USD/Token ] - _ratePerTierDiscountPoly.push([BigNumber(0.05 * 10 ** 18), BigNumber(0.08 * 10 ** 18), BigNumber(0.13 * 10 ** 18)]); // [ 0.05 USD/Token, 0.08 USD/Token, 0.13 USD/Token ] - _tokensPerTierTotal.push([BigNumber(200 * 10 ** 18), BigNumber(500 * 10 ** 18), BigNumber(300 * 10 ** 18)]); // [ 1000 Token, 2000 Token, 1500 Token ] - _tokensPerTierDiscountPoly.push([BigNumber(0), BigNumber(50 * 10 ** 18), BigNumber(300 * 10 ** 18)]); // [ 0 Token, 1000 Token, 1500 Token ] - _nonAccreditedLimitUSD.push(new BigNumber(10 * 10 ** 18)); // 20 USD - _minimumInvestmentUSD.push(new BigNumber(0)); // 1 wei USD + _startTime.push(new BN(currentTime).add(new BN(duration.days(2)))); + _endTime.push(new BN(_startTime[stoId]).add(new BN(currentTime).add(new BN(duration.days(100))))); + _ratePerTier.push([new BN(50).mul(e16), new BN(130).mul(e16), new BN(170).mul(e16)]); // [ 0.05 USD/Token, 0.10 USD/Token, 0.15 USD/Token ] + _ratePerTierDiscountPoly.push([new BN(50).mul(e16), new BN(80).mul(e16), new BN(130).mul(e16)]); // [ 0.05 USD/Token, 0.08 USD/Token, 0.13 USD/Token ] + _tokensPerTierTotal.push([new BN(200).mul(e18), new BN(500).mul(e18), new BN(300).mul(e18)]); // [ 1000 Token, 2000 Token, 1500 Token ] + _tokensPerTierDiscountPoly.push([new BN(0), new BN(50).mul(e18), new BN(300).mul(e18)]); // [ 0 Token, 1000 Token, 1500 Token ] + _nonAccreditedLimitUSD.push(new BN(10).mul(e18)); // 20 USD + _minimumInvestmentUSD.push(new BN(0)); // 1 wei USD _fundRaiseTypes.push([0, 1, 2]); _wallet.push(WALLET); - _reserveWallet.push(RESERVEWALLET); + _treasuryWallet.push(TREASURYWALLET); _usdToken.push(I_DaiToken.address); let config = [ @@ -284,55 +306,55 @@ contract("USDTieredSTO Sim", accounts => { _minimumInvestmentUSD[stoId], _fundRaiseTypes[stoId], _wallet[stoId], - _reserveWallet[stoId], + _treasuryWallet[stoId], [_usdToken[stoId]] ]; let bytesSTO = web3.eth.abi.encodeFunctionCall(functionSignature, config); - let tx = await I_SecurityToken.addModule(I_USDTieredSTOFactory.address, bytesSTO, 0, 0, { from: ISSUER, gasPrice: GAS_PRICE }); + let tx = await I_SecurityToken.addModule(I_USDTieredSTOFactory.address, bytesSTO, new BN(0), new BN(0), false, { from: ISSUER, gasPrice: GAS_PRICE }); console.log(" Gas addModule: ".grey + tx.receipt.gasUsed.toString().grey); assert.equal(tx.logs[2].args._types[0], STOKEY, "USDTieredSTO doesn't get deployed"); assert.equal(web3.utils.hexToString(tx.logs[2].args._name), "USDTieredSTO", "USDTieredSTOFactory module was not added"); - I_USDTieredSTO_Array.push(USDTieredSTO.at(tx.logs[2].args._module)); + I_USDTieredSTO_Array.push(await USDTieredSTO.at(tx.logs[2].args._module)); - assert.equal(await I_USDTieredSTO_Array[stoId].startTime.call(), _startTime[stoId], "Incorrect _startTime in config"); - assert.equal(await I_USDTieredSTO_Array[stoId].endTime.call(), _endTime[stoId], "Incorrect _endTime in config"); + assert.equal((await I_USDTieredSTO_Array[stoId].startTime.call()).toString(), _startTime[stoId].toString(), "Incorrect _startTime in config"); + assert.equal((await I_USDTieredSTO_Array[stoId].endTime.call()).toString(), _endTime[stoId].toString(), "Incorrect _endTime in config"); for (var i = 0; i < _ratePerTier[stoId].length; i++) { assert.equal( - (await I_USDTieredSTO_Array[stoId].tiers.call(i))[0].toNumber(), - _ratePerTier[stoId][i].toNumber(), + (await I_USDTieredSTO_Array[stoId].tiers.call(i))[0].toString(), + _ratePerTier[stoId][i].toString(), "Incorrect _ratePerTier in config" ); assert.equal( - (await I_USDTieredSTO_Array[stoId].tiers.call(i))[1].toNumber(), - _ratePerTierDiscountPoly[stoId][i].toNumber(), + (await I_USDTieredSTO_Array[stoId].tiers.call(i))[1].toString(), + _ratePerTierDiscountPoly[stoId][i].toString(), "Incorrect _ratePerTierDiscountPoly in config" ); assert.equal( - (await I_USDTieredSTO_Array[stoId].tiers.call(i))[2].toNumber(), - _tokensPerTierTotal[stoId][i].toNumber(), + (await I_USDTieredSTO_Array[stoId].tiers.call(i))[2].toString(), + _tokensPerTierTotal[stoId][i].toString(), "Incorrect _tokensPerTierTotal in config" ); assert.equal( - (await I_USDTieredSTO_Array[stoId].tiers.call(i))[3].toNumber(), - _tokensPerTierDiscountPoly[stoId][i].toNumber(), + (await I_USDTieredSTO_Array[stoId].tiers.call(i))[3].toString(), + _tokensPerTierDiscountPoly[stoId][i].toString(), "Incorrect _tokensPerTierDiscountPoly in config" ); } assert.equal( - (await I_USDTieredSTO_Array[stoId].nonAccreditedLimitUSD.call()).toNumber(), - _nonAccreditedLimitUSD[stoId].toNumber(), + (await I_USDTieredSTO_Array[stoId].nonAccreditedLimitUSD.call()).toString(), + _nonAccreditedLimitUSD[stoId].toString(), "Incorrect _nonAccreditedLimitUSD in config" ); assert.equal( - (await I_USDTieredSTO_Array[stoId].minimumInvestmentUSD.call()).toNumber(), - _minimumInvestmentUSD[stoId].toNumber(), + (await I_USDTieredSTO_Array[stoId].minimumInvestmentUSD.call()).toString(), + _minimumInvestmentUSD[stoId].toString(), "Incorrect _minimumInvestmentUSD in config" ); assert.equal(await I_USDTieredSTO_Array[stoId].wallet.call(), _wallet[stoId], "Incorrect _wallet in config"); assert.equal( - await I_USDTieredSTO_Array[stoId].reserveWallet.call(), - _reserveWallet[stoId], + await I_USDTieredSTO_Array[stoId].treasuryWallet.call(), + _treasuryWallet[stoId], "Incorrect _reserveWallet in config" ); assert.equal( @@ -340,7 +362,7 @@ contract("USDTieredSTO Sim", accounts => { _tokensPerTierTotal[stoId].length, "Incorrect number of tiers" ); - assert.equal((await I_USDTieredSTO_Array[stoId].getPermissions()).length, 0, "Incorrect number of permissions"); + assert.equal((await I_USDTieredSTO_Array[stoId].getPermissions()).length, new BN(2), "Incorrect number of permissions"); }); it("Should successfully prepare the STO", async () => { @@ -350,22 +372,25 @@ contract("USDTieredSTO Sim", accounts => { await increaseTime(duration.days(3)); // Whitelist - let fromTime = latestTime() + duration.days(15); - let toTime = latestTime() + duration.days(15); + let fromTime = await latestTime() + duration.days(15); + let toTime = await latestTime() + duration.days(15); let expiryTime = toTime + duration.days(100); let canBuyFromSTO = true; - await I_GeneralTransferManager.modifyWhitelist(ACCREDITED1, fromTime, toTime, expiryTime, canBuyFromSTO, { from: ISSUER }); - await I_GeneralTransferManager.modifyWhitelist(ACCREDITED2, fromTime, toTime, expiryTime, canBuyFromSTO, { from: ISSUER }); - await I_GeneralTransferManager.modifyWhitelist(NONACCREDITED1, fromTime, toTime, expiryTime, canBuyFromSTO, { from: ISSUER }); - await I_GeneralTransferManager.modifyWhitelist(NONACCREDITED2, fromTime, toTime, expiryTime, canBuyFromSTO, { from: ISSUER }); - await I_GeneralTransferManager.modifyWhitelist(NOTAPPROVED, fromTime, toTime, expiryTime, false, { from: ISSUER }); + await I_GeneralTransferManager.modifyKYCData(ACCREDITED1, fromTime, toTime, expiryTime, { from: ISSUER }); + await I_GeneralTransferManager.modifyInvestorFlag(ACCREDITED1, 0, true, { from: ISSUER }); + await I_GeneralTransferManager.modifyKYCData(ACCREDITED2, fromTime, toTime, expiryTime, { from: ISSUER }); + await I_GeneralTransferManager.modifyInvestorFlag(ACCREDITED2, 0, true, { from: ISSUER }); + await I_GeneralTransferManager.modifyKYCData(NONACCREDITED1, fromTime, toTime, expiryTime, { from: ISSUER }); + await I_GeneralTransferManager.modifyKYCData(NONACCREDITED2, fromTime, toTime, expiryTime, { from: ISSUER }); + await I_GeneralTransferManager.modifyKYCData(NOTAPPROVED, fromTime, toTime, expiryTime, { from: ISSUER }); + await I_GeneralTransferManager.modifyInvestorFlag(NOTAPPROVED, 1, true, { from: ISSUER }); await increaseTime(duration.days(3)); // Accreditation - await I_USDTieredSTO_Array[stoId].changeAccredited([ACCREDITED1, ACCREDITED2], [true, true], { from: ISSUER }); - await I_USDTieredSTO_Array[stoId].changeAccredited([NONACCREDITED1, NONACCREDITED2], [false, false], { from: ISSUER }); + await I_GeneralTransferManager.modifyInvestorFlag(ACCREDITED1, 0, true, { from: ISSUER }); + await I_GeneralTransferManager.modifyInvestorFlag(ACCREDITED2, 0, true, { from: ISSUER }); }); }); @@ -384,14 +409,15 @@ contract("USDTieredSTO Sim", accounts => { ---------------------------------------------------------- `); - let totalTokens = new BigNumber(0); + let totalTokens = new BN(0); for (var i = 0; i < _tokensPerTierTotal[stoId].length; i++) { totalTokens = totalTokens.add(_tokensPerTierTotal[stoId][i]); } - console.log("totalTokens: " + totalTokens.div(10 ** 18).toNumber()); - let tokensSold = new BigNumber(0); + let tokensSold = new BN(0); while (true) { - switch (getRandomInt(0, 5)) { + let rn = getRandomInt(0, 5); + let rno = rn.toNumber(); + switch (rno) { case 0: // ACCREDITED1 await invest(ACCREDITED1, true); break; @@ -423,8 +449,8 @@ contract("USDTieredSTO Sim", accounts => { } console.log("Next round"); tokensSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); - console.log("Tokens Sold: " + tokensSold.toString()); - if (tokensSold.gte(totalTokens.sub(1 * 10 ** 18))) { + console.log("Tokens Sold: " + tokensSold.div(e18).toString()); + if (tokensSold.gte(totalTokens.sub(new BN(1)))) { console.log(`${tokensSold} tokens sold, simulation completed successfully!`.green); break; } @@ -437,33 +463,26 @@ contract("USDTieredSTO Sim", accounts => { let USD_to_date = await I_USDTieredSTO_Array[stoId].investorInvestedUSD.call(_investor); USD_remaining = _nonAccreditedLimitUSD[stoId].sub(USD_to_date); } else { - USD_remaining = totalTokens.mul(2); + USD_remaining = totalTokens.mul(new BN(2)); } let log_remaining = USD_remaining; let isPoly = Math.random() >= 0.33; let isDai = Math.random() >= 0.33; - let Token_counter = new BigNumber(getRandomInt(1 * 10 ** 10, 50 * 10 ** 10)).mul(10 ** 8); - let investment_USD = new BigNumber(0); - let investment_ETH = new BigNumber(0); - let investment_POLY = new BigNumber(0); - let investment_DAI = new BigNumber(0); - let investment_Token = new BigNumber(0); + let Token_counter = new BN(getRandomInt(new BN(1).mul(new BN(10).pow(new BN(10))), new BN(5).mul(new BN(10).pow(new BN(11))))).mul(new BN(10).pow(new BN(8))); + let investment_USD = new BN(0); + let investment_ETH = new BN(0); + let investment_POLY = new BN(0); + let investment_DAI = new BN(0); + let investment_Token = new BN(0); let Tokens_total = []; let Tokens_discount = []; for (var i = 0; i < _ratePerTier[stoId].length; i++) { - Tokens_total.push( - (await I_USDTieredSTO_Array[stoId].tiers.call(i))[2].sub( - (await I_USDTieredSTO_Array[stoId].tiers.call(i))[4] - ) - ); - Tokens_discount.push( - (await I_USDTieredSTO_Array[stoId].tiers.call(i))[3].sub( - (await I_USDTieredSTO_Array[stoId].tiers.call(i))[5] - ) - ); + let tierData = await I_USDTieredSTO_Array[stoId].tiers.call(i); + Tokens_total.push(new BN(tierData[2]).sub(tierData[4])); + Tokens_discount.push(new BN(tierData[3]).sub(tierData[5])); } let tier = 0; @@ -476,25 +495,25 @@ contract("USDTieredSTO Sim", accounts => { let USD_overflow; let Token_overflow; - while (Token_counter.gt(0)) { + while (Token_counter.gt(new BN(0))) { if (tier == _ratePerTier[stoId].length) { break; } - if (Tokens_total[tier].gt(0)) { + if (Tokens_total[tier].gt(new BN(0))) { if (isPoly) { // 1. POLY and discount (consume up to cap then move to regular) - if (Tokens_discount[tier].gt(0)) { - Token_Tier = new BigNumber.min([Tokens_total[tier], Tokens_discount[tier], Token_counter]); - USD_Tier = Token_Tier.mul(_ratePerTierDiscountPoly[stoId][tier].div(10 ** 18)); + if (Tokens_discount[tier].gt(new BN(0))) { + Token_Tier = minBN(minBN(Tokens_total[tier], Tokens_discount[tier]), Token_counter); + USD_Tier = Token_Tier.mul(_ratePerTierDiscountPoly[stoId][tier]).div(e18); if (USD_Tier.gte(USD_remaining)) { USD_overflow = USD_Tier.sub(USD_remaining); - Token_overflow = USD_overflow.mul(10 ** 18).div(_ratePerTierDiscountPoly[stoId][tier]); + Token_overflow = USD_overflow.mul(e18).div(_ratePerTierDiscountPoly[stoId][tier]); USD_Tier = USD_Tier.sub(USD_overflow); Token_Tier = Token_Tier.sub(Token_overflow); - Token_counter = new BigNumber(0); + Token_counter = new BN(0); } - POLY_Tier = new BigNumber(USD_Tier.mul(10 ** 18).toFixed(0)); - POLY_Tier = POLY_Tier.div(USDPOLY).toFixed(0); + POLY_Tier = new BN(USD_Tier.mul(e18)); + POLY_Tier = POLY_Tier.div(USDPOLY); USD_remaining = USD_remaining.sub(USD_Tier); Tokens_total[tier] = Tokens_total[tier].sub(Token_Tier); Tokens_discount[tier] = Tokens_discount[tier].sub(Token_Tier); @@ -504,18 +523,18 @@ contract("USDTieredSTO Sim", accounts => { investment_POLY = investment_POLY.add(POLY_Tier); } // 2. POLY and regular (consume up to cap then skip to next tier) - if (Tokens_total[tier].gt(0) && Token_counter.gt(0)) { - Token_Tier = new BigNumber.min([Tokens_total[tier], Token_counter]); - USD_Tier = Token_Tier.mul(_ratePerTier[stoId][tier].div(10 ** 18)); + if (Tokens_total[tier].gt(new BN(0)) && Token_counter.gt(new BN(0))) { + Token_Tier = minBN(Tokens_total[tier], Token_counter); + USD_Tier = Token_Tier.mul(_ratePerTier[stoId][tier]).div(e18); if (USD_Tier.gte(USD_remaining)) { USD_overflow = USD_Tier.sub(USD_remaining); - Token_overflow = USD_overflow.mul(10 ** 18).div(_ratePerTier[stoId][tier]); + Token_overflow = USD_overflow.mul(e18).div(_ratePerTier[stoId][tier]); USD_Tier = USD_Tier.sub(USD_overflow); Token_Tier = Token_Tier.sub(Token_overflow); - Token_counter = new BigNumber(0); + Token_counter = new BN(0); } - POLY_Tier = new BigNumber(USD_Tier.mul(10 ** 18).toFixed(0)); - POLY_Tier = POLY_Tier.div(USDPOLY).toFixed(0); + POLY_Tier = new BN(USD_Tier.mul(e18)); + POLY_Tier = POLY_Tier.div(USDPOLY); USD_remaining = USD_remaining.sub(USD_Tier); Tokens_total[tier] = Tokens_total[tier].sub(Token_Tier); Token_counter = Token_counter.sub(Token_Tier); @@ -525,16 +544,16 @@ contract("USDTieredSTO Sim", accounts => { } } else if (isDai) { // 3. DAI (consume up to cap then skip to next tier) - Token_Tier = new BigNumber.min([Tokens_total[tier], Token_counter]); - USD_Tier = Token_Tier.mul(_ratePerTier[stoId][tier].div(10 ** 18)); + Token_Tier = minBN(Tokens_total[tier], Token_counter); + USD_Tier = Token_Tier.mul(_ratePerTier[stoId][tier]).div(e18); if (USD_Tier.gte(USD_remaining)) { USD_overflow = USD_Tier.sub(USD_remaining); - Token_overflow = USD_overflow.mul(10 ** 18).div(_ratePerTier[stoId][tier]); + Token_overflow = USD_overflow.mul(e18).div(_ratePerTier[stoId][tier]); USD_Tier = USD_Tier.sub(USD_overflow); Token_Tier = Token_Tier.sub(Token_overflow); - Token_counter = new BigNumber(0); + Token_counter = new BN(0); } - DAI_Tier = USD_Tier.toFixed(0); + DAI_Tier = USD_Tier; USD_remaining = USD_remaining.sub(USD_Tier); Tokens_total[tier] = Tokens_total[tier].sub(Token_Tier); Token_counter = Token_counter.sub(Token_Tier); @@ -543,17 +562,16 @@ contract("USDTieredSTO Sim", accounts => { investment_DAI = investment_USD; } else { // 4. ETH (consume up to cap then skip to next tier) - Token_Tier = new BigNumber.min([Tokens_total[tier], Token_counter]); - USD_Tier = Token_Tier.mul(_ratePerTier[stoId][tier].div(10 ** 18)); + Token_Tier = minBN(Tokens_total[tier], Token_counter); + USD_Tier = Token_Tier.mul(_ratePerTier[stoId][tier]).div(e18); if (USD_Tier.gte(USD_remaining)) { USD_overflow = USD_Tier.sub(USD_remaining); - Token_overflow = USD_overflow.mul(10 ** 18).div(_ratePerTier[stoId][tier]); + Token_overflow = USD_overflow.mul(e18).div(_ratePerTier[stoId][tier]); USD_Tier = USD_Tier.sub(USD_overflow); Token_Tier = Token_Tier.sub(Token_overflow); - Token_counter = new BigNumber(0); + Token_counter = new BN(0); } - ETH_Tier = new BigNumber(USD_Tier.mul(10 ** 18).toFixed(0)); - ETH_Tier = ETH_Tier.div(USDETH).toFixed(0); + ETH_Tier = USD_Tier.mul(e18).div(USDETH); USD_remaining = USD_remaining.sub(USD_Tier); Tokens_total[tier] = Tokens_total[tier].sub(Token_Tier); Token_counter = Token_counter.sub(Token_Tier); @@ -562,7 +580,7 @@ contract("USDTieredSTO Sim", accounts => { investment_ETH = investment_ETH.add(ETH_Tier); } } - tier++; + tier = tier + 1; } await processInvestment( @@ -584,9 +602,9 @@ contract("USDTieredSTO Sim", accounts => { async function investFAIL(_investor) { let isPoly = Math.random() >= 0.3; let isDAI = Math.random() >= 0.3; - let investment_POLY = new BigNumber(40 * 10 ** 18); // 10 USD = 40 POLY - let investment_ETH = new BigNumber(0.02 * 10 ** 18); // 10 USD = 0.02 ETH - let investment_DAI = new BigNumber(10 * 10 ** 18); // 10 USD = DAI DAI + let investment_POLY = new BN(40).mul(e18); // 10 USD = 40 POLY + let investment_ETH = new BN(20).mul(e16); // 10 USD = 0.02 ETH + let investment_DAI = new BN(10).mul(e18); // 10 USD = DAI DAI if (isPoly) { await I_PolyToken.getTokens(investment_POLY, _investor); @@ -620,23 +638,23 @@ contract("USDTieredSTO Sim", accounts => { Tokens_discount, tokensSold ) { - investment_Token = new BigNumber(investment_Token.toFixed(0)); - investment_USD = new BigNumber(investment_USD.toFixed(0)); - investment_POLY = new BigNumber(investment_POLY.toFixed(0)); - investment_DAI = new BigNumber(investment_DAI.toFixed(0)); - investment_ETH = new BigNumber(investment_ETH.toFixed(0)); + investment_Token = new BN(investment_Token); + investment_USD = new BN(investment_USD); + investment_POLY = new BN(investment_POLY); + investment_DAI = new BN(investment_DAI); + investment_ETH = new BN(investment_ETH); console.log(` ------------------- New Investment ------------------- Investor: ${_investor} - N-A USD Remaining: ${log_remaining.div(10 ** 18)} + N-A USD Remaining: ${log_remaining} Total Cap Remaining: ${Tokens_total} Discount Cap Remaining: ${Tokens_discount} - Total Tokens Sold: ${tokensSold.div(10 ** 18)} - Token Investment: ${investment_Token.div(10 ** 18)} - USD Investment: ${investment_USD.div(10 ** 18)} - POLY Investment: ${investment_POLY.div(10 ** 18)} - DAI Investment: ${investment_DAI.div(10 ** 18)} - ETH Investment: ${investment_ETH.div(10 ** 18)} + Total Tokens Sold: ${tokensSold} + Token Investment: ${investment_Token} + USD Investment: ${investment_USD} + POLY Investment: ${investment_POLY} + DAI Investment: ${investment_DAI} + ETH Investment: ${investment_ETH} ------------------------------------------------------ `); @@ -652,69 +670,68 @@ contract("USDTieredSTO Sim", accounts => { let init_TokenSupply = await I_SecurityToken.totalSupply(); let init_InvestorTokenBal = await I_SecurityToken.balanceOf(_investor); - let init_InvestorETHBal = new BigNumber(await web3.eth.getBalance(_investor)); + let init_InvestorETHBal = new BN(await web3.eth.getBalance(_investor)); let init_InvestorPOLYBal = await I_PolyToken.balanceOf(_investor); let init_InvestorDAIBal = await I_DaiToken.balanceOf(_investor); let init_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); - let init_STOETHBal = new BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let init_STOETHBal = new BN(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); let init_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); let init_STODAIBal = await I_DaiToken.balanceOf(I_USDTieredSTO_Array[stoId].address); let init_RaisedUSD = await I_USDTieredSTO_Array[stoId].fundsRaisedUSD.call(); let init_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(0); let init_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(1); let init_RaisedDAI = await I_USDTieredSTO_Array[stoId].fundsRaised.call(2); - let init_WalletETHBal = new BigNumber(await web3.eth.getBalance(WALLET)); + let init_WalletETHBal = new BN(await web3.eth.getBalance(WALLET)); let init_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET); let init_WalletDAIBal = await I_DaiToken.balanceOf(WALLET); let tx; - let gasCost = new BigNumber(0); + let gasCost = new BN(0); - if (isPoly && investment_POLY.gt(10)) { + if (isPoly && investment_POLY.gt(new BN(10))) { tx = await I_USDTieredSTO_Array[stoId].buyWithPOLY(_investor, investment_POLY, { from: _investor, gasPrice: GAS_PRICE }); - gasCost = new BigNumber(GAS_PRICE).mul(tx.receipt.gasUsed); + gasCost = new BN(GAS_PRICE).mul(new BN(tx.receipt.gasUsed)); console.log( - `buyWithPOLY: ${investment_Token.div(10 ** 18)} tokens for ${investment_POLY.div(10 ** 18)} POLY by ${_investor}` + `buyWithPOLY: ${investment_Token.div(e18)} tokens for ${investment_POLY.div(e18)} POLY by ${_investor}` .yellow ); - } else if (isDai && investment_DAI.gt(10)) { + } else if (isDai && investment_DAI.gt(new BN(10))) { tx = await I_USDTieredSTO_Array[stoId].buyWithUSD(_investor, investment_DAI, I_DaiToken.address, { from: _investor, gasPrice: GAS_PRICE }); - gasCost = new BigNumber(GAS_PRICE).mul(tx.receipt.gasUsed); + gasCost = new BN(GAS_PRICE).mul(new BN(tx.receipt.gasUsed)); console.log( - `buyWithUSD: ${investment_Token.div(10 ** 18)} tokens for ${investment_DAI.div(10 ** 18)} DAI by ${_investor}` + `buyWithUSD: ${investment_Token.div(e18)} tokens for ${investment_DAI.div(e18)} DAI by ${_investor}` .yellow ); - } else if (investment_ETH.gt(0)) { + } else if (investment_ETH.gt(new BN(0))) { tx = await I_USDTieredSTO_Array[stoId].buyWithETH(_investor, { from: _investor, value: investment_ETH, gasPrice: GAS_PRICE }); - gasCost = new BigNumber(GAS_PRICE).mul(tx.receipt.gasUsed); + gasCost = new BN(GAS_PRICE).mul(new BN(tx.receipt.gasUsed)); console.log( - `buyWithETH: ${investment_Token.div(10 ** 18)} tokens for ${investment_ETH.div(10 ** 18)} ETH by ${_investor}` + `buyWithETH: ${investment_Token.div(e18)} tokens for ${investment_ETH.div(e18)} ETH by ${_investor}` .yellow ); } - console.log(investment_POLY.toNumber()); let final_TokenSupply = await I_SecurityToken.totalSupply(); let final_InvestorTokenBal = await I_SecurityToken.balanceOf(_investor); - let final_InvestorETHBal = new BigNumber(await web3.eth.getBalance(_investor)); + let final_InvestorETHBal = new BN(await web3.eth.getBalance(_investor)); let final_InvestorPOLYBal = await I_PolyToken.balanceOf(_investor); let final_InvestorDAIBal = await I_DaiToken.balanceOf(_investor); let final_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); - let final_STOETHBal = new BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let final_STOETHBal = new BN(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); let final_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); let final_STODAIBal = await I_DaiToken.balanceOf(I_USDTieredSTO_Array[stoId].address); let final_RaisedUSD = await I_USDTieredSTO_Array[stoId].fundsRaisedUSD.call(); let final_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(0); let final_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(1); let final_RaisedDAI = await I_USDTieredSTO_Array[stoId].fundsRaised.call(2); - let final_WalletETHBal = new BigNumber(await web3.eth.getBalance(WALLET)); + let final_WalletETHBal = new BN(await web3.eth.getBalance(WALLET)); let final_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET); let final_WalletDAIBal = await I_DaiToken.balanceOf(WALLET); @@ -722,223 +739,56 @@ contract("USDTieredSTO Sim", accounts => { // console.log('final_TokenSupply: '+final_TokenSupply.div(10**18).toNumber()); if (isPoly) { - assert.closeTo( - final_TokenSupply.toNumber(), - init_TokenSupply.add(investment_Token).toNumber(), - TOLERANCE, - "Token Supply not changed as expected" - ); - assert.closeTo( - final_InvestorTokenBal.toNumber(), - init_InvestorTokenBal.add(investment_Token).toNumber(), - TOLERANCE, - "Investor Token Balance not changed as expected" - ); - assert.closeTo( - final_InvestorETHBal.toNumber(), - init_InvestorETHBal.sub(gasCost).toNumber(), - TOLERANCE, - "Investor ETH Balance not changed as expected" - ); - assert.closeTo( - final_InvestorPOLYBal.toNumber(), - init_InvestorPOLYBal.sub(investment_POLY).toNumber(), - TOLERANCE, - "Investor POLY Balance not changed as expected" - ); - assert.closeTo( - final_STOTokenSold.toNumber(), - init_STOTokenSold.add(investment_Token).toNumber(), - TOLERANCE, - "STO Token Sold not changed as expected" - ); - assert.closeTo( - final_STOETHBal.toNumber(), - init_STOETHBal.toNumber(), - TOLERANCE, - "STO ETH Balance not changed as expected" - ); - assert.closeTo( - final_STOPOLYBal.toNumber(), - init_STOPOLYBal.toNumber(), - TOLERANCE, - "STO POLY Balance not changed as expected" - ); - assert.closeTo( - final_RaisedUSD.toNumber(), - init_RaisedUSD.add(investment_USD).toNumber(), - TOLERANCE, - "Raised USD not changed as expected" - ); - assert.closeTo(final_RaisedETH.toNumber(), init_RaisedETH.toNumber(), TOLERANCE, "Raised ETH not changed as expected"); - assert.closeTo( - final_RaisedPOLY.toNumber(), - init_RaisedPOLY.add(investment_POLY).toNumber(), - TOLERANCE, - "Raised POLY not changed as expected" - ); - assert.closeTo( - final_WalletETHBal.toNumber(), - init_WalletETHBal.toNumber(), - TOLERANCE, - "Wallet ETH Balance not changed as expected" - ); - assert.closeTo( - final_WalletPOLYBal.toNumber(), - init_WalletPOLYBal.add(investment_POLY).toNumber(), - TOLERANCE, - "Wallet POLY Balance not changed as expected" - ); + assertIsNear(final_TokenSupply, init_TokenSupply.add(investment_Token), "Token Supply not changed as expected" ); + assertIsNear(final_InvestorTokenBal, init_InvestorTokenBal.add(investment_Token), "Investor Token Balance not changed as expected" ); + assertIsNear(final_InvestorETHBal, init_InvestorETHBal.sub(gasCost), "Investor ETH Balance not changed as expected" ); + assertIsNear(final_InvestorPOLYBal, init_InvestorPOLYBal.sub(investment_POLY), "Investor POLY Balance not changed as expected" ); + assertIsNear(final_STOTokenSold, init_STOTokenSold.add(investment_Token), "STO Token Sold not changed as expected" ); + assertIsNear(final_STOETHBal, init_STOETHBal, "STO ETH Balance not changed as expected" ); + assertIsNear(final_STOPOLYBal, init_STOPOLYBal, "STO POLY Balance not changed as expected" ); + assertIsNear(final_RaisedUSD, init_RaisedUSD.add(investment_USD), "Raised USD not changed as expected" ); + assertIsNear(final_RaisedETH, init_RaisedETH, "Raised ETH not changed as expected"); + assertIsNear(final_RaisedPOLY, init_RaisedPOLY.add(investment_POLY), "Raised POLY not changed as expected" ); + assertIsNear(final_WalletETHBal, init_WalletETHBal, "Wallet ETH Balance not changed as expected" ); + assertIsNear(final_WalletPOLYBal, init_WalletPOLYBal.add(investment_POLY), "Wallet POLY Balance not changed as expected" ); } else if (isDai) { - assert.closeTo( - final_TokenSupply.toNumber(), - init_TokenSupply.add(investment_Token).toNumber(), - TOLERANCE, - "Token Supply not changed as expected" - ); - assert.closeTo( - final_InvestorTokenBal.toNumber(), - init_InvestorTokenBal.add(investment_Token).toNumber(), - TOLERANCE, - "Investor Token Balance not changed as expected" - ); - assert.closeTo( - final_InvestorETHBal.toNumber(), - init_InvestorETHBal.sub(gasCost).toNumber(), - TOLERANCE, - "Investor ETH Balance not changed as expected" - ); - assert.closeTo( - final_InvestorDAIBal.toNumber(), - init_InvestorDAIBal.sub(investment_DAI).toNumber(), - TOLERANCE, - "Investor DAI Balance not changed as expected" - ); - assert.closeTo( - final_STOTokenSold.toNumber(), - init_STOTokenSold.add(investment_Token).toNumber(), - TOLERANCE, - "STO Token Sold not changed as expected" - ); - assert.closeTo( - final_STOETHBal.toNumber(), - init_STOETHBal.toNumber(), - TOLERANCE, - "STO ETH Balance not changed as expected" - ); - assert.closeTo( - final_STODAIBal.toNumber(), - init_STODAIBal.toNumber(), - TOLERANCE, - "STO DAI Balance not changed as expected" - ); - assert.closeTo( - final_RaisedUSD.toNumber(), - init_RaisedUSD.add(investment_USD).toNumber(), - TOLERANCE, - "Raised USD not changed as expected" - ); - assert.closeTo(final_RaisedETH.toNumber(), init_RaisedETH.toNumber(), TOLERANCE, "Raised ETH not changed as expected"); - assert.closeTo( - final_RaisedDAI.toNumber(), - init_RaisedDAI.add(investment_DAI).toNumber(), - TOLERANCE, - "Raised DAI not changed as expected" - ); - assert.closeTo( - final_WalletETHBal.toNumber(), - init_WalletETHBal.toNumber(), - TOLERANCE, - "Wallet ETH Balance not changed as expected" - ); - assert.closeTo( - final_WalletDAIBal.toNumber(), - init_WalletDAIBal.add(investment_DAI).toNumber(), - TOLERANCE, - "Wallet DAI Balance not changed as expected" - ); + assertIsNear(final_TokenSupply, init_TokenSupply.add(investment_Token), "Token Supply not changed as expected" ); + assertIsNear(final_InvestorTokenBal, init_InvestorTokenBal.add(investment_Token), "Investor Token Balance not changed as expected" ); + assertIsNear(final_InvestorETHBal, init_InvestorETHBal.sub(gasCost), "Investor ETH Balance not changed as expected" ); + assertIsNear(final_InvestorDAIBal, init_InvestorDAIBal.sub(investment_DAI), "Investor DAI Balance not changed as expected" ); + assertIsNear(final_STOTokenSold, init_STOTokenSold.add(investment_Token), "STO Token Sold not changed as expected" ); + assertIsNear(final_STOETHBal, init_STOETHBal, "STO ETH Balance not changed as expected" ); + assertIsNear(final_STODAIBal, init_STODAIBal, "STO DAI Balance not changed as expected" ); + assertIsNear(final_RaisedUSD, init_RaisedUSD.add(investment_USD), "Raised USD not changed as expected" ); + assertIsNear(final_RaisedETH, init_RaisedETH, "Raised ETH not changed as expected"); + assertIsNear(final_RaisedDAI, init_RaisedDAI.add(investment_DAI), "Raised DAI not changed as expected" ); + assertIsNear(final_WalletETHBal, init_WalletETHBal, "Wallet ETH Balance not changed as expected" ); + assertIsNear(final_WalletDAIBal, init_WalletDAIBal.add(investment_DAI), "Wallet DAI Balance not changed as expected" ); } else { - assert.closeTo( - final_TokenSupply.toNumber(), - init_TokenSupply.add(investment_Token).toNumber(), - TOLERANCE, - "Token Supply not changed as expected" - ); - assert.closeTo( - final_InvestorTokenBal.toNumber(), - init_InvestorTokenBal.add(investment_Token).toNumber(), - TOLERANCE, - "Investor Token Balance not changed as expected" - ); - assert.closeTo( - final_InvestorETHBal.toNumber(), - init_InvestorETHBal - .sub(gasCost) - .sub(investment_ETH) - .toNumber(), - TOLERANCE, - "Investor ETH Balance not changed as expected" - ); - assert.closeTo( - final_InvestorPOLYBal.toNumber(), - init_InvestorPOLYBal.toNumber(), - TOLERANCE, - "Investor POLY Balance not changed as expected" - ); - assert.closeTo( - final_STOTokenSold.toNumber(), - init_STOTokenSold.add(investment_Token).toNumber(), - TOLERANCE, - "STO Token Sold not changed as expected" - ); - assert.closeTo( - final_STOETHBal.toNumber(), - init_STOETHBal.toNumber(), - TOLERANCE, - "STO ETH Balance not changed as expected" - ); - assert.closeTo( - final_STOPOLYBal.toNumber(), - init_STOPOLYBal.toNumber(), - TOLERANCE, - "STO POLY Balance not changed as expected" - ); - assert.closeTo( - final_RaisedUSD.toNumber(), - init_RaisedUSD.add(investment_USD).toNumber(), - TOLERANCE, - "Raised USD not changed as expected" - ); - assert.closeTo( - final_RaisedETH.toNumber(), - init_RaisedETH.add(investment_ETH).toNumber(), - TOLERANCE, - "Raised ETH not changed as expected" - ); - assert.closeTo( - final_RaisedPOLY.toNumber(), - init_RaisedPOLY.toNumber(), - TOLERANCE, - "Raised POLY not changed as expected" - ); - assert.closeTo( - final_WalletETHBal.toNumber(), - init_WalletETHBal.add(investment_ETH).toNumber(), - TOLERANCE, - "Wallet ETH Balance not changed as expected" - ); - assert.closeTo( - final_WalletPOLYBal.toNumber(), - init_WalletPOLYBal.toNumber(), - TOLERANCE, - "Wallet POLY Balance not changed as expected" - ); + assertIsNear(final_TokenSupply, init_TokenSupply.add(investment_Token), "Token Supply not changed as expected" ); + assertIsNear(final_InvestorTokenBal, init_InvestorTokenBal.add(investment_Token), "Investor Token Balance not changed as expected" ); + assertIsNear(final_InvestorETHBal, init_InvestorETHBal .sub(gasCost) .sub(investment_ETH) , "Investor ETH Balance not changed as expected" ); + assertIsNear(final_InvestorPOLYBal, init_InvestorPOLYBal, "Investor POLY Balance not changed as expected" ); + assertIsNear(final_STOTokenSold, init_STOTokenSold.add(investment_Token), "STO Token Sold not changed as expected" ); + assertIsNear(final_STOETHBal, init_STOETHBal, "STO ETH Balance not changed as expected" ); + assertIsNear(final_STOPOLYBal, init_STOPOLYBal, "STO POLY Balance not changed as expected" ); + assertIsNear(final_RaisedUSD, init_RaisedUSD.add(investment_USD), "Raised USD not changed as expected" ); + assertIsNear(final_RaisedETH, init_RaisedETH.add(investment_ETH), "Raised ETH not changed as expected" ); + assertIsNear(final_RaisedPOLY, init_RaisedPOLY, "Raised POLY not changed as expected" ); + assertIsNear(final_WalletETHBal, init_WalletETHBal.add(investment_ETH), "Wallet ETH Balance not changed as expected" ); + assertIsNear(final_WalletPOLYBal, init_WalletPOLYBal, "Wallet POLY Balance not changed as expected" ); } } }); }); }); -function near(x, y, message) { - assert.isAtMost(x, y); +function assertIsNear(a, b, reason) { + a = new BN(a); + b = new BN(b); + if (a.gt(b)) { + assert.isBelow(a.sub(b).toNumber(), 4, reason); + } else { + assert.isBelow(b.sub(a).toNumber(), 4, reason); + } } diff --git a/test/r_concurrent_STO.js b/test/r_concurrent_STO.js index 3274e7fb8..4fa5c6fe1 100644 --- a/test/r_concurrent_STO.js +++ b/test/r_concurrent_STO.js @@ -7,8 +7,7 @@ import { deployDummySTOAndVerifyed, deployCappedSTOAndVerifyed, deployPresaleSTOAndVerified - } from "./helpers/createInstances"; - +} from "./helpers/createInstances"; // Import contract ABIs const CappedSTO = artifacts.require("./CappedSTO.sol"); @@ -16,12 +15,13 @@ const DummySTO = artifacts.require("./DummySTO.sol"); const PreSaleSTO = artifacts.require("./PreSaleSTO.sol"); const SecurityToken = artifacts.require("./SecurityToken.sol"); const GeneralTransferManager = artifacts.require("./GeneralTransferManager"); +const STGetter = artifacts.require("./STGetter.sol"); const Web3 = require("web3"); -const BigNumber = require("bignumber.js"); +let BN = Web3.utils.BN; const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); // Hardcoded development port -contract("Concurrent STO", accounts => { +contract("Concurrent STO", async (accounts) => { // Accounts variable declaration let account_polymath; let account_issuer; @@ -45,6 +45,9 @@ contract("Concurrent STO", accounts => { let I_SecurityToken; let I_PolyToken; let I_PolymathRegistry; + let I_STRGetter; + let I_STGetter; + let stGetter; // STO instance declaration let I_CappedSTOFactory; @@ -56,8 +59,9 @@ contract("Concurrent STO", accounts => { let message = "Transaction Should Fail!"; // Initial fees - const initRegFee = web3.utils.toWei("250"); - const STOSetupCost = 200 * Math.pow(10, 18); + const initRegFee = new BN(web3.utils.toWei("1000")); + const STOSetupCost = web3.utils.toHex(200 * Math.pow(10, 18)); + const STOSetupCostPOLY = web3.utils.toHex(800 * Math.pow(10, 18)); // Module keys const transferManagerKey = 2; @@ -69,8 +73,12 @@ contract("Concurrent STO", accounts => { const DummySTOParameters = ["uint256", "uint256", "uint256", "string"]; const PresaleSTOParameters = ["uint256"]; + let currentTime; + const address_zero = "0x0000000000000000000000000000000000000000"; + const one_address = "0x0000000000000000000000000000000000000001"; + before(async () => { - // Accounts setup + currentTime = new BN(await latestTime()); account_polymath = accounts[0]; account_issuer = accounts[1]; account_fundsReceiver = accounts[2]; @@ -92,14 +100,16 @@ contract("Concurrent STO", accounts => { I_STFactory, I_SecurityTokenRegistry, I_SecurityTokenRegistryProxy, - I_STRProxied + I_STRProxied, + I_STRGetter, + I_STGetter ] = instances; // STEP 2: Deploy the STO Factories - [I_CappedSTOFactory] = await deployCappedSTOAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, STOSetupCost); - [I_DummySTOFactory] = await deployDummySTOAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, STOSetupCost); - [I_PreSaleSTOFactory] = await deployPresaleSTOAndVerified(account_polymath, I_MRProxied, I_PolyToken.address, STOSetupCost); + [I_CappedSTOFactory] = await deployCappedSTOAndVerifyed(account_polymath, I_MRProxied, STOSetupCost); + [I_DummySTOFactory] = await deployDummySTOAndVerifyed(account_polymath, I_MRProxied, STOSetupCost); + [I_PreSaleSTOFactory] = await deployPresaleSTOAndVerified(account_polymath, I_MRProxied, STOSetupCost); // Printing all the contract addresses console.log(` @@ -129,7 +139,7 @@ contract("Concurrent STO", accounts => { it("Should register the ticker before the generation of the security token", async () => { await I_PolyToken.getTokens(initRegFee, account_issuer); await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: account_issuer }); - let tx = await I_STRProxied.registerTicker(account_issuer, symbol, name, { from: account_issuer }); + let tx = await I_STRProxied.registerNewTicker(account_issuer, symbol, { from: account_issuer }); assert.equal(tx.logs[0].args._owner, account_issuer); assert.equal(tx.logs[0].args._ticker, symbol); }); @@ -137,31 +147,32 @@ contract("Concurrent STO", accounts => { it("Should generate the new security token with the same symbol as registered above", async () => { await I_PolyToken.getTokens(initRegFee, account_issuer); await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: account_issuer }); - let _blockNo = latestBlock(); - let tx = await I_STRProxied.generateSecurityToken(name, symbol, tokenDetails, false, { from: account_issuer }); - assert.equal(tx.logs[1].args._ticker, symbol, "SecurityToken doesn't get deployed"); - I_SecurityToken = SecurityToken.at(tx.logs[1].args._securityTokenAddress); + let tx = await I_STRProxied.generateNewSecurityToken(name, symbol, tokenDetails, false, account_issuer, 0, { from: account_issuer }); + assert.equal(tx.logs[1].args._ticker, symbol, "SecurityToken doesn't get deployed"); - const log = await promisifyLogWatch(I_SecurityToken.ModuleAdded({ from: _blockNo }), 1); + I_SecurityToken = await SecurityToken.at(tx.logs[1].args._securityTokenAddress); + stGetter = await STGetter.at(I_SecurityToken.address); + assert.equal(await stGetter.getTreasuryWallet.call(), account_issuer, "Incorrect wallet set"); + const log = (await I_SecurityToken.getPastEvents('ModuleAdded', {filter: {transactionHash: tx.transactionHash}}))[0]; // Verify that GeneralTransferManager module get added successfully or not assert.equal(log.args._types[0].toNumber(), transferManagerKey); assert.equal(web3.utils.hexToString(log.args._name), "GeneralTransferManager"); }); - it("Should intialize the auto attached modules", async () => { - let moduleData = (await I_SecurityToken.getModulesByType(transferManagerKey))[0]; - I_GeneralTransferManager = GeneralTransferManager.at(moduleData); + it("Should initialize the auto attached modules", async () => { + let moduleData = (await stGetter.getModulesByType(transferManagerKey))[0]; + I_GeneralTransferManager = await GeneralTransferManager.at(moduleData); }); it("Should whitelist account_investor1", async () => { - let fromTime = latestTime(); - let toTime = latestTime() + duration.days(15); + let fromTime = await latestTime(); + let toTime = await latestTime() + duration.days(15); let expiryTime = toTime + duration.days(100); let canBuyFromSTO = true; - let tx = await I_GeneralTransferManager.modifyWhitelist(account_investor1, fromTime, toTime, expiryTime, canBuyFromSTO, { + let tx = await I_GeneralTransferManager.modifyKYCData(account_investor1, fromTime, toTime, expiryTime, { from: account_issuer, gas: 500000 }); @@ -173,13 +184,13 @@ contract("Concurrent STO", accounts => { describe("Add STO and verify transfer", async () => { it("Should attach STO modules up to the max number, then fail", async () => { const MAX_MODULES = 10; - const startTime = latestTime() + duration.days(1); - const endTime = latestTime() + duration.days(90); - const cap = web3.utils.toWei("10000"); - const rate = web3.utils.toWei("1000"); + const startTime = await latestTime() + duration.days(1); + const endTime = await latestTime() + duration.days(90); + const cap = new BN(web3.utils.toWei("10000")); + const rate = new BN(web3.utils.toWei("1000")); const fundRaiseType = [0]; const budget = 0; - const maxCost = STOSetupCost; + const maxCost = STOSetupCostPOLY; const cappedBytesSig = encodeModuleCall(CappedSTOParameters, [ startTime, endTime, @@ -192,12 +203,12 @@ contract("Concurrent STO", accounts => { const presaleBytesSig = encodeModuleCall(PresaleSTOParameters, [endTime]); for (var STOIndex = 0; STOIndex < MAX_MODULES; STOIndex++) { - await I_PolyToken.getTokens(STOSetupCost, account_issuer); - await I_PolyToken.transfer(I_SecurityToken.address, STOSetupCost, { from: account_issuer }); + await I_PolyToken.getTokens(STOSetupCostPOLY, account_issuer); + await I_PolyToken.transfer(I_SecurityToken.address, STOSetupCostPOLY, { from: account_issuer }); switch (STOIndex % 3) { case 0: // Capped STO - let tx1 = await I_SecurityToken.addModule(I_CappedSTOFactory.address, cappedBytesSig, maxCost, budget, { + let tx1 = await I_SecurityToken.addModule(I_CappedSTOFactory.address, cappedBytesSig, maxCost, budget, false, { from: account_issuer }); assert.equal(tx1.logs[3].args._types[0], stoKey, `Wrong module type added at index ${STOIndex}`); @@ -206,11 +217,11 @@ contract("Concurrent STO", accounts => { "CappedSTO", `Wrong STO module added at index ${STOIndex}` ); - I_STO_Array.push(CappedSTO.at(tx1.logs[3].args._module)); + I_STO_Array.push(await CappedSTO.at(tx1.logs[3].args._module)); break; case 1: // Dummy STO - let tx2 = await I_SecurityToken.addModule(I_DummySTOFactory.address, dummyBytesSig, maxCost, budget, { + let tx2 = await I_SecurityToken.addModule(I_DummySTOFactory.address, dummyBytesSig, maxCost, budget, false, { from: account_issuer }); assert.equal(tx2.logs[3].args._types[0], stoKey, `Wrong module type added at index ${STOIndex}`); @@ -219,11 +230,11 @@ contract("Concurrent STO", accounts => { "DummySTO", `Wrong STO module added at index ${STOIndex}` ); - I_STO_Array.push(DummySTO.at(tx2.logs[3].args._module)); + I_STO_Array.push(await DummySTO.at(tx2.logs[3].args._module)); break; case 2: // Pre Sale STO - let tx3 = await I_SecurityToken.addModule(I_PreSaleSTOFactory.address, presaleBytesSig, maxCost, budget, { + let tx3 = await I_SecurityToken.addModule(I_PreSaleSTOFactory.address, presaleBytesSig, maxCost, budget, false, { from: account_issuer }); assert.equal(tx3.logs[3].args._types[0], stoKey, `Wrong module type added at index ${STOIndex}`); @@ -232,7 +243,7 @@ contract("Concurrent STO", accounts => { "PreSaleSTO", `Wrong STO module added at index ${STOIndex}` ); - I_STO_Array.push(PreSaleSTO.at(tx3.logs[3].args._module)); + I_STO_Array.push(await PreSaleSTO.at(tx3.logs[3].args._module)); break; } } @@ -247,30 +258,34 @@ contract("Concurrent STO", accounts => { // Capped STO ETH await I_STO_Array[STOIndex].buyTokens(account_investor1, { from: account_investor1, - value: web3.utils.toWei("1", "ether") + value: new BN(web3.utils.toWei("1", "ether")) }); assert.equal(web3.utils.fromWei((await I_STO_Array[STOIndex].getRaised.call(0)).toString()), 1); assert.equal(await I_STO_Array[STOIndex].investorCount.call(), 1); break; case 1: // Dummy STO - await I_STO_Array[STOIndex].generateTokens(account_investor1, web3.utils.toWei("1000"), { from: account_issuer }); + await I_STO_Array[STOIndex].generateTokens(account_investor1, new BN(web3.utils.toWei("1000")), { from: account_issuer }); assert.equal(await I_STO_Array[STOIndex].investorCount.call(), 1); assert.equal( - (await I_STO_Array[STOIndex].investors.call(account_investor1)).dividedBy(new BigNumber(10).pow(18)).toNumber(), + (await I_STO_Array[STOIndex].investors.call(account_investor1)) + .div(new BN(10).pow(new BN(18))) + .toNumber(), 1000 ); break; case 2: // Pre Sale STO - await I_STO_Array[STOIndex].allocateTokens(account_investor1, web3.utils.toWei("1000"), web3.utils.toWei("1"), 0, { + await I_STO_Array[STOIndex].allocateTokens(account_investor1, new BN(web3.utils.toWei("1000")), new BN(web3.utils.toWei("1")), new BN(0), { from: account_issuer }); assert.equal(web3.utils.fromWei((await I_STO_Array[STOIndex].getRaised.call(0)).toString()), 1); assert.equal(web3.utils.fromWei((await I_STO_Array[STOIndex].getRaised.call(1)).toString()), 0); assert.equal(await I_STO_Array[STOIndex].investorCount.call(), 1); assert.equal( - (await I_STO_Array[STOIndex].investors.call(account_investor1)).dividedBy(new BigNumber(10).pow(18)).toNumber(), + (await I_STO_Array[STOIndex].investors.call(account_investor1)) + .div(new BN(10).pow(new BN(18))) + .toNumber(), 1000 ); break; diff --git a/test/s_v130_to_v140_upgrade.js b/test/s_v130_to_v140_upgrade.js deleted file mode 100644 index 014b44ac2..000000000 --- a/test/s_v130_to_v140_upgrade.js +++ /dev/null @@ -1,479 +0,0 @@ -const Web3 = require("web3"); -const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); // Hardcoded development port -const BigNumber = require("bignumber.js"); - -import latestTime from "./helpers/latestTime"; -import { duration } from "./helpers/utils"; -import { encodeProxyCall, encodeModuleCall } from "./helpers/encodeCall"; -import { setUpPolymathNetwork, deployCappedSTOAndVerifyed, deployGPMAndVerifyed } from "./helpers/createInstances"; - -const USDTieredSTOProxyFactory = artifacts.require("./USDTieredSTOProxyFactory.sol"); -const USDTieredSTOFactory = artifacts.require("./USDTieredSTOFactory.sol"); -const CappedSTOFactory = artifacts.require("./CappedSTOFactory.sol"); -const USDTieredSTO = artifacts.require("./USDTieredSTO.sol"); -const CappedSTO = artifacts.require("./CappedSTO.sol"); -const PolyOracle = artifacts.require("./PolyOracle.sol"); -const ETHOracle = artifacts.require("./MakerDAOOracle.sol"); -const SecurityToken = artifacts.require("./SecurityToken.sol"); -const PolyTokenFaucet = artifacts.require("./PolyTokenFaucet.sol"); -const ManualApprovalTransferManagerFactory = artifacts.require("./ManualApprovalTransferManagerFactory.sol"); - -contract("Upgrade from v1.3.0 to v1.4.0", accounts => { - // Accounts Variable declaration - let POLYMATH; - let ISSUER1; - let ISSUER2; - let ISSUER3; - let MULTISIG; - - //const GAS_PRICE = 10000000000; // 10 GWEI - - let tx; - - // Initial fee for ticker registry and security token registry - const REGFEE = web3.utils.toWei("250"); - const STOSetupCost = 0; - const address_zero = "0x0000000000000000000000000000000000000000"; - - // Module key - const STOKEY = 3; - const TMKEY = 2; - - // SecurityToken 1 Details - const symbol1 = "TOK1"; - const name1 = "TOK1 Token"; - const tokenDetails1 = "This is equity type of issuance"; - - //SecurityToken 2 Details - const symbol2 = "TOK2"; - const name2 = "TOK2 Token"; - const tokenDetails2 = "This is equity type of issuance"; - - //SecurityToken 3 Details - const symbol3 = "TOK3"; - const name3 = "TOK3 Token"; - const tokenDetails3 = "This is equity type of issuance"; - - // Contract Instance Declaration - let I_PolymathRegistry; - let I_PolyToken; - let I_DaiToken; - let I_ModuleRegistry; - let I_ModuleRegistryProxy; - let I_GeneralTransferManagerFactory; - let I_GeneralPermissionManagerFactory; - let I_SecurityTokenRegistryProxy; - let I_FeatureRegistry; - let I_STFactory; - let I_MRProxied; - let I_STRProxied; - let I_STRProxiedNew; - - let I_SecurityTokenRegistry; - //let I_UpgradedSecurityTokenRegistry - - let I_SecurityToken1; - let I_SecurityToken2; - //let I_SecurityToken3; - - let I_USDTieredSTOFactory; - let I_USDTieredSTOProxyFactory; - let I_USDOracle; - let I_POLYOracle; - let I_USDTieredSTO; - - let I_CappedSTOFactory; - let I_UpgradedCappedSTOFactory; - let I_CappedSTO; - let I_ManualApprovalTransferManagerFactory; - - const STOParameters = ["uint256", "uint256", "uint256", "uint256", "uint8[]", "address"]; - // Prepare polymath network status - before(async () => { - // Accounts setup - POLYMATH = accounts[0]; - ISSUER1 = accounts[1]; - ISSUER2 = accounts[2]; - ISSUER3 = accounts[3]; - MULTISIG = accounts[4]; - - I_DaiToken = await PolyTokenFaucet.new({ from: POLYMATH }); - - // ----------- POLYMATH NETWORK Configuration ------------ - - let instances = await setUpPolymathNetwork(POLYMATH, ISSUER1); - - [ - I_PolymathRegistry, - I_PolyToken, - I_FeatureRegistry, - I_ModuleRegistry, - I_ModuleRegistryProxy, - I_MRProxied, - I_GeneralTransferManagerFactory, - I_STFactory, - I_SecurityTokenRegistry, - I_SecurityTokenRegistryProxy, - I_STRProxied - ] = instances; - - // STEP 4: Deploy the GeneralDelegateManagerFactory - [I_GeneralPermissionManagerFactory] = await deployGPMAndVerifyed(POLYMATH, I_MRProxied, I_PolyToken.address, 0); - - // STEP 5: Deploy the CappedSTOFactory - [I_CappedSTOFactory] = await deployCappedSTOAndVerifyed(POLYMATH, I_MRProxied, I_PolyToken.address, STOSetupCost); - - // Step 12: Mint tokens to ISSUERs - await I_PolyToken.getTokens(REGFEE * 2, ISSUER1); - await I_PolyToken.getTokens(REGFEE * 2, ISSUER2); - await I_PolyToken.getTokens(REGFEE * 2, ISSUER3); - - // Step 13: Register tokens - // (A) : TOK1 - await I_PolyToken.approve(I_STRProxied.address, REGFEE, { from: ISSUER1 }); - tx = await I_STRProxied.registerTicker(ISSUER1, symbol1, name1, { from: ISSUER1 }); - assert.equal(tx.logs[0].args._owner, ISSUER1); - assert.equal(tx.logs[0].args._ticker, symbol1); - - // (B) : TOK2 - await I_PolyToken.approve(I_STRProxied.address, REGFEE, { from: ISSUER2 }); - tx = await I_STRProxied.registerTicker(ISSUER2, symbol2, name2, { from: ISSUER2 }); - assert.equal(tx.logs[0].args._owner, ISSUER2); - assert.equal(tx.logs[0].args._ticker, symbol2); - - // (C) : TOK3 - await I_PolyToken.approve(I_STRProxied.address, REGFEE, { from: ISSUER3 }); - tx = await I_STRProxied.registerTicker(ISSUER3, symbol3, name3, { from: ISSUER3 }); - assert.equal(tx.logs[0].args._owner, ISSUER3); - assert.equal(tx.logs[0].args._ticker, symbol3); - - // Step 14: Deploy tokens - // (A) : TOK1 - await I_PolyToken.approve(I_STRProxied.address, REGFEE, { from: ISSUER1 }); - let tx = await I_STRProxied.generateSecurityToken(name1, symbol1, tokenDetails1, false, { from: ISSUER1 }); - assert.equal(tx.logs[1].args._ticker, symbol1, "SecurityToken doesn't get deployed"); - I_SecurityToken1 = SecurityToken.at(tx.logs[1].args._securityTokenAddress); - - // (B) : TOK2 - await I_PolyToken.approve(I_STRProxied.address, REGFEE, { from: ISSUER2 }); - tx = await I_STRProxied.generateSecurityToken(name2, symbol2, tokenDetails2, false, { from: ISSUER2 }); - assert.equal(tx.logs[1].args._ticker, symbol2, "SecurityToken doesn't get deployed"); - I_SecurityToken2 = SecurityToken.at(tx.logs[1].args._securityTokenAddress); - - // Printing all the contract addresses - console.log(` - --------------------- Polymath Network Smart Contracts: --------------------- - PolymathRegistry: ${I_PolymathRegistry.address} - SecurityTokenRegistryProxy: ${I_SecurityTokenRegistryProxy.address} - SecurityTokenRegistry: ${I_SecurityTokenRegistry.address} - ModuleRegistryProxy: ${I_ModuleRegistryProxy.address} - ModuleRegistry: ${I_ModuleRegistry.address} - FeatureRegistry: ${I_FeatureRegistry.address} - - STFactory: ${I_STFactory.address} - GeneralTransferManagerFactory: ${I_GeneralTransferManagerFactory.address} - GeneralPermissionManagerFactory: ${I_GeneralPermissionManagerFactory.address} - - SecurityToken TOK1: ${I_SecurityToken1.address} - SecurityToken TOK2: ${I_SecurityToken2.address} - ----------------------------------------------------------------------------- - `); - }); - - describe("USDTieredSTOFactory deploy", async () => { - // Step 1: Deploy Oracles - // 1a - Deploy POLY Oracle - it("Should successfully deploy POLY Oracle and register on PolymathRegistry", async () => { - I_POLYOracle = await PolyOracle.new({ from: POLYMATH, value: web3.utils.toWei("1") }); - console.log(I_POLYOracle.address); - assert.notEqual( - I_POLYOracle.address.valueOf(), - address_zero, - "POLYOracle contract was not deployed" - ); - tx = await I_PolymathRegistry.changeAddress("PolyUsdOracle", I_POLYOracle.address, { from: POLYMATH }); - assert.equal(tx.logs[0].args._nameKey, "PolyUsdOracle"); - assert.equal(tx.logs[0].args._newAddress, I_POLYOracle.address); - }); - // 1b - Deploy ETH Oracle - it("Should successfully deploy ETH Oracle and register on PolymathRegistry", async () => { - I_USDOracle = await ETHOracle.new( - "0x216d678c14be600cb88338e763bb57755ca2b1cf", - address_zero, - "ETH", - { from: POLYMATH } - ); - assert.notEqual( - I_USDOracle.address.valueOf(), - address_zero, - "USDOracle contract was not deployed" - ); - tx = await I_PolymathRegistry.changeAddress("EthUsdOracle", I_USDOracle.address, { from: POLYMATH }); - assert.equal(tx.logs[0].args._nameKey, "EthUsdOracle"); - assert.equal(tx.logs[0].args._newAddress, I_USDOracle.address); - }); - }); - - describe("USDTieredSTOFactory deploy", async () => { - // Step 1: Deploy USDTieredSTOFactory\ - it("Should successfully deploy USDTieredSTOFactory", async () => { - I_USDTieredSTOProxyFactory = await USDTieredSTOProxyFactory.new(); - I_USDTieredSTOFactory = await USDTieredSTOFactory.new( - I_PolyToken.address, - STOSetupCost, - 0, - 0, - I_USDTieredSTOProxyFactory.address, - { from: POLYMATH } - ); - assert.notEqual( - I_USDTieredSTOFactory.address.valueOf(), - address_zero, - "USDTieredSTOFactory contract was not deployed" - ); - let setupCost = await I_USDTieredSTOFactory.setupCost({ from: POLYMATH }); - assert.equal(setupCost, STOSetupCost); - }); - // Step 2: Register and verify - it("Should successfully register and verify USDTieredSTOFactory contract", async () => { - let tx = await I_MRProxied.registerModule(I_USDTieredSTOFactory.address, { from: POLYMATH }); - assert.equal(tx.logs[0].args._moduleFactory, I_USDTieredSTOFactory.address); - tx = await I_MRProxied.verifyModule(I_USDTieredSTOFactory.address, true, { from: POLYMATH }); - assert.equal(tx.logs[0].args._moduleFactory, I_USDTieredSTOFactory.address); - assert.isTrue(tx.logs[0].args._verified); - }); - }); - - describe("CappedSTOFactory deploy", async () => { - // Step 1: Deploy new CappedSTOFactory - it("Should successfully deploy CappedSTOFactory", async () => { - I_UpgradedCappedSTOFactory = await CappedSTOFactory.new(I_PolyToken.address, STOSetupCost, 0, 0, { from: POLYMATH }); - assert.notEqual( - I_UpgradedCappedSTOFactory.address.valueOf(), - address_zero, - "CappedSTOFactory contract was not deployed" - ); - let setupCost = await I_UpgradedCappedSTOFactory.setupCost({ from: POLYMATH }); - assert.equal(setupCost, STOSetupCost); - }); - - // Step 2: Register and verify - it("Should successfully register and verify new CappedSTOFactory contract", async () => { - let tx = await I_MRProxied.registerModule(I_UpgradedCappedSTOFactory.address, { from: POLYMATH }); - assert.equal(tx.logs[0].args._moduleFactory, I_UpgradedCappedSTOFactory.address); - tx = await I_MRProxied.verifyModule(I_UpgradedCappedSTOFactory.address, true, { from: POLYMATH }); - assert.equal(tx.logs[0].args._moduleFactory, I_UpgradedCappedSTOFactory.address); - assert.isTrue(tx.logs[0].args._verified); - }); - - // Step 3: Unverify old CappedSTOFactory - it("Should successfully unverify old CappedSTOFactory contract", async () => { - let tx = await I_MRProxied.verifyModule(I_CappedSTOFactory.address, false, { from: POLYMATH }); - assert.equal(tx.logs[0].args._moduleFactory, I_CappedSTOFactory.address); - assert.isFalse(tx.logs[0].args._verified); - }); - }); - - describe("ManualApprovalTransferManagerFactory deploy", async () => { - // Step 1: Deploy new ManualApprovalTransferManager - it("Should successfully deploy ManualApprovalTransferManagerFactory", async () => { - I_ManualApprovalTransferManagerFactory = await ManualApprovalTransferManagerFactory.new(I_PolyToken.address, 0, 0, 0, { - from: POLYMATH - }); - assert.notEqual( - I_ManualApprovalTransferManagerFactory.address.valueOf(), - address_zero, - "ManualApprovalTransferManagerFactory contract was not deployed" - ); - }); - - // Step 2: Register and verify - it("Should successfully register and verify new ManualApprovalTransferManagerFactory contract", async () => { - let tx = await I_MRProxied.registerModule(I_ManualApprovalTransferManagerFactory.address, { from: POLYMATH }); - assert.equal(tx.logs[0].args._moduleFactory, I_ManualApprovalTransferManagerFactory.address); - tx = await I_MRProxied.verifyModule(I_ManualApprovalTransferManagerFactory.address, true, { from: POLYMATH }); - assert.equal(tx.logs[0].args._moduleFactory, I_ManualApprovalTransferManagerFactory.address); - assert.isTrue(tx.logs[0].args._verified); - }); - }); - - describe("Change ownerships", async () => { - /* - // Step 1: SecurityTokenRegistry - it("Should successfully change ownership of new SecurityTokenRegistry contract", async() => { - let tx = await I_STRProxiedNew.transferOwnership(MULTISIG, { from: POLYMATH }); - assert.equal(tx.logs[0].args.previousOwner, POLYMATH, "Previous owner was not Polymath account"); - assert.equal(tx.logs[0].args.newOwner, MULTISIG, "New owner is not Multisig account"); - }); - */ - - // Step 2: Oracles - it("Should successfully change ownership of both Oracles contract", async () => { - let tx = await I_USDOracle.transferOwnership(MULTISIG, { from: POLYMATH }); - assert.equal(tx.logs[0].args.previousOwner, POLYMATH, "Previous ETH Oracle owner was not Polymath account"); - assert.equal(tx.logs[0].args.newOwner, MULTISIG, "New ETH Oracle owner is not Multisig account"); - - tx = await I_POLYOracle.transferOwnership(MULTISIG, { from: POLYMATH }); - assert.equal(tx.logs[0].args.previousOwner, POLYMATH, "Previous POLY Oracle owner was not Polymath account"); - assert.equal(tx.logs[0].args.newOwner, MULTISIG, "New POLY Oracle owner is not Multisig account"); - }); - - // Step 3: USDTieredSTOFactory - it("Should successfully change ownership of USDTieredSTOFactory contract", async () => { - let tx = await I_USDTieredSTOFactory.transferOwnership(MULTISIG, { from: POLYMATH }); - assert.equal(tx.logs[0].args.previousOwner, POLYMATH, "Previous USDTieredSTOFactory owner was not Polymath account"); - assert.equal(tx.logs[0].args.newOwner, MULTISIG, "New USDTieredSTOFactory owner is not Multisig account"); - }); - - // Step 3: CappedSTOFactory - it("Should successfully change ownership of CappedSTOFactory contract", async () => { - let tx = await I_UpgradedCappedSTOFactory.transferOwnership(MULTISIG, { from: POLYMATH }); - assert.equal(tx.logs[0].args.previousOwner, POLYMATH, "Previous USDTieredSTOFactory owner was not Polymath account"); - assert.equal(tx.logs[0].args.newOwner, MULTISIG, "New USDTieredSTOFactory owner is not Multisig account"); - }); - - // Step 4: ManualApprovalTransferManagerFactory - it("Should successfully change ownership of ManualApprovalTransferManagerFactory contract", async () => { - let tx = await I_ManualApprovalTransferManagerFactory.transferOwnership(MULTISIG, { from: POLYMATH }); - assert.equal( - tx.logs[0].args.previousOwner, - POLYMATH, - "Previous ManualApprovalTransferManagerFactory owner was not Polymath account" - ); - assert.equal(tx.logs[0].args.newOwner, MULTISIG, "New ManualApprovalTransferManagerFactory owner is not Multisig account"); - }); - }); - - describe("Polymath network status post migration", async () => { - // Launch STO for TOK1 - it("Should successfully launch USDTieredSTO for first security token", async () => { - let _startTime = latestTime() + duration.days(1); - let _endTime = _startTime + duration.days(180); - let _ratePerTier = [BigNumber(0.1).mul(10 ** 18), BigNumber(0.15).mul(10 ** 18), BigNumber(0.2).mul(10 ** 18)]; - let _ratePerTierDiscountPoly = [BigNumber(0), BigNumber(0), BigNumber(0)]; - let _tokensPerTierTotal = [BigNumber(100).mul(10 ** 18), BigNumber(200).mul(10 ** 18), BigNumber(300).mul(10 ** 18)]; - let _tokensPerTierDiscountPoly = [BigNumber(0), BigNumber(0), BigNumber(0)]; - let _nonAccreditedLimitUSD = new BigNumber(100).mul(10 ** 18); - let _minimumInvestmentUSD = new BigNumber(5).mul(10 ** 18); - let _fundRaiseTypes = [0, 1]; - let _wallet = ISSUER1; - let _reserveWallet = ISSUER1; - let _usdToken = I_DaiToken.address; - - let config = [ - _startTime, - _endTime, - _ratePerTier, - _ratePerTierDiscountPoly, - _tokensPerTierTotal, - _tokensPerTierDiscountPoly, - _nonAccreditedLimitUSD, - _minimumInvestmentUSD, - _fundRaiseTypes, - _wallet, - _reserveWallet, - _usdToken - ]; - - let functionSignature = { - name: "configure", - type: "function", - inputs: [ - { - type: "uint256", - name: "_startTime" - }, - { - type: "uint256", - name: "_endTime" - }, - { - type: "uint256[]", - name: "_ratePerTier" - }, - { - type: "uint256[]", - name: "_ratePerTierDiscountPoly" - }, - { - type: "uint256[]", - name: "_tokensPerTier" - }, - { - type: "uint256[]", - name: "_tokensPerTierDiscountPoly" - }, - { - type: "uint256", - name: "_nonAccreditedLimitUSD" - }, - { - type: "uint256", - name: "_minimumInvestmentUSD" - }, - { - type: "uint8[]", - name: "_fundRaiseTypes" - }, - { - type: "address", - name: "_wallet" - }, - { - type: "address", - name: "_reserveWallet" - }, - { - type: "address", - name: "_usdToken" - } - ] - }; - - let bytesSTO = web3.eth.abi.encodeFunctionCall(functionSignature, config); - - let tx = await I_SecurityToken1.addModule(I_USDTieredSTOFactory.address, bytesSTO, 0, 0, { from: ISSUER1 }); - assert.equal(tx.logs[2].args._types[0], STOKEY, "USDTieredSTO doesn't get deployed"); - assert.equal(web3.utils.hexToString(tx.logs[2].args._name), "USDTieredSTO", "USDTieredSTOFactory module was not added"); - I_USDTieredSTO = USDTieredSTO.at(tx.logs[2].args._module); - }); - - /* - // Deploy TOK3 - it("Should successfully deploy third security token", async() => { - await I_PolyToken.approve(I_STRProxiedNew.address, REGFEE, { from: ISSUER3}); - tx = await I_STRProxiedNew.generateSecurityToken(name3, symbol3, tokenDetails3, false, { from: ISSUER3 }); - assert.equal(tx.logs[1].args._ticker, symbol3, "SecurityToken doesn't get deployed"); - I_SecurityToken3 = SecurityToken.at(tx.logs[1].args._securityTokenAddress); - }); - */ - - // Launch NewCappedSTO for TOK2 - it("Should successfully launch CappedSTO for third security token", async () => { - let startTime = latestTime() + duration.days(1); - let endTime = startTime + duration.days(30); - let cap = web3.utils.toWei("500000"); - let rate = 1000; - let fundRaiseType = 0; - let fundsReceiver = ISSUER3; - - let bytesSTO = encodeModuleCall(STOParameters, [startTime, endTime, cap, rate, [fundRaiseType], fundsReceiver]); - - let tx = await I_SecurityToken2.addModule(I_UpgradedCappedSTOFactory.address, bytesSTO, 0, 0, { from: ISSUER2 }); - assert.equal(tx.logs[2].args._types[0], STOKEY, "CappedSTO doesn't get deployed"); - assert.equal(web3.utils.hexToString(tx.logs[2].args._name), "CappedSTO", "CappedSTOFactory module was not added"); - }); - - // Attach ManualApprovalTransferManager module for TOK2 - it("Should successfully attach the ManualApprovalTransferManagerFactory with the second token", async () => { - const tx = await I_SecurityToken2.addModule(I_ManualApprovalTransferManagerFactory.address, "", 0, 0, { from: ISSUER2 }); - assert.equal(tx.logs[2].args._types[0].toNumber(), TMKEY, "ManualApprovalTransferManagerFactory doesn't get deployed"); - assert.equal( - web3.utils.toUtf8(tx.logs[2].args._name), - "ManualApprovalTransferManager", - "ManualApprovalTransferManagerFactory module was not added" - ); - I_ManualApprovalTransferManagerFactory = ManualApprovalTransferManagerFactory.at(tx.logs[2].args._module); - }); - }); -}); diff --git a/test/t_security_token_registry_proxy.js b/test/t_security_token_registry_proxy.js index 30bacc759..68069d0f5 100644 --- a/test/t_security_token_registry_proxy.js +++ b/test/t_security_token_registry_proxy.js @@ -1,20 +1,24 @@ import { duration, promisifyLogWatch, latestBlock } from "./helpers/utils"; -import { encodeProxyCall } from "./helpers/encodeCall"; +import { encodeProxyCall, encodeCall } from "./helpers/encodeCall"; +import { takeSnapshot, increaseTime, revertToSnapshot } from "./helpers/time"; import { catchRevert } from "./helpers/exceptions"; import { setUpPolymathNetwork } from "./helpers/createInstances"; const SecurityTokenRegistry = artifacts.require("./SecurityTokenRegistry.sol"); const SecurityTokenRegistryProxy = artifacts.require("./SecurityTokenRegistryProxy.sol"); const SecurityTokenRegistryMock = artifacts.require("./SecurityTokenRegistryMock.sol"); +const MockSTRGetter = artifacts.require("./MockSTRGetter.sol"); const OwnedUpgradeabilityProxy = artifacts.require("./OwnedUpgradeabilityProxy.sol"); const STFactory = artifacts.require("./STFactory.sol"); const SecurityToken = artifacts.require("./SecurityToken.sol"); +const STRGetter = artifacts.require("./STRGetter.sol"); +const STGetter = artifacts.require("./STGetter.sol"); const Web3 = require("web3"); -const BigNumber = require("bignumber.js"); +let BN = Web3.utils.BN; const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); // Hardcoded development port -contract("SecurityTokenRegistryProxy", accounts => { +contract("SecurityTokenRegistryProxy", async (accounts) => { let I_SecurityTokenRegistry; let I_SecurityTokenRegistryProxy; let I_GeneralTransferManagerFactory; @@ -28,6 +32,10 @@ contract("SecurityTokenRegistryProxy", accounts => { let I_SecurityToken; let I_ModuleRegistry; let I_FeatureRegistry; + let I_STRGetter; + let I_Getter; + let I_STGetter; + let stGetter; let account_polymath; let account_temp; @@ -35,7 +43,8 @@ contract("SecurityTokenRegistryProxy", accounts => { let account_polymath_new; // Initial fee for ticker registry and security token registry - const initRegFee = web3.utils.toWei("250"); + const initRegFee = new BN(web3.utils.toWei("250")); + const initRegFeePOLY = new BN(web3.utils.toWei("1000")); const version = "1.0.0"; const message = "Transaction Should Fail!"; @@ -46,7 +55,10 @@ contract("SecurityTokenRegistryProxy", accounts => { const decimals = 18; const transferManagerKey = 2; - const STRProxyParameters = ["address", "address", "uint256", "uint256", "address", "address"]; + + const address_zero = "0x0000000000000000000000000000000000000000"; + const one_address = "0x0000000000000000000000000000000000000001"; + const STRProxyParameters = ["address", "uint256", "uint256", "address", "address"]; async function readStorage(contractAddress, slot) { return await web3.eth.getStorageAt(contractAddress, slot); @@ -72,14 +84,16 @@ contract("SecurityTokenRegistryProxy", accounts => { I_STFactory, I_SecurityTokenRegistry, I_SecurityTokenRegistryProxy, - I_STRProxied + I_STRProxied, + I_STRGetter, + I_STGetter ] = instances; I_SecurityTokenRegistryProxy = await SecurityTokenRegistryProxy.new({ from: account_polymath }); await I_PolymathRegistry.changeAddress("SecurityTokenRegistry", I_SecurityTokenRegistryProxy.address, { from: account_polymath }); await I_MRProxied.updateFromRegistry({ from: account_polymath }); - + // Printing all the contract addresses console.log(` --------------------- Polymath Network Smart Contracts: --------------------- @@ -100,19 +114,19 @@ contract("SecurityTokenRegistryProxy", accounts => { // __upgradeabilityOwner -- index 13 it("Should attach the implementation and version", async () => { + I_STRGetter = await STRGetter.new({from: account_polymath}); let bytesProxy = encodeProxyCall(STRProxyParameters, [ I_PolymathRegistry.address, - I_STFactory.address, initRegFee, initRegFee, - I_PolyToken.address, - account_polymath + account_polymath, + I_STRGetter.address ]); await I_SecurityTokenRegistryProxy.upgradeToAndCall("1.0.0", I_SecurityTokenRegistry.address, bytesProxy, { from: account_polymath }); - let c = OwnedUpgradeabilityProxy.at(I_SecurityTokenRegistryProxy.address); - assert.equal(await readStorage(c.address, 12), I_SecurityTokenRegistry.address); + let c = await OwnedUpgradeabilityProxy.at(I_SecurityTokenRegistryProxy.address); + assert.equal(await readStorage(c.address, 12), I_SecurityTokenRegistry.address.toLowerCase()); assert.equal( web3.utils .toAscii(await readStorage(c.address, 11)) @@ -121,6 +135,9 @@ contract("SecurityTokenRegistryProxy", accounts => { "1.0.0" ); I_STRProxied = await SecurityTokenRegistry.at(I_SecurityTokenRegistryProxy.address); + I_STRGetter = await STRGetter.at(I_SecurityTokenRegistryProxy.address); + await I_STRProxied.setProtocolFactory(I_STFactory.address, 3, 0, 0); + await I_STRProxied.setLatestVersion(3, 0, 0); }); it("Verify the initialize data", async () => { @@ -130,32 +147,68 @@ contract("SecurityTokenRegistryProxy", accounts => { "Should equal to 60 days" ); assert.equal( - (await I_STRProxied.getUintValue.call(web3.utils.soliditySha3("tickerRegFee"))).toNumber(), + await I_STRProxied.getUintValue.call(web3.utils.soliditySha3("tickerRegFee")), web3.utils.toWei("250") ); }); + + it("Upgrade the proxy again and change getter", async () => { + let snapId = await takeSnapshot(); + const I_MockSTRGetter = await MockSTRGetter.new({from: account_polymath}); + const I_MockSecurityTokenRegistry = await SecurityTokenRegistry.new({ from: account_polymath }); + const bytesProxy = encodeCall("setGetterRegistry", ["address"], [I_MockSTRGetter.address]); + console.log("Getter: " + I_MockSTRGetter.address); + console.log("Registry: " + I_MockSecurityTokenRegistry.address); + console.log("STRProxy: " + I_SecurityTokenRegistryProxy.address); + + await I_SecurityTokenRegistryProxy.upgradeToAndCall("2.0.0", I_MockSecurityTokenRegistry.address, bytesProxy, { + from: account_polymath + }); + + let c = await OwnedUpgradeabilityProxy.at(I_SecurityTokenRegistryProxy.address); + assert.equal(await readStorage(c.address, 12), I_MockSecurityTokenRegistry.address.toLowerCase()); + assert.equal( + web3.utils + .toAscii(await readStorage(c.address, 11)) + .replace(/\u0000/g, "") + .replace(/\n/, ""), + "2.0.0" + ); + + const I_MockSecurityTokenRegistryProxy = await SecurityTokenRegistry.at(I_SecurityTokenRegistryProxy.address); + const I_MockSTRGetterProxy = await MockSTRGetter.at(I_SecurityTokenRegistryProxy.address); + await I_MockSecurityTokenRegistryProxy.setProtocolFactory(I_STFactory.address, 3, 1, 0); + await I_MockSecurityTokenRegistryProxy.setLatestVersion(3, 1, 0); + let newValue = await I_MockSTRGetterProxy.newFunction.call(); + assert.equal(newValue.toNumber(), 99); + //assert.isTrue(false); + await revertToSnapshot(snapId); + }); + + }); describe("Feed some data in storage", async () => { it("Register the ticker", async () => { - await I_PolyToken.getTokens(web3.utils.toWei("1000"), token_owner); - await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); - let tx = await I_STRProxied.registerTicker(token_owner, symbol, name, { from: token_owner }); + await I_PolyToken.getTokens(new BN(web3.utils.toWei("8000")), token_owner); + await I_PolyToken.approve(I_STRProxied.address, initRegFeePOLY, { from: token_owner }); + let tx = await I_STRProxied.registerNewTicker(token_owner, symbol, { from: token_owner }); assert.equal(tx.logs[0].args._owner, token_owner, "Owner should be the same as registered with the ticker"); assert.equal(tx.logs[0].args._ticker, symbol, "Same as the symbol registered in the registerTicker function call"); }); it("Should generate the new security token with the same symbol as registered above", async () => { - await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); - let _blockNo = latestBlock(); - let tx = await I_STRProxied.generateSecurityToken(name, symbol, tokenDetails, false, { from: token_owner }); + await I_PolyToken.approve(I_STRProxied.address, initRegFeePOLY, { from: token_owner }); + + let tx = await I_STRProxied.generateNewSecurityToken(name, symbol, tokenDetails, false, token_owner, 0, { from: token_owner }); // Verify the successful generation of the security token assert.equal(tx.logs[1].args._ticker, symbol, "SecurityToken doesn't get deployed"); - I_SecurityToken = SecurityToken.at(tx.logs[1].args._securityTokenAddress); - - const log = await promisifyLogWatch(I_SecurityToken.ModuleAdded({ from: _blockNo }), 1); + I_SecurityToken = await SecurityToken.at(tx.logs[1].args._securityTokenAddress); + stGetter = await STGetter.at(I_SecurityToken.address); + assert.equal(await stGetter.getTreasuryWallet.call(), token_owner, "Incorrect wallet set"); + const log = (await I_SecurityToken.getPastEvents('ModuleAdded', {filter: {transactionHash: tx.transactionHash}}))[0]; // Verify that GeneralTransferManager module get added successfully or not assert.equal(log.args._types[0].toNumber(), transferManagerKey); @@ -175,7 +228,7 @@ contract("SecurityTokenRegistryProxy", accounts => { it("Should upgrade the version and implementation address -- Implemenation address should not be 0x", async () => { await catchRevert( - I_SecurityTokenRegistryProxy.upgradeTo("1.1.0", "0x00000000000000000000000000000000000000", { from: account_polymath }) + I_SecurityTokenRegistryProxy.upgradeTo("1.1.0", address_zero, { from: account_polymath }) ); }); @@ -195,7 +248,7 @@ contract("SecurityTokenRegistryProxy", accounts => { it("Should upgrade the version and the implementation address successfully", async () => { await I_SecurityTokenRegistryProxy.upgradeTo("1.1.0", I_SecurityTokenRegistryMock.address, { from: account_polymath }); - let c = OwnedUpgradeabilityProxy.at(I_SecurityTokenRegistryProxy.address); + let c = await OwnedUpgradeabilityProxy.at(I_SecurityTokenRegistryProxy.address); assert.equal( web3.utils .toAscii(await readStorage(c.address, 11)) @@ -204,24 +257,28 @@ contract("SecurityTokenRegistryProxy", accounts => { "1.1.0", "Version mis-match" ); - assert.equal(await readStorage(c.address, 12), I_SecurityTokenRegistryMock.address, "Implemnted address is not matched"); + assert.equal(await readStorage(c.address, 12), I_SecurityTokenRegistryMock.address.toLowerCase(), "Implemnted address is not matched"); I_STRProxied = await SecurityTokenRegistryMock.at(I_SecurityTokenRegistryProxy.address); + I_Getter = await STRGetter.at(I_SecurityTokenRegistryProxy.address); }); }); describe("Execute functionality of the implementation contract on the earlier storage", async () => { it("Should get the previous data", async () => { - let _tokenAddress = await I_STRProxied.getSecurityTokenAddress.call(symbol); - let _data = await I_STRProxied.getSecurityTokenData.call(_tokenAddress); + + let _tokenAddress = await I_Getter.getSecurityTokenAddress.call(symbol); + let _data = await I_Getter.getSecurityTokenData.call(_tokenAddress); assert.equal(_data[0], symbol, "Symbol should match with registered symbol"); assert.equal(_data[1], token_owner, "Owner should be the deployer of token"); assert.equal(_data[2], tokenDetails, "Token details should matched with deployed ticker"); }); it("Should alter the old storage", async () => { - await I_STRProxied.changeTheDeployedAddress(symbol, account_temp, { from: account_polymath }); - let _tokenAddress = await I_STRProxied.getSecurityTokenAddress.call(symbol); - assert.equal(_tokenAddress, account_temp, "Should match with the changed address"); + await I_STRProxied.changeTheFee(0, { from: account_polymath }); + let feesToken = await I_STRProxied.getFees.call("0xd677304bb45536bb7fdfa6b9e47a3c58fe413f9e8f01474b0a4b9c6e0275baf2"); + console.log(feesToken); + // assert.equal(feesToken[0].toString(), origPriceUSD.toString()); + // assert.equal(feesToken[1].toString(), origPricePOLY.toString()); }); }); @@ -232,7 +289,7 @@ contract("SecurityTokenRegistryProxy", accounts => { it("Should change the ownership of the contract -- new address should not be 0x", async () => { await catchRevert( - I_SecurityTokenRegistryProxy.transferProxyOwnership("0x00000000000000000000000000000000000000", { from: account_polymath }) + I_SecurityTokenRegistryProxy.transferProxyOwnership(address_zero, { from: account_polymath }) ); }); @@ -245,7 +302,7 @@ contract("SecurityTokenRegistryProxy", accounts => { it("Should change the implementation contract and version by the new owner", async () => { I_SecurityTokenRegistry = await SecurityTokenRegistry.new({ from: account_polymath }); await I_SecurityTokenRegistryProxy.upgradeTo("1.2.0", I_SecurityTokenRegistry.address, { from: account_polymath_new }); - let c = OwnedUpgradeabilityProxy.at(I_SecurityTokenRegistryProxy.address); + let c = await OwnedUpgradeabilityProxy.at(I_SecurityTokenRegistryProxy.address); assert.equal( web3.utils .toAscii(await readStorage(c.address, 11)) @@ -254,16 +311,19 @@ contract("SecurityTokenRegistryProxy", accounts => { "1.2.0", "Version mis-match" ); - assert.equal(await readStorage(c.address, 12), I_SecurityTokenRegistry.address, "Implemnted address is not matched"); + assert.equal(await readStorage(c.address, 12), I_SecurityTokenRegistry.address.toLowerCase(), "Implemnted address is not matched"); I_STRProxied = await SecurityTokenRegistry.at(I_SecurityTokenRegistryProxy.address); }); - it("Should get the version", async() => { + it("Should get the version", async () => { assert.equal(await I_SecurityTokenRegistryProxy.version.call({ from: account_polymath_new }), "1.2.0"); }); - it("Should get the implementation address", async() => { - assert.equal(await I_SecurityTokenRegistryProxy.implementation.call({ from: account_polymath_new }), I_SecurityTokenRegistry.address); - }) + it("Should get the implementation address", async () => { + assert.equal( + await I_SecurityTokenRegistryProxy.implementation.call({ from: account_polymath_new }), + I_SecurityTokenRegistry.address + ); + }); }); }); diff --git a/test/u_module_registry_proxy.js b/test/u_module_registry_proxy.js index 546200943..353179f3e 100644 --- a/test/u_module_registry_proxy.js +++ b/test/u_module_registry_proxy.js @@ -12,16 +12,21 @@ const SecurityToken = artifacts.require("./SecurityToken.sol"); const GeneralTransferManagerLogic = artifacts.require("./GeneralTransferManager.sol"); const GeneralTransferManagerFactory = artifacts.require("./GeneralTransferManagerFactory.sol"); const GeneralPermissionManagerFactory = artifacts.require("./GeneralPermissionManagerFactory.sol"); +const GeneralPermissionManager = artifacts.require("./GeneralPermissionManager.sol"); +const STGetter = artifacts.require("./STGetter.sol"); +const DataStoreLogic = artifacts.require('./DataStore.sol'); +const DataStoreFactory = artifacts.require('./DataStoreFactory.sol'); const Web3 = require("web3"); -const BigNumber = require("bignumber.js"); +let BN = Web3.utils.BN; const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); // Hardcoded development port -contract("ModuleRegistryProxy", accounts => { +contract("ModuleRegistryProxy", async (accounts) => { let I_SecurityTokenRegistry; let I_SecurityTokenRegistryProxy; let I_GeneralTransferManagerFactory; let I_GeneralPermissionManagerfactory; + let I_GeneralPermissionManagerLogic; let I_MockModuleRegistry; let I_STFactory; let I_PolymathRegistry; @@ -32,6 +37,9 @@ contract("ModuleRegistryProxy", accounts => { let I_SecurityToken; let I_ModuleRegistry; let I_FeatureRegistry; + let I_STRGetter; + let I_STGetter; + let stGetter; let account_polymath; let account_temp; @@ -39,10 +47,10 @@ contract("ModuleRegistryProxy", accounts => { let account_polymath_new; // Initial fee for ticker registry and security token registry - const initRegFee = web3.utils.toWei("250"); const version = "1.0.0"; const message = "Transaction Should Fail!"; const address_zero = "0x0000000000000000000000000000000000000000"; + const one_address = "0x0000000000000000000000000000000000000001"; // SecurityToken Details for funds raise Type ETH const name = "Team"; const symbol = "SAP"; @@ -76,7 +84,9 @@ contract("ModuleRegistryProxy", accounts => { I_STFactory, I_SecurityTokenRegistry, I_SecurityTokenRegistryProxy, - I_STRProxied + I_STRProxied, + I_STRGetter, + I_STGetter ] = instances; I_ModuleRegistryProxy = await ModuleRegistryProxy.new({from: account_polymath}); @@ -107,8 +117,8 @@ contract("ModuleRegistryProxy", accounts => { it("Should attach the MR implementation and version", async () => { let bytesProxy = encodeProxyCall(MRProxyParameters, [I_PolymathRegistry.address, account_polymath]); await I_ModuleRegistryProxy.upgradeToAndCall("1.0.0", I_ModuleRegistry.address, bytesProxy, { from: account_polymath }); - let c = OwnedUpgradeabilityProxy.at(I_ModuleRegistryProxy.address); - assert.equal(await readStorage(c.address, 12), I_ModuleRegistry.address); + let c = await OwnedUpgradeabilityProxy.at(I_ModuleRegistryProxy.address); + assert.equal(await readStorage(c.address, 12), I_ModuleRegistry.address.toLowerCase()); assert.equal( web3.utils .toAscii(await readStorage(c.address, 11)) @@ -123,9 +133,17 @@ contract("ModuleRegistryProxy", accounts => { await I_MRProxied.updateFromRegistry({ from: account_polymath }); // STEP 4: Deploy the GeneralTransferManagerFactory - let I_GeneralTransferManagerLogic = await GeneralTransferManagerLogic.new("0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", { from: account_polymath }); + let I_GeneralTransferManagerLogic = await GeneralTransferManagerLogic.new( + address_zero, + address_zero, + { from: account_polymath } + ); + + let I_SecurityTokenLogic = await SecurityToken.new( + { from: account_polymath } + ); - I_GeneralTransferManagerFactory = await GeneralTransferManagerFactory.new(I_PolyToken.address, 0, 0, 0, I_GeneralTransferManagerLogic.address, { + I_GeneralTransferManagerFactory = await GeneralTransferManagerFactory.new(new BN(0), I_GeneralTransferManagerLogic.address, I_PolymathRegistry.address, true, { from: account_polymath }); @@ -139,16 +157,26 @@ contract("ModuleRegistryProxy", accounts => { // (A) : Register the GeneralTransferManagerFactory await I_MRProxied.registerModule(I_GeneralTransferManagerFactory.address, { from: account_polymath }); - await I_MRProxied.verifyModule(I_GeneralTransferManagerFactory.address, true, { from: account_polymath }); + await I_MRProxied.verifyModule(I_GeneralTransferManagerFactory.address, { from: account_polymath }); // Step 3: Deploy the STFactory contract - I_STFactory = await STFactory.new(I_GeneralTransferManagerFactory.address, { from: account_polymath }); - - assert.notEqual( - I_STFactory.address.valueOf(), - address_zero, - "STFactory contract was not deployed" - ); + I_STGetter = await STGetter.new({from: account_polymath}); + let I_DataStoreLogic = await DataStoreLogic.new({ from: account_polymath }); + let I_DataStoreFactory = await DataStoreFactory.new(I_DataStoreLogic.address, { from: account_polymath }); + const tokenInitBytes = { + name: "initialize", + type: "function", + inputs: [ + { + type: "address", + name: "_getterDelegate" + } + ] + }; + let tokenInitBytesCall = web3.eth.abi.encodeFunctionCall(tokenInitBytes, [I_STGetter.address]); + I_STFactory = await STFactory.new(I_PolymathRegistry.address, I_GeneralTransferManagerFactory.address, I_DataStoreFactory.address, "3.0.0", I_SecurityTokenLogic.address, tokenInitBytesCall, { from: account_polymath }); + + assert.notEqual(I_STFactory.address.valueOf(), address_zero, "STFactory contract was not deployed"); }); it("Verify the initialize data", async () => { @@ -163,7 +191,8 @@ contract("ModuleRegistryProxy", accounts => { describe("Feed some data in storage", async () => { it("Register and verify the new module", async () => { - I_GeneralPermissionManagerfactory = await GeneralPermissionManagerFactory.new(I_PolyToken.address, 0, 0, 0, { + I_GeneralPermissionManagerLogic = await GeneralPermissionManager.new("0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", { from: account_polymath }); + I_GeneralPermissionManagerfactory = await GeneralPermissionManagerFactory.new(new BN(0), I_GeneralPermissionManagerLogic.address, I_PolymathRegistry.address, true, { from: account_polymath }); @@ -174,7 +203,7 @@ contract("ModuleRegistryProxy", accounts => { ); await I_MRProxied.registerModule(I_GeneralPermissionManagerfactory.address, { from: account_polymath }); - await I_MRProxied.verifyModule(I_GeneralPermissionManagerfactory.address, true, { from: account_polymath }); + await I_MRProxied.verifyModule(I_GeneralPermissionManagerfactory.address, { from: account_polymath }); }); }); @@ -190,7 +219,7 @@ contract("ModuleRegistryProxy", accounts => { it("Should upgrade the version and implementation address -- Implemenation address should not be 0x", async () => { await catchRevert( - I_ModuleRegistryProxy.upgradeTo("1.1.0", "0x00000000000000000000000000000000000000", { from: account_polymath }) + I_ModuleRegistryProxy.upgradeTo("1.1.0", address_zero, { from: account_polymath }) ); }); @@ -208,7 +237,7 @@ contract("ModuleRegistryProxy", accounts => { it("Should upgrade the version and the implementation address successfully", async () => { await I_ModuleRegistryProxy.upgradeTo("1.1.0", I_MockModuleRegistry.address, { from: account_polymath }); - let c = OwnedUpgradeabilityProxy.at(I_ModuleRegistryProxy.address); + let c = await OwnedUpgradeabilityProxy.at(I_ModuleRegistryProxy.address); assert.equal( web3.utils .toAscii(await readStorage(c.address, 11)) @@ -217,23 +246,23 @@ contract("ModuleRegistryProxy", accounts => { "1.1.0", "Version mis-match" ); - assert.equal(await readStorage(c.address, 12), I_MockModuleRegistry.address, "Implemnted address is not matched"); + assert.equal(await readStorage(c.address, 12), I_MockModuleRegistry.address.toLowerCase(), "Implemnted address is not matched"); I_MRProxied = await MockModuleRegistry.at(I_ModuleRegistryProxy.address); }); }); describe("Execute functionality of the implementation contract on the earlier storage", async () => { it("Should get the previous data", async () => { - let _data = await I_MRProxied.getReputationByFactory.call(I_GeneralTransferManagerFactory.address); - assert.equal(_data.length, 0, "Should give the original length"); + let _data = await I_MRProxied.getFactoryDetails.call(I_GeneralTransferManagerFactory.address); + assert.equal(_data[2].length, new BN(0), "Should give the original length"); }); it("Should alter the old storage", async () => { await I_MRProxied.addMoreReputation(I_GeneralTransferManagerFactory.address, [account_polymath, account_temp], { from: account_polymath }); - let _data = await I_MRProxied.getReputationByFactory.call(I_GeneralTransferManagerFactory.address); - assert.equal(_data.length, 2, "Should give the updated length"); + let _data = await I_MRProxied.getFactoryDetails.call(I_GeneralTransferManagerFactory.address); + assert.equal(_data[2].length, 2, "Should give the updated length"); }); }); @@ -244,7 +273,7 @@ contract("ModuleRegistryProxy", accounts => { it("Should change the ownership of the contract -- new address should not be 0x", async () => { await catchRevert( - I_ModuleRegistryProxy.transferProxyOwnership("0x00000000000000000000000000000000000000", { from: account_polymath }) + I_ModuleRegistryProxy.transferProxyOwnership(address_zero, { from: account_polymath }) ); }); @@ -257,7 +286,7 @@ contract("ModuleRegistryProxy", accounts => { it("Should change the implementation contract and version by the new owner", async () => { I_ModuleRegistry = await ModuleRegistry.new({ from: account_polymath }); await I_ModuleRegistryProxy.upgradeTo("1.2.0", I_ModuleRegistry.address, { from: account_polymath_new }); - let c = OwnedUpgradeabilityProxy.at(I_ModuleRegistryProxy.address); + let c = await OwnedUpgradeabilityProxy.at(I_ModuleRegistryProxy.address); assert.equal( web3.utils .toAscii(await readStorage(c.address, 11)) @@ -266,7 +295,7 @@ contract("ModuleRegistryProxy", accounts => { "1.2.0", "Version mis-match" ); - assert.equal(await readStorage(c.address, 12), I_ModuleRegistry.address, "Implemnted address is not matched"); + assert.equal(await readStorage(c.address, 12), I_ModuleRegistry.address.toLowerCase(), "Implemnted address is not matched"); I_MRProxied = await ModuleRegistry.at(I_ModuleRegistryProxy.address); }); }); diff --git a/test/v_tracked_redemptions.js b/test/v_tracked_redemptions.js index 2d5ae8c6c..bb00302e9 100644 --- a/test/v_tracked_redemptions.js +++ b/test/v_tracked_redemptions.js @@ -1,20 +1,20 @@ import latestTime from "./helpers/latestTime"; -import { duration, promisifyLogWatch, latestBlock } from "./helpers/utils"; -import takeSnapshot, { increaseTime, revertToSnapshot } from "./helpers/time"; -import { encodeProxyCall } from "./helpers/encodeCall"; -import { catchRevert } from "./helpers/exceptions"; -import { setUpPolymathNetwork, deployRedemptionAndVerifyed } from "./helpers/createInstances"; +import {duration, promisifyLogWatch} from "./helpers/utils"; +import { takeSnapshot,increaseTime, revertToSnapshot} from "./helpers/time"; +import {catchRevert} from "./helpers/exceptions"; +import {deployRedemptionAndVerifyed, setUpPolymathNetwork} from "./helpers/createInstances"; const SecurityToken = artifacts.require("./SecurityToken.sol"); const GeneralTransferManager = artifacts.require("./GeneralTransferManager"); const TrackedRedemption = artifacts.require("./TrackedRedemption"); const GeneralPermissionManager = artifacts.require("./GeneralPermissionManager"); +const STGetter = artifacts.require("./STGetter.sol"); const Web3 = require("web3"); -const BigNumber = require("bignumber.js"); +let BN = Web3.utils.BN; const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); // Hardcoded development port -contract("TrackedRedemption", accounts => { +contract("TrackedRedemption", async (accounts) => { // Accounts Variable declaration let account_polymath; let account_issuer; @@ -25,11 +25,6 @@ contract("TrackedRedemption", accounts => { 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 @@ -52,6 +47,9 @@ contract("TrackedRedemption", accounts => { let I_MRProxied; let I_PolymathRegistry; let P_TrackedRedemptionFactory; + let I_STRGetter; + let I_STGetter; + let stGetter; // SecurityToken Details const name = "Team"; @@ -68,10 +66,14 @@ contract("TrackedRedemption", accounts => { const burnKey = 5; // Initial fee for ticker registry and security token registry - const initRegFee = web3.utils.toWei("250"); + const initRegFee = new BN(web3.utils.toWei("1000")); + + let currentTime; + const address_zero = "0x0000000000000000000000000000000000000000"; + const one_address = "0x0000000000000000000000000000000000000001"; before(async () => { - // Accounts setup + currentTime = new BN(await latestTime()); account_polymath = accounts[0]; account_issuer = accounts[1]; @@ -99,13 +101,15 @@ contract("TrackedRedemption", accounts => { I_STFactory, I_SecurityTokenRegistry, I_SecurityTokenRegistryProxy, - I_STRProxied + I_STRProxied, + I_STRGetter, + I_STGetter ] = instances; // STEP 4: Deploy the TrackedRedemption - [I_TrackedRedemptionFactory] = await deployRedemptionAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, 0); - [P_TrackedRedemptionFactory] = await deployRedemptionAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, web3.utils.toWei("500")); + [I_TrackedRedemptionFactory] = await deployRedemptionAndVerifyed(account_polymath, I_MRProxied, 0); + [P_TrackedRedemptionFactory] = await deployRedemptionAndVerifyed(account_polymath, I_MRProxied, new BN(web3.utils.toWei("500"))); // Printing all the contract addresses console.log(` @@ -128,56 +132,59 @@ contract("TrackedRedemption", accounts => { describe("Generate the SecurityToken", async () => { it("Should register the ticker before the generation of the security token", async () => { await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); - let tx = await I_STRProxied.registerTicker(token_owner, symbol, contact, { from: token_owner }); + let tx = await I_STRProxied.registerNewTicker(token_owner, symbol, { from: token_owner }); assert.equal(tx.logs[0].args._owner, token_owner); assert.equal(tx.logs[0].args._ticker, symbol.toUpperCase()); }); it("Should generate the new security token with the same symbol as registered above", async () => { await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); - let _blockNo = latestBlock(); - let tx = await I_STRProxied.generateSecurityToken(name, symbol, tokenDetails, false, { from: token_owner }); + + let tx = await I_STRProxied.generateNewSecurityToken(name, symbol, tokenDetails, false, token_owner, 0, { from: token_owner }); // 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 log = await promisifyLogWatch(I_SecurityToken.ModuleAdded({ from: _blockNo }), 1); + I_SecurityToken = await SecurityToken.at(tx.logs[1].args._securityTokenAddress); + stGetter = await STGetter.at(I_SecurityToken.address); + assert.equal(await stGetter.getTreasuryWallet.call(), token_owner, "Incorrect wallet set"); + const log = (await I_SecurityToken.getPastEvents('ModuleAdded', {filter: {transactionHash: tx.transactionHash}}))[0]; // Verify that GeneralTransferManager module get added successfully or not assert.equal(log.args._types[0].toNumber(), 2); assert.equal(web3.utils.toAscii(log.args._name).replace(/\u0000/g, ""), "GeneralTransferManager"); }); - it("Should intialize the auto attached modules", async () => { - let moduleData = (await I_SecurityToken.getModulesByType(2))[0]; - I_GeneralTransferManager = GeneralTransferManager.at(moduleData); + it("Should initialize the auto attached modules", async () => { + let moduleData = (await stGetter.getModulesByType(2))[0]; + I_GeneralTransferManager = await GeneralTransferManager.at(moduleData); }); it("Should successfully attach the paid TrackedRedemption with the security token", async () => { let snapId = await takeSnapshot(); - await I_PolyToken.getTokens(web3.utils.toWei("500"), I_SecurityToken.address); - const tx = await I_SecurityToken.addModule(P_TrackedRedemptionFactory.address, "", web3.utils.toWei("500"), 0, { from: token_owner }); + await I_PolyToken.getTokens(new BN(web3.utils.toWei("2000")), I_SecurityToken.address); + const tx = await I_SecurityToken.addModule(P_TrackedRedemptionFactory.address, "0x0", new BN(web3.utils.toWei("2000")), new BN(0), false, { + from: token_owner + }); assert.equal(tx.logs[3].args._types[0].toNumber(), burnKey, "TrackedRedemption doesn't get deployed"); assert.equal( web3.utils.toAscii(tx.logs[3].args._name).replace(/\u0000/g, ""), "TrackedRedemption", "TrackedRedemption module was not added" ); - I_TrackedRedemption = TrackedRedemption.at(tx.logs[3].args._module); + I_TrackedRedemption = await TrackedRedemption.at(tx.logs[3].args._module); await revertToSnapshot(snapId); }); it("Should successfully attach the TrackedRedemption with the security token", async () => { - const tx = await I_SecurityToken.addModule(I_TrackedRedemptionFactory.address, "", 0, 0, { from: token_owner }); + const tx = await I_SecurityToken.addModule(I_TrackedRedemptionFactory.address, "0x0", new BN(0), new BN(0), false, { from: token_owner }); assert.equal(tx.logs[2].args._types[0].toNumber(), burnKey, "TrackedRedemption doesn't get deployed"); assert.equal( web3.utils.toAscii(tx.logs[2].args._name).replace(/\u0000/g, ""), "TrackedRedemption", "TrackedRedemption module was not added" ); - I_TrackedRedemption = TrackedRedemption.at(tx.logs[2].args._module); + I_TrackedRedemption = await TrackedRedemption.at(tx.logs[2].args._module); }); }); @@ -185,12 +192,11 @@ contract("TrackedRedemption", accounts => { it("Buy some tokens for account_investor1 (1 ETH)", async () => { // Add the Investor in to the whitelist - let tx = await I_GeneralTransferManager.modifyWhitelist( + let tx = await I_GeneralTransferManager.modifyKYCData( account_investor1, - latestTime(), - latestTime(), - latestTime() + duration.days(30), - true, + currentTime, + currentTime, + currentTime.add(new BN(duration.days(30))), { from: account_issuer, gas: 500000 @@ -206,21 +212,20 @@ contract("TrackedRedemption", accounts => { // Jump time await increaseTime(5000); - // Mint some tokens - await I_SecurityToken.mint(account_investor1, web3.utils.toWei("1", "ether"), { from: token_owner }); + // issue some tokens + await I_SecurityToken.issue(account_investor1, new BN(web3.utils.toWei("1", "ether")), "0x0", { from: token_owner }); - assert.equal((await I_SecurityToken.balanceOf(account_investor1)).toNumber(), web3.utils.toWei("1", "ether")); + assert.equal((await I_SecurityToken.balanceOf(account_investor1)).toString(), new BN(web3.utils.toWei("1", "ether")).toString()); }); it("Buy some tokens for account_investor2 (2 ETH)", async () => { // Add the Investor in to the whitelist - let tx = await I_GeneralTransferManager.modifyWhitelist( + let tx = await I_GeneralTransferManager.modifyKYCData( account_investor2, - latestTime(), - latestTime(), - latestTime() + duration.days(30), - true, + currentTime, + currentTime, + currentTime.add(new BN(duration.days(30))), { from: account_issuer, gas: 500000 @@ -233,24 +238,31 @@ contract("TrackedRedemption", accounts => { "Failed in adding the investor in whitelist" ); - // Mint some tokens - await I_SecurityToken.mint(account_investor2, web3.utils.toWei("2", "ether"), { from: token_owner }); + // issue some tokens + await I_SecurityToken.issue(account_investor2, new BN(web3.utils.toWei("2", "ether")), "0x0", { from: token_owner }); - assert.equal((await I_SecurityToken.balanceOf(account_investor2)).toNumber(), web3.utils.toWei("2", "ether")); + assert.equal((await I_SecurityToken.balanceOf(account_investor2)).toString(), new BN(web3.utils.toWei("2", "ether")).toString()); }); it("Redeem some tokens - fail insufficient allowance", async () => { - await I_GeneralTransferManager.changeAllowAllBurnTransfers(true, { from: token_owner }); + await I_GeneralTransferManager.modifyTransferRequirementsMulti( + [0, 1, 2], + [true, false, false], + [true, true, false], + [false, false, false], + [false, false, false], + { from: token_owner } + ); - await catchRevert(I_TrackedRedemption.redeemTokens(web3.utils.toWei("1", "ether"), { from: account_investor1 })); + await catchRevert(I_TrackedRedemption.redeemTokens(new BN(web3.utils.toWei("1", "ether")), { from: account_investor1 })); }); it("Redeem some tokens", async () => { - await I_SecurityToken.approve(I_TrackedRedemption.address, web3.utils.toWei("1", "ether"), { from: account_investor1 }); - let tx = await I_TrackedRedemption.redeemTokens(web3.utils.toWei("1", "ether"), { from: account_investor1 }); + await I_SecurityToken.approve(I_TrackedRedemption.address, new BN(web3.utils.toWei("1", "ether")), { from: account_investor1 }); + let tx = await I_TrackedRedemption.redeemTokens(new BN(web3.utils.toWei("1", "ether")), { from: account_investor1 }); console.log(JSON.stringify(tx.logs)); assert.equal(tx.logs[0].args._investor.toLowerCase(), account_investor1.toLowerCase(), "Mismatch address"); - assert.equal(tx.logs[0].args._value, web3.utils.toWei("1", "ether"), "Wrong value"); + assert.equal(web3.utils.fromWei(web3.utils.toBN(tx.logs[0].args._value)), 1, "Wrong value"); }); it("Get the init data", async () => { @@ -265,21 +277,16 @@ contract("TrackedRedemption", accounts => { describe("Test cases for the TrackedRedemptionFactory", async () => { it("should get the exact details of the factory", async () => { - assert.equal((await I_TrackedRedemptionFactory.getSetupCost.call()).toNumber(), 0); + assert.equal((await I_TrackedRedemptionFactory.setupCost.call()).toNumber(), 0); assert.equal((await I_TrackedRedemptionFactory.getTypes.call())[0], 5); - assert.equal(await I_TrackedRedemptionFactory.version.call(), "1.0.0"); + assert.equal(await I_TrackedRedemptionFactory.version.call(), "3.0.0"); assert.equal( - web3.utils.toAscii(await I_TrackedRedemptionFactory.getName.call()).replace(/\u0000/g, ""), + web3.utils.toAscii(await I_TrackedRedemptionFactory.name.call()).replace(/\u0000/g, ""), "TrackedRedemption", "Wrong Module added" ); assert.equal(await I_TrackedRedemptionFactory.description.call(), "Track token redemptions", "Wrong Module added"); assert.equal(await I_TrackedRedemptionFactory.title.call(), "Tracked Redemption", "Wrong Module added"); - assert.equal( - await I_TrackedRedemptionFactory.getInstructions.call(), - "Allows an investor to redeem security tokens which are tracked by this module", - "Wrong Module added" - ); let tags = await I_TrackedRedemptionFactory.getTags.call(); assert.equal(tags.length, 2); }); diff --git a/test/w_lockup_transfer_manager.js b/test/w_lockup_transfer_manager.js index 41f9ea61e..4c0b48bf4 100644 --- a/test/w_lockup_transfer_manager.js +++ b/test/w_lockup_transfer_manager.js @@ -1,17 +1,18 @@ import latestTime from './helpers/latestTime'; import { duration, promisifyLogWatch, latestBlock } from './helpers/utils'; -import takeSnapshot, { increaseTime, revertToSnapshot } from './helpers/time'; +import { takeSnapshot, increaseTime, revertToSnapshot } from './helpers/time'; import { encodeProxyCall } from './helpers/encodeCall'; -import { setUpPolymathNetwork, deployLockupVolumeRTMAndVerified } from "./helpers/createInstances"; +import { setUpPolymathNetwork, deployLockUpTMAndVerified } from "./helpers/createInstances"; import { catchRevert } from "./helpers/exceptions"; const SecurityToken = artifacts.require('./SecurityToken.sol'); const GeneralTransferManager = artifacts.require('./GeneralTransferManager'); const LockUpTransferManager = artifacts.require('./LockUpTransferManager'); const GeneralPermissionManager = artifacts.require('./GeneralPermissionManager'); +const STGetter = artifacts.require("./STGetter.sol"); const Web3 = require('web3'); -const BigNumber = require('bignumber.js'); +let BN = Web3.utils.BN; const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")) // Hardcoded development port contract('LockUpTransferManager', accounts => { @@ -25,11 +26,6 @@ contract('LockUpTransferManager', accounts => { let account_investor3; let account_investor4; - // investor Details - let fromTime = latestTime(); - let toTime = latestTime(); - let expiryTime = toTime + duration.days(15); - let message = "Transaction Should Fail!"; // Contract Instance Declaration @@ -54,6 +50,9 @@ contract('LockUpTransferManager', accounts => { let I_SecurityToken_div; let I_GeneralTransferManager_div; let I_LockUpVolumeRestrictionTM_div; + let I_STGetter; + let stGetter; + let stGetter_div; // SecurityToken Details const name = "Team"; @@ -61,6 +60,7 @@ contract('LockUpTransferManager', accounts => { const tokenDetails = "This is equity type of issuance"; const decimals = 18; const contact = "team@polymath.network"; + const address_zero = "0x0000000000000000000000000000000000000000"; const name2 = "Core"; const symbol2 = "Core"; @@ -73,9 +73,11 @@ contract('LockUpTransferManager', accounts => { let temp; // Initial fee for ticker registry and security token registry - const initRegFee = web3.utils.toWei("250"); + const initRegFee = new BN(web3.utils.toWei("1000")); + let currentTime; before(async() => { + currentTime = new BN(await latestTime()); // Accounts setup account_polymath = accounts[0]; account_issuer = accounts[1]; @@ -99,13 +101,14 @@ contract('LockUpTransferManager', accounts => { I_STFactory, I_SecurityTokenRegistry, I_SecurityTokenRegistryProxy, - I_STRProxied + I_STRProxied, + I_STGetter ] = instances; // STEP 4(c): Deploy the LockUpVolumeRestrictionTMFactory - [I_LockUpTransferManagerFactory] = await deployLockupVolumeRTMAndVerified(account_polymath, I_MRProxied, I_PolyToken.address, 0); + [I_LockUpTransferManagerFactory] = await deployLockUpTMAndVerified(account_polymath, I_MRProxied, 0); // STEP 4(d): Deploy the LockUpVolumeRestrictionTMFactory - [P_LockUpTransferManagerFactory] = await deployLockupVolumeRTMAndVerified(account_polymath, I_MRProxied, I_PolyToken.address, web3.utils.toWei("500")); + [P_LockUpTransferManagerFactory] = await deployLockUpTMAndVerified(account_polymath, I_MRProxied, new BN(web3.utils.toWei("500"))); // Printing all the contract addresses console.log(` @@ -120,7 +123,7 @@ contract('LockUpTransferManager', accounts => { STFactory: ${I_STFactory.address} GeneralTransferManagerFactory: ${I_GeneralTransferManagerFactory.address} - LockupVolumeRestrictionTransferManagerFactory: + LockupVolumeRestrictionTransferManagerFactory: ${I_LockUpTransferManagerFactory.address} ----------------------------------------------------------------------------- `); @@ -130,7 +133,7 @@ contract('LockUpTransferManager', accounts => { it("Should register the ticker before the generation of the security token", async () => { await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); - let tx = await I_STRProxied.registerTicker(token_owner, symbol, contact, { from : token_owner }); + let tx = await I_STRProxied.registerNewTicker(token_owner, symbol, { from : token_owner }); assert.equal(tx.logs[0].args._owner, token_owner); assert.equal(tx.logs[0].args._ticker, symbol.toUpperCase()); }); @@ -138,17 +141,18 @@ contract('LockUpTransferManager', accounts => { it("Should generate the new security token with the same symbol as registered above", async () => { await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); let _blockNo = latestBlock(); - let tx = await I_STRProxied.generateSecurityToken(name, symbol, tokenDetails, false, { from: token_owner }); + let tx = await I_STRProxied.generateNewSecurityToken(name, symbol, tokenDetails, false, token_owner, 0, { from: token_owner }); // 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 log = await promisifyLogWatch(I_SecurityToken.ModuleAdded({from: _blockNo}), 1); + I_SecurityToken = await SecurityToken.at(tx.logs[1].args._securityTokenAddress); + stGetter = await STGetter.at(I_SecurityToken.address); + assert.equal(await stGetter.getTreasuryWallet.call(), token_owner, "Incorrect wallet set"); + const log = (await I_SecurityToken.getPastEvents('ModuleAdded', {filter: {transactionHash: tx.transactionHash}}))[0]; // Verify that GeneralTransferManager module get added successfully or not - assert.equal(log.args._types[0].toNumber(), 2); + assert.equal(log.args._types[0].toString(), 2); assert.equal( web3.utils.toAscii(log.args._name) .replace(/\u0000/g, ''), @@ -156,15 +160,13 @@ contract('LockUpTransferManager', accounts => { ); }); - it("Should intialize the auto attached modules", async () => { - let moduleData = (await I_SecurityToken.getModulesByType(2))[0]; - I_GeneralTransferManager = GeneralTransferManager.at(moduleData); + it("Should initialize the auto attached modules", async () => { + let moduleData = (await stGetter.getModulesByType(2))[0]; + I_GeneralTransferManager = await GeneralTransferManager.at(moduleData); }); - - it("Should register another ticker before the generation of new security token", async () => { await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); - let tx = await I_STRProxied.registerTicker(token_owner, symbol2, contact, { from : token_owner }); + let tx = await I_STRProxied.registerNewTicker(token_owner, symbol2, { from : token_owner }); assert.equal(tx.logs[0].args._owner, token_owner); assert.equal(tx.logs[0].args._ticker, symbol2.toUpperCase()); }); @@ -172,17 +174,18 @@ contract('LockUpTransferManager', accounts => { it("Should generate the new security token with the same symbol as registered above", async () => { await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); let _blockNo = latestBlock(); - let tx = await I_STRProxied.generateSecurityToken(name2, symbol2, tokenDetails, true, { from: token_owner }); + let tx = await I_STRProxied.generateNewSecurityToken(name2, symbol2, tokenDetails, true, token_owner, 0, { from: token_owner }); // Verify the successful generation of the security token assert.equal(tx.logs[1].args._ticker, symbol2.toUpperCase(), "SecurityToken doesn't get deployed"); - I_SecurityToken_div = SecurityToken.at(tx.logs[1].args._securityTokenAddress); - - const log = await promisifyLogWatch(I_SecurityToken_div.ModuleAdded({from: _blockNo}), 1); + I_SecurityToken_div = await SecurityToken.at(tx.logs[1].args._securityTokenAddress); + stGetter_div = await STGetter.at(I_SecurityToken_div.address); + assert.equal(await stGetter_div.getTreasuryWallet.call(), token_owner, "Incorrect wallet set"); + const log = (await I_SecurityToken_div.getPastEvents('ModuleAdded', {filter: {transactionHash: tx.transactionHash}}))[0]; // Verify that GeneralTransferManager module get added successfully or not - assert.equal(log.args._types[0].toNumber(), 2); + assert.equal(log.args._types[0].toString(), 2); assert.equal( web3.utils.toAscii(log.args._name) .replace(/\u0000/g, ''), @@ -190,8 +193,8 @@ contract('LockUpTransferManager', accounts => { ); }); - it("Should intialize the auto attached modules", async () => { - let moduleData = (await I_SecurityToken_div.getModulesByType(2))[0]; + it("Should initialize the auto attached modules", async () => { + let moduleData = (await stGetter_div.getModulesByType(2))[0]; I_GeneralTransferManager_div = GeneralTransferManager.at(moduleData); }); @@ -202,13 +205,12 @@ contract('LockUpTransferManager', accounts => { it("Should Buy the tokens from non-divisible", async() => { // Add the Investor in to the whitelist - - let tx = await I_GeneralTransferManager.modifyWhitelist( + console.log(account_investor1); + let tx = await I_GeneralTransferManager.modifyKYCData( account_investor1, - latestTime(), - latestTime(), - latestTime() + duration.days(10), - true, + currentTime, + currentTime, + currentTime.add(new BN(duration.days(10))), { from: account_issuer }); @@ -219,37 +221,10 @@ contract('LockUpTransferManager', accounts => { await increaseTime(5000); // Mint some tokens - await I_SecurityToken.mint(account_investor1, web3.utils.toWei('2', 'ether'), { from: token_owner }); + await I_SecurityToken.issue(account_investor1, new BN(web3.utils.toWei('2', 'ether')), "0x0", { from: token_owner }); assert.equal( - (await I_SecurityToken.balanceOf(account_investor1)).toNumber(), - web3.utils.toWei('2', 'ether') - ); - }); - - it("Should Buy the tokens from the divisible token", async() => { - // Add the Investor in to the whitelist - - let tx = await I_GeneralTransferManager_div.modifyWhitelist( - account_investor1, - latestTime(), - latestTime(), - latestTime() + duration.days(10), - true, - { - from: account_issuer - }); - - 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_div.mint(account_investor1, web3.utils.toWei('2', 'ether'), { from: token_owner }); - - assert.equal( - (await I_SecurityToken_div.balanceOf(account_investor1)).toNumber(), + (await I_SecurityToken.balanceOf(account_investor1)).toString(), web3.utils.toWei('2', 'ether') ); }); @@ -257,12 +232,11 @@ contract('LockUpTransferManager', accounts => { it("Should Buy some more tokens from non-divisible tokens", async() => { // Add the Investor in to the whitelist - let tx = await I_GeneralTransferManager.modifyWhitelist( + let tx = await I_GeneralTransferManager.modifyKYCData( account_investor2, - latestTime(), - latestTime(), - latestTime() + duration.days(10), - true, + currentTime, + currentTime, + currentTime.add(new BN(duration.days(10))), { from: account_issuer }); @@ -270,68 +244,68 @@ contract('LockUpTransferManager', accounts => { 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('10', 'ether'), { from: token_owner }); + await I_SecurityToken.issue(account_investor2, web3.utils.toWei('10', 'ether'), "0x0", { from: token_owner }); assert.equal( - (await I_SecurityToken.balanceOf(account_investor2)).toNumber(), + (await I_SecurityToken.balanceOf(account_investor2)).toString(), web3.utils.toWei('10', 'ether') ); }); it("Should unsuccessfully attach the LockUpTransferManager factory with the security token -- failed because Token is not paid", async () => { - await I_PolyToken.getTokens(web3.utils.toWei("500", "ether"), token_owner); + await I_PolyToken.getTokens(web3.utils.toWei("2000", "ether"), token_owner); await catchRevert( - I_SecurityToken.addModule(P_LockUpTransferManagerFactory.address, 0, web3.utils.toWei("500", "ether"), 0, { from: token_owner }) + I_SecurityToken.addModule(P_LockUpTransferManagerFactory.address, "0x", new BN(web3.utils.toWei("2000", "ether")), 0, false, { from: token_owner }) ) }); it("Should successfully attach the LockUpTransferManager factory 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_LockUpTransferManagerFactory.address, 0, web3.utils.toWei("500", "ether"), 0, { from: token_owner }); - assert.equal(tx.logs[3].args._types[0].toNumber(), transferManagerKey, "LockUpVolumeRestrictionTMFactory doesn't get deployed"); + await I_PolyToken.transfer(I_SecurityToken.address, new BN(web3.utils.toWei("2000", "ether")), {from: token_owner}); + console.log((await P_LockUpTransferManagerFactory.setupCost.call()).toString()); + const tx = await I_SecurityToken.addModule(P_LockUpTransferManagerFactory.address, "0x", new BN(web3.utils.toWei("2000", "ether")), new BN(0), false, { from: token_owner }); + assert.equal(tx.logs[3].args._types[0].toString(), transferManagerKey, "LockUpVolumeRestrictionTMFactory doesn't get deployed"); assert.equal( web3.utils.toAscii(tx.logs[3].args._name) .replace(/\u0000/g, ''), "LockUpTransferManager", "LockUpTransferManager module was not added" ); - P_LockUpTransferManager = LockUpTransferManager.at(tx.logs[3].args._module); + P_LockUpTransferManager = await LockUpTransferManager.at(tx.logs[3].args._module); await revertToSnapshot(snapId); }); it("Should successfully attach the LockUpVolumeRestrictionTMFactory with the non-divisible security token", async () => { - const tx = await I_SecurityToken.addModule(I_LockUpTransferManagerFactory.address, 0, 0, 0, { from: token_owner }); - assert.equal(tx.logs[2].args._types[0].toNumber(), transferManagerKey, "LockUpVolumeRestrictionTMFactory doesn't get deployed"); + const tx = await I_SecurityToken.addModule(I_LockUpTransferManagerFactory.address, "0x", 0, 0, false, { from: token_owner }); + assert.equal(tx.logs[2].args._types[0].toString(), transferManagerKey, "LockUpVolumeRestrictionTMFactory doesn't get deployed"); assert.equal( web3.utils.toAscii(tx.logs[2].args._name) .replace(/\u0000/g, ''), "LockUpTransferManager", "LockUpTransferManager module was not added" ); - I_LockUpTransferManager = LockUpTransferManager.at(tx.logs[2].args._module); + I_LockUpTransferManager = await LockUpTransferManager.at(tx.logs[2].args._module); }); it("Should successfully attach the LockUpVolumeRestrictionTMFactory with the divisible security token", async () => { - const tx = await I_SecurityToken_div.addModule(I_LockUpTransferManagerFactory.address, 0, 0, 0, { from: token_owner }); - assert.equal(tx.logs[2].args._types[0].toNumber(), transferManagerKey, "LockUpVolumeRestrictionTMFactory doesn't get deployed"); + const tx = await I_SecurityToken_div.addModule(I_LockUpTransferManagerFactory.address, "0x", 0, 0, false, { from: token_owner }); + assert.equal(tx.logs[2].args._types[0].toString(), transferManagerKey, "LockUpVolumeRestrictionTMFactory doesn't get deployed"); assert.equal( web3.utils.toAscii(tx.logs[2].args._name) .replace(/\u0000/g, ''), "LockUpTransferManager", "LockUpTransferManager module was not added" ); - I_LockUpVolumeRestrictionTM_div = LockUpTransferManager.at(tx.logs[2].args._module); + I_LockUpVolumeRestrictionTM_div = await LockUpTransferManager.at(tx.logs[2].args._module); }); it("Add a new token holder", async() => { - let tx = await I_GeneralTransferManager.modifyWhitelist( + let tx = await I_GeneralTransferManager.modifyKYCData( account_investor3, - latestTime(), - latestTime(), - latestTime() + duration.days(10), - true, + currentTime, + currentTime, + currentTime.add(new BN(duration.days(10))), { from: account_issuer }); @@ -340,10 +314,10 @@ contract('LockUpTransferManager', accounts => { // Add the Investor in to the whitelist // Mint some tokens - await I_SecurityToken.mint(account_investor3, web3.utils.toWei('10', 'ether'), { from: token_owner }); + await I_SecurityToken.issue(account_investor3, web3.utils.toWei('10', 'ether'), "0x0", { from: token_owner }); assert.equal( - (await I_SecurityToken.balanceOf(account_investor3)).toNumber(), + (await I_SecurityToken.balanceOf(account_investor3)).toString(), web3.utils.toWei('10', 'ether') ); }); @@ -357,7 +331,7 @@ contract('LockUpTransferManager', accounts => { await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('1', 'ether'), { from: account_investor2 }); assert.equal( - (await I_SecurityToken.balanceOf(account_investor1)).toNumber(), + (await I_SecurityToken.balanceOf(account_investor1)).toString(), web3.utils.toWei('3', 'ether') ); }); @@ -373,11 +347,11 @@ contract('LockUpTransferManager', accounts => { I_LockUpTransferManager.addNewLockUpToUser( account_investor2, 0, - latestTime() + + duration.seconds(1), - duration.seconds(400000), - duration.seconds(100000), - "a_lockup", - { + currentTime.add(new BN(duration.seconds(1))), + new BN(duration.seconds(400000)), + new BN(duration.seconds(100000)), + web3.utils.fromAscii("a_lockup"), + { from: token_owner } ) @@ -390,11 +364,11 @@ contract('LockUpTransferManager', accounts => { await catchRevert( I_LockUpTransferManager.addNewLockUpToUser( account_investor2, - web3.utils.toWei('1', 'ether'), - latestTime() + duration.seconds(1), - duration.seconds(400000), + new BN(web3.utils.toWei('1', 'ether')), + currentTime.add(new BN(duration.seconds(1))), + new BN(duration.seconds(400000)), 0, - "a_lockup", + web3.utils.fromAscii("a_lockup"), { from: token_owner } @@ -408,11 +382,11 @@ contract('LockUpTransferManager', accounts => { await catchRevert( I_LockUpTransferManager.addNewLockUpToUser( account_investor2, - web3.utils.toWei('1', 'ether'), - latestTime() + duration.seconds(1), + new BN(web3.utils.toWei('1', 'ether')), + currentTime.add(new BN(duration.seconds(1))), 0, - duration.seconds(100000), - "a_lockup", + new BN(duration.seconds(100000)), + web3.utils.fromAscii("a_lockup"), { from: token_owner } @@ -423,12 +397,12 @@ contract('LockUpTransferManager', accounts => { it("Should add the lockup type -- fail because of bad owner", async() => { await catchRevert( I_LockUpTransferManager.addNewLockUpType( - web3.utils.toWei('12', 'ether'), - latestTime() + duration.days(1), + new BN(web3.utils.toWei('12', 'ether')), + currentTime.add(new BN(duration.days(1))), 60, 20, - "a_lockup", - { + web3.utils.fromAscii("a_lockup"), + { from: account_investor1 } ) @@ -437,16 +411,16 @@ contract('LockUpTransferManager', accounts => { it("Should add the new lockup type", async() => { let tx = await I_LockUpTransferManager.addNewLockUpType( - web3.utils.toWei('12', 'ether'), - latestTime() + duration.days(1), + new BN(web3.utils.toWei('12', 'ether')), + currentTime.add(new BN(duration.days(1))), 60, 20, - "a_lockup", - { + web3.utils.fromAscii("a_lockup"), + { from: token_owner } ); - assert.equal((tx.logs[0].args._lockupAmount).toNumber(), web3.utils.toWei('12', 'ether')); + assert.equal((tx.logs[0].args._lockupAmount).toString(), web3.utils.toWei('12', 'ether')); }); it("Should fail to add the creation of the lockup where lockupName is already exists", async() => { @@ -454,11 +428,11 @@ contract('LockUpTransferManager', accounts => { I_LockUpTransferManager.addNewLockUpToUser( account_investor1, web3.utils.toWei('5', 'ether'), - latestTime() + duration.seconds(1), - duration.seconds(400000), - duration.seconds(100000), - "a_lockup", - { + currentTime.add(new BN(duration.seconds(1))), + new BN(duration.seconds(400000)), + new BN(duration.seconds(100000)), + web3.utils.fromAscii("a_lockup"), + { from: token_owner } ) @@ -467,58 +441,59 @@ contract('LockUpTransferManager', accounts => { it("Should allow the creation of a lockup where the lockup amount is divisible" , async() => { // create a lockup + currentTime = new BN(await latestTime()); let tx = await I_LockUpVolumeRestrictionTM_div.addNewLockUpToUser( account_investor1, web3.utils.toWei('0.5', 'ether'), - latestTime() + duration.seconds(1), - duration.seconds(400000), - duration.seconds(100000), - "a_lockup", - { + currentTime.add(new BN(duration.seconds(1))), + new BN(duration.seconds(400000)), + new BN(duration.seconds(100000)), + web3.utils.fromAscii("a_lockup2"), + { from: token_owner } ); assert.equal(tx.logs[1].args._userAddress, account_investor1); - assert.equal((tx.logs[0].args._lockupAmount).toNumber(), web3.utils.toWei('0.5', 'ether')); + assert.equal((tx.logs[0].args._lockupAmount).toString(), web3.utils.toWei('0.5', 'ether')); }); it("Should allow the creation of a lockup where the lockup amount is prime no", async() => { // create a lockup let tx = await I_LockUpVolumeRestrictionTM_div.addNewLockUpToUser( account_investor1, - web3.utils.toWei('64951', 'ether'), - latestTime() + duration.seconds(1), - duration.seconds(400000), - duration.seconds(100000), - "b_lockup", - { + new BN(web3.utils.toWei('64951', 'ether')), + currentTime.add(new BN(duration.seconds(1))), + new BN(duration.seconds(400000)), + new BN(duration.seconds(100000)), + web3.utils.fromAscii("b_lockup"), + { from: token_owner } ); assert.equal(tx.logs[1].args._userAddress, account_investor1); - assert.equal((tx.logs[0].args._lockupAmount).toNumber(), web3.utils.toWei('64951', 'ether')); + assert.equal((tx.logs[0].args._lockupAmount).toString(), web3.utils.toWei('64951', 'ether')); }); it("Should prevent the transfer of tokens in a lockup", async() => { let balance = await I_SecurityToken.balanceOf(account_investor2) - console.log("balance", balance.dividedBy(new BigNumber(1).times(new BigNumber(10).pow(18))).toNumber()); + console.log("balance", balance.div(new BN(1).mul(new BN(10).pow(new BN(18)))).toString()); // create a lockup for their entire balance // over 12 seconds total, with 3 periods of 20 seconds each. await I_LockUpTransferManager.addNewLockUpToUser( account_investor2, balance, - latestTime() + duration.seconds(1), + currentTime.add(new BN(duration.seconds(1))), 60, 20, - "b_lockup", + web3.utils.fromAscii("b_lockup"), { from: token_owner } ); await increaseTime(2); - let tx = await I_LockUpTransferManager.getLockUp.call("b_lockup"); - console.log("Amount get unlocked:", (tx[4].toNumber())); + let tx = await I_LockUpTransferManager.getLockUp.call(web3.utils.fromAscii("b_lockup")); + console.log("Amount get unlocked:", (tx[4].toString())); await catchRevert( I_SecurityToken.transfer(account_investor1, web3.utils.toWei('1', 'ether'), { from: account_investor2 }) ); @@ -527,22 +502,22 @@ contract('LockUpTransferManager', accounts => { it("Should prevent the transfer of tokens if the amount is larger than the amount allowed by lockups", async() => { // wait 20 seconds await increaseTime(duration.seconds(20)); - let tx = await I_LockUpTransferManager.getLockUp.call("b_lockup"); - console.log("Amount get unlocked:", (tx[4].toNumber())); + let tx = await I_LockUpTransferManager.getLockUp.call(web3.utils.fromAscii("b_lockup")); + console.log("Amount get unlocked:", (tx[4].toString())); await catchRevert( I_SecurityToken.transfer(account_investor1, web3.utils.toWei('4', 'ether'), { from: account_investor2 }) ); }); it("Should allow the transfer of tokens in a lockup if a period has passed", async() => { - let tx = await I_LockUpTransferManager.getLockUp.call("b_lockup"); - console.log("Amount get unlocked:", (tx[4].toNumber())); + let tx = await I_LockUpTransferManager.getLockUp.call(web3.utils.fromAscii("b_lockup")); + console.log("Amount get unlocked:", (tx[4].toString())); await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('1', 'ether'), { from: account_investor2 }); }); it("Should again transfer of tokens in a lockup if a period has passed", async() => { - let tx = await I_LockUpTransferManager.getLockUp.call("b_lockup"); - console.log("Amount get unlocked:", (tx[4].toNumber())); + let tx = await I_LockUpTransferManager.getLockUp.call(web3.utils.fromAscii("b_lockup")); + console.log("Amount get unlocked:", (tx[4].toString())); await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('1', 'ether'), { from: account_investor2 }); }); @@ -550,17 +525,17 @@ contract('LockUpTransferManager', accounts => { // wait 20 more seconds + 1 to get rid of same block time await increaseTime(duration.seconds(21)); - let tx = await I_LockUpTransferManager.getLockUp.call( "b_lockup"); - console.log("Amount get unlocked:", (tx[4].toNumber())); + let tx = await I_LockUpTransferManager.getLockUp.call(web3.utils.fromAscii("b_lockup")); + console.log("Amount get unlocked:", (tx[4].toString())); await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('2', 'ether'), { from: account_investor2 }); }); it("Buy more tokens from secondary market to investor2", async() => { // Mint some tokens - await I_SecurityToken.mint(account_investor2, web3.utils.toWei('5', 'ether'), { from: token_owner }); + await I_SecurityToken.issue(account_investor2, web3.utils.toWei('5', 'ether'), "0x0", { from: token_owner }); assert.equal( - (await I_SecurityToken.balanceOf(account_investor2)).toNumber(), + (await I_SecurityToken.balanceOf(account_investor2)).toString(), web3.utils.toWei('10', 'ether') ); }) @@ -569,7 +544,7 @@ contract('LockUpTransferManager', accounts => { await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('4', 'ether'), { from: account_investor2 }); assert.equal( - (await I_SecurityToken.balanceOf(account_investor2)).toNumber(), + (await I_SecurityToken.balanceOf(account_investor2)).toString(), web3.utils.toWei('6', 'ether') ); }); @@ -580,10 +555,10 @@ contract('LockUpTransferManager', accounts => { // wait 20 more seconds + 1 to get rid of same block time await increaseTime(duration.seconds(21)); - console.log((await I_LockUpTransferManager.getLockedTokenToUser.call(account_investor2)).toNumber()); + console.log((await I_LockUpTransferManager.getLockedTokenToUser.call(account_investor2)).toString()); await I_SecurityToken.transfer(account_investor1, balance, { from: account_investor2 }); assert.equal( - (await I_SecurityToken.balanceOf(account_investor2)).toNumber(), + (await I_SecurityToken.balanceOf(account_investor2)).toString(), web3.utils.toWei('0', 'ether') ); }); @@ -593,10 +568,10 @@ contract('LockUpTransferManager', accounts => { I_LockUpTransferManager.addNewLockUpToUserMulti( [account_investor3], [web3.utils.toWei("6", "ether"), web3.utils.toWei("3", "ether")], - [latestTime() + duration.seconds(1), latestTime() + duration.seconds(21)], + [currentTime.add(new BN(duration.seconds(1))), currentTime.add(new BN(duration.seconds(21)))], [60, 45], [20, 15], - ["c_lockup", "d_lockup"], + [web3.utils.fromAscii("c_lockup"), web3.utils.fromAscii("d_lockup")], { from: token_owner } @@ -609,10 +584,10 @@ contract('LockUpTransferManager', accounts => { I_LockUpTransferManager.addNewLockUpToUserMulti( [account_investor3, account_investor3], [], - [latestTime() + duration.seconds(1), latestTime() + duration.seconds(21)], + [currentTime.add(new BN(duration.seconds(1))), currentTime.add(new BN(duration.seconds(21)))], [60, 45], [20, 15], - ["c_lockup", "d_lockup"], + [web3.utils.fromAscii("c_lockup"), web3.utils.fromAscii("d_lockup")], { from: token_owner } @@ -625,10 +600,10 @@ contract('LockUpTransferManager', accounts => { I_LockUpTransferManager.addNewLockUpToUserMulti( [account_investor3, account_investor3], [web3.utils.toWei("6", "ether"), web3.utils.toWei("3", "ether")], - [latestTime() + duration.seconds(1), latestTime() + duration.seconds(21)], + [currentTime.add(new BN(duration.seconds(1))), currentTime.add(new BN(duration.seconds(21)))], [60, 45, 50], [20, 15], - ["c_lockup", "d_lockup"], + [web3.utils.fromAscii("c_lockup"), web3.utils.fromAscii("d_lockup")], { from: token_owner } @@ -637,14 +612,14 @@ contract('LockUpTransferManager', accounts => { }) it("Should fail to add the multiple lockups -- because array length mismatch", async() => { - await catchRevert( + await catchRevert( I_LockUpTransferManager.addNewLockUpToUserMulti( [account_investor3, account_investor3], [web3.utils.toWei("6", "ether"), web3.utils.toWei("3", "ether")], - [latestTime() + duration.seconds(1), latestTime() + duration.seconds(21)], + [currentTime.add(new BN(duration.seconds(1))), currentTime.add(new BN(duration.seconds(21)))], [60, 45, 50], [20, 15, 10], - ["c_lockup", "d_lockup"], + [web3.utils.fromAscii("c_lockup"), web3.utils.fromAscii("d_lockup")], { from: token_owner } @@ -653,14 +628,14 @@ contract('LockUpTransferManager', accounts => { }) it("Should fail to add the multiple lockups -- because array length mismatch", async() => { - await catchRevert( + await catchRevert( I_LockUpTransferManager.addNewLockUpToUserMulti( [account_investor3, account_investor3], [web3.utils.toWei("6", "ether"), web3.utils.toWei("3", "ether")], - [latestTime() + duration.seconds(1), latestTime() + duration.seconds(21)], + [currentTime.add(new BN(duration.seconds(1))), currentTime.add(new BN(duration.seconds(21)))], [60, 45], [20, 15], - ["c_lockup"], + [web3.utils.fromAscii("c_lockup")], { from: token_owner } @@ -669,22 +644,23 @@ contract('LockUpTransferManager', accounts => { }); it("Should add the multiple lockup to a address", async() => { + currentTime = new BN(await latestTime()); await I_LockUpTransferManager.addNewLockUpToUserMulti( [account_investor3, account_investor3], [web3.utils.toWei("6", "ether"), web3.utils.toWei("3", "ether")], - [latestTime() + duration.seconds(1), latestTime() + duration.seconds(21)], + [currentTime.add(new BN(duration.seconds(1))), currentTime.add(new BN(duration.seconds(21)))], [60, 45], [20, 15], - ["c_lockup", "d_lockup"], + [web3.utils.fromAscii("c_lockup"), web3.utils.fromAscii("d_lockup")], { from: token_owner } ); await increaseTime(1); - let tx = await I_LockUpTransferManager.getLockUp.call("c_lockup"); - let tx2 = await I_LockUpTransferManager.getLockUp.call("d_lockup"); - console.log("Total Amount get unlocked:", (tx[4].toNumber()) + (tx2[4].toNumber())); + let tx = await I_LockUpTransferManager.getLockUp.call(web3.utils.fromAscii("c_lockup")); + let tx2 = await I_LockUpTransferManager.getLockUp.call(web3.utils.fromAscii("d_lockup")); + console.log("Total Amount get unlocked:", (tx[4].toString()) + (tx2[4].toString())); await catchRevert( I_SecurityToken.transfer(account_investor2, web3.utils.toWei('2', 'ether'), { from: account_investor3 }) ); @@ -695,9 +671,27 @@ contract('LockUpTransferManager', accounts => { // increase 20 sec that makes 1 period passed // 2 from a period and 1 is already unlocked await increaseTime(21); + console.log(`\t Total balance: ${web3.utils.fromWei((await I_SecurityToken.balanceOf.call(account_investor3)).toString())}`); + console.log(`\t Locked balance: ${web3.utils.fromWei((await I_SecurityToken.balanceOfByPartition.call(web3.utils.utf8ToHex(`LOCKED`), account_investor3)).toString())}`); + console.log(`\t Unlocked balance: ${web3.utils.fromWei((await I_SecurityToken.balanceOfByPartition.call(web3.utils.utf8ToHex(`UNLOCKED`), account_investor3)).toString())}`); await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('3'), { from: account_investor3 }) }) + it("Should check the balance of the locked tokens", async() => { + console.log(`\t Total balance: ${web3.utils.fromWei((await I_SecurityToken.balanceOf.call(account_investor3)).toString())}`); + console.log(`\t Locked balance: ${web3.utils.fromWei((await I_SecurityToken.balanceOfByPartition.call(web3.utils.utf8ToHex(`LOCKED`), account_investor3)).toString())}`); + console.log(`\t Unlocked balance: ${web3.utils.fromWei((await I_SecurityToken.balanceOfByPartition.call(web3.utils.utf8ToHex(`UNLOCKED`), account_investor3)).toString())}`); + assert.equal( + web3.utils.fromWei((await I_SecurityToken.balanceOf.call(account_investor3)).toString()), + web3.utils.fromWei((await I_SecurityToken.balanceOfByPartition.call(web3.utils.utf8ToHex(`LOCKED`), account_investor3)).toString()) + ); + console.log(`\t Wrong partition: ${web3.utils.fromWei((await I_SecurityToken.balanceOfByPartition.call(web3.utils.toHex(`OCKED`), account_investor3)).toString())}`); + assert.equal( + web3.utils.fromWei((await I_SecurityToken.balanceOfByPartition.call(web3.utils.toHex(`OCKED`), account_investor3)).toString()), + 0 + ); + }); + it("Should transfer the tokens after passing another period of the lockup", async() => { // increase the 15 sec that makes first period of another lockup get passed // allow 1 token to transfer @@ -706,50 +700,77 @@ contract('LockUpTransferManager', accounts => { await catchRevert( I_SecurityToken.transfer(account_investor1, web3.utils.toWei('3'), { from: account_investor3 }) ) + let lockedBalance = web3.utils.fromWei((await I_LockUpTransferManager.getTokensByPartition.call(web3.utils.utf8ToHex(`LOCKED`), account_investor3, new BN(0))).toString()); + let unlockedBalance = web3.utils.fromWei((await I_LockUpTransferManager.getTokensByPartition.call(web3.utils.utf8ToHex(`UNLOCKED`), account_investor3, new BN(0))).toString()); + console.log(`\t Total balance: ${web3.utils.fromWei((await I_SecurityToken.balanceOf.call(account_investor3)).toString())}`); + console.log(`\t Locked balance of the investor by the lockup: ${web3.utils.fromWei((await I_LockUpTransferManager.getLockedTokenToUser.call(account_investor3)).toString())}`); + console.log(`Paused status: ${await I_LockUpTransferManager.paused.call()}`); + console.log(`\t Locked balance: ${lockedBalance}`); + console.log(`\t Unlocked Balance: ${unlockedBalance}`); + assert.equal( + web3.utils.fromWei((await I_SecurityToken.balanceOf.call(account_investor3)).toString()), + parseInt(lockedBalance) + parseInt(unlockedBalance) + ); // second txn will pass because 1 token is in unlocked state await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('1'), { from: account_investor3 }) }); it("Should transfer the tokens from both the lockup simultaneously", async() => { // Increase the 20 sec (+ 1 to mitigate the block time) that unlocked another 2 tokens from the lockup 1 and simultaneously unlocked 1 - // more token from the lockup 2 + // more token from the lockup 2 await increaseTime(21); - + + let lockedBalance = web3.utils.fromWei((await I_SecurityToken.balanceOfByPartition.call(web3.utils.utf8ToHex(`LOCKED`), account_investor3)).toString()); + let unlockedBalance = web3.utils.fromWei((await I_SecurityToken.balanceOfByPartition.call(web3.utils.utf8ToHex(`UNLOCKED`), account_investor3)).toString()); + console.log(`\t Total balance: ${web3.utils.fromWei((await I_SecurityToken.balanceOf.call(account_investor3)).toString())}`); + console.log(`\t Locked balance: ${lockedBalance}`); + console.log(` \t Unlocked Amount for lockup 1: ${web3.utils.fromWei(((await I_LockUpTransferManager.getLockUp.call(web3.utils.fromAscii("c_lockup")))[4]).toString())}`) + console.log(` \t Unlocked Amount for lockup 2: ${web3.utils.fromWei(((await I_LockUpTransferManager.getLockUp.call(web3.utils.fromAscii("d_lockup")))[4]).toString())}`) + console.log(`\t Unlocked Balance: ${unlockedBalance}`); + await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('3'), { from: account_investor3 }) assert.equal( - (await I_SecurityToken.balanceOf(account_investor3)).toNumber(), + (await I_SecurityToken.balanceOf(account_investor3)).toString(), web3.utils.toWei('3', 'ether') ); + console.log("After transaction"); + lockedBalance = web3.utils.fromWei((await I_SecurityToken.balanceOfByPartition.call(web3.utils.utf8ToHex(`LOCKED`), account_investor3)).toString()); + unlockedBalance = web3.utils.fromWei((await I_SecurityToken.balanceOfByPartition.call(web3.utils.utf8ToHex(`UNLOCKED`), account_investor3)).toString()); + console.log(`\t Total balance: ${web3.utils.fromWei((await I_SecurityToken.balanceOf.call(account_investor3)).toString())}`); + console.log(`\t Locked balance: ${lockedBalance}`); + console.log(` \t Unlocked Amount for lockup 1: ${web3.utils.fromWei(((await I_LockUpTransferManager.getLockUp.call(web3.utils.fromAscii("c_lockup")))[4]).toString())}`) + console.log(` \t Unlocked Amount for lockup 2: ${web3.utils.fromWei(((await I_LockUpTransferManager.getLockUp.call(web3.utils.fromAscii("d_lockup")))[4]).toString())}`) + console.log(`\t Unlocked Balance: ${unlockedBalance}`); }); it("Should remove multiple lockup --failed because of bad owner", async() => { await catchRevert( I_LockUpTransferManager.removeLockUpFromUserMulti( [account_investor3, account_investor3], - ["c_lockup", "d_lockup"], + [web3.utils.fromAscii("c_lockup"), web3.utils.fromAscii("d_lockup")], { from: account_polymath } ) - ); + ); }); it("Should remove the multiple lockup -- failed because of invalid lockupname", async() => { await catchRevert( I_LockUpTransferManager.removeLockUpFromUserMulti( [account_investor3, account_investor3], - ["c_lockup", "e_lockup"], + [web3.utils.fromAscii("c_lockup"), web3.utils.fromAscii("e_lockup")], { from: account_polymath } ) - ); + ); }) it("Should remove the multiple lockup", async() => { await I_LockUpTransferManager.removeLockUpFromUserMulti( [account_investor3, account_investor3], - ["d_lockup", "c_lockup"], + [web3.utils.fromAscii("d_lockup"), web3.utils.fromAscii("c_lockup")], { from: token_owner } @@ -759,29 +780,29 @@ contract('LockUpTransferManager', accounts => { }); it("Should fail to modify the lockup -- because of bad owner", async() => { - await I_SecurityToken.mint(account_investor3, web3.utils.toWei("9"), {from: token_owner}); - + await I_SecurityToken.issue(account_investor3, web3.utils.toWei("9"), "0x0", {from: token_owner}); + let tx = await I_LockUpTransferManager.addNewLockUpToUser( account_investor3, web3.utils.toWei("9"), - latestTime() + duration.minutes(5), + currentTime.add(new BN(duration.minutes(5))), 60, 20, - "z_lockup", - { + web3.utils.fromAscii("z_lockup"), + { from: token_owner } ); - + await catchRevert( // edit the lockup I_LockUpTransferManager.modifyLockUpType( web3.utils.toWei("9"), - latestTime() + duration.seconds(1), + currentTime.add(new BN(duration.seconds(1))), 60, 20, - "z_lockup", - { + web3.utils.fromAscii("z_lockup"), + { from: account_polymath } ) @@ -793,11 +814,11 @@ contract('LockUpTransferManager', accounts => { // edit the lockup I_LockUpTransferManager.modifyLockUpType( web3.utils.toWei("9"), - latestTime() - duration.seconds(50), + currentTime.add(new BN(duration.seconds(50))), 60, 20, - "z_lockup", - { + web3.utils.fromAscii("z_lockup"), + { from: token_owner } ) @@ -809,11 +830,11 @@ contract('LockUpTransferManager', accounts => { // edit the lockup I_LockUpTransferManager.modifyLockUpType( web3.utils.toWei("9"), - latestTime() + duration.seconds(50), + currentTime.add(new BN(duration.seconds(50))), 60, 20, - "m_lockup", - { + web3.utils.fromAscii("m_lockup"), + { from: token_owner } ) @@ -822,13 +843,14 @@ contract('LockUpTransferManager', accounts => { it("should successfully modify the lockup", async() => { // edit the lockup + currentTime = new BN(await latestTime()); await I_LockUpTransferManager.modifyLockUpType( web3.utils.toWei("9"), - latestTime() + duration.seconds(50), + currentTime.add(new BN(duration.seconds(50))), 60, 20, - "z_lockup", - { + web3.utils.fromAscii("z_lockup"), + { from: token_owner } ); @@ -839,18 +861,18 @@ contract('LockUpTransferManager', accounts => { // balance here should be 12000000000000000000 (12e18 or 12 eth) let balance = await I_SecurityToken.balanceOf(account_investor1) - console.log("balance", balance.dividedBy(new BigNumber(1).times(new BigNumber(10).pow(18))).toNumber()); + console.log("balance", balance.div(new BN(1).mul(new BN(10).pow(new BN(18)))).toString()); // create a lockup for their entire balance // over 16 seconds total, with 4 periods of 4 seconds each. await I_LockUpTransferManager.addNewLockUpToUser( account_investor1, balance, - latestTime() + duration.minutes(5), + currentTime.add(new BN(duration.minutes(5))), 60, 20, - "f_lockup", - { + web3.utils.fromAscii("f_lockup"), + { from: token_owner } ); @@ -859,26 +881,26 @@ contract('LockUpTransferManager', accounts => { I_SecurityToken.transfer(account_investor2, web3.utils.toWei('1', 'ether'), { from: account_investor1 }) ); - let lockUp = await I_LockUpTransferManager.getLockUp("f_lockup"); + let lockUp = await I_LockUpTransferManager.getLockUp(web3.utils.fromAscii("f_lockup")); console.log(lockUp); // elements in lockup array are uint lockUpPeriodSeconds, uint releaseFrequencySeconds, uint startTime, uint totalAmount assert.equal( - lockUp[0].dividedBy(new BigNumber(1).times(new BigNumber(10).pow(18))).toNumber(), - balance.dividedBy(new BigNumber(1).times(new BigNumber(10).pow(18))).toNumber() + lockUp[0].div(new BN(1).mul(new BN(10).pow(new BN(18)))).toString(), + balance.div(new BN(1).mul(new BN(10).pow(new BN(18)))).toString() ); - assert.equal(lockUp[2].toNumber(), 60); - assert.equal(lockUp[3].toNumber(), 20); - assert.equal(lockUp[4].toNumber(), 0); + assert.equal(lockUp[2].toString(), 60); + assert.equal(lockUp[3].toString(), 20); + assert.equal(lockUp[4].toString(), 0); // edit the lockup - temp = latestTime() + duration.seconds(1); + temp = currentTime.add(new BN(duration.seconds(1))); await I_LockUpTransferManager.modifyLockUpType( balance, - temp, + temp, 60, 20, - "f_lockup", - { + web3.utils.fromAscii("f_lockup"), + { from: token_owner } ); @@ -898,16 +920,16 @@ contract('LockUpTransferManager', accounts => { it("Modify the lockup during the lockup periods", async() => { let balance = await I_SecurityToken.balanceOf(account_investor1) - let lockUp = await I_LockUpTransferManager.getLockUp("f_lockup"); - console.log(lockUp[4].dividedBy(new BigNumber(1).times(new BigNumber(10).pow(18))).toNumber()); + let lockUp = await I_LockUpTransferManager.getLockUp(web3.utils.fromAscii("f_lockup")); + console.log(lockUp[4].div(new BN(1).mul(new BN(10).pow(new BN(18)))).toString()); // edit the lockup await I_LockUpTransferManager.modifyLockUpType( balance, - latestTime() + duration.days(10), + currentTime.add(new BN(duration.days(10))), 90, 30, - "f_lockup", - { + web3.utils.fromAscii("f_lockup"), + { from: token_owner } ); @@ -916,27 +938,30 @@ contract('LockUpTransferManager', accounts => { it("Should remove the lockup multi", async() => { await I_LockUpTransferManager.addNewLockUpTypeMulti( [web3.utils.toWei("10"), web3.utils.toWei("16")], - [latestTime() + duration.days(1), latestTime() + duration.days(1)], + [currentTime.add(new BN(duration.days(1))), currentTime.add(new BN(duration.days(1)))], [50000, 50000], [10000, 10000], - ["k_lockup", "l_lockup"], + [web3.utils.fromAscii("k_lockup"), web3.utils.fromAscii("l_lockup")], { from: token_owner } ); - // removing the lockup type - let tx = await I_LockUpTransferManager.removeLockupType("k_lockup", {from: token_owner}); + // removing the lockup type + let tx = await I_LockUpTransferManager.removeLockupType(web3.utils.fromAscii("k_lockup"), {from: token_owner}); assert.equal(web3.utils.toUtf8(tx.logs[0].args._lockupName), "k_lockup"); // attaching the lockup to a user - await I_LockUpTransferManager.addLockUpByName(account_investor2, "l_lockup", {from: token_owner}); + await I_LockUpTransferManager.addLockUpByName(account_investor2, web3.utils.fromAscii("l_lockup"), {from: token_owner}); + + //Should not allow to add a user to a lockup multiple times + await catchRevert(I_LockUpTransferManager.addLockUpByName(account_investor2, web3.utils.fromAscii("l_lockup"), {from: token_owner})); // try to delete the lockup but fail await catchRevert( - I_LockUpTransferManager.removeLockupType("l_lockup", {from: token_owner}) + I_LockUpTransferManager.removeLockupType(web3.utils.fromAscii("l_lockup"), {from: token_owner}) ); }) @@ -945,7 +970,7 @@ contract('LockUpTransferManager', accounts => { }); it("Should succesfully get the non existed lockup value, it will give everything 0", async() => { - let data = await I_LockUpTransferManager.getLockUp(9); + let data = await I_LockUpTransferManager.getLockUp(web3.utils.fromAscii("foo")); assert.equal(data[0], 0); }) @@ -972,9 +997,9 @@ contract('LockUpTransferManager', accounts => { describe("LockUpTransferManager Transfer Manager Factory test cases", async() => { it("Should get the exact details of the factory", async() => { - assert.equal(await I_LockUpTransferManagerFactory.getSetupCost.call(),0); + assert.equal(await I_LockUpTransferManagerFactory.setupCost.call(),0); assert.equal((await I_LockUpTransferManagerFactory.getTypes.call())[0],2); - assert.equal(web3.utils.toAscii(await I_LockUpTransferManagerFactory.getName.call()) + assert.equal(web3.utils.toAscii(await I_LockUpTransferManagerFactory.name.call()) .replace(/\u0000/g, ''), "LockUpTransferManager", "Wrong Module added"); @@ -984,10 +1009,7 @@ contract('LockUpTransferManager', accounts => { assert.equal(await I_LockUpTransferManagerFactory.title.call(), "LockUp Transfer Manager", "Wrong Module added"); - assert.equal(await I_LockUpTransferManagerFactory.getInstructions.call(), - "Allows an issuer to set lockup periods for user addresses, with funds distributed over time. Init function takes no parameters.", - "Wrong Module added"); - assert.equal(await I_LockUpTransferManagerFactory.version.call(), "1.0.0"); + assert.equal(await I_LockUpTransferManagerFactory.version.call(), "3.0.0"); }); it("Should get the tags of the factory", async() => { diff --git a/test/x_scheduled_checkpoints.js b/test/x_scheduled_checkpoints.js index 5fcc03a74..ecb48892f 100644 --- a/test/x_scheduled_checkpoints.js +++ b/test/x_scheduled_checkpoints.js @@ -1,19 +1,25 @@ -import latestTime from './helpers/latestTime'; -import { duration, promisifyLogWatch, latestBlock } from './helpers/utils'; -import takeSnapshot, { increaseTime, revertToSnapshot } from './helpers/time'; -import { encodeProxyCall, encodeModuleCall } from './helpers/encodeCall'; +import latestTime from "./helpers/latestTime"; +import { duration, promisifyLogWatch, latestBlock } from "./helpers/utils"; +import { takeSnapshot, increaseTime, revertToSnapshot, jumpToTime } from "./helpers/time"; +import { encodeProxyCall, encodeModuleCall } from "./helpers/encodeCall"; import { setUpPolymathNetwork, deployScheduleCheckpointAndVerified } from "./helpers/createInstances"; -const SecurityToken = artifacts.require('./SecurityToken.sol'); -const GeneralTransferManager = artifacts.require('./GeneralTransferManager'); -const ScheduledCheckpoint = artifacts.require('./ScheduledCheckpoint.sol'); +const SecurityToken = artifacts.require("./SecurityToken.sol"); +const GeneralTransferManager = artifacts.require("./GeneralTransferManager"); +const ScheduledCheckpoint = artifacts.require("./ScheduledCheckpoint.sol"); +const STGetter = artifacts.require("./STGetter.sol") +const Web3 = require("web3"); +let BN = Web3.utils.BN; +const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); // Hardcoded development port -const Web3 = require('web3'); -const BigNumber = require('bignumber.js'); -const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")) // Hardcoded development port +process.env.COVERAGE ? contract.skip : contract("ScheduledCheckpoint", async (accounts) => { -contract('ScheduledCheckpoint', accounts => { + const SECONDS = 0; + const DAYS = 1; + const WEEKS = 2; + const MONTHS = 3; + const YEARS = 4; // Accounts Variable declaration let account_polymath; @@ -24,11 +30,6 @@ contract('ScheduledCheckpoint', accounts => { let account_investor3; let account_investor4; - // investor Details - let fromTime = latestTime(); - let toTime = latestTime(); - let expiryTime = toTime + duration.days(15); - let message = "Transaction Should Fail!"; // Contract Instance Declaration @@ -48,6 +49,9 @@ contract('ScheduledCheckpoint', accounts => { let I_SecurityToken; let I_PolyToken; let I_PolymathRegistry; + let I_STRGetter; + let I_STGetter; + let stGetter; // SecurityToken Details const name = "Team"; @@ -62,10 +66,13 @@ contract('ScheduledCheckpoint', accounts => { const stoKey = 3; // Initial fee for ticker registry and security token registry - const initRegFee = web3.utils.toWei("250"); + const initRegFee = new BN(web3.utils.toWei("1000")); + + let currentTime; + const address_zero = "0x0000000000000000000000000000000000000000"; + const one_address = "0x0000000000000000000000000000000000000001"; - before(async() => { - // Accounts setup + before(async () => { account_polymath = accounts[0]; account_issuer = accounts[1]; @@ -74,6 +81,9 @@ contract('ScheduledCheckpoint', accounts => { account_investor1 = accounts[7]; account_investor2 = accounts[8]; account_investor3 = accounts[9]; + //await jumpToTime(Math.floor((new Date().getTime())/1000)); + await jumpToTime(1553040000); // 03/20/2019 @ 12:00am (UTC) + currentTime = new BN(await latestTime()); // Step 1: Deploy the genral PM ecosystem let instances = await setUpPolymathNetwork(account_polymath, token_owner); @@ -89,11 +99,13 @@ contract('ScheduledCheckpoint', accounts => { I_STFactory, I_SecurityTokenRegistry, I_SecurityTokenRegistryProxy, - I_STRProxied + I_STRProxied, + I_STRGetter, + I_STGetter ] = instances; // STEP 2: Deploy the ScheduleCheckpointModule - [I_ScheduledCheckpointFactory] = await deployScheduleCheckpointAndVerified(account_polymath, I_MRProxied, I_PolyToken.address, 0); + [I_ScheduledCheckpointFactory] = await deployScheduleCheckpointAndVerified(account_polymath, I_MRProxied, 0); // Printing all the contract addresses console.log(` @@ -113,277 +125,718 @@ contract('ScheduledCheckpoint', accounts => { `); }); - describe("Generate the SecurityToken", async() => { - + describe("Generate the SecurityToken", async () => { it("Should register the ticker before the generation of the security token", async () => { await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); - let tx = await I_STRProxied.registerTicker(token_owner, symbol, contact, { from : token_owner }); + let tx = await I_STRProxied.registerNewTicker(token_owner, symbol, { from: token_owner }); assert.equal(tx.logs[0].args._owner, token_owner); assert.equal(tx.logs[0].args._ticker, symbol.toUpperCase()); }); it("Should generate the new security token with the same symbol as registered above", async () => { await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); - let _blockNo = latestBlock(); - let tx = await I_STRProxied.generateSecurityToken(name, symbol, tokenDetails, false, { from: token_owner }); + + let tx = await I_STRProxied.generateNewSecurityToken(name, symbol, tokenDetails, false, token_owner, 0, { from: token_owner }); // 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 log = await promisifyLogWatch(I_SecurityToken.ModuleAdded({from: _blockNo}), 1); + I_SecurityToken = await SecurityToken.at(tx.logs[1].args._securityTokenAddress); + stGetter = await STGetter.at(I_SecurityToken.address); + assert.equal(await stGetter.getTreasuryWallet.call(), token_owner, "Incorrect wallet set"); + const log = (await I_SecurityToken.getPastEvents('ModuleAdded', {filter: {transactionHash: tx.transactionHash}}))[0]; // Verify that GeneralTransferManager module get added successfully or not - assert.equal(log.args._types[0].toNumber(), 2); - assert.equal( - web3.utils.toAscii(log.args._name) - .replace(/\u0000/g, ''), - "GeneralTransferManager" - ); + assert.equal(log.args._types[0].toString(), 2); + assert.equal(web3.utils.toAscii(log.args._name).replace(/\u0000/g, ""), "GeneralTransferManager"); }); - it("Should intialize the auto attached modules", async () => { - let moduleData = (await I_SecurityToken.getModulesByType(2))[0]; - I_GeneralTransferManager = GeneralTransferManager.at(moduleData); - + it("Should initialize the auto attached modules", async () => { + let moduleData = (await stGetter.getModulesByType(2))[0]; + I_GeneralTransferManager = await GeneralTransferManager.at(moduleData); }); - }); - describe("Buy tokens using on-chain whitelist", async() => { - + describe("Buy tokens using on-chain whitelist", async () => { it("Should successfully attach the ScheduledCheckpoint with the security token", async () => { - await I_SecurityToken.changeGranularity(1, {from: token_owner}); - const tx = await I_SecurityToken.addModule(I_ScheduledCheckpointFactory.address, "", 0, 0, { from: token_owner }); - assert.equal(tx.logs[2].args._types[0].toNumber(), 4, "ScheduledCheckpoint doesn't get deployed"); - assert.equal(tx.logs[2].args._types[1].toNumber(), 2, "ScheduledCheckpoint doesn't get deployed"); + await I_SecurityToken.changeGranularity(1, { from: token_owner }); + const tx = await I_SecurityToken.addModule(I_ScheduledCheckpointFactory.address, "0x0", new BN(0), new BN(0), false, { from: token_owner }); + assert.equal(tx.logs[2].args._types[0].toString(), 4, "ScheduledCheckpoint doesn't get deployed"); + assert.equal(tx.logs[2].args._types[1].toString(), 2, "ScheduledCheckpoint doesn't get deployed"); assert.equal( - web3.utils.toAscii(tx.logs[2].args._name) - .replace(/\u0000/g, ''), + web3.utils.toAscii(tx.logs[2].args._name).replace(/\u0000/g, ""), "ScheduledCheckpoint", "ScheduledCheckpoint module was not added" ); - I_ScheduledCheckpoint = ScheduledCheckpoint.at(tx.logs[2].args._module); + I_ScheduledCheckpoint = await ScheduledCheckpoint.at(tx.logs[2].args._module); }); let startTime; let interval; + let timeUnit = SECONDS; it("Should create a daily checkpoint", async () => { - startTime = latestTime() + 100; + startTime = await latestTime() + 100; interval = 24 * 60 * 60; console.log("Creating scheduled CP: " + startTime, interval); - await I_ScheduledCheckpoint.addSchedule("CP1", startTime, interval, {from: token_owner}); - console.log("2: " + latestTime()); + await I_ScheduledCheckpoint.addSchedule(web3.utils.fromAscii("CP1"), startTime, interval, timeUnit, { from: token_owner }); + console.log("2: " + await latestTime()); }); it("Remove (temp) daily checkpoint", async () => { let snap_Id = await takeSnapshot(); - await I_ScheduledCheckpoint.removeSchedule("CP1", {from: token_owner}); + await I_ScheduledCheckpoint.removeSchedule(web3.utils.fromAscii("CP1"), { from: token_owner }); await revertToSnapshot(snap_Id); }); - it("Should Buy the tokens for account_investor1", async() => { + it("Should Buy the tokens for account_investor1", async () => { // Add the Investor in to the whitelist - console.log("3: " + latestTime()); + console.log("3: " + await latestTime()); - let tx = await I_GeneralTransferManager.modifyWhitelist( + let tx = await I_GeneralTransferManager.modifyKYCData( account_investor1, - latestTime(), - latestTime(), - latestTime() + duration.days(10), - true, + currentTime, + currentTime, + currentTime.add(new BN(duration.days(10))), { from: account_issuer, gas: 6000000 - }); + } + ); - assert.equal(tx.logs[0].args._investor.toLowerCase(), account_investor1.toLowerCase(), "Failed in adding the investor in whitelist"); + assert.equal( + tx.logs[0].args._investor.toLowerCase(), + account_investor1.toLowerCase(), + "Failed in adding the investor in whitelist" + ); // Jump time - console.log("4: " + latestTime()); + console.log("4: " + await latestTime()); await increaseTime(5000); // We should be after the first scheduled checkpoint, and before the second - console.log("5: " + latestTime()); + console.log("5: " + await latestTime()); - assert.isTrue(latestTime() > startTime); - assert.isTrue(latestTime() <= startTime + interval); - console.log("6: " + latestTime()); + assert.isTrue(await latestTime() > startTime); + assert.isTrue(await latestTime() <= startTime + interval); + console.log("6: " + await latestTime()); // Mint some tokens - await I_SecurityToken.mint(account_investor1, web3.utils.toWei('1', 'ether'), { from: token_owner }); + await I_SecurityToken.issue(account_investor1, new BN(web3.utils.toWei("1", "ether")), "0x0", { from: token_owner }); - assert.equal( - (await I_SecurityToken.balanceOf(account_investor1)).toNumber(), - web3.utils.toWei('1', 'ether') - ); + assert.equal((await I_SecurityToken.balanceOf(account_investor1)).toString(), new BN(web3.utils.toWei("1", "ether")).toString()); }); - it("Should have checkpoint created with correct balances", async() => { - let cp1 = await I_ScheduledCheckpoint.getSchedule("CP1"); - checkSchedule(cp1, "CP1", startTime, startTime + interval, interval, [1], [startTime], [1]); - assert.equal((await I_SecurityToken.balanceOfAt(account_investor1, 0)).toNumber(), 0); - assert.equal((await I_SecurityToken.balanceOfAt(account_investor1, 1)).toNumber(), 0); + it("Should have checkpoint created with correct balances", async () => { + let cp1 = await I_ScheduledCheckpoint.getSchedule(web3.utils.fromAscii("CP1")); + checkSchedule(cp1, web3.utils.fromAscii("CP1"), startTime, startTime + interval, interval, timeUnit, [1], [startTime], [1]); + assert.equal((await stGetter.balanceOfAt(account_investor1, 0)).toString(), 0); + assert.equal((await stGetter.balanceOfAt(account_investor1, 1)).toString(), 0); }); - it("Should Buy some more tokens for account_investor2", async() => { + it("Should Buy some more tokens for account_investor2", async () => { // Add the Investor in to the whitelist - let tx = await I_GeneralTransferManager.modifyWhitelist( + let tx = await I_GeneralTransferManager.modifyKYCData( account_investor2, - latestTime(), - latestTime(), - latestTime() + duration.days(10), - true, + currentTime, + currentTime, + currentTime.add(new BN(duration.days(10))), { from: account_issuer, gas: 6000000 - }); + } + ); - assert.equal(tx.logs[0].args._investor.toLowerCase(), account_investor2.toLowerCase(), "Failed in adding the investor in whitelist"); + assert.equal( + tx.logs[0].args._investor.toLowerCase(), + account_investor2.toLowerCase(), + "Failed in adding the investor in whitelist" + ); // We should be after the first scheduled checkpoint, and before the second - assert.isTrue(latestTime() > startTime); - assert.isTrue(latestTime() <= startTime + interval); + assert.isTrue(await latestTime() > startTime); + assert.isTrue(await latestTime() <= startTime + interval); // Mint some tokens - await I_SecurityToken.mint(account_investor2, web3.utils.toWei('1', 'ether'), { from: token_owner }); + await I_SecurityToken.issue(account_investor2, new BN(web3.utils.toWei("1", "ether")), "0x0", { from: token_owner }); - assert.equal( - (await I_SecurityToken.balanceOf(account_investor2)).toNumber(), - web3.utils.toWei('1', 'ether') - ); + assert.equal((await I_SecurityToken.balanceOf(account_investor2)).toString(), new BN(web3.utils.toWei("1", "ether")).toString()); }); - it("No additional checkpoints created", async() => { - let cp1 = await I_ScheduledCheckpoint.getSchedule("CP1"); - checkSchedule(cp1, "CP1", startTime, startTime + interval, interval, [1], [startTime], [1]); - assert.equal((await I_SecurityToken.balanceOfAt(account_investor2, 0)).toNumber(), 0); - assert.equal((await I_SecurityToken.balanceOfAt(account_investor2, 1)).toNumber(), 0); + it("No additional checkpoints created", async () => { + let cp1 = await I_ScheduledCheckpoint.getSchedule(web3.utils.fromAscii("CP1")); + checkSchedule(cp1, web3.utils.fromAscii("CP1"), startTime, startTime + interval, interval, timeUnit, [1], [startTime], [1]); + assert.equal((await stGetter.balanceOfAt(account_investor2, 0)).toString(), 0); + assert.equal((await stGetter.balanceOfAt(account_investor2, 1)).toString(), 0); }); - it("Add a new token holder - account_investor3", async() => { - - let tx = await I_GeneralTransferManager.modifyWhitelist( + it("Add a new token holder - account_investor3", async () => { + let tx = await I_GeneralTransferManager.modifyKYCData( account_investor3, - latestTime(), - latestTime(), - latestTime() + duration.days(10), - true, + currentTime, + currentTime, + currentTime.add(new BN(duration.days(10))), { from: account_issuer, gas: 6000000 - }); + } + ); - assert.equal(tx.logs[0].args._investor.toLowerCase(), account_investor3.toLowerCase(), "Failed in adding the investor in whitelist"); + assert.equal( + tx.logs[0].args._investor.toLowerCase(), + account_investor3.toLowerCase(), + "Failed in adding the investor in whitelist" + ); // Jump time await increaseTime(interval); // We should be after the first scheduled checkpoint, and before the second - assert.isTrue(latestTime() > startTime + interval); - assert.isTrue(latestTime() <= startTime + (2 * interval)); + assert.isTrue(await latestTime() > startTime + interval); + assert.isTrue(await latestTime() <= startTime + 2 * interval); // Add the Investor in to the whitelist // Mint some tokens - await I_SecurityToken.mint(account_investor3, web3.utils.toWei('1', 'ether'), { from: token_owner }); + await I_SecurityToken.issue(account_investor3, new BN(web3.utils.toWei("1", "ether")), "0x0", { from: token_owner }); - assert.equal( - (await I_SecurityToken.balanceOf(account_investor3)).toNumber(), - web3.utils.toWei('1', 'ether') - ); + assert.equal((await I_SecurityToken.balanceOf(account_investor3)).toString(), new BN(web3.utils.toWei("1", "ether")).toString()); }); - it("Should have new checkpoint created with correct balances", async() => { - let cp1 = await I_ScheduledCheckpoint.getSchedule("CP1"); - checkSchedule(cp1, "CP1", startTime, startTime + (2 * interval), interval, [1, 2], [startTime, startTime + interval], [1, 1]); - assert.equal((await I_SecurityToken.balanceOfAt(account_investor3, 0)).toNumber(), 0); - assert.equal((await I_SecurityToken.balanceOfAt(account_investor3, 1)).toNumber(), 0); - assert.equal((await I_SecurityToken.balanceOfAt(account_investor3, 2)).toNumber(), 0); - assert.equal((await I_SecurityToken.balanceOfAt(account_investor2, 0)).toNumber(), 0); - assert.equal((await I_SecurityToken.balanceOfAt(account_investor2, 1)).toNumber(), 0); - assert.equal((await I_SecurityToken.balanceOfAt(account_investor2, 2)).toNumber(), web3.utils.toWei('1', 'ether')); - assert.equal((await I_SecurityToken.balanceOfAt(account_investor1, 0)).toNumber(), 0); - assert.equal((await I_SecurityToken.balanceOfAt(account_investor1, 1)).toNumber(), 0); - assert.equal((await I_SecurityToken.balanceOfAt(account_investor1, 2)).toNumber(), web3.utils.toWei('1', 'ether')); + it("Should have new checkpoint created with correct balances", async () => { + let cp1 = await I_ScheduledCheckpoint.getSchedule(web3.utils.fromAscii("CP1")); + checkSchedule(cp1, web3.utils.fromAscii("CP1"), startTime, startTime + 2 * interval, interval, timeUnit, [1, 2], [startTime, startTime + interval], [1, 1]); + assert.equal((await stGetter.balanceOfAt(account_investor3, 0)).toString(), 0); + assert.equal((await stGetter.balanceOfAt(account_investor3, 1)).toString(), 0); + assert.equal((await stGetter.balanceOfAt(account_investor3, 2)).toString(), 0); + assert.equal((await stGetter.balanceOfAt(account_investor2, 0)).toString(), 0); + assert.equal((await stGetter.balanceOfAt(account_investor2, 1)).toString(), 0); + assert.equal((await stGetter.balanceOfAt(account_investor2, 2)).toString(), new BN(web3.utils.toWei("1", "ether")).toString()); + assert.equal((await stGetter.balanceOfAt(account_investor1, 0)).toString(), 0); + assert.equal((await stGetter.balanceOfAt(account_investor1, 1)).toString(), 0); + assert.equal((await stGetter.balanceOfAt(account_investor1, 2)).toString(), new BN(web3.utils.toWei("1", "ether")).toString()); }); - it("Should have correct balances for investor 3 after new checkpoint", async() => { + it("Should have correct balances for investor 3 after new checkpoint", async () => { // Jump time await increaseTime(2 * interval); // We should be after the first scheduled checkpoint, and before the second - assert.isTrue(latestTime() > startTime + (3 * interval)); - assert.isTrue(latestTime() <= startTime + (4 * interval)); - await I_SecurityToken.transfer(account_investor3, web3.utils.toWei('0.5', 'ether'), { from: account_investor1 }); - let cp1 = await I_ScheduledCheckpoint.getSchedule("CP1"); - checkSchedule(cp1, "CP1", startTime, startTime + (4 * interval), interval, [1, 2, 3], [startTime, startTime + interval, startTime + (2 * interval)], [1, 1, 2]); - assert.equal((await I_SecurityToken.balanceOfAt(account_investor3, 0)).toNumber(), 0); - assert.equal((await I_SecurityToken.balanceOfAt(account_investor3, 1)).toNumber(), 0); - assert.equal((await I_SecurityToken.balanceOfAt(account_investor3, 2)).toNumber(), 0); - assert.equal((await I_SecurityToken.balanceOfAt(account_investor3, 3)).toNumber(), web3.utils.toWei('1', 'ether')); + assert.isTrue(await latestTime() > startTime + 3 * interval); + assert.isTrue(await latestTime() <= startTime + 4 * interval); + await I_SecurityToken.transfer(account_investor3, new BN(web3.utils.toWei("0.5", "ether")), { from: account_investor1 }); + let cp1 = await I_ScheduledCheckpoint.getSchedule(web3.utils.fromAscii("CP1")); + checkSchedule( + cp1, + web3.utils.fromAscii("CP1"), + startTime, + startTime + 4 * interval, + interval, + timeUnit, + [1, 2, 3], + [startTime, startTime + interval, startTime + 2 * interval], + [1, 1, 2] + ); + assert.equal((await stGetter.balanceOfAt(account_investor3, 0)).toString(), 0); + assert.equal((await stGetter.balanceOfAt(account_investor3, 1)).toString(), 0); + assert.equal((await stGetter.balanceOfAt(account_investor3, 2)).toString(), 0); + assert.equal((await stGetter.balanceOfAt(account_investor3, 3)).toString(), new BN(web3.utils.toWei("1", "ether")).toString()); + + assert.equal((await stGetter.balanceOfAt(account_investor2, 0)).toString(), 0); + assert.equal((await stGetter.balanceOfAt(account_investor2, 1)).toString(), 0); + assert.equal((await stGetter.balanceOfAt(account_investor2, 2)).toString(), new BN(web3.utils.toWei("1", "ether")).toString()); + assert.equal((await stGetter.balanceOfAt(account_investor2, 3)).toString(), new BN(web3.utils.toWei("1", "ether")).toString()); + + assert.equal((await stGetter.balanceOfAt(account_investor1, 0)).toString(), 0); + assert.equal((await stGetter.balanceOfAt(account_investor1, 1)).toString(), 0); + assert.equal((await stGetter.balanceOfAt(account_investor1, 2)).toString(), new BN(web3.utils.toWei("1", "ether")).toString()); + assert.equal((await stGetter.balanceOfAt(account_investor1, 3)).toString(), new BN(web3.utils.toWei("1", "ether")).toString()); + }); - assert.equal((await I_SecurityToken.balanceOfAt(account_investor2, 0)).toNumber(), 0); - assert.equal((await I_SecurityToken.balanceOfAt(account_investor2, 1)).toNumber(), 0); - assert.equal((await I_SecurityToken.balanceOfAt(account_investor2, 2)).toNumber(), web3.utils.toWei('1', 'ether')); - assert.equal((await I_SecurityToken.balanceOfAt(account_investor2, 3)).toNumber(), web3.utils.toWei('1', 'ether')); + it("Manually update checkpoints", async () => { + await increaseTime(interval); + await I_ScheduledCheckpoint.updateAll({ from: token_owner }); + + let cp1 = await I_ScheduledCheckpoint.getSchedule(web3.utils.fromAscii("CP1")); + checkSchedule( + cp1, + web3.utils.fromAscii("CP1"), + startTime, + startTime + 5 * interval, + interval, + timeUnit, + [1, 2, 3, 4], + [startTime, startTime + interval, startTime + 2 * interval, startTime + 4 * interval], + [1, 1, 2, 1] + ); + assert.equal((await stGetter.balanceOfAt(account_investor3, 0)).toString(), 0); + assert.equal((await stGetter.balanceOfAt(account_investor3, 1)).toString(), 0); + assert.equal((await stGetter.balanceOfAt(account_investor3, 2)).toString(), 0); + assert.equal((await stGetter.balanceOfAt(account_investor3, 3)).toString(), new BN(web3.utils.toWei("1", "ether")).toString()); + assert.equal((await stGetter.balanceOfAt(account_investor3, 4)).toString(), new BN(web3.utils.toWei("1.5", "ether")).toString()); + + assert.equal((await stGetter.balanceOfAt(account_investor2, 0)).toString(), 0); + assert.equal((await stGetter.balanceOfAt(account_investor2, 1)).toString(), 0); + assert.equal((await stGetter.balanceOfAt(account_investor2, 2)).toString(), new BN(web3.utils.toWei("1", "ether")).toString()); + assert.equal((await stGetter.balanceOfAt(account_investor2, 3)).toString(), new BN(web3.utils.toWei("1", "ether")).toString()); + assert.equal((await stGetter.balanceOfAt(account_investor2, 4)).toString(), new BN(web3.utils.toWei("1", "ether")).toString()); + + assert.equal((await stGetter.balanceOfAt(account_investor1, 0)).toString(), 0); + assert.equal((await stGetter.balanceOfAt(account_investor1, 1)).toString(), 0); + assert.equal((await stGetter.balanceOfAt(account_investor1, 2)).toString(), new BN(web3.utils.toWei("1", "ether")).toString()); + assert.equal((await stGetter.balanceOfAt(account_investor1, 3)).toString(), new BN(web3.utils.toWei("1", "ether")).toString()); + assert.equal((await stGetter.balanceOfAt(account_investor1, 4)).toString(), new BN(web3.utils.toWei("0.5", "ether")).toString()); + }); - assert.equal((await I_SecurityToken.balanceOfAt(account_investor1, 0)).toNumber(), 0); - assert.equal((await I_SecurityToken.balanceOfAt(account_investor1, 1)).toNumber(), 0); - assert.equal((await I_SecurityToken.balanceOfAt(account_investor1, 2)).toNumber(), web3.utils.toWei('1', 'ether')); - assert.equal((await I_SecurityToken.balanceOfAt(account_investor1, 3)).toNumber(), web3.utils.toWei('1', 'ether')); + it("Should get the permission", async () => { + let perm = await I_ScheduledCheckpoint.getPermissions.call(); + assert.equal(perm.length, 0); + }); + it("Remove daily checkpoint", async () => { + await I_ScheduledCheckpoint.removeSchedule(web3.utils.fromAscii("CP1"), {from: token_owner}); }); - it("Manually update checkpoints", async() => { - await increaseTime(interval); + }); + + describe("Tests for monthly scheduled checkpoints", async() => { + + let name = web3.utils.fromAscii("CP-M-1"); + let startTime; + let interval = 5; + let timeUnit = MONTHS; + + it("Should create a monthly checkpoint", async () => { + startTime = new BN(await latestTime()).add(new BN(100)); + + let tx = await I_ScheduledCheckpoint.addSchedule(name, startTime, interval, timeUnit, {from: token_owner}); + checkScheduleLog(tx.logs[0], name, startTime, interval, timeUnit); + }); + + it("Check one monthly checkpoint", async() => { + await increaseTime(100); + await I_ScheduledCheckpoint.updateAll({from: token_owner}); + + let schedule = await I_ScheduledCheckpoint.getSchedule(name); + let checkpoints = [5]; + let timestamps = [startTime]; + let periods = [1]; + checkSchedule(schedule, name, startTime, addMonths(startTime, interval), interval, timeUnit, checkpoints, timestamps, periods); + }); + + it("Check two monthly checkpoints", async() => { + await increaseTime(duration.days(31 * interval)); + await I_ScheduledCheckpoint.updateAll({from: token_owner}); + + let schedule = await I_ScheduledCheckpoint.getSchedule(name); + let checkpoints = [5, 6]; + let timestamps = [startTime, addMonths(startTime, interval)]; + let periods = [1, 1]; + checkSchedule(schedule, name, startTime, addMonths(startTime, interval * 2), interval, timeUnit, checkpoints, timestamps, periods); + }); + + it("Check three monthly checkpoints", async() => { + await increaseTime(duration.days(31 * interval * 2)); await I_ScheduledCheckpoint.updateAll({from: token_owner}); - let cp1 = await I_ScheduledCheckpoint.getSchedule("CP1"); - checkSchedule(cp1, "CP1", startTime, startTime + (5 * interval), interval, [1, 2, 3, 4], [startTime, startTime + interval, startTime + (2 * interval), startTime + (4 * interval)], [1, 1, 2, 1]); - assert.equal((await I_SecurityToken.balanceOfAt(account_investor3, 0)).toNumber(), 0); - assert.equal((await I_SecurityToken.balanceOfAt(account_investor3, 1)).toNumber(), 0); - assert.equal((await I_SecurityToken.balanceOfAt(account_investor3, 2)).toNumber(), 0); - assert.equal((await I_SecurityToken.balanceOfAt(account_investor3, 3)).toNumber(), web3.utils.toWei('1', 'ether')); - assert.equal((await I_SecurityToken.balanceOfAt(account_investor3, 4)).toNumber(), web3.utils.toWei('1.5', 'ether')); + let schedule = await I_ScheduledCheckpoint.getSchedule(name); + let checkpoints = [5, 6, 7]; + let timestamps = [startTime, addMonths(startTime, interval), addMonths(startTime, interval * 2)]; + let periods = [1, 1, 2]; + checkSchedule(schedule, name, startTime, addMonths(startTime, interval * 4), interval, timeUnit, checkpoints, timestamps, periods); + }); + + it("Check four monthly checkpoints", async() => { + await increaseTime(duration.days(31 * interval)); + await I_ScheduledCheckpoint.updateAll({from: token_owner}); - assert.equal((await I_SecurityToken.balanceOfAt(account_investor2, 0)).toNumber(), 0); - assert.equal((await I_SecurityToken.balanceOfAt(account_investor2, 1)).toNumber(), 0); - assert.equal((await I_SecurityToken.balanceOfAt(account_investor2, 2)).toNumber(), web3.utils.toWei('1', 'ether')); - assert.equal((await I_SecurityToken.balanceOfAt(account_investor2, 3)).toNumber(), web3.utils.toWei('1', 'ether')); - assert.equal((await I_SecurityToken.balanceOfAt(account_investor2, 4)).toNumber(), web3.utils.toWei('1', 'ether')); + let schedule = await I_ScheduledCheckpoint.getSchedule(name); + let checkpoints = [5, 6, 7, 8]; + let timestamps = [startTime, addMonths(startTime, interval), addMonths(startTime, interval * 2), addMonths(startTime, interval * 4)]; + let periods = [1, 1, 2, 1]; + checkSchedule(schedule, name, startTime, addMonths(startTime, interval * 5), interval, timeUnit, checkpoints, timestamps, periods); + }); - assert.equal((await I_SecurityToken.balanceOfAt(account_investor1, 0)).toNumber(), 0); - assert.equal((await I_SecurityToken.balanceOfAt(account_investor1, 1)).toNumber(), 0); - assert.equal((await I_SecurityToken.balanceOfAt(account_investor1, 2)).toNumber(), web3.utils.toWei('1', 'ether')); - assert.equal((await I_SecurityToken.balanceOfAt(account_investor1, 3)).toNumber(), web3.utils.toWei('1', 'ether')); - assert.equal((await I_SecurityToken.balanceOfAt(account_investor1, 4)).toNumber(), web3.utils.toWei('0.5', 'ether')); + it("Check five monthly checkpoints", async() => { + await increaseTime(duration.days(31 * interval * 3)); + await I_ScheduledCheckpoint.updateAll({from: token_owner}); + let schedule = await I_ScheduledCheckpoint.getSchedule(name); + let checkpoints = [5, 6, 7, 8, 9]; + let timestamps = [startTime, addMonths(startTime, interval), addMonths(startTime, interval * 2), addMonths(startTime, interval * 4), addMonths(startTime, interval * 5)]; + let periods = [1, 1, 2, 1, 3]; + checkSchedule(schedule, name, startTime, addMonths(startTime, interval * 8), interval, timeUnit, checkpoints, timestamps, periods); }); - it("Should get the permission", async() => { - let perm = await I_ScheduledCheckpoint.getPermissions.call(); - assert.equal(perm.length, 0); + it("Remove monthly checkpoint", async () => { + await I_ScheduledCheckpoint.removeSchedule(name, {from: token_owner}); + }); + + }); + + describe("Tests for yearly scheduled checkpoints", async() => { + + let name = web3.utils.fromAscii("CP-Y-1"); + let startTime; + let interval = 3; + let timeUnit = YEARS; + + it("Should create a yearly checkpoint", async () => { + startTime = await latestTime() + 100; + + let tx = await I_ScheduledCheckpoint.addSchedule(name, startTime, interval, timeUnit, {from: token_owner}); + checkScheduleLog(tx.logs[0], name, startTime, interval, timeUnit); + }); + + it("Check one yearly checkpoint", async() => { + await increaseTime(100); + await I_ScheduledCheckpoint.updateAll({from: token_owner}); + + let schedule = await I_ScheduledCheckpoint.getSchedule(name); + let checkpoints = [10]; + let timestamps = [startTime]; + let periods = [1]; + checkSchedule(schedule, name, startTime, addYears(startTime, interval), interval, timeUnit, checkpoints, timestamps, periods); + }); + + it("Check two yearly checkpoints", async() => { + await increaseTime(duration.days(366 * interval)); + await I_ScheduledCheckpoint.updateAll({from: token_owner}); + + let schedule = await I_ScheduledCheckpoint.getSchedule(name); + let checkpoints = [10, 11]; + let timestamps = [startTime, addYears(startTime, interval)]; + let periods = [1, 1]; + checkSchedule(schedule, name, startTime, addYears(startTime, interval * 2), interval, timeUnit, checkpoints, timestamps, periods); + }); + + it("Check three yearly checkpoints", async() => { + await increaseTime(duration.days(366 * interval * 2)); + await I_ScheduledCheckpoint.updateAll({from: token_owner}); + + let schedule = await I_ScheduledCheckpoint.getSchedule(name); + let checkpoints = [10, 11, 12]; + let timestamps = [startTime, addYears(startTime, interval), addYears(startTime, interval * 2)]; + let periods = [1, 1, 2]; + checkSchedule(schedule, name, startTime, addYears(startTime, interval * 4), interval, timeUnit, checkpoints, timestamps, periods); + }); + + it("Check four yearly checkpoints", async() => { + await increaseTime(duration.days(366 * interval)); + await I_ScheduledCheckpoint.updateAll({from: token_owner}); + + let schedule = await I_ScheduledCheckpoint.getSchedule(name); + let checkpoints = [10, 11, 12, 13]; + let timestamps = [startTime, addYears(startTime, interval), addYears(startTime, interval * 2), addYears(startTime, interval * 4)]; + let periods = [1, 1, 2, 1]; + checkSchedule(schedule, name, startTime, addYears(startTime, interval * 5), interval, timeUnit, checkpoints, timestamps, periods); + }); + + it("Check five yearly checkpoints", async() => { + await increaseTime(duration.days(366 * interval * 3)); + await I_ScheduledCheckpoint.updateAll({from: token_owner}); + + let schedule = await I_ScheduledCheckpoint.getSchedule(name); + let checkpoints = [10, 11, 12, 13, 14]; + let timestamps = [startTime, addYears(startTime, interval), addYears(startTime, interval * 2), addYears(startTime, interval * 4), addYears(startTime, interval * 5)]; + let periods = [1, 1, 2, 1, 3]; + checkSchedule(schedule, name, startTime, addYears(startTime, interval * 8), interval, timeUnit, checkpoints, timestamps, periods); + }); + + it("Remove monthly checkpoint", async () => { + await I_ScheduledCheckpoint.removeSchedule(name, {from: token_owner}); + }); + + }); + + describe("Tests for monthly scheduled checkpoints -- end of month", async() => { + let name = web3.utils.fromAscii("CP-M-2"); + let previousTime; + let startDate; + let startTime; + let interval = 1; + let timeUnit = MONTHS; + + it("Should create a monthly checkpoint -- December 31", async () => { + previousTime = await latestTime(); + + startDate = new Date(previousTime * 1000); + startDate.setUTCMonth(11, 31); + startTime = startDate.getTime() / 1000; + console.log("previousTime:" + previousTime); + console.log("startTime:" + startTime); + console.log("startDate:" + startDate.toUTCString()); + + let tx = await I_ScheduledCheckpoint.addSchedule(name, startTime, interval, timeUnit, {from: token_owner}); + checkScheduleLog(tx.logs[0], name, startTime, interval, timeUnit); + }); + + it("Check monthly checkpoint -- January 31", async() => { + await increaseTime(startTime - previousTime + 100); + await I_ScheduledCheckpoint.updateAll({from: token_owner}); + + let schedule = await I_ScheduledCheckpoint.getSchedule(name); + let checkpoints = [15]; + let nextTime = addMonths(startTime, interval); + let timestamps = [startTime]; + let periods = [1]; + checkSchedule(schedule, name, startTime, nextTime, interval, timeUnit, checkpoints, timestamps, periods); + }); + + function getDaysInFebruary() { + let days; + if ((startDate.getUTCFullYear() + 1) % 4 === 0) { + days = 29; + } else { + days = 28; + } + return days; + } + + function getEndOfFebruary(startTime, days) { + return setDate(addYears(startTime, 1), 1, days); //addMonths(startTime, interval * 2) + } + + it("Check monthly checkpoints -- February 28/29", async() => { + await increaseTime(duration.days(31 * interval)); + await I_ScheduledCheckpoint.updateAll({from: token_owner}); + + let schedule = await I_ScheduledCheckpoint.getSchedule(name); + let checkpoints = [15, 16]; + let days = getDaysInFebruary(); + let nextTime = getEndOfFebruary(startTime, days); + let timestamps = [startTime, addMonths(startTime, interval)]; + let periods = [1, 1]; + checkSchedule(schedule, name, startTime, nextTime, interval, timeUnit, checkpoints, timestamps, periods); + }); + + it("Check monthly checkpoints -- March 31", async() => { + let days = getDaysInFebruary(); + await increaseTime(duration.days(days * interval)); + await I_ScheduledCheckpoint.updateAll({from: token_owner}); + + let schedule = await I_ScheduledCheckpoint.getSchedule(name); + let checkpoints = [15, 16, 17]; + let nextTime = addMonths(startTime, interval * 3); + let timestamps = [startTime, addMonths(startTime, interval), getEndOfFebruary(startTime, days)]; + let periods = [1, 1, 1]; + + for (let i = 0; i < timestamps.length; i++) { + assert.equal(schedule[6][i].toString(), timestamps[i]); + console.log(new Date(schedule[6][i].toString() * 1000).toUTCString()); + } + console.log("expected:" + new Date(nextTime * 1000).toUTCString()); + console.log("actual:" + new Date(schedule[2].toString() * 1000).toUTCString()); + checkSchedule(schedule, name, startTime, nextTime, interval, timeUnit, checkpoints, timestamps, periods); + }); + + it("Remove monthly checkpoint", async () => { + await I_ScheduledCheckpoint.removeSchedule(name, {from: token_owner}); + }); + + }); + + describe("Tests for daily scheduled checkpoints", async() => { + + let name = web3.utils.fromAscii("CP-D-1"); + let startTime; + let interval = 13; + let timeUnit = DAYS; + + it("Should create a daily checkpoint", async () => { + startTime = await latestTime() + 100; + + let tx = await I_ScheduledCheckpoint.addSchedule(name, startTime, interval, timeUnit, {from: token_owner}); + checkScheduleLog(tx.logs[0], name, startTime, interval, timeUnit); + }); + + it("Check one daily checkpoint", async() => { + await increaseTime(100); + await I_ScheduledCheckpoint.updateAll({from: token_owner}); + + let schedule = await I_ScheduledCheckpoint.getSchedule(name); + let checkpoints = [18]; + let timestamps = [startTime]; + let periods = [1]; + checkSchedule(schedule, name, startTime, addDays(startTime, interval), interval, timeUnit, checkpoints, timestamps, periods); + }); + + it("Check two daily checkpoints", async() => { + await increaseTime(duration.days(interval)); + await I_ScheduledCheckpoint.updateAll({from: token_owner}); + + let schedule = await I_ScheduledCheckpoint.getSchedule(name); + let checkpoints = [18, 19]; + let timestamps = [startTime, addDays(startTime, interval)]; + let periods = [1, 1]; + checkSchedule(schedule, name, startTime, addDays(startTime, interval * 2), interval, timeUnit, checkpoints, timestamps, periods); + }); + + it("Check three daily checkpoints", async() => { + await increaseTime(duration.days(interval * 2)); + await I_ScheduledCheckpoint.updateAll({from: token_owner}); + + let schedule = await I_ScheduledCheckpoint.getSchedule(name); + let checkpoints = [18, 19, 20]; + let timestamps = [startTime, addDays(startTime, interval), addDays(startTime, interval * 2)]; + let periods = [1, 1, 2]; + checkSchedule(schedule, name, startTime, addDays(startTime, interval * 4), interval, timeUnit, checkpoints, timestamps, periods); + }); + + it("Check four daily checkpoints", async() => { + await increaseTime(duration.days(interval)); + await I_ScheduledCheckpoint.updateAll({from: token_owner}); + + let schedule = await I_ScheduledCheckpoint.getSchedule(name); + let checkpoints = [18, 19, 20, 21]; + let timestamps = [startTime, addDays(startTime, interval), addDays(startTime, interval * 2), addDays(startTime, interval * 4)]; + let periods = [1, 1, 2, 1]; + checkSchedule(schedule, name, startTime, addDays(startTime, interval * 5), interval, timeUnit, checkpoints, timestamps, periods); + }); + + it("Check five daily checkpoints", async() => { + await increaseTime(duration.days(interval * 3)); + await I_ScheduledCheckpoint.updateAll({from: token_owner}); + + let schedule = await I_ScheduledCheckpoint.getSchedule(name); + let checkpoints = [18, 19, 20, 21, 22]; + let timestamps = [startTime, addDays(startTime, interval), addDays(startTime, interval * 2), addDays(startTime, interval * 4), addDays(startTime, interval * 5)]; + let periods = [1, 1, 2, 1, 3]; + checkSchedule(schedule, name, startTime, addDays(startTime, interval * 8), interval, timeUnit, checkpoints, timestamps, periods); + }); + + it("Remove daily checkpoint", async () => { + await I_ScheduledCheckpoint.removeSchedule(name, {from: token_owner}); + }); + + }); + + describe("Tests for weekly scheduled checkpoints", async() => { + + let name = web3.utils.fromAscii("CP-M-1"); + let startTime; + let interval = 9; + let timeUnit = WEEKS; + + it("Should create a weekly checkpoint", async () => { + startTime = await latestTime() + 100; + + let tx = await I_ScheduledCheckpoint.addSchedule(name, startTime, interval, timeUnit, {from: token_owner}); + checkScheduleLog(tx.logs[0], name, startTime, interval, timeUnit); + }); + + it("Check one weekly checkpoint", async() => { + await increaseTime(100); + await I_ScheduledCheckpoint.updateAll({from: token_owner}); + + let schedule = await I_ScheduledCheckpoint.getSchedule(name); + let checkpoints = [23]; + let timestamps = [startTime]; + let periods = [1]; + + checkSchedule(schedule, name, startTime, addWeeks(startTime, interval), interval, timeUnit, checkpoints, timestamps, periods); + }); + + it("Check two weekly checkpoints", async() => { + await increaseTime(duration.days(7 * interval)); + await I_ScheduledCheckpoint.updateAll({from: token_owner}); + + let schedule = await I_ScheduledCheckpoint.getSchedule(name); + let checkpoints = [23, 24]; + let timestamps = [startTime, addWeeks(startTime, interval)]; + let periods = [1, 1]; + checkSchedule(schedule, name, startTime, addWeeks(startTime, interval * 2), interval, timeUnit, checkpoints, timestamps, periods); + }); + + it("Check three weekly checkpoints", async() => { + await increaseTime(duration.days(7 * interval * 2)); + await I_ScheduledCheckpoint.updateAll({from: token_owner}); + + let schedule = await I_ScheduledCheckpoint.getSchedule(name); + let checkpoints = [23, 24, 25]; + let timestamps = [startTime, addWeeks(startTime, interval), addWeeks(startTime, interval * 2)]; + let periods = [1, 1, 2]; + checkSchedule(schedule, name, startTime, addWeeks(startTime, interval * 4), interval, timeUnit, checkpoints, timestamps, periods); + }); + + it("Check four weekly checkpoints", async() => { + await increaseTime(duration.days(7 * interval)); + await I_ScheduledCheckpoint.updateAll({from: token_owner}); + + let schedule = await I_ScheduledCheckpoint.getSchedule(name); + let checkpoints = [23, 24, 25, 26]; + let timestamps = [startTime, addWeeks(startTime, interval), addWeeks(startTime, interval * 2), addWeeks(startTime, interval * 4)]; + let periods = [1, 1, 2, 1]; + checkSchedule(schedule, name, startTime, addWeeks(startTime, interval * 5), interval, timeUnit, checkpoints, timestamps, periods); + }); + + it("Check five weekly checkpoints", async() => { + await increaseTime(duration.days(7 * interval * 3)); + await I_ScheduledCheckpoint.updateAll({from: token_owner}); + + let schedule = await I_ScheduledCheckpoint.getSchedule(name); + let checkpoints = [23, 24, 25, 26, 27]; + let timestamps = [startTime, addWeeks(startTime, interval), addWeeks(startTime, interval * 2), addWeeks(startTime, interval * 4), addWeeks(startTime, interval * 5)]; + let periods = [1, 1, 2, 1, 3]; + checkSchedule(schedule, name, startTime, addWeeks(startTime, interval * 8), interval, timeUnit, checkpoints, timestamps, periods); + }); + + it("Remove weekly checkpoint", async () => { + await I_ScheduledCheckpoint.removeSchedule(name, {from: token_owner}); }); }); }); -function checkSchedule(schedule, name, startTime, nextTime, interval, checkpoints, timestamps, periods) { - assert.equal(web3.utils.toAscii(schedule[0]).replace(/\u0000/g, ''), name); - assert.equal(schedule[1].toNumber(), startTime); - assert.equal(schedule[2].toNumber(), nextTime); - assert.equal(schedule[3].toNumber(), interval); - assert.equal(schedule[4].length, checkpoints.length); +function setDate(time, month, day) { + let startDate = new Date(time * 1000); + startDate.setUTCMonth(month, day); + return startDate.getTime() / 1000; +} + +function addDays(timestamp, days) { + let time = new Date(timestamp * 1000); + return time.setUTCDate(time.getUTCDate() + days) / 1000; +} + +function addWeeks(timestamp, weeks) { + return addDays(timestamp, weeks * 7); +} + +function addMonths(timestamp, months) { + let time = new Date(timestamp * 1000); + return time.setUTCMonth(time.getUTCMonth() + months) / 1000; +} + +function addYears(timestamp, years) { + let time = new Date(timestamp * 1000); + return time.setUTCFullYear(time.getUTCFullYear() + years) / 1000; +} + +function checkScheduleLog(log, name, startTime, interval, timeUnit) { + assert.equal(web3.utils.toAscii(log.args._name).replace(/\u0000/g, ""), web3.utils.toAscii(name)); + assert.equal(log.args._startTime.toString(), startTime.toString()); + assert.equal(log.args._interval.toString(), interval.toString()); + assert.equal(log.args._timeUint.toString(), timeUnit.toString()); +} + +function checkSchedule(schedule, name, startTime, nextTime, interval, timeUnit, checkpoints, timestamps, periods) { + assert.equal(web3.utils.toAscii(schedule[0]).replace(/\u0000/g, ""), web3.utils.toAscii(name)); + assert.equal(schedule[1].toString(), startTime.toString()); + assert.equal(schedule[2].toString(), nextTime.toString()); + assert.equal(schedule[3].toString(), interval.toString()); + assert.equal(schedule[4].toString(), timeUnit.toString()); + assert.equal(schedule[5].length, checkpoints.length); for (let i = 0; i < checkpoints.length; i++) { - assert.equal(schedule[4][i].toNumber(), checkpoints[i]); + assert.equal(schedule[5][i].toString(), checkpoints[i].toString()); } - assert.equal(schedule[5].length, timestamps.length); + assert.equal(schedule[6].length, timestamps.length); for (let i = 0; i < timestamps.length; i++) { - assert.equal(schedule[5][i].toNumber(), timestamps[i]); + assert.equal(schedule[6][i].toString(), timestamps[i].toString()); } - assert.equal(schedule[6].length, periods.length); + assert.equal(schedule[7].length, periods.length); + let totalPeriods = 0; for (let i = 0; i < periods.length; i++) { - assert.equal(schedule[6][i].toNumber(), periods[i]); + assert.equal(schedule[7][i].toString(), periods[i].toString()); + totalPeriods += periods[i]; } + assert.equal(schedule[8].toString(), totalPeriods.toString()); } diff --git a/test/y_volume_restriction_tm.js b/test/y_volume_restriction_tm.js index e456d0c4a..f02f5f38e 100644 --- a/test/y_volume_restriction_tm.js +++ b/test/y_volume_restriction_tm.js @@ -1,6 +1,6 @@ import latestTime from './helpers/latestTime'; -import { signData } from './helpers/signData'; -import { pk } from './helpers/testprivateKey'; +import {signData} from './helpers/signData'; +import { pk } from './helpers/testprivateKey'; import { duration, promisifyLogWatch, latestBlock } from './helpers/utils'; import { takeSnapshot, increaseTime, revertToSnapshot } from './helpers/time'; import { catchRevert } from "./helpers/exceptions"; @@ -9,9 +9,10 @@ import { setUpPolymathNetwork, deployVRTMAndVerifyed } from "./helpers/createIns const SecurityToken = artifacts.require('./SecurityToken.sol'); const GeneralTransferManager = artifacts.require('./GeneralTransferManager.sol'); const VolumeRestrictionTM = artifacts.require('./VolumeRestrictionTM.sol'); +const STGetter = artifacts.require("./STGetter.sol"); const Web3 = require('web3'); -const BigNumber = require('bignumber.js'); +const BN = Web3.utils.BN; const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")) // Hardcoded development port contract('VolumeRestrictionTransferManager', accounts => { @@ -29,9 +30,9 @@ contract('VolumeRestrictionTransferManager', accounts => { let account_delegate2; let account_delegate3; // investor Details - let fromTime = latestTime(); - let toTime = latestTime(); - let expiryTime = toTime + duration.days(15); + let fromTime; + let toTime; + let expiryTime; let message = "Transaction Should Fail!"; @@ -54,6 +55,8 @@ contract('VolumeRestrictionTransferManager', accounts => { let I_STRProxied; let I_PolyToken; let I_PolymathRegistry; + let I_STGetter; + let stGetter; // SecurityToken Details const name = "Team"; @@ -61,46 +64,52 @@ contract('VolumeRestrictionTransferManager', accounts => { const tokenDetails = "This is equity type of issuance"; const decimals = 18; const contact = "team@polymath.network"; - const delegateDetails = "Hello I am legit delegate"; + const delegateDetails = web3.utils.toHex("Hello I am legit delegate"); // Module key const delegateManagerKey = 1; const transferManagerKey = 2; const stoKey = 3; - let tempAmount = new BigNumber(0); + let tempAmount = new BN(0); let tempArray = new Array(); let tempArray3 = new Array(); let tempArrayGlobal = new Array(); let delegateArray = new Array(); // Initial fee for ticker registry and security token registry - const initRegFee = web3.utils.toWei("250"); + const initRegFee = new BN(web3.utils.toWei("1000")); + + const address_zero = "0x0000000000000000000000000000000000000000"; async function print(data, account) { console.log(` - Latest timestamp: ${data[0].toNumber()} - SumOfLastPeriod: ${data[1].dividedBy(new BigNumber(10).pow(18)).toNumber()} - Days Covered: ${data[2].toNumber()} - Latest timestamp daily: ${data[3].toNumber()} - Individual Total Trade on latestTimestamp : ${(await I_VolumeRestrictionTM.getTotalTradedByUser.call(account, data[0])) - .dividedBy(new BigNumber(10).pow(18)).toNumber()} - Individual Total Trade on daily latestTimestamp : ${(await I_VolumeRestrictionTM.getTotalTradedByUser.call(account, data[3])) - .dividedBy(new BigNumber(10).pow(18)).toNumber()} + Latest timestamp: ${data[0].toString()} + SumOfLastPeriod: ${web3.utils.fromWei(data[1]).toString()} + Days Covered: ${data[2].toString()} + Latest timestamp daily: ${data[3].toString()} + Individual Total Trade on latestTimestamp : ${web3.utils.fromWei(await I_VolumeRestrictionTM.getTotalTradedByUser.call(account, data[0])) + .toString()} + Individual Total Trade on daily latestTimestamp : ${web3.utils.fromWei(await I_VolumeRestrictionTM.getTotalTradedByUser.call(account, data[3])) + .toString()} Last Transaction time in UTC: ${(new Date((data[4].toNumber()) * 1000 )).toUTCString()} `) } + async function getLatestTime() { + return new BN(await latestTime()); + } + async function printRestrictedData(data) { let investors = data[0]; - for (let i = 0; i < investors.length; i++) { + for (let i = 0 ; i < investors.length; i++) { console.log(` Token holder: ${data[0][i]} - Start Time: ${data[2][i].toNumber()} - Rolling Period In Days: ${data[3][i].toNumber()} - End Time : ${data[4][i].toNumber()} + Start Time: ${data[2][i].toString()} + Rolling Period In Days: ${data[3][i].toString()} + End Time : ${data[4][i].toString()} Allowed Tokens: ${web3.utils.fromWei(data[1][i].toString())} - Type of Restriction: ${data[5][i].toNumber()} + Type of Restriction: ${data[5][i].toString()} `) } } @@ -116,15 +125,39 @@ contract('VolumeRestrictionTransferManager', accounts => { return sum; } + async function verifyPartitionBalance(investorAddress, lockedValue, unlockedValue) { + assert.equal( + web3.utils.fromWei( + ( + await I_VolumeRestrictionTM.getTokensByPartition.call(web3.utils.toHex("LOCKED"), investorAddress, new BN(0)) + ).toString() + ), + lockedValue + ); + + assert.equal( + web3.utils.fromWei( + ( + await I_VolumeRestrictionTM.getTokensByPartition.call(web3.utils.toHex("UNLOCKED"), investorAddress, new BN(0)) + ).toString() + ), + unlockedValue + ); + } + async function setTime() { - let currentTime = latestTime(); - let currentHour = (new Date(currentTime * 1000)).getUTCHours(); - console.log(`Earlier time ${new Date(latestTime() * 1000).toUTCString()}`); + let currentTime = await getLatestTime(); + let currentHour = (new Date(currentTime.toNumber() * 1000)).getUTCHours(); + console.log(`Earlier time ${new Date((await getLatestTime()).toNumber() * 1000).toUTCString()}`); await increaseTime(duration.hours(24 - currentHour)); - console.log(`Current time ${new Date(latestTime() * 1000).toUTCString()}`); + console.log(`Current time ${new Date((await getLatestTime()).toNumber() * 1000).toUTCString()}`); } - before(async () => { + before(async() => { + let newLatestTime = await getLatestTime(); + fromTime = newLatestTime; + toTime = newLatestTime; + expiryTime = toTime.add(new BN(duration.days(15))); // Accounts setup account_polymath = accounts[0]; account_issuer = accounts[1]; @@ -154,13 +187,14 @@ contract('VolumeRestrictionTransferManager', accounts => { I_STFactory, I_SecurityTokenRegistry, I_SecurityTokenRegistryProxy, - I_STRProxied + I_STRProxied, + I_STGetter ] = instances; // STEP 5: Deploy the VolumeRestrictionTMFactory - [I_VolumeRestrictionTMFactory] = await deployVRTMAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, 0); + [I_VolumeRestrictionTMFactory] = await deployVRTMAndVerifyed(account_polymath, I_MRProxied, 0); // STEP 6: Deploy the VolumeRestrictionTMFactory - [P_VolumeRestrictionTMFactory] = await deployVRTMAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, web3.utils.toWei("500")); + [P_VolumeRestrictionTMFactory] = await deployVRTMAndVerifyed(account_polymath, I_MRProxied, new BN(web3.utils.toWei("500"))); // Printing all the contract addresses console.log(` @@ -182,90 +216,96 @@ contract('VolumeRestrictionTransferManager', accounts => { describe("Generate the SecurityToken", async () => { it("Should register the ticker before the generation of the security token", async () => { await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); - let tx = await I_STRProxied.registerTicker(token_owner, symbol, contact, { from: token_owner }); + let tx = await I_STRProxied.registerNewTicker(token_owner, symbol, { from: token_owner }); assert.equal(tx.logs[0].args._owner, token_owner); assert.equal(tx.logs[0].args._ticker, symbol.toUpperCase()); }); it("Should generate the new security token with the same symbol as registered above", async () => { await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); - let _blockNo = latestBlock(); - let tx = await I_STRProxied.generateSecurityToken(name, symbol, tokenDetails, true, { from: token_owner }); + + let tx = await I_STRProxied.generateNewSecurityToken(name, symbol, tokenDetails, true, token_owner, 0, { from: token_owner }); // 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 log = await promisifyLogWatch(I_SecurityToken.ModuleAdded({ from: _blockNo }), 1); + I_SecurityToken = await SecurityToken.at(tx.logs[1].args._securityTokenAddress); + stGetter = await STGetter.at(I_SecurityToken.address); + assert.equal(await stGetter.getTreasuryWallet.call(), token_owner, "Incorrect wallet set"); + const log = (await I_SecurityToken.getPastEvents('ModuleAdded', {filter: {transactionHash: tx.transactionHash}}))[0]; // Verify that GeneralTransferManager module get added successfully or not - assert.equal(log.args._types[0].toNumber(), 2); + assert.equal(log.args._types[0].toString(), 2); assert.equal(web3.utils.toAscii(log.args._name).replace(/\u0000/g, ""), "GeneralTransferManager"); }); - it("Should intialize the auto attached modules", async () => { - let moduleData = (await I_SecurityToken.getModulesByType(2))[0]; - I_GeneralTransferManager = GeneralTransferManager.at(moduleData); + it("Should initialize the auto attached modules", async () => { + let moduleData = (await stGetter.getModulesByType(2))[0]; + I_GeneralTransferManager = await GeneralTransferManager.at(moduleData); }); }); - describe("Attach the VRTM", async () => { - it("Deploy the VRTM and attach with the ST", async () => { - let tx = await I_SecurityToken.addModule(I_VolumeRestrictionTMFactory.address, 0, 0, 0, { from: token_owner }); + describe("Attach the VRTM", async() => { + it("Deploy the VRTM and attach with the ST", async()=> { + let tx = await I_SecurityToken.addModule(I_VolumeRestrictionTMFactory.address, "0x0", new BN(0), new BN(0), false, {from: token_owner }); assert.equal(tx.logs[2].args._moduleFactory, I_VolumeRestrictionTMFactory.address); assert.equal( web3.utils.toUtf8(tx.logs[2].args._name), "VolumeRestrictionTM", "VolumeRestrictionTMFactory doesn not added"); - I_VolumeRestrictionTM = VolumeRestrictionTM.at(tx.logs[2].args._module); + I_VolumeRestrictionTM = await VolumeRestrictionTM.at(tx.logs[2].args._module); }); - it("Transfer some tokens to different account", async () => { + it("Transfer some tokens to different account", async() => { // Add tokens in to the whitelist - await I_GeneralTransferManager.modifyWhitelistMulti( - [account_investor1, account_investor2, account_investor3], - [latestTime(), latestTime(), latestTime()], - [latestTime(), latestTime(), latestTime()], - [latestTime() + duration.days(60), latestTime() + duration.days(60), latestTime() + duration.days(60)], - [true, true, true], - { - from: token_owner - } + let newLatestTime = await getLatestTime(); + await I_GeneralTransferManager.modifyKYCDataMulti( + [account_investor1, account_investor2, account_investor3], + [newLatestTime, newLatestTime, newLatestTime], + [newLatestTime, newLatestTime, newLatestTime], + [newLatestTime.add(new BN(duration.days(60))), newLatestTime.add(new BN(duration.days(60))), newLatestTime.add(new BN(duration.days(60)))], + { + from: token_owner + } ); // Mint some tokens and transferred to whitelisted addresses - await I_SecurityToken.mint(account_investor1, web3.utils.toWei("40", "ether"), { from: token_owner }); - await I_SecurityToken.mint(account_investor2, web3.utils.toWei("30", "ether"), { from: token_owner }); - await I_SecurityToken.mint(account_investor3, web3.utils.toWei("30", "ether"), { from: token_owner }); + await I_SecurityToken.issue(account_investor1, new BN(web3.utils.toWei("40", "ether")), "0x0", {from: token_owner}); + await I_SecurityToken.issue(account_investor2, new BN(web3.utils.toWei("30", "ether")), "0x0", {from: token_owner}); + await I_SecurityToken.issue(account_investor3, new BN(web3.utils.toWei("30", "ether")), "0x0", {from: token_owner}); // Check the balance of the investors let bal1 = await I_SecurityToken.balanceOf.call(account_investor1); let bal2 = await I_SecurityToken.balanceOf.call(account_investor2); // Verifying the balances - assert.equal(web3.utils.fromWei((bal1.toNumber()).toString()), 40); - assert.equal(web3.utils.fromWei((bal2.toNumber()).toString()), 30); + assert.equal(web3.utils.fromWei((bal1.toString()).toString()), 40); + assert.equal(web3.utils.fromWei((bal2.toString()).toString()), 30); }); - it("Should transfer the tokens freely without any restriction", async () => { - await I_SecurityToken.transfer(account_investor3, web3.utils.toWei('5', 'ether'), { from: account_investor1 }); + it("Should transfer the tokens freely without any restriction", async() => { + await verifyPartitionBalance(account_investor1, 0, 40); + console.log( + await I_SecurityToken.canTransfer.call(account_investor3, new BN(web3.utils.toWei('5', 'ether')), "0x0", {from: account_investor1}) + ) + console.log(web3.utils.fromWei((await I_SecurityToken.balanceOf.call(account_investor1)).toString())); + await I_SecurityToken.transfer(account_investor3, new BN(web3.utils.toWei('5', 'ether')), { from: account_investor1 }); let bal1 = await I_SecurityToken.balanceOf.call(account_investor3); - // Verifying the balances - assert.equal(web3.utils.fromWei((bal1.toNumber()).toString()), 35); + // Verifying the balances + assert.equal(web3.utils.fromWei((bal1.toString()).toString()), 35); }); }) - describe("Test for the addIndividualRestriction", async () => { - - it("Should add the restriction -- failed because of bad owner", async () => { + describe("Test for the addIndividualRestriction", async() => { + it("Should add the restriction -- failed because of bad owner", async() => { + let newLatestTime = await getLatestTime(); await catchRevert( I_VolumeRestrictionTM.addIndividualRestriction( account_investor1, - web3.utils.toWei("12"), - latestTime() + duration.seconds(2), + new BN(web3.utils.toWei("12")), + newLatestTime.add(new BN(duration.seconds(2))), 3, - latestTime() + duration.days(10), + newLatestTime.add(new BN(duration.days(10))), 0, { from: account_polymath @@ -274,14 +314,16 @@ contract('VolumeRestrictionTransferManager', accounts => { ); }) - it("Should add the restriction -- failed because of bad parameters i.e invalid restriction type", async () => { + it("Should add the restriction -- failed because of bad parameters i.e invalid restriction type", async() => { + let newLatestTime = await getLatestTime(); + await catchRevert( I_VolumeRestrictionTM.addIndividualRestriction( account_investor1, - web3.utils.toWei("12"), - latestTime() + duration.seconds(2), + new BN(web3.utils.toWei("12")), + newLatestTime.add(new BN(duration.seconds(2))), 3, - latestTime() + duration.days(10), + newLatestTime.add(new BN(duration.days(10))), 3, { from: token_owner @@ -290,14 +332,15 @@ contract('VolumeRestrictionTransferManager', accounts => { ); }) - it("Should add the restriction -- failed because of bad parameters i.e Invalid value of allowed tokens", async () => { + it("Should add the restriction -- failed because of bad parameters i.e Invalid value of allowed tokens", async() => { + let newLatestTime = await getLatestTime(); await catchRevert( I_VolumeRestrictionTM.addIndividualRestriction( account_investor1, 0, - latestTime() + duration.seconds(2), + newLatestTime.add(new BN(duration.seconds(2))), 3, - latestTime() + duration.days(10), + newLatestTime.add(new BN(duration.days(10))), 0, { from: token_owner @@ -306,14 +349,16 @@ contract('VolumeRestrictionTransferManager', accounts => { ); }) - it("Should add the restriction -- failed because of bad parameters i.e Percentage of tokens not within (0,100]", async () => { + it("Should add the restriction -- failed because of bad parameters i.e Percentage of tokens not within (0,100]", async() => { + let newLatestTime = await getLatestTime(); + await catchRevert( I_VolumeRestrictionTM.addIndividualRestriction( account_investor1, 0, - latestTime() + duration.seconds(2), + newLatestTime.add(new BN(duration.seconds(2))), 3, - latestTime() + duration.days(10), + newLatestTime.add(new BN(duration.days(10))), 1, { from: token_owner @@ -322,14 +367,15 @@ contract('VolumeRestrictionTransferManager', accounts => { ); }) - it("Should add the restriction -- failed because of bad parameters i.e Percentage of tokens not within (0,100]", async () => { + it("Should add the restriction -- failed because of bad parameters i.e Percentage of tokens not within (0,100]", async() => { + let newLatestTime = await getLatestTime(); await catchRevert( I_VolumeRestrictionTM.addIndividualRestriction( account_investor1, - web3.utils.toWei("10"), - latestTime() + duration.seconds(2), + new BN(web3.utils.toWei("10")), + newLatestTime.add(new BN(duration.seconds(2))), 3, - latestTime() + duration.days(10), + newLatestTime.add(new BN(duration.days(10))), 1, { from: token_owner @@ -338,14 +384,15 @@ contract('VolumeRestrictionTransferManager', accounts => { ); }) - it("Should add the restriction -- failed because of bad parameters i.e invalid dates", async () => { + it("Should add the restriction -- failed because of bad parameters i.e invalid dates", async() => { + let newLatestTime = await getLatestTime(); await catchRevert( I_VolumeRestrictionTM.addIndividualRestriction( account_investor1, - web3.utils.toWei("10"), - latestTime() - duration.seconds(5), + new BN(web3.utils.toWei("10")), + newLatestTime.sub(new BN(duration.seconds(5))), 3, - latestTime() + duration.days(10), + newLatestTime.add(new BN(duration.days(10))), 0, { from: token_owner @@ -354,14 +401,15 @@ contract('VolumeRestrictionTransferManager', accounts => { ); }) - it("Should add the restriction -- failed because of bad parameters i.e invalid dates", async () => { + it("Should add the restriction -- failed because of bad parameters i.e invalid dates", async() => { + let newLatestTime = await getLatestTime(); await catchRevert( I_VolumeRestrictionTM.addIndividualRestriction( account_investor1, - web3.utils.toWei("10"), - latestTime() + duration.days(2), + new BN(web3.utils.toWei("10")), + newLatestTime.add(new BN(duration.days(2))), 3, - latestTime() + duration.days(1), + newLatestTime.add(new BN(duration.days(1))), 0, { from: token_owner @@ -370,14 +418,15 @@ contract('VolumeRestrictionTransferManager', accounts => { ); }); - it("Should add the restriction -- failed because of bad parameters i.e invalid rolling period", async () => { + it("Should add the restriction -- failed because of bad parameters i.e invalid rolling period", async() => { + let newLatestTime = await getLatestTime(); await catchRevert( I_VolumeRestrictionTM.addIndividualRestriction( account_investor1, - web3.utils.toWei("10"), - latestTime() + duration.days(2), + new BN(web3.utils.toWei("10")), + newLatestTime.add(new BN(duration.days(2))), 0, - latestTime() + duration.days(10), + newLatestTime.add(new BN(duration.days(10))), 0, { from: token_owner @@ -386,14 +435,15 @@ contract('VolumeRestrictionTransferManager', accounts => { ); }); - it("Should add the restriction -- failed because of bad parameters i.e invalid rolling period", async () => { + it("Should add the restriction -- failed because of bad parameters i.e invalid rolling period", async() => { + let newLatestTime = await getLatestTime(); await catchRevert( I_VolumeRestrictionTM.addIndividualRestriction( account_investor1, - web3.utils.toWei("10"), - latestTime() + duration.days(2), + new BN(web3.utils.toWei("10")), + newLatestTime.add(new BN(duration.days(2))), 366, - latestTime() + duration.days(10), + newLatestTime.add(new BN(duration.days(10))), 0, { from: token_owner @@ -402,14 +452,15 @@ contract('VolumeRestrictionTransferManager', accounts => { ); }); - it("Should add the restriction -- failed because of bad parameters i.e invalid rolling period", async () => { + it("Should add the restriction -- failed because of bad parameters i.e invalid rolling period", async() => { + let newLatestTime = await getLatestTime(); await catchRevert( I_VolumeRestrictionTM.addIndividualRestriction( account_investor1, - web3.utils.toWei("10"), - latestTime() + duration.days(2), + new BN(web3.utils.toWei("10")), + newLatestTime.add(new BN(duration.days(2))), 3, - latestTime() + duration.days(3), + newLatestTime.add(new BN(duration.days(3))), 0, { from: token_owner @@ -418,34 +469,36 @@ contract('VolumeRestrictionTransferManager', accounts => { ); }); - it("Should add the restriction successfully", async () => { + it("Should add the restriction succesfully", async() => { + let newLatestTime = await getLatestTime(); let tx = await I_VolumeRestrictionTM.addIndividualRestriction( - account_investor1, - web3.utils.toWei("12"), - latestTime() + duration.seconds(10), - 3, - latestTime() + duration.days(5), - 0, - { - from: token_owner - } - ); + account_investor1, + new BN(web3.utils.toWei("12")), + newLatestTime.add(new BN(duration.seconds(2))), + 3, + newLatestTime.add(new BN(duration.days(5))), + 0, + { + from: token_owner + } + ); assert.equal(tx.logs[0].args._holder, account_investor1); assert.equal(tx.logs[0].args._typeOfRestriction, 0); - let data = await I_VolumeRestrictionTM.getRestrictedData.call(); + let data = await I_VolumeRestrictionTM.getRestrictionData.call(); await printRestrictedData(data); assert.equal(data[0][0], account_investor1); }); - it("Should add the restriction for multiple investor -- failed because of bad owner", async () => { + it("Should add the restriction for multiple investor -- failed because of bad owner", async() => { + let newLatestTime = await getLatestTime(); await catchRevert( I_VolumeRestrictionTM.addIndividualRestrictionMulti( [account_investor2, account_delegate3, account_investor4], - [web3.utils.toWei("12"), web3.utils.toWei("10"), web3.utils.toWei("15")], - [latestTime() + duration.seconds(5), latestTime() + duration.seconds(5), latestTime() + duration.seconds(5)], - [3, 4, 5], - [latestTime() + duration.days(5), latestTime() + duration.days(6), latestTime() + duration.days(7)], - [0, 0, 0], + [new BN(web3.utils.toWei("12")), new BN(web3.utils.toWei("10")), new BN(web3.utils.toWei("15"))], + [newLatestTime.add(new BN(duration.seconds(2))), newLatestTime.add(new BN(duration.seconds(2))), newLatestTime.add(new BN(duration.seconds(2)))], + [3,4,5], + [newLatestTime.add(new BN(duration.days(5))), newLatestTime.add(new BN(duration.days(6))), newLatestTime.add(new BN(duration.days(7)))], + [0,0,0], { from: account_polymath } @@ -453,15 +506,16 @@ contract('VolumeRestrictionTransferManager', accounts => { ) }); - it("Should add the restriction for multiple investor -- failed because of bad parameters i.e length mismatch", async () => { + it("Should add the restriction for multiple investor -- failed because of bad parameters i.e length mismatch", async() => { + let newLatestTime = await getLatestTime(); await catchRevert( I_VolumeRestrictionTM.addIndividualRestrictionMulti( [account_investor2, account_delegate3], - [web3.utils.toWei("12"), web3.utils.toWei("10"), web3.utils.toWei("15")], - [latestTime() + duration.seconds(5), latestTime() + duration.seconds(5), latestTime() + duration.seconds(5)], - [3, 4, 5], - [latestTime() + duration.days(5), latestTime() + duration.days(6), latestTime() + duration.days(7)], - [0, 0, 0], + [new BN(web3.utils.toWei("12")), new BN(web3.utils.toWei("10")), new BN(web3.utils.toWei("15"))], + [newLatestTime.add(new BN(duration.seconds(2))), newLatestTime.add(new BN(duration.seconds(2))), newLatestTime.add(new BN(duration.seconds(2)))], + [3,4,5], + [newLatestTime.add(new BN(duration.days(5))), newLatestTime.add(new BN(duration.days(6))), newLatestTime.add(new BN(duration.days(7)))], + [0,0,0], { from: token_owner } @@ -469,15 +523,16 @@ contract('VolumeRestrictionTransferManager', accounts => { ) }); - it("Should add the restriction for multiple investor -- failed because of bad parameters i.e length mismatch", async () => { + it("Should add the restriction for multiple investor -- failed because of bad parameters i.e length mismatch", async() => { + let newLatestTime = await getLatestTime(); await catchRevert( I_VolumeRestrictionTM.addIndividualRestrictionMulti( [account_investor2, account_delegate3, account_investor4], - [web3.utils.toWei("12"), web3.utils.toWei("10")], - [latestTime() + duration.seconds(5), latestTime() + duration.seconds(5), latestTime() + duration.seconds(5)], - [3, 4, 5], - [latestTime() + duration.days(5), latestTime() + duration.days(6), latestTime() + duration.days(7)], - [0, 0, 0], + [new BN(web3.utils.toWei("12")), new BN(web3.utils.toWei("10"))], + [newLatestTime.add(new BN(duration.seconds(2))), newLatestTime.add(new BN(duration.seconds(2))), newLatestTime.add(new BN(duration.seconds(2)))], + [3,4,5], + [newLatestTime.add(new BN(duration.days(5))), newLatestTime.add(new BN(duration.days(6))), newLatestTime.add(new BN(duration.days(7)))], + [0,0,0], { from: account_polymath } @@ -485,15 +540,16 @@ contract('VolumeRestrictionTransferManager', accounts => { ) }); - it("Should add the restriction for multiple investor -- failed because of bad parameters i.e length mismatch", async () => { + it("Should add the restriction for multiple investor -- failed because of bad parameters i.e length mismatch", async() => { + let newLatestTime = await getLatestTime(); await catchRevert( I_VolumeRestrictionTM.addIndividualRestrictionMulti( [account_investor2, account_delegate3, account_investor4], - [web3.utils.toWei("12"), web3.utils.toWei("10"), web3.utils.toWei("15")], - [latestTime() + duration.seconds(5), latestTime() + duration.seconds(5)], - [3, 4, 5], - [latestTime() + duration.days(5), latestTime() + duration.days(6), latestTime() + duration.days(7)], - [0, 0, 0], + [new BN(web3.utils.toWei("12")), new BN(web3.utils.toWei("10")), new BN(web3.utils.toWei("15"))], + [newLatestTime.add(new BN(duration.seconds(2))), newLatestTime.add(new BN(duration.seconds(2)))], + [3,4,5], + [newLatestTime.add(new BN(duration.days(5))), newLatestTime.add(new BN(duration.days(6))), newLatestTime.add(new BN(duration.days(7)))], + [0,0,0], { from: token_owner } @@ -501,15 +557,16 @@ contract('VolumeRestrictionTransferManager', accounts => { ) }); - it("Should add the restriction for multiple investor -- failed because of bad parameters i.e length mismatch", async () => { + it("Should add the restriction for multiple investor -- failed because of bad parameters i.e length mismatch", async() => { + let newLatestTime = await getLatestTime(); await catchRevert( I_VolumeRestrictionTM.addIndividualRestrictionMulti( [account_investor2, account_delegate3, account_investor4], - [web3.utils.toWei("12"), web3.utils.toWei("10"), web3.utils.toWei("15")], - [latestTime() + duration.seconds(5), latestTime() + duration.seconds(5), latestTime() + duration.seconds(5)], + [new BN(web3.utils.toWei("12")), new BN(web3.utils.toWei("10")), new BN(web3.utils.toWei("15"))], + [newLatestTime.add(new BN(duration.seconds(2))), newLatestTime.add(new BN(duration.seconds(2))), newLatestTime.add(new BN(duration.seconds(2)))], [3], - [latestTime() + duration.days(5), latestTime() + duration.days(6), latestTime() + duration.days(7)], - [0, 0, 0], + [newLatestTime.add(new BN(duration.days(5))), newLatestTime.add(new BN(duration.days(6))), newLatestTime.add(new BN(duration.days(7)))], + [0,0,0], { from: token_owner } @@ -517,15 +574,16 @@ contract('VolumeRestrictionTransferManager', accounts => { ) }); - it("Should add the restriction for multiple investor -- failed because of bad parameters i.e length mismatch", async () => { + it("Should add the restriction for multiple investor -- failed because of bad parameters i.e length mismatch", async() => { + let newLatestTime = await getLatestTime(); await catchRevert( I_VolumeRestrictionTM.addIndividualRestrictionMulti( [account_investor2, account_delegate3, account_investor4], - [web3.utils.toWei("12"), web3.utils.toWei("10"), web3.utils.toWei("15")], - [latestTime() + duration.seconds(5), latestTime() + duration.seconds(5), latestTime() + duration.seconds(5)], + [new BN(web3.utils.toWei("12")), new BN(web3.utils.toWei("10")), new BN(web3.utils.toWei("15"))], + [newLatestTime.add(new BN(duration.seconds(2))), newLatestTime.add(new BN(duration.seconds(2))), newLatestTime.add(new BN(duration.seconds(2)))], [3, 4, 5], - [latestTime() + duration.days(5)], - [0, 0, 0], + [newLatestTime.add(new BN(duration.days(5)))], + [0,0,0], { from: token_owner } @@ -533,14 +591,15 @@ contract('VolumeRestrictionTransferManager', accounts => { ) }); - it("Should add the restriction for multiple investor -- failed because of bad parameters i.e length mismatch", async () => { + it("Should add the restriction for multiple investor -- failed because of bad parameters i.e length mismatch", async() => { + let newLatestTime = await getLatestTime(); await catchRevert( I_VolumeRestrictionTM.addIndividualRestrictionMulti( [account_investor2, account_delegate3, account_investor4], - [web3.utils.toWei("12"), web3.utils.toWei("10"), web3.utils.toWei("15")], - [latestTime() + duration.seconds(5), latestTime() + duration.seconds(5), latestTime() + duration.seconds(5)], + [new BN(web3.utils.toWei("12")), new BN(web3.utils.toWei("10")), new BN(web3.utils.toWei("15"))], + [newLatestTime.add(new BN(duration.seconds(2))), newLatestTime.add(new BN(duration.seconds(2))), newLatestTime.add(new BN(duration.seconds(2)))], [3, 4, 5], - [latestTime() + duration.days(5), latestTime() + duration.days(6), latestTime() + duration.days(7)], + [newLatestTime.add(new BN(duration.days(5))), newLatestTime.add(new BN(duration.days(6))), newLatestTime.add(new BN(duration.days(7)))], [], { from: token_owner @@ -549,31 +608,32 @@ contract('VolumeRestrictionTransferManager', accounts => { ) }); - it("Should add the restriction for multiple investor successfully", async () => { + it("Should add the restriction for multiple investor successfully", async() => { + let newLatestTime = await getLatestTime(); await I_VolumeRestrictionTM.addIndividualRestrictionMulti( - [account_investor2, account_delegate3, account_investor4], - [web3.utils.toWei("12"), web3.utils.toWei("10"), web3.utils.toWei("15")], - [latestTime() + duration.minutes(1), latestTime() + duration.minutes(1), latestTime() + duration.minutes(1)], - [3, 4, 5], - [latestTime() + duration.days(5), latestTime() + duration.days(6), latestTime() + duration.days(7)], - [0, 0, 0], - { - from: token_owner - } + [account_investor2, account_delegate3, account_investor4], + [new BN(web3.utils.toWei("12")), new BN(web3.utils.toWei("10")), new BN(web3.utils.toWei("15"))], + [0, 0, 0], + [3, 4, 5], + [newLatestTime.add(new BN(duration.days(5))), newLatestTime.add(new BN(duration.days(6))), newLatestTime.add(new BN(duration.days(7)))], + [0,0,0], + { + from: token_owner + } ); - assert.equal((await I_VolumeRestrictionTM.individualRestriction.call(account_investor2))[2].toNumber(), 3); - assert.equal((await I_VolumeRestrictionTM.individualRestriction.call(account_delegate3))[2].toNumber(), 4); - assert.equal((await I_VolumeRestrictionTM.individualRestriction.call(account_investor4))[2].toNumber(), 5); + assert.equal((await I_VolumeRestrictionTM.getIndividualRestriction.call(account_investor2))[2].toString(), 3); + assert.equal((await I_VolumeRestrictionTM.getIndividualRestriction.call(account_delegate3))[2].toString(), 4); + assert.equal((await I_VolumeRestrictionTM.getIndividualRestriction.call(account_investor4))[2].toString(), 5); - let data = await I_VolumeRestrictionTM.getRestrictedData.call(); + let data = await I_VolumeRestrictionTM.getRestrictionData.call(); await printRestrictedData(data); assert.equal(data[0].length, 4); }); - it("Should remove the restriction multi -- failed because of address is 0", async () => { + it("Should remove the restriction multi -- failed because of address is 0", async() => { await catchRevert( I_VolumeRestrictionTM.removeIndividualRestrictionMulti( - [0, account_delegate3, account_investor4], + [address_zero, account_delegate3, account_investor4], { from: token_owner } @@ -581,10 +641,10 @@ contract('VolumeRestrictionTransferManager', accounts => { ); }); - it("Should successfully remove the restriction", async () => { - await I_VolumeRestrictionTM.removeIndividualRestriction(account_investor2, { from: token_owner }); - assert.equal((await I_VolumeRestrictionTM.individualRestriction.call(account_investor2))[3].toNumber(), 0); - let data = await I_VolumeRestrictionTM.getRestrictedData.call(); + it("Should successfully remove the restriction", async() => { + await I_VolumeRestrictionTM.removeIndividualRestriction(account_investor2, {from: token_owner}); + assert.equal((await I_VolumeRestrictionTM.getIndividualRestriction.call(account_investor2))[3].toString(), 0); + let data = await I_VolumeRestrictionTM.getRestrictionData.call(); await printRestrictedData(data); assert.equal(data[0].length, 3); for (let i = 0; i < data[0].length; i++) { @@ -592,106 +652,112 @@ contract('VolumeRestrictionTransferManager', accounts => { } }); - it("Should remove the restriction -- failed because restriction not present anymore", async () => { + it("Should remove the restriction -- failed because restriction not present anymore", async() => { await catchRevert( - I_VolumeRestrictionTM.removeIndividualRestriction(account_investor2, { from: token_owner }) + I_VolumeRestrictionTM.removeIndividualRestriction(account_investor2, {from: token_owner}) ); }); - it("Should remove the restriction multi", async () => { + it("Should remove the restriction multi", async() => { await I_VolumeRestrictionTM.removeIndividualRestrictionMulti( [account_delegate3, account_investor4], { from: token_owner } ) - let data = await I_VolumeRestrictionTM.getRestrictedData.call(); + let data = await I_VolumeRestrictionTM.getRestrictionData.call(); await printRestrictedData(data); assert.equal(data[0].length, 1); }); - it("Should add the restriction successfully after the expiry of previous one for investor 1", async () => { + it("Should add the restriction succesfully after the expiry of previous one for investor 1", async() => { await increaseTime(duration.days(5.1)); - + let newLatestTime = await getLatestTime(); console.log( `Estimated gas for addIndividualRestriction: ${await I_VolumeRestrictionTM.addIndividualRestriction.estimateGas( account_investor1, - web3.utils.toWei("12"), - 0, + new BN(web3.utils.toWei("12")), + newLatestTime.add(new BN(duration.seconds(2))), 3, - latestTime() + duration.days(6), + newLatestTime.add(new BN(duration.days(6))), 0, { from: token_owner } )} `); - + newLatestTime = await getLatestTime(); let tx = await I_VolumeRestrictionTM.addIndividualRestriction( - account_investor1, - web3.utils.toWei("12"), - 0, - 3, - latestTime() + duration.days(6), - 0, - { - from: token_owner - } - ); + account_investor1, + new BN(web3.utils.toWei("12")), + newLatestTime.add(new BN(duration.seconds(2))), + 3, + newLatestTime.add(new BN(duration.days(6))), + 0, + { + from: token_owner + } + ); assert.equal(tx.logs[1].args._holder, account_investor1); assert.equal(tx.logs[1].args._typeOfRestriction, 0); - let data = await I_VolumeRestrictionTM.getRestrictedData.call(); + let data = await I_VolumeRestrictionTM.getRestrictionData.call(); await printRestrictedData(data); assert.equal(data[0].length, 1); assert.equal(data[0][0], account_investor1); }); - it("Should not successfully transact the tokens -- failed because volume is above the limit", async () => { + it("Should not successfully transact the tokens -- failed because volume is above the limit", async() => { await increaseTime(duration.seconds(10)); await catchRevert( - I_SecurityToken.transfer(account_investor3, web3.utils.toWei("13"), { from: account_investor1 }) + I_SecurityToken.transfer(account_investor3, new BN(web3.utils.toWei("13")), { from: account_investor1}) ); }); - it("Should successfully transact the tokens by investor 1 just after the startTime", async () => { + it("Should succesfully transact the tokens by investor 1 just after the startTime", async() => { // Check the transfer will be valid or not by calling the verifyTransfer() directly by using _isTransfer = false - let result = await I_VolumeRestrictionTM.verifyTransfer.call(account_investor1, account_investor3, web3.utils.toWei('.3'), "0x0", false); - assert.equal(result.toNumber(), 1); + let result = await I_VolumeRestrictionTM.verifyTransfer.call(account_investor1, account_investor3, new BN(web3.utils.toWei('.3', "ether")), "0x0"); + assert.equal(result[0].toString(), 1); // Perform the transaction console.log(` - Gas estimation (Individual): ${await I_SecurityToken.transfer.estimateGas(account_investor3, web3.utils.toWei('.3'), { from: account_investor1 })}` + Gas estimation (Individual): ${await I_SecurityToken.transfer.estimateGas(account_investor3, new BN(web3.utils.toWei('.3', "ether")), {from: account_investor1})}` ); - await I_SecurityToken.transfer(account_investor3, web3.utils.toWei('.3'), { from: account_investor1 }); + // Back and forth verifying the partition balances + await verifyPartitionBalance(account_investor1, 23, 12); + await I_VolumeRestrictionTM.pause({from: token_owner}); + await verifyPartitionBalance(account_investor1, 0, 35); + await I_VolumeRestrictionTM.unpause({from: token_owner}); + await verifyPartitionBalance(account_investor1, 23, 12); + + await I_SecurityToken.transfer(account_investor3, new BN(web3.utils.toWei('.3')), {from: account_investor1}); // Check the balance of the investors let bal1 = await I_SecurityToken.balanceOf.call(account_investor1); // Verifying the balances - assert.equal(web3.utils.fromWei((bal1.toNumber()).toString()), 34.7); + assert.equal(web3.utils.fromWei((bal1.toString()).toString()), 34.7); let data = await I_VolumeRestrictionTM.getIndividualBucketDetailsToUser.call(account_investor1); await print(data, account_investor1); - assert.equal( - (await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_investor1, data[0])) - .dividedBy(new BigNumber(10).pow(18)).toNumber(), + web3.utils.fromWei(await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_investor1, data[0])), 0.3 ); assert.equal( - data[0].toNumber(), - (await I_VolumeRestrictionTM.individualRestriction.call(account_investor1))[1].toNumber() + data[0].toString(), + (await I_VolumeRestrictionTM.getIndividualRestriction.call(account_investor1))[1].toString() ); - assert.equal(web3.utils.fromWei((data[1].toNumber()).toString()), 0.3); + assert.equal(web3.utils.fromWei(data[1].toString()), 0.3); tempArray.push(0.3); }); - it("Should fail to add the individual daily restriction -- Bad msg.sender", async () => { + it("Should fail to add the individual daily restriction -- Bad msg.sender", async() => { + let newLatestTime = await getLatestTime(); await catchRevert( I_VolumeRestrictionTM.addIndividualDailyRestriction( account_investor3, - web3.utils.toWei("6"), - latestTime() + duration.seconds(1), - latestTime() + duration.days(4), + new BN(web3.utils.toWei("6")), + newLatestTime.add(new BN(duration.seconds(1))), + newLatestTime.add(new BN(duration.days(4))), 0, { from: account_investor1 @@ -700,13 +766,14 @@ contract('VolumeRestrictionTransferManager', accounts => { ); }) - it("Should fail to add the individual daily restriction -- Bad params value", async () => { + it("Should fail to add the individual daily restriction -- Bad params value", async() => { + let newLatestTime = await getLatestTime(); await catchRevert( I_VolumeRestrictionTM.addIndividualDailyRestriction( account_investor3, - web3.utils.toWei("6"), - latestTime() + duration.seconds(1), - latestTime() + duration.days(4), + new BN(web3.utils.toWei("6")), + newLatestTime.add(new BN(duration.seconds(1))), + newLatestTime.add(new BN(duration.days(4))), 1, { from: token_owner @@ -715,13 +782,14 @@ contract('VolumeRestrictionTransferManager', accounts => { ); }) - it("Should fail to add the individual daily restriction -- Bad params value", async () => { + it("Should fail to add the individual daily restriction -- Bad params value", async() => { + let newLatestTime = await getLatestTime(); await catchRevert( I_VolumeRestrictionTM.addIndividualDailyRestriction( account_investor3, 0, - latestTime() + duration.seconds(1), - latestTime() + duration.days(4), + newLatestTime.add(new BN(duration.seconds(1))), + newLatestTime.add(new BN(duration.days(4))), 0, { from: token_owner @@ -730,13 +798,14 @@ contract('VolumeRestrictionTransferManager', accounts => { ); }) - it("Should fail to add the individual daily restriction -- Bad params value", async () => { + it("Should fail to add the individual daily restriction -- Bad params value", async() => { + let newLatestTime = await getLatestTime(); await catchRevert( I_VolumeRestrictionTM.addIndividualDailyRestriction( account_investor3, - web3.utils.toWei("6"), - latestTime() + duration.days(5), - latestTime() + duration.days(4), + new BN(web3.utils.toWei("6")), + newLatestTime.add(new BN(duration.days(5))), + newLatestTime.add(new BN(duration.days(4))), 0, { from: token_owner @@ -745,92 +814,101 @@ contract('VolumeRestrictionTransferManager', accounts => { ); }) - it("Should add the individual daily restriction for investor 3", async () => { + it("Should add the individual daily restriction for investor 3", async() => { + let newLatestTime = await getLatestTime(); let tx = await I_VolumeRestrictionTM.addIndividualDailyRestriction( - account_investor3, - web3.utils.toWei("6"), - latestTime() + duration.seconds(10), - latestTime() + duration.days(4), - 0, - { - from: token_owner - } - ); + account_investor3, + new BN(web3.utils.toWei("6")), + newLatestTime.add(new BN(duration.seconds(10))), + newLatestTime.add(new BN(duration.days(4))), + 0, + { + from: token_owner + } + ); assert.equal(tx.logs[0].args._holder, account_investor3); assert.equal(tx.logs[0].args._typeOfRestriction, 0); - assert.equal((tx.logs[0].args._allowedTokens).toNumber(), web3.utils.toWei("6")); - let data = await I_VolumeRestrictionTM.getRestrictedData.call(); + assert.equal((tx.logs[0].args._allowedTokens).toString(), new BN(web3.utils.toWei("6"))); + let data = await I_VolumeRestrictionTM.getRestrictionData.call(); await printRestrictedData(data); assert.equal(data[0].length, 2); assert.equal(data[0][1], account_investor3); - let dataRestriction = await I_VolumeRestrictionTM.individualDailyRestriction.call(account_investor3); + let dataRestriction = await I_VolumeRestrictionTM.getIndividualDailyRestriction.call(account_investor3); console.log(` *** Individual Daily restriction data *** - Allowed Tokens: ${dataRestriction[0].dividedBy(new BigNumber(10).pow(18)).toNumber()} - StartTime : ${dataRestriction[1].toNumber()} - Rolling Period in days : ${dataRestriction[2].toNumber()} - EndTime : ${dataRestriction[3].toNumber()} - Type of Restriction: ${dataRestriction[4].toNumber()} + Allowed Tokens: ${dataRestriction[0].div(new BN(10).pow(new BN(18))).toString()} + StartTime : ${dataRestriction[1].toString()} + Rolling Period in days : ${dataRestriction[2].toString()} + EndTime : ${dataRestriction[3].toString()} + Type of Restriction: ${dataRestriction[4].toString()} `); }); - it("Should transfer the tokens within the individual daily restriction limits", async () => { + it("Should transfer the tokens within the individual daily restriction limits", async() => { // transfer 2 tokens as per the limit - await increaseTime(15); // increase 5 seconds to layoff the time gap - let startTime = (await I_VolumeRestrictionTM.individualDailyRestriction.call(account_investor3))[1].toNumber(); + await increaseTime(12); // increase 12 seconds to layoff the time gap + // verify the partition balance + console.log(web3.utils.fromWei((await I_SecurityToken.balanceOf.call(account_investor3)).toString())); + await verifyPartitionBalance(account_investor3, 29.3, 6); + + let startTime = (await I_VolumeRestrictionTM.getIndividualDailyRestriction.call(account_investor3))[1].toString(); console.log(` - Gas Estimation for the Individual daily tx - ${await I_SecurityToken.transfer.estimateGas(account_investor2, web3.utils.toWei("2"), { from: account_investor3 })} + Gas Estimation for the Individual daily tx - ${await I_SecurityToken.transfer.estimateGas(account_investor2, new BN(web3.utils.toWei("2")), {from: account_investor3})} `) - await I_SecurityToken.transfer(account_investor2, web3.utils.toWei("2"), { from: account_investor3 }); + await I_SecurityToken.transfer(account_investor2, new BN(web3.utils.toWei("2")), {from: account_investor3}); let data = await I_VolumeRestrictionTM.getIndividualBucketDetailsToUser.call(account_investor3); await print(data, account_investor3); - await increaseTime(duration.minutes(15)); - + // verify the partition balance + await verifyPartitionBalance(account_investor3, 29.3, 4); console.log(` - Gas Estimation for the Individual daily tx - ${await I_SecurityToken.transfer.estimateGas(account_investor2, web3.utils.toWei("4"), { from: account_investor3 })} + Gas Estimation for the Individual daily tx - ${await I_SecurityToken.transfer.estimateGas(account_investor2, new BN(web3.utils.toWei("4")), {from: account_investor3})} `) // transfer the 4 tokens which is under the limit - await I_SecurityToken.transfer(account_investor2, web3.utils.toWei("4"), { from: account_investor3 }); + await I_SecurityToken.transfer(account_investor2, new BN(web3.utils.toWei("4")), {from: account_investor3}); let newData = await I_VolumeRestrictionTM.getIndividualBucketDetailsToUser.call(account_investor3); await print(newData, account_investor3); - - assert.equal(newData[3].toNumber(), data[3].toNumber()); - assert.equal(data[3].toNumber(), startTime); - assert.equal((await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_investor3, data[3])) - .dividedBy(new BigNumber(10).pow(18)).toNumber(), 6); + // verify the partition balance + await verifyPartitionBalance(account_investor3, 29.3, 0); + assert.equal(newData[3].toString(), data[3].toString()); + assert.equal(data[3].toString(), startTime); + assert.equal(web3.utils.fromWei(await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_investor3, data[3])) + , 6); }); - it("Should fail to transfer more tokens --because of the above limit", async () => { + it("Should fail to transfer more tokens --because of the above limit", async() => { await catchRevert( - I_SecurityToken.transfer(account_investor2, web3.utils.toWei(".1"), { from: account_investor3 }) + I_SecurityToken.transfer(account_investor2, new BN(web3.utils.toWei(".1")), {from: account_investor3}) ); }); - it("Should try to send after the one day completion", async () => { + it("Should try to send after the one day completion", async() => { // increase the EVM time by one day await increaseTime(duration.days(1)); - let startTime = (await I_VolumeRestrictionTM.individualDailyRestriction.call(account_investor3))[1].toNumber(); + let startTime = (await I_VolumeRestrictionTM.getIndividualDailyRestriction.call(account_investor3))[1].toString(); console.log(` - Gas Estimation for the Individual daily tx - ${await I_SecurityToken.transfer.estimateGas(account_investor2, web3.utils.toWei("2"), { from: account_investor3 })} + Gas Estimation for the Individual daily tx - ${await I_SecurityToken.transfer.estimateGas(account_investor2, new BN(web3.utils.toWei("2")), {from: account_investor3})} `) - await I_SecurityToken.transfer(account_investor2, web3.utils.toWei("2"), { from: account_investor3 }); + // verify the partition balance + await verifyPartitionBalance(account_investor3, 23.3, 6); + await I_SecurityToken.transfer(account_investor2, new BN(web3.utils.toWei("2")), {from: account_investor3}); let data = await I_VolumeRestrictionTM.getIndividualBucketDetailsToUser.call(account_investor3); await print(data, account_investor3); - - assert.equal(data[3].toNumber(), startTime + duration.days(1)); - assert.equal((await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_investor3, data[3])) - .dividedBy(new BigNumber(10).pow(18)).toNumber(), 2); + await verifyPartitionBalance(account_investor3, 23.3, 4); + assert.equal(data[3].toString(), new BN(startTime).add(new BN(duration.days(1)))); + assert.equal(web3.utils.fromWei(await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_investor3, data[3])) + , 2); }); - it("Should add the daily restriction on the investor 1", async () => { + it("Should add the daily restriction on the investor 1", async() => { + let newLatestTime = await getLatestTime(); let tx = await I_VolumeRestrictionTM.addIndividualDailyRestriction( account_investor1, - new BigNumber(5).times(new BigNumber(10).pow(16)), - latestTime() + duration.seconds(5), - latestTime() + duration.days(4), + new BN(5).mul(new BN(10).pow(new BN(16))), + 0, + newLatestTime.add(new BN(duration.days(4))), 1, { from: token_owner @@ -838,70 +916,75 @@ contract('VolumeRestrictionTransferManager', accounts => { ); assert.equal(tx.logs[0].args._holder, account_investor1); - assert.equal((tx.logs[0].args._typeOfRestriction).toNumber(), 1); - assert.equal((tx.logs[0].args._allowedTokens).dividedBy(new BigNumber(10).pow(16)).toNumber(), 5); - let data = await I_VolumeRestrictionTM.getRestrictedData.call(); + assert.equal((tx.logs[0].args._typeOfRestriction).toString(), 1); + assert.equal(web3.utils.fromWei(new BN(tx.logs[0].args._allowedTokens)), 0.05); + let data = await I_VolumeRestrictionTM.getRestrictionData.call(); await printRestrictedData(data); assert.equal(data[0].length, 3); assert.equal(data[0][2], account_investor3); assert.equal(data[0][0], account_investor1); - let dataRestriction = await I_VolumeRestrictionTM.individualDailyRestriction.call(account_investor1); + let dataRestriction = await I_VolumeRestrictionTM.getIndividualDailyRestriction.call(account_investor1); console.log(` *** Individual Daily restriction data *** - Allowed Tokens: ${dataRestriction[0].dividedBy(new BigNumber(10).pow(16)).toNumber()} % of TotalSupply - StartTime : ${dataRestriction[1].toNumber()} - Rolling Period in days : ${dataRestriction[2].toNumber()} - EndTime : ${dataRestriction[3].toNumber()} - Type of Restriction: ${dataRestriction[4].toNumber()} + Allowed Tokens: ${dataRestriction[0].div(new BN(10).pow(new BN(16))).toString()} % of TotalSupply + StartTime : ${dataRestriction[1].toString()} + Rolling Period in days : ${dataRestriction[2].toString()} + EndTime : ${dataRestriction[3].toString()} + Type of Restriction: ${dataRestriction[4].toString()} `); + // verify the partition balance + let currentBalance = web3.utils.fromWei((await I_SecurityToken.balanceOf.call(account_investor1)).toString()); + let percentatgeBalance = dataRestriction[0].div(new BN(10).pow(new BN(16))).toString(); + await increaseTime(2); + await verifyPartitionBalance(account_investor1, 29.7, percentatgeBalance); }); - it("Should transfer tokens on the 2nd day by investor1 (Individual + Individual daily)", async () => { - await increaseTime(6); - let startTime = (await I_VolumeRestrictionTM.individualRestriction.call(account_investor1))[1].toNumber(); - let rollingPeriod = (await I_VolumeRestrictionTM.individualRestriction.call(account_investor1))[2].toNumber(); + it("Should transfer tokens on the 2nd day by investor1 (Individual + Individual daily)", async() => { + await increaseTime(2); + let startTime = (await I_VolumeRestrictionTM.getIndividualRestriction.call(account_investor1))[1].toString(); + let rollingPeriod = (await I_VolumeRestrictionTM.getIndividualRestriction.call(account_investor1))[2].toString(); console.log(` - Gas estimation (Individual + Individual daily): ${await I_SecurityToken.transfer.estimateGas(account_investor2, web3.utils.toWei("2"), { from: account_investor1 })}` + Gas estimation (Individual + Individual daily): ${await I_SecurityToken.transfer.estimateGas(account_investor2, new BN(web3.utils.toWei("2")), {from: account_investor1})}` ); - await I_SecurityToken.transfer(account_investor2, web3.utils.toWei("2"), { from: account_investor1 }); + await I_SecurityToken.transfer(account_investor2, new BN(web3.utils.toWei("2")), {from: account_investor1}); + await verifyPartitionBalance(account_investor1, 29.7, 3); // Check the balance of the investors let bal1 = await I_SecurityToken.balanceOf.call(account_investor1); // Verifying the balances - assert.equal(web3.utils.fromWei((bal1.toNumber()).toString()), 32.7); + assert.equal(web3.utils.fromWei(bal1.toString()), 32.7); tempArray.push(2); let data = await I_VolumeRestrictionTM.getIndividualBucketDetailsToUser.call(account_investor1); await print(data, account_investor1); // get the trade amount using the timestamp - let amt = (await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_investor1, data[0].toNumber())) - .dividedBy(new BigNumber(10).pow(18)).toNumber(); - + let amt = web3.utils.fromWei(await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_investor1, data[0])); // Verify the storage changes - assert.equal(data[0].toNumber(), startTime + duration.days(data[2].toNumber())); - assert.equal(data[1].dividedBy(new BigNumber(10).pow(18)).toNumber(), await calculateSum(rollingPeriod, tempArray)); - assert.equal(data[2].toNumber(), 1); - assert.equal(data[3].toNumber(), - (await I_VolumeRestrictionTM.individualDailyRestriction.call(account_investor1))[1].toNumber()); + assert.equal(data[0].toString(), new BN(startTime).add(new BN(duration.days(data[2].toString())))); + assert.equal(web3.utils.fromWei(data[1]), await calculateSum(rollingPeriod, tempArray)); + assert.equal(data[2].toString(), 1); + assert.equal(data[3].toString(), + (await I_VolumeRestrictionTM.getIndividualDailyRestriction.call(account_investor1))[1].toString()); assert.equal(amt, 2); }); - it("Should fail to transfer by investor 1 -- because voilating the individual daily", async () => { + it("Should fail to transfer by investor 1 -- because voilating the individual daily", async() => { // transfer 4 tokens -- voilate the daily restriction await catchRevert( - I_SecurityToken.transfer(account_investor2, web3.utils.toWei("4"), { from: account_investor1 }) + I_SecurityToken.transfer(account_investor2, new BN(web3.utils.toWei("4")), {from: account_investor1}) ); }); - it("Should add the individual restriction to investor 3", async () => { + it("Should add the individual restriction to investor 3", async() => { + let newLatestTime = await getLatestTime(); let tx = await I_VolumeRestrictionTM.addIndividualRestriction( account_investor3, - new BigNumber(15.36).times(new BigNumber(10).pow(16)), // 15.36 tokens as totalsupply is 1000 - latestTime() + duration.seconds(10), + new BN(1536).mul(new BN(10).pow(new BN(14))), // 15.36 tokens as totalsupply is 1000 + newLatestTime.add(new BN(duration.seconds(2))), 6, - latestTime() + duration.days(15), + newLatestTime.add(new BN(duration.days(15))), 1, { from: token_owner @@ -911,97 +994,103 @@ contract('VolumeRestrictionTransferManager', accounts => { assert.equal(tx.logs[0].args._holder, account_investor3); assert.equal(tx.logs[0].args._typeOfRestriction, 1); - let data = await I_VolumeRestrictionTM.getRestrictedData.call(); + let data = await I_VolumeRestrictionTM.getRestrictionData.call(); await printRestrictedData(data); assert.equal(data[0].length, 4); assert.equal(data[0][2], account_investor3); assert.equal(data[0][0], account_investor1); }); - it("Should transfer the token by the investor 3 with in the (Individual + Individual daily limit)", async () => { - await increaseTime(duration.seconds(15)); + it("Should transfer the token by the investor 3 with in the (Individual + Individual daily limit)", async() => { + await increaseTime(4); + console.log("Balance of investor 3" + + web3.utils.fromWei((await I_SecurityToken.balanceOf.call(account_investor3)).toString()) + ) + await verifyPartitionBalance(account_investor3, 23.3, 4); // Allowed 4 tokens to transfer - let startTime = (await I_VolumeRestrictionTM.individualRestriction.call(account_investor3))[1].toNumber(); - let rollingPeriod = (await I_VolumeRestrictionTM.individualRestriction.call(account_investor3))[2].toNumber(); - let startTimeDaily = (await I_VolumeRestrictionTM.individualDailyRestriction.call(account_investor3))[1].toNumber(); + let startTime = (await I_VolumeRestrictionTM.getIndividualRestriction.call(account_investor3))[1].toString(); + let rollingPeriod = (await I_VolumeRestrictionTM.getIndividualRestriction.call(account_investor3))[2].toString(); + let startTimeDaily = (await I_VolumeRestrictionTM.getIndividualDailyRestriction.call(account_investor3))[1].toString(); console.log(` - Gas estimation (Individual + Individual daily): ${await I_SecurityToken.transfer.estimateGas(account_investor2, web3.utils.toWei("4"), { from: account_investor3 })}` + Gas estimation (Individual + Individual daily): ${await I_SecurityToken.transfer.estimateGas(account_investor2, new BN(web3.utils.toWei("4")), {from: account_investor3})}` ); // Check the balance of the investors let bal1 = await I_SecurityToken.balanceOf.call(account_investor3); - await I_SecurityToken.transfer(account_investor2, web3.utils.toWei("4"), { from: account_investor3 }); + await I_SecurityToken.transfer(account_investor2, new BN(web3.utils.toWei("4")), {from: account_investor3}); + await verifyPartitionBalance(account_investor3, 23.3, 0); tempArray3.push(4); // Check the balance of the investors let bal2 = await I_SecurityToken.balanceOf.call(account_investor3); // Verifying the balances - assert.equal(web3.utils.fromWei(((bal1.minus(bal2)).toNumber()).toString()), 4); + assert.equal(web3.utils.fromWei(((bal1.sub(bal2)).toString()).toString()), 4); let data = await I_VolumeRestrictionTM.getIndividualBucketDetailsToUser.call(account_investor3); await print(data, account_investor3); // get the trade amount using the timestamp - let amt = (await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_investor3, data[0].toNumber())) - .dividedBy(new BigNumber(10).pow(18)).toNumber(); + let amt = (await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_investor3, data[0].toString())) + .div(new BN(10).pow(new BN(18))).toString(); // Verify the storage changes - assert.equal(data[0].toNumber(), startTime + duration.days(data[2].toNumber())); - assert.equal(data[1].dividedBy(new BigNumber(10).pow(18)).toNumber(), 4); - assert.equal(data[2].toNumber(), 0); - assert.equal(data[3].toNumber(), startTimeDaily + duration.days(1)); + assert.equal(data[0].toString(), new BN(startTime).add(new BN(duration.days(data[2].toString())))); + assert.equal(web3.utils.fromWei(data[1]), 4); + assert.equal(data[2].toString(), 0); + assert.equal(data[3].toString(), new BN(startTimeDaily).add(new BN(duration.days(1)))); assert.equal(amt, 4); - }); - it("Should fail during transferring more tokens by investor3 -- Voilating the daily Limit", async () => { + it("Should fail during transferring more tokens by investor3 -- Voilating the daily Limit", async() => { await catchRevert( - I_SecurityToken.transfer(account_investor2, web3.utils.toWei("1"), { from: account_investor3 }) + I_SecurityToken.transfer(account_investor2, new BN(web3.utils.toWei("1")), {from: account_investor3}) ); }); - it("Should remove the daily individual limit and transfer more tokens on a same day -- failed because of bad owner", async () => { + it("Should remove the daily individual limit and transfer more tokens on a same day -- failed because of bad owner", async() => { // remove the Individual daily restriction await catchRevert( - I_VolumeRestrictionTM.removeIndividualDailyRestriction(account_investor3, { from: account_investor4 }) + I_VolumeRestrictionTM.removeIndividualDailyRestriction(account_investor3, {from: account_investor4}) ); }) - it("Should remove the daily individual limit and transfer more tokens on a same day", async () => { + it("Should remove the daily individual limit and transfer more tokens on a same day", async() => { // remove the Individual daily restriction - let tx = await I_VolumeRestrictionTM.removeIndividualDailyRestriction(account_investor3, { from: token_owner }); + let tx = await I_VolumeRestrictionTM.removeIndividualDailyRestriction(account_investor3, {from: token_owner}); assert.equal(tx.logs[0].args._holder, account_investor3); - let dataAdd = await I_VolumeRestrictionTM.getRestrictedData.call(); + let dataAdd = await I_VolumeRestrictionTM.getRestrictionData.call(); await printRestrictedData(dataAdd); assert.equal(dataAdd[0].length, 3); assert.equal(dataAdd[0][0], account_investor1); assert.equal(dataAdd[0][2], account_investor3); - let startTime = (await I_VolumeRestrictionTM.individualRestriction.call(account_investor3))[1].toNumber(); + let startTime = (await I_VolumeRestrictionTM.getIndividualRestriction.call(account_investor3))[1].toString(); // transfer more tokens on the same day - await I_SecurityToken.transfer(account_investor2, web3.utils.toWei("4"), { from: account_investor3 }); - tempArray3[tempArray3.length - 1] += 4; + await I_SecurityToken.transfer(account_investor2, new BN(web3.utils.toWei("4")), {from: account_investor3}); + await verifyPartitionBalance(account_investor3, 11.94, 7.36); + tempArray3[tempArray3.length -1] += 4; let data = await I_VolumeRestrictionTM.getIndividualBucketDetailsToUser.call(account_investor3); await print(data, account_investor3); // get the trade amount using the timestamp - let amt = (await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_investor3, data[0].toNumber())) - .dividedBy(new BigNumber(10).pow(18)).toNumber(); + let amt = (await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_investor3, data[0].toString())) + .div(new BN(10).pow(new BN(18))).toString(); // Verify the storage changes - assert.equal(data[0].toNumber(), startTime + duration.days(data[2].toNumber())); - assert.equal(data[1].dividedBy(new BigNumber(10).pow(18)).toNumber(), 8); - assert.equal(data[2].toNumber(), 0); - assert.equal(data[3].toNumber(), 0); + assert.equal(data[0].toString(), new BN(startTime).add(new BN(duration.days(data[2].toString())))); + assert.equal(web3.utils.fromWei(data[1]), 8); + assert.equal(data[2].toString(), 0); + assert.equal(data[3].toString(), 0); assert.equal(amt, 8); }); - it("Should add the new Individual daily restriction and transact the tokens", async () => { + it("Should add the new Individual daily restriction and transact the tokens", async() => { + let newLatestTime = await getLatestTime(); // add new restriction let tx = await I_VolumeRestrictionTM.addIndividualDailyRestriction( account_investor3, - web3.utils.toWei("2"), - latestTime() + duration.days(1), - latestTime() + duration.days(4), + new BN(web3.utils.toWei("2")), + newLatestTime.add(new BN(duration.days(1))), + newLatestTime.add(new BN(duration.days(4))), 0, { from: token_owner @@ -1010,53 +1099,54 @@ contract('VolumeRestrictionTransferManager', accounts => { assert.equal(tx.logs[0].args._holder, account_investor3); assert.equal(tx.logs[0].args._typeOfRestriction, 0); - assert.equal((tx.logs[0].args._allowedTokens).toNumber(), web3.utils.toWei("2")); - let dataRestriction = await I_VolumeRestrictionTM.individualDailyRestriction.call(account_investor3); + assert.equal((tx.logs[0].args._allowedTokens).toString(), new BN(web3.utils.toWei("2"))); + let dataRestriction = await I_VolumeRestrictionTM.getIndividualDailyRestriction.call(account_investor3); console.log(` *** Individual Daily restriction data *** - Allowed Tokens: ${dataRestriction[0].dividedBy(new BigNumber(10).pow(18)).toNumber()} - StartTime : ${dataRestriction[1].toNumber()} - Rolling Period in days : ${dataRestriction[2].toNumber()} - EndTime : ${dataRestriction[3].toNumber()} - Type of Restriction: ${dataRestriction[4].toNumber()} + Allowed Tokens: ${dataRestriction[0].div(new BN(10).pow(new BN(18))).toString()} + StartTime : ${dataRestriction[1].toString()} + Rolling Period in days : ${dataRestriction[2].toString()} + EndTime : ${dataRestriction[3].toString()} + Type of Restriction: ${dataRestriction[4].toString()} `); - let rollingPeriod = (await I_VolumeRestrictionTM.individualRestriction.call(account_investor3))[2].toNumber(); - let startTime = (await I_VolumeRestrictionTM.individualRestriction.call(account_investor3))[1].toNumber(); + let rollingPeriod = (await I_VolumeRestrictionTM.getIndividualRestriction.call(account_investor3))[2].toString(); + let startTime = (await I_VolumeRestrictionTM.getIndividualRestriction.call(account_investor3))[1].toString(); // Increase the time by one day await increaseTime(duration.days(1.1)); //sell tokens upto the limit - await I_SecurityToken.transfer(account_investor2, web3.utils.toWei("2"), { from: account_investor3 }); + await I_SecurityToken.transfer(account_investor2, new BN(web3.utils.toWei("2")), {from: account_investor3}); tempArray3.push(2); let data = await I_VolumeRestrictionTM.getIndividualBucketDetailsToUser.call(account_investor3); await print(data, account_investor3); // get the trade amount using the timestamp - let amt = (await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_investor3, data[0].toNumber())) - .dividedBy(new BigNumber(10).pow(18)).toNumber(); + let amt = (await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_investor3, data[0].toString())) + .div(new BN(10).pow(new BN(18))).toString(); // Verify the storage changes - assert.equal(data[0].toNumber(), startTime + duration.days(data[2].toNumber())); - assert.equal(data[1].dividedBy(new BigNumber(10).pow(18)).toNumber(), await calculateSum(rollingPeriod, tempArray3)); - assert.equal(data[2].toNumber(), 1); - assert.equal(data[3].toNumber(), dataRestriction[1].toNumber()); + assert.equal(data[0].toString(), new BN(startTime).add(new BN(duration.days(data[2].toString())))); + assert.equal(web3.utils.fromWei(data[1]), await calculateSum(rollingPeriod, tempArray3)); + assert.equal(data[2].toString(), 1); + assert.equal(data[3].toString(), dataRestriction[1].toString()); assert.equal(amt, 2); // Fail to sell more tokens than the limit await catchRevert( - I_SecurityToken.transfer(account_investor2, web3.utils.toWei("2"), { from: account_investor3 }) + I_SecurityToken.transfer(account_investor2, new BN(web3.utils.toWei("2")), {from: account_investor3}) ); }); - it("Should fail to modify the individual daily restriction -- bad owner", async () => { + it("Should fail to modify the individual daily restriction -- bad owner", async() => { + let newLatestTime = await getLatestTime(); await catchRevert( I_VolumeRestrictionTM.modifyIndividualDailyRestriction( account_investor3, - web3.utils.toWei('3'), - latestTime(), - latestTime() + duration.days(5), + new BN(web3.utils.toWei('3')), + newLatestTime, + newLatestTime.add(new BN(duration.days(5))), 0, { from: account_polymath @@ -1065,482 +1155,504 @@ contract('VolumeRestrictionTransferManager', accounts => { ); }); - it("Should modify the individual daily restriction", async () => { + it("Should modify the individual daily restriction", async() => { + let newLatestTime = await getLatestTime(); await I_VolumeRestrictionTM.modifyIndividualDailyRestriction( - account_investor3, - web3.utils.toWei('3'), - latestTime() + duration.seconds(10), - latestTime() + duration.days(5), - 0, - { - from: token_owner - } + account_investor3, + new BN(web3.utils.toWei('3')), + newLatestTime.add(new BN(duration.seconds(10))), + newLatestTime.add(new BN(duration.days(5))), + 0, + { + from: token_owner + } ); - let dataRestriction = await I_VolumeRestrictionTM.individualDailyRestriction.call(account_investor3); + let dataRestriction = await I_VolumeRestrictionTM.getIndividualDailyRestriction.call(account_investor3); console.log(` *** Modify Individual Daily restriction data *** - Allowed Tokens: ${dataRestriction[0].dividedBy(new BigNumber(10).pow(18)).toNumber()} - StartTime : ${dataRestriction[1].toNumber()} - Rolling Period in days : ${dataRestriction[2].toNumber()} - EndTime : ${dataRestriction[3].toNumber()} - Type of Restriction: ${dataRestriction[4].toNumber()} + Allowed Tokens: ${dataRestriction[0].div(new BN(10).pow(new BN(18))).toString()} + StartTime : ${dataRestriction[1].toString()} + Rolling Period in days : ${dataRestriction[2].toString()} + EndTime : ${dataRestriction[3].toString()} + Type of Restriction: ${dataRestriction[4].toString()} `); }); - it("Should allow to sell to transfer more tokens by investor3", async () => { + it("Should allow to sell to transfer more tokens by investor3", async() => { await increaseTime(duration.seconds(15)); - let startTime = (await I_VolumeRestrictionTM.individualRestriction.call(account_investor3))[1].toNumber(); - let startTimedaily = (await I_VolumeRestrictionTM.individualDailyRestriction.call(account_investor3))[1].toNumber(); - let rollingPeriod = (await I_VolumeRestrictionTM.individualRestriction.call(account_investor3))[2].toNumber(); + let startTime = (await I_VolumeRestrictionTM.getIndividualRestriction.call(account_investor3))[1].toString(); + let startTimedaily = (await I_VolumeRestrictionTM.getIndividualDailyRestriction.call(account_investor3))[1].toString(); + let rollingPeriod = (await I_VolumeRestrictionTM.getIndividualRestriction.call(account_investor3))[2].toString(); //sell tokens upto the limit - await I_SecurityToken.transfer(account_investor2, web3.utils.toWei("3"), { from: account_investor3 }); - tempArray3[tempArray3.length - 1] += 3; + await I_SecurityToken.transfer(account_investor2, new BN(web3.utils.toWei("3")), {from: account_investor3}); + tempArray3[tempArray3.length -1] += 3; let data = await I_VolumeRestrictionTM.getIndividualBucketDetailsToUser.call(account_investor3); await print(data, account_investor3); // get the trade amount using the timestamp - let amt = (await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_investor3, data[0].toNumber())) - .dividedBy(new BigNumber(10).pow(18)).toNumber(); + let amt = web3.utils.fromWei(await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_investor3, data[0].toString())); // Verify the storage changes - assert.equal(data[0].toNumber(), startTime + duration.days(data[2].toNumber())); - assert.equal(data[1].dividedBy(new BigNumber(10).pow(18)).toNumber(), await calculateSum(rollingPeriod, tempArray3)); - assert.equal(data[2].toNumber(), 1); - assert.equal(data[3].toNumber(), startTimedaily); + assert.equal(data[0].toString(), new BN(startTime).add(new BN(duration.days(data[2].toString())))); + assert.equal(web3.utils.fromWei(data[1]), await calculateSum(rollingPeriod, tempArray3)); + assert.equal(data[2].toString(), 1); + assert.equal(data[3].toString(), startTimedaily); assert.equal(amt, 5); }); - it("Should allow to transact the tokens on the other day", async () => { - let startTime = (await I_VolumeRestrictionTM.individualRestriction.call(account_investor3))[1].toNumber(); - let startTimedaily = (await I_VolumeRestrictionTM.individualDailyRestriction.call(account_investor3))[1].toNumber(); - let rollingPeriod = (await I_VolumeRestrictionTM.individualRestriction.call(account_investor3))[2].toNumber(); + it("Should allow to transact the tokens on the other day", async() => { + let startTime = (await I_VolumeRestrictionTM.getIndividualRestriction.call(account_investor3))[1].toString(); + let startTimedaily = (await I_VolumeRestrictionTM.getIndividualDailyRestriction.call(account_investor3))[1].toString(); + let rollingPeriod = (await I_VolumeRestrictionTM.getIndividualRestriction.call(account_investor3))[2].toString(); - await increaseTime(duration.days(1)); + await increaseTime(duration.days(1.1)); //sell tokens upto the limit - await I_SecurityToken.transfer(account_investor2, web3.utils.toWei("2.36"), { from: account_investor3 }); + await I_SecurityToken.transfer(account_investor2, new BN(web3.utils.toWei("2.36")), {from: account_investor3}); tempArray3.push(2.36); let data = await I_VolumeRestrictionTM.getIndividualBucketDetailsToUser.call(account_investor3); await print(data, account_investor3); // get the trade amount using the timestamp - let amt = (await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_investor3, data[0].toNumber())) - .dividedBy(new BigNumber(10).pow(18)).toNumber(); + let amt = web3.utils.fromWei(await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_investor3, data[0].toString())); // Verify the storage changes - assert.equal(data[0].toNumber(), startTime + duration.days(data[2].toNumber())); - assert.equal(data[1].dividedBy(new BigNumber(10).pow(18)).toNumber(), await calculateSum(rollingPeriod, tempArray3)); - assert.equal(data[2].toNumber(), 2); - assert.equal(data[3].toNumber(), startTimedaily + duration.days(1)); + assert.equal(data[0].toString(), new BN(startTime).add(new BN(duration.days(data[2].toString())))); + assert.equal(web3.utils.fromWei(data[1]), await calculateSum(rollingPeriod, tempArray3)); + assert.equal(data[2].toString(), 2); + assert.equal(data[3].toString(), new BN(startTimedaily).add(new BN(duration.days(1)))); assert.equal(amt, 2.36); }); - it("Should fail to transfer the tokens after completion of the total amount", async () => { + it("Should fail to transfer the tokens after completion of the total amount", async() => { await catchRevert( - I_SecurityToken.transfer(account_investor2, web3.utils.toWei("0.3"), { from: account_investor3 }) + I_SecurityToken.transfer(account_investor2, new BN(web3.utils.toWei("0.3")), {from: account_investor3}) ); }) - it("Should sell more tokens on the same day after changing the total supply", async () => { - await I_SecurityToken.mint(account_investor3, web3.utils.toWei("10"), { from: token_owner }); + it("Should sell more tokens on the same day after changing the total supply", async() => { + await I_SecurityToken.issue(account_investor3, new BN(web3.utils.toWei("10")), "0x0", {from: token_owner}); - let startTime = (await I_VolumeRestrictionTM.individualRestriction.call(account_investor3))[1].toNumber(); - let startTimedaily = (await I_VolumeRestrictionTM.individualDailyRestriction.call(account_investor3))[1].toNumber(); - let rollingPeriod = (await I_VolumeRestrictionTM.individualRestriction.call(account_investor3))[2].toNumber(); + let startTime = (await I_VolumeRestrictionTM.getIndividualRestriction.call(account_investor3))[1].toString(); + let startTimedaily = (await I_VolumeRestrictionTM.getIndividualDailyRestriction.call(account_investor3))[1].toString(); + let rollingPeriod = (await I_VolumeRestrictionTM.getIndividualRestriction.call(account_investor3))[2].toString(); //sell tokens upto the limit - await I_SecurityToken.transfer(account_investor2, web3.utils.toWei(".50"), { from: account_investor3 }); - tempArray3[tempArray3.length - 1] += .50; + await I_SecurityToken.transfer(account_investor2, new BN(web3.utils.toWei(".50")), {from: account_investor3}); + tempArray3[tempArray3.length -1] += .50; let data = await I_VolumeRestrictionTM.getIndividualBucketDetailsToUser.call(account_investor3); await print(data, account_investor3); // get the trade amount using the timestamp - let amt = (await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_investor3, data[0].toNumber())) - .dividedBy(new BigNumber(10).pow(18)).toNumber(); + let amt = web3.utils.fromWei(await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_investor3, data[0].toString())); // Verify the storage changes - assert.equal(data[0].toNumber(), startTime + duration.days(data[2].toNumber())); - assert.equal(data[1].dividedBy(new BigNumber(10).pow(18)).toNumber(), await calculateSum(rollingPeriod, tempArray3)); - assert.equal(data[2].toNumber(), 2); - assert.equal(data[3].toNumber(), startTimedaily + duration.days(1)); + assert.equal(data[0].toString(), new BN(startTime).add(new BN(duration.days(data[2].toString())))); + assert.equal(web3.utils.fromWei(data[1]), await calculateSum(rollingPeriod, tempArray3)); + assert.equal(data[2].toString(), 2); + assert.equal(data[3].toString(), new BN(startTimedaily).add(new BN(duration.days(1)))); assert.equal(amt, 2.86); }); - it("Should fail to transact tokens more than the allowed in the second rolling period", async () => { + it("Should fail to transact tokens more than the allowed in the second rolling period", async() => { + let newLatestTime = await getLatestTime(); await increaseTime(duration.days(4)); let i for (i = 0; i < 3; i++) { tempArray3.push(0); } - console.log(`Diff Days: ${(latestTime() - ((await I_VolumeRestrictionTM.getIndividualBucketDetailsToUser.call(account_investor3))[0]).toNumber()) / 86400}`); + console.log(`Diff Days: ${(newLatestTime - ((await I_VolumeRestrictionTM.getIndividualBucketDetailsToUser.call(account_investor3))[0]).toString()) / 86400}`); let allowedAmount = (tempArray3[0] + 1.1); await catchRevert( - I_SecurityToken.transfer(account_investor2, web3.utils.toWei(allowedAmount.toString()), { from: account_investor3 }) + I_SecurityToken.transfer(account_investor2, new BN(web3.utils.toWei(allowedAmount.toString())), {from: account_investor3}) ); }) - it("Should successfully transact tokens in the second rolling period", async () => { + it("Should successfully to transact tokens in the second rolling period", async() => { // Should transact freely tokens daily limit is also ended - let startTime = (await I_VolumeRestrictionTM.individualRestriction.call(account_investor3))[1].toNumber(); - let startTimedaily = (await I_VolumeRestrictionTM.individualDailyRestriction.call(account_investor3))[1].toNumber(); - let rollingPeriod = (await I_VolumeRestrictionTM.individualRestriction.call(account_investor3))[2].toNumber(); + let startTime = (await I_VolumeRestrictionTM.getIndividualRestriction.call(account_investor3))[1].toString(); + let startTimedaily = (await I_VolumeRestrictionTM.getIndividualDailyRestriction.call(account_investor3))[1].toString(); + let rollingPeriod = (await I_VolumeRestrictionTM.getIndividualRestriction.call(account_investor3))[2].toString(); let allowedAmount = (tempArray3[0] + 1); //sell tokens upto the limit - await I_SecurityToken.transfer(account_investor2, web3.utils.toWei(allowedAmount.toString()), { from: account_investor3 }); + await I_SecurityToken.transfer(account_investor2, new BN(web3.utils.toWei(allowedAmount.toString())), {from: account_investor3}); tempArray3.push(allowedAmount); let data = await I_VolumeRestrictionTM.getIndividualBucketDetailsToUser.call(account_investor3); await print(data, account_investor3); // get the trade amount using the timestamp - let amt = (await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_investor3, data[0].toNumber())) - .dividedBy(new BigNumber(10).pow(18)).toNumber(); + let amt = web3.utils.fromWei(await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_investor3, data[0].toString())); // Verify the storage changes - assert.equal(data[0].toNumber(), startTime + duration.days(data[2].toNumber())); - assert.equal(data[1].dividedBy(new BigNumber(10).pow(18)).toNumber(), await calculateSum(rollingPeriod, tempArray3)); - assert.equal(data[2].toNumber(), 6); - assert.equal(data[3].toNumber(), startTimedaily + duration.days(1)); + assert.equal(data[0].toString(), new BN(startTime).add(new BN(duration.days(data[2].toString())))); + assert.equal(web3.utils.fromWei(data[1]), await calculateSum(rollingPeriod, tempArray3)); + assert.equal(data[2].toString(), 6); + assert.equal(data[3].toString(), new BN(startTimedaily).add(new BN(duration.days(1))).toString()); assert.equal(amt, allowedAmount); }); - it("Should sell more tokens on the net day of rolling period", async () => { + it("Should sell more tokens on the next day of rolling period", async() => { await increaseTime(duration.days(3)); - let startTime = (await I_VolumeRestrictionTM.individualRestriction.call(account_investor3))[1].toNumber(); - let startTimedaily = (await I_VolumeRestrictionTM.individualDailyRestriction.call(account_investor3))[1].toNumber(); - let rollingPeriod = (await I_VolumeRestrictionTM.individualRestriction.call(account_investor3))[2].toNumber(); + let startTime = (await I_VolumeRestrictionTM.getIndividualRestriction.call(account_investor3))[1].toString(); + let startTimedaily = (await I_VolumeRestrictionTM.getIndividualDailyRestriction.call(account_investor3))[1].toString(); + let rollingPeriod = (await I_VolumeRestrictionTM.getIndividualRestriction.call(account_investor3))[2].toString(); tempArray3.push(0); tempArray3.push(0); + let dataRestriction = await I_VolumeRestrictionTM.getIndividualRestriction.call(account_investor3); + console.log(` + *** Individual Daily restriction data *** + Allowed Tokens: ${dataRestriction[0].div(new BN(10).pow(new BN(18))).toString()} + StartTime : ${dataRestriction[1].toString()} + Rolling Period in days : ${dataRestriction[2].toString()} + EndTime : ${dataRestriction[3].toString()} + Type of Restriction: ${dataRestriction[4].toString()} + `); //sell tokens upto the limit - await I_SecurityToken.transfer(account_investor2, web3.utils.toWei("7"), { from: account_investor3 }); + console.log( + "Balance of Investor3 :" + + web3.utils.fromWei((await I_SecurityToken.balanceOf.call(account_investor3)).toString()) + ); + console.log( + web3.utils.fromWei( + ( + await I_VolumeRestrictionTM.getTokensByPartition.call(web3.utils.toHex("LOCKED"), account_investor3, new BN(0)) + ).toString() + ) + ) + console.log( + web3.utils.fromWei( + ( + await I_VolumeRestrictionTM.getTokensByPartition.call(web3.utils.toHex("UNLOCKED"), account_investor3, new BN(0)) + ).toString() + ) + ) + await I_SecurityToken.transfer(account_investor2, new BN(web3.utils.toWei("7")), {from: account_investor3}); tempArray3.push(7) let data = await I_VolumeRestrictionTM.getIndividualBucketDetailsToUser.call(account_investor3); await print(data, account_investor3); // get the trade amount using the timestamp - let amt = (await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_investor3, data[0].toNumber())) - .dividedBy(new BigNumber(10).pow(18)).toNumber(); + let amt = web3.utils.fromWei(await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_investor3, data[0].toString())); // Verify the storage changes - assert.equal(data[0].toNumber(), startTime + duration.days(data[2].toNumber())); - assert.equal(data[1].dividedBy(new BigNumber(10).pow(18)).toNumber(), await calculateSum(rollingPeriod, tempArray3)); - assert.equal(data[2].toNumber(), 9); - assert.equal(data[3].toNumber(), startTimedaily + duration.days(1)); + assert.equal(data[0].toString(), new BN(startTime).add(new BN(duration.days(data[2].toString())))); + assert.equal(web3.utils.fromWei(data[1]), await calculateSum(rollingPeriod, tempArray3)); + assert.equal(data[2].toString(), 9); + assert.equal(data[3].toString(), new BN(startTimedaily).add(new BN(duration.days(1)))); assert.equal(amt, 7); }) - it("Should transfer after the 5 days", async () => { + it("Should transfer after the 5 days", async() => { await increaseTime(duration.days(4.5)); - for (let i = 0; i < 3; i++) { + for (let i = 0; i <3; i++) { tempArray3.push(0); } - let startTime = (await I_VolumeRestrictionTM.individualRestriction.call(account_investor3))[1].toNumber(); - let startTimedaily = (await I_VolumeRestrictionTM.individualDailyRestriction.call(account_investor3))[1].toNumber(); - let rollingPeriod = (await I_VolumeRestrictionTM.individualRestriction.call(account_investor3))[2].toNumber(); + let startTime = (await I_VolumeRestrictionTM.getIndividualRestriction.call(account_investor3))[1].toString(); + let startTimedaily = (await I_VolumeRestrictionTM.getIndividualDailyRestriction.call(account_investor3))[1].toString(); + let rollingPeriod = (await I_VolumeRestrictionTM.getIndividualRestriction.call(account_investor3))[2].toString(); - await I_SecurityToken.transfer(account_investor3, web3.utils.toWei("25"), { from: account_investor2 }); + await I_SecurityToken.transfer(account_investor3, new BN(web3.utils.toWei("25")), {from: account_investor2}); //sell tokens upto the limit - await I_SecurityToken.transfer(account_investor2, web3.utils.toWei("8"), { from: account_investor3 }); + await I_SecurityToken.transfer(account_investor2, new BN(web3.utils.toWei("8")), {from: account_investor3}); tempArray3.push(8); let data = await I_VolumeRestrictionTM.getIndividualBucketDetailsToUser.call(account_investor3); await print(data, account_investor3); // get the trade amount using the timestamp - let amt = (await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_investor3, data[0].toNumber())) - .dividedBy(new BigNumber(10).pow(18)).toNumber(); + let amt = web3.utils.fromWei(await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_investor3, data[0].toString())); // Verify the storage changes - assert.equal(data[0].toNumber(), startTime + duration.days(data[2].toNumber())); - assert.equal(data[1].dividedBy(new BigNumber(10).pow(18)).toNumber(), await calculateSum(rollingPeriod, tempArray3)); - assert.equal(data[2].toNumber(), 13); - assert.equal(data[3].toNumber(), startTimedaily + duration.days(1)); + assert.equal(data[0].toString(), new BN(startTime).add(new BN(duration.days(data[2].toString())))); + assert.equal(web3.utils.fromWei(data[1]), await calculateSum(rollingPeriod, tempArray3)); + assert.equal(data[2].toString(), 13); + assert.equal(data[3].toString(), new BN(startTimedaily).add(new BN(duration.days(1)))); assert.equal(amt, 8); }); - it("Should freely transfer the tokens after one day (completion of individual restriction)", async () => { + it("Should freely transfer the tokens after one day (completion of individual restriction)", async() => { // increase one time await increaseTime(duration.days(2)); - await I_SecurityToken.transfer(account_investor2, web3.utils.toWei("17"), { from: account_investor3 }); + await I_SecurityToken.transfer(account_investor2, new BN(web3.utils.toWei("17")), {from: account_investor3}); }); }); - describe("Test cases for the Default restrictions", async () => { + describe("Test cases for the Default restrictions", async() => { - it("Should add the investor 4 in the whitelist", async () => { - await I_GeneralTransferManager.modifyWhitelist( + it("Should add the investor 4 in the whitelist", async() => { + let newLatestTime = await getLatestTime(); + await I_GeneralTransferManager.modifyKYCData( account_investor4, - latestTime(), - latestTime(), - latestTime() + duration.days(30), - true, + newLatestTime, + newLatestTime, + newLatestTime.add(new BN(duration.days(30))), { from: token_owner } ); }); - it("Should mint some tokens to investor 4", async () => { - await I_SecurityToken.mint(account_investor4, web3.utils.toWei("20"), { from: token_owner }); + it("Should issue some tokens to investor 4", async() => { + await I_SecurityToken.issue(account_investor4, new BN(web3.utils.toWei("20")), "0x0", {from: token_owner}); }); - it("Should add the default daily restriction successfully", async () => { + it("Should add the default daily restriction successfully", async() => { + let newLatestTime = await getLatestTime(); await I_VolumeRestrictionTM.addDefaultDailyRestriction( - new BigNumber(2.75).times(new BigNumber(10).pow(16)), - latestTime() + duration.seconds(10), - latestTime() + duration.days(3), + new BN(275).mul(new BN(10).pow(new BN(14))), + 0, + newLatestTime.add(new BN(duration.days(3))), 1, { from: token_owner } ); - let dataRestriction = await I_VolumeRestrictionTM.defaultDailyRestriction.call(); + let dataRestriction = await I_VolumeRestrictionTM.getDefaultDailyRestriction.call(); console.log(` *** Add Individual Daily restriction data *** - Allowed Tokens: ${dataRestriction[0].dividedBy(new BigNumber(10).pow(16)).toNumber()} % of TotalSupply - StartTime : ${dataRestriction[1].toNumber()} - Rolling Period in days : ${dataRestriction[2].toNumber()} - EndTime : ${dataRestriction[3].toNumber()} - Type of Restriction: ${dataRestriction[4].toNumber()} + Allowed Tokens: ${dataRestriction[0].div(new BN(10).pow(new BN(16))).toString()} % of TotalSupply + StartTime : ${dataRestriction[1].toString()} + Rolling Period in days : ${dataRestriction[2].toString()} + EndTime : ${dataRestriction[3].toString()} + Type of Restriction: ${dataRestriction[4].toString()} `); }); - it("Should fail to transfer above the daily limit", async () => { - await increaseTime(duration.seconds(15)); + it("Should fail to transfer above the daily limit", async() => { + await increaseTime(2); // increase time to layoff the time gap await catchRevert( - I_SecurityToken.transfer(account_investor3, web3.utils.toWei("5"), { from: account_investor4 }) + I_SecurityToken.transfer(account_investor3, new BN(web3.utils.toWei("5")), {from: account_investor4}) ) }) - it("Should transfer the token by investor 4", async () => { - let startTimedaily = (await I_VolumeRestrictionTM.defaultDailyRestriction.call())[1].toNumber(); + it("Should transfer the token by investor 4", async() => { + let startTimedaily = (await I_VolumeRestrictionTM.getDefaultDailyRestriction.call())[1].toString(); //sell tokens upto the limit - await I_SecurityToken.transfer(account_investor2, web3.utils.toWei("3.57"), { from: account_investor4 }); + await I_SecurityToken.transfer(account_investor2, new BN(web3.utils.toWei("3.57")), {from: account_investor4}); let data = await I_VolumeRestrictionTM.getDefaultBucketDetailsToUser.call(account_investor4); await print(data, account_investor3); // get the trade amount using the timestamp - let amt = (await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_investor4, data[3].toNumber())) - .dividedBy(new BigNumber(10).pow(18)).toNumber(); + let amt = web3.utils.fromWei(await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_investor4, data[3].toString())); // Verify the storage changes - assert.equal(data[0].toNumber(), 0); - assert.equal(data[1].toNumber(), 0); - assert.equal(data[2].toNumber(), 0); - assert.equal(data[3].toNumber(), startTimedaily); + assert.equal(data[0].toString(), 0); + assert.equal(data[1].toString(), 0); + assert.equal(data[2].toString(), 0); + assert.equal(data[3].toString(), startTimedaily); assert.equal(amt, 3.57); }); - it("Should transfer the tokens freely after ending the default daily restriction", async () => { + it("Should transfer the tokens freely after ending the default daily restriction", async() => { await increaseTime(duration.days(3) + 10); //sell tokens upto the limit - let tx = await I_SecurityToken.transfer(account_investor2, web3.utils.toWei("5"), { from: account_investor4 }); - assert.equal((tx.logs[0].args.value).toNumber(), web3.utils.toWei("5")); + let tx = await I_SecurityToken.transfer(account_investor2, new BN(web3.utils.toWei("5")), {from: account_investor4}); + assert.equal((tx.logs[0].args.value).toString(), new BN(web3.utils.toWei("5"))); // Transfer the tokens again to investor 3 - await I_SecurityToken.transfer(account_investor3, web3.utils.toWei("40"), { from: account_investor2 }); + await I_SecurityToken.transfer(account_investor3, new BN(web3.utils.toWei("40")), {from: account_investor2}); }) - it("Should successfully add the default restriction", async () => { + it("Should successfully add the default restriction", async() => { + let newLatestTime = await getLatestTime(); await I_VolumeRestrictionTM.addDefaultRestriction( - web3.utils.toWei("10"), - latestTime() + duration.seconds(10), + new BN(web3.utils.toWei("10")), + 0, 5, - latestTime() + duration.days(10), + newLatestTime.add(new BN(duration.days(10))), 0, { from: token_owner } ); - let data = await I_VolumeRestrictionTM.defaultRestriction.call(); - assert.equal(data[0].toNumber(), web3.utils.toWei("10")); - assert.equal(data[2].toNumber(), 5); - let dataRestriction = await I_VolumeRestrictionTM.defaultRestriction.call(); + let data = await I_VolumeRestrictionTM.getDefaultRestriction.call(); + assert.equal(data[0].toString(), new BN(web3.utils.toWei("10"))); + assert.equal(data[2].toString(), 5); + let dataRestriction = await I_VolumeRestrictionTM.getDefaultRestriction.call(); console.log(` - *** Add Default restriction data *** - Allowed Tokens: ${dataRestriction[0].dividedBy(new BigNumber(10).pow(18)).toNumber()} - StartTime : ${dataRestriction[1].toNumber()} - Rolling Period in days : ${dataRestriction[2].toNumber()} - EndTime : ${dataRestriction[3].toNumber()} - Type of Restriction: ${dataRestriction[4].toNumber()} + *** Add Individual restriction data *** + Allowed Tokens: ${dataRestriction[0].div(new BN(10).pow(new BN(18))).toString()} + StartTime : ${dataRestriction[1].toString()} + Rolling Period in days : ${dataRestriction[2].toString()} + EndTime : ${dataRestriction[3].toString()} + Type of Restriction: ${dataRestriction[4].toString()} `); }); - it("Should transfer tokens on by investor 3 (comes under the Default restriction)", async () => { - await increaseTime(15); + it("Should transfer tokens on by investor 3 (comes under the Default restriction)", async() => { + await increaseTime(10); tempArray3.length = 0; - let startTime = (await I_VolumeRestrictionTM.defaultRestriction.call())[1].toNumber(); - let startTimedaily = (await I_VolumeRestrictionTM.defaultDailyRestriction.call())[1].toNumber(); - let rollingPeriod = (await I_VolumeRestrictionTM.defaultRestriction.call())[2].toNumber(); + let startTime = (await I_VolumeRestrictionTM.getDefaultRestriction.call())[1].toString(); + let startTimedaily = (await I_VolumeRestrictionTM.getDefaultDailyRestriction.call())[1].toString(); + let rollingPeriod = (await I_VolumeRestrictionTM.getDefaultRestriction.call())[2].toString(); //sell tokens upto the limit - await I_SecurityToken.transfer(account_investor2, web3.utils.toWei("5"), { from: account_investor3 }); + await I_SecurityToken.transfer(account_investor2, new BN(web3.utils.toWei("5")), {from: account_investor3}); tempArray3.push(5); let data = await I_VolumeRestrictionTM.getDefaultBucketDetailsToUser.call(account_investor3); await print(data, account_investor3); // get the trade amount using the timestamp - let amt = (await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_investor3, data[0].toNumber())) - .dividedBy(new BigNumber(10).pow(18)).toNumber(); + let amt = web3.utils.fromWei(await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_investor3, data[0].toString())); // Verify the storage changes - assert.equal(data[0].toNumber(), startTime + duration.days(data[2].toNumber())); - assert.equal(data[1].dividedBy(new BigNumber(10).pow(18)).toNumber(), await calculateSum(rollingPeriod, tempArray3)); - assert.equal(data[2].toNumber(), 0); - assert.equal(data[3].toNumber(), 0); + assert.equal(data[0].toString(), new BN(startTime).add(new BN(duration.days(data[2].toString())))); + assert.equal(web3.utils.fromWei(data[1]), await calculateSum(rollingPeriod, tempArray3)); + assert.equal(data[2].toString(), 0); + assert.equal(data[3].toString(), 0); assert.equal(amt, 5); // Transfer tokens on another day await increaseTime(duration.days(1)); //sell tokens upto the limit - await I_SecurityToken.transfer(account_investor2, web3.utils.toWei("3"), { from: account_investor3 }); + await I_SecurityToken.transfer(account_investor2, new BN(web3.utils.toWei("3")), {from: account_investor3}); tempArray3.push(3); data = await I_VolumeRestrictionTM.getDefaultBucketDetailsToUser.call(account_investor3); await print(data, account_investor3); // get the trade amount using the timestamp - amt = (await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_investor3, data[0].toNumber())) - .dividedBy(new BigNumber(10).pow(18)).toNumber(); + amt = web3.utils.fromWei(await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_investor3, data[0].toString())); // Verify the storage changes - assert.equal(data[0].toNumber(), startTime + duration.days(data[2].toNumber())); - assert.equal(data[1].dividedBy(new BigNumber(10).pow(18)).toNumber(), await calculateSum(rollingPeriod, tempArray3)); - assert.equal(data[2].toNumber(), 1); - assert.equal(data[3].toNumber(), 0); + assert.equal(data[0].toString(), new BN(startTime).add(new BN(duration.days(data[2].toString())))); + assert.equal(web3.utils.fromWei(data[1]), await calculateSum(rollingPeriod, tempArray3)); + assert.equal(data[2].toString(), 1); + assert.equal(data[3].toString(), 0); assert.equal(amt, 3); }); - it("Should fail to transfer more tokens than the available default limit", async () => { + it("Should fail to transfer more tokens than the available default limit", async() => { await catchRevert( - I_SecurityToken.transfer(account_investor2, web3.utils.toWei("3"), { from: account_investor3 }) + I_SecurityToken.transfer(account_investor2, new BN(web3.utils.toWei("3")), {from: account_investor3}) ); }); - it("Should able to transfer tokens in the next rolling period", async () => { + it("Should able to transfer tokens in the next rolling period", async() => { + let newLatestTime = await getLatestTime(); await increaseTime(duration.days(4.1)); - console.log(`*** Diff days: ${(latestTime() - ((await I_VolumeRestrictionTM.getDefaultBucketDetailsToUser.call(account_investor3))[0]).toNumber()) / 86400}`) + console.log(`*** Diff days: ${(newLatestTime - ((await I_VolumeRestrictionTM.getDefaultBucketDetailsToUser.call(account_investor3))[0]).toString()) / 86400}`) for (let i = 0; i < 3; i++) { tempArray3.push(0); } - let startTime = (await I_VolumeRestrictionTM.defaultRestriction.call())[1].toNumber(); - let startTimedaily = (await I_VolumeRestrictionTM.defaultDailyRestriction.call())[1].toNumber(); - let rollingPeriod = (await I_VolumeRestrictionTM.defaultRestriction.call())[2].toNumber(); + let startTime = (await I_VolumeRestrictionTM.getDefaultRestriction.call())[1].toString(); + let startTimedaily = (await I_VolumeRestrictionTM.getDefaultDailyRestriction.call())[1].toString(); + let rollingPeriod = (await I_VolumeRestrictionTM.getDefaultRestriction.call())[2].toString(); //sell tokens upto the limit - await I_SecurityToken.transfer(account_investor2, web3.utils.toWei("7"), { from: account_investor3 }); + await I_SecurityToken.transfer(account_investor2, new BN(web3.utils.toWei("7")), {from: account_investor3}); tempArray3.push(7); let data = await I_VolumeRestrictionTM.getDefaultBucketDetailsToUser.call(account_investor3); await print(data, account_investor3); // get the trade amount using the timestamp - let amt = (await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_investor3, data[0].toNumber())) - .dividedBy(new BigNumber(10).pow(18)).toNumber(); + let amt = web3.utils.fromWei(await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_investor3, data[0].toString())); // Verify the storage changes - assert.equal(data[0].toNumber(), startTime + duration.days(data[2].toNumber())); - assert.equal(data[1].dividedBy(new BigNumber(10).pow(18)).toNumber(), await calculateSum(rollingPeriod, tempArray3)); - assert.equal(data[2].toNumber(), 5); - assert.equal(data[3].toNumber(), 0); + assert.equal(data[0].toString(), new BN(startTime).add(new BN(duration.days(data[2].toString())))); + assert.equal(web3.utils.fromWei(data[1]), await calculateSum(rollingPeriod, tempArray3)); + assert.equal(data[2].toString(), 5); + assert.equal(data[3].toString(), 0); assert.equal(amt, 7); // Try to transact more on the same day but fail await catchRevert( - I_SecurityToken.transfer(account_investor2, web3.utils.toWei("1"), { from: account_investor3 }) + I_SecurityToken.transfer(account_investor2, new BN(web3.utils.toWei("1")), {from: account_investor3}) ); }); - it("Should add the daily default restriction again", async () => { + it("Should add the daily default restriction again", async() => { + let newLatestTime = await getLatestTime(); await I_VolumeRestrictionTM.addDefaultDailyRestriction( - web3.utils.toWei("2"), - latestTime() + duration.seconds(10), - latestTime() + duration.days(3), + new BN(web3.utils.toWei("2")), + newLatestTime.add(new BN(duration.seconds(10))), + newLatestTime.add(new BN(duration.days(3))), 0, { from: token_owner } ); - let dataRestriction = await I_VolumeRestrictionTM.defaultDailyRestriction.call(); + let dataRestriction = await I_VolumeRestrictionTM.getDefaultDailyRestriction.call(); console.log(` *** Add Individual Daily restriction data *** - Allowed Tokens: ${dataRestriction[0].dividedBy(new BigNumber(10).pow(16)).toNumber()} - StartTime : ${dataRestriction[1].toNumber()} - Rolling Period in days : ${dataRestriction[2].toNumber()} - EndTime : ${dataRestriction[3].toNumber()} - Type of Restriction: ${dataRestriction[4].toNumber()} + Allowed Tokens: ${dataRestriction[0].div(new BN(10).pow(new BN(16))).toString()} + StartTime : ${dataRestriction[1].toString()} + Rolling Period in days : ${dataRestriction[2].toString()} + EndTime : ${dataRestriction[3].toString()} + Type of Restriction: ${dataRestriction[4].toString()} `); }); - it("Should not able to transfer tokens more than the default daily restriction", async () => { + it("Should not able to transfer tokens more than the default daily restriction", async() => { await increaseTime(duration.seconds(15)); await catchRevert( - I_SecurityToken.transfer(account_investor2, web3.utils.toWei("3"), { from: account_investor3 }) + I_SecurityToken.transfer(account_investor2, new BN(web3.utils.toWei("3")), {from: account_investor3}) ); }); - it("Should able to transfer tokens within the limit of (daily default + default) restriction", async () => { + it("Should able to transfer tokens within the limit of (daily default + default) restriction", async() => { await increaseTime(duration.days(1)); - let startTime = (await I_VolumeRestrictionTM.defaultRestriction.call())[1].toNumber(); - let startTimedaily = (await I_VolumeRestrictionTM.defaultDailyRestriction.call())[1].toNumber(); - let rollingPeriod = (await I_VolumeRestrictionTM.defaultRestriction.call())[2].toNumber(); + let startTime = (await I_VolumeRestrictionTM.getDefaultRestriction.call())[1].toString(); + let startTimedaily = (await I_VolumeRestrictionTM.getDefaultDailyRestriction.call())[1].toString(); + let rollingPeriod = (await I_VolumeRestrictionTM.getDefaultRestriction.call())[2].toString(); //sell tokens upto the limit - await I_SecurityToken.transfer(account_investor2, web3.utils.toWei("2"), { from: account_investor3 }); + await I_SecurityToken.transfer(account_investor2, new BN(web3.utils.toWei("2")), {from: account_investor3}); tempArray3.push(2); let data = await I_VolumeRestrictionTM.getDefaultBucketDetailsToUser.call(account_investor3); await print(data, account_investor3); // get the trade amount using the timestamp - let amt = (await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_investor3, data[0].toNumber())) - .dividedBy(new BigNumber(10).pow(18)).toNumber(); + let amt = web3.utils.fromWei(await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_investor3, data[0].toString())); // Verify the storage changes - assert.equal(data[0].toNumber(), startTime + duration.days(data[2].toNumber())); - assert.equal(data[1].dividedBy(new BigNumber(10).pow(18)).toNumber(), await calculateSum(rollingPeriod, tempArray3)); - assert.equal(data[2].toNumber(), 6); - assert.equal(data[3].toNumber(), startTimedaily + duration.days(1)); + assert.equal(data[0].toString(), new BN(startTime).add(new BN(duration.days(data[2].toString())))); + assert.equal(web3.utils.fromWei(data[1]), await calculateSum(rollingPeriod, tempArray3)); + assert.equal(data[2].toString(), 6); + assert.equal(data[3].toString(), (new BN(startTimedaily).add(new BN(duration.days(1)))).toString()); assert.equal(amt, 2); }); }) - describe("Test for the exemptlist", async () => { + describe("Test for the exemptlist", async() => { - it("Should add the token holder in the exemption list -- failed because of bad owner", async () => { + it("Should add the token holder in the exemption list -- failed because of bad owner", async() => { await catchRevert( - I_VolumeRestrictionTM.changeExemptWalletList(account_investor4, true, { from: account_polymath }) + I_VolumeRestrictionTM.changeExemptWalletList(account_investor4, true, {from: account_polymath}) ); }); - it("Should add the token holder in the exemption list", async () => { - await I_VolumeRestrictionTM.changeExemptWalletList(account_investor4, true, { from: token_owner }); + it("Should add the token holder in the exemption list", async() => { + await I_VolumeRestrictionTM.changeExemptWalletList(account_investor4, true, {from: token_owner}); console.log(await I_VolumeRestrictionTM.getExemptAddress.call()); let beforeBal = await I_SecurityToken.balanceOf.call(account_investor4); - await I_SecurityToken.transfer(account_investor3, web3.utils.toWei("3"), { from: account_investor4 }); + await I_SecurityToken.transfer(account_investor3, new BN(web3.utils.toWei("3")), {from: account_investor4}); let afterBal = await I_SecurityToken.balanceOf.call(account_investor4); - let diff = beforeBal.minus(afterBal); - assert.equal(web3.utils.fromWei((diff.toNumber()).toString()), 3); + let diff = beforeBal.sub(afterBal); + assert.equal(web3.utils.fromWei((diff.toString()).toString()), 3); }); - it("Should add multiple token holders to exemption list and check the getter value", async () => { + it("Should add multiple token holders to exemption list and check the getter value", async() => { let holders = [account_investor1, account_investor3, account_investor2, account_delegate2]; let change = [true, true, true, true]; for (let i = 0; i < holders.length; i++) { - await I_VolumeRestrictionTM.changeExemptWalletList(holders[i], change[i], { from: token_owner }); + await I_VolumeRestrictionTM.changeExemptWalletList(holders[i], change[i], {from: token_owner}); } let data = await I_VolumeRestrictionTM.getExemptAddress.call(); assert.equal(data.length, 5); @@ -1551,8 +1663,8 @@ contract('VolumeRestrictionTransferManager', accounts => { assert.equal(data[4], account_delegate2); }); - it("Should unexempt a particular address", async () => { - await I_VolumeRestrictionTM.changeExemptWalletList(account_investor1, false, { from: token_owner }); + it("Should unexempt a particular address", async() => { + await I_VolumeRestrictionTM.changeExemptWalletList(account_investor1, false, {from: token_owner}); let data = await I_VolumeRestrictionTM.getExemptAddress.call(); assert.equal(data.length, 4); assert.equal(data[0], account_investor4); @@ -1561,14 +1673,14 @@ contract('VolumeRestrictionTransferManager', accounts => { assert.equal(data[3], account_investor2); }); - it("Should fail to unexempt the same address again", async () => { + it("Should fail to unexempt the same address again", async() => { await catchRevert( - I_VolumeRestrictionTM.changeExemptWalletList(account_investor1, false, { from: token_owner }) + I_VolumeRestrictionTM.changeExemptWalletList(account_investor1, false, {from: token_owner}) ); }); - it("Should delete the last element of the exemption list", async () => { - await I_VolumeRestrictionTM.changeExemptWalletList(account_investor2, false, { from: token_owner }); + it("Should delete the last element of the exemption list", async() => { + await I_VolumeRestrictionTM.changeExemptWalletList(account_investor2, false, {from: token_owner}); let data = await I_VolumeRestrictionTM.getExemptAddress.call(); assert.equal(data.length, 3); assert.equal(data[0], account_investor4); @@ -1576,128 +1688,130 @@ contract('VolumeRestrictionTransferManager', accounts => { assert.equal(data[2], account_investor3); }); - it("Should delete multiple investor from the exemption list", async () => { + it("Should delete multiple investor from the exemption list", async() => { let holders = [account_delegate2, account_investor4, account_investor3]; let change = [false, false, false]; for (let i = 0; i < holders.length; i++) { - await I_VolumeRestrictionTM.changeExemptWalletList(holders[i], change[i], { from: token_owner }); + await I_VolumeRestrictionTM.changeExemptWalletList(holders[i], change[i], {from: token_owner}); } let data = await I_VolumeRestrictionTM.getExemptAddress.call(); assert.equal(data.length, 0); }); }); - describe("Test for modify functions", async () => { + describe("Test for modify functions", async() => { - it("Should add the individual restriction for multiple investor", async () => { + it("Should add the individual restriction for multiple investor", async() => { + let newLatestTime = await getLatestTime(); await I_VolumeRestrictionTM.addIndividualRestrictionMulti( [account_investor3, account_delegate3], - [web3.utils.toWei("15"), new BigNumber(12.78).times(new BigNumber(10).pow(16))], - [latestTime() + duration.days(1), latestTime() + duration.days(2)], + [new BN(web3.utils.toWei("15")), new BN(1278).mul(new BN(10).pow(new BN(14)))], + [newLatestTime.add(new BN(duration.days(1))), newLatestTime.add(new BN(duration.days(2)))], [15, 20], - [latestTime() + duration.days(40), latestTime() + duration.days(60)], - [0, 1], + [newLatestTime.add(new BN(duration.days(40))), newLatestTime.add(new BN(duration.days(60)))], + [0,1], { from: token_owner } ); - let indi1 = await I_VolumeRestrictionTM.individualRestriction.call(account_investor3); - let indi2 = await I_VolumeRestrictionTM.individualRestriction.call(account_delegate3); + let indi1 = await I_VolumeRestrictionTM.getIndividualRestriction.call(account_investor3); + let indi2 = await I_VolumeRestrictionTM.getIndividualRestriction.call(account_delegate3); - assert.equal(indi1[0].dividedBy(new BigNumber(10).pow(18)), 15); - assert.equal(indi2[0].dividedBy(new BigNumber(10).pow(16)), 12.78); + assert.equal(indi1[0].div(new BN(10).pow(new BN(18))).toString(), 15); + assert.equal(indi2[0].div(new BN(10).pow(new BN(14))).toString(), 1278); - assert.equal(indi1[2].toNumber(), 15); - assert.equal(indi2[2].toNumber(), 20); + assert.equal(indi1[2].toString(), 15); + assert.equal(indi2[2].toString(), 20); - assert.equal(indi1[4].toNumber(), 0); - assert.equal(indi2[4].toNumber(), 1); + assert.equal(indi1[4].toString(), 0); + assert.equal(indi2[4].toString(), 1); }); - it("Should modify the details before the starttime passed", async () => { + it("Should modify the details before the starttime passed", async() => { + let newLatestTime = await getLatestTime(); await I_VolumeRestrictionTM.modifyIndividualRestrictionMulti( [account_investor3, account_delegate3], - [new BigNumber(12.78).times(new BigNumber(10).pow(16)), web3.utils.toWei("15")], - [latestTime() + duration.days(1), latestTime() + duration.days(2)], + [new BN(1278).mul(new BN(10).pow(new BN(14))), new BN(web3.utils.toWei("15"))], + [newLatestTime.add(new BN(duration.days(1))), newLatestTime.add(new BN(duration.days(2)))], [20, 15], - [latestTime() + duration.days(40), latestTime() + duration.days(60)], - [1, 0], + [newLatestTime.add(new BN(duration.days(40))), newLatestTime.add(new BN(duration.days(60)))], + [1,0], { from: token_owner } ); - let indi1 = await I_VolumeRestrictionTM.individualRestriction.call(account_investor3); - let indi2 = await I_VolumeRestrictionTM.individualRestriction.call(account_delegate3); + let indi1 = await I_VolumeRestrictionTM.getIndividualRestriction.call(account_investor3); + let indi2 = await I_VolumeRestrictionTM.getIndividualRestriction.call(account_delegate3); - assert.equal(indi2[0].dividedBy(new BigNumber(10).pow(18)), 15); - assert.equal(indi1[0].dividedBy(new BigNumber(10).pow(16)), 12.78); + assert.equal(indi2[0].div(new BN(10).pow(new BN(18))).toString(), 15); + assert.equal(indi1[0].div(new BN(10).pow(new BN(14))).toString(), 1278); - assert.equal(indi2[2].toNumber(), 15); - assert.equal(indi1[2].toNumber(), 20); + assert.equal(indi2[2].toString(), 15); + assert.equal(indi1[2].toString(), 20); - assert.equal(indi2[4].toNumber(), 0); - assert.equal(indi1[4].toNumber(), 1); + assert.equal(indi2[4].toString(), 0); + assert.equal(indi1[4].toString(), 1); }); }); describe("Test the major issue from the audit ( Possible accounting corruption between individualRestriction​ and ​defaultRestriction)", async() => { - + it("Should add the individual restriction for the delegate 2 address", async() => { - await I_GeneralTransferManager.modifyWhitelist( + let currentTime = await getLatestTime(); + await I_GeneralTransferManager.modifyKYCData( account_delegate2, - latestTime(), - latestTime(), - latestTime() + duration.days(30), - true, + currentTime, + currentTime, + currentTime.add(new BN(duration.days(30))), { from: token_owner } ); - await I_SecurityToken.mint(account_delegate2, web3.utils.toWei("50"), { from: token_owner }); + await I_SecurityToken.issue(account_delegate2, new BN(web3.utils.toWei("50")), "0x0", { from: token_owner }); // Use to set the time to start of the day 0:00 to test the edge case properly await setTime(); - + currentTime = await getLatestTime(); await I_VolumeRestrictionTM.addIndividualRestriction( account_delegate2, web3.utils.toWei("12"), - latestTime() + duration.minutes(1), - 2, - latestTime() + duration.days(5.5), - 0, + currentTime.add(new BN(duration.minutes(1))), + new BN(2), + currentTime.add(new BN(duration.days(5.5))), + new BN(0), { from: token_owner } ); - assert.equal((await I_VolumeRestrictionTM.individualRestriction.call(account_delegate2))[2].toNumber(), 2); + assert.equal((await I_VolumeRestrictionTM.getIndividualRestriction.call(account_delegate2))[2].toNumber(), 2); - let data = await I_VolumeRestrictionTM.getRestrictedData.call(); + let data = await I_VolumeRestrictionTM.getRestrictionData.call(); await printRestrictedData(data); - - // Add default restriction as well + // Add default restriction as well + currentTime = await getLatestTime(); await I_VolumeRestrictionTM.removeDefaultRestriction({from: token_owner}); await I_VolumeRestrictionTM.addDefaultRestriction( web3.utils.toWei("5"), - latestTime() + duration.minutes(1), - 5, - latestTime() + duration.days(20), - 0, + currentTime.add(new BN(duration.minutes(1))), + new BN(5), + currentTime.add(new BN(duration.days(20))), + new BN(0), { from: token_owner } ); - data = await I_VolumeRestrictionTM.defaultRestriction.call(); - assert.equal(data[0].toNumber(), web3.utils.toWei("5")); - assert.equal(data[2].toNumber(), 5); - let dataRestriction = await I_VolumeRestrictionTM.defaultRestriction.call(); + data = await I_VolumeRestrictionTM.getDefaultRestriction.call(); + assert.equal(data[0].toString(), web3.utils.toWei("5")); + assert.equal(data[2].toString(), 5); + let dataRestriction = await I_VolumeRestrictionTM.getDefaultRestriction.call(); console.log(` *** Add Default restriction data *** - Allowed Tokens: ${dataRestriction[0].dividedBy(new BigNumber(10).pow(18)).toNumber()} + Allowed Tokens: ${web3.utils.fromWei(dataRestriction[0])} StartTime : ${dataRestriction[1].toNumber()} Rolling Period in days : ${dataRestriction[2].toNumber()} EndTime : ${dataRestriction[3].toNumber()} @@ -1708,8 +1822,8 @@ contract('VolumeRestrictionTransferManager', accounts => { it("Should transact with delegate address 2", async() => { await increaseTime(duration.minutes(2)); - let startTime = (await I_VolumeRestrictionTM.individualRestriction.call(account_delegate2))[1].toNumber(); - let rollingPeriod = (await I_VolumeRestrictionTM.individualRestriction.call(account_delegate2))[2].toNumber(); + let startTime = (await I_VolumeRestrictionTM.getIndividualRestriction.call(account_delegate2))[1].toNumber(); + let rollingPeriod = (await I_VolumeRestrictionTM.getIndividualRestriction.call(account_delegate2))[2].toNumber(); //sell tokens upto the limit await I_SecurityToken.transfer(account_investor2, web3.utils.toWei("6"), {from: account_delegate2}); delegateArray.push(6); @@ -1725,12 +1839,10 @@ contract('VolumeRestrictionTransferManager', accounts => { await print(data, account_delegate2); // get the trade amount using the timestamp - let amt = (await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_delegate2, data[0].toNumber())) - .dividedBy(new BigNumber(10).pow(18)).toNumber(); - + let amt = web3.utils.fromWei(await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_delegate2, data[0].toString())); // Verify the storage changes assert.equal(data[0].toNumber(), startTime + duration.days(data[2].toNumber())); - assert.equal(data[1].dividedBy(new BigNumber(10).pow(18)).toNumber(), await calculateSum(rollingPeriod, delegateArray)); + assert.equal(web3.utils.fromWei(data[1].toString()), await calculateSum(rollingPeriod, delegateArray)); assert.equal(data[2].toNumber(), 0); assert.equal(amt, 6); @@ -1748,10 +1860,9 @@ contract('VolumeRestrictionTransferManager', accounts => { console.log(`Print the individual bucket details`); let dataIndividual = await I_VolumeRestrictionTM.getIndividualBucketDetailsToUser.call(account_delegate2); await print(dataIndividual, account_delegate2); - + // get the trade amount using the timestamp - let amtTraded = (await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_delegate2, dataIndividual[0])) - .dividedBy(new BigNumber(10).pow(18)).toNumber(); + let amtTraded = web3.utils.fromWei(await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_delegate2, dataIndividual[0].toString())); // Verify the storage changes assert.equal(dataIndividual[0].toNumber(), startTime + duration.days(dataIndividual[2].toNumber())); assert.equal(dataIndividual[2].toNumber(), 5); @@ -1768,9 +1879,9 @@ contract('VolumeRestrictionTransferManager', accounts => { it("Should transact under the default restriction unaffected from the edge case", async() => { await increaseTime(duration.days(0.5)); - let individualStartTime = (await I_VolumeRestrictionTM.individualRestriction.call(account_delegate2))[1].toNumber(); - let startTime = (await I_VolumeRestrictionTM.defaultRestriction.call())[1].toNumber(); - let rollingPeriod = (await I_VolumeRestrictionTM.defaultRestriction.call())[2].toNumber(); + let individualStartTime = (await I_VolumeRestrictionTM.getIndividualRestriction.call(account_delegate2))[1].toNumber(); + let startTime = (await I_VolumeRestrictionTM.getDefaultRestriction.call())[1].toNumber(); + let rollingPeriod = (await I_VolumeRestrictionTM.getDefaultRestriction.call())[2].toNumber(); //sell tokens upto the limit await I_SecurityToken.transfer(account_investor2, web3.utils.toWei("4"), {from: account_delegate2}); @@ -1790,8 +1901,7 @@ contract('VolumeRestrictionTransferManager', accounts => { console.log(data[4].toString()); // get the trade amount using the timestamp - let amt = (await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_delegate2, data[0].toNumber())) - .dividedBy(new BigNumber(10).pow(18)).toNumber(); + let amt = web3.utils.fromWei(await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_delegate2, data[0].toString())); // Verify the storage changes assert.equal(data[0].toNumber(), startTime + duration.days(data[2].toNumber())); assert.equal(data[2].toNumber(), 6); @@ -1799,28 +1909,28 @@ contract('VolumeRestrictionTransferManager', accounts => { }); it("Should check whether user is able to transfer when amount is less than the restriction limit (when restriction change)", async() => { - + let currentTime = await getLatestTime(); await I_VolumeRestrictionTM.addIndividualRestriction( account_delegate2, web3.utils.toWei("7"), - latestTime() + duration.minutes(1), + currentTime.add(new BN(duration.minutes(1))), 1, - latestTime() + duration.days(2), + currentTime.add(new BN(duration.days(2))), 0, { from: token_owner } ); - assert.equal((await I_VolumeRestrictionTM.individualRestriction.call(account_delegate2))[2].toNumber(), 1); - let individualStartTime = (await I_VolumeRestrictionTM.individualRestriction.call(account_delegate2))[1].toNumber(); - let startTime = (await I_VolumeRestrictionTM.defaultRestriction.call())[1].toNumber(); - let rollingPeriod = (await I_VolumeRestrictionTM.defaultRestriction.call())[2].toNumber(); + assert.equal((await I_VolumeRestrictionTM.getIndividualRestriction.call(account_delegate2))[2].toNumber(), 1); + let individualStartTime = (await I_VolumeRestrictionTM.getIndividualRestriction.call(account_delegate2))[1].toNumber(); + let startTime = (await I_VolumeRestrictionTM.getDefaultRestriction.call())[1].toNumber(); + let rollingPeriod = (await I_VolumeRestrictionTM.getDefaultRestriction.call())[2].toNumber(); await increaseTime(duration.minutes(2)); // sell tokens when user restriction changes from the default restriction to individual restriction await catchRevert (I_SecurityToken.transfer(account_investor1, web3.utils.toWei("5")), {from: account_delegate2}); - + // allow to transact when the day limit is with in the restriction. default allow to transact maximum 5 tokens within // a given rolling period. 4 tokens are already sold here user trying to sell 1 more token on the same day await I_SecurityToken.transfer(account_investor1, web3.utils.toWei("1"), {from: account_delegate2}); @@ -1833,8 +1943,7 @@ contract('VolumeRestrictionTransferManager', accounts => { assert.equal(dataIndividual[0].toNumber(), individualStartTime + duration.days(dataIndividual[2].toNumber())); assert.equal(dataIndividual[2].toNumber(), 0); // get the trade amount using the timestamp - let amt = (await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_delegate2, dataIndividual[0].toNumber())) - .dividedBy(new BigNumber(10).pow(18)).toNumber(); + let amt = web3.utils.fromWei(await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_delegate2, dataIndividual[0].toString())); assert.equal(amt, 1); console.log(`Print the default bucket details`); @@ -1847,31 +1956,28 @@ contract('VolumeRestrictionTransferManager', accounts => { }); }); - describe("VolumeRestriction Transfer Manager Factory test cases", async () => { + describe("VolumeRestriction Transfer Manager Factory test cases", async() => { - it("Should get the exact details of the factory", async () => { - assert.equal(await I_VolumeRestrictionTMFactory.getSetupCost.call(), 0); - assert.equal((await I_VolumeRestrictionTMFactory.getTypes.call())[0], 2); - assert.equal(web3.utils.toAscii(await I_VolumeRestrictionTMFactory.getName.call()) - .replace(/\u0000/g, ''), - "VolumeRestrictionTM", - "Wrong Module added"); + it("Should get the exact details of the factory", async() => { + assert.equal(await I_VolumeRestrictionTMFactory.setupCost.call(),0); + assert.equal((await I_VolumeRestrictionTMFactory.getTypes.call())[0],2); + assert.equal(web3.utils.toAscii(await I_VolumeRestrictionTMFactory.name.call()) + .replace(/\u0000/g, ''), + "VolumeRestrictionTM", + "Wrong Module added"); assert.equal(await I_VolumeRestrictionTMFactory.description.call(), - "Manage transfers based on the volume of tokens that needs to be transact", - "Wrong Module added"); + "Manage transfers based on the volume of tokens that needs to be transact", + "Wrong Module added"); assert.equal(await I_VolumeRestrictionTMFactory.title.call(), - "Volume Restriction Transfer Manager", - "Wrong Module added"); - assert.equal(await I_VolumeRestrictionTMFactory.getInstructions.call(), - "Module used to restrict the volume of tokens traded by the token holders", - "Wrong Module added"); - assert.equal(await I_VolumeRestrictionTMFactory.version.call(), "1.0.0"); + "Volume Restriction Transfer Manager", + "Wrong Module added"); + assert.equal(await I_VolumeRestrictionTMFactory.version.call(), "3.0.0"); }); - it("Should get the tags of the factory", async () => { + it("Should get the tags of the factory", async() => { let tags = await I_VolumeRestrictionTMFactory.getTags.call(); - assert.equal(tags.length, 5); - assert.equal(web3.utils.toAscii(tags[0]).replace(/\u0000/g, ''), "Maximum Volume"); + assert.equal(tags.length, 3); + assert.equal(web3.utils.toAscii(tags[0]).replace(/\u0000/g, ''), "Rolling Period"); }); }); diff --git a/test/z_blacklist_transfer_manager.js b/test/z_blacklist_transfer_manager.js index 0dff595be..63057fe18 100644 --- a/test/z_blacklist_transfer_manager.js +++ b/test/z_blacklist_transfer_manager.js @@ -1,6 +1,6 @@ import latestTime from './helpers/latestTime'; import { duration, ensureException, promisifyLogWatch, latestBlock } from './helpers/utils'; -import takeSnapshot, { increaseTime, revertToSnapshot } from './helpers/time'; +import { takeSnapshot, increaseTime, revertToSnapshot } from './helpers/time'; import { encodeProxyCall, encodeModuleCall } from './helpers/encodeCall'; import { setUpPolymathNetwork, deployGPMAndVerifyed, deployBlacklistTMAndVerified } from "./helpers/createInstances"; import { catchRevert } from "./helpers/exceptions"; @@ -8,9 +8,10 @@ import { catchRevert } from "./helpers/exceptions"; const GeneralTransferManager = artifacts.require("./GeneralTransferManager"); const BlacklistTransferManager = artifacts.require("./BlacklistTransferManager"); const SecurityToken = artifacts.require("./SecurityToken.sol"); +const STGetter = artifacts.require("./STGetter.sol"); -const Web3 = require('web3'); -const BigNumber = require('bignumber.js'); +const Web3 = require('web3'); +let BN = Web3.utils.BN; const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")) // Hardcoded development port contract('BlacklistTransferManager', accounts => { @@ -53,6 +54,8 @@ contract('BlacklistTransferManager', accounts => { let I_SecurityToken; let I_PolyToken; let I_PolymathRegistry; + let I_STGetter; + let stGetter; // SecurityToken Details const name = "Team"; @@ -60,22 +63,44 @@ contract('BlacklistTransferManager', accounts => { const tokenDetails = "This is equity type of issuance"; const decimals = 18; const contact = "team@polymath.network"; - + const address_zero = "0x0000000000000000000000000000000000000000"; // Module key const delegateManagerKey = 1; const transferManagerKey = 2; const stoKey = 3; // Initial fee for ticker registry and security token registry - const initRegFee = web3.utils.toWei("250"); + const initRegFee = web3.utils.toWei("1000"); // BlacklistTransferManager details const holderCount = 2; // Maximum number of token holders const STRProxyParameters = ['address', 'address', 'uint256', 'uint256', 'address', 'address']; const MRProxyParameters = ['address', 'address']; let bytesSTO = encodeModuleCall(['uint256'], [holderCount]); + let currentTime; + + async function verifyPartitionBalance(investorAddress, lockedValue, unlockedValue) { + assert.equal( + web3.utils.fromWei( + ( + await I_BlacklistTransferManager.getTokensByPartition.call(web3.utils.toHex("LOCKED"), investorAddress, new BN(0)) + ).toString() + ), + lockedValue + ); + + assert.equal( + web3.utils.fromWei( + ( + await I_BlacklistTransferManager.getTokensByPartition.call(web3.utils.toHex("UNLOCKED"), investorAddress, new BN(0)) + ).toString() + ), + unlockedValue + ); + } before(async() => { + currentTime = new BN(await latestTime()); // Accounts setup account_polymath = accounts[0]; account_issuer = accounts[1]; @@ -101,17 +126,18 @@ contract('BlacklistTransferManager', accounts => { I_STFactory, I_SecurityTokenRegistry, I_SecurityTokenRegistryProxy, - I_STRProxied + I_STRProxied, + I_STGetter ] = instances; // STEP 2: Deploy the GeneralDelegateManagerFactory - [I_GeneralPermissionManagerFactory] = await deployGPMAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, 0); + [I_GeneralPermissionManagerFactory] = await deployGPMAndVerifyed(account_polymath, I_MRProxied, new BN(0)); // STEP 3(a): Deploy the PercentageTransferManager - [I_BlacklistTransferManagerFactory] = await deployBlacklistTMAndVerified(account_polymath, I_MRProxied, I_PolyToken.address, 0); + [I_BlacklistTransferManagerFactory] = await deployBlacklistTMAndVerified(account_polymath, I_MRProxied, new BN(0)); // STEP 4(b): Deploy the PercentageTransferManager - [P_BlacklistTransferManagerFactory] = await deployBlacklistTMAndVerified(account_polymath, I_MRProxied, I_PolyToken.address, web3.utils.toWei("500", "ether")); + [P_BlacklistTransferManagerFactory] = await deployBlacklistTMAndVerified(account_polymath, I_MRProxied, new BN(web3.utils.toWei("500", "ether"))); // ----------- POLYMATH NETWORK Configuration ------------ // Printing all the contract addresses @@ -137,7 +163,7 @@ contract('BlacklistTransferManager', accounts => { it("Should register the ticker before the generation of the security token", async () => { await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner}); - let tx = await I_STRProxied.registerTicker(token_owner, symbol, contact, { from : token_owner }); + let tx = await I_STRProxied.registerNewTicker(token_owner, symbol, { from : token_owner }); assert.equal(tx.logs[0].args._owner, token_owner); assert.equal(tx.logs[0].args._ticker, symbol.toUpperCase()); }); @@ -145,16 +171,17 @@ contract('BlacklistTransferManager', accounts => { it("Should generate the new security token with the same symbol as registered above", async () => { await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner}); let _blockNo = latestBlock(); - let tx = await I_STRProxied.generateSecurityToken(name, symbol, tokenDetails, false, { from: token_owner }); + let tx = await I_STRProxied.generateNewSecurityToken(name, symbol, tokenDetails, false, token_owner, 0, { from: token_owner }); // 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 log = await promisifyLogWatch(I_SecurityToken.ModuleAdded({from: _blockNo}), 1); + I_SecurityToken = await SecurityToken.at(tx.logs[1].args._securityTokenAddress); + stGetter = await STGetter.at(I_SecurityToken.address); + assert.equal(await stGetter.getTreasuryWallet.call(), token_owner, "Incorrect wallet set"); + const log = (await I_SecurityToken.getPastEvents('ModuleAdded', {filter: {transactionHash: tx.transactionHash}}))[0]; // Verify that GeneralTransferManager module get added successfully or not - assert.equal(log.args._types[0].toNumber(), 2); + assert.equal(log.args._types[0].toString(), 2); assert.equal( web3.utils.toAscii(log.args._name) .replace(/\u0000/g, ''), @@ -162,46 +189,47 @@ contract('BlacklistTransferManager', accounts => { ); }); - it("Should intialize the auto attached modules", async () => { - let moduleData = (await I_SecurityToken.getModulesByType(2))[0]; - I_GeneralTransferManager = GeneralTransferManager.at(moduleData); + it("Should initialize the auto attached modules", async () => { + let moduleData = (await stGetter.getModulesByType(2))[0]; + I_GeneralTransferManager = await GeneralTransferManager.at(moduleData); }); it("Should successfully attach the BlacklistTransferManager factory with the security token", async () => { - await I_PolyToken.getTokens(web3.utils.toWei("500", "ether"), token_owner); + await I_PolyToken.getTokens(web3.utils.toWei("2000", "ether"), token_owner); await catchRevert ( - I_SecurityToken.addModule(P_BlacklistTransferManagerFactory.address, bytesSTO, web3.utils.toWei("500", "ether"), 0, { - from: token_owner + I_SecurityToken.addModule(P_BlacklistTransferManagerFactory.address, bytesSTO, web3.utils.toWei("2000", "ether"), 0, false, { + from: token_owner }) ); }); it("Should successfully attach the BlacklistTransferManager factory 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_BlacklistTransferManagerFactory.address, bytesSTO, web3.utils.toWei("500", "ether"), 0, { from: token_owner }); - assert.equal(tx.logs[3].args._types[0].toNumber(), transferManagerKey, "BlacklistTransferManager doesn't get deployed"); + await I_PolyToken.transfer(I_SecurityToken.address, web3.utils.toWei("2000", "ether"), {from: token_owner}); + const tx = await I_SecurityToken.addModule(P_BlacklistTransferManagerFactory.address, bytesSTO, web3.utils.toWei("2000", "ether"), 0, false, { from: token_owner }); + assert.equal(tx.logs[3].args._types[0].toString(), transferManagerKey, "BlacklistTransferManager doesn't get deployed"); assert.equal( web3.utils.toAscii(tx.logs[3].args._name) .replace(/\u0000/g, ''), "BlacklistTransferManager", "BlacklistTransferManagerFactory module was not added" ); - P_BlacklistTransferManager = BlacklistTransferManager.at(tx.logs[3].args._module); + P_BlacklistTransferManager = await BlacklistTransferManager.at(tx.logs[3].args._module); await revertToSnapshot(snapId); }); it("Should successfully attach the BlacklistTransferManager with the security token", async () => { - const tx = await I_SecurityToken.addModule(I_BlacklistTransferManagerFactory.address, bytesSTO, 0, 0, { from: token_owner }); - assert.equal(tx.logs[2].args._types[0].toNumber(), transferManagerKey, "BlacklistTransferManager doesn't get deployed"); + const tx = await I_SecurityToken.addModule(I_BlacklistTransferManagerFactory.address, bytesSTO, 0, 0, false, { from: token_owner }); + console.log(tx); + assert.equal(tx.logs[2].args._types[0].toString(), transferManagerKey, "BlacklistTransferManager doesn't get deployed"); assert.equal( web3.utils.toAscii(tx.logs[2].args._name) .replace(/\u0000/g, ''), "BlacklistTransferManager", "BlacklistTransferManager module was not added" ); - I_BlacklistTransferManager = BlacklistTransferManager.at(tx.logs[2].args._module); + I_BlacklistTransferManager = await BlacklistTransferManager.at(tx.logs[2].args._module); }); }); @@ -211,39 +239,37 @@ contract('BlacklistTransferManager', accounts => { it("Should Buy the tokens", async() => { // Add the Investor in to the whitelist - let tx = await I_GeneralTransferManager.modifyWhitelist( + let tx = await I_GeneralTransferManager.modifyKYCData( account_investor1, - latestTime(), - latestTime(), - latestTime() + duration.days(50), - true, + currentTime, + currentTime, + currentTime.add(new BN(duration.days(50))), { from: account_issuer }); 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('5', 'ether'), { from: token_owner }); - + await I_SecurityToken.issue(account_investor1, web3.utils.toWei('5', 'ether'), "0x0", { from: token_owner }); + assert.equal( - (await I_SecurityToken.balanceOf(account_investor1)).toNumber(), + (await I_SecurityToken.balanceOf(account_investor1)).toString(), web3.utils.toWei('5', 'ether') ); - + }); it("Should Buy some more tokens", async() => { // Add the Investor in to the whitelist - let tx = await I_GeneralTransferManager.modifyWhitelist( + let tx = await I_GeneralTransferManager.modifyKYCData( account_investor2, - latestTime(), - latestTime(), - latestTime() + duration.days(50), - true, + currentTime, + currentTime, + currentTime.add(new BN(duration.days(50))), { from: account_issuer }); @@ -251,10 +277,10 @@ contract('BlacklistTransferManager', accounts => { 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 }); + await I_SecurityToken.issue(account_investor2, web3.utils.toWei('2', 'ether'), "0x0", { from: token_owner }); assert.equal( - (await I_SecurityToken.balanceOf(account_investor2)).toNumber(), + (await I_SecurityToken.balanceOf(account_investor2)).toString(), web3.utils.toWei('2', 'ether') ); }); @@ -262,12 +288,11 @@ contract('BlacklistTransferManager', accounts => { it("Should Buy some more tokens", async() => { // Add the Investor in to the whitelist - let tx = await I_GeneralTransferManager.modifyWhitelist( + let tx = await I_GeneralTransferManager.modifyKYCData( account_investor3, - latestTime(), - latestTime(), - latestTime() + duration.days(50), - true, + currentTime, + currentTime, + currentTime.add(new BN(duration.days(50))), { from: account_issuer }); @@ -275,10 +300,10 @@ contract('BlacklistTransferManager', accounts => { 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('2', 'ether'), { from: token_owner }); + await I_SecurityToken.issue(account_investor3, web3.utils.toWei('2', 'ether'), "0x0", { from: token_owner }); assert.equal( - (await I_SecurityToken.balanceOf(account_investor3)).toNumber(), + (await I_SecurityToken.balanceOf(account_investor3)).toString(), web3.utils.toWei('2', 'ether') ); }); @@ -286,12 +311,11 @@ contract('BlacklistTransferManager', accounts => { it("Should Buy some more tokens", async() => { // Add the Investor in to the whitelist - let tx = await I_GeneralTransferManager.modifyWhitelist( + let tx = await I_GeneralTransferManager.modifyKYCData( account_investor4, - latestTime(), - latestTime(), - latestTime() + duration.days(50), - true, + currentTime, + currentTime, + currentTime.add(new BN(duration.days(50))), { from: account_issuer }); @@ -299,10 +323,10 @@ contract('BlacklistTransferManager', accounts => { assert.equal(tx.logs[0].args._investor.toLowerCase(), account_investor4.toLowerCase(), "Failed in adding the investor in whitelist"); // Mint some tokens - await I_SecurityToken.mint(account_investor4, web3.utils.toWei('2', 'ether'), { from: token_owner }); + await I_SecurityToken.issue(account_investor4, web3.utils.toWei('2', 'ether'), "0x0", { from: token_owner }); assert.equal( - (await I_SecurityToken.balanceOf(account_investor4)).toNumber(), + (await I_SecurityToken.balanceOf(account_investor4)).toString(), web3.utils.toWei('2', 'ether') ); }); @@ -310,12 +334,11 @@ contract('BlacklistTransferManager', accounts => { it("Should Buy some more tokens", async() => { // Add the Investor in to the whitelist - let tx = await I_GeneralTransferManager.modifyWhitelist( + let tx = await I_GeneralTransferManager.modifyKYCData( account_investor5, - latestTime(), - latestTime(), - latestTime() + duration.days(50), - true, + currentTime, + currentTime, + currentTime.add(new BN(duration.days(50))), { from: account_issuer }); @@ -323,10 +346,10 @@ contract('BlacklistTransferManager', accounts => { assert.equal(tx.logs[0].args._investor.toLowerCase(), account_investor5.toLowerCase(), "Failed in adding the investor in whitelist"); // Mint some tokens - await I_SecurityToken.mint(account_investor5, web3.utils.toWei('2', 'ether'), { from: token_owner }); + await I_SecurityToken.issue(account_investor5, web3.utils.toWei('2', 'ether'), "0x0", { from: token_owner }); assert.equal( - (await I_SecurityToken.balanceOf(account_investor5)).toNumber(), + (await I_SecurityToken.balanceOf(account_investor5)).toString(), web3.utils.toWei('2', 'ether') ); }); @@ -334,126 +357,127 @@ contract('BlacklistTransferManager', accounts => { it("Should add the blacklist", async() => { //Add the new blacklist - let tx = await I_BlacklistTransferManager.addBlacklistType(latestTime()+1000, latestTime()+3000, "a_blacklist", 20, { from: token_owner }); + currentTime = new BN(await latestTime()); + let tx = await I_BlacklistTransferManager.addBlacklistType(currentTime.add(new BN(1000)), currentTime.add(new BN(3000)), web3.utils.fromAscii("a_blacklist"), 20, { from: token_owner }); assert.equal(web3.utils.hexToUtf8(tx.logs[0].args._blacklistName), "a_blacklist", "Failed in adding the type in blacklist"); }); it("Should fail in adding the blacklist as blacklist type already exist", async() => { await catchRevert( - I_BlacklistTransferManager.addBlacklistType(latestTime()+1000, latestTime()+3000, "a_blacklist", 20, { - from: token_owner + I_BlacklistTransferManager.addBlacklistType(currentTime.add(new BN(1000)), currentTime.add(new BN(3000)), web3.utils.fromAscii("a_blacklist"), 20, { + from: token_owner }) - ); + ); }); it("Should fail in adding the blacklist as the blacklist name is invalid", async() => { await catchRevert( - I_BlacklistTransferManager.addBlacklistType(latestTime()+1000, latestTime()+3000, "", 20, { - from: token_owner + I_BlacklistTransferManager.addBlacklistType(currentTime.add(new BN(1000)), currentTime.add(new BN(3000)), web3.utils.fromAscii(""), 20, { + from: token_owner }) - ); + ); }); it("Should fail in adding the blacklist as the start date is invalid", async() => { await catchRevert( - I_BlacklistTransferManager.addBlacklistType(0, latestTime()+3000, "b_blacklist", 20, { - from: token_owner + I_BlacklistTransferManager.addBlacklistType(0, currentTime.add(new BN(3000)), web3.utils.fromAscii("b_blacklist"), 20, { + from: token_owner }) ); }); it("Should fail in adding the blacklist as the dates are invalid", async() => { await catchRevert( - I_BlacklistTransferManager.addBlacklistType(latestTime()+4000, latestTime()+3000, "b_blacklist", 20, { - from: token_owner + I_BlacklistTransferManager.addBlacklistType(currentTime.add(new BN(4000)), currentTime.add(new BN(3000)), web3.utils.fromAscii("b_blacklist"), 20, { + from: token_owner }) ); }); it("Should fail in adding the blacklist because only owner can add the blacklist", async() => { await catchRevert( - I_BlacklistTransferManager.addBlacklistType(latestTime()+1000, latestTime()+3000, "b_blacklist", 20, { - from: account_investor1 + I_BlacklistTransferManager.addBlacklistType(currentTime.add(new BN(1000)), currentTime.add(new BN(3000)), web3.utils.fromAscii("b_blacklist"), 20, { + from: account_investor1 }) - ); + ); }); it("Should fail in adding the blacklist because repeat period is less than the difference of start time and end time", async() => { await catchRevert( - I_BlacklistTransferManager.addBlacklistType(latestTime() + 1000, latestTime() + duration.days(2), "b_blacklist", 1, { - from: token_owner + I_BlacklistTransferManager.addBlacklistType(currentTime.add(new BN(1000)), currentTime.add(new BN(duration.days(2))), web3.utils.fromAscii("b_blacklist"), 1, { + from: token_owner }) - ); + ); }); it("Should add the mutiple blacklist", async() => { //Add the new blacklist - let startTime = [latestTime()+2000,latestTime()+3000]; - let endTime = [latestTime()+5000,latestTime()+8000]; - let name = ["y_blacklist","z_blacklist"]; + let startTime = [currentTime.add(new BN(2000)),currentTime.add(new BN(3000))]; + let endTime = [currentTime.add(new BN(5000)),currentTime.add(new BN(8000))]; + let name = [web3.utils.fromAscii("y_blacklist"),web3.utils.fromAscii("z_blacklist")]; let repeatTime = [15,30]; let tx = await I_BlacklistTransferManager.addBlacklistTypeMulti(startTime, endTime, name, repeatTime, { from: token_owner }); - + let event_data = tx.logs; for (var i = 0; i < event_data.length; i++) { let blacklistName = event_data[i].args._blacklistName; - assert.equal(web3.utils.hexToUtf8(blacklistName), name[i], "Failed in adding the blacklist"); + assert.equal(web3.utils.hexToUtf8(blacklistName), web3.utils.hexToUtf8(name[i]), "Failed in adding the blacklist"); } }); it("Should fail in adding the mutiple blacklist because only owner can add it", async() => { //Add the new blacklist - let startTime = [latestTime()+2000,latestTime()+3000]; - let endTime = [latestTime()+5000,latestTime()+8000]; - let name = ["y_blacklist","z_blacklist"]; + let startTime = [currentTime.add(new BN(2000)),currentTime.add(new BN(3000))]; + let endTime = [currentTime.add(new BN(5000)),currentTime.add(new BN(8000))]; + let name = [web3.utils.fromAscii("y_blacklist"),web3.utils.fromAscii("z_blacklist")]; let repeatTime = [15,30]; await catchRevert( - I_BlacklistTransferManager.addBlacklistTypeMulti(startTime, endTime, name, repeatTime, { - from: account_investor1 + I_BlacklistTransferManager.addBlacklistTypeMulti(startTime, endTime, name, repeatTime, { + from: account_investor1 }) ); }); it("Should fail in adding the mutiple blacklist because array lenth are different", async() => { //Add the new blacklist - let startTime = [latestTime()+2000,latestTime()+3000]; - let endTime = [latestTime()+5000,latestTime()+8000]; - let name = ["y_blacklist","z_blacklist","w_blacklist"]; + let startTime = [currentTime.add(new BN(2000)),currentTime.add(new BN(3000))]; + let endTime = [currentTime.add(new BN(5000)),currentTime.add(new BN(8000))]; + let name = [web3.utils.fromAscii("y_blacklist"),web3.utils.fromAscii("z_blacklist"),web3.utils.fromAscii("w_blacklist")]; let repeatTime = [15,30]; await catchRevert( - I_BlacklistTransferManager.addBlacklistTypeMulti(startTime, endTime, name, repeatTime, { - from: token_owner + I_BlacklistTransferManager.addBlacklistTypeMulti(startTime, endTime, name, repeatTime, { + from: token_owner }) ); }); it("Should modify the blacklist", async() => { //Modify the existing blacklist - let tx = await I_BlacklistTransferManager.modifyBlacklistType(latestTime()+2000, latestTime()+3000, "a_blacklist", 20, { from: token_owner }); + let tx = await I_BlacklistTransferManager.modifyBlacklistType(currentTime.add(new BN(2000)), currentTime.add(new BN(3000)), web3.utils.fromAscii("a_blacklist"), 20, { from: token_owner }); assert.equal(web3.utils.hexToUtf8(tx.logs[0].args._blacklistName), "a_blacklist", "Failed in modifying the startdate of blacklist"); }); it("Should fail in modifying the blacklist as the name is invalid", async() => { await catchRevert( - I_BlacklistTransferManager.modifyBlacklistType(latestTime()+2000, latestTime()+3000, "", 20, { - from: token_owner + I_BlacklistTransferManager.modifyBlacklistType(currentTime.add(new BN(2000)), currentTime.add(new BN(3000)), web3.utils.fromAscii(""), 20, { + from: token_owner }) ); }); it("Should fail in modifying the blacklist as the dates are invalid", async() => { await catchRevert( - I_BlacklistTransferManager.modifyBlacklistType(latestTime()+4000, latestTime()+3000, "b_blacklist", 20, { - from: token_owner + I_BlacklistTransferManager.modifyBlacklistType(currentTime.add(new BN(4000)), currentTime.add(new BN(3000)), web3.utils.fromAscii("b_blacklist"), 20, { + from: token_owner }) - ); + ); }); it("Should fail in modifying the blacklist as the repeat in days is invalid", async() => { await catchRevert( - I_BlacklistTransferManager.modifyBlacklistType(latestTime()+2000, latestTime()+3000, "b_blacklist", 0, { - from: token_owner + I_BlacklistTransferManager.modifyBlacklistType(currentTime.add(new BN(2000)), currentTime.add(new BN(3000)), web3.utils.fromAscii("b_blacklist"), 0, { + from: token_owner }) ); }); @@ -461,109 +485,110 @@ contract('BlacklistTransferManager', accounts => { it("Should fail in modifying the blacklist as only owner can modify the blacklist", async() => { await catchRevert( - I_BlacklistTransferManager.modifyBlacklistType(latestTime()+1000, latestTime()+3000, "a_blacklist", 20, { - from: account_investor1 + I_BlacklistTransferManager.modifyBlacklistType(currentTime.add(new BN(1000)), currentTime.add(new BN(3000)), web3.utils.fromAscii("a_blacklist"), 20, { + from: account_investor1 }) ); }); it("Should fail in modifying the blacklist as blacklist type doesnot exist", async() => { await catchRevert( - I_BlacklistTransferManager.modifyBlacklistType(latestTime()+1000, latestTime()+3000, "b_blacklist", 20, { - from: token_owner + I_BlacklistTransferManager.modifyBlacklistType(currentTime.add(new BN(1000)), currentTime.add(new BN(3000)), web3.utils.fromAscii("b_blacklist"), 20, { + from: token_owner }) - ); + ); }); it("Should modify the mutiple blacklist", async() => { //Add the new blacklist - let startTime = [latestTime()+3000,latestTime()+3000]; - let endTime = [latestTime()+5000,latestTime()+7000]; - let name = ["y_blacklist","z_blacklist"]; + let startTime = [currentTime.add(new BN(3000)),currentTime.add(new BN(3000))]; + let endTime = [currentTime.add(new BN(5000)),currentTime.add(new BN(7000))]; + let name = [web3.utils.fromAscii("y_blacklist"),web3.utils.fromAscii("z_blacklist")]; let repeatTime = [15,30]; let tx = await I_BlacklistTransferManager.modifyBlacklistTypeMulti(startTime, endTime, name, repeatTime, { from: token_owner }); - + let event_data = tx.logs; for (var i = 0; i < event_data.length; i++) { let blacklistName = event_data[i].args._blacklistName; - assert.equal(web3.utils.hexToUtf8(blacklistName), name[i], "Failed in adding the blacklist"); + assert.equal(web3.utils.hexToUtf8(blacklistName), web3.utils.hexToUtf8(name[i]), "Failed in adding the blacklist"); } }); it("Should fail in modifying the mutiple blacklist because only owner can add it", async() => { //Add the new blacklist - let startTime = [latestTime()+3000,latestTime()+3000]; - let endTime = [latestTime()+5000,latestTime()+7000]; - let name = ["y_blacklist","z_blacklist"]; + let startTime = [currentTime.add(new BN(3000)),currentTime.add(new BN(3000))]; + let endTime = [currentTime.add(new BN(5000)),currentTime.add(new BN(7000))]; + let name = [web3.utils.fromAscii("y_blacklist"),web3.utils.fromAscii("z_blacklist")]; let repeatTime = [15,30]; await catchRevert( - I_BlacklistTransferManager.modifyBlacklistTypeMulti(startTime, endTime, name, repeatTime, { - from: account_investor1 + I_BlacklistTransferManager.modifyBlacklistTypeMulti(startTime, endTime, name, repeatTime, { + from: account_investor1 }) ); }); it("Should fail in modifying the mutiple blacklist because array length are different", async() => { //Add the new blacklist - let startTime = [latestTime()+3000,latestTime()+3000]; - let endTime = [latestTime()+5000,latestTime()+7000]; - let name = ["y_blacklist","z_blacklist","w_blacklist"]; + let startTime = [currentTime.add(new BN(3000)),currentTime.add(new BN(3000))]; + let endTime = [currentTime.add(new BN(5000)),currentTime.add(new BN(7000))]; + let name = [web3.utils.fromAscii("y_blacklist"),web3.utils.fromAscii("z_blacklist"),web3.utils.fromAscii("w_blacklist")]; let repeatTime = [15,30]; await catchRevert( - I_BlacklistTransferManager.modifyBlacklistTypeMulti(startTime, endTime, name, repeatTime, { - from: token_owner + I_BlacklistTransferManager.modifyBlacklistTypeMulti(startTime, endTime, name, repeatTime, { + from: token_owner }) ); }); it("Should add investor to the blacklist", async() => { //Add investor to the existing blacklist - let tx = await I_BlacklistTransferManager.addInvestorToBlacklist(account_investor1, "a_blacklist", { from: token_owner }); + let tx = await I_BlacklistTransferManager.addInvestorToBlacklist(account_investor1, web3.utils.fromAscii("a_blacklist"), { from: token_owner }); assert.equal(tx.logs[0].args._investor.toLowerCase(), account_investor1.toLowerCase(), "Failed in adding the investor to the blacklist"); }); it("Should fail in adding the investor to the blacklist because only owner can add the investor", async() => { await catchRevert( - I_BlacklistTransferManager.addInvestorToBlacklist(account_investor2, "a_blacklist", { - from: account_investor1 + I_BlacklistTransferManager.addInvestorToBlacklist(account_investor2, web3.utils.fromAscii("a_blacklist"), { + from: account_investor1 }) ); }); it("Should fail in adding the investor to the blacklist as investor address is invalid", async() => { await catchRevert( - I_BlacklistTransferManager.addInvestorToBlacklist(0x0, "a_blacklist", { - from: token_owner + I_BlacklistTransferManager.addInvestorToBlacklist("0x0000000000000000000000000000000000000000", web3.utils.fromAscii("a_blacklist"), { + from: token_owner }) - ); + ); }); it("Should fail in adding the investor to the non existing blacklist", async() => { await catchRevert( - I_BlacklistTransferManager.addInvestorToBlacklist(account_investor2, "b_blacklist", { - from: token_owner + I_BlacklistTransferManager.addInvestorToBlacklist(account_investor2, web3.utils.fromAscii("b_blacklist"), { + from: token_owner }) ); }); it("Should get the list of investors associated to blacklist", async() => { - let perm = await I_BlacklistTransferManager.getListOfAddresses.call("a_blacklist"); + let perm = await I_BlacklistTransferManager.getListOfAddresses.call(web3.utils.fromAscii("a_blacklist")); assert.equal(perm.length, 1); }); it("Should fail in getting the list of investors from the non existing blacklist", async() => { await catchRevert( - I_BlacklistTransferManager.getListOfAddresses.call("b_blacklist") + I_BlacklistTransferManager.getListOfAddresses.call(web3.utils.fromAscii("b_blacklist")) ); }); - it("Should investor be able to transfer token because current time is less than the blacklist start time", async() => { + it("Should investor be able to transfer token because current time is less than the blacklist start time", async() => { //Trasfer tokens + await verifyPartitionBalance(account_investor1, 0, 5); await I_SecurityToken.transfer(account_investor2, web3.utils.toWei('1', 'ether'), { from: account_investor1 }); assert.equal( - (await I_SecurityToken.balanceOf(account_investor2)).toNumber(), + (await I_SecurityToken.balanceOf(account_investor2)).toString(), web3.utils.toWei('3', 'ether') ); }); @@ -571,11 +596,11 @@ contract('BlacklistTransferManager', accounts => { it("Should investor be able to transfer token as it is not in blacklist time period", async() => { // Jump time await increaseTime(duration.seconds(4000)); - + await verifyPartitionBalance(account_investor1, 0, 4); //Trasfer tokens await I_SecurityToken.transfer(account_investor2, web3.utils.toWei('1', 'ether'), { from: account_investor1 }); assert.equal( - (await I_SecurityToken.balanceOf(account_investor2)).toNumber(), + (await I_SecurityToken.balanceOf(account_investor2)).toString(), web3.utils.toWei('4', 'ether') ); }); @@ -583,35 +608,41 @@ contract('BlacklistTransferManager', accounts => { it("Should fail in transfer the tokens as the investor in blacklist", async() => { // Jump time await increaseTime(duration.days(20) - 1500); + await verifyPartitionBalance(account_investor1, 3, 0); await catchRevert( - I_SecurityToken.transfer(account_investor2, web3.utils.toWei('1', 'ether'), { - from: account_investor1 + I_SecurityToken.transfer(account_investor2, web3.utils.toWei('1', 'ether'), { + from: account_investor1 }) ); }); it("Should investor is able transfer the tokens- because BlacklistTransferManager is paused", async() => { await I_BlacklistTransferManager.pause({from:token_owner}); + await verifyPartitionBalance(account_investor1, 0, 3); //Trasfer tokens await I_SecurityToken.transfer(account_investor2, web3.utils.toWei('1', 'ether'), { from: account_investor1 }); assert.equal( - (await I_SecurityToken.balanceOf(account_investor2)).toNumber(), + (await I_SecurityToken.balanceOf(account_investor2)).toString(), web3.utils.toWei('5', 'ether') ); + await verifyPartitionBalance(account_investor1, 0, 2); }); it("Should investor fail in transfer token as it is in blacklist time period", async() => { + await I_BlacklistTransferManager.unpause({from:token_owner}); - await I_BlacklistTransferManager.addBlacklistType(latestTime()+500, latestTime()+4000, "k_blacklist", 8, { from: token_owner }); - - await I_BlacklistTransferManager.addInvestorToBlacklist(account_investor2, "k_blacklist", { from: token_owner }); + await verifyPartitionBalance(account_investor1, 2, 0); + currentTime = new BN(await latestTime()); + await I_BlacklistTransferManager.addBlacklistType(currentTime.add(new BN(500)), currentTime.add(new BN(4000)), web3.utils.fromAscii("k_blacklist"), 8, { from: token_owner }); + + await I_BlacklistTransferManager.addInvestorToBlacklist(account_investor2, web3.utils.fromAscii("k_blacklist"), { from: token_owner }); // Jump time await increaseTime(3500); - + //Trasfer tokens await catchRevert( - I_SecurityToken.transfer(account_investor3, web3.utils.toWei('1', 'ether'), { - from: account_investor2 + I_SecurityToken.transfer(account_investor3, web3.utils.toWei('1', 'ether'), { + from: account_investor2 }) ) }); @@ -619,38 +650,39 @@ contract('BlacklistTransferManager', accounts => { it("Should investor be able to transfer token as it is not in blacklist time period", async() => { // Jump time await increaseTime(1000); - + //Trasfer tokens await I_SecurityToken.transfer(account_investor3, web3.utils.toWei('1', 'ether'), { from: account_investor2 }); assert.equal( - (await I_SecurityToken.balanceOf(account_investor3)).toNumber(), + (await I_SecurityToken.balanceOf(account_investor3)).toString(), web3.utils.toWei('3', 'ether') ); }); it("Should investor fail in transfer token as it is in blacklist time period", async() => { - + // Jump time await increaseTime(duration.days(8) - 1000); //Trasfer tokens await catchRevert( - I_SecurityToken.transfer(account_investor3, web3.utils.toWei('1', 'ether'), { - from: account_investor2 + I_SecurityToken.transfer(account_investor3, web3.utils.toWei('1', 'ether'), { + from: account_investor2 }) ); }); it("Should investor fail in transfer token as it is in blacklist time period", async() => { - await I_BlacklistTransferManager.addBlacklistType(latestTime()+5000, latestTime()+8000, "l_blacklist", 5, { from: token_owner }); - - await I_BlacklistTransferManager.addInvestorToBlacklist(account_investor3, "l_blacklist", { from: token_owner }); + currentTime = new BN(await latestTime()); + await I_BlacklistTransferManager.addBlacklistType(currentTime.add(new BN(5000)), currentTime.add(new BN(8000)), web3.utils.fromAscii("l_blacklist"), 5, { from: token_owner }); + + await I_BlacklistTransferManager.addInvestorToBlacklist(account_investor3, web3.utils.fromAscii("l_blacklist"), { from: token_owner }); // Jump time await increaseTime(5500); - + //Trasfer tokens await catchRevert( - I_SecurityToken.transfer(account_investor4, web3.utils.toWei('1', 'ether'), { - from: account_investor3 + I_SecurityToken.transfer(account_investor4, web3.utils.toWei('1', 'ether'), { + from: account_investor3 }) ); }); @@ -658,117 +690,120 @@ contract('BlacklistTransferManager', accounts => { it("Should investor be able to transfer token as it is not in blacklist time period", async() => { // Jump time await increaseTime(3000); - + //Trasfer tokens await I_SecurityToken.transfer(account_investor4, web3.utils.toWei('1', 'ether'), { from: account_investor3 }); assert.equal( - (await I_SecurityToken.balanceOf(account_investor4)).toNumber(), + (await I_SecurityToken.balanceOf(account_investor4)).toString(), web3.utils.toWei('3', 'ether') ); }); it("Should investor fail in transfer token as it is in blacklist time period", async() => { - + // Jump time await increaseTime(duration.days(5) - 3000); //Trasfer tokens await catchRevert( - I_SecurityToken.transfer(account_investor4, web3.utils.toWei('1', 'ether'), { - from: account_investor3 - }) + I_SecurityToken.transfer(account_investor4, web3.utils.toWei('1', 'ether'), { + from: account_investor3 + }) ); }); it("Should delete the blacklist type", async() => { - await I_BlacklistTransferManager.addBlacklistType(latestTime()+1000, latestTime()+3000, "b_blacklist", 20, { from: token_owner }); - let tx = await I_BlacklistTransferManager.deleteBlacklistType("b_blacklist", { from: token_owner }); + currentTime = new BN(await latestTime()); + await I_BlacklistTransferManager.addBlacklistType(currentTime.add(new BN(1000)), currentTime.add(new BN(3000)), web3.utils.fromAscii("b_blacklist"), 20, { from: token_owner }); + let tx = await I_BlacklistTransferManager.deleteBlacklistType(web3.utils.fromAscii("b_blacklist"), { from: token_owner }); assert.equal(web3.utils.hexToUtf8(tx.logs[0].args._blacklistName), "b_blacklist", "Failed in deleting the blacklist"); }); - it("Only owner have the permission to delete thr blacklist type", async() => { - await I_BlacklistTransferManager.addBlacklistType(latestTime()+1000, latestTime()+3000, "b_blacklist", 20, { from: token_owner }); + it("Only owner have the permission to delete the blacklist type", async() => { + currentTime = new BN(await latestTime()); + await I_BlacklistTransferManager.addBlacklistType(currentTime.add(new BN(1000)), currentTime.add(new BN(3000)), web3.utils.fromAscii("b_blacklist"), 20, { from: token_owner }); await catchRevert( - I_BlacklistTransferManager.deleteBlacklistType("b_blacklist", { - from: account_investor1 + I_BlacklistTransferManager.deleteBlacklistType(web3.utils.fromAscii("b_blacklist"), { + from: account_investor1 }) ); }); it("Should fail in deleting the blacklist type as the blacklist has associated addresses", async() => { await catchRevert( - I_BlacklistTransferManager.deleteBlacklistType("a_blacklist", { - from: token_owner + I_BlacklistTransferManager.deleteBlacklistType(web3.utils.fromAscii("a_blacklist"), { + from: token_owner }) - ); + ); }); it("Should fail in deleting the blacklist type as the blacklist doesnot exist", async() => { await catchRevert( - I_BlacklistTransferManager.deleteBlacklistType("c_blacklist", { - from: token_owner + I_BlacklistTransferManager.deleteBlacklistType(web3.utils.fromAscii("c_blacklist"), { + from: token_owner }) ); }); it("Should delete the mutiple blacklist type", async() => { - let name = ["y_blacklist","z_blacklist"]; + let name = [web3.utils.fromAscii("y_blacklist"),web3.utils.fromAscii("z_blacklist")]; let tx = await I_BlacklistTransferManager.deleteBlacklistTypeMulti(name, { from: token_owner }); - + let event_data = tx.logs; for (var i = 0; i < event_data.length; i++) { let blacklistName = event_data[i].args._blacklistName; - assert.equal(web3.utils.hexToUtf8(blacklistName), name[i], "Failed in deleting the blacklist"); + assert.equal(web3.utils.hexToUtf8(blacklistName), web3.utils.hexToUtf8(name[i]), "Failed in deleting the blacklist"); } }); it("Should fail in deleting multiple blacklist type because only owner can do it.", async() => { - let name = ["b_blacklist","a_blacklist"]; + let name = [web3.utils.fromAscii("b_blacklist"),web3.utils.fromAscii("a_blacklist")]; await catchRevert( - I_BlacklistTransferManager.deleteBlacklistTypeMulti(name, { - from: account_investor1 + I_BlacklistTransferManager.deleteBlacklistTypeMulti(name, { + from: account_investor1 }) ); }); it("Should delete the investor from all the associated blacklist", async() => { + currentTime = new BN(await latestTime()); let data = await I_BlacklistTransferManager.getBlacklistNamesToUser.call(account_investor1); - await I_BlacklistTransferManager.addBlacklistType(latestTime()+1000, latestTime()+3000, "g_blacklist", 20, { from: token_owner }); - await I_BlacklistTransferManager.addInvestorToBlacklist(account_investor1, "g_blacklist", { from: token_owner }); + await I_BlacklistTransferManager.addBlacklistType(currentTime.add(new BN(1000)), currentTime.add(new BN(3000)), web3.utils.fromAscii("g_blacklist"), 20, { from: token_owner }); + await I_BlacklistTransferManager.addInvestorToBlacklist(account_investor1, web3.utils.fromAscii("g_blacklist"), { from: token_owner }); let tx = await I_BlacklistTransferManager.deleteInvestorFromAllBlacklist(account_investor1, { from: token_owner }); assert.equal(tx.logs[0].args._investor.toLowerCase(), account_investor1.toLowerCase(), "Failed in deleting the investor from the blacklist"); }); it("Only owner has the permission to delete the investor from all the blacklist type", async() => { - await I_BlacklistTransferManager.addInvestorToBlacklist(account_investor1, "g_blacklist", { from: token_owner }); + await I_BlacklistTransferManager.addInvestorToBlacklist(account_investor1,web3.utils.fromAscii("g_blacklist"), { from: token_owner }); await catchRevert( - I_BlacklistTransferManager.deleteInvestorFromAllBlacklist(account_investor1, { - from: account_investor2 + I_BlacklistTransferManager.deleteInvestorFromAllBlacklist(account_investor1, { + from: account_investor2 }) - ) + ) }); it("Should fail in deleting the investor from all the associated blacklist as th address is invalid", async() => { await catchRevert( - I_BlacklistTransferManager.deleteInvestorFromAllBlacklist(0x0, { - from: token_owner + I_BlacklistTransferManager.deleteInvestorFromAllBlacklist("0x0000000000000000000000000000000000000000", { + from: token_owner }) ); }); it("Should fail in deleting the investor because investor is not associated to any blacklist", async() => { await catchRevert( - I_BlacklistTransferManager.deleteInvestorFromAllBlacklist(account_investor5, { - from: token_owner + I_BlacklistTransferManager.deleteInvestorFromAllBlacklist(account_investor5, { + from: token_owner }) ); }); it("Should delete the mutiple investor from all the associated blacklist", async() => { - await I_BlacklistTransferManager.addInvestorToBlacklist(account_investor5, "g_blacklist", { from: token_owner }); - await I_BlacklistTransferManager.addInvestorToBlacklist(account_investor2, "g_blacklist", { from: token_owner }); + await I_BlacklistTransferManager.addInvestorToBlacklist(account_investor5, web3.utils.fromAscii("g_blacklist"), { from: token_owner }); + await I_BlacklistTransferManager.addInvestorToBlacklist(account_investor2, web3.utils.fromAscii("g_blacklist"), { from: token_owner }); let investor = [account_investor5,account_investor2]; let tx = await I_BlacklistTransferManager.deleteInvestorFromAllBlacklistMulti(investor, { from: token_owner }); let event_data = tx.logs; @@ -778,22 +813,22 @@ contract('BlacklistTransferManager', accounts => { }); it("Should fail in deleting the mutiple investor from all the associated blacklist because only owner can do it.", async() => { - await I_BlacklistTransferManager.addInvestorToBlacklist(account_investor5, "g_blacklist", { from: token_owner }); - await I_BlacklistTransferManager.addInvestorToBlacklist(account_investor2, "g_blacklist", { from: token_owner }); + await I_BlacklistTransferManager.addInvestorToBlacklist(account_investor5, web3.utils.fromAscii("g_blacklist"), { from: token_owner }); + await I_BlacklistTransferManager.addInvestorToBlacklist(account_investor2, web3.utils.fromAscii("g_blacklist"), { from: token_owner }); let investor = [account_investor5,account_investor2]; await catchRevert( - I_BlacklistTransferManager.deleteInvestorFromAllBlacklistMulti(investor, { - from: account_investor1 + I_BlacklistTransferManager.deleteInvestorFromAllBlacklistMulti(investor, { + from: account_investor1 }) ); }); it("Should delete the mutiple investor from particular associated blacklists", async() => { - await I_BlacklistTransferManager.addBlacklistType(latestTime()+1000, latestTime()+3000, "s_blacklist", 20, { from: token_owner }); - await I_BlacklistTransferManager.addInvestorToBlacklist(account_investor5, "s_blacklist", { from: token_owner }); + await I_BlacklistTransferManager.addBlacklistType(currentTime.add(new BN(1000)), currentTime.add(new BN(3000)), web3.utils.fromAscii("s_blacklist"), 20, { from: token_owner }); + await I_BlacklistTransferManager.addInvestorToBlacklist(account_investor5, web3.utils.fromAscii("s_blacklist"), { from: token_owner }); // await I_BlacklistTransferManager.addInvestorToBlacklist(account_investor2, "g_blacklist", { from: token_owner }); let investor = [account_investor5,account_investor2]; - let blacklistName = ["s_blacklist","g_blacklist"]; + let blacklistName = [web3.utils.fromAscii("s_blacklist"),web3.utils.fromAscii("g_blacklist")]; let tx = await I_BlacklistTransferManager.deleteMultiInvestorsFromBlacklistMulti(investor,blacklistName, { from: token_owner }); let event_data = tx.logs; for (var i = 0; i < event_data.length; i++) { @@ -803,74 +838,74 @@ contract('BlacklistTransferManager', accounts => { }); it("Should fail in deleting the mutiple investor from particular associated blacklist because only owner can do it.", async() => { - await I_BlacklistTransferManager.addInvestorToBlacklist(account_investor5, "s_blacklist", { from: token_owner }); - await I_BlacklistTransferManager.addInvestorToBlacklist(account_investor2, "g_blacklist", { from: token_owner }); + await I_BlacklistTransferManager.addInvestorToBlacklist(account_investor5, web3.utils.fromAscii("s_blacklist"), { from: token_owner }); + await I_BlacklistTransferManager.addInvestorToBlacklist(account_investor2, web3.utils.fromAscii("g_blacklist"), { from: token_owner }); let investor = [account_investor5,account_investor2]; - let blacklistName = ["s_blacklist","g_blacklist"]; + let blacklistName = [web3.utils.fromAscii("s_blacklist"),web3.utils.fromAscii("g_blacklist")]; await catchRevert( - I_BlacklistTransferManager.deleteMultiInvestorsFromBlacklistMulti(investor,blacklistName, { - from: account_investor1 + I_BlacklistTransferManager.deleteMultiInvestorsFromBlacklistMulti(investor,blacklistName, { + from: account_investor1 }) ); }); it("Should fail in deleting the mutiple investor from particular associated blacklist because array length is incorrect.", async() => { let investor = [account_investor5]; - let blacklistName = ["s_blacklist","g_blacklist"]; + let blacklistName = [web3.utils.fromAscii("s_blacklist"),web3.utils.fromAscii("g_blacklist")]; await catchRevert( - I_BlacklistTransferManager.deleteMultiInvestorsFromBlacklistMulti(investor,blacklistName, { - from: token_owner + I_BlacklistTransferManager.deleteMultiInvestorsFromBlacklistMulti(investor,blacklistName, { + from: token_owner }) ); }); it("Should delete the investor from the blacklist type", async() => { - await I_BlacklistTransferManager.addBlacklistType(latestTime()+1000, latestTime()+3000, "f_blacklist", 20, { from: token_owner }); - await I_BlacklistTransferManager.addInvestorToBlacklist(account_investor1, "f_blacklist", { from: token_owner }); - await I_BlacklistTransferManager.addInvestorToBlacklist(account_investor5, "f_blacklist", { from: token_owner }); - await I_BlacklistTransferManager.addBlacklistType(latestTime()+500, latestTime()+8000, "q_blacklist", 10, { from: token_owner }); - await I_BlacklistTransferManager.addInvestorToBlacklist(account_investor1, "q_blacklist", { from: token_owner }); - let tx = await I_BlacklistTransferManager.deleteInvestorFromBlacklist(account_investor1, "f_blacklist", { from: token_owner }); + await I_BlacklistTransferManager.addBlacklistType(currentTime.add(new BN(1000)), currentTime.add(new BN(3000)), web3.utils.fromAscii("f_blacklist"), 20, { from: token_owner }); + await I_BlacklistTransferManager.addInvestorToBlacklist(account_investor1, web3.utils.fromAscii("f_blacklist"), { from: token_owner }); + await I_BlacklistTransferManager.addInvestorToBlacklist(account_investor5, web3.utils.fromAscii("f_blacklist"), { from: token_owner }); + await I_BlacklistTransferManager.addBlacklistType(currentTime.add(new BN(500)), currentTime.add(new BN(8000)), web3.utils.fromAscii("q_blacklist"), 10, { from: token_owner }); + await I_BlacklistTransferManager.addInvestorToBlacklist(account_investor1, web3.utils.fromAscii("q_blacklist"), { from: token_owner }); + let tx = await I_BlacklistTransferManager.deleteInvestorFromBlacklist(account_investor1, web3.utils.fromAscii("f_blacklist"), { from: token_owner }); assert.equal(tx.logs[0].args._investor.toLowerCase(), account_investor1.toLowerCase(), "Failed in deleting the investor from the blacklist"); }); it("Only owner can delete the investor from the blacklist type", async() => { - await I_BlacklistTransferManager.addInvestorToBlacklist(account_investor1, "f_blacklist", { from: token_owner }); + await I_BlacklistTransferManager.addInvestorToBlacklist(account_investor1, web3.utils.fromAscii("f_blacklist"), { from: token_owner }); await catchRevert( - I_BlacklistTransferManager.deleteInvestorFromBlacklist(account_investor1, "f_blacklist", { - from: account_investor2 + I_BlacklistTransferManager.deleteInvestorFromBlacklist(account_investor1, web3.utils.fromAscii("f_blacklist"), { + from: account_investor2 }) ); }); it("Should fail in deleting the investor because the investor address is invalid", async() => { await catchRevert( - I_BlacklistTransferManager.deleteInvestorFromBlacklist(0x0, "f_blacklist", { - from: token_owner + I_BlacklistTransferManager.deleteInvestorFromBlacklist("0x0000000000000000000000000000000000000000", web3.utils.fromAscii("f_blacklist"), { + from: token_owner }) ); }); it("Should fail in deleting the investor because the investor is not associated to blacklist", async() => { - await I_BlacklistTransferManager.deleteInvestorFromBlacklist(account_investor1, "f_blacklist", { from: token_owner }); + await I_BlacklistTransferManager.deleteInvestorFromBlacklist(account_investor1, web3.utils.fromAscii("f_blacklist"), { from: token_owner }); await catchRevert( - I_BlacklistTransferManager.deleteInvestorFromBlacklist(account_investor1, "f_blacklist", { - from: token_owner + I_BlacklistTransferManager.deleteInvestorFromBlacklist(account_investor1, web3.utils.fromAscii("f_blacklist"), { + from: token_owner }) ); }); it("Should fail in deleting the investor because the blacklist name is invalid", async() => { await catchRevert( - I_BlacklistTransferManager.deleteInvestorFromBlacklist(account_investor1, "", { - from: token_owner + I_BlacklistTransferManager.deleteInvestorFromBlacklist(account_investor1, web3.utils.fromAscii(""), { + from: token_owner }) ); }); it("Should add investor and new blacklist type", async() => { - let tx = await I_BlacklistTransferManager.addInvestorToNewBlacklist(latestTime()+1000, latestTime()+3000, "c_blacklist", 20, account_investor3, { from: token_owner }); + let tx = await I_BlacklistTransferManager.addInvestorToNewBlacklist(currentTime.add(new BN(1000)), currentTime.add(new BN(3000)), web3.utils.fromAscii("c_blacklist"), 20, account_investor3, { from: token_owner }); assert.equal(web3.utils.hexToUtf8(tx.logs[0].args._blacklistName), "c_blacklist", "Failed in adding the blacklist"); assert.equal(tx.logs[1].args._investor, account_investor3, "Failed in adding the investor to blacklist"); @@ -878,67 +913,67 @@ contract('BlacklistTransferManager', accounts => { it("Should fail in adding the investor and new blacklist type", async() => { await catchRevert( - I_BlacklistTransferManager.addInvestorToNewBlacklist(latestTime()+1000, latestTime()+3000, "c_blacklist", 20, account_investor3, { - from: account_investor2 + I_BlacklistTransferManager.addInvestorToNewBlacklist(currentTime.add(new BN(1000)), currentTime.add(new BN(3000)), web3.utils.fromAscii("c_blacklist"), 20, account_investor3, { + from: account_investor2 }) ); }); it("Should add mutiple investor to blacklist", async() => { - await I_BlacklistTransferManager.addBlacklistType(latestTime()+1000, latestTime()+3000, "d_blacklist", 20, { from: token_owner }); + await I_BlacklistTransferManager.addBlacklistType(currentTime.add(new BN(1000)), currentTime.add(new BN(3000)), web3.utils.fromAscii("d_blacklist"), 20, { from: token_owner }); let investor = [account_investor4,account_investor5]; - let tx = await I_BlacklistTransferManager.addInvestorToBlacklistMulti([account_investor4,account_investor5], "d_blacklist", { from: token_owner }); - + let tx = await I_BlacklistTransferManager.addInvestorToBlacklistMulti([account_investor4,account_investor5], web3.utils.fromAscii("d_blacklist"), { from: token_owner }); + let event_data = tx.logs; for (var i = 0; i < event_data.length; i++) { let user = event_data[i].args._investor; assert.equal(user, investor[i], "Failed in adding the investor to blacklist"); } - + }); it("Should fail in adding the mutiple investor to the blacklist", async() => { await catchRevert( - I_BlacklistTransferManager.addInvestorToBlacklistMulti([account_investor4,account_investor5], "b_blacklist", { - from: account_investor1 + I_BlacklistTransferManager.addInvestorToBlacklistMulti([account_investor4,account_investor5], web3.utils.fromAscii("b_blacklist"), { + from: account_investor1 }) ); }); it("Should add mutiple investor to the mutiple blacklist", async() => { - await I_BlacklistTransferManager.addBlacklistType(latestTime()+1000, latestTime()+3000, "m_blacklist", 20, { from: token_owner }); - await I_BlacklistTransferManager.addBlacklistType(latestTime()+1000, latestTime()+3000, "n_blacklist", 20, { from: token_owner }); + await I_BlacklistTransferManager.addBlacklistType(currentTime.add(new BN(1000)), currentTime.add(new BN(3000)), web3.utils.fromAscii("m_blacklist"), 20, { from: token_owner }); + await I_BlacklistTransferManager.addBlacklistType(currentTime.add(new BN(1000)), currentTime.add(new BN(3000)), web3.utils.fromAscii("n_blacklist"), 20, { from: token_owner }); let investor = [account_investor4,account_investor5]; - let blacklistName =["m_blacklist","n_blacklist"]; + let blacklistName =[web3.utils.fromAscii("m_blacklist"),web3.utils.fromAscii("n_blacklist")]; let tx = await I_BlacklistTransferManager.addMultiInvestorToBlacklistMulti(investor, blacklistName, { from: token_owner }); - + let event_data = tx.logs; for (var i = 0; i < event_data.length; i++) { let user = event_data[i].args._investor; let blacklist = event_data[i].args._blacklistName; assert.equal(user, investor[i], "Failed in adding the investor to blacklist"); - assert.equal(web3.utils.hexToUtf8(blacklist), blacklistName[i], "Failed in adding the investor to blacklist"); + assert.equal(web3.utils.hexToUtf8(blacklist), web3.utils.hexToUtf8(blacklistName[i]), "Failed in adding the investor to blacklist"); } - + }); it("Should fail in adding the mutiple investor to the mutiple blacklist because only owner can do it.", async() => { let investor = [account_investor4,account_investor5]; - let blacklistName = ["m_blacklist","n_blacklist"]; + let blacklistName = [ web3.utils.fromAscii("m_blacklist"), web3.utils.fromAscii("n_blacklist")]; await I_BlacklistTransferManager.deleteMultiInvestorsFromBlacklistMulti(investor,blacklistName, { from: token_owner }); await catchRevert( - I_BlacklistTransferManager.addMultiInvestorToBlacklistMulti(investor, blacklistName, { - from: account_investor1 + I_BlacklistTransferManager.addMultiInvestorToBlacklistMulti(investor, blacklistName, { + from: account_investor1 }) ); }); it("Should fail in adding mutiple investor to the mutiple blacklist because array length is not same", async() => { let investor = [account_investor4,account_investor5]; - let blacklistName =["m_blacklist"]; + let blacklistName =[web3.utils.fromAscii("m_blacklist")]; await catchRevert( - I_BlacklistTransferManager.addMultiInvestorToBlacklistMulti(investor, blacklistName, { - from: token_owner + I_BlacklistTransferManager.addMultiInvestorToBlacklistMulti(investor, blacklistName, { + from: token_owner }) ); }); @@ -954,11 +989,37 @@ contract('BlacklistTransferManager', accounts => { }); }); + describe("Test cases for blacklist with repeat period 0 (Never repeat)", async() => { + it("Should add a new blacklist with no repeat time", async() => { + let curTime = await latestTime(); + await I_BlacklistTransferManager.deleteInvestorFromAllBlacklist(account_investor3, { from: token_owner }); + await I_BlacklistTransferManager.addInvestorToNewBlacklist( + new BN(curTime).add(new BN(100)), + new BN(curTime).add(new BN(1000)), + web3.utils.fromAscii("anewbl"), + 0, + account_investor3, + { from: token_owner} + ); + await increaseTime(200); + await catchRevert(I_SecurityToken.transfer(account_investor4, web3.utils.toWei('1', 'ether'), { from: account_investor3 })); + }); + + it("Should allow transfer after blacklist end time", async() => { + await increaseTime(2000); + await I_SecurityToken.transfer(account_investor4, web3.utils.toWei('1', 'ether'), { from: account_investor3 }); + assert.equal( + (await I_SecurityToken.balanceOf(account_investor4)).toString(), + web3.utils.toWei('4', 'ether') + ); + }); + }); + describe("Test cases for the factory", async() => { it("Should get the exact details of the factory", async() => { assert.equal(await I_BlacklistTransferManagerFactory.setupCost.call(),0); assert.equal((await I_BlacklistTransferManagerFactory.getTypes.call())[0],2); - assert.equal(web3.utils.toAscii(await I_BlacklistTransferManagerFactory.getName.call()) + assert.equal(web3.utils.toAscii(await I_BlacklistTransferManagerFactory.name.call()) .replace(/\u0000/g, ''), "BlacklistTransferManager", "Wrong Module added"); @@ -968,15 +1029,12 @@ contract('BlacklistTransferManager', accounts => { assert.equal(await I_BlacklistTransferManagerFactory.title.call(), "Blacklist Transfer Manager", "Wrong Module added"); - assert.equal(await I_BlacklistTransferManagerFactory.getInstructions.call(), - "Allows an issuer to blacklist the addresses.", - "Wrong Module added"); assert.equal(await I_BlacklistTransferManagerFactory.version.call(), - "2.1.0", + "3.0.0", "Wrong Module added"); - + }); - + it("Should get the tags of the factory", async() => { let tags = await I_BlacklistTransferManagerFactory.getTags.call(); assert.equal(web3.utils.toAscii(tags[0]).replace(/\u0000/g, ''),"Blacklist"); diff --git a/test/z_fuzz_test_adding_removing_modules_ST.js b/test/z_fuzz_test_adding_removing_modules_ST.js index 5b6c8b57d..870a19158 100644 --- a/test/z_fuzz_test_adding_removing_modules_ST.js +++ b/test/z_fuzz_test_adding_removing_modules_ST.js @@ -4,12 +4,12 @@ import { pk } from './helpers/testprivateKey'; import { duration, promisifyLogWatch, latestBlock } from './helpers/utils'; import { takeSnapshot, increaseTime, revertToSnapshot } from './helpers/time'; import { catchRevert } from "./helpers/exceptions"; -import { setUpPolymathNetwork, - deployGPMAndVerifyed, - deployCountTMAndVerifyed, - deployLockupVolumeRTMAndVerified, - deployPercentageTMAndVerified, - deployManualApprovalTMAndVerifyed +import { setUpPolymathNetwork, + deployGPMAndVerifyed, + deployCountTMAndVerifyed, + deployLockUpTMAndVerified, + deployPercentageTMAndVerified, + deployManualApprovalTMAndVerifyed } from "./helpers/createInstances"; import { encodeModuleCall } from "./helpers/encodeCall"; @@ -22,11 +22,11 @@ const CountTransferManager = artifacts.require("./CountTransferManager"); const ManualApprovalTransferManager = artifacts.require('./ManualApprovalTransferManager'); const VolumeRestrictionTransferManager = artifacts.require('./LockUpTransferManager'); const PercentageTransferManager = artifacts.require('./PercentageTransferManager'); - +const STGetter = artifacts.require("./STGetter.sol"); const Web3 = require('web3'); -const BigNumber = require('bignumber.js'); +const BN = Web3.utils.BN; const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")) // Hardcoded development port contract('GeneralPermissionManager', accounts => { @@ -68,12 +68,14 @@ contract('GeneralPermissionManager', accounts => { let I_STRProxied; let I_PolyToken; let I_PolymathRegistry; - + let I_STGetter; + let stGetter; + //Define all modules for test let I_CountTransferManagerFactory; let I_CountTransferManager; - + let I_ManualApprovalTransferManagerFactory; let I_ManualApprovalTransferManager; @@ -91,14 +93,14 @@ contract('GeneralPermissionManager', accounts => { const contact = "team@polymath.network"; const delegateDetails = "Hello I am legit delegate"; const STVRParameters = ["bool", "uint256", "bool"]; - + const address_zero = "0x0000000000000000000000000000000000000000"; // Module key const delegateManagerKey = 1; const transferManagerKey = 2; const stoKey = 3; // Initial fee for ticker registry and security token registry - const initRegFee = web3.utils.toWei("250"); + const initRegFee = web3.utils.toWei("1000"); let _details = "details holding for test"; let testRepeat = 20; @@ -145,21 +147,22 @@ contract('GeneralPermissionManager', accounts => { I_STFactory, I_SecurityTokenRegistry, I_SecurityTokenRegistryProxy, - I_STRProxied + I_STRProxied, + I_STGetter ] = instances; // STEP 5: Deploy the GeneralDelegateManagerFactory - [I_GeneralPermissionManagerFactory] = await deployGPMAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, 0); + [I_GeneralPermissionManagerFactory] = await deployGPMAndVerifyed(account_polymath, I_MRProxied, 0); // STEP 6: Deploy the GeneralDelegateManagerFactory - [P_GeneralPermissionManagerFactory] = await deployGPMAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, web3.utils.toWei("500")); + [P_GeneralPermissionManagerFactory] = await deployGPMAndVerifyed(account_polymath, I_MRProxied, web3.utils.toWei("500")); - [I_CountTransferManagerFactory] = await deployCountTMAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, 0); + [I_CountTransferManagerFactory] = await deployCountTMAndVerifyed(account_polymath, I_MRProxied, 0); // Deploy Modules - [I_CountTransferManagerFactory] = await deployCountTMAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, 0); - [I_ManualApprovalTransferManagerFactory] = await deployManualApprovalTMAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, 0); - [I_VolumeRestrictionTransferManagerFactory] = await deployLockupVolumeRTMAndVerified(account_polymath, I_MRProxied, I_PolyToken.address, 0); - [I_PercentageTransferManagerFactory] = await deployPercentageTMAndVerified(account_polymath, I_MRProxied, I_PolyToken.address, 0); + [I_CountTransferManagerFactory] = await deployCountTMAndVerifyed(account_polymath, I_MRProxied, 0); + [I_ManualApprovalTransferManagerFactory] = await deployManualApprovalTMAndVerifyed(account_polymath, I_MRProxied, 0); + [I_VolumeRestrictionTransferManagerFactory] = await deployLockUpTMAndVerified(account_polymath, I_MRProxied, 0); + [I_PercentageTransferManagerFactory] = await deployPercentageTMAndVerified(account_polymath, I_MRProxied, 0); // Printing all the contract addresses console.log(` @@ -181,7 +184,7 @@ contract('GeneralPermissionManager', accounts => { describe("Generate the SecurityToken", async () => { it("Should register the ticker before the generation of the security token", async () => { await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); - let tx = await I_STRProxied.registerTicker(token_owner, symbol, contact, { from: token_owner }); + let tx = await I_STRProxied.registerNewTicker(token_owner, symbol, { from: token_owner }); assert.equal(tx.logs[0].args._owner, token_owner); assert.equal(tx.logs[0].args._ticker, symbol.toUpperCase()); }); @@ -189,41 +192,43 @@ contract('GeneralPermissionManager', accounts => { it("Should generate the new security token with the same symbol as registered above", async () => { await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); let _blockNo = latestBlock(); - let tx = await I_STRProxied.generateSecurityToken(name, symbol, tokenDetails, false, { from: token_owner }); + let tx = await I_STRProxied.generateNewSecurityToken(name, symbol, tokenDetails, false, token_owner, 0, { from: token_owner }); // 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 log = await promisifyLogWatch(I_SecurityToken.ModuleAdded({ from: _blockNo }), 1); + I_SecurityToken = await SecurityToken.at(tx.logs[1].args._securityTokenAddress); + stGetter = await STGetter.at(I_SecurityToken.address); + assert.equal(await stGetter.getTreasuryWallet.call(), token_owner, "Incorrect wallet set"); + const log = (await I_SecurityToken.getPastEvents('ModuleAdded', {filter: {transactionHash: tx.transactionHash}}))[0]; // Verify that GeneralTransferManager module get added successfully or not assert.equal(log.args._types[0].toNumber(), 2); assert.equal(web3.utils.toAscii(log.args._name).replace(/\u0000/g, ""), "GeneralTransferManager"); }); - it("Should intialize the auto attached modules", async () => { - let moduleData = (await I_SecurityToken.getModulesByType(2))[0]; - I_GeneralTransferManager = GeneralTransferManager.at(moduleData); + it("Should initialize the auto attached modules", async () => { + let moduleData = (await stGetter.getModulesByType(2))[0]; + I_GeneralTransferManager = await GeneralTransferManager.at(moduleData); }); it("Should successfully attach the General permission manager factory with the security token -- failed because Token is not paid", async () => { let errorThrown = false; - await I_PolyToken.getTokens(web3.utils.toWei("500", "ether"), token_owner); + await I_PolyToken.getTokens(web3.utils.toWei("2000", "ether"), token_owner); await catchRevert( - I_SecurityToken.addModule(P_GeneralPermissionManagerFactory.address, "0x", web3.utils.toWei("500", "ether"), 0, { from: token_owner }) + I_SecurityToken.addModule(P_GeneralPermissionManagerFactory.address, "0x0", web3.utils.toWei("2000", "ether"), 0, false, { from: token_owner }) ); }); it("Should successfully attach the General permission manager factory with the security token", async () => { let snapId = await takeSnapshot(); - await I_PolyToken.transfer(I_SecurityToken.address, web3.utils.toWei("500", "ether"), { from: token_owner }); + await I_PolyToken.transfer(I_SecurityToken.address, web3.utils.toWei("2000", "ether"), { from: token_owner }); const tx = await I_SecurityToken.addModule( P_GeneralPermissionManagerFactory.address, - "0x", - web3.utils.toWei("500", "ether"), + "0x0", + web3.utils.toWei("2000", "ether"), 0, + false, { from: token_owner } ); assert.equal(tx.logs[3].args._types[0].toNumber(), delegateManagerKey, "General Permission Manager doesn't get deployed"); @@ -232,19 +237,19 @@ contract('GeneralPermissionManager', accounts => { "GeneralPermissionManager", "GeneralPermissionManagerFactory module was not added" ); - P_GeneralPermissionManager = GeneralPermissionManager.at(tx.logs[3].args._module); + P_GeneralPermissionManager = await GeneralPermissionManager.at(tx.logs[3].args._module); await revertToSnapshot(snapId); }); it("Should successfully attach the General permission manager factory with the security token", async () => { - const tx = await I_SecurityToken.addModule(I_GeneralPermissionManagerFactory.address, "0x", 0, 0, { from: token_owner }); + const tx = await I_SecurityToken.addModule(I_GeneralPermissionManagerFactory.address, "0x0", 0, 0, false, { from: token_owner }); assert.equal(tx.logs[2].args._types[0].toNumber(), delegateManagerKey, "General Permission Manager doesn't get deployed"); assert.equal( web3.utils.toAscii(tx.logs[2].args._name).replace(/\u0000/g, ""), "GeneralPermissionManager", "GeneralPermissionManagerFactory module was not added" ); - I_GeneralPermissionManager = GeneralPermissionManager.at(tx.logs[2].args._module); + I_GeneralPermissionManager = await GeneralPermissionManager.at(tx.logs[2].args._module); }); }); @@ -257,7 +262,7 @@ contract('GeneralPermissionManager', accounts => { console.log("1"); // fuzz test loop over total times of testRepeat for (var i = 0; i < testRepeat; i++) { - + console.log("1.2"); // choose a random module with in the totalMoudules available @@ -273,7 +278,7 @@ contract('GeneralPermissionManager', accounts => { bytesSTO = encodeModuleCall(["uint256"], [holderCount]); } else if (random.module == 'PercentageTransferManager'){ console.log("PTM 01"); - const holderPercentage = 70 * 10**16; + const holderPercentage = 70 * 10**16; bytesSTO = web3.eth.abi.encodeFunctionCall({ name: 'configure', type: 'function', @@ -290,13 +295,13 @@ contract('GeneralPermissionManager', accounts => { } else { console.log("no data defined for choosen module "+random.module); } - + // attach it to the ST - let tx = await I_SecurityToken.addModule(randomFactory.address, bytesSTO, 0, 0, { from: token_owner }); + let tx = await I_SecurityToken.addModule(randomFactory.address, bytesSTO, 0, 0, false, { from: token_owner }); console.log("1.3"); - let randomModuleInstance = randomModule.at(tx.logs[2].args._module); + let randomModuleInstance = await randomModule.at(tx.logs[2].args._module); console.log("successfully attached module " + randomModuleInstance.address); - + // remove it from the ST tx = await I_SecurityToken.archiveModule(randomModuleInstance.address, { from: token_owner }); console.log("1.4"); diff --git a/test/z_fuzzer_volumn_restriction_transfer_manager.js b/test/z_fuzzer_volumn_restriction_transfer_manager.js index b03ab6fdc..48ffea344 100644 --- a/test/z_fuzzer_volumn_restriction_transfer_manager.js +++ b/test/z_fuzzer_volumn_restriction_transfer_manager.js @@ -9,9 +9,9 @@ import { setUpPolymathNetwork, deployVRTMAndVerifyed } from "./helpers/createIns const SecurityToken = artifacts.require('./SecurityToken.sol'); const GeneralTransferManager = artifacts.require('./GeneralTransferManager.sol'); const VolumeRestrictionTM = artifacts.require('./VolumeRestrictionTM.sol'); - +const STGetter = artifacts.require("./STGetter.sol"); const Web3 = require('web3'); -const BigNumber = require('bignumber.js'); +const BN = Web3.utils.BN; const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")) // Hardcoded development port contract('VolumeRestrictionTransferManager', accounts => { @@ -29,8 +29,8 @@ contract('VolumeRestrictionTransferManager', accounts => { let account_delegate2; let account_delegate3; // investor Details - let fromTime = latestTime(); - let toTime = latestTime(); + let fromTime = currentTime; + let toTime = currentTime; let expiryTime = toTime + duration.days(15); let message = "Transaction Should Fail!"; @@ -54,6 +54,8 @@ contract('VolumeRestrictionTransferManager', accounts => { let I_STRProxied; let I_PolyToken; let I_PolymathRegistry; + let I_STGetter; + let stGetter; // SecurityToken Details const name = "Team"; @@ -62,30 +64,31 @@ contract('VolumeRestrictionTransferManager', accounts => { const decimals = 18; const contact = "team@polymath.network"; const delegateDetails = "Hello I am legit delegate"; + const address_zero = "0x0000000000000000000000000000000000000000"; // Module key const delegateManagerKey = 1; const transferManagerKey = 2; const stoKey = 3; - let tempAmount = new BigNumber(0); + let tempAmount = new BN(0); let tempArray = new Array(); let tempArray3 = new Array(); let tempArrayGlobal = new Array(); // Initial fee for ticker registry and security token registry - const initRegFee = web3.utils.toWei("250"); + const initRegFee = web3.utils.toWei("1000"); async function print(data, account) { console.log(` - Latest timestamp: ${data[0].toNumber()} - SumOfLastPeriod: ${data[1].dividedBy(new BigNumber(10).pow(18)).toNumber()} - Days Covered: ${data[2].toNumber()} - Latest timestamp daily: ${data[3].toNumber()} + Latest timestamp: ${data[0].toString()} + SumOfLastPeriod: ${data[1].dividedBy(new BN(10).pow(18)).toString()} + Days Covered: ${data[2].toString()} + Latest timestamp daily: ${data[3].toString()} Individual Total Trade on latestTimestamp : ${(await I_VolumeRestrictionTM.getTotalTradedByUser.call(account, data[0])) - .dividedBy(new BigNumber(10).pow(18)).toNumber()} + .dividedBy(new BN(10).pow(18)).toString()} Individual Total Trade on daily latestTimestamp : ${(await I_VolumeRestrictionTM.getTotalTradedByUser.call(account, data[3])) - .dividedBy(new BigNumber(10).pow(18)).toNumber()} + .dividedBy(new BN(10).pow(18)).toString()} `) } @@ -102,16 +105,17 @@ contract('VolumeRestrictionTransferManager', accounts => { async function printIR(data) { console.log(` - Allowed Tokens : ${data[0].dividedBy(new BigNumber(10).pow(18)).toNumber()} - StartTime : ${data[1].toNumber()} - Rolling Period : ${data[2].toNumber()} - EndTime : ${data[3].toNumber()} - Restriction Type: ${data[4].toNumber() == 0 ? "Fixed" : "Percentage"} + Allowed Tokens : ${web3.utils.fromWei(data[0])} + StartTime : ${data[1].toString()} + Rolling Period : ${data[2].toString()} + EndTime : ${data[3].toString()} + Restriction Type: ${data[4].toString() == 0 ? "Fixed" : "Percentage"} `) } - + let currentTime; before(async() => { // Accounts setup + currentTime = new BN(await latestTime()); account_polymath = accounts[0]; account_issuer = accounts[1]; @@ -140,13 +144,14 @@ contract('VolumeRestrictionTransferManager', accounts => { I_STFactory, I_SecurityTokenRegistry, I_SecurityTokenRegistryProxy, - I_STRProxied + I_STRProxied, + I_STGetter ] = instances; // STEP 5: Deploy the VolumeRestrictionTMFactory - [I_VolumeRestrictionTMFactory] = await deployVRTMAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, 0); + [I_VolumeRestrictionTMFactory] = await deployVRTMAndVerifyed(account_polymath, I_MRProxied, 0); // STEP 6: Deploy the VolumeRestrictionTMFactory - [P_VolumeRestrictionTMFactory] = await deployVRTMAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, web3.utils.toWei("500")); + [P_VolumeRestrictionTMFactory] = await deployVRTMAndVerifyed(account_polymath, I_MRProxied, web3.utils.toWei("500")); // Printing all the contract addresses console.log(` @@ -168,62 +173,64 @@ contract('VolumeRestrictionTransferManager', accounts => { describe("Generate the SecurityToken", async () => { it("Should register the ticker before the generation of the security token", async () => { await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); - let tx = await I_STRProxied.registerTicker(token_owner, symbol, contact, { from: token_owner }); + let tx = await I_STRProxied.registerNewTicker(token_owner, symbol, { from: token_owner }); assert.equal(tx.logs[0].args._owner, token_owner); assert.equal(tx.logs[0].args._ticker, symbol.toUpperCase()); }); it("Should generate the new security token with the same symbol as registered above", async () => { await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); - let _blockNo = latestBlock(); - let tx = await I_STRProxied.generateSecurityToken(name, symbol, tokenDetails, true, { from: token_owner }); + + let tx = await I_STRProxied.generateNewSecurityToken(name, symbol, tokenDetails, true, token_owner, 0, { from: token_owner }); + console.log(tx.logs); // 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 log = await promisifyLogWatch(I_SecurityToken.ModuleAdded({ from: _blockNo }), 1); + I_SecurityToken = await SecurityToken.at(tx.logs[1].args._securityTokenAddress); + stGetter = await STGetter.at(I_SecurityToken.address); + assert.equal(await stGetter.getTreasuryWallet.call(), token_owner, "Incorrect wallet set"); + const log = (await I_SecurityToken.getPastEvents('ModuleAdded', {filter: {transactionHash: tx.transactionHash}}))[0]; // Verify that GeneralTransferManager module get added successfully or not - assert.equal(log.args._types[0].toNumber(), 2); + assert.equal(log.args._types[0].toString(), 2); assert.equal(web3.utils.toAscii(log.args._name).replace(/\u0000/g, ""), "GeneralTransferManager"); }); - it("Should intialize the auto attached modules", async () => { - let moduleData = (await I_SecurityToken.getModulesByType(2))[0]; - I_GeneralTransferManager = GeneralTransferManager.at(moduleData); + it("Should initialize the auto attached modules", async () => { + let moduleData = (await stGetter.getModulesByType(2))[0]; + I_GeneralTransferManager = await GeneralTransferManager.at(moduleData); }); }); - describe("Attach the VRTMaaaaa", async() => { + describe("Attach the VRTM", async() => { it("Deploy the VRTM and attach with the ST", async()=> { - let tx = await I_SecurityToken.addModule(I_VolumeRestrictionTMFactory.address, 0, 0, 0, {from: token_owner }); + let tx = await I_SecurityToken.addModule(I_VolumeRestrictionTMFactory.address, "0x0", 0, 0, false, {from: token_owner }); assert.equal(tx.logs[2].args._moduleFactory, I_VolumeRestrictionTMFactory.address); assert.equal( web3.utils.toUtf8(tx.logs[2].args._name), "VolumeRestrictionTM", "VolumeRestrictionTMFactory doesn not added"); - I_VolumeRestrictionTM = VolumeRestrictionTM.at(tx.logs[2].args._module); + I_VolumeRestrictionTM = await VolumeRestrictionTM.at(tx.logs[2].args._module); }); it("Transfer some tokens to different account", async() => { // Add tokens in to the whitelist - await I_GeneralTransferManager.modifyWhitelistMulti( + currentTime = new BN(await latestTime()); + await I_GeneralTransferManager.modifyKYCDataMulti( [account_investor1, account_investor2, account_investor3], - [latestTime(), latestTime(), latestTime()], - [latestTime(), latestTime(), latestTime()], - [latestTime() + duration.days(60), latestTime() + duration.days(60), latestTime() + duration.days(60)], - [true, true, true], + [currentTime, currentTime, currentTime], + [currentTime, currentTime, currentTime], + [currentTime.add(new BN(duration.days(60))), currentTime.add(new BN(duration.days(60))), currentTime.add(new BN(duration.days(60)))], { from: token_owner } ); // Mint some tokens and transferred to whitelisted addresses - await I_SecurityToken.mint(account_investor1, web3.utils.toWei("100", "ether"), {from: token_owner}); - await I_SecurityToken.mint(account_investor2, web3.utils.toWei("30", "ether"), {from: token_owner}); - await I_SecurityToken.mint(account_investor3, web3.utils.toWei("30", "ether"), {from: token_owner}); + await I_SecurityToken.issue(account_investor1, web3.utils.toWei("100", "ether"), "0x0", {from: token_owner}); + await I_SecurityToken.issue(account_investor2, web3.utils.toWei("30", "ether"), "0x0", {from: token_owner}); + await I_SecurityToken.issue(account_investor3, web3.utils.toWei("30", "ether"), "0x0", {from: token_owner}); }); @@ -233,58 +240,59 @@ contract('VolumeRestrictionTransferManager', accounts => { it("Should work with multiple transaction within 1 day with Individual and daily Restrictions", async() => { // let snapId = await takeSnapshot(); - - var testRepeat = 0; + + var testRepeat = 0; for (var i = 0; i < testRepeat; i++) { console.log("fuzzer number " + i); - var individualRestrictTotalAmount = Math.floor(Math.random() * 10); + var individualRestrictTotalAmount = Math.floor(Math.random() * 10); if ( individualRestrictTotalAmount == 0 ) { individualRestrictTotalAmount = 1; } - var dailyRestrictionAmount = Math.floor(Math.random() * 10); + var dailyRestrictionAmount = Math.floor(Math.random() * 10); if ( dailyRestrictionAmount == 0 ) { dailyRestrictionAmount = 1; } - var rollingPeriod = 2; - var sumOfLastPeriod = 0; + var rollingPeriod = 2; + var sumOfLastPeriod = 0; console.log("a"); - + currentTime = new BN(await latestTime()); // 1 - add individual restriction with a random number let tx = await I_VolumeRestrictionTM.addIndividualRestriction( account_investor1, web3.utils.toWei(individualRestrictTotalAmount.toString()), - latestTime() + duration.seconds(2), + currentTime.add(new BN(duration.seconds(2))), rollingPeriod, - latestTime() + duration.days(3), + currentTime.add(new BN(duration.days(3))), 0, { from: token_owner } - ); - - console.log("b"); + ); + currentTime = new BN(await latestTime()); + console.log("b"); tx = await I_VolumeRestrictionTM.addIndividualDailyRestriction( account_investor1, web3.utils.toWei(dailyRestrictionAmount.toString()), - latestTime() + duration.seconds(1), - latestTime() + duration.days(4), + currentTime.add(new BN(duration.seconds(1))), + currentTime.add(new BN(duration.days(4))), 0, { from: token_owner } ); - + console.log("c"); var txNumber = 10; // define fuzz test amount for tx within 24 hrs - + currentTime = new BN(await latestTime()); for (var j=0; j { }); it("Should work with fuzz test for individual restriction and general restriction", async() => { - // let snapId = await takeSnapshot(); + // let snapId = await takeSnapshot(); var testRepeat = 0; for (var i = 0; i < testRepeat; i++) { console.log("fuzzer number " + i); - var individualRestrictTotalAmount = Math.floor(Math.random() * 10); + var individualRestrictTotalAmount = Math.floor(Math.random() * 10); if (individualRestrictTotalAmount == 0 ) { individualRestrictTotalAmount = 1; } - var defaultRestrictionAmount = Math.floor(Math.random() * 10); - var rollingPeriod = 2; - var sumOfLastPeriod = 0; + var defaultRestrictionAmount = Math.floor(Math.random() * 10); + var rollingPeriod = 2; + var sumOfLastPeriod = 0; console.log("a"); - + currentTime = new BN(await latestTime()); // 1 - add individual restriction with a random number let tx = await I_VolumeRestrictionTM.addIndividualRestriction( account_investor1, web3.utils.toWei(individualRestrictTotalAmount.toString()), - latestTime() + duration.seconds(1), + currentTime.add(new BN(duration.seconds(1))), rollingPeriod, - latestTime() + duration.days(3), + currentTime.add(new BN(duration.days(3))), 0, { from: token_owner } - ); - - console.log("b"); + ); + currentTime = new BN(await latestTime()); + console.log("b"); tx = await I_VolumeRestrictionTM.addDefaultRestriction( account_investor1, - latestTime() + duration.seconds(1), + currentTime.add(new BN(duration.seconds(1))), rollingPeriod, - latestTime() + duration.days(4), + currentTime.add(new BN(duration.days(4))), 0, { from: token_owner } ); - + currentTime = new BN(await latestTime()); console.log("c"); - var txNumber = 10; // define fuzz test amount for tx + var txNumber = 10; // define fuzz test amount for tx for (var j = 0; j < txNumber; j++) { await increaseTime(duration.seconds(5)); + currentTime = new BN(await latestTime()); console.log("2"); // generate a random amount @@ -405,10 +414,11 @@ contract('VolumeRestrictionTransferManager', accounts => { // remove individual restriction and it should fall to default restriction await I_VolumeRestrictionTM.removeIndividualRestriction(account_investor1, {from: token_owner}); console.log("individual restriction now removed --> fall back to default restriction"); - + for (var j=0; j { // check against daily and total restrictions to determine if the transaction should pass or not if (accumulatedTxValue > defaultRestrictionAmount) { console.log("tx should fail"); - await catchRevert( + await catchRevert( I_SecurityToken.transfer(account_investor3, web3.utils.toWei(transactionAmount.toString()), {from: account_investor1}) ); console.log("tx failed as expected due to over limit"); } else if ( accumulatedTxValue <= defaultRestrictionAmount ) { - + console.log("tx should succeed"); await I_SecurityToken.transfer(account_investor3, web3.utils.toWei(transactionAmount.toString()), {from: account_investor1}); @@ -475,8 +485,8 @@ contract('VolumeRestrictionTransferManager', accounts => { let tx = await I_VolumeRestrictionTM.addIndividualDailyRestriction( account_investor1, web3.utils.toWei(dailyRestrictionAmount.toString()), - latestTime() + duration.seconds(startTime), - latestTime() + duration.days(50), + currentTime.add(new BN(duration.seconds(startTime))), + currentTime.add(new BN(duration.days(50))), 0, { from: token_owner @@ -498,15 +508,16 @@ contract('VolumeRestrictionTransferManager', accounts => { // perform multiple transactions for (var j = 0; j < txNumber; j++) { - var timeIncreaseBetweenTx = Math.floor(Math.random() * 10) * 3600; + var timeIncreaseBetweenTx = Math.floor(Math.random() * 10) * 3600; await increaseTime(duration.seconds(timeIncreaseBetweenTx)); + currentTime = new BN(await latestTime()); accumulatedTimeIncrease = timeIncreaseBetweenTx + accumulatedTimeIncrease; console.log("4"); // generate a random amount var transactionAmount = Math.floor(Math.random() * 10); - + // check today's limit var dayNumber = Math.floor(accumulatedTimeIncrease/(24*3600)) + 1; @@ -524,7 +535,7 @@ contract('VolumeRestrictionTransferManager', accounts => { console.log("tx failed as expected due to over limit"); } else if ((todayLimitUsed + transactionAmount) <= dailyRestrictionAmount) { - + console.log("tx should succeed"); await I_SecurityToken.transfer(account_investor3, web3.utils.toWei(transactionAmount.toString()), {from: account_investor1}); @@ -556,13 +567,13 @@ contract('VolumeRestrictionTransferManager', accounts => { for (var i = 0; i < testRepeat; i++) { console.log("fuzzer number " + i); - var individualRestrictTotalAmount = Math.floor(Math.random() * 10); + var individualRestrictTotalAmount = Math.floor(Math.random() * 10); if (individualRestrictTotalAmount === 0 ) { individualRestrictTotalAmount = 1; } - var defaultRestrictionAmount = Math.floor(Math.random() * 10); - var rollingPeriod = 2; - var sumOfLastPeriod = 0; + var defaultRestrictionAmount = Math.floor(Math.random() * 10); + var rollingPeriod = 2; + var sumOfLastPeriod = 0; console.log("a"); @@ -570,9 +581,9 @@ contract('VolumeRestrictionTransferManager', accounts => { let tx = await I_VolumeRestrictionTM.addIndividualRestriction( account_investor1, web3.utils.toWei(individualRestrictTotalAmount.toString()), - latestTime() + duration.seconds(1), + currentTime.add(new BN(duration.seconds(1))), rollingPeriod, - latestTime() + duration.days(3), + currentTime.add(new BN(duration.days(3))), 0, { from: token_owner @@ -587,6 +598,7 @@ contract('VolumeRestrictionTransferManager', accounts => { for (var j = 0; j < txNumber; j++) { await increaseTime(duration.seconds(5)); + currentTime = new BN(await latestTime()); console.log("2"); // generate a random amount @@ -621,27 +633,27 @@ contract('VolumeRestrictionTransferManager', accounts => { }); it("Should work if IR is modified", async () => { - + console.log(`\t\t Starting of the IR modification test case`.blue); - + var testRepeat = 1; for (var i = 0; i < testRepeat; i++) { console.log("\t\t fuzzer number " + i); let precision = 100; - var individualRestrictionTotalAmount = Math.floor(Math.random() * (10 * precision - 1 * precision) + 1 * precision) / (1*precision); - var rollingPeriod = 2; - var sumOfLastPeriod = 0; + var individualRestrictionTotalAmount = Math.floor(Math.random() * (10 * precision - 1 * precision) + 1 * precision) / (1*precision); + var rollingPeriod = 2; + var sumOfLastPeriod = 0; console.log(`\t\t Add individual restriction with TotalAmount: ${individualRestrictionTotalAmount}\n`.green); - + // 1 - add individual restriction with a random number let tx = await I_VolumeRestrictionTM.addIndividualRestriction( account_investor1, web3.utils.toWei(individualRestrictionTotalAmount.toString()), - latestTime() + duration.days(2), + currentTime.add(new BN(duration.days(2))), rollingPeriod, - latestTime() + duration.days(5), + currentTime.add(new BN(duration.days(5))), 0, { from: token_owner @@ -656,30 +668,34 @@ contract('VolumeRestrictionTransferManager', accounts => { console.log(`\t\t Test number: ${j}\n`); // modify IR - var newIR = Math.floor(Math.random() * (10 * precision - 1 * precision) + 1 * precision) / (1*precision); + var newIR = Math.floor(Math.random() * (10 * precision - 1 * precision) + 1 * precision) / (1*precision); + console.log("Original Restriction"); + printIR(await I_VolumeRestrictionTM.getIndividualRestriction(account_investor1, {from: token_owner})); + currentTime = new BN(await latestTime()); + console.log(`\t\t Modification of the IR with new startTime: ${currentTime + duration.days(1+j)} and new total amount: ${newIR} `.green); - printIR(await I_VolumeRestrictionTM.individualRestriction(account_investor1, {from: token_owner})); - - console.log(`\t\t Modification of the IR with new startTime: ${latestTime() + duration.days(1+j)} and new total amount: ${newIR} `.green); - await I_VolumeRestrictionTM.modifyIndividualRestriction( account_investor1, web3.utils.toWei(newIR.toString()), - latestTime() + duration.days(1+j), + currentTime.add(new BN(duration.days(1+j))), rollingPeriod, - latestTime() + duration.days(5+j), + currentTime.add(new BN(duration.days(5+j))), 0, { from: token_owner } ); - + console.log("Modified Restriction"); + printIR(await I_VolumeRestrictionTM.getIndividualRestriction(account_investor1, {from: token_owner})); console.log(`\t\t Successfully IR modified`); - let snapId = await takeSnapshot(); + let snapId = await takeSnapshot(); await increaseTime(duration.days(2+j)); // generate a random amount - var transactionAmount = Math.floor(Math.random() * (10 * precision - 1 * precision) + 1 * precision) / (1*precision); + var transactionAmount = Math.floor(Math.random() * (10 * precision - 1 * precision) + 1 * precision) / (1*precision); // check against daily and total restrictions to determine if the transaction should pass or not + console.log("Transaction Amount: " + transactionAmount); + console.log("newIR Amount: " + newIR); + console.log("currentTime: " + await latestTime()); if (transactionAmount > newIR) { console.log("\t\t Tx should fail"); @@ -707,5 +723,5 @@ contract('VolumeRestrictionTransferManager', accounts => { }); }); - -}); \ No newline at end of file + +}); diff --git a/test/z_general_permission_manager_fuzzer.js b/test/z_general_permission_manager_fuzzer.js index d80b36094..d09ca70b9 100644 --- a/test/z_general_permission_manager_fuzzer.js +++ b/test/z_general_permission_manager_fuzzer.js @@ -1,31 +1,33 @@ -import latestTime from './helpers/latestTime'; -import {signData} from './helpers/signData'; -import { pk } from './helpers/testprivateKey'; -import { duration, promisifyLogWatch, latestBlock } from './helpers/utils'; -import { takeSnapshot, increaseTime, revertToSnapshot } from './helpers/time'; +import latestTime from "./helpers/latestTime"; +import { signData } from "./helpers/signData"; +import { pk } from "./helpers/testprivateKey"; +import { duration, promisifyLogWatch, latestBlock } from "./helpers/utils"; +import { takeSnapshot, increaseTime, revertToSnapshot } from "./helpers/time"; import { catchRevert } from "./helpers/exceptions"; -import { setUpPolymathNetwork, - deployGPMAndVerifyed, - deployCountTMAndVerifyed, - deployLockupVolumeRTMAndVerified, - deployPercentageTMAndVerified, - deployManualApprovalTMAndVerifyed +import { + setUpPolymathNetwork, + deployGPMAndVerifyed, + deployCountTMAndVerifyed, + deployLockUpTMAndVerified, + deployPercentageTMAndVerified, + deployManualApprovalTMAndVerifyed } from "./helpers/createInstances"; import { encodeModuleCall } from "./helpers/encodeCall"; -const SecurityToken = artifacts.require('./SecurityToken.sol'); -const GeneralTransferManager = artifacts.require('./GeneralTransferManager'); -const GeneralPermissionManager = artifacts.require('./GeneralPermissionManager'); +const SecurityToken = artifacts.require("./SecurityToken.sol"); +const GeneralTransferManager = artifacts.require("./GeneralTransferManager"); +const GeneralPermissionManager = artifacts.require("./GeneralPermissionManager"); const CountTransferManager = artifacts.require("./CountTransferManager"); -const PercentageTransferManager = artifacts.require('./PercentageTransferManager'); -const ManualApprovalTransferManager = artifacts.require('./ManualApprovalTransferManager'); +const VolumeRestrictionTransferManager = artifacts.require("./VolumeRestrictionTM"); +const PercentageTransferManager = artifacts.require("./PercentageTransferManager"); +const ManualApprovalTransferManager = artifacts.require("./ManualApprovalTransferManager"); +const STGetter = artifacts.require("./STGetter.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('GeneralPermissionManager', accounts => { +const Web3 = require("web3"); +let BN = Web3.utils.BN; +const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); // Hardcoded development port +contract("GeneralPermissionManager Fuzz", async (accounts) => { // Accounts Variable declaration let account_polymath; let account_issuer; @@ -37,10 +39,6 @@ contract('GeneralPermissionManager', accounts => { let account_investor4; let account_delegate; let account_delegate2; - // investor Details - let fromTime = latestTime(); - let toTime = latestTime(); - let expiryTime = toTime + duration.days(15); let message = "Transaction Should Fail!"; @@ -69,8 +67,16 @@ contract('GeneralPermissionManager', accounts => { let I_PolymathRegistry; let I_CountTransferManagerFactory; let I_CountTransferManager; + let I_SingleTradeVolumeRestrictionManagerFactory; + let I_SingleTradeVolumeRestrictionManager; + let I_SingleTradeVolumeRestrictionPercentageManager; + let P_SingleTradeVolumeRestrictionManager; + let P_SingleTradeVolumeRestrictionManagerFactory; let I_ManualApprovalTransferManagerFactory; let I_ManualApprovalTransferManager; + let I_STRGetter; + let I_STGetter; + let stGetter; // SecurityToken Details const name = "Team"; @@ -78,7 +84,7 @@ contract('GeneralPermissionManager', accounts => { const tokenDetails = "This is equity type of issuance"; const decimals = 18; const contact = "team@polymath.network"; - const delegateDetails = "Hello I am legit delegate"; + const managerDetails = web3.utils.fromAscii("Hello"); const STVRParameters = ["bool", "uint256", "bool"]; // Module key @@ -87,21 +93,26 @@ contract('GeneralPermissionManager', accounts => { const stoKey = 3; // Initial fee for ticker registry and security token registry - const initRegFee = web3.utils.toWei("250"); + const initRegFee = new BN(web3.utils.toWei("1000")); // CountTransferManager details - const holderCount = 2; // Maximum number of token holders - let bytesSTO = encodeModuleCall(["uint256"], [holderCount]); + const holderCount = 2; // Maximum number of token holders + let bytesSTO = encodeModuleCall(["uint256"], [holderCount]); - let _details = "details holding for test"; + let _details = "details holding for test"; + let _description = "some description"; let testRepeat = 20; - // permission manager fuzz test - let perms = ['ADMIN','WHITELIST', 'FLAGS', 'TRANSFER_APPROVAL']; - let totalPerms = perms.length; + // permission manager fuzz test + let perms = ["ADMIN", "WHITELIST", "FLAGS", "TRANSFER_APPROVAL"]; + let totalPerms = perms.length; + + let currentTime; + const address_zero = "0x0000000000000000000000000000000000000000"; + const one_address = "0x0000000000000000000000000000000000000001"; before(async () => { - // Accounts setup + currentTime = new BN(await latestTime()); account_polymath = accounts[0]; account_issuer = accounts[1]; @@ -115,7 +126,6 @@ contract('GeneralPermissionManager', accounts => { account_delegate = accounts[7]; // account_delegate2 = accounts[6]; - // Step 1: Deploy the genral PM ecosystem let instances = await setUpPolymathNetwork(account_polymath, token_owner); @@ -130,22 +140,24 @@ contract('GeneralPermissionManager', accounts => { I_STFactory, I_SecurityTokenRegistry, I_SecurityTokenRegistryProxy, - I_STRProxied + I_STRProxied, + I_STRGetter, + I_STGetter ] = instances; // STEP 5: Deploy the GeneralDelegateManagerFactory - [I_GeneralPermissionManagerFactory] = await deployGPMAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, 0); + [I_GeneralPermissionManagerFactory] = await deployGPMAndVerifyed(account_polymath, I_MRProxied, 0); // STEP 6: Deploy the GeneralDelegateManagerFactory - [P_GeneralPermissionManagerFactory] = await deployGPMAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, web3.utils.toWei("500")); + [P_GeneralPermissionManagerFactory] = await deployGPMAndVerifyed(account_polymath, I_MRProxied, new BN(web3.utils.toWei("500"))); - // Deploy Modules - [I_CountTransferManagerFactory] = await deployCountTMAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, 0); + // Deploy Modules + [I_CountTransferManagerFactory] = await deployCountTMAndVerifyed(account_polymath, I_MRProxied, 0); - [I_VolumeRestrictionTransferManagerFactory] = await deployLockupVolumeRTMAndVerified(account_polymath, I_MRProxied, I_PolyToken.address, 0); + [I_VolumeRestrictionTransferManagerFactory] = await deployLockUpTMAndVerified(account_polymath, I_MRProxied, 0); - [I_PercentageTransferManagerFactory] = await deployPercentageTMAndVerified(account_polymath, I_MRProxied, I_PolyToken.address, 0); + [I_PercentageTransferManagerFactory] = await deployPercentageTMAndVerified(account_polymath, I_MRProxied, 0); - [I_ManualApprovalTransferManagerFactory] = await deployManualApprovalTMAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, 0); + [I_ManualApprovalTransferManagerFactory] = await deployManualApprovalTMAndVerifyed(account_polymath, I_MRProxied, 0); // Printing all the contract addresses console.log(` @@ -167,49 +179,53 @@ contract('GeneralPermissionManager', accounts => { describe("Generate the SecurityToken", async () => { it("Should register the ticker before the generation of the security token", async () => { await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); - let tx = await I_STRProxied.registerTicker(token_owner, symbol, contact, { from: token_owner }); + let tx = await I_STRProxied.registerNewTicker(token_owner, symbol, { from: token_owner }); assert.equal(tx.logs[0].args._owner, token_owner); assert.equal(tx.logs[0].args._ticker, symbol.toUpperCase()); }); it("Should generate the new security token with the same symbol as registered above", async () => { await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); - let _blockNo = latestBlock(); - let tx = await I_STRProxied.generateSecurityToken(name, symbol, tokenDetails, false, { from: token_owner }); + + let tx = await I_STRProxied.generateNewSecurityToken(name, symbol, tokenDetails, false, token_owner, 0, { from: token_owner }); // 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 log = await promisifyLogWatch(I_SecurityToken.ModuleAdded({ from: _blockNo }), 1); + I_SecurityToken = await SecurityToken.at(tx.logs[1].args._securityTokenAddress); + stGetter = await STGetter.at(I_SecurityToken.address); + assert.equal(await stGetter.getTreasuryWallet.call(), token_owner, "Incorrect wallet set"); + const log = (await I_SecurityToken.getPastEvents('ModuleAdded', {filter: {transactionHash: tx.transactionHash}}))[0]; // Verify that GeneralTransferManager module get added successfully or not assert.equal(log.args._types[0].toNumber(), 2); assert.equal(web3.utils.toAscii(log.args._name).replace(/\u0000/g, ""), "GeneralTransferManager"); }); - it("Should intialize the auto attached modules", async () => { - let moduleData = (await I_SecurityToken.getModulesByType(2))[0]; - I_GeneralTransferManager = GeneralTransferManager.at(moduleData); + it("Should initialize the auto attached modules", async () => { + let moduleData = (await stGetter.getModulesByType(2))[0]; + I_GeneralTransferManager = await GeneralTransferManager.at(moduleData); }); it("Should successfully attach the General permission manager factory with the security token -- failed because Token is not paid", async () => { let errorThrown = false; - await I_PolyToken.getTokens(web3.utils.toWei("500", "ether"), token_owner); + await I_PolyToken.getTokens(new BN(web3.utils.toWei("2000", "ether")), token_owner); await catchRevert( - I_SecurityToken.addModule(P_GeneralPermissionManagerFactory.address, "0x", web3.utils.toWei("500", "ether"), 0, { from: token_owner }) + I_SecurityToken.addModule(P_GeneralPermissionManagerFactory.address, "0x", new BN(web3.utils.toWei("2000", "ether")), new BN(0), false, { + from: token_owner + }) ); }); - it("Should successfully attach the General permission manager factory with the security token", async () => { + it("Should successfully attach the General permission manager factory with the security token - paid module", async () => { let snapId = await takeSnapshot(); - await I_PolyToken.transfer(I_SecurityToken.address, web3.utils.toWei("500", "ether"), { from: token_owner }); + await I_PolyToken.transfer(I_SecurityToken.address, new BN(web3.utils.toWei("2000", "ether")), { from: token_owner }); const tx = await I_SecurityToken.addModule( P_GeneralPermissionManagerFactory.address, "0x", - web3.utils.toWei("500", "ether"), - 0, + new BN(web3.utils.toWei("2000", "ether")), + new BN(0), + false, { from: token_owner } ); assert.equal(tx.logs[3].args._types[0].toNumber(), delegateManagerKey, "General Permission Manager doesn't get deployed"); @@ -218,207 +234,246 @@ contract('GeneralPermissionManager', accounts => { "GeneralPermissionManager", "GeneralPermissionManagerFactory module was not added" ); - P_GeneralPermissionManager = GeneralPermissionManager.at(tx.logs[3].args._module); + P_GeneralPermissionManager = await GeneralPermissionManager.at(tx.logs[3].args._module); await revertToSnapshot(snapId); }); - it("Should successfully attach the General permission manager factory with the security token", async () => { - const tx = await I_SecurityToken.addModule(I_GeneralPermissionManagerFactory.address, "0x", 0, 0, { from: token_owner }); + it("Should successfully attach the General permission manager factory with the security token - free module", async () => { + const tx = await I_SecurityToken.addModule(I_GeneralPermissionManagerFactory.address, "0x", new BN(0), new BN(0), false, { from: token_owner }); assert.equal(tx.logs[2].args._types[0].toNumber(), delegateManagerKey, "General Permission Manager doesn't get deployed"); assert.equal( web3.utils.toAscii(tx.logs[2].args._name).replace(/\u0000/g, ""), "GeneralPermissionManager", "GeneralPermissionManagerFactory module was not added" ); - I_GeneralPermissionManager = GeneralPermissionManager.at(tx.logs[2].args._module); + I_GeneralPermissionManager = await GeneralPermissionManager.at(tx.logs[2].args._module); }); }); describe("fuzz test for general transfer manager", async () => { - - it("should pass fuzz test for changeIssuanceAddress(), changeSigningAddress() ", async () => { - + it("should pass fuzz test for changeIssuanceAddress() ", async () => { console.log("1"); // fuzz test loop over total times of testRepeat, inside each loop, we use a variable j to randomly choose an account out of the 10 default accounts for (var i = 2; i < testRepeat; i++) { var j = Math.floor(Math.random() * 10); - if (j === 1 || j === 0) { j = 2 }; // exclude account 1 & 0 because they might come with default perms - + if (j === 1 || j === 0) { + j = 2; + } // exclude account 1 & 0 because they might come with default perms + // add account as a Delegate if it is not - if (await I_GeneralPermissionManager.checkDelegate(accounts[j]) !== true) { - await I_GeneralPermissionManager.addDelegate(accounts[j], _details, { from: token_owner }); + if ((await I_GeneralPermissionManager.checkDelegate(accounts[j])) !== true) { + await I_GeneralPermissionManager.addDelegate(accounts[j], web3.utils.fromAscii(_details), { from: token_owner }); } // target permission should alaways be false for each test before assigning - if (await I_GeneralPermissionManager.checkPermission(accounts[j], I_GeneralTransferManager.address, 'FLAGS') === true) { - await I_GeneralPermissionManager.changePermission(accounts[j], I_GeneralTransferManager.address, 'FLAGS', false, { from: token_owner }); - } else if (await I_GeneralPermissionManager.checkPermission(accounts[j], I_GeneralTransferManager.address, 'WHITELIST') === true) { - await I_GeneralPermissionManager.changePermission(accounts[j], I_GeneralTransferManager.address, 'WHITELIST', false, { from: token_owner }); + if ((await I_GeneralPermissionManager.checkPermission(accounts[j], I_GeneralTransferManager.address, web3.utils.fromAscii("ADMIN"))) === true) { + await I_GeneralPermissionManager.changePermission(accounts[j], I_GeneralTransferManager.address, web3.utils.fromAscii("ADMIN"), false, { + from: token_owner + }); + } else if ( + (await I_GeneralPermissionManager.checkPermission(accounts[j], I_GeneralTransferManager.address, web3.utils.fromAscii("ADMIN"))) === true + ) { + await I_GeneralPermissionManager.changePermission(accounts[j], I_GeneralTransferManager.address, web3.utils.fromAscii("ADMIN"), false, { + from: token_owner + }); } // assign a random perm let randomPerms = perms[Math.floor(Math.random() * Math.floor(totalPerms))]; - let fromTime = latestTime(); - let toTime = latestTime() + duration.days(20); + let fromTime = await latestTime(); + let toTime = await latestTime() + duration.days(20); let expiryTime = toTime + duration.days(10); - await I_GeneralPermissionManager.changePermission(accounts[j], I_GeneralTransferManager.address, randomPerms, true, { from: token_owner }); + await I_GeneralPermissionManager.changePermission(accounts[j], I_GeneralTransferManager.address, web3.utils.fromAscii(randomPerms), true, { + from: token_owner + }); - let currentAllowAllTransferStats = await I_GeneralTransferManager.allowAllTransfers(); - let currentAllowAllWhitelistTransfersStats = await I_GeneralTransferManager.allowAllWhitelistTransfers(); - let currentAllowAllWhitelistIssuancesStats = await I_GeneralTransferManager.allowAllWhitelistIssuances(); - let currentAllowAllBurnTransfersStats = await I_GeneralTransferManager.allowAllBurnTransfers(); console.log("2"); // let userPerm = await I_GeneralPermissionManager.checkPermission(accounts[j], I_GeneralTransferManager.address, 'FLAGS'); - if (randomPerms === 'FLAGS') { - console.log("Test number " + i + " with account " + j + " and perm " + randomPerms + " about to start") - await I_GeneralTransferManager.changeIssuanceAddress( accounts[j], { from: accounts[j] }); + if (randomPerms === "ADMIN") { + console.log("Test number " + i + " with account " + j + " and perm " + randomPerms + " about to start"); + await I_GeneralTransferManager.changeIssuanceAddress(accounts[j], { from: accounts[j] }); assert.equal(await I_GeneralTransferManager.issuanceAddress(), accounts[j]); - await I_GeneralTransferManager.changeSigningAddress( accounts[j], { from: accounts[j] }); - assert.equal(await I_GeneralTransferManager.signingAddress(), accounts[j]); - - await I_GeneralTransferManager.changeAllowAllTransfers(!currentAllowAllTransferStats, { from: accounts[j] }); - assert.equal(await I_GeneralTransferManager.allowAllTransfers(), !currentAllowAllTransferStats); - - await I_GeneralTransferManager.changeAllowAllWhitelistTransfers(!currentAllowAllWhitelistTransfersStats, { from: accounts[j] }); - assert.equal(await I_GeneralTransferManager.allowAllWhitelistTransfers(), !currentAllowAllWhitelistTransfersStats); - - await I_GeneralTransferManager.changeAllowAllWhitelistIssuances(!currentAllowAllWhitelistIssuancesStats, { from: accounts[j] }); - assert.equal(await I_GeneralTransferManager.allowAllWhitelistIssuances(), !currentAllowAllWhitelistIssuancesStats); - - await I_GeneralTransferManager.changeAllowAllBurnTransfers(!currentAllowAllBurnTransfersStats, { from: accounts[j] }); - assert.equal(await I_GeneralTransferManager.allowAllBurnTransfers(), !currentAllowAllBurnTransfersStats); - - console.log("Test number " + i + " with account " + j + " and perm " + randomPerms + " for functions with require perm FLAGS passed") + console.log( + "Test number " + + i + + " with account " + + j + + " and perm " + + randomPerms + + " for functions with require perm FLAGS passed" + ); } else { - await catchRevert(I_GeneralTransferManager.changeIssuanceAddress( accounts[j], { from: accounts[j] })); - await catchRevert(I_GeneralTransferManager.changeSigningAddress( accounts[j], { from: accounts[j] })); - await catchRevert(I_GeneralTransferManager.changeAllowAllTransfers( !currentAllowAllTransferStats, { from: accounts[j] })); - await catchRevert(I_GeneralTransferManager.changeAllowAllWhitelistTransfers( !currentAllowAllWhitelistTransfersStats, { from: accounts[j] })); - await catchRevert(I_GeneralTransferManager.changeAllowAllWhitelistIssuances( !currentAllowAllWhitelistIssuancesStats, { from: accounts[j] })); - await catchRevert(I_GeneralTransferManager.changeAllowAllBurnTransfers( !currentAllowAllBurnTransfersStats, { from: accounts[j] })); - console.log("Test number " + i + " with account " + j + " and perm " + randomPerms + " for functions require perm FLAGS failed as expected"); + await catchRevert(I_GeneralTransferManager.changeIssuanceAddress(accounts[j], { from: accounts[j] })); + console.log( + "Test number " + + i + + " with account " + + j + + " and perm " + + randomPerms + + " for functions require perm FLAGS failed as expected" + ); } console.log("3"); - if (randomPerms === 'WHITELIST') { - let tx = await I_GeneralTransferManager.modifyWhitelist(accounts[j], fromTime, toTime, expiryTime, 1, { from: accounts[j] }); + if (randomPerms === "ADMIN") { + let tx = await I_GeneralTransferManager.modifyKYCData(accounts[j], fromTime, toTime, expiryTime, { + from: accounts[j] + }); assert.equal(tx.logs[0].args._investor, accounts[j]); console.log("3.1"); - let tx2 = await I_GeneralTransferManager.modifyWhitelistMulti([accounts[3], accounts[4]], [fromTime, fromTime], [toTime, toTime], [expiryTime, expiryTime], [1, 1], { from: accounts[j] }); + let tx2 = await I_GeneralTransferManager.modifyKYCDataMulti( + [accounts[3], accounts[4]], + [fromTime, fromTime], + [toTime, toTime], + [expiryTime, expiryTime], + { from: accounts[j] } + ); console.log(tx2.logs[1].args); assert.equal(tx2.logs[1].args._investor, accounts[4]); console.log("3.2"); } else { console.log("3.3"); - await catchRevert(I_GeneralTransferManager.modifyWhitelist(accounts[j], fromTime, toTime, expiryTime, 1, { from: accounts[j] })); + await catchRevert( + I_GeneralTransferManager.modifyKYCData(accounts[j], fromTime, toTime, expiryTime, { from: accounts[j] }) + ); console.log("3.4"); - await catchRevert(I_GeneralTransferManager.modifyWhitelistMulti([accounts[3], accounts[4]], [fromTime, fromTime], [toTime, toTime], [expiryTime, expiryTime], [1, 1], { from: accounts[j] })); + await catchRevert( + I_GeneralTransferManager.modifyKYCDataMulti( + [accounts[3], accounts[4]], + [fromTime, fromTime], + [toTime, toTime], + [expiryTime, expiryTime], + { from: accounts[j] } + ) + ); console.log("3.5"); } } console.log("4"); await I_GeneralTransferManager.changeIssuanceAddress("0x0000000000000000000000000000000000000000", { from: token_owner }); - }) + }); }); - describe("fuzz test for count transfer manager", async () => { - - it("Should successfully attach the CountTransferManager with the security token", async () => { - const tx = await I_SecurityToken.addModule(I_CountTransferManagerFactory.address, bytesSTO, 0, 0, { from: token_owner }); + describe("fuzz test for count transfer manager", async () => { + it("Should successfully attach the CountTransferManager with the security token", async () => { + const tx = await I_SecurityToken.addModule(I_CountTransferManagerFactory.address, bytesSTO, new BN(0), new BN(0), false, { from: token_owner }); assert.equal(tx.logs[2].args._types[0].toNumber(), transferManagerKey, "CountTransferManager doesn't get deployed"); assert.equal( web3.utils.toAscii(tx.logs[2].args._name).replace(/\u0000/g, ""), "CountTransferManager", "CountTransferManager module was not added" ); - I_CountTransferManager = CountTransferManager.at(tx.logs[2].args._module); + I_CountTransferManager = await CountTransferManager.at(tx.logs[2].args._module); }); it("should pass fuzz test for changeHolderCount()", async () => { // fuzz test loop over total times of testRepeat, inside each loop, we use a variable j to randomly choose an account out of the 10 default accounts - for (var i = 2; i < testRepeat; i++) { - var j = Math.floor(Math.random() * 10); - if (j === 1 || j === 0) { j = 2 }; // exclude account 1 & 0 because they might come with default perms - + for (var i = 2; i < testRepeat; i++) { + var j = Math.floor(Math.random() * 10); + if (j === 1 || j === 0) { + j = 2; + } // exclude account 1 & 0 because they might come with default perms + // add account as a Delegate if it is not - if (await I_GeneralPermissionManager.checkDelegate(accounts[j]) !== true) { - await I_GeneralPermissionManager.addDelegate(accounts[j], _details, { from: token_owner }); - } + if ((await I_GeneralPermissionManager.checkDelegate(accounts[j])) !== true) { + await I_GeneralPermissionManager.addDelegate(accounts[j], web3.utils.fromAscii(_details), { from: token_owner }); + } // target permission should alaways be false for each test before assigning - if (await I_GeneralPermissionManager.checkPermission(accounts[j], I_CountTransferManager.address, 'ADMIN') === true) { - await I_GeneralPermissionManager.changePermission(accounts[j], I_CountTransferManager.address, 'ADMIN', false, { from: token_owner }); + if ((await I_GeneralPermissionManager.checkPermission(accounts[j], I_CountTransferManager.address, web3.utils.fromAscii("ADMIN"))) === true) { + await I_GeneralPermissionManager.changePermission(accounts[j], I_CountTransferManager.address, web3.utils.fromAscii("ADMIN"), false, { + from: token_owner + }); } - // assign a random perm + // assign a random perm let randomPerms = perms[Math.floor(Math.random() * Math.floor(totalPerms))]; - await I_GeneralPermissionManager.changePermission(accounts[j], I_CountTransferManager.address, randomPerms, true, { from: token_owner }); - if (randomPerms === 'ADMIN') { + await I_GeneralPermissionManager.changePermission(accounts[j], I_CountTransferManager.address, web3.utils.fromAscii(randomPerms), true, { + from: token_owner + }); + if (randomPerms === "ADMIN") { // console.log("Test number " + i + " with account " + j + " and perm " + randomPerms + " should pass"); - await I_CountTransferManager.changeHolderCount(i + 1, { from: accounts[j] }); - assert.equal((await I_CountTransferManager.maxHolderCount()).toNumber(), i + 1); - console.log("Test number " + i + " with account " + j + " and perm " + randomPerms + " passed"); - } else { + await I_CountTransferManager.changeHolderCount(i + 1, { from: accounts[j] }); + assert.equal((await I_CountTransferManager.maxHolderCount()).toNumber(), i + 1); + console.log("Test number " + i + " with account " + j + " and perm " + randomPerms + " passed"); + } else { // console.log("Test number " + i + " with account " + j + " and perm " + randomPerms + " should failed"); - await catchRevert(I_CountTransferManager.changeHolderCount(i+1, { from: accounts[j] })); - console.log("Test number " + i + " with account " + j + " and perm " + randomPerms + " failed as expected"); - } - } + await catchRevert(I_CountTransferManager.changeHolderCount(i + 1, { from: accounts[j] })); + console.log("Test number " + i + " with account " + j + " and perm " + randomPerms + " failed as expected"); + } + } }); - }); - + }); describe("fuzz test for percentage transfer manager", async () => { - // PercentageTransferManager details - const holderPercentage = 70 * 10**16; // Maximum number of token holders - - let bytesSTO = web3.eth.abi.encodeFunctionCall({ - name: 'configure', - type: 'function', - inputs: [{ - type: 'uint256', - name: '_maxHolderPercentage' - },{ - type: 'bool', - name: '_allowPrimaryIssuance' - } - ] - }, [holderPercentage, false]); - - it("Should successfully attach the percentage transfer manager with the security token", async () => { + const holderPercentage = 70 * 10 ** 16; // Maximum number of token holders + + let bytesSTO = web3.eth.abi.encodeFunctionCall( + { + name: "configure", + type: "function", + inputs: [ + { + type: "uint256", + name: "_maxHolderPercentage" + }, + { + type: "bool", + name: "_allowPrimaryIssuance" + } + ] + }, + [holderPercentage, false] + ); + + it("Should successfully attach the percentage transfer manager with the security token", async () => { console.log("1"); - const tx = await I_SecurityToken.addModule(I_PercentageTransferManagerFactory.address, bytesSTO, 0, 0, { from: token_owner }); - I_PercentageTransferManager = PercentageTransferManager.at(tx.logs[2].args._module); + const tx = await I_SecurityToken.addModule(I_PercentageTransferManagerFactory.address, bytesSTO, new BN(0), new BN(0), false, { from: token_owner }); + I_PercentageTransferManager = await PercentageTransferManager.at(tx.logs[2].args._module); }); - it("should pass fuzz test for modifyWhitelist with perm WHITELIST", async () => { + it("should pass fuzz test for modifyWhitelist with perm ADMIN", async () => { // fuzz test loop over total times of testRepeat, inside each loop, we use a variable j to randomly choose an account out of the 10 default accounts for (var i = 2; i < testRepeat; i++) { var j = Math.floor(Math.random() * 10); - if (j === 1 || j === 0) { j = 2 }; // exclude account 1 & 0 because they might come with default perms - + if (j === 1 || j === 0) { + j = 2; + } // exclude account 1 & 0 because they might come with default perms + // add account as a Delegate if it is not - if (await I_GeneralPermissionManager.checkDelegate(accounts[j]) !== true) { - await I_GeneralPermissionManager.addDelegate(accounts[j], _details, { from: token_owner }); + if ((await I_GeneralPermissionManager.checkDelegate(accounts[j])) !== true) { + await I_GeneralPermissionManager.addDelegate(accounts[j], web3.utils.fromAscii(_details), { from: token_owner }); } // target permission should alaways be false for each test before assigning - if (await I_GeneralPermissionManager.checkPermission(accounts[j], I_PercentageTransferManager.address, 'WHITELIST') === true) { - await I_GeneralPermissionManager.changePermission(accounts[j], I_PercentageTransferManager.address, 'WHITELIST', false, { from: token_owner }); + if ( + (await I_GeneralPermissionManager.checkPermission(accounts[j], I_PercentageTransferManager.address, web3.utils.fromAscii("ADMIN"))) === + true + ) { + await I_GeneralPermissionManager.changePermission( + accounts[j], + I_PercentageTransferManager.address, + web3.utils.fromAscii("ADMIN"), + false, + { from: token_owner } + ); } // assign a random perm let randomPerms = perms[Math.floor(Math.random() * Math.floor(totalPerms))]; - await I_GeneralPermissionManager.changePermission(accounts[j], I_PercentageTransferManager.address, randomPerms, true, { from: token_owner }); + await I_GeneralPermissionManager.changePermission(accounts[j], I_PercentageTransferManager.address, web3.utils.fromAscii(randomPerms), true, { + from: token_owner + }); //try add multi lock ups - if (randomPerms === 'WHITELIST') { + if (randomPerms === "ADMIN") { // console.log("Test number " + i + " with account " + j + " and perm " + randomPerms + " should pass"); await I_PercentageTransferManager.modifyWhitelist(account_investor3, 1, { from: accounts[j] }); - console.log("Test number " + i + " with account " + j + " and perm WHITELIST passed as expected"); + console.log("Test number " + i + " with account " + j + " and perm ADMIN passed as expected"); } else { // console.log("Test number " + i + " with account " + j + " and perm " + randomPerms + " should failed"); await catchRevert(I_PercentageTransferManager.modifyWhitelist(account_investor3, 1, { from: accounts[j] })); @@ -427,72 +482,95 @@ contract('GeneralPermissionManager', accounts => { } }); - it("should pass fuzz test for modifyWhitelistMulti with perm WHITELIST", async () => { + it("should pass fuzz test for modifyWhitelistMulti with perm ADMIN", async () => { // fuzz test loop over total times of testRepeat, inside each loop, we use a variable j to randomly choose an account out of the 10 default accounts - for (var i = 2; i < testRepeat; i++) { + for (var i = 2; i < testRepeat; i++) { var j = Math.floor(Math.random() * 10); - if (j === 1 || j === 0) { j = 2 }; // exclude account 1 & 0 because they might come with default perms - + if (j === 1 || j === 0) { + j = 2; + } // exclude account 1 & 0 because they might come with default perms + // add account as a Delegate if it is not - if (await I_GeneralPermissionManager.checkDelegate(accounts[j]) !== true) { - await I_GeneralPermissionManager.addDelegate(accounts[j], _details, { from: token_owner }); + if ((await I_GeneralPermissionManager.checkDelegate(accounts[j])) !== true) { + await I_GeneralPermissionManager.addDelegate(accounts[j], web3.utils.fromAscii(_details), { from: token_owner }); } // target permission should alaways be false for each test before assigning - if (await I_GeneralPermissionManager.checkPermission(accounts[j], I_PercentageTransferManager.address, 'WHITELIST') === true) { - await I_GeneralPermissionManager.changePermission(accounts[j], I_PercentageTransferManager.address, 'WHITELIST', false, { from: token_owner }); + if ( + (await I_GeneralPermissionManager.checkPermission(accounts[j], I_PercentageTransferManager.address, web3.utils.fromAscii("ADMIN"))) === + true + ) { + await I_GeneralPermissionManager.changePermission( + accounts[j], + I_PercentageTransferManager.address, + web3.utils.fromAscii("ADMIN"), + false, + { from: token_owner } + ); } // assign a random perm let randomPerms = perms[Math.floor(Math.random() * Math.floor(totalPerms))]; - await I_GeneralPermissionManager.changePermission(accounts[j], I_PercentageTransferManager.address, randomPerms, true, { from: token_owner }); - - if (randomPerms === 'WHITELIST') { - // console.log("Test number " + i + " with account " + j + " and perm " + randomPerms + " should pass"); - await I_PercentageTransferManager.modifyWhitelistMulti([account_investor3, account_investor4], [0, 1], { from: accounts[j] }); - console.log("Test number " + i + " with account " + j + " and perm WHITELIST passed as expected"); + await I_GeneralPermissionManager.changePermission(accounts[j], I_PercentageTransferManager.address, web3.utils.fromAscii(randomPerms), true, { + from: token_owner + }); + if (randomPerms === "ADMIN") { + // console.log("Test number " + i + " with account " + j + " and perm " + randomPerms + " should pass"); + await I_PercentageTransferManager.modifyWhitelistMulti([account_investor3, account_investor4], [0, 1], { + from: accounts[j] + }); + console.log("Test number " + i + " with account " + j + " and perm ADMIN passed as expected"); } else { // console.log("Test number " + i + " with account " + j + " and perm " + randomPerms + " should failed"); - await catchRevert( I_PercentageTransferManager.modifyWhitelistMulti([account_investor3, account_investor4], [0, 1], { from: accounts[j] })); + await catchRevert( + I_PercentageTransferManager.modifyWhitelistMulti([account_investor3, account_investor4], [0, 1], { + from: accounts[j] + }) + ); console.log("Test number " + i + " with account " + j + " and perm " + randomPerms + " failed as expected"); } } }); it("should pass fuzz test for setAllowPrimaryIssuance with perm ADMIN", async () => { - - // let snapId = await takeSnapshot(); + // let snapId = await takeSnapshot(); // fuzz test loop over total times of testRepeat, inside each loop, we use a variable j to randomly choose an account out of the 10 default accounts - for (var i = 2; i < testRepeat; i++) { - + for (var i = 2; i < testRepeat; i++) { var j = Math.floor(Math.random() * 10); - if (j === 1 || j === 0) { j = 2 }; // exclude account 1 & 0 because they might come with default perms - + if (j === 1 || j === 0) { + j = 2; + } // exclude account 1 & 0 because they might come with default perms + // add account as a Delegate if it is not - if (await I_GeneralPermissionManager.checkDelegate(accounts[j]) !== true) { - await I_GeneralPermissionManager.addDelegate(accounts[j], _details, { from: token_owner }); + if ((await I_GeneralPermissionManager.checkDelegate(accounts[j])) !== true) { + await I_GeneralPermissionManager.addDelegate(accounts[j], web3.utils.fromAscii(_details), { from: token_owner }); } // target permission should alaways be false for each test before assigning - if (await I_GeneralPermissionManager.checkPermission(accounts[j], I_PercentageTransferManager.address, 'ADMIN') === true) { - await I_GeneralPermissionManager.changePermission(accounts[j], I_PercentageTransferManager.address, 'ADMIN', false, { from: token_owner }); + if ( + (await I_GeneralPermissionManager.checkPermission(accounts[j], I_PercentageTransferManager.address, web3.utils.fromAscii("ADMIN"))) === true + ) { + await I_GeneralPermissionManager.changePermission(accounts[j], I_PercentageTransferManager.address, web3.utils.fromAscii("ADMIN"), false, { + from: token_owner + }); } // assign a random perm let randomPerms = perms[Math.floor(Math.random() * Math.floor(totalPerms))]; - await I_GeneralPermissionManager.changePermission(accounts[j], I_PercentageTransferManager.address, randomPerms, true, { from: token_owner }); + await I_GeneralPermissionManager.changePermission(accounts[j], I_PercentageTransferManager.address, web3.utils.fromAscii(randomPerms), true, { + from: token_owner + }); let primaryIssuanceStat = await I_PercentageTransferManager.allowPrimaryIssuance({ from: token_owner }); - - if (randomPerms === 'ADMIN') { - console.log("Test number " + i + " with account " + j + " and perm " + randomPerms + " should pass"); - await I_PercentageTransferManager.setAllowPrimaryIssuance(!primaryIssuanceStat, { from: accounts[j] }); - console.log("Test number " + i + " with account " + j + " and perm ADMIN passed as expected"); + if (randomPerms === "ADMIN") { + console.log("Test number " + i + " with account " + j + " and perm " + randomPerms + " should pass"); + await I_PercentageTransferManager.setAllowPrimaryIssuance(!primaryIssuanceStat, { from: accounts[j] }); + console.log("Test number " + i + " with account " + j + " and perm ADMIN passed as expected"); } else { console.log("Test number " + i + " with account " + j + " and perm " + randomPerms + " should failed"); - await catchRevert( I_PercentageTransferManager.setAllowPrimaryIssuance(!primaryIssuanceStat, { from: accounts[j] })); + await catchRevert(I_PercentageTransferManager.setAllowPrimaryIssuance(!primaryIssuanceStat, { from: accounts[j] })); console.log("Test number " + i + " with account " + j + " and perm " + randomPerms + " failed as expected"); } // await revertToSnapshot(snapId); @@ -500,57 +578,69 @@ contract('GeneralPermissionManager', accounts => { }); }); - describe("fuzz test for manual approval transfer manager", async () => { - it("Should successfully attach the ManualApprovalTransferManager with the security token", async () => { - const tx = await I_SecurityToken.addModule(I_ManualApprovalTransferManagerFactory.address, "", 0, 0, { from: token_owner }); + const tx = await I_SecurityToken.addModule(I_ManualApprovalTransferManagerFactory.address, "0x0", new BN(0), new BN(0), false, { from: token_owner }); assert.equal(tx.logs[2].args._types[0].toNumber(), transferManagerKey, "ManualApprovalTransferManager doesn't get deployed"); assert.equal( web3.utils.toUtf8(tx.logs[2].args._name), "ManualApprovalTransferManager", "ManualApprovalTransferManager module was not added" ); - I_ManualApprovalTransferManager = ManualApprovalTransferManager.at(tx.logs[2].args._module); + I_ManualApprovalTransferManager = await ManualApprovalTransferManager.at(tx.logs[2].args._module); }); - it("should pass fuzz test for addManualApproval & revokeManualApproval with perm TRANSFER_APPROVAL", async () => { - + it("should pass fuzz test for addManualApproval & revokeManualApproval with perm ADMIN", async () => { let tx; // fuzz test loop over total times of testRepeat, inside each loop, we use a variable j to randomly choose an account out of the 10 default accounts for (var i = 2; i < testRepeat; i++) { - let snapId = await takeSnapshot(); - var j = Math.floor(Math.random() * 10); - if (j === 1 || j === 0) { j = 2 }; // exclude account 1 & 0 because they might come with default perms - + if (j === 1 || j === 0) { + j = 2; + } // exclude account 1 & 0 because they might come with default perms + // add account as a Delegate if it is not - if (await I_GeneralPermissionManager.checkDelegate(accounts[j]) !== true) { - await I_GeneralPermissionManager.addDelegate(accounts[j], _details, { from: token_owner }); + if ((await I_GeneralPermissionManager.checkDelegate(accounts[j])) !== true) { + await I_GeneralPermissionManager.addDelegate(accounts[j], web3.utils.fromAscii(_details), { from: token_owner }); } // target permission should alaways be false for each test before assigning - if (await I_GeneralPermissionManager.checkPermission(accounts[j], I_ManualApprovalTransferManager.address, 'TRANSFER_APPROVAL') === true) { - await I_GeneralPermissionManager.changePermission(accounts[j], I_ManualApprovalTransferManager.address, 'TRANSFER_APPROVAL', false, { from: token_owner }); + if ( + (await I_GeneralPermissionManager.checkPermission( + accounts[j], + I_ManualApprovalTransferManager.address, + web3.utils.fromAscii("ADMIN") + )) === true + ) { + await I_GeneralPermissionManager.changePermission( + accounts[j], + I_ManualApprovalTransferManager.address, + web3.utils.fromAscii("ADMIN"), + false, + { from: token_owner } + ); } // assign a random perm let randomPerms = perms[Math.floor(Math.random() * Math.floor(totalPerms))]; - await I_GeneralPermissionManager.changePermission(accounts[j], I_ManualApprovalTransferManager.address, randomPerms, true, { from: token_owner }); - - if (randomPerms === "TRANSFER_APPROVAL" ) { - console.log("Test number " + i + " with account " + j + " and perm TRANSFER_APPROVAL " + " should pass"); + await I_GeneralPermissionManager.changePermission(accounts[j], I_ManualApprovalTransferManager.address, web3.utils.fromAscii(randomPerms), true, { + from: token_owner + }); + + if (randomPerms === "ADMIN") { + console.log("Test number " + i + " with account " + j + " and perm ADMIN " + " should pass"); + let nextTime = await latestTime() + duration.days(1); await I_ManualApprovalTransferManager.addManualApproval( account_investor1, account_investor4, - web3.utils.toWei("2", "ether"), - latestTime() + duration.days(1), - "ABC", + new BN(web3.utils.toWei("2", "ether")), + nextTime, + web3.utils.fromAscii(_details), { from: accounts[j] } ); - + console.log("2"); tx = await I_ManualApprovalTransferManager.revokeManualApproval(account_investor1, account_investor4, { from: accounts[j] @@ -558,43 +648,44 @@ contract('GeneralPermissionManager', accounts => { assert.equal(tx.logs[0].args._from, account_investor1); assert.equal(tx.logs[0].args._to, account_investor4); assert.equal(tx.logs[0].args._addedBy, accounts[j]); - + console.log("3"); - console.log("Test number " + i + " with account " + j + " and perm TRANSFER_APPROVAL passed as expected"); + console.log("Test number " + i + " with account " + j + " and perm ADMIN passed as expected"); } else { console.log("Test number " + i + " with account " + j + " and perm " + randomPerms + " should failed"); + let nextTime = await latestTime() + duration.days(1); await catchRevert( I_ManualApprovalTransferManager.addManualApproval( account_investor1, account_investor4, - web3.utils.toWei("2", "ether"), - latestTime() + duration.days(1), - "ABC", + new BN(web3.utils.toWei("2", "ether")), + nextTime, + web3.utils.fromAscii(_details), { from: accounts[j] } ) ); + nextTime = await latestTime() + duration.days(1); await I_ManualApprovalTransferManager.addManualApproval( account_investor1, account_investor4, - web3.utils.toWei("2", "ether"), - latestTime() + duration.days(1), - "ABC", + new BN(web3.utils.toWei("2", "ether")), + nextTime, + web3.utils.fromAscii(_details), { from: token_owner } ); - await catchRevert(I_ManualApprovalTransferManager.revokeManualApproval(account_investor1, account_investor4, { - from: accounts[j] - }) + await catchRevert( + I_ManualApprovalTransferManager.revokeManualApproval(account_investor1, account_investor4, { + from: accounts[j] + }) ); - console.log("Test number " + i + " with account " + j + " and perm " + randomPerms + " failed as expected"); } await revertToSnapshot(snapId); - }; + } }); }); - }); diff --git a/test/z_vesting_escrow_wallet.js b/test/z_vesting_escrow_wallet.js index d15acddd1..573963162 100644 --- a/test/z_vesting_escrow_wallet.js +++ b/test/z_vesting_escrow_wallet.js @@ -9,9 +9,9 @@ const SecurityToken = artifacts.require('./SecurityToken.sol'); const GeneralTransferManager = artifacts.require('./GeneralTransferManager'); const GeneralPermissionManager = artifacts.require("./GeneralPermissionManager"); const VestingEscrowWallet = artifacts.require('./VestingEscrowWallet.sol'); - +const STGetter = artifacts.require("./STGetter.sol"); const Web3 = require('web3'); -const BigNumber = require('bignumber.js'); +const BN = Web3.utils.BN; const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));// Hardcoded development port contract('VestingEscrowWallet', accounts => { @@ -27,6 +27,7 @@ contract('VestingEscrowWallet', accounts => { let account_beneficiary1; let account_beneficiary2; let account_beneficiary3; + let wallet_operator; let beneficiaries; @@ -50,6 +51,8 @@ contract('VestingEscrowWallet', accounts => { let I_SecurityToken; let I_PolyToken; let I_PolymathRegistry; + let I_STGetter; + let stGetter; // SecurityToken Details const name = "Team"; @@ -57,7 +60,7 @@ contract('VestingEscrowWallet', accounts => { const tokenDetails = "This is equity type of issuance"; const decimals = 18; const contact = "team@polymath.network"; - const delegateDetails = "Hello I am legit delegate"; + const delegateDetails = web3.utils.toHex("Hello I am legit delegate"); // Module key const delegateManagerKey = 1; @@ -65,13 +68,18 @@ contract('VestingEscrowWallet', accounts => { const stoKey = 3; // Initial fee for ticker registry and security token registry - const initRegFee = web3.utils.toWei("250"); + const initRegFee = new BN(web3.utils.toWei("1000")); + + let currentTime; + const address_zero = "0x0000000000000000000000000000000000000000"; before(async () => { + currentTime = new BN(await latestTime()); // Accounts setup account_polymath = accounts[0]; token_owner = accounts[1]; wallet_admin = accounts[2]; + wallet_operator = accounts[3]; account_beneficiary1 = accounts[6]; account_beneficiary2 = accounts[7]; @@ -97,14 +105,15 @@ contract('VestingEscrowWallet', accounts => { I_STFactory, I_SecurityTokenRegistry, I_SecurityTokenRegistryProxy, - I_STRProxied + I_STRProxied, + I_STGetter ] = instances; // STEP 2: Deploy the GeneralDelegateManagerFactory - [I_GeneralPermissionManagerFactory] = await deployGPMAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, 0); + [I_GeneralPermissionManagerFactory] = await deployGPMAndVerifyed(account_polymath, I_MRProxied, 0); // STEP 3: Deploy the VestingEscrowWallet - [I_VestingEscrowWalletFactory] = await deployVestingEscrowWalletAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, 0); + [I_VestingEscrowWalletFactory] = await deployVestingEscrowWalletAndVerifyed(account_polymath, I_MRProxied, 0); // Printing all the contract addresses console.log(` @@ -119,7 +128,7 @@ contract('VestingEscrowWallet', accounts => { STFactory: ${I_STFactory.address} GeneralTransferManagerFactory: ${I_GeneralTransferManagerFactory.address} GeneralPermissionManagerFactory: ${I_GeneralPermissionManagerFactory.address} - + I_VestingEscrowWalletFactory: ${I_VestingEscrowWalletFactory.address} ----------------------------------------------------------------------------- `); @@ -129,88 +138,96 @@ contract('VestingEscrowWallet', accounts => { it("Should register the ticker before the generation of the security token", async () => { await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); - let tx = await I_STRProxied.registerTicker(token_owner, symbol, contact, { from : token_owner }); + let tx = await I_STRProxied.registerNewTicker(token_owner, symbol, { from : token_owner }); assert.equal(tx.logs[0].args._owner, token_owner); assert.equal(tx.logs[0].args._ticker, symbol.toUpperCase()); }); it("Should generate the new security token with the same symbol as registered above", async () => { await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); - let _blockNo = latestBlock(); - let tx = await I_STRProxied.generateSecurityToken(name, symbol, tokenDetails, false, { from: token_owner }); + + let tx = await I_STRProxied.generateNewSecurityToken(name, symbol, tokenDetails, false, token_owner, 0, { from: token_owner }); // 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 log = await promisifyLogWatch(I_SecurityToken.ModuleAdded({from: _blockNo}), 1); + I_SecurityToken = await SecurityToken.at(tx.logs[1].args._securityTokenAddress); + stGetter = await STGetter.at(I_SecurityToken.address); + assert.equal(await stGetter.getTreasuryWallet.call(), token_owner, "Incorrect wallet set"); + const log = (await I_SecurityToken.getPastEvents('ModuleAdded', {filter: {transactionHash: tx.transactionHash}}))[0]; // Verify that GeneralTransferManager module get added successfully or not - assert.equal(log.args._types[0].toNumber(), 2); - assert.equal( - web3.utils.toAscii(log.args._name) - .replace(/\u0000/g, ''), - "GeneralTransferManager" - ); + assert.equal(log.args._types[0].toString(), 2); + assert.equal(web3.utils.toAscii(log.args._name).replace(/\u0000/g, ""), "GeneralTransferManager"); }); - it("Should intialize the auto attached modules", async () => { - let moduleData = (await I_SecurityToken.getModulesByType(2))[0]; - I_GeneralTransferManager = GeneralTransferManager.at(moduleData); + it("Should initialize the auto attached modules", async () => { + let moduleData = (await stGetter.getModulesByType(2))[0]; + I_GeneralTransferManager = await GeneralTransferManager.at(moduleData); }); it("Should successfully attach the General permission manager factory with the security token", async () => { - const tx = await I_SecurityToken.addModule(I_GeneralPermissionManagerFactory.address, "0x", 0, 0, { from: token_owner }); - assert.equal(tx.logs[2].args._types[0].toNumber(), delegateManagerKey, "General Permission Manager doesn't get deployed"); + const tx = await I_SecurityToken.addModule(I_GeneralPermissionManagerFactory.address, "0x", new BN(0), new BN(0), false, { from: token_owner }); + assert.equal(tx.logs[2].args._types[0].toString(), delegateManagerKey, "General Permission Manager doesn't get deployed"); assert.equal( web3.utils.toAscii(tx.logs[2].args._name).replace(/\u0000/g, ""), "GeneralPermissionManager", "GeneralPermissionManagerFactory module was not added" ); - I_GeneralPermissionManager = GeneralPermissionManager.at(tx.logs[2].args._module); + I_GeneralPermissionManager = await GeneralPermissionManager.at(tx.logs[2].args._module); }); it("Should successfully attach the VestingEscrowWallet with the security token", async () => { let bytesData = encodeModuleCall( ["address"], - [token_owner] + [address_zero] ); await I_SecurityToken.changeGranularity(1, {from: token_owner}); - const tx = await I_SecurityToken.addModule(I_VestingEscrowWalletFactory.address, bytesData, 0, 0, { from: token_owner }); + const tx = await I_SecurityToken.addModule(I_VestingEscrowWalletFactory.address, bytesData, 0, 0, false, { from: token_owner }); - assert.equal(tx.logs[2].args._types[0].toNumber(), 6, "VestingEscrowWallet doesn't get deployed"); + assert.equal(tx.logs[3].args._types[0].toString(), 7, "VestingEscrowWallet doesn't get deployed"); assert.equal( - web3.utils.toAscii(tx.logs[2].args._name) + web3.utils.toAscii(tx.logs[3].args._name) .replace(/\u0000/g, ''), "VestingEscrowWallet", "VestingEscrowWallet module was not added" ); - I_VestingEscrowWallet = VestingEscrowWallet.at(tx.logs[2].args._module); + I_VestingEscrowWallet = await VestingEscrowWallet.at(tx.logs[3].args._module); + }); + + it("Should fail to change the treasury wallet", async() => { + await catchRevert( + I_VestingEscrowWallet.changeTreasuryWallet(token_owner, {from: account_beneficiary1}) + ); + }) + + it("Should change the treasury wallet", async() => { + await I_VestingEscrowWallet.changeTreasuryWallet(token_owner, {from: token_owner}); + assert.equal(await I_VestingEscrowWallet.treasuryWallet.call(), token_owner); }); it("Should Buy the tokens for token_owner", async() => { // Add the Investor in to the whitelist - let tx = await I_GeneralTransferManager.modifyWhitelist( + let tx = await I_GeneralTransferManager.modifyKYCData( token_owner, - latestTime(), - latestTime(), - latestTime() + durationUtil.days(10), - true, + currentTime, + currentTime, + currentTime.add(new BN(durationUtil.days(10))), { from: token_owner, gas: 6000000 - }); + } + ); assert.equal(tx.logs[0].args._investor.toLowerCase(), token_owner.toLowerCase(), "Failed in adding the token_owner in whitelist"); // Mint some tokens - await I_SecurityToken.mint(token_owner, web3.utils.toWei('1', 'ether'), { from: token_owner }); + await I_SecurityToken.issue(token_owner, web3.utils.toHex(web3.utils.toWei('1', 'ether')), "0x0", { from: token_owner }); assert.equal( - (await I_SecurityToken.balanceOf(token_owner)).toNumber(), + (await I_SecurityToken.balanceOf(token_owner)).toString(), web3.utils.toWei('1', 'ether') ); @@ -218,12 +235,11 @@ contract('VestingEscrowWallet', accounts => { it("Should whitelist investors", async() => { // Add the Investor in to the whitelist - let tx = await I_GeneralTransferManager.modifyWhitelistMulti( + let tx = await I_GeneralTransferManager.modifyKYCDataMulti( [I_VestingEscrowWallet.address, account_beneficiary1, account_beneficiary2, account_beneficiary3], - [latestTime(), latestTime(), latestTime(), latestTime()], - [latestTime(), latestTime(), latestTime(), latestTime()], - [latestTime() + durationUtil.days(10), latestTime() + durationUtil.days(10), latestTime() + durationUtil.days(10), latestTime() + durationUtil.days(10)], - [true, true, true, true], + [currentTime, currentTime, currentTime, currentTime], + [currentTime, currentTime, currentTime, currentTime], + [currentTime.add(new BN(durationUtil.days(10))), currentTime.add(new BN(durationUtil.days(10))), currentTime.add(new BN(durationUtil.days(10))), currentTime.add(new BN(durationUtil.days(10)))], { from: token_owner, gas: 6000000 @@ -235,50 +251,60 @@ contract('VestingEscrowWallet', accounts => { assert.equal(tx.logs[3].args._investor, account_beneficiary3); }); - it("Should successfully add the delegate", async() => { + it("Should successfully add the delegate (wallet_admin)", async() => { let tx = await I_GeneralPermissionManager.addDelegate(wallet_admin, delegateDetails, { from: token_owner}); assert.equal(tx.logs[0].args._delegate, wallet_admin); }); + it("Should successfully add the delegate (wallet_operator)", async() => { + let tx = await I_GeneralPermissionManager.addDelegate(wallet_operator, delegateDetails, { from: token_owner}); + assert.equal(tx.logs[0].args._delegate, wallet_operator); + }); + it("Should provide the permission", async() => { let tx = await I_GeneralPermissionManager.changePermission( wallet_admin, I_VestingEscrowWallet.address, - "ADMIN", + web3.utils.toHex("ADMIN"), true, {from: token_owner} ); assert.equal(tx.logs[0].args._delegate, wallet_admin); }); + it("Should provide the permission", async() => { + let tx = await I_GeneralPermissionManager.changePermission( + wallet_operator, + I_VestingEscrowWallet.address, + web3.utils.toHex("OPERATOR"), + true, + {from: token_owner} + ); + assert.equal(tx.logs[0].args._delegate, wallet_operator); + }); + it("Should get the permission", async () => { let perm = await I_VestingEscrowWallet.getPermissions.call(); assert.equal(web3.utils.toAscii(perm[0]).replace(/\u0000/g, ""), "ADMIN"); + assert.equal(web3.utils.toAscii(perm[1]).replace(/\u0000/g, ""), "OPERATOR"); }); it("Should get the tags of the factory", async () => { let tags = await I_VestingEscrowWalletFactory.getTags.call(); - assert.equal(tags.length, 2); - assert.equal(web3.utils.toAscii(tags[0]).replace(/\u0000/g, ""), "Vested"); - assert.equal(web3.utils.toAscii(tags[1]).replace(/\u0000/g, ""), "Escrow Wallet"); - }); - - it("Should get the instructions of the factory", async () => { - assert.equal( - (await I_VestingEscrowWalletFactory.getInstructions.call()).replace(/\u0000/g, ""), - "Issuer can deposit tokens to the contract and create the vesting schedule for the given address (Affiliate/Employee). These address can withdraw tokens according to there vesting schedule." - ); + assert.equal(tags.length, 3); + assert.equal(web3.utils.toAscii(tags[0]).replace(/\u0000/g, ""), "Vesting"); + assert.equal(web3.utils.toAscii(tags[1]).replace(/\u0000/g, ""), "Escrow"); }); }); describe("Depositing and withdrawing tokens", async () => { - it("Should not be able to change treasury wallet -- fail because address is invalid", async () => { - await catchRevert( - I_VestingEscrowWallet.changeTreasuryWallet(0, {from: token_owner}) - ); - }); + // it("Should not be able to change treasury wallet -- fail because address is invalid", async () => { + // await catchRevert( + // I_VestingEscrowWallet.changeTreasuryWallet(address_zero, {from: token_owner}) + // ); + // }); it("Should not be able to deposit -- fail because of permissions check", async () => { await catchRevert( @@ -322,7 +348,7 @@ contract('VestingEscrowWallet', accounts => { assert.equal(unassignedTokens, numberOfTokens); let balance = await I_SecurityToken.balanceOf.call(I_VestingEscrowWallet.address); - assert.equal(balance.toNumber(), numberOfTokens); + assert.equal(balance.toString(), numberOfTokens); }); it("Should not be able to withdraw tokens to a treasury -- fail because of permissions check", async () => { @@ -333,20 +359,33 @@ contract('VestingEscrowWallet', accounts => { it("Should not be able to withdraw tokens to a treasury -- fail because of zero amount", async () => { await catchRevert( - I_VestingEscrowWallet.sendToTreasury(0, {from: wallet_admin}) + I_VestingEscrowWallet.sendToTreasury(0, {from: wallet_operator}) ); }); it("Should not be able to withdraw tokens to a treasury -- fail because amount is greater than unassigned tokens", async () => { let numberOfTokens = 25000 * 2; await catchRevert( - I_VestingEscrowWallet.sendToTreasury(numberOfTokens, {from: wallet_admin}) + I_VestingEscrowWallet.sendToTreasury(numberOfTokens, {from: wallet_operator}) ); }); - it("Should withdraw tokens to a treasury", async () => { - let numberOfTokens = 25000; - const tx = await I_VestingEscrowWallet.sendToTreasury(numberOfTokens, {from: wallet_admin}); + it("Should withdraw partial tokens to a treasury", async () => { + let numberOfTokens = 5000; + const tx = await I_VestingEscrowWallet.sendToTreasury(numberOfTokens, {from: wallet_operator}); + + assert.equal(tx.logs[0].args._numberOfTokens, numberOfTokens); + + let unassignedTokens = await I_VestingEscrowWallet.unassignedTokens.call(); + assert.equal(unassignedTokens, 20000); + + let balance = await I_SecurityToken.balanceOf.call(I_VestingEscrowWallet.address); + assert.equal(balance.toString(), 20000); + }); + + it("Should withdraw all tokens to a treasury", async () => { + let numberOfTokens = 20000; + const tx = await I_VestingEscrowWallet.sendToTreasury(numberOfTokens, {from: wallet_operator}); assert.equal(tx.logs[0].args._numberOfTokens, numberOfTokens); @@ -354,20 +393,21 @@ contract('VestingEscrowWallet', accounts => { assert.equal(unassignedTokens, 0); let balance = await I_SecurityToken.balanceOf.call(I_VestingEscrowWallet.address); - assert.equal(balance.toNumber(), 0); + assert.equal(balance.toString(), 0); }); it("Should not be able to push available tokens -- fail because of permissions check", async () => { - let templateName = "template-01"; + currentTime = new BN(await latestTime()); + let templateName = web3.utils.toHex("template-01"); let numberOfTokens = 75000; - let duration = durationUtil.seconds(30); - let frequency = durationUtil.seconds(10); - let timeShift = durationUtil.seconds(100); - let startTime = latestTime() + timeShift; + let duration = new BN(durationUtil.seconds(30)); + let frequency = new BN(durationUtil.seconds(10)); + let timeShift = new BN(durationUtil.seconds(100)); + let startTime = currentTime.add(timeShift); await I_SecurityToken.approve(I_VestingEscrowWallet.address, numberOfTokens, { from: token_owner }); await I_VestingEscrowWallet.depositTokens(numberOfTokens, {from: token_owner}); await I_VestingEscrowWallet.addSchedule(account_beneficiary3, templateName, numberOfTokens, duration, frequency, startTime, {from: wallet_admin}); - await increaseTime(timeShift + frequency); + await increaseTime(durationUtil.seconds(110)); await catchRevert( I_VestingEscrowWallet.pushAvailableTokens(account_beneficiary3, {from: account_beneficiary1}) @@ -376,25 +416,25 @@ contract('VestingEscrowWallet', accounts => { it("Should not be able to remove template -- fail because template is used", async () => { await catchRevert( - I_VestingEscrowWallet.removeTemplate("template-01", {from: wallet_admin}) + I_VestingEscrowWallet.removeTemplate(web3.utils.toHex("template-01"), {from: wallet_admin}) ); }); it("Should push available tokens to the beneficiary address", async () => { let numberOfTokens = 75000; - const tx = await I_VestingEscrowWallet.pushAvailableTokens(account_beneficiary3, {from: wallet_admin}); + const tx = await I_VestingEscrowWallet.pushAvailableTokens(account_beneficiary3, {from: wallet_operator}); assert.equal(tx.logs[0].args._beneficiary, account_beneficiary3); - assert.equal(tx.logs[0].args._numberOfTokens.toNumber(), numberOfTokens / 3); + assert.equal(tx.logs[0].args._numberOfTokens.toString(), numberOfTokens / 3); let balance = await I_SecurityToken.balanceOf.call(account_beneficiary3); - assert.equal(balance.toNumber(), numberOfTokens / 3); + assert.equal(balance.toString(), numberOfTokens / 3); await I_SecurityToken.transfer(token_owner, balance, {from: account_beneficiary3}); }); it("Should fail to modify vesting schedule -- fail because schedule already started", async () => { - let templateName = "template-01"; - let startTime = latestTime() + 100; + let templateName = web3.utils.toHex("template-01"); + let startTime = currentTime + 100; await catchRevert( I_VestingEscrowWallet.modifySchedule(account_beneficiary3, templateName, startTime, {from: wallet_admin}) ); @@ -402,33 +442,38 @@ contract('VestingEscrowWallet', accounts => { await I_VestingEscrowWallet.revokeAllSchedules(account_beneficiary3, {from: wallet_admin}); await I_VestingEscrowWallet.removeTemplate(templateName, {from: wallet_admin}); let unassignedTokens = await I_VestingEscrowWallet.unassignedTokens.call(); - await I_VestingEscrowWallet.sendToTreasury(unassignedTokens, {from: wallet_admin}); + await I_VestingEscrowWallet.sendToTreasury(unassignedTokens, {from: wallet_operator}); }); it("Should fail to modify vesting schedule -- fail because date in the past", async () => { await catchRevert( - I_VestingEscrowWallet.modifySchedule(account_beneficiary3, "template-01", latestTime() - 1000, {from: wallet_admin}) + I_VestingEscrowWallet.modifySchedule(account_beneficiary3, web3.utils.toHex("template-01"), currentTime - 1000, {from: wallet_admin}) ); }); it("Should withdraw available tokens to the beneficiary address", async () => { - let templateName = "template-02"; + currentTime = new BN(await latestTime()); + let templateName = web3.utils.toHex("template-02"); let numberOfTokens = 33000; - let duration = durationUtil.seconds(30); - let frequency = durationUtil.seconds(10); - let timeShift = durationUtil.seconds(100); - let startTime = latestTime() + timeShift; + let duration = new BN(durationUtil.seconds(30)); + let frequency = new BN(durationUtil.seconds(10)); + let timeShift = new BN(durationUtil.seconds(100)); + let startTime = currentTime.add(timeShift); await I_SecurityToken.approve(I_VestingEscrowWallet.address, numberOfTokens, { from: token_owner }); await I_VestingEscrowWallet.depositTokens(numberOfTokens, {from: token_owner}); await I_VestingEscrowWallet.addSchedule(account_beneficiary3, templateName, numberOfTokens, duration, frequency, startTime, {from: wallet_admin}); - await increaseTime(timeShift + frequency * 3); - + await increaseTime(durationUtil.seconds(130)); + await I_VestingEscrowWallet.pause({from: token_owner}); + await catchRevert( + I_VestingEscrowWallet.pullAvailableTokens({from: account_beneficiary3}) + ); + await I_VestingEscrowWallet.unpause({from: token_owner}); const tx = await I_VestingEscrowWallet.pullAvailableTokens({from: account_beneficiary3}); assert.equal(tx.logs[0].args._beneficiary, account_beneficiary3); - assert.equal(tx.logs[0].args._numberOfTokens.toNumber(), numberOfTokens); + assert.equal(tx.logs[0].args._numberOfTokens.toString(), numberOfTokens); let balance = await I_SecurityToken.balanceOf.call(account_beneficiary3); - assert.equal(balance.toNumber(), numberOfTokens); + assert.equal(balance.toString(), numberOfTokens); let schedule = await I_VestingEscrowWallet.getSchedule.call(account_beneficiary3, templateName); checkSchedule(schedule, numberOfTokens, duration, frequency, startTime, COMPLETED); @@ -438,28 +483,52 @@ contract('VestingEscrowWallet', accounts => { await I_VestingEscrowWallet.removeTemplate(templateName, {from: wallet_admin}); }); + it("Should fetch the unused tokens from the wallet", async() => { + await I_PolyToken.getTokens(new BN(20).mul(new BN(10).pow(new BN(18))), I_VestingEscrowWallet.address, {from: token_owner}); + let previousBalance = web3.utils.fromWei((await I_PolyToken.balanceOf.call(I_VestingEscrowWallet.address)).toString()); + await catchRevert( + I_VestingEscrowWallet.reclaimERC20(I_PolyToken.address, {from: account_beneficiary2}) + ) + await I_VestingEscrowWallet.reclaimERC20(I_PolyToken.address, {from: token_owner}); + let newBalance = web3.utils.fromWei((await I_PolyToken.balanceOf.call(I_VestingEscrowWallet.address)).toString()); + assert.equal(previousBalance - newBalance, 20); + }); + + it("Should fail to transfer the tokens to wallet", async() => { + await catchRevert ( + web3.eth.sendTransaction({ + from: token_owner, + to: I_VestingEscrowWallet.address, + gas: 2100000, + value: new BN(web3.utils.toWei("1", "ether")) + }) + ); + }); + it("Should withdraw available tokens 2 times by 3 schedules to the beneficiary address", async () => { + currentTime = new BN(await latestTime()); let schedules = [ { - templateName: "template-1-01", - numberOfTokens: 100000, - duration: durationUtil.minutes(4), - frequency: durationUtil.minutes(1) + templateName: web3.utils.toHex("template-1-01"), + numberOfTokens: new BN(100000), + duration: new BN(durationUtil.minutes(4)), + frequency: new BN(durationUtil.minutes(1)) }, { - templateName: "template-1-02", - numberOfTokens: 30000, - duration: durationUtil.minutes(6), - frequency: durationUtil.minutes(1) + templateName: web3.utils.toHex("template-1-02"), + numberOfTokens: new BN(30000), + duration: new BN(durationUtil.minutes(6)), + frequency: new BN(durationUtil.minutes(1)) }, { - templateName: "template-1-03", - numberOfTokens: 2000, - duration: durationUtil.minutes(10), - frequency: durationUtil.minutes(1) + templateName: web3.utils.toHex("template-1-03"), + numberOfTokens: new BN(2000), + duration: new BN(durationUtil.minutes(10)), + frequency: new BN(durationUtil.minutes(1)) } ]; + let timeShift = new BN(durationUtil.seconds(100)); let totalNumberOfTokens = getTotalNumberOfTokens(schedules); await I_SecurityToken.approve(I_VestingEscrowWallet.address, totalNumberOfTokens, {from: token_owner}); await I_VestingEscrowWallet.depositTokens(totalNumberOfTokens, {from: token_owner}); @@ -468,7 +537,7 @@ contract('VestingEscrowWallet', accounts => { let numberOfTokens = schedules[i].numberOfTokens; let duration = schedules[i].duration; let frequency = schedules[i].frequency; - let startTime = latestTime() + durationUtil.seconds(100); + let startTime = currentTime.add(timeShift); await I_VestingEscrowWallet.addSchedule(account_beneficiary3, templateName, numberOfTokens, duration, frequency, startTime, {from: wallet_admin}); } let stepCount = 6; @@ -478,24 +547,24 @@ contract('VestingEscrowWallet', accounts => { const tx = await I_VestingEscrowWallet.pullAvailableTokens({from: account_beneficiary3}); assert.equal(tx.logs[0].args._beneficiary, account_beneficiary3); - assert.equal(tx.logs[0].args._numberOfTokens.toNumber(), 100000); + assert.equal(tx.logs[0].args._numberOfTokens.toString(), 100000); assert.equal(tx.logs[1].args._beneficiary, account_beneficiary3); - assert.equal(tx.logs[1].args._numberOfTokens.toNumber(), 30000 / 6 * stepCount); + assert.equal(tx.logs[1].args._numberOfTokens.toString(), 30000 / 6 * stepCount); assert.equal(tx.logs[2].args._beneficiary, account_beneficiary3); - assert.equal(tx.logs[2].args._numberOfTokens.toNumber(), 2000 / 10 * stepCount); + assert.equal(tx.logs[2].args._numberOfTokens.toString(), 2000 / 10 * stepCount); let balance = await I_SecurityToken.balanceOf.call(account_beneficiary3); - assert.equal(balance.toNumber(), numberOfTokens); + assert.equal(balance.toString(), numberOfTokens); stepCount = 4; await increaseTime(durationUtil.minutes(stepCount) + durationUtil.seconds(100)); const tx2 = await I_VestingEscrowWallet.pullAvailableTokens({from: account_beneficiary3}); assert.equal(tx2.logs[0].args._beneficiary, account_beneficiary3); - assert.equal(tx2.logs[0].args._numberOfTokens.toNumber(), 2000 / 10 * stepCount); + assert.equal(tx2.logs[0].args._numberOfTokens.toString(), 2000 / 10 * stepCount); balance = await I_SecurityToken.balanceOf.call(account_beneficiary3); - assert.equal(balance.toNumber(), totalNumberOfTokens); + assert.equal(balance.toString(), totalNumberOfTokens); await I_SecurityToken.transfer(token_owner, balance, {from: account_beneficiary3}); await I_VestingEscrowWallet.revokeAllSchedules(account_beneficiary3, {from: wallet_admin}); @@ -507,76 +576,78 @@ contract('VestingEscrowWallet', accounts => { }); describe("Adding, modifying and revoking vesting schedule", async () => { + let template_2_01 = web3.utils.toHex("template-2-01"); let schedules = [ { - templateName: "template-2-01", + templateName: web3.utils.toHex("template-2-01"), numberOfTokens: 100000, - duration: durationUtil.years(4), - frequency: durationUtil.years(1), - startTime: latestTime() + durationUtil.days(1) + duration: new BN(durationUtil.years(4)), + frequency: new BN(durationUtil.years(1)), + // startTime: currentTime.add(new BN(durationUtil.days(1))) }, { - templateName: "template-2-02", + templateName: web3.utils.toHex("template-2-02"), numberOfTokens: 30000, - duration: durationUtil.weeks(6), - frequency: durationUtil.weeks(1), - startTime: latestTime() + durationUtil.days(2) + duration: new BN(durationUtil.weeks(6)), + frequency: new BN(durationUtil.weeks(1)), + // startTime: currentTime.add(new BN(durationUtil.days(2))) }, { - templateName: "template-2-03", + templateName: web3.utils.toHex("template-2-03"), numberOfTokens: 2000, - duration: durationUtil.days(10), - frequency: durationUtil.days(2), - startTime: latestTime() + durationUtil.days(3) + duration: new BN(durationUtil.days(10)), + frequency: new BN(durationUtil.days(2)), + // startTime: currentTime.add(new BN(durationUtil.days(3))) } ]; it("Should fail to add vesting schedule to the beneficiary address -- fail because address in invalid", async () => { await catchRevert( - I_VestingEscrowWallet.addSchedule(0, "template-2-01", 100000, 4, 1, latestTime() + durationUtil.days(1), {from: wallet_admin}) + I_VestingEscrowWallet.addSchedule(address_zero, template_2_01, 100000, 4, 1, currentTime.add(new BN(durationUtil.days(1))), {from: wallet_admin}) ); }); it("Should fail to add vesting schedule to the beneficiary address -- fail because start date in the past", async () => { await catchRevert( - I_VestingEscrowWallet.addSchedule(account_beneficiary1, "template-2-01", 100000, 4, 1, latestTime() - durationUtil.days(1), {from: wallet_admin}) + I_VestingEscrowWallet.addSchedule(account_beneficiary1, template_2_01, 100000, 4, 1, currentTime.add(new BN(durationUtil.days(1))), {from: wallet_admin}) ); }); it("Should fail to add vesting schedule to the beneficiary address -- fail because number of tokens is 0", async () => { await catchRevert( - I_VestingEscrowWallet.addSchedule(account_beneficiary1, "template-2-01", 0, 4, 1, latestTime() + durationUtil.days(1), {from: wallet_admin}) + I_VestingEscrowWallet.addSchedule(account_beneficiary1, template_2_01, 0, 4, 1, currentTime.add(new BN(durationUtil.days(1))), {from: wallet_admin}) ); }); it("Should fail to add vesting schedule to the beneficiary address -- fail because duration can't be divided entirely by frequency", async () => { await catchRevert( - I_VestingEscrowWallet.addSchedule(account_beneficiary1, "template-2-01", 100000, 4, 3, latestTime() + durationUtil.days(1), {from: wallet_admin}) + I_VestingEscrowWallet.addSchedule(account_beneficiary1, template_2_01, 100000, 4, 3, currentTime.add(new BN(durationUtil.days(1))), {from: wallet_admin}) ); }); it("Should fail to add vesting schedule to the beneficiary address -- fail because number of tokens can't be divided entirely by period count", async () => { await catchRevert( - I_VestingEscrowWallet.addSchedule(account_beneficiary1, "template-2-01", 5, 4, 1, latestTime() + durationUtil.days(1), {from: wallet_admin}) + I_VestingEscrowWallet.addSchedule(account_beneficiary1, template_2_01, 5, 4, 1, currentTime.add(new BN(durationUtil.days(1))), {from: wallet_admin}) ); }); it("Should fail to get vesting schedule -- fail because address is invalid", async () => { await catchRevert( - I_VestingEscrowWallet.getSchedule(0, "template-2-01") + I_VestingEscrowWallet.getSchedule(address_zero, template_2_01) ); }); it("Should fail to get vesting schedule -- fail because schedule not found", async () => { + await catchRevert( - I_VestingEscrowWallet.getSchedule(account_beneficiary1, "template-2-01") + I_VestingEscrowWallet.getSchedule(account_beneficiary1, template_2_01) ); }); it("Should fail to get count of vesting schedule -- fail because address is invalid", async () => { await catchRevert( - I_VestingEscrowWallet.getScheduleCount(0) + I_VestingEscrowWallet.getScheduleCount(address_zero) ); }); @@ -585,22 +656,24 @@ contract('VestingEscrowWallet', accounts => { let numberOfTokens = schedules[0].numberOfTokens; let duration = schedules[0].duration; let frequency = schedules[0].frequency; - let startTime = schedules[0].startTime; + let startTime = currentTime.add(new BN(durationUtil.days(1))); + currentTime = new BN(await latestTime()); await I_SecurityToken.approve(I_VestingEscrowWallet.address, numberOfTokens, {from: token_owner}); await I_VestingEscrowWallet.depositTokens(numberOfTokens, {from: token_owner}); await catchRevert( I_VestingEscrowWallet.addSchedule(account_beneficiary1, templateName, numberOfTokens, duration, frequency, startTime, {from: account_beneficiary1}) ); let unassignedTokens = await I_VestingEscrowWallet.unassignedTokens.call(); - await I_VestingEscrowWallet.sendToTreasury(unassignedTokens, {from: wallet_admin}); + await I_VestingEscrowWallet.sendToTreasury(unassignedTokens, {from: wallet_operator}); }); it("Should add vesting schedule to the beneficiary address", async () => { + currentTime = new BN(await latestTime()); let templateName = schedules[0].templateName; let numberOfTokens = schedules[0].numberOfTokens; let duration = schedules[0].duration; let frequency = schedules[0].frequency; - let startTime = schedules[0].startTime; + let startTime = currentTime.add(new BN(durationUtil.days(1))); await I_SecurityToken.approve(I_VestingEscrowWallet.address, numberOfTokens, {from: token_owner}); await I_VestingEscrowWallet.depositTokens(numberOfTokens, {from: token_owner}); const tx = await I_VestingEscrowWallet.addSchedule(account_beneficiary1, templateName, numberOfTokens, duration, frequency, startTime, {from: wallet_admin}); @@ -615,15 +688,16 @@ contract('VestingEscrowWallet', accounts => { checkSchedule(schedule, numberOfTokens, duration, frequency, startTime, CREATED); let templates = await I_VestingEscrowWallet.getTemplateNames.call(account_beneficiary1); - assert.equal(web3.utils.hexToUtf8(templates[0]), templateName); + assert.equal(web3.utils.hexToUtf8(templates[0]), web3.utils.hexToUtf8(templateName)); }); it("Should add vesting schedule without depositing to the beneficiary address", async () => { - let templateName = "template-2-01-2"; + currentTime = new BN(await latestTime()); + let templateName = web3.utils.toHex("template-2-01-2"); let numberOfTokens = schedules[0].numberOfTokens; let duration = schedules[0].duration; let frequency = schedules[0].frequency; - let startTime = schedules[0].startTime; + let startTime = currentTime.add(new BN(durationUtil.days(1))); await I_SecurityToken.approve(I_VestingEscrowWallet.address, numberOfTokens, {from: token_owner}); const tx = await I_VestingEscrowWallet.addSchedule(account_beneficiary1, templateName, numberOfTokens, duration, frequency, startTime, {from: token_owner}); @@ -640,12 +714,13 @@ contract('VestingEscrowWallet', accounts => { await I_VestingEscrowWallet.revokeSchedule(account_beneficiary1, templateName, {from: wallet_admin}); await I_VestingEscrowWallet.removeTemplate(templateName, {from: wallet_admin}); let unassignedTokens = await I_VestingEscrowWallet.unassignedTokens.call(); - await I_VestingEscrowWallet.sendToTreasury(unassignedTokens, {from: wallet_admin}); + await I_VestingEscrowWallet.sendToTreasury(unassignedTokens, {from: wallet_operator}); }); it("Should fail to modify vesting schedule -- fail because schedule not found", async () => { - let templateName = "template-2-03"; - let startTime = schedules[0].startTime; + currentTime = new BN(await latestTime()); + let templateName = web3.utils.toHex("template-2-03"); + let startTime = currentTime.add(new BN(durationUtil.days(1))); await catchRevert( I_VestingEscrowWallet.modifySchedule(account_beneficiary1, templateName, startTime, {from: wallet_admin}) ); @@ -653,41 +728,42 @@ contract('VestingEscrowWallet', accounts => { it("Should not be able to modify schedule -- fail because of permissions check", async () => { await catchRevert( - I_VestingEscrowWallet.modifySchedule(account_beneficiary1, "template-2-01", latestTime() + 100, {from: account_beneficiary1}) + I_VestingEscrowWallet.modifySchedule(account_beneficiary1, web3.utils.toHex("template-2-01"), currentTime.add(new BN(100)), {from: account_beneficiary1}) ); }); it("Should modify vesting schedule for the beneficiary's address", async () => { - let templateName = "template-2-01"; + currentTime = new BN(await latestTime()); + let templateName = web3.utils.toHex("template-2-01"); let numberOfTokens = schedules[0].numberOfTokens; let duration = schedules[0].duration; let frequency = schedules[0].frequency; - let startTime = schedules[1].startTime; + let startTime = currentTime.add(new BN(durationUtil.days(2))); const tx = await I_VestingEscrowWallet.modifySchedule(account_beneficiary1, templateName, startTime, {from: wallet_admin}); checkScheduleLog(tx.logs[0], account_beneficiary1, templateName, startTime); let scheduleCount = await I_VestingEscrowWallet.getScheduleCount.call(account_beneficiary1); - assert.equal(scheduleCount.toNumber(), 1); + assert.equal(scheduleCount.toString(), 1); - let schedule = await I_VestingEscrowWallet.getSchedule.call(account_beneficiary1, "template-2-01"); + let schedule = await I_VestingEscrowWallet.getSchedule.call(account_beneficiary1, templateName); checkSchedule(schedule, numberOfTokens, duration, frequency, startTime, CREATED); }); it("Should not be able to revoke schedule -- fail because of permissions check", async () => { await catchRevert( - I_VestingEscrowWallet.revokeSchedule(account_beneficiary1, "template-2-01", {from: account_beneficiary1}) + I_VestingEscrowWallet.revokeSchedule(account_beneficiary1, web3.utils.toHex("template-2-01"), {from: account_beneficiary1}) ); }); it("Should revoke vesting schedule from the beneficiary address", async () => { - let templateName = "template-2-01"; + let templateName = web3.utils.toHex("template-2-01"); const tx = await I_VestingEscrowWallet.revokeSchedule(account_beneficiary1, templateName, {from: wallet_admin}); let unassignedTokens = await I_VestingEscrowWallet.unassignedTokens.call(); - await I_VestingEscrowWallet.sendToTreasury(unassignedTokens, {from: wallet_admin}); + await I_VestingEscrowWallet.sendToTreasury(unassignedTokens, {from: wallet_operator}); assert.equal(tx.logs[0].args._beneficiary, account_beneficiary1); - assert.equal(web3.utils.hexToUtf8(tx.logs[0].args._templateName), templateName); + assert.equal(web3.utils.hexToUtf8(tx.logs[0].args._templateName), web3.utils.hexToUtf8(templateName)); let scheduleCount = await I_VestingEscrowWallet.getScheduleCount.call(account_beneficiary1); assert.equal(scheduleCount, 0); @@ -697,23 +773,29 @@ contract('VestingEscrowWallet', accounts => { it("Should fail to revoke vesting schedule -- fail because address is invalid", async () => { await catchRevert( - I_VestingEscrowWallet.revokeSchedule(0, "template-2-01", {from: wallet_admin}) + I_VestingEscrowWallet.revokeSchedule(address_zero, web3.utils.toHex("template-2-01"), {from: wallet_admin}) ); }); it("Should fail to revoke vesting schedule -- fail because schedule not found", async () => { await catchRevert( - I_VestingEscrowWallet.revokeSchedule(account_beneficiary1, "template-2-02", {from: wallet_admin}) + I_VestingEscrowWallet.revokeSchedule(account_beneficiary1, web3.utils.toHex("template-2-02"), {from: wallet_admin}) ); }); it("Should fail to revoke vesting schedules -- fail because address is invalid", async () => { await catchRevert( - I_VestingEscrowWallet.revokeAllSchedules(0, {from: wallet_admin}) + I_VestingEscrowWallet.revokeAllSchedules(address_zero, {from: wallet_admin}) ); }); it("Should add 3 vesting schedules to the beneficiary address", async () => { + currentTime = new BN(await latestTime()); + let startTimes = [ + currentTime.add(new BN(durationUtil.days(1))), + currentTime.add(new BN(durationUtil.days(2))), + currentTime.add(new BN(durationUtil.days(3))) + ]; let totalNumberOfTokens = getTotalNumberOfTokens(schedules); await I_SecurityToken.approve(I_VestingEscrowWallet.address, totalNumberOfTokens, {from: token_owner}); await I_VestingEscrowWallet.depositTokens(totalNumberOfTokens, {from: token_owner}); @@ -722,7 +804,7 @@ contract('VestingEscrowWallet', accounts => { let numberOfTokens = schedules[i].numberOfTokens; let duration = schedules[i].duration; let frequency = schedules[i].frequency; - let startTime = schedules[i].startTime; + let startTime = startTimes[i]; const tx = await I_VestingEscrowWallet.addSchedule(account_beneficiary2, templateName, numberOfTokens, duration, frequency, startTime, {from: wallet_admin}); checkTemplateLog(tx.logs[0], templateName, numberOfTokens, duration, frequency); @@ -746,10 +828,10 @@ contract('VestingEscrowWallet', accounts => { let templateName = schedules[1].templateName; const tx = await I_VestingEscrowWallet.revokeSchedule(account_beneficiary2, templateName, {from: wallet_admin}); let unassignedTokens = await I_VestingEscrowWallet.unassignedTokens.call(); - await I_VestingEscrowWallet.sendToTreasury(unassignedTokens, {from: wallet_admin}); + await I_VestingEscrowWallet.sendToTreasury(unassignedTokens, {from: wallet_operator}); assert.equal(tx.logs[0].args._beneficiary, account_beneficiary2); - assert.equal(web3.utils.hexToUtf8(tx.logs[0].args._templateName), templateName); + assert.equal(web3.utils.hexToUtf8(tx.logs[0].args._templateName), web3.utils.hexToUtf8(templateName)); let scheduleCount = await I_VestingEscrowWallet.getScheduleCount.call(account_beneficiary2); assert.equal(scheduleCount, 2); @@ -758,7 +840,7 @@ contract('VestingEscrowWallet', accounts => { it("Should revoke 2 vesting schedules from the beneficiary address", async () => { const tx = await I_VestingEscrowWallet.revokeAllSchedules(account_beneficiary2, {from: wallet_admin}); let unassignedTokens = await I_VestingEscrowWallet.unassignedTokens.call(); - await I_VestingEscrowWallet.sendToTreasury(unassignedTokens, {from: wallet_admin}); + await I_VestingEscrowWallet.sendToTreasury(unassignedTokens, {from: wallet_operator}); assert.equal(tx.logs[0].args._beneficiary, account_beneficiary2); @@ -767,24 +849,25 @@ contract('VestingEscrowWallet', accounts => { }); it("Should push available tokens during revoking vesting schedule", async () => { + currentTime = new BN(await latestTime()); let schedules = [ { - templateName: "template-3-01", - numberOfTokens: 100000, - duration: durationUtil.minutes(4), - frequency: durationUtil.minutes(1) + templateName: web3.utils.toHex("template-3-01"), + numberOfTokens: new BN(100000), + duration: new BN(durationUtil.minutes(4)), + frequency: new BN(durationUtil.minutes(1)) }, { - templateName: "template-3-02", - numberOfTokens: 30000, - duration: durationUtil.minutes(6), - frequency: durationUtil.minutes(1) + templateName: web3.utils.toHex("template-3-02"), + numberOfTokens: new BN(30000), + duration: new BN(durationUtil.minutes(6)), + frequency: new BN(durationUtil.minutes(1)) }, { - templateName: "template-3-03", - numberOfTokens: 2000, - duration: durationUtil.minutes(10), - frequency: durationUtil.minutes(1) + templateName: web3.utils.toHex("template-3-03"), + numberOfTokens: new BN(2000), + duration: new BN(durationUtil.minutes(10)), + frequency: new BN(durationUtil.minutes(1)) } ]; @@ -796,77 +879,76 @@ contract('VestingEscrowWallet', accounts => { let numberOfTokens = schedules[i].numberOfTokens; let duration = schedules[i].duration; let frequency = schedules[i].frequency; - let startTime = latestTime() + durationUtil.seconds(100); + let startTime = currentTime.add(new BN(100)); await I_VestingEscrowWallet.addSchedule(account_beneficiary3, templateName, numberOfTokens, duration, frequency, startTime, {from: wallet_admin}); } let stepCount = 3; await increaseTime(durationUtil.minutes(stepCount) + durationUtil.seconds(100)); - const tx = await I_VestingEscrowWallet.revokeSchedule(account_beneficiary3, "template-3-01", {from: wallet_admin}); + const tx = await I_VestingEscrowWallet.revokeSchedule(account_beneficiary3, web3.utils.toHex("template-3-01"), {from: wallet_admin}); assert.equal(tx.logs[0].args._beneficiary, account_beneficiary3); - assert.equal(tx.logs[0].args._numberOfTokens.toNumber(), 100000 / 4 * stepCount); + assert.equal(tx.logs[0].args._numberOfTokens.toString(), 100000 / 4 * stepCount); let balance = await I_SecurityToken.balanceOf.call(account_beneficiary3); - assert.equal(balance.toNumber(), 100000 / 4 * stepCount); + assert.equal(balance.toString(), 100000 / 4 * stepCount); stepCount = 7; await increaseTime(durationUtil.minutes(stepCount)); const tx2 = await I_VestingEscrowWallet.revokeAllSchedules(account_beneficiary3, {from: wallet_admin}); assert.equal(tx2.logs[0].args._beneficiary, account_beneficiary3); - assert.equal(tx2.logs[0].args._numberOfTokens.toNumber(), 2000); + assert.equal(tx2.logs[0].args._numberOfTokens.toString(), 2000); assert.equal(tx2.logs[1].args._beneficiary, account_beneficiary3); - assert.equal(tx2.logs[1].args._numberOfTokens.toNumber(), 30000); + assert.equal(tx2.logs[1].args._numberOfTokens.toString(), 30000); for (let i = 0; i < schedules.length; i++) { await I_VestingEscrowWallet.removeTemplate(schedules[i].templateName, {from: wallet_admin}); } balance = await I_SecurityToken.balanceOf.call(account_beneficiary3); - assert.equal(balance.toNumber(), totalNumberOfTokens - 100000 / 4); + assert.equal(balance.toString(), totalNumberOfTokens - 100000 / 4); await I_SecurityToken.transfer(token_owner, balance, {from: account_beneficiary3}); let unassignedTokens = await I_VestingEscrowWallet.unassignedTokens.call(); - await I_VestingEscrowWallet.sendToTreasury(unassignedTokens, {from: wallet_admin}); + await I_VestingEscrowWallet.sendToTreasury(unassignedTokens, {from: wallet_operator}); }); }); describe("Adding, using and removing templates", async () => { - let schedules = [ { - templateName: "template-4-01", + templateName: web3.utils.toHex("template-4-01"), numberOfTokens: 100000, - duration: durationUtil.years(4), - frequency: durationUtil.years(1), - startTime: latestTime() + durationUtil.days(1) + duration: new BN(durationUtil.years(4)), + frequency: new BN(durationUtil.years(1)), + // startTime: currentTime.add(new BN(durationUtil.days(1))) }, { - templateName: "template-4-02", + templateName: web3.utils.toHex("template-4-02"), numberOfTokens: 30000, - duration: durationUtil.weeks(6), - frequency: durationUtil.weeks(1), - startTime: latestTime() + durationUtil.days(2) + duration: new BN(durationUtil.weeks(6)), + frequency: new BN(durationUtil.weeks(1)), + // startTime: currentTime.add(new BN(durationUtil.days(2))) }, { - templateName: "template-4-03", + templateName: web3.utils.toHex("template-4-03"), numberOfTokens: 2000, - duration: durationUtil.days(10), - frequency: durationUtil.days(2), - startTime: latestTime() + durationUtil.days(3) + duration: new BN(durationUtil.days(10)), + frequency: new BN(durationUtil.days(2)), + // startTime: currentTime.add(new BN(durationUtil.days(3))) } ]; it("Should not be able to add template -- fail because of permissions check", async () => { await catchRevert( - I_VestingEscrowWallet.addTemplate("template-4-01", 25000, 4, 1, {from: account_beneficiary1}) + I_VestingEscrowWallet.addTemplate(web3.utils.toHex("template-4-01"), 25000, 4, 1, {from: account_beneficiary1}) ); }); it("Should not be able to add template -- fail because of invalid name", async () => { await catchRevert( - I_VestingEscrowWallet.addTemplate("", 25000, 4, 1, {from: wallet_admin}) + I_VestingEscrowWallet.addTemplate(web3.utils.toHex(""), 25000, 4, 1, {from: wallet_admin}) ); }); @@ -879,58 +961,57 @@ contract('VestingEscrowWallet', accounts => { let frequency = schedules[i].frequency; const tx = await I_VestingEscrowWallet.addTemplate(templateName, numberOfTokens, duration, frequency, {from: wallet_admin}); - assert.equal(web3.utils.hexToUtf8(tx.logs[0].args._name), templateName); - assert.equal(tx.logs[0].args._numberOfTokens.toNumber(), numberOfTokens); - assert.equal(tx.logs[0].args._duration.toNumber(), duration); - assert.equal(tx.logs[0].args._frequency.toNumber(), frequency); + assert.equal(web3.utils.hexToUtf8(tx.logs[0].args._name), web3.utils.hexToUtf8(templateName)); + assert.equal(tx.logs[0].args._numberOfTokens.toString(), numberOfTokens); + assert.equal(tx.logs[0].args._duration.toString(), duration); + assert.equal(tx.logs[0].args._frequency.toString(), frequency); } let templateNames = await I_VestingEscrowWallet.getAllTemplateNames.call(); for (let i = 0, j = oldTemplateCount; i < schedules.length; i++, j++) { - assert.equal(web3.utils.hexToUtf8(templateNames[j]), schedules[i].templateName); + assert.equal(web3.utils.hexToUtf8(templateNames[j]), web3.utils.hexToUtf8(schedules[i].templateName)); } }); it("Should not be able to add template -- fail because template already exists", async () => { await catchRevert( - I_VestingEscrowWallet.addTemplate("template-4-01", 25000, 4, 1, {from: wallet_admin}) + I_VestingEscrowWallet.addTemplate(web3.utils.toHex("template-4-01"), 25000, 4, 1, {from: wallet_admin}) ); }); it("Should not be able to remove template -- fail because of permissions check", async () => { await catchRevert( - I_VestingEscrowWallet.removeTemplate("template-4-02", {from: account_beneficiary1}) + I_VestingEscrowWallet.removeTemplate(web3.utils.toHex("template-4-02"), {from: account_beneficiary1}) ); }); it("Should not be able to remove template -- fail because template not found", async () => { await catchRevert( - I_VestingEscrowWallet.removeTemplate("template-444-02", {from: wallet_admin}) + I_VestingEscrowWallet.removeTemplate(web3.utils.toHex("template-444-02"), {from: wallet_admin}) ); }); it("Should remove template", async () => { - const tx = await I_VestingEscrowWallet.removeTemplate("template-4-02", {from: wallet_admin}); + const tx = await I_VestingEscrowWallet.removeTemplate(web3.utils.toHex("template-4-02"), {from: wallet_admin}); assert.equal(web3.utils.hexToUtf8(tx.logs[0].args._name), "template-4-02"); }); it("Should fail to add vesting schedule from template -- fail because template not found", async () => { - let startTime = schedules[2].startTime; await catchRevert( - I_VestingEscrowWallet.addScheduleFromTemplate(account_beneficiary1, "template-4-02", startTime, {from: wallet_admin}) + I_VestingEscrowWallet.addScheduleFromTemplate(account_beneficiary1, web3.utils.toHex("template-4-02"), currentTime, {from: wallet_admin}) ); }); it("Should not be able to add schedule from template -- fail because of permissions check", async () => { await catchRevert( - I_VestingEscrowWallet.addScheduleFromTemplate(account_beneficiary1, "template-4-01", latestTime(), {from: account_beneficiary1}) + I_VestingEscrowWallet.addScheduleFromTemplate(account_beneficiary1, web3.utils.toHex("template-4-01"), currentTime, {from: account_beneficiary1}) ); }); it("Should not be able to add vesting schedule from template -- fail because template not found", async () => { await catchRevert( - I_VestingEscrowWallet.addScheduleFromTemplate(account_beneficiary1, "template-777", latestTime() + 100, {from: wallet_admin}) + I_VestingEscrowWallet.addScheduleFromTemplate(account_beneficiary1, web3.utils.toHex("template-777"), currentTime + 100, {from: wallet_admin}) ); }); @@ -939,7 +1020,8 @@ contract('VestingEscrowWallet', accounts => { let numberOfTokens = schedules[2].numberOfTokens; let duration = schedules[2].duration; let frequency = schedules[2].frequency; - let startTime = schedules[2].startTime; + let startTime = currentTime.add(new BN(durationUtil.days(3))); + currentTime = new BN(await latestTime()); await I_SecurityToken.approve(I_VestingEscrowWallet.address, numberOfTokens, { from: token_owner }); await I_VestingEscrowWallet.depositTokens(numberOfTokens, {from: token_owner}); const tx = await I_VestingEscrowWallet.addScheduleFromTemplate(account_beneficiary1, templateName, startTime, {from: wallet_admin}); @@ -954,27 +1036,27 @@ contract('VestingEscrowWallet', accounts => { await I_VestingEscrowWallet.revokeSchedule(account_beneficiary1, templateName, {from: wallet_admin}); let unassignedTokens = await I_VestingEscrowWallet.unassignedTokens.call(); - await I_VestingEscrowWallet.sendToTreasury(unassignedTokens, {from: wallet_admin}); + await I_VestingEscrowWallet.sendToTreasury(unassignedTokens, {from: wallet_operator}); }); it("Should not be able to add vesting schedule from template -- fail because template already added", async () => { let templateName = schedules[2].templateName; await catchRevert( - I_VestingEscrowWallet.addScheduleFromTemplate(account_beneficiary1, templateName, latestTime() + 100, {from: wallet_admin}) + I_VestingEscrowWallet.addScheduleFromTemplate(account_beneficiary1, templateName, currentTime.add(new BN(100)), {from: wallet_admin}) ); }); it("Should fail to remove template", async () => { await catchRevert( - I_VestingEscrowWallet.removeTemplate("template-4-02", {from: wallet_admin}) + I_VestingEscrowWallet.removeTemplate(web3.utils.toHex("template-4-02"), {from: wallet_admin}) ); }); it("Should remove 2 Templates", async () => { let templateCount = await I_VestingEscrowWallet.getTemplateCount.call({from: wallet_admin}); - await I_VestingEscrowWallet.removeTemplate("template-4-01", {from: wallet_admin}); - await I_VestingEscrowWallet.removeTemplate("template-4-03", {from: wallet_admin}); + await I_VestingEscrowWallet.removeTemplate(web3.utils.toHex("template-4-01"), {from: wallet_admin}); + await I_VestingEscrowWallet.removeTemplate(web3.utils.toHex("template-4-03"), {from: wallet_admin}); let templateCountAfterRemoving = await I_VestingEscrowWallet.getTemplateCount.call({from: wallet_admin}); assert.equal(templateCount - templateCountAfterRemoving, 2); @@ -984,17 +1066,20 @@ contract('VestingEscrowWallet', accounts => { describe("Tests for multi operations", async () => { - let templateNames = ["template-5-01", "template-5-02", "template-5-03"]; + let templateNames = [web3.utils.toHex("template-5-01"), web3.utils.toHex("template-5-02"), web3.utils.toHex("template-5-03")]; it("Should not be able to add schedules to the beneficiaries -- fail because of permissions check", async () => { - let startTimes = [latestTime() + 100, latestTime() + 100, latestTime() + 100]; + currentTime = new BN(await latestTime()); + let startTime = currentTime.add(new BN(100)); + let startTimes = [startTime, startTime, startTime]; await catchRevert( I_VestingEscrowWallet.addScheduleMulti(beneficiaries, templateNames, [10000, 10000, 10000], [4, 4, 4], [1, 1, 1], startTimes, {from: account_beneficiary1}) ); }); it("Should not be able to add schedules to the beneficiaries -- fail because of arrays sizes mismatch", async () => { - let startTimes = [latestTime() + 100, latestTime() + 100, latestTime() + 100]; + let startTime = currentTime.add(new BN(100)); + let startTimes = [startTime, startTime, startTime]; let totalNumberOfTokens = 60000; await I_SecurityToken.approve(I_VestingEscrowWallet.address, totalNumberOfTokens, {from: token_owner}); await I_VestingEscrowWallet.depositTokens(totalNumberOfTokens, {from: token_owner}); @@ -1002,15 +1087,15 @@ contract('VestingEscrowWallet', accounts => { I_VestingEscrowWallet.addScheduleMulti(beneficiaries, templateNames, [20000, 30000, 10000], [4, 4], [1, 1, 1], startTimes, {from: wallet_admin}) ); let unassignedTokens = await I_VestingEscrowWallet.unassignedTokens.call(); - await I_VestingEscrowWallet.sendToTreasury(unassignedTokens, {from: wallet_admin}); + await I_VestingEscrowWallet.sendToTreasury(unassignedTokens, {from: wallet_operator}); }); it("Should add schedules for 3 beneficiaries", async () => { let numberOfTokens = [15000, 15000, 15000]; - let durations = [durationUtil.seconds(50), durationUtil.seconds(50), durationUtil.seconds(50)]; - let frequencies = [durationUtil.seconds(10), durationUtil.seconds(10), durationUtil.seconds(10)]; - let timeShift = durationUtil.seconds(100); - let startTimes = [latestTime() + timeShift, latestTime() + timeShift, latestTime() + timeShift]; + let durations = [new BN(durationUtil.seconds(50)), new BN(durationUtil.seconds(50)), new BN(durationUtil.seconds(50))]; + let frequencies = [new BN(durationUtil.seconds(10)), new BN(durationUtil.seconds(10)), new BN(durationUtil.seconds(10))]; + let timeShift = new BN(durationUtil.seconds(100)); + let startTimes = [currentTime.add(timeShift), currentTime.add(timeShift), currentTime.add(timeShift)]; let totalNumberOfTokens = 60000; await I_SecurityToken.approve(I_VestingEscrowWallet.address, totalNumberOfTokens, {from: token_owner}); @@ -1033,17 +1118,17 @@ contract('VestingEscrowWallet', accounts => { }); it("Should not be able modify vesting schedule for 3 beneficiary's addresses -- fail because of arrays sizes mismatch", async () => { - let timeShift = durationUtil.seconds(100); - let startTimes = [latestTime() + timeShift, latestTime() + timeShift, latestTime() + timeShift]; + let timeShift = new BN(durationUtil.seconds(100)); + let startTimes = [currentTime.add(timeShift), currentTime.add(timeShift), currentTime.add(timeShift)]; await catchRevert( - I_VestingEscrowWallet.modifyScheduleMulti(beneficiaries, ["template-5-01"], startTimes, {from: wallet_admin}) + I_VestingEscrowWallet.modifyScheduleMulti(beneficiaries, [web3.utils.toHex("template-5-01")], startTimes, {from: wallet_admin}) ); }); it("Should not be able to modify schedules for the beneficiaries -- fail because of permissions check", async () => { - let timeShift = durationUtil.seconds(100); - let startTimes = [latestTime() + timeShift, latestTime() + timeShift, latestTime() + timeShift]; + let timeShift = new BN(durationUtil.seconds(100)); + let startTimes = [currentTime.add(timeShift), currentTime.add(timeShift), currentTime.add(timeShift)]; await catchRevert( I_VestingEscrowWallet.modifyScheduleMulti(beneficiaries, templateNames, startTimes, {from: account_beneficiary1}) @@ -1051,14 +1136,14 @@ contract('VestingEscrowWallet', accounts => { }); it("Should modify vesting schedule for 3 beneficiary's addresses", async () => { - let numberOfTokens = [15000, 15000, 15000]; - let durations = [durationUtil.seconds(50), durationUtil.seconds(50), durationUtil.seconds(50)]; - let frequencies = [durationUtil.seconds(10), durationUtil.seconds(10), durationUtil.seconds(10)]; - let timeShift = durationUtil.seconds(100); - let startTimes = [latestTime() + timeShift, latestTime() + timeShift, latestTime() + timeShift]; + let numberOfTokens = [new BN(15000), new BN(15000), new BN(15000)]; + let durations = [new BN(durationUtil.seconds(50)), new BN(durationUtil.seconds(50)), new BN(durationUtil.seconds(50))]; + let frequencies = [new BN(durationUtil.seconds(10)), new BN(durationUtil.seconds(10)), new BN(durationUtil.seconds(10))]; + let timeShift = new BN(durationUtil.seconds(100)); + let startTimes = [currentTime.add(timeShift), currentTime.add(timeShift), currentTime.add(timeShift)]; const tx = await I_VestingEscrowWallet.modifyScheduleMulti(beneficiaries, templateNames, startTimes, {from: wallet_admin}); - await increaseTime(timeShift + frequencies[0]); + await increaseTime(110); for (let i = 0; i < beneficiaries.length; i++) { let log = tx.logs[i]; @@ -1075,42 +1160,43 @@ contract('VestingEscrowWallet', accounts => { it("Should not be able to send available tokens to the beneficiaries addresses -- fail because of array size", async () => { await catchRevert( - I_VestingEscrowWallet.pushAvailableTokensMulti(0, 3, {from: wallet_admin}) + I_VestingEscrowWallet.pushAvailableTokensMulti(new BN(0), new BN(3), {from: wallet_operator}) ); }); it("Should not be able to send available tokens to the beneficiaries -- fail because of permissions check", async () => { await catchRevert( - I_VestingEscrowWallet.pushAvailableTokensMulti(0, 2, {from: account_beneficiary1}) + I_VestingEscrowWallet.pushAvailableTokensMulti(new BN(0), new BN(2), {from: account_beneficiary1}) ); }); it("Should send available tokens to the beneficiaries addresses", async () => { - const tx = await I_VestingEscrowWallet.pushAvailableTokensMulti(0, 2, {from: wallet_admin}); + const tx = await I_VestingEscrowWallet.pushAvailableTokensMulti(0, 2, {from: wallet_operator}); for (let i = 0; i < beneficiaries.length; i++) { let log = tx.logs[i]; let beneficiary = beneficiaries[i]; - assert.equal(log.args._numberOfTokens.toNumber(), 3000); + assert.equal(log.args._numberOfTokens.toString(), 3000); let balance = await I_SecurityToken.balanceOf.call(beneficiary); - assert.equal(balance.toNumber(), 3000); + assert.equal(balance.toString(), 3000); await I_SecurityToken.transfer(token_owner, balance, {from: beneficiary}); await I_VestingEscrowWallet.revokeAllSchedules(beneficiary, {from: wallet_admin}); await I_VestingEscrowWallet.removeTemplate(templateNames[i], {from: wallet_admin}); let unassignedTokens = await I_VestingEscrowWallet.unassignedTokens.call(); - await I_VestingEscrowWallet.sendToTreasury(unassignedTokens, {from: wallet_admin}); + await I_VestingEscrowWallet.sendToTreasury(unassignedTokens, {from: wallet_operator}); } }); it("Should not be able to add schedules from template to the beneficiaries -- fail because of permissions check", async () => { - let templateName = "template-6-01"; + let templateName = web3.utils.toHex("template-6-01"); let numberOfTokens = 18000; let duration = durationUtil.weeks(3); let frequency = durationUtil.weeks(1); let templateNames = [templateName, templateName, templateName]; - let startTimes = [latestTime() + durationUtil.seconds(100), latestTime() + durationUtil.seconds(100), latestTime() + durationUtil.seconds(100)]; + let startTime = currentTime.add(new BN(durationUtil.seconds(100))); + let startTimes = [startTime, startTime, startTime]; let totalNumberOfTokens = numberOfTokens * 3; await I_SecurityToken.approve(I_VestingEscrowWallet.address, totalNumberOfTokens, {from: token_owner}); @@ -1123,12 +1209,14 @@ contract('VestingEscrowWallet', accounts => { }); it("Should add schedules from template for 3 beneficiaries", async () => { - let templateName = "template-6-01"; + currentTime = new BN(await latestTime()); + let templateName = web3.utils.toHex("template-6-01"); let numberOfTokens = 18000; let duration = durationUtil.weeks(3); let frequency = durationUtil.weeks(1); let templateNames = [templateName, templateName, templateName]; - let startTimes = [latestTime() + 100, latestTime() + 100, latestTime() + 100]; + let startTime = currentTime.add(new BN(durationUtil.seconds(100))); + let startTimes = [startTime, startTime, startTime]; let tx = await I_VestingEscrowWallet.addScheduleFromTemplateMulti(beneficiaries, templateNames, startTimes, {from: wallet_admin}); for (let i = 0; i < beneficiaries.length; i++) { @@ -1160,7 +1248,7 @@ contract('VestingEscrowWallet', accounts => { } let unassignedTokens = await I_VestingEscrowWallet.unassignedTokens.call(); - await I_VestingEscrowWallet.sendToTreasury(unassignedTokens, {from: wallet_admin}); + await I_VestingEscrowWallet.sendToTreasury(unassignedTokens, {from: wallet_operator}); }); }); @@ -1168,30 +1256,30 @@ contract('VestingEscrowWallet', accounts => { }); function checkTemplateLog(log, templateName, numberOfTokens, duration, frequency) { - assert.equal(web3.utils.hexToUtf8(log.args._name), templateName); - assert.equal(log.args._numberOfTokens.toNumber(), numberOfTokens); - assert.equal(log.args._duration.toNumber(), duration); - assert.equal(log.args._frequency.toNumber(), frequency); + assert.equal(web3.utils.hexToUtf8(log.args._name), web3.utils.hexToUtf8(templateName)); + assert.equal(log.args._numberOfTokens.toString(), numberOfTokens); + assert.equal(log.args._duration.toString(), duration); + assert.equal(log.args._frequency.toString(), frequency); } function checkScheduleLog(log, beneficiary, templateName, startTime) { assert.equal(log.args._beneficiary, beneficiary); - assert.equal(web3.utils.hexToUtf8(log.args._templateName), templateName); - assert.equal(log.args._startTime.toNumber(), startTime); + assert.equal(web3.utils.hexToUtf8(log.args._templateName), web3.utils.hexToUtf8(templateName)); + assert.equal(log.args._startTime.toString(), startTime); } function checkSchedule(schedule, numberOfTokens, duration, frequency, startTime, state) { - assert.equal(schedule[0].toNumber(), numberOfTokens); - assert.equal(schedule[1].toNumber(), duration); - assert.equal(schedule[2].toNumber(), frequency); - assert.equal(schedule[3].toNumber(), startTime); - assert.equal(schedule[5].toNumber(), state); + assert.equal(schedule[0].toString(), numberOfTokens); + assert.equal(schedule[1].toString(), duration); + assert.equal(schedule[2].toString(), frequency); + assert.equal(schedule[3].toString(), startTime); + assert.equal(schedule[5].toString(), state); } function getTotalNumberOfTokens(schedules) { - let numberOfTokens = 0; + let numberOfTokens = new BN(0); for (let i = 0; i < schedules.length; i++) { - numberOfTokens += schedules[i].numberOfTokens; + numberOfTokens = numberOfTokens.add(new BN(schedules[i].numberOfTokens)); } return numberOfTokens; } diff --git a/test/za_datastore.js b/test/za_datastore.js new file mode 100644 index 000000000..701d27831 --- /dev/null +++ b/test/za_datastore.js @@ -0,0 +1,480 @@ +import latestTime from "./helpers/latestTime"; +import { catchRevert } from "./helpers/exceptions"; +import { takeSnapshot, increaseTime, revertToSnapshot } from "./helpers/time"; +import { setUpPolymathNetwork } from "./helpers/createInstances"; +const SecurityToken = artifacts.require("./SecurityToken.sol"); +const DataStore = artifacts.require("./DataStore.sol"); +const STGetter = artifacts.require("./STGetter.sol"); + +const Web3 = require("web3"); +let BN = Web3.utils.BN; +const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); // Hardcoded development port + +contract("Data store", async (accounts) => { + // Accounts Variable declaration + let account_polymath; + let token_owner; + + // Contract Instance Declaration + let I_GeneralTransferManagerFactory; + let I_SecurityTokenRegistryProxy; + let I_ModuleRegistry; + let I_FeatureRegistry; + let I_SecurityTokenRegistry; + let I_STRProxied; + let I_STFactory; + let I_SecurityToken; + let I_PolyToken; + let I_PolymathRegistry; + let I_DataStore; + let I_ModuleRegistryProxy; + let I_MRProxied; + let I_STRGetter; + let I_STGetter; + let stGetter; + // SecurityToken Details + const name = "Team"; + const symbol = "sap"; + const tokenDetails = "This is equity type of issuance"; + const contact = "team@polymath.network"; + const key = "0x41"; + const key2 = "0x42"; + const bytes32data = "0x4200000000000000000000000000000000000000000000000000000000000000"; + const bytes32data2 = "0x4400000000000000000000000000000000000000000000000000000000000000"; + + // Initial fee for ticker registry and security token registry + const initRegFee = new BN(web3.utils.toWei("1000")); + + const address_zero = "0x0000000000000000000000000000000000000000"; + const address_one = "0x0000000000000000000000000000000000000001"; + const address_two = "0x0000000000000000000000000000000000000002"; + + before(async () => { + account_polymath = accounts[0]; + token_owner = accounts[1]; + + // Step 1: Deploy the genral PM ecosystem + let instances = await setUpPolymathNetwork(account_polymath, token_owner); + + [ + I_PolymathRegistry, + I_PolyToken, + I_FeatureRegistry, + I_ModuleRegistry, + I_ModuleRegistryProxy, + I_MRProxied, + I_GeneralTransferManagerFactory, + I_STFactory, + I_SecurityTokenRegistry, + I_SecurityTokenRegistryProxy, + I_STRProxied, + I_STRGetter, + I_STGetter + ] = instances; + + + + // Printing all the contract addresses + console.log(` + --------------------- Polymath Network Smart Contracts: --------------------- + PolymathRegistry: ${I_PolymathRegistry.address} + SecurityTokenRegistryProxy: ${I_SecurityTokenRegistryProxy.address} + SecurityTokenRegistry: ${I_SecurityTokenRegistry.address} + ModuleRegistry: ${I_ModuleRegistry.address} + FeatureRegistry: ${I_FeatureRegistry.address} + + STFactory: ${I_STFactory.address} + GeneralTransferManagerFactory: ${I_GeneralTransferManagerFactory.address} + ----------------------------------------------------------------------------- + `); + }); + + describe("Generate the SecurityToken", async () => { + it("Should register the ticker before the generation of the security token", async () => { + await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); + let tx = await I_STRProxied.registerNewTicker(token_owner, symbol, { from: token_owner }); + assert.equal(tx.logs[0].args._owner, token_owner); + assert.equal(tx.logs[0].args._ticker, symbol.toUpperCase()); + }); + + it("Should generate the new security token with the same symbol as registered above", async () => { + await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); + + let tx = await I_STRProxied.generateNewSecurityToken(name, symbol, tokenDetails, false, token_owner, 0, { from: token_owner }); + + // Verify the successful generation of the security token + assert.equal(tx.logs[1].args._ticker, symbol.toUpperCase(), "SecurityToken doesn't get deployed"); + + I_SecurityToken = await SecurityToken.at(tx.logs[1].args._securityTokenAddress); + stGetter = await STGetter.at(I_SecurityToken.address); + assert.equal(await stGetter.getTreasuryWallet.call(), token_owner, "Incorrect wallet set"); + const log = (await I_SecurityToken.getPastEvents('ModuleAdded', { filter: { transactionHash: tx.transactionHash } }))[0]; + + // Verify that GeneralTransferManager module get added successfully or not + assert.equal(log.args._types[0].toNumber(), 2); + assert.equal(web3.utils.toUtf8(log.args._name), "GeneralTransferManager"); + }); + + it("Should fetch data store address", async () => { + I_DataStore = await DataStore.at(await I_SecurityToken.dataStore()); + }); + }); + + describe("Should attach to security token securely", async () => { + it("Should be attached to a security token upon deployment", async () => { + assert.equal(await I_DataStore.securityToken(), I_SecurityToken.address, "Incorrect Security Token attached"); + }); + + it("Should not allow non-issuer to change security token address", async () => { + await catchRevert(I_DataStore.setSecurityToken(address_one, { from: account_polymath })); + }); + + it("Should allow issuer to change security token address", async () => { + let snapId = await takeSnapshot(); + await I_DataStore.setSecurityToken(address_one, { from: token_owner }); + assert.equal(await I_DataStore.securityToken(), address_one, "Incorrect Security Token attached"); + await revertToSnapshot(snapId); + assert.equal(await I_DataStore.securityToken(), I_SecurityToken.address, "Incorrect Security Token attached"); + }); + }); + + describe("Should set data correctly", async () => { + it("Should set and fetch uint256 correctly", async () => { + await catchRevert(I_DataStore.setUint256("0x0", 1, { from: token_owner })); + await I_DataStore.setUint256(key, 1, { from: token_owner }); + assert.equal((await I_DataStore.getUint256(key)).toNumber(), 1, "Incorrect Data Inserted"); + }); + + it("Should set and fetch bytes32 correctly", async () => { + await I_DataStore.setBytes32(key, bytes32data, { from: token_owner }); + assert.equal(await I_DataStore.getBytes32(key), bytes32data, "Incorrect Data Inserted"); + }); + + it("Should set and fetch address correctly", async () => { + await I_DataStore.setAddress(key, address_one, { from: token_owner }); + assert.equal(await I_DataStore.getAddress(key), address_one, "Incorrect Data Inserted"); + }); + + it("Should set and fetch string correctly", async () => { + await I_DataStore.setString(key, name, { from: token_owner }); + assert.equal(await I_DataStore.getString(key), name, "Incorrect Data Inserted"); + }); + + it("Should set and fetch bytes correctly", async () => { + await I_DataStore.setBytes(key, bytes32data, { from: token_owner }); + assert.equal(await I_DataStore.getBytes(key), bytes32data, "Incorrect Data Inserted"); + }); + + it("Should set and fetch bool correctly", async () => { + await I_DataStore.setBool(key, true, { from: token_owner }); + assert.equal(await I_DataStore.getBool(key), true, "Incorrect Data Inserted"); + }); + + it("Should set and fetch uint256 array correctly", async () => { + let arr = [1, 2]; + await I_DataStore.setUint256Array(key, arr, { from: token_owner }); + let arr2 = await I_DataStore.getUint256Array(key); + let arrLen = await I_DataStore.getUint256ArrayLength(key); + let arrElement2 = await I_DataStore.getUint256ArrayElement(key, 1); + assert.equal(arr2[0].toNumber(), arr[0], "Incorrect Data Inserted"); + assert.equal(arr2[1].toNumber(), arr[1], "Incorrect Data Inserted"); + assert.equal(arrLen, arr.length, "Incorrect Array Length"); + assert.equal(arrElement2.toNumber(), arr[1], "Incorrect array element"); + }); + + it("Should set and fetch bytes32 array correctly", async () => { + let arr = [bytes32data, bytes32data2]; + await I_DataStore.setBytes32Array(key, arr, { from: token_owner }); + let arr2 = await I_DataStore.getBytes32Array(key); + let arrLen = await I_DataStore.getBytes32ArrayLength(key); + let arrElement2 = await I_DataStore.getBytes32ArrayElement(key, 1); + assert.equal(arr2[0], arr[0], "Incorrect Data Inserted"); + assert.equal(arr2[1], arr[1], "Incorrect Data Inserted"); + assert.equal(arrLen, arr.length, "Incorrect Array Length"); + assert.equal(arrElement2, arr[1], "Incorrect array element"); + }); + + it("Should set and fetch address array correctly", async () => { + let arr = [address_zero, address_one]; + await I_DataStore.setAddressArray(key, arr, { from: token_owner }); + let arr2 = await I_DataStore.getAddressArray(key); + let arrLen = await I_DataStore.getAddressArrayLength(key); + let arrElement2 = await I_DataStore.getAddressArrayElement(key, 1); + assert.equal(arr2[0], arr[0], "Incorrect Data Inserted"); + assert.equal(arr2[1], arr[1], "Incorrect Data Inserted"); + assert.equal(arrLen, arr.length, "Incorrect Array Length"); + assert.equal(arrElement2, arr[1], "Incorrect array element"); + }); + + it("Should set and fetch bool array correctly", async () => { + let arr = [false, true]; + await I_DataStore.setBoolArray(key, arr, { from: token_owner }); + let arr2 = await I_DataStore.getBoolArray(key); + let arrLen = await I_DataStore.getBoolArrayLength(key); + let arrElement2 = await I_DataStore.getBoolArrayElement(key, 1); + assert.equal(arr2[0], arr[0], "Incorrect Data Inserted"); + assert.equal(arr2[1], arr[1], "Incorrect Data Inserted"); + assert.equal(arrLen, arr.length, "Incorrect Array Length"); + assert.equal(arrElement2, arr[1], "Incorrect array element"); + }); + + it("Should insert uint256 into Array", async () => { + let arrLen = await I_DataStore.getUint256ArrayLength(key); + await I_DataStore.insertUint256(key, new BN(10), { from: token_owner }); + let arrElement = await I_DataStore.getUint256ArrayElement(key, arrLen.toNumber()); + let arrElements = await I_DataStore.getUint256ArrayElements(key, 0, arrLen.toNumber()); + assert.equal(arrElement.toNumber(), arrElements[arrLen.toNumber()].toNumber()); + assert.equal(arrLen.toNumber() + 1, (await I_DataStore.getUint256ArrayLength(key)).toNumber(), "Incorrect Array Length"); + assert.equal(arrElement.toNumber(), 10, "Incorrect array element"); + }); + + it("Should insert bytes32 into Array", async () => { + let arrLen = await I_DataStore.getBytes32ArrayLength(key); + await I_DataStore.insertBytes32(key, bytes32data, { from: token_owner }); + let arrElement = await I_DataStore.getBytes32ArrayElement(key, arrLen.toNumber()); + let arrElements = await I_DataStore.getBytes32ArrayElements(key, 0, arrLen.toNumber()); + assert.equal(arrElement, arrElements[arrLen.toNumber()]); + assert.equal(arrLen.toNumber() + 1, (await I_DataStore.getBytes32ArrayLength(key)).toNumber(), "Incorrect Array Length"); + assert.equal(arrElement, bytes32data, "Incorrect array element"); + }); + + it("Should insert address into Array", async () => { + let arrLen = await I_DataStore.getAddressArrayLength(key); + await I_DataStore.insertAddress(key, address_one, { from: token_owner }); + let arrElement = await I_DataStore.getAddressArrayElement(key, arrLen.toNumber()); + let arrElements = await I_DataStore.getAddressArrayElements(key, 0, arrLen.toNumber()); + assert.equal(arrElement, arrElements[arrLen.toNumber()]); + assert.equal(arrLen.toNumber() + 1, (await I_DataStore.getAddressArrayLength(key)).toNumber(), "Incorrect Array Length"); + assert.equal(arrElement, address_one, "Incorrect array element"); + }); + + it("Should insert bool into Array", async () => { + let arrLen = await I_DataStore.getBoolArrayLength(key); + await I_DataStore.insertBool(key, true, { from: token_owner }); + let arrElement = await I_DataStore.getBoolArrayElement(key, arrLen.toNumber()); + let arrElements = await I_DataStore.getBoolArrayElements(key, 0, arrLen.toNumber()); + assert.equal(arrElement, arrElements[arrLen.toNumber()]); + assert.equal(arrLen.toNumber() + 1, (await I_DataStore.getBoolArrayLength(key)).toNumber(), "Incorrect Array Length"); + assert.equal(arrElement, true, "Incorrect array element"); + }); + + it("Should delete uint256 from Array", async () => { + let arrLen = await I_DataStore.getUint256ArrayLength(key); + let indexToDelete = arrLen.toNumber() - 2; + let lastElement = await I_DataStore.getUint256ArrayElement(key, arrLen.toNumber() - 1); + await I_DataStore.deleteUint256(key, indexToDelete, { from: token_owner }); + assert.equal(arrLen.toNumber() - 1, (await I_DataStore.getUint256ArrayLength(key)).toNumber(), "Incorrect Array Length"); + assert.equal(lastElement.toNumber(), (await I_DataStore.getUint256ArrayElement(key, indexToDelete)).toNumber(), "Incorrect array element"); + }); + + it("Should delete bytes32 from Array", async () => { + let arrLen = await I_DataStore.getBytes32ArrayLength(key); + let indexToDelete = arrLen.toNumber() - 2; + let lastElement = await I_DataStore.getBytes32ArrayElement(key, arrLen.toNumber() - 1); + await I_DataStore.deleteBytes32(key, indexToDelete, { from: token_owner }); + assert.equal(arrLen.toNumber() - 1, (await I_DataStore.getBytes32ArrayLength(key)).toNumber(), "Incorrect Array Length"); + assert.equal(lastElement, await I_DataStore.getBytes32ArrayElement(key, indexToDelete), "Incorrect array element"); + }); + + it("Should delete address from Array", async () => { + let arrLen = await I_DataStore.getAddressArrayLength(key); + let indexToDelete = arrLen.toNumber() - 2; + let lastElement = await I_DataStore.getAddressArrayElement(key, arrLen.toNumber() - 1); + await I_DataStore.deleteAddress(key, indexToDelete, { from: token_owner }); + assert.equal(arrLen.toNumber() - 1, (await I_DataStore.getAddressArrayLength(key)).toNumber(), "Incorrect Array Length"); + assert.equal(lastElement, await I_DataStore.getAddressArrayElement(key, indexToDelete), "Incorrect array element"); + }); + + it("Should delete bool from Array", async () => { + let arrLen = await I_DataStore.getBoolArrayLength(key); + let indexToDelete = arrLen.toNumber() - 2; + let lastElement = await I_DataStore.getBoolArrayElement(key, arrLen.toNumber() - 1); + await I_DataStore.deleteBool(key, indexToDelete, { from: token_owner }); + assert.equal(arrLen.toNumber() - 1, (await I_DataStore.getBoolArrayLength(key)).toNumber(), "Incorrect Array Length"); + assert.equal(lastElement, await I_DataStore.getBoolArrayElement(key, indexToDelete), "Incorrect array element"); + }); + + it("Should set and fetch multiple uint256 correctly", async () => { + await catchRevert(I_DataStore.setUint256Multi([key], [1,2], { from: token_owner })); + await I_DataStore.setUint256Multi([key, key2], [1,2], { from: token_owner }); + assert.equal((await I_DataStore.getUint256(key)).toNumber(), 1, "Incorrect Data Inserted"); + assert.equal((await I_DataStore.getUint256(key2)).toNumber(), 2, "Incorrect Data Inserted"); + }); + + it("Should set and fetch multiple bytes32 correctly", async () => { + await I_DataStore.setBytes32Multi([key, key2], [bytes32data, bytes32data2], { from: token_owner }); + assert.equal(await I_DataStore.getBytes32(key), bytes32data, "Incorrect Data Inserted"); + assert.equal(await I_DataStore.getBytes32(key2), bytes32data2, "Incorrect Data Inserted"); + }); + + it("Should set and fetch multiple address correctly", async () => { + await I_DataStore.setAddressMulti([key, key2], [address_one, address_two], { from: token_owner }); + assert.equal(await I_DataStore.getAddress(key), address_one, "Incorrect Data Inserted"); + assert.equal(await I_DataStore.getAddress(key2), address_two, "Incorrect Data Inserted"); + }); + + it("Should set and fetch multiple bool correctly", async () => { + await I_DataStore.setBoolMulti([key, key2], [true, true], { from: token_owner }); + assert.equal(await I_DataStore.getBool(key), true, "Incorrect Data Inserted"); + assert.equal(await I_DataStore.getBool(key2), true, "Incorrect Data Inserted"); + }); + + it("Should insert multiple uint256 into multiple Array", async () => { + let arrLen = await I_DataStore.getUint256ArrayLength(key); + let arrLen2 = await I_DataStore.getUint256ArrayLength(key2); + await I_DataStore.insertUint256Multi([key, key2], [10, 20], { from: token_owner }); + let arrElement = await I_DataStore.getUint256ArrayElement(key, arrLen.toNumber()); + let arrElement2 = await I_DataStore.getUint256ArrayElement(key2, arrLen2.toNumber()); + assert.equal(arrLen.toNumber() + 1, (await I_DataStore.getUint256ArrayLength(key)).toNumber(), "Incorrect Array Length"); + assert.equal(arrElement.toNumber(), 10, "Incorrect array element"); + assert.equal(arrLen2.toNumber() + 1, (await I_DataStore.getUint256ArrayLength(key2)).toNumber(), "Incorrect Array Length"); + assert.equal(arrElement2.toNumber(), 20, "Incorrect array element"); + }); + + it("Should insert multiple bytes32 into multiple Array", async () => { + let arrLen = await I_DataStore.getBytes32ArrayLength(key); + let arrLen2 = await I_DataStore.getBytes32ArrayLength(key2); + await I_DataStore.insertBytes32Multi([key, key2], [bytes32data, bytes32data2], { from: token_owner }); + let arrElement = await I_DataStore.getBytes32ArrayElement(key, arrLen.toNumber()); + let arrElement2 = await I_DataStore.getBytes32ArrayElement(key2, arrLen2.toNumber()); + assert.equal(arrLen.toNumber() + 1, (await I_DataStore.getBytes32ArrayLength(key)).toNumber(), "Incorrect Array Length"); + assert.equal(arrLen2.toNumber() + 1, (await I_DataStore.getBytes32ArrayLength(key2)).toNumber(), "Incorrect Array Length"); + assert.equal(arrElement, bytes32data, "Incorrect array element"); + assert.equal(arrElement2, bytes32data2, "Incorrect array element"); + }); + + it("Should insert multiple address into multiple Array", async () => { + let arrLen = await I_DataStore.getAddressArrayLength(key); + let arrLen2 = await I_DataStore.getAddressArrayLength(key2); + await I_DataStore.insertAddressMulti([key, key2], [address_one, address_two], { from: token_owner }); + let arrElement = await I_DataStore.getAddressArrayElement(key, arrLen.toNumber()); + let arrElement2 = await I_DataStore.getAddressArrayElement(key2, arrLen2.toNumber()); + assert.equal(arrLen.toNumber() + 1, (await I_DataStore.getAddressArrayLength(key)).toNumber(), "Incorrect Array Length"); + assert.equal(arrLen2.toNumber() + 1, (await I_DataStore.getAddressArrayLength(key2)).toNumber(), "Incorrect Array Length"); + assert.equal(arrElement, address_one, "Incorrect array element"); + assert.equal(arrElement2, address_two, "Incorrect array element"); + }); + + it("Should insert multiple bool into multiple Array", async () => { + let arrLen = await I_DataStore.getBoolArrayLength(key); + let arrLen2 = await I_DataStore.getBoolArrayLength(key2); + await I_DataStore.insertBoolMulti([key, key2], [true, true], { from: token_owner }); + let arrElement = await I_DataStore.getBoolArrayElement(key, arrLen.toNumber()); + let arrElement2 = await I_DataStore.getBoolArrayElement(key2, arrLen2.toNumber()); + assert.equal(arrLen.toNumber() + 1, (await I_DataStore.getBoolArrayLength(key)).toNumber(), "Incorrect Array Length"); + assert.equal(arrLen2.toNumber() + 1, (await I_DataStore.getBoolArrayLength(key2)).toNumber(), "Incorrect Array Length"); + assert.equal(arrElement, true, "Incorrect array element"); + assert.equal(arrElement2, true, "Incorrect array element"); + }); + }); + + describe("Should not allow unautohrized modification to data", async () => { + it("Should not allow unauthorized addresses to modify uint256", async () => { + await catchRevert(I_DataStore.setUint256(key, new BN(1), { from: account_polymath })); + }); + + it("Should not allow unauthorized addresses to modify bytes32", async () => { + await catchRevert(I_DataStore.setBytes32(key, bytes32data, { from: account_polymath })); + }); + + it("Should not allow unauthorized addresses to modify address", async () => { + await catchRevert(I_DataStore.setAddress(key, address_one, { from: account_polymath })); + }); + + it("Should not allow unauthorized addresses to modify string", async () => { + await catchRevert(I_DataStore.setString(key, name, { from: account_polymath })); + }); + + it("Should not allow unauthorized addresses to modify bytes", async () => { + await catchRevert(I_DataStore.setBytes32(key, bytes32data, { from: account_polymath })); + }); + + it("Should not allow unauthorized addresses to modify bool", async () => { + await catchRevert(I_DataStore.setBool(key, true, { from: account_polymath })); + }); + + it("Should not allow unauthorized addresses to modify uint256 array", async () => { + let arr = [1, 2]; + await catchRevert(I_DataStore.setUint256Array(key, arr, { from: account_polymath })); + }); + + it("Should not allow unauthorized addresses to modify bytes32 array", async () => { + let arr = [bytes32data, bytes32data2]; + await catchRevert(I_DataStore.setBytes32Array(key, arr, { from: account_polymath })); + }); + + it("Should not allow unauthorized addresses to modify address array", async () => { + let arr = [address_zero, address_one]; + await catchRevert(I_DataStore.setAddressArray(key, arr, { from: account_polymath })); + }); + + it("Should not allow unauthorized addresses to modify bool array", async () => { + let arr = [false, true]; + await catchRevert(I_DataStore.setBoolArray(key, arr, { from: account_polymath })); + }); + + it("Should not allow unauthorized addresses to insert uint256 into Array", async () => { + await catchRevert(I_DataStore.insertUint256(key, new BN(10), { from: account_polymath })); + }); + + it("Should not allow unauthorized addresses to insert bytes32 into Array", async () => { + await catchRevert(I_DataStore.insertBytes32(key, bytes32data, { from: account_polymath })); + }); + + it("Should not allow unauthorized addresses to insert address into Array", async () => { + await catchRevert(I_DataStore.insertAddress(key, address_one, { from: account_polymath })); + }); + + it("Should not allow unauthorized addresses to insert bool into Array", async () => { + await catchRevert(I_DataStore.insertBool(key, true, { from: account_polymath })); + }); + + it("Should not allow unauthorized addresses to delete uint256 from Array", async () => { + await catchRevert(I_DataStore.deleteUint256(key, 0, { from: account_polymath })); + }); + + it("Should not allow unauthorized addresses to delete bytes32 from Array", async () => { + await catchRevert(I_DataStore.deleteBytes32(key, 0, { from: account_polymath })); + }); + + it("Should not allow unauthorized addresses to delete address from Array", async () => { + await catchRevert(I_DataStore.deleteAddress(key, 0, { from: account_polymath })); + }); + + it("Should not allow unauthorized addresses to delete bool from Array", async () => { + await catchRevert(I_DataStore.deleteBool(key, 0, { from: account_polymath })); + }); + + it("Should not allow unauthorized addresses to modify multiple uint256", async () => { + await catchRevert(I_DataStore.setUint256Multi([key, key2], [1,2], { from: account_polymath })); + }); + + it("Should not allow unauthorized addresses to modify multiple bytes32", async () => { + await catchRevert(I_DataStore.setBytes32Multi([key, key2], [bytes32data, bytes32data2], { from: account_polymath })); + }); + + it("Should not allow unauthorized addresses to modify multiple address", async () => { + await catchRevert(I_DataStore.setAddressMulti([key, key2], [address_one, address_two], { from: account_polymath })); + }); + + it("Should not allow unauthorized addresses to modify multiple bool", async () => { + await catchRevert(I_DataStore.setBoolMulti([key, key2], [true, true], { from: account_polymath })); + }); + + it("Should not allow unauthorized addresses to insert multiple uint256 into multiple Array", async () => { + await catchRevert(I_DataStore.insertUint256Multi([key, key2], [10, 20], { from: account_polymath })); + }); + + it("Should not allow unauthorized addresses to insert multiple bytes32 into multiple Array", async () => { + await catchRevert(I_DataStore.insertBytes32Multi([key, key2], [bytes32data, bytes32data2], { from: account_polymath })); + }); + + it("Should not allow unauthorized addresses to insert multiple address into multiple Array", async () => { + await catchRevert(I_DataStore.insertAddressMulti([key, key2], [address_one, address_two], { from: account_polymath })); + }); + + it("Should not allow unauthorized addresses to insert multiple bool into multiple Array", async () => { + await catchRevert(I_DataStore.insertBoolMulti([key, key2], [true, true], { from: account_polymath })); + }); + }); +}); diff --git a/test/zb_signed_transfer_manager.js b/test/zb_signed_transfer_manager.js new file mode 100644 index 000000000..396ae1fa5 --- /dev/null +++ b/test/zb_signed_transfer_manager.js @@ -0,0 +1,309 @@ +import latestTime from "./helpers/latestTime"; +import { duration, promisifyLogWatch, latestBlock } from "./helpers/utils"; +import { takeSnapshot, increaseTime, revertToSnapshot } from "./helpers/time"; +import { getSignSTMData } from "./helpers/signData"; +import { pk } from "./helpers/testprivateKey"; +import { encodeProxyCall, encodeModuleCall } from "./helpers/encodeCall"; +import { catchRevert } from "./helpers/exceptions"; +import { setUpPolymathNetwork, deployGPMAndVerifyed, deploySignedTMAndVerifyed} from "./helpers/createInstances"; + +const DummySTO = artifacts.require("./DummySTO.sol"); +const SecurityToken = artifacts.require("./SecurityToken.sol"); +const GeneralTransferManager = artifacts.require("./GeneralTransferManager"); +const GeneralPermissionManager = artifacts.require("./GeneralPermissionManager"); +const SignedTransferManager = artifacts.require("./SignedTransferManager"); +const STGetter = artifacts.require("./STGetter.sol"); + +const Web3 = require("web3"); +let BN = Web3.utils.BN; +const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); // Hardcoded development port + +contract("SignedTransferManager", accounts => { + // Accounts Variable declaration + let account_polymath; + let account_issuer; + let token_owner; + let token_owner_pk; + let account_investor1; + let account_investor2; + let account_investor3; + let account_investor4; + + // Contract Instance Declaration + let I_GeneralPermissionManagerFactory; + let I_GeneralTransferManagerFactory; + let I_SecurityTokenRegistryProxy; + let I_GeneralPermissionManager; + let I_GeneralTransferManager; + let I_ModuleRegistryProxy; + let I_ModuleRegistry; + let I_FeatureRegistry; + let I_SecurityTokenRegistry; + let I_DummySTOFactory; + let I_STFactory; + let I_SecurityToken; + let I_STRProxied; + let I_MRProxied; + let I_DummySTO; + let I_PolyToken; + let I_PolymathRegistry; + let I_SignedTransferManagerFactory; + let P_SignedTransferManagerFactory; + let I_SignedTransferManager; + let I_STGetter; + let stGetter; + + // SecurityToken Details + const name = "Team"; + const symbol = "sap"; + const tokenDetails = "This is equity type of issuance"; + const decimals = 18; + const contact = "team@polymath.network"; + const address_zero = "0x0000000000000000000000000000000000000000"; + + // Module key + const delegateManagerKey = 1; + const transferManagerKey = 2; + const stoKey = 3; + + // Initial fee for ticker registry and security token registry + const initRegFee = web3.utils.toWei("1000"); + + let currentTime; + let validFrom = new BN(0); + + before(async () => { + // Accounts setup + currentTime = new BN(await latestTime()); + account_polymath = accounts[0]; + account_issuer = accounts[1]; + + token_owner = account_issuer; + token_owner_pk = pk.account_1; + + account_investor1 = accounts[8]; + account_investor2 = accounts[9]; + account_investor3 = accounts[6]; + account_investor4 = accounts[7]; + + // Step 1: Deploy the genral PM ecosystem + let instances = await setUpPolymathNetwork(account_polymath, token_owner); + + [ + I_PolymathRegistry, + I_PolyToken, + I_FeatureRegistry, + I_ModuleRegistry, + I_ModuleRegistryProxy, + I_MRProxied, + I_GeneralTransferManagerFactory, + I_STFactory, + I_SecurityTokenRegistry, + I_SecurityTokenRegistryProxy, + I_STRProxied, + I_STGetter + ] = instances; + + // STEP 2: Deploy the GeneralPermissionManagerFactory + [I_GeneralPermissionManagerFactory] = await deployGPMAndVerifyed(account_polymath, I_MRProxied, new BN(0)); + // STEP 3: Deploy the SignedTransferManagerFactory + [I_SignedTransferManagerFactory] = await deploySignedTMAndVerifyed(account_polymath, I_MRProxied, new BN(0)); + // STEP 4: Deploy the Paid SignedTransferManagerFactory + [P_SignedTransferManagerFactory] = await deploySignedTMAndVerifyed(account_polymath, I_MRProxied, web3.utils.toWei("500", "ether")); + + // Printing all the contract addresses + console.log(` + --------------------- Polymath Network Smart Contracts: --------------------- + PolymathRegistry: ${I_PolymathRegistry.address} + SecurityTokenRegistryProxy: ${I_SecurityTokenRegistryProxy.address} + SecurityTokenRegistry: ${I_SecurityTokenRegistry.address} + ModuleRegistryProxy: ${I_ModuleRegistryProxy.address} + ModuleRegistry: ${I_ModuleRegistry.address} + FeatureRegistry: ${I_FeatureRegistry.address} + + ManualApprovalTransferManagerFactory: ${I_SignedTransferManagerFactory.address} + + + ----------------------------------------------------------------------------- + `); + }); + + describe("Generate the SecurityToken", async () => { + it("Should register the ticker before the generation of the security token", async () => { + await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); + let tx = await I_STRProxied.registerNewTicker(token_owner, symbol, { from: token_owner }); + assert.equal(tx.logs[0].args._owner, token_owner); + assert.equal(tx.logs[0].args._ticker, symbol.toUpperCase()); + }); + + it("Should generate the new security token with the same symbol as registered above", async () => { + await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); + + let tx = await I_STRProxied.generateNewSecurityToken(name, symbol, tokenDetails, false, token_owner, 0, { from: token_owner }); + + // Verify the successful generation of the security token + assert.equal(tx.logs[1].args._ticker, symbol.toUpperCase(), "SecurityToken doesn't get deployed"); + + I_SecurityToken = await SecurityToken.at(tx.logs[1].args._securityTokenAddress); + stGetter = await STGetter.at(I_SecurityToken.address); + assert.equal(await stGetter.getTreasuryWallet.call(), token_owner, "Incorrect wallet set"); + const log = (await I_SecurityToken.getPastEvents('ModuleAdded', {filter: {transactionHash: tx.transactionHash}}))[0]; + + // Verify that GeneralTransferManager module get added successfully or not + assert.equal(log.args._types[0].toNumber(), 2); + assert.equal(web3.utils.toAscii(log.args._name).replace(/\u0000/g, ""), "GeneralTransferManager"); + }); + + it("Should initialize the auto attached modules", async () => { + let moduleData = (await stGetter.getModulesByType(2))[0]; + I_GeneralTransferManager = await GeneralTransferManager.at(moduleData); + }); + }); + + + describe("signed transfer manager tests", async () => { + + it("Should Buy the tokens", async () => { + // Add the Investor in to the whitelist + + let tx = await I_GeneralTransferManager.modifyKYCData( + account_investor1, + currentTime, + currentTime, + currentTime.add(new BN(duration.days(10))), + { + from: account_issuer + } + ); + + 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.issue(account_investor1, new BN(web3.utils.toWei("2", "ether")), "0x0", { from: token_owner }); + + assert.equal((await I_SecurityToken.balanceOf(account_investor1)).toString(), new BN(web3.utils.toWei("2", "ether")).toString()); + }); + + + it("Should successfully attach the SignedTransferManager with the security token", async () => { + const tx = await I_SecurityToken.addModule(I_SignedTransferManagerFactory.address, "0x0",new BN(0),new BN(0), false, { from: token_owner }); + assert.equal(tx.logs[2].args._types[0].toNumber(), transferManagerKey, "SignedTransferManager doesn't get deployed"); + assert.equal( + web3.utils.toUtf8(tx.logs[2].args._name), + "SignedTransferManager", + "SignedTransferManager module was not added" + ); + I_SignedTransferManager = await SignedTransferManager.at(tx.logs[2].args._module); + }); + + it("should fail to transfer because transaction is not verified yet.", async () => { + await catchRevert(I_SecurityToken.transfer(account_investor2, web3.utils.toWei("1", "ether"), { from: account_investor1 })); + }); + + it("Should successfully attach the permission manager factory with the security token", async () => { + console.log((await I_GeneralPermissionManagerFactory.setupCostInPoly.call()).toString()); + const tx = await I_SecurityToken.addModule(I_GeneralPermissionManagerFactory.address, "0x0", new BN(0), new BN(0), false, { from: token_owner }); + assert.equal(tx.logs[2].args._types[0].toNumber(), delegateManagerKey, "GeneralPermissionManager doesn't get deployed"); + assert.equal( + web3.utils.toAscii(tx.logs[2].args._name).replace(/\u0000/g, ""), + "GeneralPermissionManager", + "GeneralPermissionManager module was not added" + ); + I_GeneralPermissionManager = await GeneralPermissionManager.at(tx.logs[2].args._module); + }); + + it("should allow to invalidate siganture if sender is the signer and is in the signer list", async () => { + let oneeth = new BN(web3.utils.toWei("1", "ether")); + let signer = web3.eth.accounts.create(); + await web3.eth.personal.importRawKey(signer.privateKey, ""); + await web3.eth.personal.unlockAccount(signer.address, "", 6000); + await web3.eth.sendTransaction({ from: token_owner, to: signer.address, value: oneeth }); + + let log = await I_GeneralPermissionManager.addDelegate(signer.address, web3.utils.fromAscii("My details"), { from: token_owner }); + assert.equal(log.logs[0].args._delegate, signer.address); + await I_GeneralPermissionManager.changePermission(signer.address, I_SignedTransferManager.address, web3.utils.fromAscii("OPERATOR"), true, { + from: token_owner + }); + + let nonce = new BN(10); + let expiry = new BN(currentTime.add(new BN(duration.days(100)))); + let data = await getSignSTMData( + I_SignedTransferManager.address, + nonce, + validFrom, + expiry, + account_investor1, + account_investor2, + oneeth, + signer.privateKey + ); + + assert.equal(await I_SignedTransferManager.checkSignatureValidity(data), true); + await I_SignedTransferManager.invalidateSignature(account_investor1, account_investor2, oneeth, data, {from: signer.address}); + assert.equal(await I_SignedTransferManager.checkSignatureValidity(data), false); + }); + + it("should allow transfer with valid sig", async () => { + let signer = web3.eth.accounts.create(); + let log = await I_GeneralPermissionManager.addDelegate(signer.address, web3.utils.fromAscii("My details"), { from: token_owner }); + assert.equal(log.logs[0].args._delegate, signer.address); + await I_GeneralPermissionManager.changePermission(signer.address, I_SignedTransferManager.address, web3.utils.fromAscii("OPERATOR"), true, { + from: token_owner + }); + let oneeth = new BN(web3.utils.toWei("1", "ether")); + let nonce = new BN(10); + let expiry = new BN(currentTime.add(new BN(duration.days(100)))); + let data = await getSignSTMData( + I_SignedTransferManager.address, + nonce, + validFrom, + expiry, + account_investor1, + account_investor2, + oneeth, + signer.privateKey + ); + + let balance11 = await I_SecurityToken.balanceOf(account_investor1); + let balance21 = await I_SecurityToken.balanceOf(account_investor2); + + assert.equal(await I_SignedTransferManager.checkSignatureValidity(data), true); + + await I_SecurityToken.transferWithData(account_investor2, oneeth, data, {from: account_investor1}); + + assert.equal(await I_SignedTransferManager.checkSignatureValidity(data), false); + await catchRevert(I_SecurityToken.transferWithData(account_investor2, oneeth, data, {from: account_investor1})); + + assert.equal(balance11.sub(oneeth).toString(), (await I_SecurityToken.balanceOf(account_investor1)).toString()); + assert.equal(balance21.add(oneeth).toString(), (await I_SecurityToken.balanceOf(account_investor2)).toString()); + + + }); + + it("should not allow transfer if the signer is not on the signer list", async () => { + let signer = web3.eth.accounts.create(); + let oneeth = new BN(web3.utils.toWei("1", "ether")); + let nonce = new BN(10); + let expiry = new BN(currentTime.add(new BN(duration.days(100)))); + let data = await getSignSTMData( + I_SignedTransferManager.address, + nonce, + validFrom, + expiry, + account_investor1, + account_investor2, + oneeth, + signer.privateKey + ); + + await catchRevert(I_SecurityToken.transferWithData(account_investor2, oneeth, data, {from: account_investor1})); + }); + }); +}); diff --git a/test/zc_plcr_voting_checkpoint.js b/test/zc_plcr_voting_checkpoint.js new file mode 100644 index 000000000..5e5585715 --- /dev/null +++ b/test/zc_plcr_voting_checkpoint.js @@ -0,0 +1,732 @@ +import latestTime from "./helpers/latestTime"; +import {duration, promisifyLogWatch} from "./helpers/utils"; +import { takeSnapshot,increaseTime, revertToSnapshot} from "./helpers/time"; +import {catchRevert} from "./helpers/exceptions"; +import {deployPLCRVoteCheckpoint, setUpPolymathNetwork, deployGPMAndVerifyed} from "./helpers/createInstances"; + +const SecurityToken = artifacts.require("./SecurityToken.sol"); +const GeneralTransferManager = artifacts.require("./GeneralTransferManager"); +const GeneralPermissionManager = artifacts.require("./GeneralPermissionManager"); +const PLCRVotingCheckpoint = artifacts.require("./PLCRVotingCheckpoint"); +const STGetter = artifacts.require("./STGetter.sol"); + +const Web3 = require("web3"); +let BN = Web3.utils.BN; +const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); // Hardcoded development port + +contract("PLCRVotingCheckpoint", async (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_investor5; + let account_temp; + let account_delegate; + + // Contract Instance Declaration + let I_GeneralPermissionManagerFactory; + let I_SecurityTokenRegistryProxy; + let I_GeneralTransferManagerFactory; + let I_PLCRVotingCheckpointFactory; + let P_PLCRVotingCheckpointFactory; + let I_PLCRVotingCheckpoint; + let I_GeneralTransferManager; + let I_ModuleRegistryProxy; + let I_ModuleRegistry; + let I_FeatureRegistry; + let I_GeneralPermissionManager; + let I_SecurityTokenRegistry; + let I_STRProxied; + let I_STFactory; + let I_SecurityToken; + let I_PolyToken; + let I_MRProxied; + let I_PolymathRegistry; + let I_STRGetter; + let I_STGetter; + let stGetter; + + // SecurityToken Details + const name = "Team"; + const symbol = "SAP"; + const tokenDetails = "This is equity type of issuance"; + const decimals = 18; + let saltArray = new Array(); + // Module key + const delegateManagerKey = 1; + const transferManagerKey = 2; + const stoKey = 3; + const checkpointKey = 4; + const burnKey = 5; + + // Initial fee for ticker registry and security token registry + const initRegFee = new BN(web3.utils.toWei("1000")); + + let currentTime; + const address_zero = "0x0000000000000000000000000000000000000000"; + const one_address = "0x0000000000000000000000000000000000000001"; + + before(async () => { + currentTime = new BN(await latestTime()); + 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_investor5 = accounts[4]; + account_temp = accounts[2]; + account_delegate = accounts[3]; + + // ----------- POLYMATH NETWORK Configuration ------------ + + // Step 1: Deploy the genral PM ecosystem + let instances = await setUpPolymathNetwork(account_polymath, token_owner); + + [ + I_PolymathRegistry, + I_PolyToken, + I_FeatureRegistry, + I_ModuleRegistry, + I_ModuleRegistryProxy, + I_MRProxied, + I_GeneralTransferManagerFactory, + I_STFactory, + I_SecurityTokenRegistry, + I_SecurityTokenRegistryProxy, + I_STRProxied, + I_STRGetter, + I_STGetter + ] = instances; + + + // STEP 4: Deploy the WeightedVoteCheckpoint + [I_PLCRVotingCheckpointFactory] = await deployPLCRVoteCheckpoint(account_polymath, I_MRProxied, 0); + [P_PLCRVotingCheckpointFactory] = await deployPLCRVoteCheckpoint(account_polymath, I_MRProxied, new BN(web3.utils.toWei("500"))); + + [I_GeneralPermissionManagerFactory] = await deployGPMAndVerifyed(account_polymath, I_MRProxied, 0); + + // Printing all the contract addresses + console.log(` + --------------------- Polymath Network Smart Contracts: --------------------- + PolymathRegistry: ${I_PolymathRegistry.address} + SecurityTokenRegistryProxy: ${I_SecurityTokenRegistryProxy.address} + SecurityTokenRegistry: ${I_SecurityTokenRegistry.address} + ModuleRegistry: ${I_ModuleRegistry.address} + ModuleRegistryProxy: ${I_ModuleRegistryProxy.address} + FeatureRegistry: ${I_FeatureRegistry.address} + + STFactory: ${I_STFactory.address} + GeneralTransferManagerFactory: ${I_GeneralTransferManagerFactory.address} + + PLCRVotingCheckpointFactory: ${I_PLCRVotingCheckpointFactory.address} + ----------------------------------------------------------------------------- + `); + }); + + function getRandom() { + return Math.floor(Math.random() * 1000000000000000); + } + + describe("\t\t Test case for PLCR Vote Checkpoint module \n", async() => { + + describe("\t\t Attaching the PLCR vote checkpoint module \n", async() => { + + it("\t\t Should register the ticker before the generation of the security token \n", async () => { + await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); + let tx = await I_STRProxied.registerNewTicker(token_owner, symbol, { from: token_owner }); + assert.equal(tx.logs[0].args._owner, token_owner); + assert.equal(tx.logs[0].args._ticker, symbol); + }); + + it("\t\t Should generate the new security token with the same symbol as registered above \n", async () => { + await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); + + let tx = await I_STRProxied.generateNewSecurityToken(name, symbol, tokenDetails, false, token_owner, 0, { from: token_owner }); + + // Verify the successful generation of the security token + assert.equal(tx.logs[1].args._ticker, symbol, "SecurityToken doesn't get deployed"); + + I_SecurityToken = await SecurityToken.at(tx.logs[1].args._securityTokenAddress); + stGetter = await STGetter.at(I_SecurityToken.address); + const log = (await I_SecurityToken.getPastEvents('ModuleAdded', {filter: {transactionHash: tx.transactionHash}}))[0]; + // Verify that GeneralTransferManager module get added successfully or not + assert.equal(log.args._types[0].toNumber(), transferManagerKey); + assert.equal(web3.utils.hexToString(log.args._name), "GeneralTransferManager"); + }); + + it("\t\t Should initialize the auto attached modules \n", async () => { + let moduleData = (await stGetter.getModulesByType(transferManagerKey))[0]; + I_GeneralTransferManager = await GeneralTransferManager.at(moduleData); + }); + + it("\t\t Should attach the voting module with the ST \n", async() => { + let tx = await I_SecurityToken.addModule(I_PLCRVotingCheckpointFactory.address, "0x0", new BN(0), new BN(0), false, {from: token_owner}); + assert.equal(tx.logs[2].args._types[0], checkpointKey, "Checkpoint doesn't get deployed"); + assert.equal(web3.utils.hexToString(tx.logs[2].args._name), "PLCRVotingCheckpoint", "PLCRVotingCheckpoint module was not added"); + I_PLCRVotingCheckpoint = await PLCRVotingCheckpoint.at(tx.logs[2].args._module); + }); + + it("\t\t Should fail to attach the voting module because allowance is unsufficent \n", async() => { + await catchRevert( + I_SecurityToken.addModule(P_PLCRVotingCheckpointFactory.address, "0x0", new BN(web3.utils.toWei("500")), 0, false, {from: token_owner}) + ); + }); + + it("\t\t Should attach the voting module with the ST \n", async() => { + let id = await takeSnapshot(); + await I_PolyToken.transfer(I_SecurityToken.address, new BN(web3.utils.toWei("2000", "ether")), { from: token_owner }); + let tx = await I_SecurityToken.addModule(P_PLCRVotingCheckpointFactory.address, "0x0", new BN(web3.utils.toWei("2000")), 0, false, {from: token_owner}); + assert.equal(tx.logs[3].args._types[0], checkpointKey, "Checkpoint doesn't get deployed"); + assert.equal(web3.utils.hexToString(tx.logs[3].args._name), "PLCRVotingCheckpoint", "PLCRVotingCheckpoint module was not added"); + await revertToSnapshot(id); + }); + + it("\t\t Should attach the general permission manager", async() => { + let tx = await I_SecurityToken.addModule(I_GeneralPermissionManagerFactory.address, "0x0", 0, 0, false, {from: token_owner}); + assert.equal(tx.logs[2].args._types[0], delegateManagerKey, "Permission manager doesn't get deployed"); + assert.equal(web3.utils.hexToString(tx.logs[2].args._name), "GeneralPermissionManager", "GeneralPermissionManager module was not added"); + I_GeneralPermissionManager = await GeneralPermissionManager.at(tx.logs[2].args._module); + }); + }); + + describe("\t\t Test for createBallot \n", async() => { + + it("\t\t Should fail to create ballot -- bad owner \n", async() => { + await catchRevert( + I_PLCRVotingCheckpoint.createBallot(new BN(duration.days(5)), new BN(duration.days(10)), new BN(3), new BN(46.57).mul(new BN(10).pow(new BN(16))), {from: account_polymath}) + ); + }); + + it("\t\t Should fail to create ballot -- bad commit duration \n", async() => { + await catchRevert( + I_PLCRVotingCheckpoint.createBallot(new BN(0), new BN(duration.days(10)), new BN(3), new BN(46.57).mul(new BN(10).pow(new BN(16))), {from: token_owner}) + ); + }); + + it("\t\t Should fail to create ballot -- bad reveal duration \n", async() => { + await catchRevert( + I_PLCRVotingCheckpoint.createBallot(new BN(duration.days(10)), new BN(0), new BN(3), new BN(46.57).mul(new BN(10).pow(new BN(16))), {from: token_owner}) + ); + }); + + it("\t\t Should fail to create ballot -- bad proposed quorum \n", async() => { + await catchRevert( + I_PLCRVotingCheckpoint.createBallot(new BN(duration.days(10)), new BN(duration.days(10)), new BN(3), new BN(0).mul(new BN(10).pow(new BN(16))), {from: token_owner}) + ); + }); + + it("\t\t Should fail to create ballot -- bad proposed quorum more than 100 % \n", async() => { + await catchRevert( + I_PLCRVotingCheckpoint.createBallot(new BN(duration.days(10)), new BN(duration.days(10)), new BN(3), new BN(46.57).mul(new BN(10).pow(new BN(18))), {from: token_owner}) + ); + }); + + it("\t\t Should fail to create ballot -- bad no of proposals \n", async() => { + await catchRevert( + I_PLCRVotingCheckpoint.createBallot(new BN(duration.days(5)), new BN(duration.days(10)), new BN(0), new BN(46.57).mul(new BN(10).pow(new BN(16))), {from: token_owner}) + ); + }); + + it("\t\t Should fail to create ballot -- bad no of proposals \n", async() => { + await catchRevert( + I_PLCRVotingCheckpoint.createBallot(new BN(duration.days(5)), new BN(duration.days(10)), new BN(1), new BN(46.57).mul(new BN(10).pow(new BN(16))), {from: token_owner}) + ); + }); + + it("\t\t Mint some tokens and transfer to whitelisted investors \n", async() => { + // Whitelist multiple investors + let time = new BN(await latestTime()); + await I_GeneralTransferManager.modifyKYCDataMulti( + [account_investor1, account_investor2, account_investor3, account_investor4, account_investor5], + [time, time, time, time, time], + [time, time, time, time, time], + [time + duration.days(200), time + duration.days(200), time + duration.days(200), time + duration.days(200), time + duration.days(200)], + { + from: token_owner + } + ); + + // mint tokens to whitelisted investors + + await I_SecurityToken.issueMulti( + [account_investor1, account_investor2, account_investor3, account_investor4], + [new BN(web3.utils.toWei("500")), new BN(web3.utils.toWei("1000")), new BN(web3.utils.toWei("5000")), new BN(web3.utils.toWei("100"))], + { + from: token_owner + } + ); + + assert.equal(web3.utils.fromWei((await I_SecurityToken.balanceOf.call(account_investor1)).toString()), 500); + assert.equal(web3.utils.fromWei((await I_SecurityToken.balanceOf.call(account_investor2)).toString()), 1000); + assert.equal(web3.utils.fromWei((await I_SecurityToken.balanceOf.call(account_investor3)).toString()), 5000); + assert.equal(web3.utils.fromWei((await I_SecurityToken.balanceOf.call(account_investor4)).toString()), 100); + }); + + it("\t\t Should fail to create ballot -- Invalid checkpoint Id \n", async() => { + let startTime = new BN(await latestTime()); + let commitTime = new BN(duration.days(4)); + let revealTime = new BN(duration.days(5)); + await catchRevert( + I_PLCRVotingCheckpoint.createCustomBallot(commitTime, revealTime, new BN(3), new BN(50).mul(new BN(10).pow(new BN(16))), new BN(45), startTime, {from: token_owner}) + ); + }); + + it("\t\t Should fail to create ballot -- Invalid start time \n", async() => { + let startTime = new BN(await latestTime()); + let commitTime = new BN(duration.days(4)); + let revealTime = new BN(duration.days(5)); + await catchRevert( + I_PLCRVotingCheckpoint.createCustomBallot(commitTime, revealTime, new BN(3), new BN(50).mul(new BN(10).pow(new BN(16))), new BN(0), 0, {from: token_owner}) + ); + }); + + it("\t\t SHould give admin permission to the delegate \n", async() => { + await I_GeneralPermissionManager.addDelegate(account_delegate, web3.utils.toHex("I am a delegate"), {from: token_owner}); + await I_GeneralPermissionManager.changePermission(account_delegate, I_PLCRVotingCheckpoint.address, web3.utils.toHex("ADMIN"), true, {from: token_owner}); + }); + + it("\t\t Should create the ballot successfully \n", async() => { + let startTime = new BN(await latestTime()).add(new BN(duration.minutes(5))); + let commitTime = new BN(duration.days(4)); + let revealTime = new BN(duration.days(5)); + await I_SecurityToken.createCheckpoint({from: token_owner}); + let checkpointId = await I_SecurityToken.currentCheckpointId.call(); + let tx = await I_PLCRVotingCheckpoint.createCustomBallot(commitTime, revealTime, new BN(3), new BN(47.8).mul(new BN(10).pow(new BN(16))), checkpointId, startTime, {from: account_delegate}); + let timeData = await I_PLCRVotingCheckpoint.getBallotCommitRevealDuration.call(new BN(0)); + assert.equal(timeData[0].toString(), commitTime); + assert.equal(timeData[1].toString(), revealTime); + assert.equal((tx.logs[0].args._noOfProposals).toString(), 3); + assert.equal((tx.logs[0].args._checkpointId).toString(), 1); + assert.equal((tx.logs[0].args._ballotId).toString(), 0); + }); + }); + + describe("\t\t Test case for commitVote \n", async() => { + + it("\t\t Should fail to commitVote -- bad ballot id \n", async() => { + await catchRevert( + I_PLCRVotingCheckpoint.commitVote(new BN(2), web3.utils.toHex("Some secret"), {from: account_investor1}) + ); + }); + + it("\t\t Should fail to commitVote -- not in the commit stage \n", async() => { + let salt = getRandom(); + await catchRevert( + I_PLCRVotingCheckpoint.commitVote(new BN(0), web3.utils.soliditySha3(1, salt), {from: account_investor1}) + ); + }); + + it("\t\t Should fail to commitVote -- secret vote is 0 \n", async() => { + await increaseTime(duration.minutes(7)); // Increase time to make it under the commit stage + await catchRevert( + I_PLCRVotingCheckpoint.commitVote(new BN(0), "0x0", {from: account_investor1}) + ); + }); + + it("\t\t Should change some ballot status \n", async() => { + await I_PLCRVotingCheckpoint.changeBallotStatus(new BN(0), false, {from: token_owner}); + let data = await I_PLCRVotingCheckpoint.getBallotDetails.call(new BN(0)); + assert.isFalse(data[7]); + }); + + it("\t\t Should fail to commitVote because ballot is not active \n", async() => { + let salt = getRandom(); + await catchRevert( + I_PLCRVotingCheckpoint.commitVote(new BN(0), web3.utils.soliditySha3(1, salt), {from: account_investor1}) + ); + }); + + it("\t\t Should change some ballot status \n", async() => { + await I_PLCRVotingCheckpoint.changeBallotStatus(new BN(0), true, {from: token_owner}); + let data = await I_PLCRVotingCheckpoint.getBallotDetails.call(new BN(0)); + assert.isTrue(data[7]); + }); + + it("\t\t Should fail to add voter in ballot exemption list -- address is zero", async() => { + await catchRevert( + I_PLCRVotingCheckpoint.changeBallotExemptedVotersList(new BN(0), "0x0000000000000000000000000000000000000000", true, {from: token_owner}) + ); + }); + + it("\t\t Should fail to add voter in ballot exemption list -- invalid ballot id", async() => { + await catchRevert( + I_PLCRVotingCheckpoint.changeBallotExemptedVotersList(new BN(5), account_investor1, true, {from: token_owner}) + ); + }); + + it("\t\t Should add the voter in to the ballot exemption list", async() => { + let tx = await I_PLCRVotingCheckpoint.changeBallotExemptedVotersList(new BN(0), account_investor1, true, {from: token_owner}); + assert.equal((tx.logs[0].args._ballotId).toString(), 0); + assert.equal(tx.logs[0].args._voter, account_investor1); + assert.equal(tx.logs[0].args._exempt, true); + }); + + it("\t\t Should fail to add voter in ballot exemption list -- doing the same change again", async() => { + await catchRevert( + I_PLCRVotingCheckpoint.changeBallotExemptedVotersList(new BN(0), account_investor1, true, {from: token_owner}) + ); + }); + + it("\t\t Should fail to vote -- voter is present in the exemption list", async() => { + let salt = getRandom(); + await catchRevert( + I_PLCRVotingCheckpoint.commitVote(new BN(0), web3.utils.soliditySha3(2, salt), {from: account_investor1}) + ); + }); + + it("\t\t Should add the multiple voter in to the ballot exemption list -- failed ", async() => { + await catchRevert( + I_PLCRVotingCheckpoint.changeBallotExemptedVotersListMulti(new BN(0), [account_investor1, account_investor2], [false], {from: token_owner}) + ); + }); + + it("\t\t Should add the multiple voter in to the ballot exemption list --failed because of bad msg.sender", async() => { + await catchRevert( + I_PLCRVotingCheckpoint.changeBallotExemptedVotersListMulti(new BN(0), [account_investor1], [false], {from: account_polymath}) + ); + }); + + + it("\t\t Should add the multiple voter in to the ballot exemption list", async() => { + await I_PLCRVotingCheckpoint.changeBallotExemptedVotersListMulti(new BN(0), [account_investor1], [false], {from: token_owner}); + assert.isTrue(await I_PLCRVotingCheckpoint.isVoterAllowed.call(new BN(0), account_investor1)); + }); + + it("\t\t Should successfully vote by account investor1 \n", async() => { + let salt = getRandom(); + saltArray.push(salt); + let tx = await I_PLCRVotingCheckpoint.commitVote(new BN(0), web3.utils.soliditySha3(2, salt), {from: account_investor1}); + assert.equal(tx.logs[0].args._ballotId, 0); + assert.equal(tx.logs[0].args._secretVote, web3.utils.soliditySha3(2, salt)); + assert.equal(tx.logs[0].args._voter, account_investor1); + + let data = await I_PLCRVotingCheckpoint.getBallotDetails.call(new BN(0)); + assert.equal(data[5], 3); + assert.equal(data[7], true); + }); + + it("\t\t Should failed to vote again \n", async() => { + let salt = getRandom(); + await catchRevert( + I_PLCRVotingCheckpoint.commitVote(new BN(0), web3.utils.soliditySha3(1, salt), {from: account_investor1}) + ) + }); + + it("\t\t Should fail to add voter in default exemption list -- address is zero", async() => { + await catchRevert( + I_PLCRVotingCheckpoint.changeDefaultExemptedVotersList("0x0000000000000000000000000000000000000000", true, {from: token_owner}) + ); + }); + + it("\t\t Should add the voter in to the default exemption list", async() => { + let tx = await I_PLCRVotingCheckpoint.changeDefaultExemptedVotersList(account_investor3, true, {from: token_owner}); + assert.equal(tx.logs[0].args._voter, account_investor3); + assert.equal(tx.logs[0].args._exempt, true); + }); + + it("\t\t Should fail to add voter in default exemption list -- doing the same change again", async() => { + await catchRevert( + I_PLCRVotingCheckpoint.changeDefaultExemptedVotersList(account_investor3, true, {from: token_owner}) + ); + }); + + it("\t\t Should fail to vote -- voter is present in the exemption list", async() => { + let salt = getRandom(); + await catchRevert( + I_PLCRVotingCheckpoint.commitVote(new BN(0), web3.utils.soliditySha3(1, salt), {from: account_investor3}) + ); + }); + + it("\t\t Should change the deafult exemption list using Multi function -- failed because of mismatch array length", async() => { + await catchRevert( + I_PLCRVotingCheckpoint.changeDefaultExemptedVotersListMulti([account_investor3, account_investor1], [false], {from: token_owner}) + ); + }); + + it("\t\t Should change the default exemption list by allowing investor 1 to vote", async() => { + await I_PLCRVotingCheckpoint.changeDefaultExemptedVotersListMulti([account_investor3], [false], {from: token_owner}); + assert.isTrue(await I_PLCRVotingCheckpoint.isVoterAllowed.call(new BN(0), account_investor3)); + }); + + it("\t\t Should change the default exemption list using Multi function", async() => { + await I_PLCRVotingCheckpoint.changeDefaultExemptedVotersListMulti([account_investor3, account_investor1, account_investor2], [true, true, true], {from: token_owner}); + assert.isFalse(await I_PLCRVotingCheckpoint.isVoterAllowed.call(new BN(0), account_investor3)); + assert.isFalse(await I_PLCRVotingCheckpoint.isVoterAllowed.call(new BN(0), account_investor1)); + assert.isFalse(await I_PLCRVotingCheckpoint.isVoterAllowed.call(new BN(0), account_investor2)); + assert.equal((await I_PLCRVotingCheckpoint.getDefaultExemptionVotersList.call()).length, 3); + }); + + it("\t\t Should change the default exemption list by allowing investor 1 to vote again", async() => { + await I_PLCRVotingCheckpoint.changeDefaultExemptedVotersList(account_investor3, false, {from: token_owner}); + assert.isTrue(await I_PLCRVotingCheckpoint.isVoterAllowed.call(new BN(0), account_investor3)); + }); + + it("\t\t Should change the default exemption list using Multi function", async() => { + await I_PLCRVotingCheckpoint.changeDefaultExemptedVotersListMulti([account_investor2, account_investor1], [false, false], {from: token_owner}); + assert.isTrue(await I_PLCRVotingCheckpoint.isVoterAllowed.call(new BN(0), account_investor1)); + assert.isTrue(await I_PLCRVotingCheckpoint.isVoterAllowed.call(new BN(0), account_investor2)); + }); + + it("\t\t Should successfully vote by account investor3 \n", async() => { + let salt = getRandom(); + saltArray.push(salt); + let tx = await I_PLCRVotingCheckpoint.commitVote(new BN(0), web3.utils.soliditySha3(1, salt), {from: account_investor3}); + assert.equal(tx.logs[0].args._ballotId, 0); + assert.equal(tx.logs[0].args._secretVote, web3.utils.soliditySha3(1, salt)); + assert.equal(tx.logs[0].args._voter, account_investor3); + + let data = await I_PLCRVotingCheckpoint.getBallotDetails.call(new BN(0)); + assert.equal(data[5], 3); + assert.equal(data[7], true); + }); + + it("\t\t Should successfully vote by account investor2 \n", async() => { + let salt = getRandom(); + saltArray.push(salt); + let tx = await I_PLCRVotingCheckpoint.commitVote(new BN(0), web3.utils.soliditySha3(2, salt), {from: account_investor2}); + assert.equal(tx.logs[0].args._ballotId, 0); + assert.equal(tx.logs[0].args._secretVote, web3.utils.soliditySha3(2, salt)); + assert.equal(tx.logs[0].args._voter, account_investor2); + + let data = await I_PLCRVotingCheckpoint.getBallotDetails.call(new BN(0)); + assert.equal(data[5], 3); + assert.equal(data[7], true); + }); + + it("\t\t Mint some more tokens and transferred to the tokens holders \n", async() => { + await I_SecurityToken.issueMulti( + [account_investor1, account_investor2, account_investor3, account_investor5], + [new BN(web3.utils.toWei("3000")), + new BN(web3.utils.toWei("2000")), + new BN(web3.utils.toWei("500")), + new BN(web3.utils.toWei("3500")) + ], + { + from: token_owner + } + ); + + assert.equal(web3.utils.fromWei((await I_SecurityToken.balanceOf.call(account_investor1)).toString()), 3500); + assert.equal(web3.utils.fromWei((await I_SecurityToken.balanceOf.call(account_investor2)).toString()), 3000); + assert.equal(web3.utils.fromWei((await I_SecurityToken.balanceOf.call(account_investor3)).toString()), 5500); + assert.equal(web3.utils.fromWei((await I_SecurityToken.balanceOf.call(account_investor5)).toString()), 3500); + }); + + it("\t\t Should successfully vote by the investor 4", async() => { + let salt = getRandom(); + saltArray.push(salt); + let tx = await I_PLCRVotingCheckpoint.commitVote(new BN(0), web3.utils.soliditySha3(3, salt), {from: account_investor4}); + assert.equal(tx.logs[0].args._ballotId, 0); + assert.equal(tx.logs[0].args._secretVote, web3.utils.soliditySha3(3, salt)); + assert.equal(tx.logs[0].args._voter, account_investor4); + + let data = await I_PLCRVotingCheckpoint.getBallotDetails.call(new BN(0)); + assert.equal(data[5], 3); + assert.equal(data[7], true); + }) + + it("\t\t Should fail to vote with a zero weight \n", async() => { + let salt = getRandom(); + await catchRevert( + I_PLCRVotingCheckpoint.commitVote(new BN(0), web3.utils.soliditySha3(2, salt), {from: account_investor5}) + ); + }); + + it("\t\t Should create a new ballot \n", async() => { + let commitTime = new BN(duration.days(10)); + let revealTime = new BN(duration.days(10)); + let tx = await I_PLCRVotingCheckpoint.createBallot(commitTime, revealTime, new BN(4), new BN(51).mul(new BN(10).pow(new BN(16))), {from: account_delegate}); + assert.equal((tx.logs[0].args._noOfProposals).toString(), 4); + assert.equal((tx.logs[0].args._checkpointId).toString(), 2); + assert.equal((tx.logs[0].args._ballotId).toString(), 1); + }); + + it("\t\t Should reveal the vote -- failed because not a valid stage \n", async() => { + await catchRevert( + I_PLCRVotingCheckpoint.revealVote(new BN(0), new BN(0), saltArray[1], {from: account_investor3}) + ); + }) + + it("\t\t Should try to commit vote but failed because commit periods end \n", async() => { + let salt = getRandom(); + await increaseTime(duration.days(4)); + + await catchRevert( + I_PLCRVotingCheckpoint.commitVote(new BN(0), web3.utils.soliditySha3(1, salt), {from: account_investor2}) + ); + }); + + it("\t\t Should fail to reveal vote -- not a valid ballot Id \n", async() => { + await catchRevert( + I_PLCRVotingCheckpoint.revealVote(new BN(4), new BN(1), saltArray[1], {from: account_investor3}) + ); + }); + + it("\t\t Should fali to reveal the vote -- not have the secret vote \n", async() => { + await catchRevert( + I_PLCRVotingCheckpoint.revealVote(new BN(0), new BN(1), saltArray[1], {from: account_investor4}) + ); + }); + + it("\t\t Should fail to reveal vote -- not a valid choice of proposal \n", async() => { + await catchRevert( + I_PLCRVotingCheckpoint.revealVote(new BN(0), new BN(5), saltArray[1], {from: account_investor3}) + ); + }); + + it("\t\t Should fail to reveal vote -- Invalid salt \n", async() => { + await catchRevert( + I_PLCRVotingCheckpoint.revealVote(new BN(0), new BN(1), getRandom(), {from: account_investor3}) + ); + }); + + it("\t\t Should successfully reveal the vote by investor 3 \n", async() => { + let tx = await I_PLCRVotingCheckpoint.revealVote(new BN(0), new BN(1), saltArray[1], {from: account_investor3}); + assert.equal(tx.logs[0].args._voter, account_investor3); + assert.equal(tx.logs[0].args._ballotId, 0); + assert.equal(tx.logs[0].args._choiceOfProposal, 1); + assert.equal(tx.logs[0].args._salt, saltArray[1]); + let data = await I_PLCRVotingCheckpoint.getBallotDetails.call(new BN(0)); + assert.equal(data[5], 3); + assert.equal(data[6], 1); + assert.equal(data[7], true); + }); + + it("\t\t Should fail to change the ballot status-- bad owner \n", async() => { + await catchRevert( + I_PLCRVotingCheckpoint.changeBallotStatus(new BN(0), false, {from: account_polymath}) + ); + }); + + it("\t\t Should fail to change the ballot status-- no change in the state \n", async() => { + await catchRevert( + I_PLCRVotingCheckpoint.changeBallotStatus(new BN(0), true, {from: token_owner}) + ); + }); + + it("\t\t Should change the status of the ballot with the help of changeBallotStatus \n", async() => { + let tx = await I_PLCRVotingCheckpoint.changeBallotStatus(new BN(0), false, {from: token_owner}); + assert.equal(tx.logs[0].args._ballotId, 0); + assert.isFalse(tx.logs[0].args._newStatus); + }); + + it("\t\t Should fail to reveal vote as ballot status is false \n", async() => { + await catchRevert( + I_PLCRVotingCheckpoint.revealVote(new BN(0), new BN(2), saltArray[0], {from: account_investor1}) + ); + }); + + it("\t\t Should successfully reveal the vote by investor 1 \n", async() => { + let tx = await I_PLCRVotingCheckpoint.changeBallotStatus(new BN(0), true, {from: token_owner}); + assert.equal(tx.logs[0].args._ballotId, 0); + assert.isTrue(tx.logs[0].args._newStatus); + + let txData = await I_PLCRVotingCheckpoint.revealVote(new BN(0), new BN(2), saltArray[0], {from: account_investor1}); + assert.equal(txData.logs[0].args._voter, account_investor1); + assert.equal(txData.logs[0].args._ballotId, 0); + assert.equal(txData.logs[0].args._choiceOfProposal, 2); + assert.equal(txData.logs[0].args._salt, saltArray[0]); + let data = await I_PLCRVotingCheckpoint.getBallotDetails.call(new BN(0)); + assert.equal(data[5], 3); + assert.equal(data[6], 2); + assert.equal(data[7], true); + }); + + it("\t\t Should fail to reveal vote again \n", async() => { + await catchRevert( + I_PLCRVotingCheckpoint.revealVote(new BN(0), new BN(2), saltArray[0], {from: account_investor1}) + ); + }); + + it("\t\t Should ge the ballot stage \n", async() => { + let stage1 = await I_PLCRVotingCheckpoint.getCurrentBallotStage.call(new BN(0)); + assert.equal(stage1.toString(), 2); + let stage2 = await I_PLCRVotingCheckpoint.getCurrentBallotStage.call(new BN(1)); + assert.equal(stage2.toString(), 1); + }); + + it("\t\t Should fail to reveal vote when reveal period is over \n", async() => { + await increaseTime(duration.days(5)); + await catchRevert( + I_PLCRVotingCheckpoint.revealVote(new BN(0), new BN(3), saltArray[3], {from: account_investor4}) + ); + }); + + it("\t\t Should check who votes whom \n", async() => { + // If we give Invalid ballot id, This function will always return 0 + assert.equal(((await I_PLCRVotingCheckpoint.getSelectedProposal.call(new BN(5), account_investor1))).toString(), 0); + + assert.equal(((await I_PLCRVotingCheckpoint.getSelectedProposal.call(new BN(0), account_investor1))).toString(), 2); + assert.equal(((await I_PLCRVotingCheckpoint.getSelectedProposal.call(new BN(0), account_investor2))).toString(), 0); + assert.equal(((await I_PLCRVotingCheckpoint.getSelectedProposal.call(new BN(0), account_investor3))).toString(), 1); + }); + + it("\t\t Should give the result to 0 because ballot id is not valid", async() => { + let data = await I_PLCRVotingCheckpoint.getBallotResults.call(new BN(5)); + assert.equal(data[0].length, 0); + assert.equal(data[1].length, 0); + assert.equal(data[2].toString(), 0); + assert.isFalse(data[3]); + assert.equal(data[4].toString(), 0); + }); + + it("\t\t Should get the result of the ballot \n", async() => { + let data = await I_PLCRVotingCheckpoint.getBallotResults.call(new BN(0)); + assert.equal(web3.utils.fromWei((data[0][0]).toString()), 5000); + assert.equal(web3.utils.fromWei((data[0][1]).toString()), 500); + assert.equal(data[1].length, 0); + assert.equal(data[2].toString(), 1); + assert.isTrue(data[3]); + assert.equal(data[4].toString(), 2); + }); + + it("\t\t Should fail to change the ballot status after the ballot ends", async() => { + await increaseTime(duration.days(20)); + await catchRevert( + I_PLCRVotingCheckpoint.changeBallotStatus(new BN(1), false, {from: token_owner}) + ); + }); + }); + + describe("\t\t General function test \n", async() => { + + it("\t\t Should check the permission \n", async() => { + let data = await I_PLCRVotingCheckpoint.getPermissions.call(); + assert.equal(data.length, 1); + }); + + it("\t\t Should check the init function \n", async() => { + assert.equal(await I_PLCRVotingCheckpoint.getInitFunction.call(), "0x00000000"); + }); + }); + + describe("\t\t Factory test cases \n", async() => { + it("\t\t Should get the exact details of the factory \n", async () => { + assert.equal((await I_PLCRVotingCheckpointFactory.setupCost.call()).toNumber(), 0); + assert.equal((await I_PLCRVotingCheckpointFactory.getTypes.call())[0], 4); + assert.equal(await I_PLCRVotingCheckpointFactory.version.call(), "3.0.0"); + assert.equal( + web3.utils.toAscii(await I_PLCRVotingCheckpointFactory.name.call()).replace(/\u0000/g, ""), + "PLCRVotingCheckpoint", + "Wrong Module added" + ); + assert.equal( + await I_PLCRVotingCheckpointFactory.description.call(), + "Commit & reveal technique used for voting", + "Wrong Module added" + ); + assert.equal(await I_PLCRVotingCheckpointFactory.title.call(), "PLCR Voting Checkpoint", "Wrong Module added"); + let tags = await I_PLCRVotingCheckpointFactory.getTags.call(); + assert.equal(tags.length, 3); + }); + }); + }); +}); diff --git a/test/zd_weighted_vote_checkpoint.js b/test/zd_weighted_vote_checkpoint.js new file mode 100644 index 000000000..0f32d1f48 --- /dev/null +++ b/test/zd_weighted_vote_checkpoint.js @@ -0,0 +1,524 @@ +import latestTime from "./helpers/latestTime"; +import {duration, promisifyLogWatch} from "./helpers/utils"; +import { takeSnapshot,increaseTime, revertToSnapshot} from "./helpers/time"; +import {catchRevert} from "./helpers/exceptions"; +import {deployWeightedVoteCheckpoint, setUpPolymathNetwork} from "./helpers/createInstances"; + +const SecurityToken = artifacts.require("./SecurityToken.sol"); +const GeneralTransferManager = artifacts.require("./GeneralTransferManager"); +const WeightedVoteCheckpoint = artifacts.require("./WeightedVoteCheckpoint"); +const STGetter = artifacts.require("./STGetter.sol"); + +const Web3 = require("web3"); +let BN = Web3.utils.BN; +const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); // Hardcoded development port + +contract("WeightedVoteCheckpoint", async (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; + + // Contract Instance Declaration + let I_GeneralPermissionManagerFactory; + let I_SecurityTokenRegistryProxy; + let I_GeneralTransferManagerFactory; + let I_WeightedVoteCheckpointFactory; + let P_WeightedVoteCheckpointFactory; + let I_WeightedVoteCheckpoint; + let I_GeneralTransferManager; + let I_ModuleRegistryProxy; + let I_ModuleRegistry; + let I_FeatureRegistry; + let I_SecurityTokenRegistry; + let I_STRProxied; + let I_STFactory; + let I_SecurityToken; + let I_PolyToken; + let I_MRProxied; + let I_PolymathRegistry; + let I_STRGetter; + let I_STGetter; + let stGetter; + + // SecurityToken Details + const name = "Team"; + const symbol = "SAP"; + const tokenDetails = "This is equity type of issuance"; + const decimals = 18; + + // Module key + const delegateManagerKey = 1; + const transferManagerKey = 2; + const stoKey = 3; + const checkpointKey = 4; + const burnKey = 5; + + // Initial fee for ticker registry and security token registry + const initRegFee = new BN(web3.utils.toWei("1000")); + + let currentTime; + const address_zero = "0x0000000000000000000000000000000000000000"; + const one_address = "0x0000000000000000000000000000000000000001"; + + before(async () => { + currentTime = new BN(await latestTime()); + 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 1: Deploy the genral PM ecosystem + let instances = await setUpPolymathNetwork(account_polymath, token_owner); + + [ + I_PolymathRegistry, + I_PolyToken, + I_FeatureRegistry, + I_ModuleRegistry, + I_ModuleRegistryProxy, + I_MRProxied, + I_GeneralTransferManagerFactory, + I_STFactory, + I_SecurityTokenRegistry, + I_SecurityTokenRegistryProxy, + I_STRProxied, + I_STRGetter, + I_STGetter + ] = instances; + + + // STEP 4: Deploy the WeightedVoteCheckpoint + [I_WeightedVoteCheckpointFactory] = await deployWeightedVoteCheckpoint(account_polymath, I_MRProxied, 0); + [P_WeightedVoteCheckpointFactory] = await deployWeightedVoteCheckpoint(account_polymath, I_MRProxied, new BN(web3.utils.toWei("500"))); + + // Printing all the contract addresses + console.log(` + --------------------- Polymath Network Smart Contracts: --------------------- + PolymathRegistry: ${I_PolymathRegistry.address} + SecurityTokenRegistryProxy: ${I_SecurityTokenRegistryProxy.address} + SecurityTokenRegistry: ${I_SecurityTokenRegistry.address} + ModuleRegistry: ${I_ModuleRegistry.address} + ModuleRegistryProxy: ${I_ModuleRegistryProxy.address} + FeatureRegistry: ${I_FeatureRegistry.address} + + STFactory: ${I_STFactory.address} + GeneralTransferManagerFactory: ${I_GeneralTransferManagerFactory.address} + + WeightedVoteCheckpointFactory: ${I_WeightedVoteCheckpointFactory.address} + ----------------------------------------------------------------------------- + `); + }); + + describe("\t\t Test case for Weighted Vote Checkpoint module \n", async() => { + + describe("\t\t Attaching the Weighted vote checkpoint module \n", async() => { + + it("\t\t Should register the ticker before the generation of the security token \n", async () => { + await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); + let tx = await I_STRProxied.registerNewTicker(token_owner, symbol, { from: token_owner }); + assert.equal(tx.logs[0].args._owner, token_owner); + assert.equal(tx.logs[0].args._ticker, symbol); + }); + + it("\t\t Should generate the new security token with the same symbol as registered above \n", async () => { + await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); + + let tx = await I_STRProxied.generateNewSecurityToken(name, symbol, tokenDetails, false, token_owner, 0, { from: token_owner }); + + // Verify the successful generation of the security token + assert.equal(tx.logs[1].args._ticker, symbol, "SecurityToken doesn't get deployed"); + + I_SecurityToken = await SecurityToken.at(tx.logs[1].args._securityTokenAddress); + stGetter = await STGetter.at(I_SecurityToken.address); + const log = (await I_SecurityToken.getPastEvents('ModuleAdded', {filter: {transactionHash: tx.transactionHash}}))[0]; + // Verify that GeneralTransferManager module get added successfully or not + assert.equal(log.args._types[0].toNumber(), transferManagerKey); + assert.equal(web3.utils.hexToString(log.args._name), "GeneralTransferManager"); + }); + + it("\t\t Should initialize the auto attached modules \n", async () => { + let moduleData = (await stGetter.getModulesByType(transferManagerKey))[0]; + I_GeneralTransferManager = await GeneralTransferManager.at(moduleData); + }); + + it("\t\t Should attach the voting module with the ST \n", async() => { + let tx = await I_SecurityToken.addModule(I_WeightedVoteCheckpointFactory.address, "0x0", 0, 0, false, {from: token_owner}); + assert.equal(tx.logs[2].args._types[0], checkpointKey, "Checkpoint doesn't get deployed"); + assert.equal(web3.utils.hexToString(tx.logs[2].args._name), "WeightedVoteCheckpoint", "WeightedVoteCheckpoint module was not added"); + I_WeightedVoteCheckpoint = await WeightedVoteCheckpoint.at(tx.logs[2].args._module); + }); + + it("\t\t Should fail to attach the voting module because allowance is unsufficent \n", async() => { + await catchRevert( + I_SecurityToken.addModule(P_WeightedVoteCheckpointFactory.address, "0x0", new BN(web3.utils.toWei("500")), 0, false, {from: token_owner}) + ); + }); + + it("\t\t Should attach the voting module with the ST \n", async() => { + let id = await takeSnapshot(); + await I_PolyToken.transfer(I_SecurityToken.address, new BN(web3.utils.toWei("2000", "ether")), { from: token_owner }); + let tx = await I_SecurityToken.addModule(P_WeightedVoteCheckpointFactory.address, "0x0", new BN(web3.utils.toWei("2000")), 0, false, {from: token_owner}); + assert.equal(tx.logs[3].args._types[0], checkpointKey, "Checkpoint doesn't get deployed"); + assert.equal(web3.utils.hexToString(tx.logs[3].args._name), "WeightedVoteCheckpoint", "WeightedVoteCheckpoint module was not added"); + await revertToSnapshot(id); + }); + }); + + describe("\t\t Test for createBallot \n", async() => { + + it("\t\t Should fail to create ballot -- bad owner \n", async() => { + await catchRevert( + I_WeightedVoteCheckpoint.createBallot(new BN(duration.days(5)), new BN(5), new BN(51).mul(new BN(10).pow(new BN(16))), {from: account_polymath}) + ); + }); + + it("\t\t Should fail to create ballot -- bad duration \n", async() => { + await catchRevert( + I_WeightedVoteCheckpoint.createBallot(new BN(0), new BN(5), new BN(51).mul(new BN(10).pow(new BN(16))), {from: token_owner}) + ); + }); + + it("\t\t Should fail to create ballot -- bad no of proposals \n", async() => { + await catchRevert( + I_WeightedVoteCheckpoint.createBallot(new BN(duration.days(5)), new BN(1), new BN(51).mul(new BN(10).pow(new BN(16))), {from: token_owner}) + ); + }); + + it("\t\t Should fail to create ballot -- zero value of quorum \n", async() => { + await catchRevert( + I_WeightedVoteCheckpoint.createBallot(new BN(duration.days(5)), new BN(1), new BN(0), {from: token_owner}) + ); + }); + + it("\t\t Should fail to create ballot -- value of quorum is more than the limit\n", async() => { + await catchRevert( + I_WeightedVoteCheckpoint.createBallot(new BN(duration.days(5)), new BN(1), new BN(51).mul(new BN(10).pow(new BN(17))), {from: token_owner}) + ); + }); + + it("\t\t Mint some tokens and transfer to whitelisted investors \n", async() => { + // Whitelist multiple investors + let time = new BN(await latestTime()); + await I_GeneralTransferManager.modifyKYCDataMulti( + [account_investor1, account_investor2, account_investor3, account_investor4], + [time, time, time, time], + [time, time, time, time], + [time + duration.days(200), time + duration.days(200), time + duration.days(200), time + duration.days(200)], + { + from: token_owner + } + ); + + // mint tokens to whitelisted investors + + await I_SecurityToken.issueMulti( + [account_investor1, account_investor2, account_investor3], + [new BN(web3.utils.toWei("500")), new BN(web3.utils.toWei("1000")), new BN(web3.utils.toWei("5000"))], + { + from: token_owner + } + ); + + assert.equal(web3.utils.fromWei((await I_SecurityToken.balanceOf.call(account_investor1)).toString()), 500); + assert.equal(web3.utils.fromWei((await I_SecurityToken.balanceOf.call(account_investor2)).toString()), 1000); + assert.equal(web3.utils.fromWei((await I_SecurityToken.balanceOf.call(account_investor3)).toString()), 5000); + }); + + it("\t\t Should fail to create ballot -- Invalid checkpoint Id \n", async() => { + let startTime = new BN(await latestTime()); + let endTime = new BN(await latestTime() + duration.days(4)); + await catchRevert( + I_WeightedVoteCheckpoint.createCustomBallot(new BN(5), new BN(51).mul(new BN(10).pow(new BN(17))), startTime, endTime, new BN(100), {from: token_owner}) + ); + }); + + it("\t\t Should fail to create ballot -- Invalid startTime \n", async() => { + let startTime = new BN(await latestTime()); + let endTime = new BN(await latestTime() + duration.days(4)); + await I_SecurityToken.createCheckpoint({from: token_owner}); + let checkpointId = await I_SecurityToken.currentCheckpointId.call(); + await catchRevert( + I_WeightedVoteCheckpoint.createCustomBallot(checkpointId, new BN(51).mul(new BN(10).pow(new BN(16))), 0, endTime, new BN(3), {from: token_owner}) + ); + }); + + it("\t\t Should fail to create ballot -- Invalid endTimes \n", async() => { + let startTime = new BN(await latestTime() + duration.days(10)); + let endTime = new BN(await latestTime() + duration.days(4)); + await catchRevert( + I_WeightedVoteCheckpoint.createCustomBallot(new BN(1), new BN(51).mul(new BN(10).pow(new BN(16))), startTime, endTime, new BN(3), {from: token_owner}) + ); + }); + + it("\t\t Should create the ballot successfully \n", async() => { + let tx = await I_WeightedVoteCheckpoint.createBallot(new BN(duration.days(5)), new BN(3), new BN(51).mul(new BN(10).pow(new BN(16))), {from: token_owner}); + assert.equal((tx.logs[0].args._noOfProposals).toString(), 3); + assert.equal((tx.logs[0].args._checkpointId).toString(), 2); + assert.equal((tx.logs[0].args._ballotId).toString(), 0); + }); + }); + + describe("\t\t Test case for castVote \n", async() => { + + it("\t\t Should fail to caste vote -- bad ballot id \n", async() => { + await catchRevert( + I_WeightedVoteCheckpoint.castVote(new BN(2), new BN(1), {from: account_investor1}) + ); + }); + + it("\t\t Should fail to caste vote -- bad proposal id \n", async() => { + await catchRevert( + I_WeightedVoteCheckpoint.castVote(new BN(0), new BN(4), {from: account_investor1}) + ); + }); + + it("\t\t Should fail to caste vote -- weight is 0 \n", async() => { + await catchRevert( + I_WeightedVoteCheckpoint.castVote(new BN(0), new BN(1), {from: account_investor4}) + ); + }); + + it("\t\t Should fail to add voter in ballot exemption list -- address is zero", async() => { + await catchRevert( + I_WeightedVoteCheckpoint.changeBallotExemptedVotersList(new BN(0), "0x0000000000000000000000000000000000000000", true, {from: token_owner}) + ); + }); + + it("\t\t Should fail to add voter in ballot exemption list -- invalid ballot id", async() => { + await catchRevert( + I_WeightedVoteCheckpoint.changeBallotExemptedVotersList(new BN(5), account_investor2, true, {from: token_owner}) + ); + }); + + it("\t\t Should add the voter in to the ballot exemption list", async() => { + let tx = await I_WeightedVoteCheckpoint.changeBallotExemptedVotersList(new BN(0), account_investor2, true, {from: token_owner}); + assert.equal((tx.logs[0].args._ballotId).toString(), 0); + assert.equal(tx.logs[0].args._voter, account_investor2); + assert.equal(tx.logs[0].args._exempt, true); + }); + + it("\t\t Should fail to add voter in ballot exemption list -- doing the same change again", async() => { + await catchRevert( + I_WeightedVoteCheckpoint.changeBallotExemptedVotersList(new BN(0), account_investor2, true, {from: token_owner}) + ); + }); + + it("\t\t Should fail to vote -- voter is present in the exemption list", async() => { + await catchRevert( + I_WeightedVoteCheckpoint.castVote(new BN(0), new BN(2), {from: account_investor2}) + ); + }); + + it("\t\t Should add the multiple voter in to the ballot exemption list -- failed ", async() => { + await catchRevert( + I_WeightedVoteCheckpoint.changeBallotExemptedVotersListMulti(new BN(0), [account_investor2, account_investor1], [false], {from: token_owner}) + ); + }); + + it("\t\t Should add the multiple voter in to the ballot exemption list", async() => { + await I_WeightedVoteCheckpoint.changeBallotExemptedVotersListMulti(new BN(0), [account_investor2], [false], {from: token_owner}); + assert.isTrue(await I_WeightedVoteCheckpoint.isVoterAllowed.call(new BN(0), account_investor2)); + }); + + it("\t\t Should successfully vote by account investor2 \n", async() => { + let tx = await I_WeightedVoteCheckpoint.castVote(new BN(0), new BN(2), {from: account_investor2}); + assert.equal(tx.logs[0].args._ballotId, 0); + assert.equal(tx.logs[0].args._proposalId, 2); + assert.equal(tx.logs[0].args._voter, account_investor2); + + let data = await I_WeightedVoteCheckpoint.getBallotDetails.call(new BN(0)); + assert.equal(data[6], 1); + assert.equal(data[5], 3); + assert.equal(data[7], true); + }); + + it("\t\t Should fail to add voter in default exemption list -- address is zero", async() => { + await catchRevert( + I_WeightedVoteCheckpoint.changeDefaultExemptedVotersList("0x0000000000000000000000000000000000000000", true, {from: token_owner}) + ); + }); + + it("\t\t Should add the voter in to the default exemption list", async() => { + let tx = await I_WeightedVoteCheckpoint.changeDefaultExemptedVotersList(account_investor1, true, {from: token_owner}); + assert.equal(tx.logs[0].args._voter, account_investor1); + assert.equal(tx.logs[0].args._exempt, true); + }); + + it("\t\t Should fail to add voter in default exemption list -- doing the same change again", async() => { + await catchRevert( + I_WeightedVoteCheckpoint.changeDefaultExemptedVotersList(account_investor1, true, {from: token_owner}) + ); + }); + + it("\t\t Should fail to vote -- voter is present in the exemption list", async() => { + await catchRevert( + I_WeightedVoteCheckpoint.castVote(new BN(0), new BN(1), {from: account_investor1}) + ); + }); + + it("\t\t Should change the default exemption list by allowing investor 1 to vote", async() => { + await I_WeightedVoteCheckpoint.changeDefaultExemptedVotersList(account_investor1, false, {from: token_owner}); + assert.isTrue(await I_WeightedVoteCheckpoint.isVoterAllowed.call(new BN(0), account_investor1)); + }); + + it("\t\t Should successfully vote by account investor1 \n", async() => { + let tx = await I_WeightedVoteCheckpoint.castVote(new BN(0), new BN(1), {from: account_investor1}); + assert.equal(tx.logs[0].args._ballotId, 0); + assert.equal(tx.logs[0].args._proposalId, 1); + assert.equal(tx.logs[0].args._voter, account_investor1); + + let data = await I_WeightedVoteCheckpoint.getBallotDetails.call(new BN(0)); + assert.equal(data[6], 2); + assert.equal(data[5], 3); + assert.equal(data[7], true); + }); + + it("\t\t Should fail to vote again \n", async() => { + await catchRevert( + I_WeightedVoteCheckpoint.castVote(new BN(0), new BN(2), {from: account_investor2}) + ); + }); + + it("\t\t Should fail to change the ballot status-- bad owner \n", async() => { + await catchRevert( + I_WeightedVoteCheckpoint.changeBallotStatus(new BN(0), false, {from: account_polymath}) + ); + }); + + it("\t\t Should fail to change the ballot status-- no change in the state \n", async() => { + await catchRevert( + I_WeightedVoteCheckpoint.changeBallotStatus(new BN(0), true, {from: account_polymath}) + ); + }); + + it("\t\t Should change the status of the ballot with the help of changeBallotStatus \n", async() => { + let tx = await I_WeightedVoteCheckpoint.changeBallotStatus(new BN(0), false, {from: token_owner}); + assert.equal(tx.logs[0].args._ballotId, 0); + assert.equal(tx.logs[0].args._isActive, false); + }); + + it("\t\t Should fail to vote because ballot is disabled \n", async() => { + await catchRevert( + I_WeightedVoteCheckpoint.castVote(new BN(0), new BN(2), {from: account_investor3}) + ); + }); + + it("\t\t Should turn on the ballot \n", async() => { + let tx = await I_WeightedVoteCheckpoint.changeBallotStatus(new BN(0), true, {from: token_owner}); + assert.equal(tx.logs[0].args._ballotId, 0); + assert.equal(tx.logs[0].args._isActive, true); + }); + + it("\t\t Should successfully vote \n", async() => { + let tx = await I_WeightedVoteCheckpoint.castVote(new BN(0), new BN(1), {from: account_investor3}); + assert.equal(tx.logs[0].args._ballotId, 0); + assert.equal(tx.logs[0].args._proposalId, 1); + assert.equal(tx.logs[0].args._voter, account_investor3); + + let data = await I_WeightedVoteCheckpoint.getBallotDetails.call(new BN(0)); + console.log(data); + assert.equal(data[6], 3); + assert.equal(data[5], 3); + assert.equal(data[7], true); + }); + + it("\t\t Should fail to vote when the duration of vote is complete \n", async() => { + await increaseTime(duration.days(6)); + + // transfer some funds to account_investor4 + await I_SecurityToken.issue( + account_investor4, + new BN(web3.utils.toWei("500")), + "0x0", + { + from: token_owner + } + ); + await catchRevert( + I_WeightedVoteCheckpoint.castVote(new BN(0), new BN(2), {from: account_investor4}) + ); + }); + + it("\t\t Should fail to change the status of the ballot -- already ended \n", async() => { + await catchRevert( + I_WeightedVoteCheckpoint.changeBallotStatus(new BN(0), false, {from: token_owner}) + ); + }); + + it("\t\t Should check who votes whom \n", async() => { + // If we give Invalid ballot id, This function will always return 0 + assert.equal((await I_WeightedVoteCheckpoint.getSelectedProposal.call(new BN(13), account_investor1)).toString(), 0); + + assert.equal((await I_WeightedVoteCheckpoint.getSelectedProposal.call(new BN(0), account_investor1)).toString(), 1); + assert.equal((await I_WeightedVoteCheckpoint.getSelectedProposal.call(new BN(0), account_investor2)).toString(), 2); + assert.equal((await I_WeightedVoteCheckpoint.getSelectedProposal.call(new BN(0), account_investor3)).toString(), 1); + }); + + it("\t\t Should get the result of the ballot \n", async() => { + let data = await I_WeightedVoteCheckpoint.getBallotResults.call(new BN(5)); + assert.equal(data[4], 0); + assert.equal(data[0].length, 0); + assert.equal(data[0].length, 0); + assert.equal(data[2], 0); + assert.isFalse(data[3]); + }); + + it("\t\t Should get the result of the ballot \n", async() => { + let data = await I_WeightedVoteCheckpoint.getBallotResults.call(new BN(0)); + console.log(data); + assert.equal(data[4], 3); + assert.equal(web3.utils.fromWei((data[0][0]).toString()), 5500); + assert.equal(web3.utils.fromWei((data[0][1]).toString()), 1000); + assert.equal(data[2], 1); + assert.isTrue(data[3]); + }); + }); + + describe("\t\t General function test \n", async() => { + + it("\t\t Should check the permission \n", async() => { + let data = await I_WeightedVoteCheckpoint.getPermissions.call(); + assert.equal(data.length, 1); + }); + + it("\t\t Should check the init function \n", async() => { + assert.equal(await I_WeightedVoteCheckpoint.getInitFunction.call(), "0x00000000"); + }); + }); + + describe("\t\t Factory test cases \n", async() => { + it("\t\t Should get the exact details of the factory \n", async () => { + assert.equal((await I_WeightedVoteCheckpointFactory.setupCost.call()).toNumber(), 0); + assert.equal((await I_WeightedVoteCheckpointFactory.getTypes.call())[0], 4); + assert.equal(await I_WeightedVoteCheckpointFactory.version.call(), "3.0.0"); + assert.equal( + web3.utils.toAscii(await I_WeightedVoteCheckpointFactory.name.call()).replace(/\u0000/g, ""), + "WeightedVoteCheckpoint", + "Wrong Module added" + ); + assert.equal( + await I_WeightedVoteCheckpointFactory.description.call(), + "Weighted votes based on token amount", + "Wrong Module added" + ); + assert.equal(await I_WeightedVoteCheckpointFactory.title.call(), "Weighted Vote Checkpoint", "Wrong Module added"); + let tags = await I_WeightedVoteCheckpointFactory.getTags.call(); + assert.equal(tags.length, 3); + }); + }); + }); +}); diff --git a/truffle-ci.js b/truffle-ci.js index f6d2bf7ae..008baa8ec 100644 --- a/truffle-ci.js +++ b/truffle-ci.js @@ -1,8 +1,6 @@ require('babel-register'); require('babel-polyfill'); -let HDWalletProvider = require("truffle-hdwallet-provider"); - module.exports = { networks: { development: { @@ -11,13 +9,6 @@ module.exports = { network_id: '*', // Match any network id gas: 7900000, }, - kovan: { - provider: () => { - return new HDWalletProvider(process.env.PRIVATE_KEY, "https://kovan.mudit.blog/"); - }, - network_id: '42', - gasPrice: 2000000000 - }, coverage: { host: "localhost", network_id: "*", @@ -37,13 +28,11 @@ module.exports = { } } }, - solc: { - optimizer: { - enabled: true, - runs: 200, - }, - }, mocha: { - enableTimeouts: false + enableTimeouts: false, + reporter: "mocha-junit-reporter", + reporterOptions: { + mochaFile: './test-results/mocha/results.xml' + } } }; diff --git a/truffle-config-gas.js b/truffle-config-gas.js new file mode 100644 index 000000000..edc6c325b --- /dev/null +++ b/truffle-config-gas.js @@ -0,0 +1,81 @@ +require('babel-register'); +require('babel-polyfill'); +const fs = require('fs'); +const NonceTrackerSubprovider = require("web3-provider-engine/subproviders/nonce-tracker") + +const HDWalletProvider = require("truffle-hdwallet-provider"); + +module.exports = { + networks: { + development: { + host: 'localhost', + port: 8545, + network_id: '*', // Match any network id + gas: 7900000, + }, + mainnet: { + host: 'localhost', + port: 8545, + network_id: '1', // Match any network id + gas: 7900000, + gasPrice: 10000000000 + }, + ropsten: { + // provider: new HDWalletProvider(privKey, "http://localhost:8545"), + host: 'localhost', + port: 8545, + network_id: '3', // Match any network id + gas: 4500000, + gasPrice: 150000000000 + }, + rinkeby: { + // provider: new HDWalletProvider(privKey, "http://localhost:8545"), + host: 'localhost', + port: 8545, + network_id: '4', // Match any network id + gas: 7500000, + gasPrice: 10000000000 + }, + kovan: { + provider: () => { + const key = fs.readFileSync('./privKey').toString(); + let wallet = new HDWalletProvider(key, "https://kovan.infura.io/") + var nonceTracker = new NonceTrackerSubprovider() + wallet.engine._providers.unshift(nonceTracker) + nonceTracker.setEngine(wallet.engine) + return wallet + }, + network_id: '42', // Match any network id + gas: 7900000, + gasPrice: 5000000000 + }, + coverage: { + host: "localhost", + network_id: "*", + port: 8545, // <-- If you change this, also set the port option in .solcover.js. + gas: 0xfffffffff , // <-- Use this high gas value + gasPrice: 0x01 // <-- Use this low gas price + } + }, + compilers: { + solc: { + version: "native", + settings: { + optimizer: { + enabled: true, + runs: 200 + } + } + } + }, + mocha: { + enableTimeouts: false, + reporter: 'eth-gas-reporter', + reporterOptions : { + currency: 'USD', + gasPrice: 5, + onlyCalledMethods: true, + showTimeSpent: true + } + } +}; diff --git a/truffle-config.js b/truffle-config.js index c993327ea..e9097c648 100644 --- a/truffle-config.js +++ b/truffle-config.js @@ -1,9 +1,17 @@ require('babel-register'); require('babel-polyfill'); +require('dotenv').config(); const fs = require('fs'); const NonceTrackerSubprovider = require("web3-provider-engine/subproviders/nonce-tracker") -const HDWalletProvider = require("truffle-hdwallet-provider-privkey"); +const HDWalletProvider = require("truffle-hdwallet-provider"); + +let ver; +if (process.env.POLYMATH_NATIVE_SOLC) { + ver = "native"; +} else { + ver = "0.5.8"; +} module.exports = { networks: { @@ -53,15 +61,20 @@ module.exports = { host: "localhost", network_id: "*", port: 8545, // <-- If you change this, also set the port option in .solcover.js. - gas: 0xfffffffffff, // <-- Use this high gas value + gas: 0xfffffffff , // <-- Use this high gas value gasPrice: 0x01 // <-- Use this low gas price } }, - solc: { - optimizer: { - enabled: true, - runs: 200, - }, + compilers: { + solc: { + version: ver, + settings: { + optimizer: { + enabled: true, + runs: 200 + } + } + } }, mocha: { enableTimeouts: false diff --git a/upgrade.js b/upgrade.js new file mode 100644 index 000000000..af0561dac --- /dev/null +++ b/upgrade.js @@ -0,0 +1,19 @@ +const fs = require('fs'); +const glob = require("glob"); + +let regex = new RegExp('((const|let|var) (.*) = artifacts.require(.)*)', 'gi'); +let m; + +glob("test/**/*.js", function (er, files) { + files.forEach(function(filename) { + fs.readFile(filename, 'utf-8', function(err, content) { + if (err) { + return console.log(err); + } + content = content.replace(regex, '$1\n$3.numberFormat = "BN";'); + fs.writeFile(filename, content, 'utf8', function (err) { + if (err) return console.log(err); + }); + }); + }); +}) \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 0ce8a7a37..66ba16a49 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16,9 +16,33 @@ esutils "^2.0.2" js-tokens "^4.0.0" -"@types/node@^10.3.2": - version "10.12.18" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.18.tgz#1d3ca764718915584fcd9f6344621b7672665c67" +"@types/concat-stream@^1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@types/concat-stream/-/concat-stream-1.6.0.tgz#394dbe0bb5fee46b38d896735e8b68ef2390d00d" + dependencies: + "@types/node" "*" + +"@types/form-data@0.0.33": + version "0.0.33" + resolved "https://registry.yarnpkg.com/@types/form-data/-/form-data-0.0.33.tgz#c9ac85b2a5fd18435b8c85d9ecb50e6d6c893ff8" + dependencies: + "@types/node" "*" + +"@types/node@*": + version "11.9.4" + resolved "https://registry.yarnpkg.com/@types/node/-/node-11.9.4.tgz#ceb0048a546db453f6248f2d1d95e937a6f00a14" + +"@types/node@^8.0.0": + version "8.10.40" + resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.40.tgz#4314888d5cd537945d73e9ce165c04cc550144a4" + +"@types/node@^9.3.0", "@types/node@^9.4.1": + version "9.6.42" + resolved "https://registry.yarnpkg.com/@types/node/-/node-9.6.42.tgz#96fd9c8cf15fbf2c16fe525fc2be97c49cdd0c2f" + +"@types/qs@^6.2.31": + version "6.5.1" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.5.1.tgz#a38f69c62528d56ba7bd1f91335a8004988d72f7" abbrev@1: version "1.1.1" @@ -28,6 +52,12 @@ abbrev@1.0.x: version "1.0.9" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135" +abi-decoder@^1.0.8: + version "1.2.0" + resolved "https://registry.yarnpkg.com/abi-decoder/-/abi-decoder-1.2.0.tgz#c42882dbb91b444805f0cd203a87a5cc3c22f4a8" + dependencies: + web3 "^0.18.4" + abstract-leveldown@~2.6.0: version "2.6.3" resolved "https://registry.yarnpkg.com/abstract-leveldown/-/abstract-leveldown-2.6.3.tgz#1c5e8c6a5ef965ae8c35dfb3a8770c476b82c4b8" @@ -51,18 +81,9 @@ acorn-jsx@^5.0.0: version "5.0.1" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.0.1.tgz#32a064fd925429216a09b141102bfdd185fae40e" -acorn@^6.0.2: - version "6.1.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.1.1.tgz#7d25ae05bb8ad1f9b699108e1094ecd7884adc1f" - integrity sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA== - -aes-js@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.0.0.tgz#e21df10ad6c2053295bcbb8dab40b09dbea87e4d" - -aes-js@^3.1.1: - version "3.1.2" - resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.1.2.tgz#db9aabde85d5caabbfc0d4f2a4446960f627146a" +acorn@^6.0.7: + version "6.1.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.1.0.tgz#b0a3be31752c97a0f7013c5f4903b71a05db6818" ajv@^5.2.2: version "5.5.2" @@ -73,19 +94,9 @@ ajv@^5.2.2: fast-json-stable-stringify "^2.0.0" json-schema-traverse "^0.3.0" -ajv@^6.5.3, ajv@^6.9.1: - version "6.10.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.0.tgz#90d0d54439da587cd7e843bfb7045f50bd22bdf1" - integrity sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg== - dependencies: - fast-deep-equal "^2.0.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -ajv@^6.5.5: - version "6.6.2" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.6.2.tgz#caceccf474bf3fc3ce3b147443711a24063cc30d" +ajv@^6.5.5, ajv@^6.9.1: + version "6.9.1" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.9.1.tgz#a4d3683d74abc5670e75f0b16520f70a20ea8dc1" dependencies: fast-deep-equal "^2.0.1" fast-json-stable-stringify "^2.0.0" @@ -103,10 +114,9 @@ amdefine@>=0.0.4: version "1.0.1" resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" -ansi-escapes@^3.0.0: +ansi-escapes@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" - integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== ansi-regex@^2.0.0, ansi-regex@^2.1.1: version "2.1.1" @@ -141,6 +151,10 @@ anymatch@^1.3.0: micromatch "^2.1.5" normalize-path "^2.0.0" +app-module-path@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/app-module-path/-/app-module-path-2.2.0.tgz#641aa55dfb7d6a6f0a8141c4b9c0aa50b6c24dd5" + aproba@^1.0.3: version "1.2.0" resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" @@ -155,7 +169,6 @@ are-we-there-yet@~1.1.2: argparse@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== dependencies: sprintf-js "~1.0.2" @@ -189,6 +202,10 @@ array-unique@^0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" +asap@~2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" + asn1.js@^4.0.0: version "4.10.1" resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0" @@ -240,10 +257,10 @@ async@1.x, async@^1.4.2, async@~1.5.2: resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" async@^2.0.1, async@^2.1.2, async@^2.4.0, async@^2.4.1, async@^2.5.0: - version "2.6.1" - resolved "https://registry.yarnpkg.com/async/-/async-2.6.1.tgz#b245a23ca71930044ec53fa46aa00a3e87c6a610" + version "2.6.2" + resolved "https://registry.yarnpkg.com/async/-/async-2.6.2.tgz#18330ea7e6e313887f5d2f2a904bac6fe4dd5381" dependencies: - lodash "^4.17.10" + lodash "^4.17.11" async@~0.9.0: version "0.9.2" @@ -861,10 +878,15 @@ babylon@^6.18.0: version "6.18.0" resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3" +backoff@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/backoff/-/backoff-2.5.0.tgz#f616eda9d3e4b66b8ca7fca79f695722c5f8e26f" + dependencies: + precond "0.2" + balanced-match@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" - integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= base-x@^3.0.2: version "3.0.5" @@ -872,10 +894,6 @@ base-x@^3.0.2: dependencies: safe-buffer "^5.0.1" -base64-js@0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-0.0.8.tgz#1101e9544f4a76b1bc3b26d452ca96d7a35e7978" - base64-js@^1.0.2: version "1.3.0" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3" @@ -898,21 +916,13 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" -bignumber.js@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-5.0.0.tgz#fbce63f09776b3000a83185badcde525daf34833" - bignumber.js@^4.0.2: version "4.1.0" resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-4.1.0.tgz#db6f14067c140bd46624815a7916c92d9b6c24b1" bignumber.js@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-8.0.1.tgz#5d419191370fb558c64e3e5f70d68e5947138832" - -"bignumber.js@git+https://github.com/debris/bignumber.js#master": - version "2.0.7" - resolved "git+https://github.com/debris/bignumber.js#c7a38de919ed75e6fb6ba38051986e294b328df9" + version "8.0.2" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-8.0.2.tgz#d8c4e1874359573b1ef03011a2d861214aeef137" "bignumber.js@git+https://github.com/debris/bignumber.js.git#94d7146671b9719e00a09c29b01a691bc85048c2": version "2.0.7" @@ -923,14 +933,10 @@ bignumber.js@^8.0.1: resolved "git+https://github.com/frozeman/bignumber.js-nolookahead.git#57692b3ecfc98bbdd6b3a516cb2353652ea49934" binary-extensions@^1.0.0: - version "1.12.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.12.0.tgz#c2d780f53d45bba8317a8902d4ceeaf3a6385b14" - -bindings@^1.2.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.3.1.tgz#21fc7c6d67c18516ec5aaa2815b145ff77b26ea5" + version "1.13.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.0.tgz#9523e001306a32444b907423f1de2164222f6ab1" -bindings@^1.3.1: +bindings@^1.2.1, bindings@^1.3.1: version "1.4.0" resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.4.0.tgz#909efa49f2ebe07ecd3cb136778f665052040127" dependencies: @@ -978,6 +984,12 @@ bl@^1.0.0: readable-stream "^2.3.5" safe-buffer "^5.1.1" +block-stream@*: + version "0.0.9" + resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a" + dependencies: + inherits "~2.0.0" + bluebird@^2.9.34: version "2.11.0" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-2.11.0.tgz#534b9033c022c9579c56ba3b3e5a5caafbb650e1" @@ -990,7 +1002,7 @@ bn.js@4.11.6: version "4.11.6" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.6.tgz#53344adb14617a13f6e8dd2ce28905d1c0ba3215" -bn.js@4.11.8, bn.js@=4.11.8, bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.10.0, bn.js@^4.11.0, bn.js@^4.11.3, bn.js@^4.11.6, bn.js@^4.4.0, bn.js@^4.8.0: +bn.js@=4.11.8, bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.10.0, bn.js@^4.11.0, bn.js@^4.11.1, bn.js@^4.11.3, bn.js@^4.11.6, bn.js@^4.11.8, bn.js@^4.4.0, bn.js@^4.8.0: version "4.11.8" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" @@ -1026,7 +1038,6 @@ borc@^2.0.2: brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== dependencies: balanced-match "^1.0.0" concat-map "0.0.1" @@ -1127,24 +1138,12 @@ browserslist@^3.2.6: caniuse-lite "^1.0.30000844" electron-to-chromium "^1.3.47" -bs58@=4.0.1, bs58@^4.0.0, bs58@^4.0.1: +bs58@=4.0.1, bs58@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" dependencies: base-x "^3.0.2" -bs58@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/bs58/-/bs58-2.0.1.tgz#55908d58f1982aba2008fa1bed8f91998a29bf8d" - -bs58check@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/bs58check/-/bs58check-2.1.2.tgz#53b018291228d82a5aa08e7d796fdafda54aebfc" - dependencies: - bs58 "^4.0.0" - create-hash "^1.1.0" - safe-buffer "^5.1.2" - bson@^1.0.4: version "1.1.0" resolved "https://registry.yarnpkg.com/bson/-/bson-1.1.0.tgz#bee57d1fb6a87713471af4e32bcae36de814b5b0" @@ -1184,14 +1183,6 @@ buffer-xor@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" -buffer@^3.0.1: - version "3.6.0" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-3.6.0.tgz#a72c936f77b96bf52f5f7e7b467180628551defb" - dependencies: - base64-js "0.0.8" - ieee754 "^1.1.4" - isarray "^1.0.0" - buffer@^4.9.0: version "4.9.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298" @@ -1200,17 +1191,13 @@ buffer@^4.9.0: ieee754 "^1.1.4" isarray "^1.0.0" -buffer@^5.0.5: +buffer@^5.0.5, buffer@^5.2.1: version "5.2.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.2.1.tgz#dd57fa0f109ac59c602479044dca7b8b3d0b71d6" dependencies: base64-js "^1.0.2" ieee754 "^1.1.4" -builtin-modules@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" - bytes@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" @@ -1229,17 +1216,9 @@ cache-base@^1.0.1: union-value "^1.0.0" unset-value "^1.0.0" -caller-path@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f" - integrity sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8= - dependencies: - callsites "^0.2.0" - -callsites@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-0.2.0.tgz#afab96262910a7f33c19a5775825c69f34e350ca" - integrity sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo= +callsites@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.0.0.tgz#fb7eb569b72ad7a45812f93fd9430a3e410b3dd3" camelcase@^3.0.0: version "3.0.0" @@ -1257,8 +1236,8 @@ caminte@0.3.7: uuid "^3.0.1" caniuse-lite@^1.0.30000844: - version "1.0.30000926" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000926.tgz#4361a99d818ca6e521dbe89a732de62a194a789c" + version "1.0.30000938" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000938.tgz#b64bf1427438df40183fce910fe24e34feda7a3f" caseless@~0.12.0: version "0.12.0" @@ -1274,9 +1253,9 @@ chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" -chalk@^2.0.0, chalk@^2.1.0, chalk@^2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e" +chalk@^2.0.0, chalk@^2.1.0, chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" dependencies: ansi-styles "^3.2.1" escape-string-regexp "^1.0.5" @@ -1286,7 +1265,7 @@ chardet@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" -charenc@~0.0.1: +"charenc@>= 0.0.1", charenc@~0.0.1: version "0.0.2" resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" @@ -1322,11 +1301,6 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: inherits "^2.0.1" safe-buffer "^5.0.1" -circular-json@^0.3.1: - version "0.3.3" - resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66" - integrity sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A== - class-utils@^0.3.5: version "0.3.6" resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" @@ -1353,6 +1327,15 @@ cli-cursor@^2.1.0: dependencies: restore-cursor "^2.0.0" +cli-table3@^0.5.0: + version "0.5.1" + resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.5.1.tgz#0252372d94dfc40dbd8df06005f48f31f656f202" + dependencies: + object-assign "^4.1.0" + string-width "^2.1.1" + optionalDependencies: + colors "^1.1.2" + cli-width@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" @@ -1385,13 +1368,6 @@ code-point-at@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" -coinstring@^2.0.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/coinstring/-/coinstring-2.3.0.tgz#cdb63363a961502404a25afb82c2e26d5ff627a4" - dependencies: - bs58 "^2.0.1" - create-hash "^1.1.1" - collection-visit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" @@ -1435,10 +1411,9 @@ commander@^2.14.1, commander@^2.15.0, commander@^2.19.0, commander@^2.8.1, comma version "2.19.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a" -commander@~2.20.0: - version "2.20.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422" - integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ== +commander@~2.17.1: + version "2.17.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf" commander@~2.8.1: version "2.8.1" @@ -1457,7 +1432,15 @@ component-emitter@^1.2.1: concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +concat-stream@^1.4.6, concat-stream@^1.5.1, concat-stream@^1.6.0: + version "1.6.2" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" + dependencies: + buffer-from "^1.0.0" + inherits "^2.0.3" + readable-stream "^2.2.2" + typedarray "^0.0.6" console-control-strings@^1.0.0, console-control-strings@~1.1.0: version "1.1.0" @@ -1498,8 +1481,8 @@ copy-descriptor@^0.1.0: resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" core-js@^2.4.0, core-js@^2.5.0: - version "2.6.1" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.1.tgz#87416ae817de957a3f249b3b5ca475d4aaed6042" + version "2.6.5" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.5.tgz#44bc8d249e7fb2ff5d00e0341a7ffb94fbf67895" core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" @@ -1512,7 +1495,7 @@ cors@^2.8.1: object-assign "^4" vary "^1" -coveralls@^3.0.2: +coveralls@^3.0.1: version "3.0.2" resolved "https://registry.yarnpkg.com/coveralls/-/coveralls-3.0.2.tgz#f5a0bcd90ca4e64e088b710fa8dda640aea4884f" dependencies: @@ -1530,7 +1513,7 @@ create-ecdh@^4.0.0: bn.js "^4.1.0" elliptic "^6.0.0" -create-hash@^1.1.0, create-hash@^1.1.1, create-hash@^1.1.2: +create-hash@^1.1.0, create-hash@^1.1.2: version "1.2.0" resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" dependencies: @@ -1558,6 +1541,13 @@ cron-parser@^2.7.3: is-nan "^1.2.1" moment-timezone "^0.5.23" +cross-fetch@^2.1.0, cross-fetch@^2.1.1: + version "2.2.3" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-2.2.3.tgz#e8a0b3c54598136e037f8650f8e823ccdfac198e" + dependencies: + node-fetch "2.1.2" + whatwg-fetch "2.0.4" + cross-spawn@^5.0.1: version "5.1.0" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" @@ -1576,7 +1566,7 @@ cross-spawn@^6.0.5: shebang-command "^1.2.0" which "^1.2.9" -crypt@~0.0.1: +"crypt@>= 0.0.1", crypt@~0.0.1: version "0.0.2" resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" @@ -1608,10 +1598,6 @@ csextends@^1.0.3: version "1.2.0" resolved "https://registry.yarnpkg.com/csextends/-/csextends-1.2.0.tgz#6374b210984b54d4495f29c99d3dd069b80543e5" -csv-parse@^4.2.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/csv-parse/-/csv-parse-4.3.0.tgz#e27cc1c4c5426201a896389c9f97273a0d47dad4" - cycle@1.0.x: version "1.0.3" resolved "https://registry.yarnpkg.com/cycle/-/cycle-1.0.3.tgz#21e80b2be8580f98b468f379430662b046c34ad2" @@ -1829,10 +1815,9 @@ doctrine@1.5.0: esutils "^2.0.2" isarray "^1.0.0" -doctrine@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" - integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" dependencies: esutils "^2.0.2" @@ -1840,6 +1825,11 @@ dom-walk@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.1.tgz#672226dc74c8f799ad35307df936aba11acd6018" +dotenv@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.0.0.tgz#ed310c165b4e8a97bb745b0a9d99c31bda566440" + integrity sha512-30xVGqjLjiUOArT4+M5q9sYdvuR4riM6yK9wMcas9Vbp6zZa+ocC9dp6QoftuhTPhFAiLK/0C5Ni2nou/Bk8lg== + drbg.js@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/drbg.js/-/drbg.js-1.0.1.tgz#3e36b6c42b37043823cdbc332d58f31e2445480b" @@ -1881,17 +1871,8 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" electron-to-chromium@^1.3.47: - version "1.3.96" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.96.tgz#25770ec99b8b07706dedf3a5f43fa50cb54c4f9a" - -elliptic@6.3.3: - version "6.3.3" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.3.3.tgz#5482d9646d54bcb89fd7d994fc9e2e9568876e3f" - dependencies: - bn.js "^4.4.0" - brorand "^1.0.1" - hash.js "^1.0.0" - inherits "^2.0.1" + version "1.3.113" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.113.tgz#b1ccf619df7295aea17bc6951dc689632629e4a9" elliptic@=6.4.0: version "6.4.0" @@ -1929,7 +1910,6 @@ elliptic@^6.0.0, elliptic@^6.2.3, elliptic@^6.4.0: emoji-regex@^7.0.1: version "7.0.3" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" - integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== encodeurl@~1.0.2: version "1.0.2" @@ -1941,7 +1921,7 @@ encoding@^0.1.11: dependencies: iconv-lite "~0.4.13" -end-of-stream@^1.0.0: +end-of-stream@^1.0.0, end-of-stream@^1.1.0: version "1.4.1" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43" dependencies: @@ -1989,8 +1969,8 @@ es-to-primitive@^1.2.0: is-symbol "^1.0.2" es5-ext@^0.10.14, es5-ext@^0.10.35, es5-ext@^0.10.45, es5-ext@^0.10.46, es5-ext@^0.10.9, es5-ext@~0.10.14, es5-ext@~0.10.2, es5-ext@~0.10.46: - version "0.10.46" - resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.46.tgz#efd99f67c5a7ec789baa3daa7f79870388f7f572" + version "0.10.47" + resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.47.tgz#d24232e1380daad5449a817be19bde9729024a11" dependencies: es6-iterator "~2.0.3" es6-symbol "~3.1.1" @@ -2043,19 +2023,19 @@ eslint-config-standard@^12.0.0: version "12.0.0" resolved "https://registry.yarnpkg.com/eslint-config-standard/-/eslint-config-standard-12.0.0.tgz#638b4c65db0bd5a41319f96bba1f15ddad2107d9" -eslint-import-resolver-node@^0.3.1: +eslint-import-resolver-node@^0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz#58f15fb839b8d0576ca980413476aab2472db66a" dependencies: debug "^2.6.9" resolve "^1.5.0" -eslint-module-utils@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.2.0.tgz#b270362cd88b1a48ad308976ce7fa54e98411746" +eslint-module-utils@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.3.0.tgz#546178dab5e046c8b562bbb50705e2456d7bda49" dependencies: debug "^2.6.8" - pkg-dir "^1.0.0" + pkg-dir "^2.0.0" eslint-plugin-es@^1.3.1: version "1.4.0" @@ -2064,24 +2044,24 @@ eslint-plugin-es@^1.3.1: eslint-utils "^1.3.0" regexpp "^2.0.1" -eslint-plugin-import@^2.14.0: - version "2.14.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.14.0.tgz#6b17626d2e3e6ad52cfce8807a845d15e22111a8" +eslint-plugin-import@^2.10.0: + version "2.16.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.16.0.tgz#97ac3e75d0791c4fac0e15ef388510217be7f66f" dependencies: contains-path "^0.1.0" - debug "^2.6.8" + debug "^2.6.9" doctrine "1.5.0" - eslint-import-resolver-node "^0.3.1" - eslint-module-utils "^2.2.0" - has "^1.0.1" - lodash "^4.17.4" - minimatch "^3.0.3" + eslint-import-resolver-node "^0.3.2" + eslint-module-utils "^2.3.0" + has "^1.0.3" + lodash "^4.17.11" + minimatch "^3.0.4" read-pkg-up "^2.0.0" - resolve "^1.6.0" + resolve "^1.9.0" eslint-plugin-node@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-8.0.0.tgz#fb9e8911f4543514f154bb6a5924b599aa645568" + version "8.0.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-8.0.1.tgz#55ae3560022863d141fa7a11799532340a685964" dependencies: eslint-plugin-es "^1.3.1" eslint-utils "^1.3.1" @@ -2099,9 +2079,8 @@ eslint-plugin-standard@^4.0.0: resolved "https://registry.yarnpkg.com/eslint-plugin-standard/-/eslint-plugin-standard-4.0.0.tgz#f845b45109c99cd90e77796940a344546c8f6b5c" eslint-scope@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" - integrity sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg== + version "4.0.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.0.tgz#50bf3071e9338bcdc43331794a0cb533f0136172" dependencies: esrecurse "^4.1.0" estraverse "^4.1.1" @@ -2114,53 +2093,52 @@ eslint-visitor-keys@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d" -eslint@^5.10.0: - version "5.11.1" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-5.11.1.tgz#8deda83db9f354bf9d3f53f9677af7e0e13eadda" +eslint@^5.8.0: + version "5.14.1" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-5.14.1.tgz#490a28906be313685c55ccd43a39e8d22efc04ba" dependencies: "@babel/code-frame" "^7.0.0" - ajv "^6.5.3" + ajv "^6.9.1" chalk "^2.1.0" cross-spawn "^6.0.5" debug "^4.0.1" - doctrine "^2.1.0" + doctrine "^3.0.0" eslint-scope "^4.0.0" eslint-utils "^1.3.1" eslint-visitor-keys "^1.0.0" - espree "^5.0.0" + espree "^5.0.1" esquery "^1.0.1" esutils "^2.0.2" - file-entry-cache "^2.0.0" + file-entry-cache "^5.0.1" functional-red-black-tree "^1.0.1" glob "^7.1.2" globals "^11.7.0" ignore "^4.0.6" + import-fresh "^3.0.0" imurmurhash "^0.1.4" - inquirer "^6.1.0" + inquirer "^6.2.2" js-yaml "^3.12.0" json-stable-stringify-without-jsonify "^1.0.1" levn "^0.3.0" - lodash "^4.17.5" + lodash "^4.17.11" minimatch "^3.0.4" mkdirp "^0.5.1" natural-compare "^1.4.0" optionator "^0.8.2" path-is-inside "^1.0.2" - pluralize "^7.0.0" progress "^2.0.0" regexpp "^2.0.1" - require-uncached "^1.0.3" semver "^5.5.1" strip-ansi "^4.0.0" strip-json-comments "^2.0.1" - table "^5.0.2" + table "^5.2.3" text-table "^0.2.0" -espree@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/espree/-/espree-5.0.0.tgz#fc7f984b62b36a0f543b13fb9cd7b9f4a7f5b65c" +espree@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-5.0.1.tgz#5d6526fa4fc7f0788a5cf75b15f30323e2f81f7a" dependencies: - acorn "^6.0.2" + acorn "^6.0.7" acorn-jsx "^5.0.0" eslint-visitor-keys "^1.0.0" @@ -2171,7 +2149,6 @@ esprima@2.7.x, esprima@^2.7.1: esprima@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== esquery@^1.0.1: version "1.0.1" @@ -2214,6 +2191,63 @@ eth-block-tracker@^2.2.2: pify "^2.3.0" tape "^4.6.3" +eth-block-tracker@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/eth-block-tracker/-/eth-block-tracker-3.0.1.tgz#95cd5e763c7293e0b1b2790a2a39ac2ac188a5e1" + dependencies: + eth-query "^2.1.0" + ethereumjs-tx "^1.3.3" + ethereumjs-util "^5.1.3" + ethjs-util "^0.1.3" + json-rpc-engine "^3.6.0" + pify "^2.3.0" + tape "^4.6.3" + +eth-gas-reporter@^0.1.12: + version "0.1.12" + resolved "https://registry.yarnpkg.com/eth-gas-reporter/-/eth-gas-reporter-0.1.12.tgz#6b761e05c33ae85be47840dd07468ab51d473dd8" + dependencies: + abi-decoder "^1.0.8" + cli-table3 "^0.5.0" + colors "^1.1.2" + lodash "^4.17.4" + mocha "^4.1.0" + req-cwd "^2.0.0" + request "^2.83.0" + request-promise-native "^1.0.5" + sha1 "^1.1.1" + shelljs "^0.7.8" + solidity-parser-antlr "^0.2.10" + sync-request "^6.0.0" + +eth-json-rpc-infura@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/eth-json-rpc-infura/-/eth-json-rpc-infura-3.2.0.tgz#62c3f516b51351038c32a548704467cec113ca8f" + dependencies: + cross-fetch "^2.1.1" + eth-json-rpc-middleware "^1.5.0" + json-rpc-engine "^3.4.0" + json-rpc-error "^2.0.0" + tape "^4.8.0" + +eth-json-rpc-middleware@^1.5.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/eth-json-rpc-middleware/-/eth-json-rpc-middleware-1.6.0.tgz#5c9d4c28f745ccb01630f0300ba945f4bef9593f" + dependencies: + async "^2.5.0" + eth-query "^2.1.2" + eth-tx-summary "^3.1.2" + ethereumjs-block "^1.6.0" + ethereumjs-tx "^1.3.3" + ethereumjs-util "^5.1.2" + ethereumjs-vm "^2.1.0" + fetch-ponyfill "^4.0.0" + json-rpc-engine "^3.6.0" + json-rpc-error "^2.0.0" + json-stable-stringify "^1.0.1" + promise-to-callback "^1.0.0" + tape "^4.6.3" + eth-lib@0.1.27, eth-lib@^0.1.26: version "0.1.27" resolved "https://registry.yarnpkg.com/eth-lib/-/eth-lib-0.1.27.tgz#f0b0fd144f865d2d6bf8257a40004f2e75ca1dd6" @@ -2250,7 +2284,7 @@ eth-lightwallet@^3.0.1: tweetnacl "0.13.2" web3 "0.20.2" -eth-query@^2.1.0: +eth-query@^2.0.2, eth-query@^2.1.0, eth-query@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/eth-query/-/eth-query-2.1.2.tgz#d6741d9000106b51510c72db92d6365456a6da5e" dependencies: @@ -2264,6 +2298,24 @@ eth-sig-util@^1.4.2: ethereumjs-abi "git+https://github.com/ethereumjs/ethereumjs-abi.git" ethereumjs-util "^5.1.1" +eth-tx-summary@^3.1.2: + version "3.2.3" + resolved "https://registry.yarnpkg.com/eth-tx-summary/-/eth-tx-summary-3.2.3.tgz#a52d7215616888e012fbc083b3eacd28f3e64764" + dependencies: + async "^2.1.2" + bn.js "^4.11.8" + clone "^2.0.0" + concat-stream "^1.5.1" + end-of-stream "^1.1.0" + eth-query "^2.0.2" + ethereumjs-block "^1.4.1" + ethereumjs-tx "^1.1.1" + ethereumjs-util "^5.0.1" + ethereumjs-vm "2.3.4" + through2 "^2.0.3" + treeify "^1.0.1" + web3-provider-engine "^13.3.2" + ethereum-bridge@^0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/ethereum-bridge/-/ethereum-bridge-0.6.1.tgz#53c93ed7c0e21752a91e5f089a5997e1d6fea228" @@ -2310,8 +2362,8 @@ ethereumjs-abi@0.6.4: ethereumjs-util "^4.3.0" ethereumjs-abi@^0.6.5, "ethereumjs-abi@git+https://github.com/ethereumjs/ethereumjs-abi.git": - version "0.6.5" - resolved "git+https://github.com/ethereumjs/ethereumjs-abi.git#2863c40e0982acfc0b7163f0285d4c56427c7799" + version "0.6.6" + resolved "git+https://github.com/ethereumjs/ethereumjs-abi.git#d84a96796079c8595a0c78accd1e7709f2277215" dependencies: bn.js "^4.10.0" ethereumjs-util "^5.0.0" @@ -2324,7 +2376,7 @@ ethereumjs-account@^2.0.3: rlp "^2.0.0" safe-buffer "^5.1.1" -ethereumjs-block@^1.2.2: +ethereumjs-block@^1.2.2, ethereumjs-block@^1.4.1, ethereumjs-block@^1.6.0, ethereumjs-block@~1.7.0: version "1.7.1" resolved "https://registry.yarnpkg.com/ethereumjs-block/-/ethereumjs-block-1.7.1.tgz#78b88e6cc56de29a6b4884ee75379b6860333c3f" dependencies: @@ -2334,19 +2386,19 @@ ethereumjs-block@^1.2.2: ethereumjs-util "^5.0.0" merkle-patricia-tree "^2.1.2" -ethereumjs-block@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/ethereumjs-block/-/ethereumjs-block-2.1.0.tgz#71d1b19e18061f14cf6371bf34ba31a359931360" +ethereumjs-block@~2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/ethereumjs-block/-/ethereumjs-block-2.2.0.tgz#8c6c3ab4a5eff0a16d9785fbeedbe643f4dbcbef" dependencies: async "^2.0.1" - ethereumjs-common "^0.6.0" + ethereumjs-common "^1.1.0" ethereumjs-tx "^1.2.2" ethereumjs-util "^5.0.0" merkle-patricia-tree "^2.1.2" -ethereumjs-common@^0.6.0: - version "0.6.1" - resolved "https://registry.yarnpkg.com/ethereumjs-common/-/ethereumjs-common-0.6.1.tgz#ec98edf315a7f107afb6acc48e937a8266979fae" +ethereumjs-common@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/ethereumjs-common/-/ethereumjs-common-1.1.0.tgz#5ec9086c314d619d8f05e79a0525829fcb0e93cb" ethereumjs-testrpc-sc@6.1.6: version "6.1.6" @@ -2354,7 +2406,7 @@ ethereumjs-testrpc-sc@6.1.6: dependencies: source-map-support "^0.5.3" -ethereumjs-tx@^1.2.0, ethereumjs-tx@^1.2.2, ethereumjs-tx@^1.3.1, ethereumjs-tx@^1.3.3, ethereumjs-tx@^1.3.4: +ethereumjs-tx@^1.1.1, ethereumjs-tx@^1.2.0, ethereumjs-tx@^1.2.2, ethereumjs-tx@^1.3.1, ethereumjs-tx@^1.3.3: version "1.3.7" resolved "https://registry.yarnpkg.com/ethereumjs-tx/-/ethereumjs-tx-1.3.7.tgz#88323a2d875b10549b8347e09f4862b546f3d89a" dependencies: @@ -2371,7 +2423,7 @@ ethereumjs-util@^4.3.0: rlp "^2.0.0" secp256k1 "^3.0.1" -ethereumjs-util@^5.0.0, ethereumjs-util@^5.0.1, ethereumjs-util@^5.1.1, ethereumjs-util@^5.1.3, ethereumjs-util@^5.2.0: +ethereumjs-util@^5.0.0, ethereumjs-util@^5.0.1, ethereumjs-util@^5.1.1, ethereumjs-util@^5.1.2, ethereumjs-util@^5.1.3, ethereumjs-util@^5.1.5, ethereumjs-util@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz#3e0c0d1741471acf1036052d048623dee54ad642" dependencies: @@ -2384,61 +2436,48 @@ ethereumjs-util@^5.0.0, ethereumjs-util@^5.0.1, ethereumjs-util@^5.1.1, ethereum secp256k1 "^3.0.1" ethereumjs-util@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-6.0.0.tgz#f14841c182b918615afefd744207c7932c8536c0" + version "6.1.0" + resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-6.1.0.tgz#e9c51e5549e8ebd757a339cc00f5380507e799c8" dependencies: bn.js "^4.11.0" create-hash "^1.1.2" - ethjs-util "^0.1.6" + ethjs-util "0.1.6" keccak "^1.0.2" rlp "^2.0.0" safe-buffer "^5.1.1" secp256k1 "^3.0.1" -ethereumjs-vm@^2.0.2: - version "2.5.0" - resolved "https://registry.yarnpkg.com/ethereumjs-vm/-/ethereumjs-vm-2.5.0.tgz#71dde54a093bd813c9defdc6d45ceb8fcca2f603" +ethereumjs-vm@2.3.4: + version "2.3.4" + resolved "https://registry.yarnpkg.com/ethereumjs-vm/-/ethereumjs-vm-2.3.4.tgz#f635d7cb047571a1840a6e9a74d29de4488f8ad6" dependencies: async "^2.1.2" async-eventemitter "^0.2.2" + ethereum-common "0.2.0" ethereumjs-account "^2.0.3" - ethereumjs-block "~2.1.0" - ethereumjs-common "^0.6.0" - ethereumjs-util "^6.0.0" + ethereumjs-block "~1.7.0" + ethereumjs-util "^5.1.3" fake-merkle-patricia-tree "^1.0.1" functional-red-black-tree "^1.0.1" merkle-patricia-tree "^2.1.2" - rustbn.js "~0.2.0" + rustbn.js "~0.1.1" safe-buffer "^5.1.1" -ethereumjs-wallet@^0.6.0: - version "0.6.3" - resolved "https://registry.yarnpkg.com/ethereumjs-wallet/-/ethereumjs-wallet-0.6.3.tgz#b0eae6f327637c2aeb9ccb9047b982ac542e6ab1" +ethereumjs-vm@^2.0.2, ethereumjs-vm@^2.1.0, ethereumjs-vm@^2.3.4: + version "2.6.0" + resolved "https://registry.yarnpkg.com/ethereumjs-vm/-/ethereumjs-vm-2.6.0.tgz#76243ed8de031b408793ac33907fb3407fe400c6" dependencies: - aes-js "^3.1.1" - bs58check "^2.1.2" + async "^2.1.2" + async-eventemitter "^0.2.2" + ethereumjs-account "^2.0.3" + ethereumjs-block "~2.2.0" + ethereumjs-common "^1.1.0" ethereumjs-util "^6.0.0" - hdkey "^1.1.0" - randombytes "^2.0.6" - safe-buffer "^5.1.2" - scrypt.js "^0.3.0" - utf8 "^3.0.0" - uuid "^3.3.2" - -ethers@^4.0.20: - version "4.0.20" - resolved "https://registry.yarnpkg.com/ethers/-/ethers-4.0.20.tgz#2b072b283bb19f4870daf42cf5593e5375697504" - dependencies: - "@types/node" "^10.3.2" - aes-js "3.0.0" - bn.js "^4.4.0" - elliptic "6.3.3" - hash.js "1.1.3" - js-sha3 "0.5.7" - scrypt-js "2.0.4" - setimmediate "1.0.4" - uuid "2.0.1" - xmlhttprequest "1.8.0" + fake-merkle-patricia-tree "^1.0.1" + functional-red-black-tree "^1.0.1" + merkle-patricia-tree "^2.3.2" + rustbn.js "~0.2.0" + safe-buffer "^5.1.1" ethjs-unit@0.1.6: version "0.1.6" @@ -2447,7 +2486,7 @@ ethjs-unit@0.1.6: bn.js "4.11.6" number-to-bn "1.7.0" -ethjs-util@^0.1.3, ethjs-util@^0.1.6: +ethjs-util@0.1.6, ethjs-util@^0.1.3: version "0.1.6" resolved "https://registry.yarnpkg.com/ethjs-util/-/ethjs-util-0.1.6.tgz#f308b62f185f9fe6237132fb2a9818866a5cd536" dependencies: @@ -2570,10 +2609,9 @@ extendr@^2.1.0: dependencies: typechecker "~2.0.1" -external-editor@^3.0.0: +external-editor@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.0.3.tgz#5866db29a97826dbe4bf3afd24070ead9ea43a27" - integrity sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA== dependencies: chardet "^0.7.0" iconv-lite "^0.4.24" @@ -2656,13 +2694,11 @@ figures@^2.0.0: dependencies: escape-string-regexp "^1.0.5" -file-entry-cache@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-2.0.0.tgz#c392990c3e684783d838b8c84a45d8a048458361" - integrity sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E= +file-entry-cache@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c" dependencies: - flat-cache "^1.2.1" - object-assign "^4.0.1" + flat-cache "^2.0.1" file-type@^3.8.0: version "3.9.0" @@ -2728,15 +2764,17 @@ find-up@^2.0.0, find-up@^2.1.0: dependencies: locate-path "^2.0.0" -flat-cache@^1.2.1: - version "1.3.4" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.3.4.tgz#2c2ef77525cc2929007dfffa1dd314aa9c9dee6f" - integrity sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg== +flat-cache@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" dependencies: - circular-json "^0.3.1" - graceful-fs "^4.1.2" - rimraf "~2.6.2" - write "^0.2.1" + flatted "^2.0.0" + rimraf "2.6.3" + write "1.0.3" + +flatted@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.0.tgz#55122b6536ea496b4b44893ee2608141d10d9916" for-each@^0.3.2, for-each@~0.3.3: version "0.3.3" @@ -2758,7 +2796,7 @@ forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" -form-data@~2.3.2: +form-data@^2.2.0, form-data@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" dependencies: @@ -2827,23 +2865,21 @@ fs-promise@^2.0.0: fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= fs@0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/fs/-/fs-0.0.2.tgz#e1f244ef3933c1b2a64bd4799136060d0f5914f8" fsevents@^1.0.0: - version "1.2.4" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.4.tgz#f41dcb1af2582af3692da36fc55cbd8e1041c426" + version "1.2.7" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.7.tgz#4851b664a3783e52003b3c66eb0eee1074933aa4" dependencies: nan "^2.9.2" node-pre-gyp "^0.10.0" -fstream@^1.0.8: - version "1.0.12" - resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.12.tgz#4e8ba8ee2d48be4f7d0de505455548eae5932045" - integrity sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg== +fstream@^1.0.2, fstream@^1.0.8: + version "1.0.11" + resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.11.tgz#5c1fb1f117477114f0632a0eb4b71b3cb0fd3171" dependencies: graceful-fs "^4.1.2" inherits "~2.0.0" @@ -2858,13 +2894,11 @@ functional-red-black-tree@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" -ganache-cli@^6.2.5: - version "6.2.5" - resolved "https://registry.yarnpkg.com/ganache-cli/-/ganache-cli-6.2.5.tgz#efda5115fa3a0c62d7f5729fdd78da70ca55b1ad" +ganache-cli@6.1.8: + version "6.1.8" + resolved "https://registry.yarnpkg.com/ganache-cli/-/ganache-cli-6.1.8.tgz#49a8a331683a9652183f82ef1378d17e1814fcd3" dependencies: - bn.js "4.11.8" - source-map-support "0.5.9" - yargs "11.1.0" + source-map-support "^0.5.3" gauge@~2.7.3: version "2.7.4" @@ -2883,6 +2917,10 @@ get-caller-file@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" +get-port@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/get-port/-/get-port-3.2.0.tgz#dd7ce7de187c06c8bf353796ac71e099f0980ebc" + get-stream@^2.2.0: version "2.3.1" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-2.3.1.tgz#5f38f93f346009666ee0150a054167f91bdd95de" @@ -2938,7 +2976,7 @@ glob@^5.0.15: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.0.0, glob@^7.1.2, glob@~7.1.2: +glob@^7.0.0, glob@^7.1.2, glob@^7.1.3, glob@~7.1.3: version "7.1.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" dependencies: @@ -2949,18 +2987,6 @@ glob@^7.0.0, glob@^7.1.2, glob@~7.1.2: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.1.3: - version "7.1.4" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255" - integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - glob@~6.0.4: version "6.0.4" resolved "https://registry.yarnpkg.com/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22" @@ -2979,8 +3005,8 @@ global@~4.3.0: process "~0.5.1" globals@^11.7.0: - version "11.9.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-11.9.0.tgz#bde236808e987f290768a93d065060d78e6ab249" + version "11.11.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.11.0.tgz#dcf93757fa2de5486fbeed7118538adf789e9c2e" globals@^9.18.0: version "9.18.0" @@ -3006,9 +3032,8 @@ got@7.1.0, got@^7.1.0: url-to-options "^1.0.1" graceful-fs@*, graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9: - version "4.2.0" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.0.tgz#8d8fdc73977cb04104721cb53666c1ca64cd328b" - integrity sha512-jpSvDPV4Cq/bgtpndIWbI5hmYxhQGHPC4d4cqBPb4DLniCfhJokdXhwhaDuLBGLQdvvRum/UiX6ECVIPvDXqdg== + version "4.1.15" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00" "graceful-readlink@>= 1.0.0": version "1.0.1" @@ -3023,11 +3048,10 @@ growl@1.10.5, "growl@~> 1.10.0": resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" handlebars@^4.0.1: - version "4.1.2" - resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.1.2.tgz#b6b37c1ced0306b221e094fc7aca3ec23b131b67" - integrity sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw== + version "4.1.0" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.1.0.tgz#0d6a6f34ff1f63cecec8423aa4169827bf787c3a" dependencies: - neo-async "^2.6.0" + async "^2.5.0" optimist "^0.6.1" source-map "^0.6.1" optionalDependencies: @@ -3120,13 +3144,6 @@ hash-base@^3.0.0: inherits "^2.0.1" safe-buffer "^5.0.1" -hash.js@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.3.tgz#340dedbe6290187151c1ea1d777a3448935df846" - dependencies: - inherits "^2.0.3" - minimalistic-assert "^1.0.0" - hash.js@^1.0.0, hash.js@^1.0.3: version "1.1.7" resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" @@ -3134,14 +3151,6 @@ hash.js@^1.0.0, hash.js@^1.0.3: inherits "^2.0.3" minimalistic-assert "^1.0.1" -hdkey@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/hdkey/-/hdkey-1.1.0.tgz#e74e7b01d2c47f797fa65d1d839adb7a44639f29" - dependencies: - coinstring "^2.0.0" - safe-buffer "^5.1.1" - secp256k1 "^3.0.1" - he@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" @@ -3165,6 +3174,17 @@ hosted-git-info@^2.1.4: version "2.7.1" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.7.1.tgz#97f236977bd6e125408930ff6de3eec6281ec047" +http-basic@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/http-basic/-/http-basic-7.0.0.tgz#82f0a506be942732ec8deebee80e746ef5736dba" + dependencies: + "@types/concat-stream" "^1.6.0" + "@types/node" "^9.4.1" + caseless "~0.12.0" + concat-stream "^1.4.6" + http-response-object "^3.0.1" + parse-cache-control "^1.0.1" + http-errors@1.6.3, http-errors@~1.6.2, http-errors@~1.6.3: version "1.6.3" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" @@ -3178,6 +3198,12 @@ http-https@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/http-https/-/http-https-1.0.0.tgz#2f908dd5f1db4068c058cd6e6d4ce392c913389b" +http-response-object@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/http-response-object/-/http-response-object-3.0.1.tgz#90174d44c27b5e797cf6efe51a043bc889ae64bf" + dependencies: + "@types/node" "^9.3.0" + http-signature@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" @@ -3228,8 +3254,8 @@ ignore@^4.0.6: resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" ignore@^5.0.2: - version "5.0.4" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.0.4.tgz#33168af4a21e99b00c5d41cbadb6a6cb49903a45" + version "5.0.5" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.0.5.tgz#c663c548d6ce186fb33616a8ccb5d46e56bdbbf9" ignorefs@^1.0.0: version "1.2.0" @@ -3246,6 +3272,13 @@ immediate@^3.2.3: version "3.2.3" resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.2.3.tgz#d140fa8f614659bd6541233097ddaac25cdd991c" +import-fresh@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.0.0.tgz#a3d897f420cab0e671236897f75bc14b4885c390" + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" @@ -3253,17 +3286,11 @@ imurmurhash@^0.1.4: inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= dependencies: once "^1.3.0" wrappy "1" -inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -inherits@2.0.3: +inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" @@ -3275,20 +3302,20 @@ ini@~1.3.0: version "1.3.5" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" -inquirer@^6.1.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.2.1.tgz#9943fc4882161bdb0b0c9276769c75b32dbfcd52" +inquirer@^6.2.2: + version "6.2.2" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.2.2.tgz#46941176f65c9eb20804627149b743a218f25406" dependencies: - ansi-escapes "^3.0.0" - chalk "^2.0.0" + ansi-escapes "^3.2.0" + chalk "^2.4.2" cli-cursor "^2.1.0" cli-width "^2.0.0" - external-editor "^3.0.0" + external-editor "^3.0.3" figures "^2.0.0" - lodash "^4.17.10" + lodash "^4.17.11" mute-stream "0.0.7" run-async "^2.2.0" - rxjs "^6.1.0" + rxjs "^6.4.0" string-width "^2.1.0" strip-ansi "^5.0.0" through "^2.3.6" @@ -3337,12 +3364,6 @@ is-buffer@^1.1.5, is-buffer@~1.1.1: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" -is-builtin-module@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe" - dependencies: - builtin-modules "^1.0.0" - is-callable@^1.1.3, is-callable@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75" @@ -3534,8 +3555,8 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" iso-url@~0.4.4: - version "0.4.4" - resolved "https://registry.yarnpkg.com/iso-url/-/iso-url-0.4.4.tgz#473a45569b6015da0c23f831010aeff69e3841e8" + version "0.4.6" + resolved "https://registry.yarnpkg.com/iso-url/-/iso-url-0.4.6.tgz#45005c4af4984cad4f8753da411b41b74cf0a8a6" isobject@^2.0.0: version "2.1.0" @@ -3547,13 +3568,6 @@ isobject@^3.0.0, isobject@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" -isomorphic-fetch@^2.2.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" - dependencies: - node-fetch "^1.0.1" - whatwg-fetch ">=0.10.0" - isstream@0.1.x, isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" @@ -3584,10 +3598,6 @@ isurl@^1.0.0-alpha5: has-to-string-tag-x "^1.2.0" is-object "^1.0.1" -js-sha3@0.5.7: - version "0.5.7" - resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.5.7.tgz#0d4ffd8002d5333aabaf4a23eed2f6374c9f28e7" - js-sha3@^0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.6.1.tgz#5b89f77a7477679877f58c4a075240934b1f95c0" @@ -3605,9 +3615,8 @@ js-tokens@^3.0.2: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" js-yaml@3.x, js-yaml@^3.11.0, js-yaml@^3.12.0: - version "3.13.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" - integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== + version "3.12.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.1.tgz#295c8632a18a23e054cf5c9d3cecafe678167600" dependencies: argparse "^1.0.7" esprima "^4.0.0" @@ -3624,7 +3633,7 @@ jsesc@~0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" -json-rpc-engine@^3.6.0: +json-rpc-engine@^3.4.0, json-rpc-engine@^3.6.0: version "3.8.0" resolved "https://registry.yarnpkg.com/json-rpc-engine/-/json-rpc-engine-3.8.0.tgz#9d4ff447241792e1d0a232f6ef927302bb0c62a9" dependencies: @@ -3839,7 +3848,7 @@ lodash.assign@^4.0.3, lodash.assign@^4.0.6: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" -lodash@4.x, lodash@=4.17.11, lodash@^4.13.1, lodash@^4.14.2, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.4, lodash@^4.17.5: +lodash@4.x, lodash@=4.17.11, lodash@^4.14.2, lodash@^4.17.11, lodash@^4.17.4, lodash@^4.17.5: version "4.17.11" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" @@ -3855,7 +3864,7 @@ long-timeout@0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/long-timeout/-/long-timeout-0.1.1.tgz#9721d788b47e0bcb5a24c2e2bee1a0da55dab514" -loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.1: +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" dependencies: @@ -3911,8 +3920,8 @@ math-interval-parser@^1.1.0: xregexp "^2.0.0" math-random@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.1.tgz#8b3aac588b8a66e4975e3cdea67f7bb329601fac" + version "1.0.4" + resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.4.tgz#5dd6943c938548267016d4e34f057583080c514c" md5.js@^1.3.4: version "1.3.5" @@ -3972,7 +3981,7 @@ merge-descriptors@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" -merkle-patricia-tree@^2.1.2: +merkle-patricia-tree@^2.1.2, merkle-patricia-tree@^2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/merkle-patricia-tree/-/merkle-patricia-tree-2.3.2.tgz#982ca1b5a0fde00eed2f6aeed1f9152860b8208a" dependencies: @@ -4042,16 +4051,15 @@ miller-rabin@^4.0.0: bn.js "^4.0.0" brorand "^1.0.1" -mime-db@~1.37.0: - version "1.37.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.37.0.tgz#0b6a0ce6fdbe9576e25f1f2d2fde8830dc0ad0d8" - integrity sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg== +mime-db@~1.38.0: + version "1.38.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.38.0.tgz#1a2aab16da9eb167b49c6e4df2d9c68d63d8e2ad" mime-types@^2.1.12, mime-types@^2.1.16, mime-types@~2.1.18, mime-types@~2.1.19: - version "2.1.21" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.21.tgz#28995aa1ecb770742fe6ae7e58f9181c744b3f96" + version "2.1.22" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.22.tgz#fe6b355a190926ab7698c9a0556a11199b2199bd" dependencies: - mime-db "~1.37.0" + mime-db "~1.38.0" mime@1.4.1: version "1.4.1" @@ -4079,17 +4087,15 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" -"minimatch@2 || 3", minimatch@3.0.4, minimatch@^3.0.3, minimatch@^3.0.4: +"minimatch@2 || 3", minimatch@3.0.4, minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== dependencies: brace-expansion "^1.1.7" minimist@0.0.8: version "0.0.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" - integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= minimist@^1.2.0, minimist@~1.2.0: version "1.2.0" @@ -4098,19 +4104,17 @@ minimist@^1.2.0, minimist@~1.2.0: minimist@~0.0.1: version "0.0.10" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" - integrity sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8= -minipass@^2.2.1, minipass@^2.3.5: +minipass@^2.2.1, minipass@^2.3.4: version "2.3.5" resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.5.tgz#cacebe492022497f656b0f0f51e2682a9ed2d848" dependencies: safe-buffer "^5.1.2" yallist "^3.0.0" -minizlib@^1.2.1: +minizlib@^1.1.1: version "1.2.1" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.2.1.tgz#dd27ea6136243c7c880684e8672bb3a45fd9b614" - integrity sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA== dependencies: minipass "^2.2.1" @@ -4175,8 +4179,8 @@ mocha@^5.0.1: supports-color "5.4.0" mock-fs@^4.1.0: - version "4.7.0" - resolved "https://registry.yarnpkg.com/mock-fs/-/mock-fs-4.7.0.tgz#9f17e219cacb8094f4010e0a8c38589e2b33c299" + version "4.8.0" + resolved "https://registry.yarnpkg.com/mock-fs/-/mock-fs-4.8.0.tgz#eb0784ceba4b34c91a924a5112eeb36e1b5a9e29" moment-timezone@^0.5.23: version "0.5.23" @@ -4185,8 +4189,8 @@ moment-timezone@^0.5.23: moment ">= 2.9.0" "moment@>= 2.9.0", moment@^2.22.2: - version "2.23.0" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.23.0.tgz#759ea491ac97d54bac5ad776996e2a58cc1bc225" + version "2.24.0" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" mout@^0.11.0: version "0.11.1" @@ -4279,11 +4283,6 @@ negotiator@0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" -neo-async@^2.6.0: - version "2.6.1" - resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c" - integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw== - next-tick@1: version "1.0.0" resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" @@ -4303,7 +4302,11 @@ node-cache@^4.1.1: clone "2.x" lodash "4.x" -node-fetch@^1.0.1, node-fetch@~1.7.1: +node-fetch@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.1.2.tgz#ab884e8e7e57e38a944753cec706f788d1768bb5" + +node-fetch@~1.7.1: version "1.7.3" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" dependencies: @@ -4323,11 +4326,11 @@ node-pre-gyp@^0.10.0: rc "^1.2.7" rimraf "^2.6.1" semver "^5.3.0" - tar "^4.4.8" + tar "^4" node-schedule@^1.2.3: - version "1.3.1" - resolved "https://registry.yarnpkg.com/node-schedule/-/node-schedule-1.3.1.tgz#6909dd644211bca153b15afc62e1dc0afa7d28be" + version "1.3.2" + resolved "https://registry.yarnpkg.com/node-schedule/-/node-schedule-1.3.2.tgz#d774b383e2a6f6ade59eecc62254aea07cd758cb" dependencies: cron-parser "^2.7.3" long-timeout "0.1.1" @@ -4347,11 +4350,11 @@ nopt@^4.0.1: osenv "^0.1.4" normalize-package-data@^2.3.2: - version "2.4.0" - resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f" + version "2.5.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" dependencies: hosted-git-info "^2.1.4" - is-builtin-module "^1.0.0" + resolve "^1.10.0" semver "2 || 3 || 4 || 5" validate-npm-package-license "^3.0.1" @@ -4362,12 +4365,12 @@ normalize-path@^2.0.0, normalize-path@^2.0.1: remove-trailing-separator "^1.0.1" npm-bundled@^1.0.1: - version "1.0.5" - resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.5.tgz#3c1732b7ba936b3a10325aef616467c0ccbcc979" + version "1.0.6" + resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.6.tgz#e7ba9aadcef962bb61248f91721cd932b3fe6bdd" npm-packlist@^1.1.6: - version "1.1.12" - resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.1.12.tgz#22bde2ebc12e72ca482abd67afc51eb49377243a" + version "1.3.0" + resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.3.0.tgz#7f01e8e44408341379ca98cfd756e7b29bd2626c" dependencies: ignore-walk "^3.0.1" npm-bundled "^1.0.1" @@ -4419,8 +4422,8 @@ object-inspect@~1.6.0: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.6.0.tgz#c70b6cbf72f274aab4c34c0c82f5167bf82cf15b" object-keys@^1.0.12: - version "1.0.12" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.12.tgz#09c53855377575310cca62f55bb334abff7b3ed2" + version "1.1.0" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.0.tgz#11bd22348dd2e096a045ab06f6c85bcc340fa032" object-keys@~0.4.0: version "0.4.0" @@ -4460,7 +4463,6 @@ on-finished@~2.3.0: once@1.x, once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= dependencies: wrappy "1" @@ -4470,14 +4472,13 @@ onetime@^2.0.0: dependencies: mimic-fn "^1.0.0" -openzeppelin-solidity@1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/openzeppelin-solidity/-/openzeppelin-solidity-1.10.0.tgz#d77eee6653f5958d051318a61ba0b436f92216c0" +openzeppelin-solidity@2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/openzeppelin-solidity/-/openzeppelin-solidity-2.2.0.tgz#1f0e857f53bda29b77a8e72f9a629db81015fd34" optimist@^0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" - integrity sha1-2j6nRob6IaGaERwybpDrFaAZZoY= dependencies: minimist "~0.0.1" wordwrap "~0.0.2" @@ -4556,15 +4557,26 @@ p-try@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" +parent-module@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.0.tgz#df250bdc5391f4a085fb589dad761f5ad6b865b5" + dependencies: + callsites "^3.0.0" + parse-asn1@^5.0.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.1.tgz#f6bf293818332bd0dab54efb16087724745e6ca8" + version "5.1.4" + resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.4.tgz#37f6628f823fbdeb2273b4d540434a22f3ef1fcc" dependencies: asn1.js "^4.0.0" browserify-aes "^1.0.0" create-hash "^1.1.0" evp_bytestokey "^1.0.0" pbkdf2 "^3.0.3" + safe-buffer "^5.1.1" + +parse-cache-control@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parse-cache-control/-/parse-cache-control-1.0.1.tgz#8eeab3e54fa56920fe16ba38f77fa21aacc2d74e" parse-glob@^3.0.4: version "3.0.4" @@ -4618,7 +4630,7 @@ path-key@^2.0.0, path-key@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" -path-parse@^1.0.5, path-parse@^1.0.6: +path-parse@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" @@ -4680,11 +4692,11 @@ pinkie@^2.0.0: version "2.0.4" resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" -pkg-dir@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-1.0.0.tgz#7a4b508a8d5bb2d629d447056ff4e9c9314cf3d4" +pkg-dir@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b" dependencies: - find-up "^1.0.0" + find-up "^2.1.0" pkginfo@0.3.x: version "0.3.1" @@ -4694,11 +4706,6 @@ pkginfo@0.x.x: version "0.4.1" resolved "https://registry.yarnpkg.com/pkginfo/-/pkginfo-0.4.1.tgz#b5418ef0439de5425fc4995042dced14fb2a84ff" -pluralize@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777" - integrity sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow== - posix-character-classes@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" @@ -4707,6 +4714,10 @@ pragma-singleton@1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/pragma-singleton/-/pragma-singleton-1.0.3.tgz#6894317bb8d47157e59de2a4a009db7e6f63e30e" +precond@0.2: + version "0.2.3" + resolved "https://registry.yarnpkg.com/precond/-/precond-0.2.3.tgz#aa9591bcaa24923f1e0f4849d240f47efc1075ac" + prelude-ls@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" @@ -4720,8 +4731,8 @@ preserve@^0.2.0: resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" prettier@^1.15.3: - version "1.15.3" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.15.3.tgz#1feaac5bdd181237b54dbe65d874e02a1472786a" + version "1.16.4" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.16.4.tgz#73e37e73e018ad2db9c76742e2647e21790c9717" private@^0.1.6, private@^0.1.8: version "0.1.8" @@ -4746,6 +4757,12 @@ promise-to-callback@^1.0.0: is-fn "^1.0.0" set-immediate-shim "^1.0.1" +promise@^8.0.0: + version "8.0.2" + resolved "https://registry.yarnpkg.com/promise/-/promise-8.0.2.tgz#9dcd0672192c589477d56891271bdc27547ae9f0" + dependencies: + asap "~2.0.6" + prompt@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/prompt/-/prompt-1.0.0.tgz#8e57123c396ab988897fb327fd3aedc3e735e4fe" @@ -4758,11 +4775,12 @@ prompt@^1.0.0: winston "2.1.x" prop-types@^15.6.2: - version "15.6.2" - resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.2.tgz#05d5ca77b4453e985d60fc7ff8c859094a497102" + version "15.7.2" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" dependencies: - loose-envify "^1.3.1" + loose-envify "^1.4.0" object-assign "^4.1.1" + react-is "^16.8.1" proxy-addr@~2.0.4: version "2.0.4" @@ -4806,6 +4824,10 @@ qs@6.5.2, qs@~6.5.2: version "6.5.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" +qs@^6.4.0: + version "6.6.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.6.0.tgz#a99c0f69a8d26bf7ef012f871cdabb0aee4424c2" + query-string@^5.0.1: version "5.1.1" resolved "https://registry.yarnpkg.com/query-string/-/query-string-5.1.1.tgz#a78c012b71c17e05f2e3fa2319dd330682efb3cb" @@ -4822,7 +4844,7 @@ randomatic@^3.0.0: kind-of "^6.0.0" math-random "^1.0.1" -randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.0.6: +randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5: version "2.0.6" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.0.6.tgz#d302c522948588848a8d300c932b44c24231da80" dependencies: @@ -4862,22 +4884,26 @@ rc@^1.2.7: strip-json-comments "~2.0.1" react-dom@^16.2.0: - version "16.7.0" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.7.0.tgz#a17b2a7ca89ee7390bc1ed5eb81783c7461748b8" + version "16.8.2" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.8.2.tgz#7c8a69545dd554d45d66442230ba04a6a0a3c3d3" dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" prop-types "^15.6.2" - scheduler "^0.12.0" + scheduler "^0.13.2" + +react-is@^16.8.1: + version "16.8.2" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.2.tgz#09891d324cad1cb0c1f2d91f70a71a4bee34df0f" react@^16.2.0: - version "16.7.0" - resolved "https://registry.yarnpkg.com/react/-/react-16.7.0.tgz#b674ec396b0a5715873b350446f7ea0802ab6381" + version "16.8.2" + resolved "https://registry.yarnpkg.com/react/-/react-16.8.2.tgz#83064596feaa98d9c2857c4deae1848b542c9c0c" dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" prop-types "^15.6.2" - scheduler "^0.12.0" + scheduler "^0.13.2" read-pkg-up@^1.0.1: version "1.0.1" @@ -4924,7 +4950,7 @@ readable-stream@^1.0.33: isarray "0.0.1" string_decoder "~0.10.x" -readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.2.9, readable-stream@^2.3.0, readable-stream@^2.3.5: +readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.2.2, readable-stream@^2.2.9, readable-stream@^2.3.0, readable-stream@^2.3.5, readable-stream@~2.3.6: version "2.3.6" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" dependencies: @@ -5038,28 +5064,48 @@ req-cwd@^1.0.1: dependencies: req-from "^1.0.1" +req-cwd@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/req-cwd/-/req-cwd-2.0.0.tgz#d4082b4d44598036640fb73ddea01ed53db49ebc" + dependencies: + req-from "^2.0.0" + req-from@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/req-from/-/req-from-1.0.1.tgz#bf81da5147947d32d13b947dc12a58ad4587350e" dependencies: resolve-from "^2.0.0" -request-promise-core@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.1.tgz#3eee00b2c5aa83239cfb04c5700da36f81cd08b6" +req-from@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/req-from/-/req-from-2.0.0.tgz#d74188e47f93796f4aa71df6ee35ae689f3e0e70" dependencies: - lodash "^4.13.1" + resolve-from "^3.0.0" + +request-promise-core@1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.2.tgz#339f6aababcafdb31c799ff158700336301d3346" + dependencies: + lodash "^4.17.11" + +request-promise-native@^1.0.5: + version "1.0.7" + resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.7.tgz#a49868a624bdea5069f1251d0a836e0d89aa2c59" + dependencies: + request-promise-core "1.1.2" + stealthy-require "^1.1.1" + tough-cookie "^2.3.3" request-promise@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/request-promise/-/request-promise-4.2.2.tgz#d1ea46d654a6ee4f8ee6a4fea1018c22911904b4" + version "4.2.4" + resolved "https://registry.yarnpkg.com/request-promise/-/request-promise-4.2.4.tgz#1c5ed0d71441e38ad58c7ce4ea4ea5b06d54b310" dependencies: bluebird "^3.5.0" - request-promise-core "1.1.1" - stealthy-require "^1.1.0" - tough-cookie ">=2.3.3" + request-promise-core "1.1.2" + stealthy-require "^1.1.1" + tough-cookie "^2.3.3" -request@^2.67.0, request@^2.79.0, request@^2.81.0, request@^2.85.0, request@^2.88.0: +request@^2.67.0, request@^2.79.0, request@^2.81.0, request@^2.83.0, request@^2.85.0, request@^2.88.0: version "2.88.0" resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" dependencies: @@ -5092,27 +5138,26 @@ require-from-string@^1.1.0: version "1.2.1" resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-1.2.1.tgz#529c9ccef27380adfec9a2f965b649bbee636418" +require-from-string@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + require-main-filename@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" -require-uncached@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/require-uncached/-/require-uncached-1.0.3.tgz#4e0d56d6c9662fd31e43011c4b95aa49955421d3" - integrity sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM= - dependencies: - caller-path "^0.1.0" - resolve-from "^1.0.0" - -resolve-from@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226" - integrity sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY= - resolve-from@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-2.0.0.tgz#9480ab20e94ffa1d9e80a804c7ea147611966b57" +resolve-from@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + resolve-url@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" @@ -5121,18 +5166,12 @@ resolve@1.1.x: version "1.1.7" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" -resolve@^1.1.6, resolve@^1.5.0, resolve@^1.6.0, resolve@^1.8.1: - version "1.9.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.9.0.tgz#a14c6fdfa8f92a7df1d996cb7105fa744658ea06" +resolve@^1.1.6, resolve@^1.10.0, resolve@^1.5.0, resolve@^1.8.1, resolve@^1.9.0, resolve@~1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.10.0.tgz#3bdaaeaf45cc07f375656dfd2e54ed0810b101ba" dependencies: path-parse "^1.0.6" -resolve@~1.7.1: - version "1.7.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.7.1.tgz#aadd656374fd298aee895bc026b8297418677fd3" - dependencies: - path-parse "^1.0.5" - restore-cursor@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" @@ -5154,7 +5193,7 @@ revalidator@0.1.x: version "0.1.8" resolved "https://registry.yarnpkg.com/revalidator/-/revalidator-0.1.8.tgz#fece61bfa0c1b52a206bd6b18198184bdd523a3b" -rimraf@2, rimraf@2.x.x, rimraf@^2.2.8, rimraf@^2.6.1, rimraf@~2.6.2: +rimraf@2, rimraf@2.6.3, rimraf@2.x.x, rimraf@^2.2.8, rimraf@^2.6.1: version "2.6.3" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" dependencies: @@ -5168,9 +5207,10 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: inherits "^2.0.1" rlp@^2.0.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/rlp/-/rlp-2.2.1.tgz#9cacf53ad2579163cc56fba64b1f4336f1f2fa46" + version "2.2.2" + resolved "https://registry.yarnpkg.com/rlp/-/rlp-2.2.2.tgz#e6677b83cca9105371d930e01d8ffc1263139d05" dependencies: + bn.js "^4.11.1" safe-buffer "^5.1.1" run-async@^2.2.0: @@ -5179,14 +5219,17 @@ run-async@^2.2.0: dependencies: is-promise "^2.1.0" +rustbn.js@~0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/rustbn.js/-/rustbn.js-0.1.2.tgz#979fa0f9562216dd667c9d2cd179ae5d13830eff" + rustbn.js@~0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/rustbn.js/-/rustbn.js-0.2.0.tgz#8082cb886e707155fd1cb6f23bd591ab8d55d0ca" -rxjs@^6.1.0: +rxjs@^6.4.0: version "6.4.0" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.4.0.tgz#f3bb0fe7bda7fb69deac0c16f17b50b0b8790504" - integrity sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw== dependencies: tslib "^1.9.0" @@ -5232,9 +5275,9 @@ scandirectory@^2.5.0: safefs "^3.1.2" taskgroup "^4.0.5" -scheduler@^0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.12.0.tgz#8ab17699939c0aedc5a196a657743c496538647b" +scheduler@^0.13.2: + version "0.13.2" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.13.2.tgz#969eaee2764a51d2e97b20a60963b2546beff8fa" dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" @@ -5243,10 +5286,6 @@ scrypt-async@^1.2.0: version "1.3.1" resolved "https://registry.yarnpkg.com/scrypt-async/-/scrypt-async-1.3.1.tgz#a11fd6fac981b4b823ee01dee0221169500ddae9" -scrypt-js@2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-2.0.4.tgz#32f8c5149f0797672e551c07e230f834b6af5f16" - scrypt.js@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/scrypt.js/-/scrypt.js-0.2.0.tgz#af8d1465b71e9990110bedfc593b9479e03a8ada" @@ -5254,14 +5293,6 @@ scrypt.js@0.2.0: scrypt "^6.0.2" scryptsy "^1.2.1" -scrypt.js@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/scrypt.js/-/scrypt.js-0.3.0.tgz#6c62d61728ad533c8c376a2e5e3e86d41a95c4c0" - dependencies: - scryptsy "^1.2.1" - optionalDependencies: - scrypt "^6.0.2" - scrypt@^6.0.2: version "6.0.3" resolved "https://registry.yarnpkg.com/scrypt/-/scrypt-6.0.3.tgz#04e014a5682b53fa50c2d5cce167d719c06d870d" @@ -5275,8 +5306,8 @@ scryptsy@^1.2.1: pbkdf2 "^3.0.3" secp256k1@^3.0.1: - version "3.6.1" - resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-3.6.1.tgz#f0475d42096218ff00e45a127242abdff9285335" + version "3.6.2" + resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-3.6.2.tgz#da835061c833c74a12f75c73d2ec2e980f00dc1f" dependencies: bindings "^1.2.1" bip66 "^1.1.3" @@ -5368,10 +5399,6 @@ set-value@^2.0.0: is-plain-object "^2.0.3" split-string "^3.0.1" -setimmediate@1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.4.tgz#20e81de622d4a02588ce0c8da8973cbcf1d3138f" - setimmediate@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" @@ -5387,6 +5414,13 @@ sha.js@^2.4.0, sha.js@^2.4.8: inherits "^2.0.1" safe-buffer "^5.0.1" +sha1@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/sha1/-/sha1-1.1.1.tgz#addaa7a93168f393f19eb2b15091618e2700f848" + dependencies: + charenc ">= 0.0.1" + crypt ">= 0.0.1" + sha3@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/sha3/-/sha3-1.2.2.tgz#a66c5098de4c25bc88336ec8b4817d005bca7ba9" @@ -5403,7 +5437,7 @@ shebang-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" -shelljs@^0.7.4: +shelljs@^0.7.4, shelljs@^0.7.8: version "0.7.8" resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.7.8.tgz#decbcf874b0d1e5fb72e14b164a9683048e9acb3" dependencies: @@ -5442,7 +5476,6 @@ slash@^1.0.0: slice-ansi@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" - integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ== dependencies: ansi-styles "^3.2.0" astral-regex "^1.0.0" @@ -5487,7 +5520,7 @@ sol-explore@^1.6.2: version "1.6.2" resolved "https://registry.yarnpkg.com/sol-explore/-/sol-explore-1.6.2.tgz#43ae8c419fd3ac056a05f8a9d1fb1022cd41ecc2" -sol-merger@^0.1.3: +sol-merger@^0.1.2: version "0.1.3" resolved "https://registry.yarnpkg.com/sol-merger/-/sol-merger-0.1.3.tgz#184284ba4811aebe8950f510df4e8218f568b35f" dependencies: @@ -5498,15 +5531,16 @@ sol-merger@^0.1.3: fs-extra "^7.0.1" glob "^7.1.2" -solc@0.4.24: - version "0.4.24" - resolved "https://registry.yarnpkg.com/solc/-/solc-0.4.24.tgz#354f14b269b38cbaa82a47d1ff151723502b954e" +solc@0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/solc/-/solc-0.5.0.tgz#2deb2ae992acac3afb909f85c38d00f01dcb335e" dependencies: fs-extra "^0.30.0" + keccak "^1.0.2" memorystream "^0.3.1" - require-from-string "^1.1.0" - semver "^5.3.0" - yargs "^4.7.1" + require-from-string "^2.0.0" + semver "^5.5.0" + yargs "^11.0.0" solc@^0.4.2: version "0.4.25" @@ -5546,6 +5580,10 @@ solidity-docgen@^0.1.1: react-dom "^16.2.0" shelljs "^0.8.1" +solidity-parser-antlr@^0.2.10: + version "0.2.15" + resolved "https://registry.yarnpkg.com/solidity-parser-antlr/-/solidity-parser-antlr-0.2.15.tgz#4be687a0a53da268c6a07398e0cfb3168896d610" + solidity-parser-sc@0.4.11: version "0.4.11" resolved "https://registry.yarnpkg.com/solidity-parser-sc/-/solidity-parser-sc-0.4.11.tgz#86734c9205537007f4d6201b57176e41696ee607" @@ -5558,9 +5596,9 @@ solium-plugin-security@0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/solium-plugin-security/-/solium-plugin-security-0.1.1.tgz#2a87bcf8f8c3abf7d198e292e4ac080284e3f3f6" -solium@^1.1.8: - version "1.2.1" - resolved "https://registry.yarnpkg.com/solium/-/solium-1.2.1.tgz#ead11f2921ba2337461752155cc96149ea08f247" +solium@^1.1.6: + version "1.2.3" + resolved "https://registry.yarnpkg.com/solium/-/solium-1.2.3.tgz#88167ea6c8f7d1b68b59d3a6a78d563ca455ecdd" dependencies: ajv "^5.2.2" chokidar "^1.6.0" @@ -5573,12 +5611,12 @@ solium@^1.1.8: sol-digger "0.0.2" sol-explore "1.6.1" solium-plugin-security "0.1.1" - solparse "2.2.7" + solparse "2.2.8" text-table "^0.2.0" -solparse@2.2.7: - version "2.2.7" - resolved "https://registry.yarnpkg.com/solparse/-/solparse-2.2.7.tgz#5479ff4ba4ca6900df89b902b5af1ee23b816250" +solparse@2.2.8: + version "2.2.8" + resolved "https://registry.yarnpkg.com/solparse/-/solparse-2.2.8.tgz#d13e42dbed95ce32f43894f5ec53f00d14cf9f11" dependencies: mocha "^4.0.1" pegjs "^0.10.0" @@ -5598,19 +5636,19 @@ source-map-resolve@^0.5.0: source-map-url "^0.4.0" urix "^0.1.0" -source-map-support@0.5.9, source-map-support@^0.5.3: - version "0.5.9" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.9.tgz#41bc953b2534267ea2d605bccfa7bfa3111ced5f" - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - source-map-support@^0.4.15: version "0.4.18" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f" dependencies: source-map "^0.5.6" +source-map-support@^0.5.3: + version "0.5.10" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.10.tgz#2214080bc9d51832511ee2bab96e3c2f9353120c" + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + source-map-url@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" @@ -5664,11 +5702,10 @@ sprintf-js@>=1.0.3: sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= sshpk@^1.7.0: - version "1.16.0" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.0.tgz#1d4963a2fbffe58050aa9084ca20be81741c07de" + version "1.16.1" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" dependencies: asn1 "~0.2.3" assert-plus "^1.0.0" @@ -5703,7 +5740,7 @@ stdio@^0.2.7: version "0.2.7" resolved "https://registry.yarnpkg.com/stdio/-/stdio-0.2.7.tgz#a1c57da10fe1cfaa0c3bf683c9d0743d1b660839" -stealthy-require@^1.1.0: +stealthy-require@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" @@ -5729,7 +5766,6 @@ string-width@^1.0.1: string-width@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.0.0.tgz#5a1690a57cc78211fffd9bf24bbe24d090604eb1" - integrity sha512-rr8CUxBbvOZDUvc5lNIJ+OC1nPVpz+Siw9VBtUjB9b6jZehZLFt0JMCZzShFHIsI8cbhm0EsNIfWJMFV3cu3Ew== dependencies: emoji-regex "^7.0.1" is-fullwidth-code-point "^2.0.0" @@ -5847,30 +5883,43 @@ swarm-js@0.1.37: tar.gz "^1.0.5" xhr-request-promise "^0.1.2" -table@^5.0.2: +sync-request@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/sync-request/-/sync-request-6.0.0.tgz#db867eccc4ed31bbcb9fa3732393a3413da582ed" + dependencies: + http-response-object "^3.0.1" + sync-rpc "^1.2.1" + then-request "^6.0.0" + +sync-rpc@^1.2.1: + version "1.3.4" + resolved "https://registry.yarnpkg.com/sync-rpc/-/sync-rpc-1.3.4.tgz#24bcbdb2ffcb98f23690c15b304660085cdd206c" + dependencies: + get-port "^3.1.0" + +table@^5.2.3: version "5.2.3" resolved "https://registry.yarnpkg.com/table/-/table-5.2.3.tgz#cde0cc6eb06751c009efab27e8c820ca5b67b7f2" - integrity sha512-N2RsDAMvDLvYwFcwbPyF3VmVSSkuF+G1e+8inhBLtHpvwXGw4QRPEZhihQNeEN0i1up6/f6ObCJXNdlRG3YVyQ== dependencies: ajv "^6.9.1" lodash "^4.17.11" slice-ansi "^2.1.0" string-width "^3.0.0" -tape@^4.4.0, tape@^4.6.3: - version "4.9.2" - resolved "https://registry.yarnpkg.com/tape/-/tape-4.9.2.tgz#f233e40f09dc7e00fcf9b26755453c3822ad28c0" +tape@^4.4.0, tape@^4.6.3, tape@^4.8.0: + version "4.10.1" + resolved "https://registry.yarnpkg.com/tape/-/tape-4.10.1.tgz#f73be60888dcb120f08b57f947af65a829506a5f" dependencies: deep-equal "~1.0.1" defined "~1.0.0" for-each "~0.3.3" function-bind "~1.1.1" - glob "~7.1.2" + glob "~7.1.3" has "~1.0.3" inherits "~2.0.3" minimist "~1.2.0" object-inspect "~1.6.0" - resolve "~1.7.1" + resolve "~1.10.0" resumer "~0.0.0" string.prototype.trim "~1.1.2" through "~2.3.8" @@ -5895,20 +5944,27 @@ tar.gz@^1.0.5: commander "^2.8.1" fstream "^1.0.8" mout "^0.11.0" - tar "^4.4.8" + tar "^2.1.1" -tar@^4.4.8: - version "4.4.10" - resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.10.tgz#946b2810b9a5e0b26140cf78bea6b0b0d689eba1" - integrity sha512-g2SVs5QIxvo6OLp0GudTqEf05maawKUxXru104iaayWA09551tFCTI8f1Asb4lPfkBr91k07iL4c11XO3/b0tA== +tar@^2.1.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1" + dependencies: + block-stream "*" + fstream "^1.0.2" + inherits "2" + +tar@^4: + version "4.4.8" + resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.8.tgz#b19eec3fde2a96e64666df9fdb40c5ca1bc3747d" dependencies: chownr "^1.1.1" fs-minipass "^1.2.5" - minipass "^2.3.5" - minizlib "^1.2.1" + minipass "^2.3.4" + minizlib "^1.1.1" mkdirp "^0.5.0" safe-buffer "^5.1.2" - yallist "^3.0.3" + yallist "^3.0.2" taskgroup@^4.0.5, taskgroup@^4.2.0: version "4.3.1" @@ -5921,6 +5977,22 @@ text-table@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" +then-request@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/then-request/-/then-request-6.0.0.tgz#2cab198e48f2d8e79c8c1ed260198368a4a0bcba" + dependencies: + "@types/concat-stream" "^1.6.0" + "@types/form-data" "0.0.33" + "@types/node" "^8.0.0" + "@types/qs" "^6.2.31" + caseless "~0.12.0" + concat-stream "^1.6.0" + form-data "^2.2.0" + http-basic "^7.0.0" + http-response-object "^3.0.1" + promise "^8.0.0" + qs "^6.4.0" + thenify-all@^1.0.0, thenify-all@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" @@ -5933,7 +6005,14 @@ thenify-all@^1.0.0, thenify-all@^1.6.0: dependencies: any-promise "^1.0.0" -through@^2.3.6, through@~2.3.4, through@~2.3.8: +through2@^2.0.3: + version "2.0.5" + resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" + dependencies: + readable-stream "~2.3.6" + xtend "~4.0.1" + +through@^2.3.6, through@^2.3.8, through@~2.3.4, through@~2.3.8: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" @@ -5994,7 +6073,7 @@ to-regex@^3.0.1, to-regex@^3.0.2: regex-not "^1.0.2" safe-regex "^1.1.0" -tough-cookie@>=2.3.3: +tough-cookie@^2.3.3: version "2.5.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" dependencies: @@ -6012,6 +6091,10 @@ tree-kill@^1.2.0: version "1.2.1" resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.1.tgz#5398f374e2f292b9dcc7b2e71e30a5c3bb6c743a" +treeify@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/treeify/-/treeify-1.1.0.tgz#4e31c6a463accd0943879f30667c4fdaff411bb8" + trim-right@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" @@ -6020,38 +6103,22 @@ trim@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/trim/-/trim-0.0.1.tgz#5858547f6b290757ee95cccc666fb50084c460dd" -truffle-hdwallet-provider-privkey@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/truffle-hdwallet-provider-privkey/-/truffle-hdwallet-provider-privkey-0.3.0.tgz#6688f5f3db5dce2cb9f502f7b52dab7b5e2522a3" - dependencies: - ethereumjs-tx "^1.3.4" - ethereumjs-wallet "^0.6.0" - web3 "^0.20.6" - web3-provider-engine "^13.8.0" - -truffle-hdwallet-provider@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/truffle-hdwallet-provider/-/truffle-hdwallet-provider-1.0.3.tgz#4ae845f9b2de23f47c4f0fbc6ef2d4b13b494604" +truffle-hdwallet-provider@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/truffle-hdwallet-provider/-/truffle-hdwallet-provider-1.0.4.tgz#f00ea8dbf8c243dad551f3f68813e09d159c8174" dependencies: any-promise "^1.3.0" bindings "^1.3.1" websocket "^1.0.28" -truffle-wallet-provider@0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/truffle-wallet-provider/-/truffle-wallet-provider-0.0.5.tgz#db59ce6fa1c558766011137509a94dfca8d1408e" - dependencies: - ethereumjs-wallet "^0.6.0" - web3 "^0.18.2" - web3-provider-engine "^8.4.0" - -truffle@4.1.14: - version "4.1.14" - resolved "https://registry.yarnpkg.com/truffle/-/truffle-4.1.14.tgz#8d2c298e29abf9b1e486e44ff9faca6d34bb9030" +truffle@^5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/truffle/-/truffle-5.0.4.tgz#fc68cb6a6a35b46a7ca69eca7b64d161b491db3d" dependencies: + app-module-path "^2.2.0" mocha "^4.1.0" original-require "1.0.1" - solc "0.4.24" + solc "0.5.0" tslib@^1.9.0: version "1.9.3" @@ -6104,12 +6171,15 @@ typedarray-to-buffer@^3.1.2, typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" +typedarray@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" + uglify-js@^3.1.4: - version "3.6.0" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.6.0.tgz#704681345c53a8b2079fb6cec294b05ead242ff5" - integrity sha512-W+jrUHJr3DXKhrsS7NUVxn3zqMOFn0hL/Ei6v0anCIMoKC93TjcflTagwIHLW7SfMFfiQuktQyFVCFHGUE0+yg== + version "3.4.9" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.9.tgz#af02f180c1207d76432e473ed24a28f4a782bae3" dependencies: - commander "~2.20.0" + commander "~2.17.1" source-map "~0.6.1" ultron@~1.1.0: @@ -6117,11 +6187,11 @@ ultron@~1.1.0: resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c" unbzip2-stream@^1.0.9: - version "1.3.1" - resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.3.1.tgz#7854da51622a7e63624221196357803b552966a1" + version "1.3.3" + resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.3.3.tgz#d156d205e670d8d8c393e1c02ebd506422873f6a" dependencies: - buffer "^3.0.1" - through "^2.3.6" + buffer "^5.2.1" + through "^2.3.8" underscore@1.8.3: version "1.8.3" @@ -6195,10 +6265,6 @@ utf8@^2.1.1, utf8@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/utf8/-/utf8-2.1.2.tgz#1fa0d9270e9be850d9b05027f63519bf46457d96" -utf8@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/utf8/-/utf8-3.0.0.tgz#f052eed1364d696e769ef058b183df88c87f69d1" - util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" @@ -6262,78 +6328,78 @@ watchr@~2.4.13: taskgroup "^4.2.0" typechecker "^2.0.8" -web3-bzz@1.0.0-beta.34: - version "1.0.0-beta.34" - resolved "https://registry.yarnpkg.com/web3-bzz/-/web3-bzz-1.0.0-beta.34.tgz#068d37777ab65e5c60f8ec8b9a50cfe45277929c" +web3-bzz@1.0.0-beta.35: + version "1.0.0-beta.35" + resolved "https://registry.yarnpkg.com/web3-bzz/-/web3-bzz-1.0.0-beta.35.tgz#9d5e1362b3db2afd77d65619b7cd46dd5845c192" dependencies: got "7.1.0" swarm-js "0.1.37" underscore "1.8.3" -web3-core-helpers@1.0.0-beta.34: - version "1.0.0-beta.34" - resolved "https://registry.yarnpkg.com/web3-core-helpers/-/web3-core-helpers-1.0.0-beta.34.tgz#b168da00d3e19e156bc15ae203203dd4dfee2d03" +web3-core-helpers@1.0.0-beta.35: + version "1.0.0-beta.35" + resolved "https://registry.yarnpkg.com/web3-core-helpers/-/web3-core-helpers-1.0.0-beta.35.tgz#d681d218a0c6e3283ee1f99a078ab9d3eef037f1" dependencies: underscore "1.8.3" - web3-eth-iban "1.0.0-beta.34" - web3-utils "1.0.0-beta.34" + web3-eth-iban "1.0.0-beta.35" + web3-utils "1.0.0-beta.35" -web3-core-method@1.0.0-beta.34: - version "1.0.0-beta.34" - resolved "https://registry.yarnpkg.com/web3-core-method/-/web3-core-method-1.0.0-beta.34.tgz#ec163c8a2c490fa02a7ec15559fa7307fc7cc6dd" +web3-core-method@1.0.0-beta.35: + version "1.0.0-beta.35" + resolved "https://registry.yarnpkg.com/web3-core-method/-/web3-core-method-1.0.0-beta.35.tgz#fc10e2d546cf4886038e6130bd5726b0952a4e5f" dependencies: underscore "1.8.3" - web3-core-helpers "1.0.0-beta.34" - web3-core-promievent "1.0.0-beta.34" - web3-core-subscriptions "1.0.0-beta.34" - web3-utils "1.0.0-beta.34" + web3-core-helpers "1.0.0-beta.35" + web3-core-promievent "1.0.0-beta.35" + web3-core-subscriptions "1.0.0-beta.35" + web3-utils "1.0.0-beta.35" -web3-core-promievent@1.0.0-beta.34: - version "1.0.0-beta.34" - resolved "https://registry.yarnpkg.com/web3-core-promievent/-/web3-core-promievent-1.0.0-beta.34.tgz#a4f4fa6784bb293e82c60960ae5b56a94cd03edc" +web3-core-promievent@1.0.0-beta.35: + version "1.0.0-beta.35" + resolved "https://registry.yarnpkg.com/web3-core-promievent/-/web3-core-promievent-1.0.0-beta.35.tgz#4f1b24737520fa423fee3afee110fbe82bcb8691" dependencies: any-promise "1.3.0" eventemitter3 "1.1.1" -web3-core-requestmanager@1.0.0-beta.34: - version "1.0.0-beta.34" - resolved "https://registry.yarnpkg.com/web3-core-requestmanager/-/web3-core-requestmanager-1.0.0-beta.34.tgz#01f8f6cf2ae6b6f0b70c38bae1ef741b5bab215c" +web3-core-requestmanager@1.0.0-beta.35: + version "1.0.0-beta.35" + resolved "https://registry.yarnpkg.com/web3-core-requestmanager/-/web3-core-requestmanager-1.0.0-beta.35.tgz#2b77cbf6303720ad68899b39fa7f584dc03dbc8f" dependencies: underscore "1.8.3" - web3-core-helpers "1.0.0-beta.34" - web3-providers-http "1.0.0-beta.34" - web3-providers-ipc "1.0.0-beta.34" - web3-providers-ws "1.0.0-beta.34" + web3-core-helpers "1.0.0-beta.35" + web3-providers-http "1.0.0-beta.35" + web3-providers-ipc "1.0.0-beta.35" + web3-providers-ws "1.0.0-beta.35" -web3-core-subscriptions@1.0.0-beta.34: - version "1.0.0-beta.34" - resolved "https://registry.yarnpkg.com/web3-core-subscriptions/-/web3-core-subscriptions-1.0.0-beta.34.tgz#9fed144033f221c3cf21060302ffdaf5ef2de2de" +web3-core-subscriptions@1.0.0-beta.35: + version "1.0.0-beta.35" + resolved "https://registry.yarnpkg.com/web3-core-subscriptions/-/web3-core-subscriptions-1.0.0-beta.35.tgz#c1b76a2ad3c6e80f5d40b8ba560f01e0f4628758" dependencies: eventemitter3 "1.1.1" underscore "1.8.3" - web3-core-helpers "1.0.0-beta.34" + web3-core-helpers "1.0.0-beta.35" -web3-core@1.0.0-beta.34: - version "1.0.0-beta.34" - resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-1.0.0-beta.34.tgz#121be8555e9fb00d2c5d05ddd3381d0c9e46987e" +web3-core@1.0.0-beta.35: + version "1.0.0-beta.35" + resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-1.0.0-beta.35.tgz#0c44d3c50d23219b0b1531d145607a9bc7cd4b4f" dependencies: - web3-core-helpers "1.0.0-beta.34" - web3-core-method "1.0.0-beta.34" - web3-core-requestmanager "1.0.0-beta.34" - web3-utils "1.0.0-beta.34" + web3-core-helpers "1.0.0-beta.35" + web3-core-method "1.0.0-beta.35" + web3-core-requestmanager "1.0.0-beta.35" + web3-utils "1.0.0-beta.35" -web3-eth-abi@1.0.0-beta.34: - version "1.0.0-beta.34" - resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-1.0.0-beta.34.tgz#034533e3aa2f7e59ff31793eaea685c0ed5af67a" +web3-eth-abi@1.0.0-beta.35: + version "1.0.0-beta.35" + resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-1.0.0-beta.35.tgz#2eb9c1c7c7233db04010defcb192293e0db250e6" dependencies: bn.js "4.11.6" underscore "1.8.3" - web3-core-helpers "1.0.0-beta.34" - web3-utils "1.0.0-beta.34" + web3-core-helpers "1.0.0-beta.35" + web3-utils "1.0.0-beta.35" -web3-eth-accounts@1.0.0-beta.34: - version "1.0.0-beta.34" - resolved "https://registry.yarnpkg.com/web3-eth-accounts/-/web3-eth-accounts-1.0.0-beta.34.tgz#e09142eeecc797ac3459b75e9b23946d3695f333" +web3-eth-accounts@1.0.0-beta.35: + version "1.0.0-beta.35" + resolved "https://registry.yarnpkg.com/web3-eth-accounts/-/web3-eth-accounts-1.0.0-beta.35.tgz#7d0e5a69f510dc93874471599eb7abfa9ddf3e63" dependencies: any-promise "1.3.0" crypto-browserify "3.12.0" @@ -6341,67 +6407,67 @@ web3-eth-accounts@1.0.0-beta.34: scrypt.js "0.2.0" underscore "1.8.3" uuid "2.0.1" - web3-core "1.0.0-beta.34" - web3-core-helpers "1.0.0-beta.34" - web3-core-method "1.0.0-beta.34" - web3-utils "1.0.0-beta.34" + web3-core "1.0.0-beta.35" + web3-core-helpers "1.0.0-beta.35" + web3-core-method "1.0.0-beta.35" + web3-utils "1.0.0-beta.35" -web3-eth-contract@1.0.0-beta.34: - version "1.0.0-beta.34" - resolved "https://registry.yarnpkg.com/web3-eth-contract/-/web3-eth-contract-1.0.0-beta.34.tgz#9dbb38fae7643a808427a20180470ec7415c91e6" +web3-eth-contract@1.0.0-beta.35: + version "1.0.0-beta.35" + resolved "https://registry.yarnpkg.com/web3-eth-contract/-/web3-eth-contract-1.0.0-beta.35.tgz#5276242d8a3358d9f1ce92b71575c74f9015935c" dependencies: underscore "1.8.3" - web3-core "1.0.0-beta.34" - web3-core-helpers "1.0.0-beta.34" - web3-core-method "1.0.0-beta.34" - web3-core-promievent "1.0.0-beta.34" - web3-core-subscriptions "1.0.0-beta.34" - web3-eth-abi "1.0.0-beta.34" - web3-utils "1.0.0-beta.34" - -web3-eth-iban@1.0.0-beta.34: - version "1.0.0-beta.34" - resolved "https://registry.yarnpkg.com/web3-eth-iban/-/web3-eth-iban-1.0.0-beta.34.tgz#9af458605867ccf74ea979aaf326b38ba6a5ba0c" + web3-core "1.0.0-beta.35" + web3-core-helpers "1.0.0-beta.35" + web3-core-method "1.0.0-beta.35" + web3-core-promievent "1.0.0-beta.35" + web3-core-subscriptions "1.0.0-beta.35" + web3-eth-abi "1.0.0-beta.35" + web3-utils "1.0.0-beta.35" + +web3-eth-iban@1.0.0-beta.35: + version "1.0.0-beta.35" + resolved "https://registry.yarnpkg.com/web3-eth-iban/-/web3-eth-iban-1.0.0-beta.35.tgz#5aa10327a9abb26bcfc4ba79d7bad18a002b332c" dependencies: bn.js "4.11.6" - web3-utils "1.0.0-beta.34" + web3-utils "1.0.0-beta.35" -web3-eth-personal@1.0.0-beta.34: - version "1.0.0-beta.34" - resolved "https://registry.yarnpkg.com/web3-eth-personal/-/web3-eth-personal-1.0.0-beta.34.tgz#9afba167342ebde5420bcd5895c3f6c34388f205" +web3-eth-personal@1.0.0-beta.35: + version "1.0.0-beta.35" + resolved "https://registry.yarnpkg.com/web3-eth-personal/-/web3-eth-personal-1.0.0-beta.35.tgz#ecac95b7a53d04a567447062d5cae5f49879e89f" dependencies: - web3-core "1.0.0-beta.34" - web3-core-helpers "1.0.0-beta.34" - web3-core-method "1.0.0-beta.34" - web3-net "1.0.0-beta.34" - web3-utils "1.0.0-beta.34" + web3-core "1.0.0-beta.35" + web3-core-helpers "1.0.0-beta.35" + web3-core-method "1.0.0-beta.35" + web3-net "1.0.0-beta.35" + web3-utils "1.0.0-beta.35" -web3-eth@1.0.0-beta.34: - version "1.0.0-beta.34" - resolved "https://registry.yarnpkg.com/web3-eth/-/web3-eth-1.0.0-beta.34.tgz#74086000850c6fe6f535ef49837d6d4bb6113268" +web3-eth@1.0.0-beta.35: + version "1.0.0-beta.35" + resolved "https://registry.yarnpkg.com/web3-eth/-/web3-eth-1.0.0-beta.35.tgz#c52c804afb95e6624b6f5e72a9af90fbf5005b68" dependencies: underscore "1.8.3" - web3-core "1.0.0-beta.34" - web3-core-helpers "1.0.0-beta.34" - web3-core-method "1.0.0-beta.34" - web3-core-subscriptions "1.0.0-beta.34" - web3-eth-abi "1.0.0-beta.34" - web3-eth-accounts "1.0.0-beta.34" - web3-eth-contract "1.0.0-beta.34" - web3-eth-iban "1.0.0-beta.34" - web3-eth-personal "1.0.0-beta.34" - web3-net "1.0.0-beta.34" - web3-utils "1.0.0-beta.34" - -web3-net@1.0.0-beta.34: - version "1.0.0-beta.34" - resolved "https://registry.yarnpkg.com/web3-net/-/web3-net-1.0.0-beta.34.tgz#427cea2f431881449c8e38d523290f173f9ff63d" - dependencies: - web3-core "1.0.0-beta.34" - web3-core-method "1.0.0-beta.34" - web3-utils "1.0.0-beta.34" - -web3-provider-engine@^13.8.0: + web3-core "1.0.0-beta.35" + web3-core-helpers "1.0.0-beta.35" + web3-core-method "1.0.0-beta.35" + web3-core-subscriptions "1.0.0-beta.35" + web3-eth-abi "1.0.0-beta.35" + web3-eth-accounts "1.0.0-beta.35" + web3-eth-contract "1.0.0-beta.35" + web3-eth-iban "1.0.0-beta.35" + web3-eth-personal "1.0.0-beta.35" + web3-net "1.0.0-beta.35" + web3-utils "1.0.0-beta.35" + +web3-net@1.0.0-beta.35: + version "1.0.0-beta.35" + resolved "https://registry.yarnpkg.com/web3-net/-/web3-net-1.0.0-beta.35.tgz#5c6688e0dea71fcd910ee9dc5437b94b7f6b3354" + dependencies: + web3-core "1.0.0-beta.35" + web3-core-method "1.0.0-beta.35" + web3-utils "1.0.0-beta.35" + +web3-provider-engine@^13.3.2: version "13.8.0" resolved "https://registry.yarnpkg.com/web3-provider-engine/-/web3-provider-engine-13.8.0.tgz#4c7c1ad2af5f1fe10343b8a65495879a2f9c00df" dependencies: @@ -6425,60 +6491,66 @@ web3-provider-engine@^13.8.0: xhr "^2.2.0" xtend "^4.0.1" -web3-provider-engine@^8.4.0: - version "8.6.1" - resolved "https://registry.yarnpkg.com/web3-provider-engine/-/web3-provider-engine-8.6.1.tgz#4d86e19e30caaf97df351511ec0f60136e5b30eb" +web3-provider-engine@^14.1.0: + version "14.1.0" + resolved "https://registry.yarnpkg.com/web3-provider-engine/-/web3-provider-engine-14.1.0.tgz#91590020f8b8c1b65846321310cbfdb039090fc6" dependencies: - async "^2.1.2" + async "^2.5.0" + backoff "^2.5.0" clone "^2.0.0" + cross-fetch "^2.1.0" + eth-block-tracker "^3.0.0" + eth-json-rpc-infura "^3.1.0" + eth-sig-util "^1.4.2" ethereumjs-block "^1.2.2" ethereumjs-tx "^1.2.0" - ethereumjs-util "^5.0.1" - ethereumjs-vm "^2.0.2" - isomorphic-fetch "^2.2.0" - request "^2.67.0" + ethereumjs-util "^5.1.5" + ethereumjs-vm "^2.3.4" + json-rpc-error "^2.0.0" + json-stable-stringify "^1.0.1" + promise-to-callback "^1.0.0" + readable-stream "^2.2.9" + request "^2.85.0" semaphore "^1.0.3" - solc "^0.4.2" - tape "^4.4.0" - web3 "^0.16.0" + ws "^5.1.1" xhr "^2.2.0" xtend "^4.0.1" -web3-providers-http@1.0.0-beta.34: - version "1.0.0-beta.34" - resolved "https://registry.yarnpkg.com/web3-providers-http/-/web3-providers-http-1.0.0-beta.34.tgz#e561b52bbb43766282007d40285bfe3550c27e7a" +web3-providers-http@1.0.0-beta.35: + version "1.0.0-beta.35" + resolved "https://registry.yarnpkg.com/web3-providers-http/-/web3-providers-http-1.0.0-beta.35.tgz#92059d9d6de6e9f82f4fae30b743efd841afc1e1" dependencies: - web3-core-helpers "1.0.0-beta.34" - xhr2 "0.1.4" + web3-core-helpers "1.0.0-beta.35" + xhr2-cookies "1.1.0" -web3-providers-ipc@1.0.0-beta.34: - version "1.0.0-beta.34" - resolved "https://registry.yarnpkg.com/web3-providers-ipc/-/web3-providers-ipc-1.0.0-beta.34.tgz#a1b77f1a306d73649a9c039052e40cb71328d00a" +web3-providers-ipc@1.0.0-beta.35: + version "1.0.0-beta.35" + resolved "https://registry.yarnpkg.com/web3-providers-ipc/-/web3-providers-ipc-1.0.0-beta.35.tgz#031afeb10fade2ebb0ef2fb82f5e58c04be842d9" dependencies: oboe "2.1.3" underscore "1.8.3" - web3-core-helpers "1.0.0-beta.34" + web3-core-helpers "1.0.0-beta.35" -web3-providers-ws@1.0.0-beta.34: - version "1.0.0-beta.34" - resolved "https://registry.yarnpkg.com/web3-providers-ws/-/web3-providers-ws-1.0.0-beta.34.tgz#7de70f1b83f2de36476772156becfef6e3516eb3" +web3-providers-ws@1.0.0-beta.35: + version "1.0.0-beta.35" + resolved "https://registry.yarnpkg.com/web3-providers-ws/-/web3-providers-ws-1.0.0-beta.35.tgz#5d38603fd450243a26aae0ff7f680644e77fa240" dependencies: underscore "1.8.3" - web3-core-helpers "1.0.0-beta.34" + web3-core-helpers "1.0.0-beta.35" websocket "git://github.com/frozeman/WebSocket-Node.git#browserifyCompatible" -web3-shh@1.0.0-beta.34: - version "1.0.0-beta.34" - resolved "https://registry.yarnpkg.com/web3-shh/-/web3-shh-1.0.0-beta.34.tgz#975061d71eaec42ccee576f7bd8f70f03844afe0" +web3-shh@1.0.0-beta.35: + version "1.0.0-beta.35" + resolved "https://registry.yarnpkg.com/web3-shh/-/web3-shh-1.0.0-beta.35.tgz#7e4a585f8beee0c1927390937c6537748a5d1a58" dependencies: - web3-core "1.0.0-beta.34" - web3-core-method "1.0.0-beta.34" - web3-core-subscriptions "1.0.0-beta.34" - web3-net "1.0.0-beta.34" + web3-core "1.0.0-beta.35" + web3-core-method "1.0.0-beta.35" + web3-core-subscriptions "1.0.0-beta.35" + web3-net "1.0.0-beta.35" -web3-utils@1.0.0-beta.34: - version "1.0.0-beta.34" - resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-1.0.0-beta.34.tgz#9411fc39aaef39ca4e06169f762297d9ff020970" +web3-utils@1.0.0-beta.35: + version "1.0.0-beta.35" + resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-1.0.0-beta.35.tgz#ced9e1df47c65581c441c5f2af76b05a37a273d7" dependencies: bn.js "4.11.6" eth-lib "0.1.27" @@ -6508,28 +6580,19 @@ web3@0.20.2: xhr2 "*" xmlhttprequest "*" -web3@1.0.0-beta.34: - version "1.0.0-beta.34" - resolved "https://registry.yarnpkg.com/web3/-/web3-1.0.0-beta.34.tgz#347e561b784098cb5563315f490479a1d91f2ab1" +web3@1.0.0-beta.35: + version "1.0.0-beta.35" + resolved "https://registry.yarnpkg.com/web3/-/web3-1.0.0-beta.35.tgz#6475095bd451a96e50a32b997ddee82279292f11" dependencies: - web3-bzz "1.0.0-beta.34" - web3-core "1.0.0-beta.34" - web3-eth "1.0.0-beta.34" - web3-eth-personal "1.0.0-beta.34" - web3-net "1.0.0-beta.34" - web3-shh "1.0.0-beta.34" - web3-utils "1.0.0-beta.34" + web3-bzz "1.0.0-beta.35" + web3-core "1.0.0-beta.35" + web3-eth "1.0.0-beta.35" + web3-eth-personal "1.0.0-beta.35" + web3-net "1.0.0-beta.35" + web3-shh "1.0.0-beta.35" + web3-utils "1.0.0-beta.35" -web3@^0.16.0: - version "0.16.0" - resolved "https://registry.yarnpkg.com/web3/-/web3-0.16.0.tgz#a4554175cd462943035b1f1d39432f741c6b6019" - dependencies: - bignumber.js "git+https://github.com/debris/bignumber.js#master" - crypto-js "^3.1.4" - utf8 "^2.1.1" - xmlhttprequest "*" - -web3@^0.18.2, web3@^0.18.4: +web3@^0.18.4: version "0.18.4" resolved "https://registry.yarnpkg.com/web3/-/web3-0.18.4.tgz#81ec1784145491f2eaa8955b31c06049e07c5e7d" dependencies: @@ -6539,16 +6602,6 @@ web3@^0.18.2, web3@^0.18.4: xhr2 "*" xmlhttprequest "*" -web3@^0.20.6: - version "0.20.7" - resolved "https://registry.yarnpkg.com/web3/-/web3-0.20.7.tgz#1605e6d81399ed6f85a471a4f3da0c8be57df2f7" - dependencies: - bignumber.js "git+https://github.com/frozeman/bignumber.js-nolookahead.git" - crypto-js "^3.1.4" - utf8 "^2.1.1" - xhr2-cookies "^1.1.0" - xmlhttprequest "*" - websocket@^1.0.28: version "1.0.28" resolved "https://registry.yarnpkg.com/websocket/-/websocket-1.0.28.tgz#9e5f6fdc8a3fe01d4422647ef93abdd8d45a78d3" @@ -6567,9 +6620,9 @@ websocket@^1.0.28: typedarray-to-buffer "^3.1.2" yaeti "^0.0.6" -whatwg-fetch@>=0.10.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb" +whatwg-fetch@2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f" which-module@^1.0.0: version "1.0.0" @@ -6625,7 +6678,6 @@ wordwrap@^1.0.0, wordwrap@~1.0.0: wordwrap@~0.0.2: version "0.0.3" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" - integrity sha1-o9XabNXAvAAI03I0u68b7WMFkQc= wrap-ansi@^2.0.0: version "2.1.0" @@ -6637,12 +6689,10 @@ wrap-ansi@^2.0.0: wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -write@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/write/-/write-0.2.1.tgz#5fc03828e264cea3fe91455476f7a3c566cb0757" - integrity sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c= +write@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3" dependencies: mkdirp "^0.5.1" @@ -6654,6 +6704,12 @@ ws@^3.0.0: safe-buffer "~5.1.0" ultron "~1.1.0" +ws@^5.1.1: + version "5.2.2" + resolved "https://registry.yarnpkg.com/ws/-/ws-5.2.2.tgz#dffef14866b8e8dc9133582514d1befaf96e980f" + dependencies: + async-limiter "~1.0.0" + xhr-request-promise@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/xhr-request-promise/-/xhr-request-promise-0.1.2.tgz#343c44d1ee7726b8648069682d0f840c83b4261d" @@ -6672,13 +6728,13 @@ xhr-request@^1.0.1: url-set-query "^1.0.0" xhr "^2.0.4" -xhr2-cookies@^1.1.0: +xhr2-cookies@1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/xhr2-cookies/-/xhr2-cookies-1.1.0.tgz#7d77449d0999197f155cb73b23df72505ed89d48" dependencies: cookiejar "^2.1.1" -xhr2@*, xhr2@0.1.4: +xhr2@*: version "0.1.4" resolved "https://registry.yarnpkg.com/xhr2/-/xhr2-0.1.4.tgz#7f87658847716db5026323812f818cadab387a5f" @@ -6695,7 +6751,7 @@ xml@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5" -xmlhttprequest@*, xmlhttprequest@1.8.0: +xmlhttprequest@*: version "1.8.0" resolved "https://registry.yarnpkg.com/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz#67fe075c5c24fef39f9d65f5f7b7fe75171968fc" @@ -6703,7 +6759,7 @@ xregexp@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-2.0.0.tgz#52a63e56ca0b84a7f3a5f3d61872f126ad7a5943" -xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.0: +xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.0, xtend@~4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" @@ -6725,7 +6781,7 @@ yallist@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" -yallist@^3.0.0, yallist@^3.0.3: +yallist@^3.0.0, yallist@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9" @@ -6748,9 +6804,9 @@ yargs-parser@^9.0.2: dependencies: camelcase "^4.1.0" -yargs@11.1.0: - version "11.1.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-11.1.0.tgz#90b869934ed6e871115ea2ff58b03f4724ed2d77" +yargs@^10.0.3: + version "10.1.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-10.1.2.tgz#454d074c2b16a51a43e2fb7807e4f9de69ccb5c5" dependencies: cliui "^4.0.0" decamelize "^1.1.1" @@ -6763,11 +6819,11 @@ yargs@11.1.0: string-width "^2.0.0" which-module "^2.0.0" y18n "^3.2.1" - yargs-parser "^9.0.2" + yargs-parser "^8.1.0" -yargs@^10.0.3: - version "10.1.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-10.1.2.tgz#454d074c2b16a51a43e2fb7807e4f9de69ccb5c5" +yargs@^11.0.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-11.1.0.tgz#90b869934ed6e871115ea2ff58b03f4724ed2d77" dependencies: cliui "^4.0.0" decamelize "^1.1.1" @@ -6780,7 +6836,7 @@ yargs@^10.0.3: string-width "^2.0.0" which-module "^2.0.0" y18n "^3.2.1" - yargs-parser "^8.1.0" + yargs-parser "^9.0.2" yargs@^4.6.0, yargs@^4.7.1: version "4.8.1"