├── .gitignore ├── LICENSE ├── README.md ├── build └── contracts │ ├── Address.json │ ├── Blacklistable.json │ ├── BlacklisterRole.json │ ├── Burnable.json │ ├── BurnerRole.json │ ├── Context.json │ ├── ERC1404.json │ ├── ERC20.json │ ├── ERC20Detailed.json │ ├── Escrowable.json │ ├── EscrowerRole.json │ ├── IERC20.json │ ├── Initializable.json │ ├── Math.json │ ├── Migrations.json │ ├── Mintable.json │ ├── MinterRole.json │ ├── OwnerRole.json │ ├── Pausable.json │ ├── PauserRole.json │ ├── Proxiable.json │ ├── Proxy.json │ ├── Revocable.json │ ├── RevocableToAddress.json │ ├── RevokerRole.json │ ├── Roles.json │ ├── SafeERC20.json │ ├── SafeMath.json │ ├── TokenSoftToken.json │ ├── TokenSoftTokenEscrow.json │ ├── TokenSoftTokenEscrowNotProxiable.json │ ├── TokenSoftTokenV2.json │ ├── Whitelistable.json │ └── WhitelisterRole.json ├── contracts ├── @openzeppelin │ ├── README.md │ ├── contracts-ethereum-package │ │ └── contracts │ │ │ ├── GSN │ │ │ └── Context.sol │ │ │ ├── access │ │ │ └── Roles.sol │ │ │ ├── math │ │ │ ├── Math.sol │ │ │ ├── README.adoc │ │ │ └── SafeMath.sol │ │ │ ├── token │ │ │ └── ERC20 │ │ │ │ ├── ERC20.sol │ │ │ │ ├── ERC20Detailed.sol │ │ │ │ ├── IERC20.sol │ │ │ │ ├── README.adoc │ │ │ │ └── SafeERC20.sol │ │ │ └── utils │ │ │ └── Address.sol │ └── upgrades │ │ └── contracts │ │ └── Initializable.sol ├── ERC1404.sol ├── Migrations.sol ├── Proxy.sol ├── TokenSoftToken.sol ├── TokenSoftTokenV2.sol ├── capabilities │ ├── Blacklistable.sol │ ├── Burnable.sol │ ├── Mintable.sol │ ├── Pausable.sol │ ├── Proxiable.sol │ ├── Revocable.sol │ ├── RevocableToAddress.sol │ └── Whitelistable.sol ├── roles │ ├── BlacklisterRole.sol │ ├── BurnerRole.sol │ ├── MinterRole.sol │ ├── OwnerRole.sol │ ├── PauserRole.sol │ ├── RevokerRole.sol │ └── WhitelisterRole.sol └── testing │ ├── Escrowable.sol │ ├── EscrowerRole.sol │ ├── TokenSoftTokenEscrow.sol │ └── TokenSoftTokenEscrowNotProxiable.sol ├── coverage.json ├── example_whitelist.png ├── flattened ├── v1.0 │ ├── Proxy.v1.0-flat.sol │ └── TokensoftToken.v1.0-flat.sol └── v2.0 │ ├── Proxy.v.2.0-flat.sol │ └── TokensoftToken.v.2.0-flat.sol ├── funding.json ├── migrations ├── 1_initial_migration.js └── 2_deploy_contracts.js ├── package.json ├── scripts ├── genAddOwnerPayload.js ├── genAddWhitelistPayload.js └── test.sh ├── test ├── 1404Restrictions.js ├── Constants.js ├── TokenSoftToken.js ├── Transfers.js ├── capabilities │ ├── Blacklistable.js │ ├── Burnable.js │ ├── Mintable.js │ ├── Pausable.js │ ├── Proxiable.js │ ├── Revocable.js │ ├── RevocableToAddress.js │ └── Whitelistable.js └── roles │ ├── BlacklisterRole.js │ ├── BurnerRole.js │ ├── MinterRole.js │ ├── OwnerRole.js │ ├── PauserRole.js │ ├── RevokerRole.js │ └── WhitelisterRole.js ├── truffle-config.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | .coverage_artifacts 64 | .coverage_contracts -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Tokensoft Inc 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TokenSoft Token 2 | ERC20 Token with 1404 Restrictions 3 | 4 | ## Use Case 5 | The TokenSoft token is an ERC20 compatible token with transfer restrictions added that follow the ERC1404 standard. The 1404 Restrictions will use whitelists to segregate groups of accounts so they are only allowed to transfer to designated destination addresses. 6 | 7 | ## Token 8 | The following features will be determined at deploy time, locking them in place. 9 | 10 | - Name 11 | - Symbol 12 | - Decimals 13 | 14 | The following feature can be increased via minting and burning after deployment. 15 | 16 | - Total Supply 17 | 18 | On deployment, all tokens will be transferred to the owner account passed in during deployment. 19 | 20 | ## Restrictions 21 | 22 | If a token transfer is restricted, the code will follow the ERC1404 spec and revert the transaction. Any wallets interacting with an ERC1404 token contract should first query the contract to determine if the transfer is allowed, and if not, show the appropriate error to the user (including the reason code/text from the contract). 23 | 24 | ## Roles 25 | All accounts need to be granted a role by an admin in order to be able to interact with the contract's administrative functions: 26 | 27 | - Owner: Owners are responsible for managing permissions of all roles. 28 | - BurnerRole: These accounts can burn tokens from accounts. 29 | - MinterRole: These accounts can mint new tokens to other accounts. 30 | - PauserRole: These accounts can halt all transfers on the contract. 31 | - RevokerRole: These accounts can revoke tokens from other accounts into their own. 32 | - WhitelisterRole: These accounts can configure whitelist rules and add/remove accounts from whitelists. 33 | - BlacklisterRole: These accounts can add or remove addresses to or from the blacklist 34 | 35 | ## Owners 36 | 37 | Owner accounts can add and remove other account addresses to all Roles, including Owners. Owners can remove themselves from being an Owner, so care needs to be taken to ensure at least 1 address maintains ownership (unless the goal is to remove all owners). 38 | 39 | The Owner account specified at the time of deployment will be the only Owner account by default. 40 | 41 | ## Whitelists 42 | Before tokens can be transferred to a new address, it must be validated that the source is allowed to send to that destination address and that the destination address can receive funds. If the sending client does not check this in advance and sends an invalid transfer, the transfer functionality will fail and the transaction will revert. 43 | 44 | Owner accounts will have the ability to transfer tokens to any valid address, regardless of the whitelist configuration state. 45 | 46 | An Owner can enable and disable the whitelist functionality to remove the whitelist restrictions on transfers. The default state is set as enabled/disabled in the initialization of the token contract. 47 | 48 | An address can only be a member of one whitelist at any point in time. If an admin adds any address to a new whitelist, it will no longer be a member of the previous whitelist it was on (if any). Adding an address to a whitelist of ID 0 will remove it from all whitelists, as whitelist ID 0 is invalid. Removing an address from the existing whitelist will set it to belong to whitelist 0. An address with whitelist 0 will be prevented from transferring or receiving tokens. Any tokens on a whitelist 0 account are frozen. All addresses belong to whitelist 0 by default. 49 | 50 | Any whitelist can be configured to have multiple Outbound Whitelists. When a transfer is initiated, the restriction logic will first determine the whitelist that both the source and destination belong to. Then it will determine if the source whitelist is configured to allow transactions to the destination whitelist. If either address is on whitelist 0 the transfer will be restricted. Also, the transfer will be restricted if the source whitelist is not configured to send to the destination whitelist. 51 | 52 | Example 53 | - Whitelist A is only allowed to send to itself. 54 | - Whitelist B is allowed to send to itself and whitelist A. 55 | - Whitelist C is allowed to send to itself and whitelists A and B. 56 | - Whitelist D is not allowed to transfer to any whitelist, including itself. 57 | 58 |

59 | 60 |

61 | 62 | 63 | A total of 255 whitelists can be utilized, each with the ability to restrict transfers to all other whitelists. 64 | 65 | By default, all whitelists will **NOT** be allowed to transfer between source and destination addresses within the same whitelist. This must explicitly be enabled. By default all whitelists block all transfers. 66 | 67 | Administrators will have the ability modify a whitelist beyond the default configuration to add or remove outbound whitelists. 68 | 69 | ## Blacklists 70 | Before tokens can be transferred to or from an address, if the blacklisting feature is enabled, it will check to ensure both the source and destination are not blacklisted. If either account is blacklisted the transfer will fail. 71 | 72 | An Owner can enable and disable the blacklist functionality to add or remove restrictions on transfers. The default state is set as disabled. 73 | 74 | Accounts with the BlacklisterRole can add or remove accounts to or from the blacklist. 75 | 76 | ## Pausing 77 | 78 | The Pauser accounts may pause/un-pause the contract. When the contract is paused all transfers will be blocked. When deployed the contract is initially un-paused. 79 | 80 | ## Minting 81 | Minter accounts can mint tokens to other accounts. Minting tokens increases the total supply of tokens and the balance of the account the tokens are minted to. 82 | 83 | ## Burning 84 | Burner accounts can burn tokens from other accounts. Burning tokens decreases the total supply of tokens and the balance of the account the tokens are burned from. 85 | 86 | ## Revoking 87 | Revoker accounts can revoke tokens from any account. Revoking tokens has no effect on the total supply, it increases the balance of the account revoking the tokens and decreases the balance of the account the tokens are revoked from. 88 | 89 | ## Upgrading via Proxy 90 | 91 | The contract is upgradeable and allows for Owners to update the contract logic while maintaining contract state. Contracts can be upgraded to have more or less restrictive transfer logic or new transfer paradigms including escrow. Upgrading can be a **potentially destructive** operation if the new contract is incompatible with the existing contract due to broken upgrade methods or memory layout issues. 92 | 93 | To update the contract logic: 94 | >**1:** Deploy the new contract to the ethereum mainnet, this contract must implement the Proxiable contract as defined https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1822.md 95 | 96 | >**2:** An Owner must then call the updateCodeAddress method on the existing token contract with the address of the contract deployed in step 1 97 | 98 | The Proxiable contract checks that the new logic contract implements the proxiableUUID method and returns the proper hash but care must still be taken to ensure the new contract implements correct update logic and compatible memory layout. https://blog.trailofbits.com/2018/09/05/contract-upgrade-anti-patterns/ 99 | 100 | # Testing 101 | You should be able to install dependencies and then run tests: 102 | ``` 103 | $ npm install 104 | $ npm run test 105 | ``` 106 | 107 | For unit test code coverage metrics: 108 | ``` 109 | $ npm run coverage 110 | ``` 111 | 112 | # General Warnings 113 | 114 | ### Proxy Deployment 115 | The initial deployment logic will not guarantee you are setting the logic address to a valid contract address. If you set this to an external address, or a contract that is not "Proxiable" then the deployment will result in an invalid state. This should just be a normal check after deployment and initialization that contract state is valid. 116 | 117 | ### Approve/TransferFrom ERC20 "Double Spend" 118 | It is a known issue with ERC20 that incorrectly using approve could allow a spending to transfer more tokens than is desired. To prevent this, ALWAYS set the approval amount to 0 before setting it to a new value. 119 | 120 | ### Centralization 121 | This token contract allows administrative capabilities that may not be expected on completely decentralized system. Accounts with administrative capabilities can burn tokens from any account, revoke tokens from any account, mint new tokens, upgrade contract logic, etc. This should be evaluated before using/interacting with the contract. 122 | 123 | The main use case for this contract is in dealing with Security Tokens which may require these capabilities from a regulatory necessity. -------------------------------------------------------------------------------- /contracts/@openzeppelin/README.md: -------------------------------------------------------------------------------- 1 | # NOTICE 2 | 3 | The contracts in this folder were copied over from 4 | 5 | ``` 6 | "@openzeppelin/contracts-ethereum-package": "2.5.0", 7 | "@openzeppelin/contracts": "2.5.1", 8 | ``` 9 | 10 | The intent is to lock the behavior and memory layout to this version and allow upgrading of the compiler version. 11 | 12 | Simply upgrading the package version may have introduced side-effects that would cause issue with memory layout during upgrading of the logic contracts underlying the proxies. -------------------------------------------------------------------------------- /contracts/@openzeppelin/contracts-ethereum-package/contracts/GSN/Context.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.6.12; 2 | 3 | import "../../../upgrades/contracts/Initializable.sol"; 4 | 5 | /* 6 | * @dev Provides information about the current execution context, including the 7 | * sender of the transaction and its data. While these are generally available 8 | * via msg.sender and msg.data, they should not be accessed in such a direct 9 | * manner, since when dealing with GSN meta-transactions the account sending and 10 | * paying for execution may not be the actual sender (as far as an application 11 | * is concerned). 12 | * 13 | * This contract is only required for intermediate, library-like contracts. 14 | */ 15 | contract Context is Initializable { 16 | // Empty internal constructor, to prevent people from mistakenly deploying 17 | // an instance of this contract, which should be used via inheritance. 18 | constructor () internal { } 19 | // solhint-disable-previous-line no-empty-blocks 20 | 21 | function _msgSender() internal view returns (address payable) { 22 | return msg.sender; 23 | } 24 | 25 | function _msgData() internal view returns (bytes memory) { 26 | this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691 27 | return msg.data; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /contracts/@openzeppelin/contracts-ethereum-package/contracts/access/Roles.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.6.12; 2 | 3 | /** 4 | * @title Roles 5 | * @dev Library for managing addresses assigned to a Role. 6 | */ 7 | library Roles { 8 | struct Role { 9 | mapping (address => bool) bearer; 10 | } 11 | 12 | /** 13 | * @dev Give an account access to this role. 14 | */ 15 | function add(Role storage role, address account) internal { 16 | require(account != address(0x0), "Invalid 0x0 address"); 17 | require(!has(role, account), "Roles: account already has role"); 18 | role.bearer[account] = true; 19 | } 20 | 21 | /** 22 | * @dev Remove an account's access to this role. 23 | */ 24 | function remove(Role storage role, address account) internal { 25 | require(has(role, account), "Roles: account does not have role"); 26 | role.bearer[account] = false; 27 | } 28 | 29 | /** 30 | * @dev Check if an account has this role. 31 | * @return bool 32 | */ 33 | function has(Role storage role, address account) internal view returns (bool) { 34 | require(account != address(0), "Roles: account is the zero address"); 35 | return role.bearer[account]; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /contracts/@openzeppelin/contracts-ethereum-package/contracts/math/Math.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.6.12; 2 | 3 | /** 4 | * @dev Standard math utilities missing in the Solidity language. 5 | */ 6 | library Math { 7 | /** 8 | * @dev Returns the largest of two numbers. 9 | */ 10 | function max(uint256 a, uint256 b) internal pure returns (uint256) { 11 | return a >= b ? a : b; 12 | } 13 | 14 | /** 15 | * @dev Returns the smallest of two numbers. 16 | */ 17 | function min(uint256 a, uint256 b) internal pure returns (uint256) { 18 | return a < b ? a : b; 19 | } 20 | 21 | /** 22 | * @dev Returns the average of two numbers. The result is rounded towards 23 | * zero. 24 | */ 25 | function average(uint256 a, uint256 b) internal pure returns (uint256) { 26 | // (a + b) / 2 can overflow, so we distribute 27 | return (a / 2) + (b / 2) + ((a % 2 + b % 2) / 2); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /contracts/@openzeppelin/contracts-ethereum-package/contracts/math/README.adoc: -------------------------------------------------------------------------------- 1 | = Math 2 | 3 | These are math-related utilities. 4 | 5 | == Libraries 6 | 7 | {{SafeMath}} 8 | 9 | {{Math}} 10 | -------------------------------------------------------------------------------- /contracts/@openzeppelin/contracts-ethereum-package/contracts/math/SafeMath.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.6.12; 2 | 3 | /** 4 | * @dev Wrappers over Solidity's arithmetic operations with added overflow 5 | * checks. 6 | * 7 | * Arithmetic operations in Solidity wrap on overflow. This can easily result 8 | * in bugs, because programmers usually assume that an overflow raises an 9 | * error, which is the standard behavior in high level programming languages. 10 | * `SafeMath` restores this intuition by reverting the transaction when an 11 | * operation overflows. 12 | * 13 | * Using this library instead of the unchecked operations eliminates an entire 14 | * class of bugs, so it's recommended to use it always. 15 | */ 16 | library SafeMath { 17 | /** 18 | * @dev Returns the addition of two unsigned integers, reverting on 19 | * overflow. 20 | * 21 | * Counterpart to Solidity's `+` operator. 22 | * 23 | * Requirements: 24 | * - Addition cannot overflow. 25 | */ 26 | function add(uint256 a, uint256 b) internal pure returns (uint256) { 27 | uint256 c = a + b; 28 | require(c >= a, "SafeMath: addition overflow"); 29 | 30 | return c; 31 | } 32 | 33 | /** 34 | * @dev Returns the subtraction of two unsigned integers, reverting on 35 | * overflow (when the result is negative). 36 | * 37 | * Counterpart to Solidity's `-` operator. 38 | * 39 | * Requirements: 40 | * - Subtraction cannot overflow. 41 | */ 42 | function sub(uint256 a, uint256 b) internal pure returns (uint256) { 43 | return sub(a, b, "SafeMath: subtraction overflow"); 44 | } 45 | 46 | /** 47 | * @dev Returns the subtraction of two unsigned integers, reverting with custom message on 48 | * overflow (when the result is negative). 49 | * 50 | * Counterpart to Solidity's `-` operator. 51 | * 52 | * Requirements: 53 | * - Subtraction cannot overflow. 54 | * 55 | * _Available since v2.4.0._ 56 | */ 57 | function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { 58 | require(b <= a, errorMessage); 59 | uint256 c = a - b; 60 | 61 | return c; 62 | } 63 | 64 | /** 65 | * @dev Returns the multiplication of two unsigned integers, reverting on 66 | * overflow. 67 | * 68 | * Counterpart to Solidity's `*` operator. 69 | * 70 | * Requirements: 71 | * - Multiplication cannot overflow. 72 | */ 73 | function mul(uint256 a, uint256 b) internal pure returns (uint256) { 74 | // Gas optimization: this is cheaper than requiring 'a' not being zero, but the 75 | // benefit is lost if 'b' is also tested. 76 | // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 77 | if (a == 0) { 78 | return 0; 79 | } 80 | 81 | uint256 c = a * b; 82 | require(c / a == b, "SafeMath: multiplication overflow"); 83 | 84 | return c; 85 | } 86 | 87 | /** 88 | * @dev Returns the integer division of two unsigned integers. Reverts on 89 | * division by zero. The result is rounded towards zero. 90 | * 91 | * Counterpart to Solidity's `/` operator. Note: this function uses a 92 | * `revert` opcode (which leaves remaining gas untouched) while Solidity 93 | * uses an invalid opcode to revert (consuming all remaining gas). 94 | * 95 | * Requirements: 96 | * - The divisor cannot be zero. 97 | */ 98 | function div(uint256 a, uint256 b) internal pure returns (uint256) { 99 | return div(a, b, "SafeMath: division by zero"); 100 | } 101 | 102 | /** 103 | * @dev Returns the integer division of two unsigned integers. Reverts with custom message on 104 | * division by zero. The result is rounded towards zero. 105 | * 106 | * Counterpart to Solidity's `/` operator. Note: this function uses a 107 | * `revert` opcode (which leaves remaining gas untouched) while Solidity 108 | * uses an invalid opcode to revert (consuming all remaining gas). 109 | * 110 | * Requirements: 111 | * - The divisor cannot be zero. 112 | * 113 | * _Available since v2.4.0._ 114 | */ 115 | function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { 116 | // Solidity only automatically asserts when dividing by 0 117 | require(b > 0, errorMessage); 118 | uint256 c = a / b; 119 | // assert(a == b * c + a % b); // There is no case in which this doesn't hold 120 | 121 | return c; 122 | } 123 | 124 | /** 125 | * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), 126 | * Reverts when dividing by zero. 127 | * 128 | * Counterpart to Solidity's `%` operator. This function uses a `revert` 129 | * opcode (which leaves remaining gas untouched) while Solidity uses an 130 | * invalid opcode to revert (consuming all remaining gas). 131 | * 132 | * Requirements: 133 | * - The divisor cannot be zero. 134 | */ 135 | function mod(uint256 a, uint256 b) internal pure returns (uint256) { 136 | return mod(a, b, "SafeMath: modulo by zero"); 137 | } 138 | 139 | /** 140 | * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), 141 | * Reverts with custom message when dividing by zero. 142 | * 143 | * Counterpart to Solidity's `%` operator. This function uses a `revert` 144 | * opcode (which leaves remaining gas untouched) while Solidity uses an 145 | * invalid opcode to revert (consuming all remaining gas). 146 | * 147 | * Requirements: 148 | * - The divisor cannot be zero. 149 | * 150 | * _Available since v2.4.0._ 151 | */ 152 | function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { 153 | require(b != 0, errorMessage); 154 | return a % b; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /contracts/@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.6.12; 2 | 3 | import "../../../../upgrades/contracts/Initializable.sol"; 4 | 5 | import "../../GSN/Context.sol"; 6 | import "./IERC20.sol"; 7 | import "../../math/SafeMath.sol"; 8 | 9 | /** 10 | * @dev Implementation of the {IERC20} interface. 11 | * 12 | * This implementation is agnostic to the way tokens are created. This means 13 | * that a supply mechanism has to be added in a derived contract using {_mint}. 14 | * For a generic mechanism see {ERC20Mintable}. 15 | * 16 | * TIP: For a detailed writeup see our guide 17 | * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How 18 | * to implement supply mechanisms]. 19 | * 20 | * We have followed general OpenZeppelin guidelines: functions revert instead 21 | * of returning `false` on failure. This behavior is nonetheless conventional 22 | * and does not conflict with the expectations of ERC20 applications. 23 | * 24 | * Additionally, an {Approval} event is emitted on calls to {transferFrom}. 25 | * This allows applications to reconstruct the allowance for all accounts just 26 | * by listening to said events. Other implementations of the EIP may not emit 27 | * these events, as it isn't required by the specification. 28 | * 29 | * Finally, the non-standard {decreaseAllowance} and {increaseAllowance} 30 | * functions have been added to mitigate the well-known issues around setting 31 | * allowances. See {IERC20-approve}. 32 | */ 33 | contract ERC20 is Initializable, Context, IERC20 { 34 | using SafeMath for uint256; 35 | 36 | mapping (address => uint256) private _balances; 37 | 38 | mapping (address => mapping (address => uint256)) private _allowances; 39 | 40 | uint256 private _totalSupply; 41 | 42 | /** 43 | * @dev See {IERC20-totalSupply}. 44 | */ 45 | function totalSupply() public view override returns (uint256) { 46 | return _totalSupply; 47 | } 48 | 49 | /** 50 | * @dev See {IERC20-balanceOf}. 51 | */ 52 | function balanceOf(address account) public view override returns (uint256) { 53 | return _balances[account]; 54 | } 55 | 56 | /** 57 | * @dev See {IERC20-transfer}. 58 | * 59 | * Requirements: 60 | * 61 | * - `recipient` cannot be the zero address. 62 | * - the caller must have a balance of at least `amount`. 63 | */ 64 | function transfer(address recipient, uint256 amount) public virtual override returns (bool) { 65 | _transfer(_msgSender(), recipient, amount); 66 | return true; 67 | } 68 | 69 | /** 70 | * @dev See {IERC20-allowance}. 71 | */ 72 | function allowance(address owner, address spender) public view override returns (uint256) { 73 | return _allowances[owner][spender]; 74 | } 75 | 76 | /** 77 | * @dev See {IERC20-approve}. 78 | * 79 | * Requirements: 80 | * 81 | * - `spender` cannot be the zero address. 82 | */ 83 | function approve(address spender, uint256 amount) public override returns (bool) { 84 | _approve(_msgSender(), spender, amount); 85 | return true; 86 | } 87 | 88 | /** 89 | * @dev See {IERC20-transferFrom}. 90 | * 91 | * Emits an {Approval} event indicating the updated allowance. This is not 92 | * required by the EIP. See the note at the beginning of {ERC20}; 93 | * 94 | * Requirements: 95 | * - `sender` and `recipient` cannot be the zero address. 96 | * - `sender` must have a balance of at least `amount`. 97 | * - the caller must have allowance for `sender`'s tokens of at least 98 | * `amount`. 99 | */ 100 | function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) { 101 | _transfer(sender, recipient, amount); 102 | _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance")); 103 | return true; 104 | } 105 | 106 | /** 107 | * @dev Atomically increases the allowance granted to `spender` by the caller. 108 | * 109 | * This is an alternative to {approve} that can be used as a mitigation for 110 | * problems described in {IERC20-approve}. 111 | * 112 | * Emits an {Approval} event indicating the updated allowance. 113 | * 114 | * Requirements: 115 | * 116 | * - `spender` cannot be the zero address. 117 | */ 118 | function increaseAllowance(address spender, uint256 addedValue) public returns (bool) { 119 | _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue)); 120 | return true; 121 | } 122 | 123 | /** 124 | * @dev Atomically decreases the allowance granted to `spender` by the caller. 125 | * 126 | * This is an alternative to {approve} that can be used as a mitigation for 127 | * problems described in {IERC20-approve}. 128 | * 129 | * Emits an {Approval} event indicating the updated allowance. 130 | * 131 | * Requirements: 132 | * 133 | * - `spender` cannot be the zero address. 134 | * - `spender` must have allowance for the caller of at least 135 | * `subtractedValue`. 136 | */ 137 | function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) { 138 | _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero")); 139 | return true; 140 | } 141 | 142 | /** 143 | * @dev Moves tokens `amount` from `sender` to `recipient`. 144 | * 145 | * This is internal function is equivalent to {transfer}, and can be used to 146 | * e.g. implement automatic token fees, slashing mechanisms, etc. 147 | * 148 | * Emits a {Transfer} event. 149 | * 150 | * Requirements: 151 | * 152 | * - `sender` cannot be the zero address. 153 | * - `recipient` cannot be the zero address. 154 | * - `sender` must have a balance of at least `amount`. 155 | */ 156 | function _transfer(address sender, address recipient, uint256 amount) internal { 157 | require(sender != address(0), "ERC20: transfer from the zero address"); 158 | require(recipient != address(0), "ERC20: transfer to the zero address"); 159 | 160 | _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance"); 161 | _balances[recipient] = _balances[recipient].add(amount); 162 | emit Transfer(sender, recipient, amount); 163 | } 164 | 165 | /** @dev Creates `amount` tokens and assigns them to `account`, increasing 166 | * the total supply. 167 | * 168 | * Emits a {Transfer} event with `from` set to the zero address. 169 | * 170 | * Requirements 171 | * 172 | * - `to` cannot be the zero address. 173 | */ 174 | function _mint(address account, uint256 amount) internal { 175 | require(account != address(0), "ERC20: mint to the zero address"); 176 | 177 | _totalSupply = _totalSupply.add(amount); 178 | _balances[account] = _balances[account].add(amount); 179 | emit Transfer(address(0), account, amount); 180 | } 181 | 182 | /** 183 | * @dev Destroys `amount` tokens from `account`, reducing the 184 | * total supply. 185 | * 186 | * Emits a {Transfer} event with `to` set to the zero address. 187 | * 188 | * Requirements 189 | * 190 | * - `account` cannot be the zero address. 191 | * - `account` must have at least `amount` tokens. 192 | */ 193 | function _burn(address account, uint256 amount) internal { 194 | require(account != address(0), "ERC20: burn from the zero address"); 195 | 196 | _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance"); 197 | _totalSupply = _totalSupply.sub(amount); 198 | emit Transfer(account, address(0), amount); 199 | } 200 | 201 | /** 202 | * @dev Sets `amount` as the allowance of `spender` over the `owner`s tokens. 203 | * 204 | * This is internal function is equivalent to `approve`, and can be used to 205 | * e.g. set automatic allowances for certain subsystems, etc. 206 | * 207 | * Emits an {Approval} event. 208 | * 209 | * Requirements: 210 | * 211 | * - `owner` cannot be the zero address. 212 | * - `spender` cannot be the zero address. 213 | */ 214 | function _approve(address owner, address spender, uint256 amount) internal { 215 | require(owner != address(0), "ERC20: approve from the zero address"); 216 | require(spender != address(0), "ERC20: approve to the zero address"); 217 | 218 | _allowances[owner][spender] = amount; 219 | emit Approval(owner, spender, amount); 220 | } 221 | 222 | /** 223 | * @dev Destroys `amount` tokens from `account`.`amount` is then deducted 224 | * from the caller's allowance. 225 | * 226 | * See {_burn} and {_approve}. 227 | */ 228 | function _burnFrom(address account, uint256 amount) internal { 229 | _burn(account, amount); 230 | _approve(account, _msgSender(), _allowances[account][_msgSender()].sub(amount, "ERC20: burn amount exceeds allowance")); 231 | } 232 | 233 | uint256[50] private ______gap; 234 | } 235 | -------------------------------------------------------------------------------- /contracts/@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20Detailed.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.6.12; 2 | 3 | import "../../../../upgrades/contracts/Initializable.sol"; 4 | import "./IERC20.sol"; 5 | 6 | /** 7 | * @dev Optional functions from the ERC20 standard. 8 | */ 9 | abstract contract ERC20Detailed is Initializable, IERC20 { 10 | string private _name; 11 | string private _symbol; 12 | uint8 private _decimals; 13 | 14 | /** 15 | * @dev Sets the values for `name`, `symbol`, and `decimals`. All three of 16 | * these values are immutable: they can only be set once during 17 | * construction. 18 | */ 19 | function initialize(string memory name, string memory symbol, uint8 decimals) public initializer { 20 | _name = name; 21 | _symbol = symbol; 22 | _decimals = decimals; 23 | } 24 | 25 | /** 26 | * @dev Returns the name of the token. 27 | */ 28 | function name() public view returns (string memory) { 29 | return _name; 30 | } 31 | 32 | /** 33 | * @dev Returns the symbol of the token, usually a shorter version of the 34 | * name. 35 | */ 36 | function symbol() public view returns (string memory) { 37 | return _symbol; 38 | } 39 | 40 | /** 41 | * @dev Returns the number of decimals used to get its user representation. 42 | * For example, if `decimals` equals `2`, a balance of `505` tokens should 43 | * be displayed to a user as `5,05` (`505 / 10 ** 2`). 44 | * 45 | * Tokens usually opt for a value of 18, imitating the relationship between 46 | * Ether and Wei. 47 | * 48 | * NOTE: This information is only used for _display_ purposes: it in 49 | * no way affects any of the arithmetic of the contract, including 50 | * {IERC20-balanceOf} and {IERC20-transfer}. 51 | */ 52 | function decimals() public view returns (uint8) { 53 | return _decimals; 54 | } 55 | 56 | uint256[50] private ______gap; 57 | } 58 | -------------------------------------------------------------------------------- /contracts/@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/IERC20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.6.12; 2 | 3 | /** 4 | * @dev Interface of the ERC20 standard as defined in the EIP. Does not include 5 | * the optional functions; to access them see {ERC20Detailed}. 6 | */ 7 | interface IERC20 { 8 | /** 9 | * @dev Returns the amount of tokens in existence. 10 | */ 11 | function totalSupply() external view returns (uint256); 12 | 13 | /** 14 | * @dev Returns the amount of tokens owned by `account`. 15 | */ 16 | function balanceOf(address account) external view returns (uint256); 17 | 18 | /** 19 | * @dev Moves `amount` tokens from the caller's account to `recipient`. 20 | * 21 | * Returns a boolean value indicating whether the operation succeeded. 22 | * 23 | * Emits a {Transfer} event. 24 | */ 25 | function transfer(address recipient, uint256 amount) external returns (bool); 26 | 27 | /** 28 | * @dev Returns the remaining number of tokens that `spender` will be 29 | * allowed to spend on behalf of `owner` through {transferFrom}. This is 30 | * zero by default. 31 | * 32 | * This value changes when {approve} or {transferFrom} are called. 33 | */ 34 | function allowance(address owner, address spender) external view returns (uint256); 35 | 36 | /** 37 | * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. 38 | * 39 | * Returns a boolean value indicating whether the operation succeeded. 40 | * 41 | * IMPORTANT: Beware that changing an allowance with this method brings the risk 42 | * that someone may use both the old and the new allowance by unfortunate 43 | * transaction ordering. One possible solution to mitigate this race 44 | * condition is to first reduce the spender's allowance to 0 and set the 45 | * desired value afterwards: 46 | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 47 | * 48 | * Emits an {Approval} event. 49 | */ 50 | function approve(address spender, uint256 amount) external returns (bool); 51 | 52 | /** 53 | * @dev Moves `amount` tokens from `sender` to `recipient` using the 54 | * allowance mechanism. `amount` is then deducted from the caller's 55 | * allowance. 56 | * 57 | * Returns a boolean value indicating whether the operation succeeded. 58 | * 59 | * Emits a {Transfer} event. 60 | */ 61 | function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); 62 | 63 | /** 64 | * @dev Emitted when `value` tokens are moved from one account (`from`) to 65 | * another (`to`). 66 | * 67 | * Note that `value` may be zero. 68 | */ 69 | event Transfer(address indexed from, address indexed to, uint256 value); 70 | 71 | /** 72 | * @dev Emitted when the allowance of a `spender` for an `owner` is set by 73 | * a call to {approve}. `value` is the new allowance. 74 | */ 75 | event Approval(address indexed owner, address indexed spender, uint256 value); 76 | } 77 | -------------------------------------------------------------------------------- /contracts/@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/README.adoc: -------------------------------------------------------------------------------- 1 | = ERC 20 2 | 3 | This set of interfaces, contracts, and utilities are all related to the https://eips.ethereum.org/EIPS/eip-20[ERC20 Token Standard]. 4 | 5 | TIP: For an overview of ERC20 tokens and a walkthrough on how to create a token contract read our xref:ROOT:tokens.adoc#ERC20[ERC20 guide]. 6 | 7 | There a few core contracts that implement the behavior specified in the EIP: 8 | 9 | * {IERC20}: the interface all ERC20 implementations should conform to 10 | * {ERC20}: the base implementation of the ERC20 interface 11 | * {ERC20Detailed}: includes the <>, 12 | <> and <> 13 | optional standard extension to the base interface 14 | 15 | Additionally there are multiple custom extensions, including: 16 | 17 | * designation of addresses that can create token supply ({ERC20Mintable}), with an optional maximum cap ({ERC20Capped}) 18 | * destruction of own tokens ({ERC20Burnable}) 19 | * designation of addresses that can pause token operations for all users ({ERC20Pausable}). 20 | 21 | Finally, there are some utilities to interact with ERC20 contracts in various ways. 22 | 23 | * {SafeERC20} is a wrapper around the interface that eliminates the need to handle boolean return values. 24 | * {TokenTimelock} can hold tokens for a beneficiary until a specified time. 25 | 26 | NOTE: This page is incomplete. We're working to improve it for the next release. Stay tuned! 27 | 28 | == Core 29 | 30 | {{IERC20}} 31 | 32 | {{ERC20}} 33 | 34 | {{ERC20Detailed}} 35 | 36 | == Extensions 37 | 38 | {{ERC20Mintable}} 39 | 40 | {{ERC20Burnable}} 41 | 42 | {{ERC20Pausable}} 43 | 44 | {{ERC20Capped}} 45 | 46 | == Utilities 47 | 48 | {{SafeERC20}} 49 | 50 | {{TokenTimelock}} 51 | -------------------------------------------------------------------------------- /contracts/@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/SafeERC20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.6.12; 2 | 3 | import "./IERC20.sol"; 4 | import "../../math/SafeMath.sol"; 5 | import "../../utils/Address.sol"; 6 | 7 | /** 8 | * @title SafeERC20 9 | * @dev Wrappers around ERC20 operations that throw on failure (when the token 10 | * contract returns false). Tokens that return no value (and instead revert or 11 | * throw on failure) are also supported, non-reverting calls are assumed to be 12 | * successful. 13 | * To use this library you can add a `using SafeERC20 for ERC20;` statement to your contract, 14 | * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. 15 | */ 16 | library SafeERC20 { 17 | using SafeMath for uint256; 18 | using Address for address; 19 | 20 | function safeTransfer(IERC20 token, address to, uint256 value) internal { 21 | callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); 22 | } 23 | 24 | function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal { 25 | callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); 26 | } 27 | 28 | function safeApprove(IERC20 token, address spender, uint256 value) internal { 29 | // safeApprove should only be called when setting an initial allowance, 30 | // or when resetting it to zero. To increase and decrease it, use 31 | // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' 32 | // solhint-disable-next-line max-line-length 33 | require((value == 0) || (token.allowance(address(this), spender) == 0), 34 | "SafeERC20: approve from non-zero to non-zero allowance" 35 | ); 36 | callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); 37 | } 38 | 39 | function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal { 40 | uint256 newAllowance = token.allowance(address(this), spender).add(value); 41 | callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); 42 | } 43 | 44 | function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal { 45 | uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero"); 46 | callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); 47 | } 48 | 49 | /** 50 | * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement 51 | * on the return value: the return value is optional (but if data is returned, it must not be false). 52 | * @param token The token targeted by the call. 53 | * @param data The call data (encoded using abi.encode or one of its variants). 54 | */ 55 | function callOptionalReturn(IERC20 token, bytes memory data) private { 56 | // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since 57 | // we're implementing it ourselves. 58 | 59 | // A Solidity high level call has three parts: 60 | // 1. The target address is checked to verify it contains contract code 61 | // 2. The call itself is made, and success asserted 62 | // 3. The return value is decoded, which in turn checks the size of the returned data. 63 | // solhint-disable-next-line max-line-length 64 | require(address(token).isContract(), "SafeERC20: call to non-contract"); 65 | 66 | // solhint-disable-next-line avoid-low-level-calls 67 | (bool success, bytes memory returndata) = address(token).call(data); 68 | require(success, "SafeERC20: low-level call failed"); 69 | 70 | if (returndata.length > 0) { // Return data is optional 71 | // solhint-disable-next-line max-line-length 72 | require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /contracts/@openzeppelin/contracts-ethereum-package/contracts/utils/Address.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.6.12; 2 | 3 | /** 4 | * @dev Collection of functions related to the address type 5 | */ 6 | library Address { 7 | /** 8 | * @dev Returns true if `account` is a contract. 9 | * 10 | * [IMPORTANT] 11 | * ==== 12 | * It is unsafe to assume that an address for which this function returns 13 | * false is an externally-owned account (EOA) and not a contract. 14 | * 15 | * Among others, `isContract` will return false for the following 16 | * types of addresses: 17 | * 18 | * - an externally-owned account 19 | * - a contract in construction 20 | * - an address where a contract will be created 21 | * - an address where a contract lived, but was destroyed 22 | * ==== 23 | */ 24 | function isContract(address account) internal view returns (bool) { 25 | // According to EIP-1052, 0x0 is the value returned for not-yet created accounts 26 | // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned 27 | // for accounts without code, i.e. `keccak256('')` 28 | bytes32 codehash; 29 | bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470; 30 | // solhint-disable-next-line no-inline-assembly 31 | assembly { codehash := extcodehash(account) } 32 | return (codehash != accountHash && codehash != 0x0); 33 | } 34 | 35 | /** 36 | * @dev Converts an `address` into `address payable`. Note that this is 37 | * simply a type cast: the actual underlying value is not changed. 38 | * 39 | * _Available since v2.4.0._ 40 | */ 41 | function toPayable(address account) internal pure returns (address payable) { 42 | return address(uint160(account)); 43 | } 44 | 45 | /** 46 | * @dev Replacement for Solidity's `transfer`: sends `amount` wei to 47 | * `recipient`, forwarding all available gas and reverting on errors. 48 | * 49 | * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost 50 | * of certain opcodes, possibly making contracts go over the 2300 gas limit 51 | * imposed by `transfer`, making them unable to receive funds via 52 | * `transfer`. {sendValue} removes this limitation. 53 | * 54 | * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. 55 | * 56 | * IMPORTANT: because control is transferred to `recipient`, care must be 57 | * taken to not create reentrancy vulnerabilities. Consider using 58 | * {ReentrancyGuard} or the 59 | * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. 60 | * 61 | * _Available since v2.4.0._ 62 | */ 63 | function sendValue(address payable recipient, uint256 amount) internal { 64 | require(address(this).balance >= amount, "Address: insufficient balance"); 65 | 66 | // solhint-disable-next-line avoid-call-value 67 | (bool success, ) = recipient.call{value:amount}(""); 68 | require(success, "Address: unable to send value, recipient may have reverted"); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /contracts/@openzeppelin/upgrades/contracts/Initializable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.4.24 <0.7.0; 2 | 3 | 4 | /** 5 | * @title Initializable 6 | * 7 | * @dev Helper contract to support initializer functions. To use it, replace 8 | * the constructor with a function that has the `initializer` modifier. 9 | * WARNING: Unlike constructors, initializer functions must be manually 10 | * invoked. This applies both to deploying an Initializable contract, as well 11 | * as extending an Initializable contract via inheritance. 12 | * WARNING: When used with inheritance, manual care must be taken to not invoke 13 | * a parent initializer twice, or ensure that all initializers are idempotent, 14 | * because this is not dealt with automatically as with constructors. 15 | */ 16 | contract Initializable { 17 | 18 | /** 19 | * @dev Indicates that the contract has been initialized. 20 | */ 21 | bool private initialized; 22 | 23 | /** 24 | * @dev Indicates that the contract is in the process of being initialized. 25 | */ 26 | bool private initializing; 27 | 28 | /** 29 | * @dev Modifier to use in the initializer function of a contract. 30 | */ 31 | modifier initializer() { 32 | require(initializing || isConstructor() || !initialized, "Contract instance has already been initialized"); 33 | 34 | bool isTopLevelCall = !initializing; 35 | if (isTopLevelCall) { 36 | initializing = true; 37 | initialized = true; 38 | } 39 | 40 | _; 41 | 42 | if (isTopLevelCall) { 43 | initializing = false; 44 | } 45 | } 46 | 47 | /// @dev Returns true if and only if the function is running in the constructor 48 | function isConstructor() private view returns (bool) { 49 | // extcodesize checks the size of the code stored in an address, and 50 | // address returns the current address. Since the code is still not 51 | // deployed when running a constructor, any checks on its code size will 52 | // yield zero, making it an effective way to detect if a contract is 53 | // under construction or not. 54 | address self = address(this); 55 | uint256 cs; 56 | assembly { cs := extcodesize(self) } 57 | return cs == 0; 58 | } 59 | 60 | // Reserved storage space to allow for layout changes in the future. 61 | uint256[50] private ______gap; 62 | } 63 | -------------------------------------------------------------------------------- /contracts/ERC1404.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.6.12; 2 | 3 | import "./@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/IERC20.sol"; 4 | 5 | abstract contract ERC1404 is IERC20 { 6 | /// @notice Detects if a transfer will be reverted and if so returns an appropriate reference code 7 | /// @param from Sending address 8 | /// @param to Receiving address 9 | /// @param value Amount of tokens being transferred 10 | /// @return Code by which to reference message for rejection reasoning 11 | /// @dev Overwrite with your custom transfer restriction logic 12 | function detectTransferRestriction (address from, address to, uint256 value) public virtual view returns (uint8); 13 | 14 | /// @notice Returns a human-readable message for a given restriction code 15 | /// @param restrictionCode Identifier for looking up a message 16 | /// @return Text showing the restriction's reasoning 17 | /// @dev Overwrite with your custom message and restrictionCode handling 18 | function messageForTransferRestriction (uint8 restrictionCode) public virtual view returns (string memory); 19 | } -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.6.12; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint public last_completed_migration; 6 | 7 | modifier restricted() { 8 | if (msg.sender == owner) _; 9 | } 10 | 11 | constructor() public { 12 | owner = msg.sender; 13 | } 14 | 15 | function setCompleted(uint completed) public restricted { 16 | last_completed_migration = completed; 17 | } 18 | 19 | function upgrade(address new_address) public restricted { 20 | Migrations upgraded = Migrations(new_address); 21 | upgraded.setCompleted(last_completed_migration); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /contracts/Proxy.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.6.12; 2 | 3 | contract Proxy { 4 | // Code position in storage is keccak256("PROXIABLE") = "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7" 5 | uint256 constant PROXIABLE_MEM_SLOT = 0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7; 6 | // constructor(bytes memory constructData, address contractLogic) public { 7 | constructor(address contractLogic) public { 8 | // Verify a valid address was passed in 9 | require(contractLogic != address(0), "Contract Logic cannot be 0x0"); 10 | 11 | // save the code address 12 | assembly { // solium-disable-line 13 | sstore(PROXIABLE_MEM_SLOT, contractLogic) 14 | } 15 | } 16 | 17 | fallback() external payable { 18 | assembly { // solium-disable-line 19 | let contractLogic := sload(PROXIABLE_MEM_SLOT) 20 | let ptr := mload(0x40) 21 | calldatacopy(ptr, 0x0, calldatasize()) 22 | let success := delegatecall(gas(), contractLogic, ptr, calldatasize(), 0, 0) 23 | let retSz := returndatasize() 24 | returndatacopy(ptr, 0, retSz) 25 | switch success 26 | case 0 { 27 | revert(ptr, retSz) 28 | } 29 | default { 30 | return(ptr, retSz) 31 | } 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /contracts/TokenSoftToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.6.12; 2 | 3 | import "./capabilities/Proxiable.sol"; 4 | import "./@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20Detailed.sol"; 5 | import "./ERC1404.sol"; 6 | import "./roles/OwnerRole.sol"; 7 | import "./capabilities/Whitelistable.sol"; 8 | import "./capabilities/Mintable.sol"; 9 | import "./capabilities/Burnable.sol"; 10 | import "./capabilities/Revocable.sol"; 11 | import "./capabilities/Pausable.sol"; 12 | 13 | contract TokenSoftToken is Proxiable, ERC20Detailed, ERC1404, OwnerRole, Whitelistable, Mintable, Burnable, Revocable, Pausable { 14 | 15 | // ERC1404 Error codes and messages 16 | uint8 public constant SUCCESS_CODE = 0; 17 | uint8 public constant FAILURE_NON_WHITELIST = 1; 18 | uint8 public constant FAILURE_PAUSED = 2; 19 | string public constant SUCCESS_MESSAGE = "SUCCESS"; 20 | string public constant FAILURE_NON_WHITELIST_MESSAGE = "The transfer was restricted due to white list configuration."; 21 | string public constant FAILURE_PAUSED_MESSAGE = "The transfer was restricted due to the contract being paused."; 22 | string public constant UNKNOWN_ERROR = "Unknown Error Code"; 23 | 24 | 25 | /** 26 | Constructor for the token to set readable details and mint all tokens 27 | to the specified owner. 28 | */ 29 | function initialize (address owner, string memory name, string memory symbol, uint8 decimals, uint256 initialSupply, bool whitelistEnabled) 30 | public 31 | initializer 32 | { 33 | ERC20Detailed.initialize(name, symbol, decimals); 34 | Mintable._mint(msg.sender, owner, initialSupply); 35 | OwnerRole._addOwner(owner); 36 | Whitelistable._setWhitelistEnabled(whitelistEnabled); 37 | } 38 | 39 | /** 40 | Public function to update the address of the code contract, retricted to owner 41 | */ 42 | function updateCodeAddress (address newAddress) public onlyOwner { 43 | Proxiable._updateCodeAddress(newAddress); 44 | } 45 | 46 | /** 47 | This function detects whether a transfer should be restricted and not allowed. 48 | If the function returns SUCCESS_CODE (0) then it should be allowed. 49 | */ 50 | function detectTransferRestriction (address from, address to, uint256) 51 | public 52 | view 53 | virtual 54 | override 55 | returns (uint8) 56 | { 57 | // Check the paused status of the contract 58 | if (Pausable.paused()) { 59 | return FAILURE_PAUSED; 60 | } 61 | 62 | // If an owner transferring, then ignore whitelist restrictions 63 | if(OwnerRole.isOwner(from)) { 64 | return SUCCESS_CODE; 65 | } 66 | 67 | // Restrictions are enabled, so verify the whitelist config allows the transfer. 68 | // Logic defined in Whitelistable parent class 69 | if(!checkWhitelistAllowed(from, to)) { 70 | return FAILURE_NON_WHITELIST; 71 | } 72 | 73 | // If no restrictions were triggered return success 74 | return SUCCESS_CODE; 75 | } 76 | 77 | /** 78 | This function allows a wallet or other client to get a human readable string to show 79 | a user if a transfer was restricted. It should return enough information for the user 80 | to know why it failed. 81 | */ 82 | function messageForTransferRestriction (uint8 restrictionCode) 83 | public 84 | view 85 | virtual 86 | override 87 | returns (string memory) 88 | { 89 | if (restrictionCode == SUCCESS_CODE) { 90 | return SUCCESS_MESSAGE; 91 | } 92 | 93 | if (restrictionCode == FAILURE_NON_WHITELIST) { 94 | return FAILURE_NON_WHITELIST_MESSAGE; 95 | } 96 | 97 | if (restrictionCode == FAILURE_PAUSED) { 98 | return FAILURE_PAUSED_MESSAGE; 99 | } 100 | 101 | // An unknown error code was passed in. 102 | return UNKNOWN_ERROR; 103 | } 104 | 105 | /** 106 | Evaluates whether a transfer should be allowed or not. 107 | */ 108 | modifier notRestricted (address from, address to, uint256 value) { 109 | uint8 restrictionCode = detectTransferRestriction(from, to, value); 110 | require(restrictionCode == SUCCESS_CODE, messageForTransferRestriction(restrictionCode)); 111 | _; 112 | } 113 | 114 | /** 115 | Overrides the parent class token transfer function to enforce restrictions. 116 | */ 117 | function transfer (address to, uint256 value) 118 | public 119 | virtual 120 | override(ERC20, IERC20) 121 | notRestricted(msg.sender, to, value) 122 | returns (bool success) 123 | { 124 | success = ERC20.transfer(to, value); 125 | } 126 | 127 | /** 128 | Overrides the parent class token transferFrom function to enforce restrictions. 129 | */ 130 | function transferFrom (address from, address to, uint256 value) 131 | public 132 | virtual 133 | override(ERC20, IERC20) 134 | notRestricted(from, to, value) 135 | returns (bool success) 136 | { 137 | success = ERC20.transferFrom(from, to, value); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /contracts/TokenSoftTokenV2.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.6.12; 2 | 3 | import "./TokenSoftToken.sol"; 4 | import "./capabilities/Blacklistable.sol"; 5 | import "./capabilities/RevocableToAddress.sol"; 6 | 7 | /** 8 | @title Tokensoft Token V2 9 | @notice This contract implements the ERC1404 Interface to add transfer restrictions to a standard ER20 token. 10 | The role based access controls allow the Owner accounts to determine which permissions are granted to admin accounts. 11 | Admin accounts can enable, disable, and configure the token restrictions built into the contract. 12 | */ 13 | contract TokenSoftTokenV2 is TokenSoftToken, Blacklistable, RevocableToAddress { 14 | 15 | /// @notice The from/to account has been explicitly denied the ability to send/receive 16 | uint8 public constant FAILURE_BLACKLIST = 3; 17 | string public constant FAILURE_BLACKLIST_MESSAGE = "Restricted due to blacklist"; 18 | 19 | /** 20 | @notice Used to detect if a proposed transfer will be allowed 21 | @dev A 0 return value is success - all other codes should be displayed to user via messageForTransferRestriction 22 | */ 23 | function detectTransferRestriction (address from, address to, uint256 amt) 24 | public 25 | override 26 | view 27 | returns (uint8) 28 | { 29 | // Restrictions are enabled, so verify the whitelist config allows the transfer. 30 | // Logic defined in Blacklistable parent class 31 | if(!checkBlacklistAllowed(from, to)) { 32 | return FAILURE_BLACKLIST; 33 | } 34 | 35 | return TokenSoftToken.detectTransferRestriction(from, to, amt); 36 | } 37 | 38 | /** 39 | @notice Returns a human readable string for the error returned via detectTransferRestriction 40 | */ 41 | function messageForTransferRestriction (uint8 restrictionCode) 42 | public 43 | override 44 | view 45 | returns (string memory) 46 | { 47 | if (restrictionCode == FAILURE_BLACKLIST) { 48 | return FAILURE_BLACKLIST_MESSAGE; 49 | } 50 | 51 | return TokenSoftToken.messageForTransferRestriction(restrictionCode); 52 | } 53 | 54 | /** 55 | @notice Transfers tokens if they are not restricted 56 | @dev Overrides the parent class token transfer function to enforce restrictions. 57 | */ 58 | function transfer (address to, uint256 value) 59 | public 60 | override(TokenSoftToken, ERC20) 61 | notRestricted(msg.sender, to, value) 62 | returns (bool success) 63 | { 64 | return TokenSoftToken.transfer(to, value); 65 | } 66 | 67 | /** 68 | @notice Transfers from a specified address if they are not restricted 69 | @dev Overrides the parent class token transferFrom function to enforce restrictions. 70 | */ 71 | function transferFrom (address from, address to, uint256 value) 72 | public 73 | override(TokenSoftToken, ERC20) 74 | notRestricted(from, to, value) 75 | returns (bool success) 76 | { 77 | return TokenSoftToken.transferFrom(from, to, value); 78 | } 79 | } -------------------------------------------------------------------------------- /contracts/capabilities/Blacklistable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.6.12; 2 | 3 | import "../roles/BlacklisterRole.sol"; 4 | 5 | /** 6 | Keeps track of Blacklists and can check if sender and reciever are configured to allow a transfer. 7 | Only administrators can update the Blacklists. 8 | */ 9 | contract Blacklistable is BlacklisterRole { 10 | // Track whether Blacklisting is enabled 11 | bool public isBlacklistEnabled; 12 | 13 | // The mapping to keep track if an address is blacklisted 14 | mapping (address => bool) public addressBlacklists; 15 | 16 | // Events to allow tracking add/remove. 17 | event AddressAddedToBlacklist(address indexed addedAddress, address indexed addedBy); 18 | event AddressRemovedFromBlacklist(address indexed removedAddress, address indexed removedBy); 19 | event BlacklistEnabledUpdated(address indexed updatedBy, bool indexed enabled); 20 | 21 | function _setBlacklistEnabled(bool enabled) internal { 22 | isBlacklistEnabled = enabled; 23 | emit BlacklistEnabledUpdated(msg.sender, enabled); 24 | } 25 | 26 | /** 27 | Sets an address's blacklisting status. Only administrators should be allowed to update this. 28 | */ 29 | function _addToBlacklist(address addressToAdd) internal { 30 | // Verify a valid address was passed in 31 | require(addressToAdd != address(0), "Cannot add 0x0"); 32 | 33 | // Verify the address is on the blacklist before it can be removed 34 | require(!addressBlacklists[addressToAdd], "Already on list"); 35 | 36 | // Set the address's white list ID 37 | addressBlacklists[addressToAdd] = true; 38 | 39 | // Emit the event for new Blacklist 40 | emit AddressAddedToBlacklist(addressToAdd, msg.sender); 41 | } 42 | 43 | /** 44 | Clears out an address from the blacklist. Only administrators should be allowed to update this. 45 | */ 46 | function _removeFromBlacklist(address addressToRemove) internal { 47 | // Verify a valid address was passed in 48 | require(addressToRemove != address(0), "Cannot remove 0x0"); 49 | 50 | // Verify the address is on the blacklist before it can be removed 51 | require(addressBlacklists[addressToRemove], "Not on list"); 52 | 53 | // Zero out the previous white list 54 | addressBlacklists[addressToRemove] = false; 55 | 56 | // Emit the event for tracking 57 | emit AddressRemovedFromBlacklist(addressToRemove, msg.sender); 58 | } 59 | 60 | 61 | /** 62 | Determine if the a sender is allowed to send to the receiver. 63 | If either the sender or receiver is blacklisted, then the transfer should be denied 64 | */ 65 | function checkBlacklistAllowed(address sender, address receiver) public view returns (bool) { 66 | // If Blacklist enforcement is not enabled, then allow all 67 | if(!isBlacklistEnabled){ 68 | return true; 69 | } 70 | 71 | // If either address is on the blacklist then fail 72 | return !addressBlacklists[sender] && !addressBlacklists[receiver]; 73 | } 74 | 75 | /** 76 | * Enable or disable the Blacklist enforcement 77 | */ 78 | function setBlacklistEnabled(bool enabled) public onlyOwner { 79 | _setBlacklistEnabled(enabled); 80 | } 81 | 82 | /** 83 | Public function that allows admins to remove an address from a Blacklist 84 | */ 85 | function addToBlacklist(address addressToAdd) public onlyBlacklister { 86 | _addToBlacklist(addressToAdd); 87 | } 88 | 89 | /** 90 | Public function that allows admins to remove an address from a Blacklist 91 | */ 92 | function removeFromBlacklist(address addressToRemove) public onlyBlacklister { 93 | _removeFromBlacklist(addressToRemove); 94 | } 95 | } -------------------------------------------------------------------------------- /contracts/capabilities/Burnable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.6.12; 2 | 3 | import "../@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20.sol"; 4 | import "../roles/BurnerRole.sol"; 5 | 6 | contract Burnable is ERC20, BurnerRole { 7 | event Burn(address indexed burner, address indexed from, uint256 amount); 8 | 9 | function _burn(address burner, address from, uint256 amount) internal returns (bool) { 10 | ERC20._burn(from, amount); 11 | emit Burn(burner, from, amount); 12 | return true; 13 | } 14 | 15 | /** 16 | Allow Burners to burn tokens from valid addresses 17 | */ 18 | function burn(address account, uint256 amount) public onlyBurner returns (bool) { 19 | return _burn(msg.sender, account, amount); 20 | } 21 | } -------------------------------------------------------------------------------- /contracts/capabilities/Mintable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.6.12; 2 | 3 | import "../@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20.sol"; 4 | import "../roles/MinterRole.sol"; 5 | 6 | contract Mintable is ERC20, MinterRole { 7 | event Mint(address indexed minter, address indexed to, uint256 amount); 8 | 9 | function _mint(address minter, address to, uint256 amount) internal returns (bool) { 10 | ERC20._mint(to, amount); 11 | emit Mint(minter, to, amount); 12 | return true; 13 | } 14 | 15 | /** 16 | Allow Owners to mint tokens to valid addresses 17 | */ 18 | function mint(address account, uint256 amount) public onlyMinter returns (bool) { 19 | return Mintable._mint(msg.sender, account, amount); 20 | } 21 | } -------------------------------------------------------------------------------- /contracts/capabilities/Pausable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.6.12; 2 | 3 | import "../roles/PauserRole.sol"; 4 | 5 | /** 6 | * @dev Contract module which allows children to implement an emergency stop 7 | * mechanism that can be triggered by an authorized account. 8 | * 9 | * This module is used through inheritance. It will make available the 10 | * modifiers `whenNotPaused` and `whenPaused`, which can be applied to 11 | * the functions of your contract. Note that they will not be pausable by 12 | * simply including this module, only once the modifiers are put in place. 13 | */ 14 | contract Pausable is PauserRole { 15 | /** 16 | * @dev Emitted when the pause is triggered by a pauser (`account`). 17 | */ 18 | event Paused(address account); 19 | 20 | /** 21 | * @dev Emitted when the pause is lifted by a pauser (`account`). 22 | */ 23 | event Unpaused(address account); 24 | 25 | bool private _paused; 26 | 27 | /** 28 | * @dev Returns true if the contract is paused, and false otherwise. 29 | */ 30 | function paused() public view returns (bool) { 31 | return _paused; 32 | } 33 | 34 | /** 35 | * @dev Modifier to make a function callable only when the contract is not paused. 36 | */ 37 | modifier whenNotPaused() { 38 | require(!_paused, "Pausable: paused"); 39 | _; 40 | } 41 | 42 | /** 43 | * @dev Modifier to make a function callable only when the contract is paused. 44 | */ 45 | modifier whenPaused() { 46 | require(_paused, "Pausable: not paused"); 47 | _; 48 | } 49 | 50 | /** 51 | * @dev Called by an Owner to pause, triggers stopped state. 52 | */ 53 | function _pause() internal { 54 | _paused = true; 55 | emit Paused(msg.sender); 56 | } 57 | 58 | /** 59 | * @dev Called by an Owner to unpause, returns to normal state. 60 | */ 61 | function _unpause() internal { 62 | _paused = false; 63 | emit Unpaused(msg.sender); 64 | } 65 | 66 | /** 67 | * @dev Called by an Owner to pause, triggers stopped state. 68 | */ 69 | function pause() public onlyPauser whenNotPaused { 70 | Pausable._pause(); 71 | } 72 | 73 | /** 74 | * @dev Called by an Owner to unpause, returns to normal state. 75 | */ 76 | function unpause() public onlyPauser whenPaused { 77 | Pausable._unpause(); 78 | } 79 | } -------------------------------------------------------------------------------- /contracts/capabilities/Proxiable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.6.12; 2 | 3 | contract Proxiable { 4 | // Code position in storage is keccak256("PROXIABLE") = "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7" 5 | uint256 constant PROXIABLE_MEM_SLOT = 0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7; 6 | 7 | event CodeAddressUpdated(address newAddress); 8 | 9 | function _updateCodeAddress(address newAddress) internal { 10 | require( 11 | bytes32(PROXIABLE_MEM_SLOT) == Proxiable(newAddress).proxiableUUID(), 12 | "Not compatible" 13 | ); 14 | assembly { // solium-disable-line 15 | sstore(PROXIABLE_MEM_SLOT, newAddress) 16 | } 17 | 18 | emit CodeAddressUpdated(newAddress); 19 | } 20 | 21 | function getLogicAddress() public view returns (address logicAddress) { 22 | assembly { // solium-disable-line 23 | logicAddress := sload(PROXIABLE_MEM_SLOT) 24 | } 25 | } 26 | 27 | function proxiableUUID() public pure returns (bytes32) { 28 | return bytes32(PROXIABLE_MEM_SLOT); 29 | } 30 | } -------------------------------------------------------------------------------- /contracts/capabilities/Revocable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.6.12; 2 | 3 | import "../@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20.sol"; 4 | import "../roles/RevokerRole.sol"; 5 | 6 | contract Revocable is ERC20, RevokerRole { 7 | 8 | event Revoke(address indexed revoker, address indexed from, uint256 amount); 9 | 10 | function _revoke( 11 | address _from, 12 | uint256 _amount 13 | ) 14 | internal 15 | returns (bool) 16 | { 17 | ERC20._transfer(_from, msg.sender, _amount); 18 | emit Revoke(msg.sender, _from, _amount); 19 | return true; 20 | } 21 | 22 | /** 23 | Allow Admins to revoke tokens from any address 24 | */ 25 | function revoke(address from, uint256 amount) public onlyRevoker returns (bool) { 26 | return _revoke(from, amount); 27 | } 28 | } -------------------------------------------------------------------------------- /contracts/capabilities/RevocableToAddress.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.6.12; 2 | 3 | import "../@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20.sol"; 4 | import "../roles/RevokerRole.sol"; 5 | 6 | contract RevocableToAddress is ERC20, RevokerRole { 7 | 8 | event RevokeToAddress(address indexed revoker, address indexed from, address indexed to, uint256 amount); 9 | 10 | function _revokeToAddress( 11 | address _from, 12 | address _to, 13 | uint256 _amount 14 | ) 15 | internal 16 | returns (bool) 17 | { 18 | ERC20._transfer(_from, _to, _amount); 19 | emit RevokeToAddress(msg.sender, _from, _to, _amount); 20 | return true; 21 | } 22 | 23 | /** 24 | Allow Admins to revoke tokens from any address to any destination 25 | */ 26 | function revokeToAddress(address from, address to, uint256 amount) public onlyRevoker returns (bool) { 27 | return _revokeToAddress(from, to, amount); 28 | } 29 | } -------------------------------------------------------------------------------- /contracts/capabilities/Whitelistable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.6.12; 2 | 3 | import "../roles/WhitelisterRole.sol"; 4 | 5 | /** 6 | Keeps track of whitelists and can check if sender and reciever are configured to allow a transfer. 7 | Only administrators can update the whitelists. 8 | Any address can only be a member of one whitelist at a time. 9 | */ 10 | contract Whitelistable is WhitelisterRole { 11 | // Track whether whitelisting is enabled 12 | bool public isWhitelistEnabled; 13 | 14 | // Zero is reserved for indicating it is not on a whitelist 15 | uint8 constant NO_WHITELIST = 0; 16 | 17 | // The mapping to keep track of which whitelist any address belongs to. 18 | // 0 is reserved for no whitelist and is the default for all addresses. 19 | mapping (address => uint8) public addressWhitelists; 20 | 21 | // The mapping to keep track of each whitelist's outbound whitelist flags. 22 | // Boolean flag indicates whether outbound transfers are enabled. 23 | mapping(uint8 => mapping (uint8 => bool)) public outboundWhitelistsEnabled; 24 | 25 | // Events to allow tracking add/remove. 26 | event AddressAddedToWhitelist(address indexed addedAddress, uint8 indexed whitelist, address indexed addedBy); 27 | event AddressRemovedFromWhitelist(address indexed removedAddress, uint8 indexed whitelist, address indexed removedBy); 28 | event OutboundWhitelistUpdated( 29 | address indexed updatedBy, uint8 indexed sourceWhitelist, uint8 indexed destinationWhitelist, bool from, bool to); 30 | event WhitelistEnabledUpdated(address indexed updatedBy, bool indexed enabled); 31 | 32 | function _setWhitelistEnabled(bool enabled) internal { 33 | isWhitelistEnabled = enabled; 34 | emit WhitelistEnabledUpdated(msg.sender, enabled); 35 | } 36 | 37 | /** 38 | Sets an address's white list ID. Only administrators should be allowed to update this. 39 | If an address is on an existing whitelist, it will just get updated to the new value (removed from previous). 40 | */ 41 | function _addToWhitelist(address addressToAdd, uint8 whitelist) internal { 42 | // Verify a valid address was passed in 43 | require(addressToAdd != address(0), "Cannot add address 0x0 to a whitelist."); 44 | 45 | // Verify the whitelist is valid 46 | require(whitelist != NO_WHITELIST, "Invalid whitelist ID supplied"); 47 | 48 | // Save off the previous white list 49 | uint8 previousWhitelist = addressWhitelists[addressToAdd]; 50 | 51 | // Set the address's white list ID 52 | addressWhitelists[addressToAdd] = whitelist; 53 | 54 | // If the previous whitelist existed then we want to indicate it has been removed 55 | if(previousWhitelist != NO_WHITELIST) { 56 | // Emit the event for tracking 57 | emit AddressRemovedFromWhitelist(addressToAdd, previousWhitelist, msg.sender); 58 | } 59 | 60 | // Emit the event for new whitelist 61 | emit AddressAddedToWhitelist(addressToAdd, whitelist, msg.sender); 62 | } 63 | 64 | /** 65 | Clears out an address's white list ID. Only administrators should be allowed to update this. 66 | */ 67 | function _removeFromWhitelist(address addressToRemove) internal { 68 | // Verify a valid address was passed in 69 | require(addressToRemove != address(0), "Cannot remove address 0x0 from a whitelist."); 70 | 71 | // Save off the previous white list 72 | uint8 previousWhitelist = addressWhitelists[addressToRemove]; 73 | 74 | // Verify the address was actually on a whitelist 75 | require(previousWhitelist != NO_WHITELIST, "Address cannot be removed from invalid whitelist."); 76 | 77 | // Zero out the previous white list 78 | addressWhitelists[addressToRemove] = NO_WHITELIST; 79 | 80 | // Emit the event for tracking 81 | emit AddressRemovedFromWhitelist(addressToRemove, previousWhitelist, msg.sender); 82 | } 83 | 84 | /** 85 | Sets the flag to indicate whether source whitelist is allowed to send to destination whitelist. 86 | Only administrators should be allowed to update this. 87 | */ 88 | function _updateOutboundWhitelistEnabled(uint8 sourceWhitelist, uint8 destinationWhitelist, bool newEnabledValue) internal { 89 | // Get the old enabled flag 90 | bool oldEnabledValue = outboundWhitelistsEnabled[sourceWhitelist][destinationWhitelist]; 91 | 92 | // Update to the new value 93 | outboundWhitelistsEnabled[sourceWhitelist][destinationWhitelist] = newEnabledValue; 94 | 95 | // Emit event for tracking 96 | emit OutboundWhitelistUpdated(msg.sender, sourceWhitelist, destinationWhitelist, oldEnabledValue, newEnabledValue); 97 | } 98 | 99 | /** 100 | Determine if the a sender is allowed to send to the receiver. 101 | The source whitelist must be enabled to send to the whitelist where the receive exists. 102 | */ 103 | function checkWhitelistAllowed(address sender, address receiver) public view returns (bool) { 104 | // If whitelist enforcement is not enabled, then allow all 105 | if(!isWhitelistEnabled){ 106 | return true; 107 | } 108 | 109 | // First get each address white list 110 | uint8 senderWhiteList = addressWhitelists[sender]; 111 | uint8 receiverWhiteList = addressWhitelists[receiver]; 112 | 113 | // If either address is not on a white list then the check should fail 114 | if(senderWhiteList == NO_WHITELIST || receiverWhiteList == NO_WHITELIST){ 115 | return false; 116 | } 117 | 118 | // Determine if the sending whitelist is allowed to send to the destination whitelist 119 | return outboundWhitelistsEnabled[senderWhiteList][receiverWhiteList]; 120 | } 121 | 122 | /** 123 | * Enable or disable the whitelist enforcement 124 | */ 125 | function setWhitelistEnabled(bool enabled) public onlyOwner { 126 | _setWhitelistEnabled(enabled); 127 | } 128 | 129 | /** 130 | Public function that allows admins to remove an address from a whitelist 131 | */ 132 | function addToWhitelist(address addressToAdd, uint8 whitelist) public onlyWhitelister { 133 | _addToWhitelist(addressToAdd, whitelist); 134 | } 135 | 136 | /** 137 | Public function that allows admins to remove an address from a whitelist 138 | */ 139 | function removeFromWhitelist(address addressToRemove) public onlyWhitelister { 140 | _removeFromWhitelist(addressToRemove); 141 | } 142 | 143 | /** 144 | Public function that allows admins to update outbound whitelists 145 | */ 146 | function updateOutboundWhitelistEnabled(uint8 sourceWhitelist, uint8 destinationWhitelist, bool newEnabledValue) public onlyWhitelister { 147 | _updateOutboundWhitelistEnabled(sourceWhitelist, destinationWhitelist, newEnabledValue); 148 | } 149 | } -------------------------------------------------------------------------------- /contracts/roles/BlacklisterRole.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.6.12; 2 | 3 | import "./OwnerRole.sol"; 4 | 5 | contract BlacklisterRole is OwnerRole { 6 | 7 | event BlacklisterAdded(address indexed addedBlacklister, address indexed addedBy); 8 | event BlacklisterRemoved(address indexed removedBlacklister, address indexed removedBy); 9 | 10 | Roles.Role private _Blacklisters; 11 | 12 | modifier onlyBlacklister() { 13 | require(isBlacklister(msg.sender), "BlacklisterRole missing"); 14 | _; 15 | } 16 | 17 | function isBlacklister(address account) public view returns (bool) { 18 | return _Blacklisters.has(account); 19 | } 20 | 21 | function _addBlacklister(address account) internal { 22 | _Blacklisters.add(account); 23 | emit BlacklisterAdded(account, msg.sender); 24 | } 25 | 26 | function _removeBlacklister(address account) internal { 27 | _Blacklisters.remove(account); 28 | emit BlacklisterRemoved(account, msg.sender); 29 | } 30 | 31 | function addBlacklister(address account) public onlyOwner { 32 | _addBlacklister(account); 33 | } 34 | 35 | function removeBlacklister(address account) public onlyOwner { 36 | _removeBlacklister(account); 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /contracts/roles/BurnerRole.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.6.12; 2 | 3 | import "./OwnerRole.sol"; 4 | 5 | contract BurnerRole is OwnerRole { 6 | 7 | event BurnerAdded(address indexed addedBurner, address indexed addedBy); 8 | event BurnerRemoved(address indexed removedBurner, address indexed removedBy); 9 | 10 | Roles.Role private _burners; 11 | 12 | modifier onlyBurner() { 13 | require(isBurner(msg.sender), "BurnerRole: caller does not have the Burner role"); 14 | _; 15 | } 16 | 17 | function isBurner(address account) public view returns (bool) { 18 | return _burners.has(account); 19 | } 20 | 21 | function _addBurner(address account) internal { 22 | _burners.add(account); 23 | emit BurnerAdded(account, msg.sender); 24 | } 25 | 26 | function _removeBurner(address account) internal { 27 | _burners.remove(account); 28 | emit BurnerRemoved(account, msg.sender); 29 | } 30 | 31 | function addBurner(address account) public onlyOwner { 32 | _addBurner(account); 33 | } 34 | 35 | function removeBurner(address account) public onlyOwner { 36 | _removeBurner(account); 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /contracts/roles/MinterRole.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.6.12; 2 | 3 | import "./OwnerRole.sol"; 4 | 5 | contract MinterRole is OwnerRole { 6 | 7 | event MinterAdded(address indexed addedMinter, address indexed addedBy); 8 | event MinterRemoved(address indexed removedMinter, address indexed removedBy); 9 | 10 | Roles.Role private _minters; 11 | 12 | modifier onlyMinter() { 13 | require(isMinter(msg.sender), "MinterRole: caller does not have the Minter role"); 14 | _; 15 | } 16 | 17 | function isMinter(address account) public view returns (bool) { 18 | return _minters.has(account); 19 | } 20 | 21 | function _addMinter(address account) internal { 22 | _minters.add(account); 23 | emit MinterAdded(account, msg.sender); 24 | } 25 | 26 | function _removeMinter(address account) internal { 27 | _minters.remove(account); 28 | emit MinterRemoved(account, msg.sender); 29 | } 30 | 31 | function addMinter(address account) public onlyOwner { 32 | _addMinter(account); 33 | } 34 | 35 | function removeMinter(address account) public onlyOwner { 36 | _removeMinter(account); 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /contracts/roles/OwnerRole.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.6.12; 2 | 3 | import "../@openzeppelin/contracts-ethereum-package/contracts/access/Roles.sol"; 4 | 5 | contract OwnerRole { 6 | using Roles for Roles.Role; 7 | 8 | event OwnerAdded(address indexed addedOwner, address indexed addedBy); 9 | event OwnerRemoved(address indexed removedOwner, address indexed removedBy); 10 | 11 | Roles.Role private _owners; 12 | 13 | modifier onlyOwner() { 14 | require(isOwner(msg.sender), "OwnerRole: caller does not have the Owner role"); 15 | _; 16 | } 17 | 18 | function isOwner(address account) public view returns (bool) { 19 | return _owners.has(account); 20 | } 21 | 22 | function addOwner(address account) public onlyOwner { 23 | _addOwner(account); 24 | } 25 | 26 | function removeOwner(address account) public onlyOwner { 27 | _removeOwner(account); 28 | } 29 | 30 | function _addOwner(address account) internal { 31 | _owners.add(account); 32 | emit OwnerAdded(account, msg.sender); 33 | } 34 | 35 | function _removeOwner(address account) internal { 36 | _owners.remove(account); 37 | emit OwnerRemoved(account, msg.sender); 38 | } 39 | } -------------------------------------------------------------------------------- /contracts/roles/PauserRole.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.6.12; 2 | 3 | import "./OwnerRole.sol"; 4 | 5 | contract PauserRole is OwnerRole { 6 | 7 | event PauserAdded(address indexed addedPauser, address indexed addedBy); 8 | event PauserRemoved(address indexed removedPauser, address indexed removedBy); 9 | 10 | Roles.Role private _pausers; 11 | 12 | modifier onlyPauser() { 13 | require(isPauser(msg.sender), "PauserRole: caller does not have the Pauser role"); 14 | _; 15 | } 16 | 17 | function isPauser(address account) public view returns (bool) { 18 | return _pausers.has(account); 19 | } 20 | 21 | function _addPauser(address account) internal { 22 | _pausers.add(account); 23 | emit PauserAdded(account, msg.sender); 24 | } 25 | 26 | function _removePauser(address account) internal { 27 | _pausers.remove(account); 28 | emit PauserRemoved(account, msg.sender); 29 | } 30 | 31 | function addPauser(address account) public onlyOwner { 32 | _addPauser(account); 33 | } 34 | 35 | function removePauser(address account) public onlyOwner { 36 | _removePauser(account); 37 | } 38 | } -------------------------------------------------------------------------------- /contracts/roles/RevokerRole.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.6.12; 2 | 3 | import "./OwnerRole.sol"; 4 | 5 | contract RevokerRole is OwnerRole { 6 | 7 | event RevokerAdded(address indexed addedRevoker, address indexed addedBy); 8 | event RevokerRemoved(address indexed removedRevoker, address indexed removedBy); 9 | 10 | Roles.Role private _revokers; 11 | 12 | modifier onlyRevoker() { 13 | require(isRevoker(msg.sender), "RevokerRole: caller does not have the Revoker role"); 14 | _; 15 | } 16 | 17 | function isRevoker(address account) public view returns (bool) { 18 | return _revokers.has(account); 19 | } 20 | 21 | function _addRevoker(address account) internal { 22 | _revokers.add(account); 23 | emit RevokerAdded(account, msg.sender); 24 | } 25 | 26 | function _removeRevoker(address account) internal { 27 | _revokers.remove(account); 28 | emit RevokerRemoved(account, msg.sender); 29 | } 30 | 31 | function addRevoker(address account) public onlyOwner { 32 | _addRevoker(account); 33 | } 34 | 35 | function removeRevoker(address account) public onlyOwner { 36 | _removeRevoker(account); 37 | } 38 | } -------------------------------------------------------------------------------- /contracts/roles/WhitelisterRole.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.6.12; 2 | 3 | import "./OwnerRole.sol"; 4 | 5 | contract WhitelisterRole is OwnerRole { 6 | 7 | event WhitelisterAdded(address indexed addedWhitelister, address indexed addedBy); 8 | event WhitelisterRemoved(address indexed removedWhitelister, address indexed removedBy); 9 | 10 | Roles.Role private _whitelisters; 11 | 12 | modifier onlyWhitelister() { 13 | require(isWhitelister(msg.sender), "WhitelisterRole: caller does not have the Whitelister role"); 14 | _; 15 | } 16 | 17 | function isWhitelister(address account) public view returns (bool) { 18 | return _whitelisters.has(account); 19 | } 20 | 21 | function _addWhitelister(address account) internal { 22 | _whitelisters.add(account); 23 | emit WhitelisterAdded(account, msg.sender); 24 | } 25 | 26 | function _removeWhitelister(address account) internal { 27 | _whitelisters.remove(account); 28 | emit WhitelisterRemoved(account, msg.sender); 29 | } 30 | 31 | function addWhitelister(address account) public onlyOwner { 32 | _addWhitelister(account); 33 | } 34 | 35 | function removeWhitelister(address account) public onlyOwner { 36 | _removeWhitelister(account); 37 | } 38 | } -------------------------------------------------------------------------------- /contracts/testing/Escrowable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.6.12; 2 | 3 | import "../@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20.sol"; 4 | import './EscrowerRole.sol'; 5 | 6 | /** 7 | Keeps track of transfers that have been requested and locked up in escrow. 8 | An administrator is required to approve/reject transfer proposals. 9 | A user can cancel a previously requested transfer that is locked in escrow. 10 | */ 11 | contract Escrowable is ERC20, EscrowerRole { 12 | 13 | // Event emitted when a proposal is created/updated 14 | event TransferProposalUpdated(address indexed updatedBy, uint requestId, ProposalState state); 15 | 16 | // The valid states a proposal can be in 17 | enum ProposalState { Pending, Approved, Rejected, Canceled } 18 | 19 | // The struct tracking each transfer proposal 20 | struct TransferProposal { 21 | address createdBy; 22 | address from; 23 | address to; 24 | uint256 value; 25 | ProposalState state; 26 | } 27 | 28 | // Transfer requests will be tracked by a monotonic ID that increases after each proposal is created 29 | uint numTransferProposals; 30 | mapping (uint => TransferProposal) public transferProposals; 31 | 32 | /** 33 | * Internal function to create a new proposal that will lock funds into escrow. This should only be called 34 | * once the balances are confirmed to be sufficient to create the proposal (account should have enough funds outside of proposals) 35 | */ 36 | function _createTransferProposal(address to, uint256 value) internal returns(bool) { 37 | 38 | 39 | // Save off the transfer request 40 | transferProposals[numTransferProposals] = TransferProposal(msg.sender, msg.sender, to, value, ProposalState.Pending); 41 | 42 | // Emit the request event 43 | emit TransferProposalUpdated(msg.sender, numTransferProposals, ProposalState.Pending); 44 | 45 | // Increment the request counter 46 | numTransferProposals = numTransferProposals + 1; 47 | 48 | // Move the tokens into this contract 49 | ERC20.transfer(address(this), value); 50 | 51 | return true; 52 | } 53 | 54 | /** 55 | * Internal function to create a new proposal from a 3rd party account that will lock funds into escrow. This should only be called 56 | * once the balances are confirmed to be sufficient to create the proposal (account should have enough funds outside of proposals, and 57 | * the calling account has been approved to transfer the amount) 58 | */ 59 | function _createTransferFromProposal(address from, address to, uint256 value) internal returns (bool) { 60 | 61 | // Save off the transfer request 62 | transferProposals[numTransferProposals] = TransferProposal(msg.sender, from, to, value, ProposalState.Pending); 63 | 64 | // Emit the request event 65 | emit TransferProposalUpdated(msg.sender, numTransferProposals, ProposalState.Pending); 66 | 67 | // Increment the request counter 68 | numTransferProposals = numTransferProposals + 1; 69 | 70 | // Move the tokens into this contract 71 | ERC20.transferFrom(from, address(this), value); 72 | 73 | return true; 74 | } 75 | 76 | function _releaseProposal(TransferProposal storage request, uint requestId, ProposalState newState) internal { 77 | // Update request 78 | request.state = newState; 79 | emit TransferProposalUpdated(msg.sender, requestId, request.state); 80 | } 81 | 82 | /** 83 | * An administrator has the ability to approve a proposal. During approval process, funds will be moved 84 | * to the target account. 85 | */ 86 | function _approveTransferProposal(uint requestId) internal { 87 | // Ensure the request ID is valid 88 | require(requestId < numTransferProposals, "Request ID is not in proper range"); 89 | 90 | // Get the request 91 | TransferProposal storage request = transferProposals[requestId]; 92 | 93 | // Ensure the request can be approved 94 | require(request.state == ProposalState.Pending, "Request must be in Pending state to approve."); 95 | 96 | // Release the proposal 97 | _releaseProposal(request, requestId, ProposalState.Approved); 98 | 99 | // Transfer funds - function will check balances 100 | ERC20._transfer(address(this), request.to, request.value); 101 | } 102 | 103 | /** 104 | * An administrator has the ability to reject a proposal. During rejection process, funds will be unlocked 105 | * from the source account. 106 | */ 107 | function _rejectTransferProposal(uint requestId) internal { 108 | // Ensure the request ID is valid 109 | require(requestId < numTransferProposals, "Request ID is not in proper range"); 110 | 111 | // Get the request 112 | TransferProposal storage request = transferProposals[requestId]; 113 | 114 | // Ensure the request can be rejected 115 | require(request.state == ProposalState.Pending, "Request must be in Pending state to reject."); 116 | 117 | // Release the proposal 118 | _releaseProposal(request, requestId, ProposalState.Rejected); 119 | 120 | // Transfer funds back to source - function will check balances 121 | ERC20._transfer(address(this), request.from, request.value); 122 | } 123 | 124 | /** 125 | * If a user locks up funds in a proposal, they can cancel it as long as an administrator has not 126 | * already approved or rejected it. 127 | */ 128 | function cancelTransferProposal(uint requestId) public { 129 | // Ensure the request ID is valid 130 | require(requestId < numTransferProposals, "Request ID is not in proper range"); 131 | 132 | // Get the request 133 | TransferProposal storage request = transferProposals[requestId]; 134 | 135 | require(request.state == ProposalState.Pending, "Request must be in Pending state to cancel."); 136 | 137 | // Ensure the message sender it the address where funds are moving from 138 | require(msg.sender == request.createdBy, "Only the creator of a request can cancel it"); 139 | 140 | // Release the proposal 141 | _releaseProposal(request, requestId, ProposalState.Canceled); 142 | 143 | // Transfer funds back to source - function will check balances 144 | ERC20._transfer(address(this), request.from, request.value); 145 | } 146 | 147 | function getTransferProposal(uint requestId) 148 | public 149 | view 150 | returns ( 151 | address createdBy, 152 | address from, 153 | address to, 154 | uint256 value, 155 | ProposalState state 156 | ) 157 | { 158 | require(requestId < numTransferProposals, "requestId is out of range"); 159 | TransferProposal storage request = transferProposals[requestId]; 160 | createdBy = request.createdBy; 161 | from = request.from; 162 | to = request.to; 163 | value = request.value; 164 | state = request.state; 165 | } 166 | } -------------------------------------------------------------------------------- /contracts/testing/EscrowerRole.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.6.12; 2 | 3 | import "../roles/OwnerRole.sol"; 4 | 5 | contract EscrowerRole is OwnerRole { 6 | 7 | event EscrowerAdded(address indexed addedEscrow, address indexed addedBy); 8 | event EscrowerRemoved(address indexed removedEscrow, address indexed removedBy); 9 | 10 | Roles.Role private _escrowers; 11 | 12 | modifier onlyEscrower() { 13 | require(isEscrower(msg.sender), "EscrowerRole: caller does not have the Escrow role"); 14 | _; 15 | } 16 | 17 | function isEscrower(address account) public view returns (bool) { 18 | return _escrowers.has(account); 19 | } 20 | 21 | function _addEscrower(address account) internal { 22 | _escrowers.add(account); 23 | emit EscrowerAdded(account, msg.sender); 24 | } 25 | 26 | function _removeEscrower(address account) internal { 27 | _escrowers.remove(account); 28 | emit EscrowerRemoved(account, msg.sender); 29 | } 30 | 31 | function addEscrower(address account) public onlyOwner { 32 | _addEscrower(account); 33 | } 34 | 35 | function removeEscrower(address account) public onlyOwner { 36 | require(msg.sender != account, "Escrowers cannot remove themselves as Escrower"); 37 | _removeEscrower(account); 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /contracts/testing/TokenSoftTokenEscrow.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.6.12; 2 | 3 | import "../capabilities/Proxiable.sol"; 4 | import "../@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20Detailed.sol"; 5 | import "../roles/OwnerRole.sol"; 6 | import "../capabilities/Whitelistable.sol"; 7 | import "../capabilities/Mintable.sol"; 8 | import "../capabilities/Burnable.sol"; 9 | import "../capabilities/Revocable.sol"; 10 | import "../capabilities/Pausable.sol"; 11 | import "./Escrowable.sol"; 12 | 13 | contract TokenSoftTokenEscrow is Proxiable, ERC20Detailed, OwnerRole, Whitelistable, Mintable, Burnable, Revocable, Pausable, Escrowable { 14 | 15 | // ERC1404 Error codes and messages` 16 | uint8 public constant SUCCESS_CODE = 0; 17 | uint8 public constant FAILURE_NON_WHITELIST = 1; 18 | uint8 public constant FAILURE_PAUSED = 2; 19 | string public constant SUCCESS_MESSAGE = "SUCCESS"; 20 | string public constant FAILURE_NON_WHITELIST_MESSAGE = "The transfer was restricted due to white list configuration."; 21 | string public constant FAILURE_PAUSED_MESSAGE = "The transfer was restricted due to the contract being paused."; 22 | string public constant UNKNOWN_ERROR = "Unknown Error Code"; 23 | 24 | /** 25 | Constructor for the token to set readable details and mint all tokens 26 | to the specified owner. 27 | */ 28 | function initialize (address owner, string memory name, string memory symbol, uint8 decimals, uint256 initialSupply, bool whitelistEnabled) 29 | public 30 | initializer 31 | { 32 | ERC20Detailed.initialize(name, symbol, decimals); 33 | Mintable._mint(msg.sender, owner, initialSupply); 34 | OwnerRole._addOwner(owner); 35 | Whitelistable._setWhitelistEnabled(whitelistEnabled); 36 | } 37 | 38 | /** 39 | Public function to update the address of the code contract, retricted to owner 40 | */ 41 | function updateCodeAddress (address newAddress) public onlyOwner { 42 | Proxiable._updateCodeAddress(newAddress); 43 | } 44 | 45 | /** 46 | Restrict rejectTransferProposals to admins only 47 | */ 48 | function rejectTransferProposal(uint requestId) public onlyEscrower { 49 | Escrowable._rejectTransferProposal(requestId); 50 | } 51 | 52 | /** 53 | Restrict approveTransferProposals to admins only 54 | */ 55 | function approveTransferProposal(uint requestId) public onlyEscrower { 56 | Escrowable._approveTransferProposal(requestId); 57 | } 58 | 59 | /** 60 | Overrides the parent class token transfer function to enforce restrictions. 61 | */ 62 | function transfer (address to, uint256 value) 63 | public 64 | override(IERC20, ERC20) 65 | returns (bool success) 66 | { 67 | success = Escrowable._createTransferProposal(to, value); 68 | } 69 | 70 | /** 71 | Overrides the parent class token transferFrom function to enforce restrictions. 72 | Note that the approved amount of tokens the sender can transfer does not get reimbursed if the 73 | Transfer proposal is rejcted or canceled. 74 | */ 75 | function transferFrom (address from, address to, uint256 value) 76 | public 77 | override(IERC20, ERC20) 78 | returns (bool success) 79 | { 80 | success = Escrowable._createTransferFromProposal(from, to, value); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /contracts/testing/TokenSoftTokenEscrowNotProxiable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.6.12; 2 | 3 | import "../@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20Detailed.sol"; 4 | import "../roles/OwnerRole.sol"; 5 | import "../capabilities/Whitelistable.sol"; 6 | import "../capabilities/Mintable.sol"; 7 | import "../capabilities/Burnable.sol"; 8 | import "../capabilities/Revocable.sol"; 9 | import "../capabilities/Pausable.sol"; 10 | import "./Escrowable.sol"; 11 | 12 | contract TokenSoftTokenEscrowNotProxiable is ERC20Detailed, OwnerRole, Whitelistable, Mintable, Burnable, Revocable, Pausable, Escrowable { 13 | 14 | // ERC1404 Error codes and messages 15 | uint8 public constant SUCCESS_CODE = 0; 16 | uint8 public constant FAILURE_NON_WHITELIST = 1; 17 | uint8 public constant FAILURE_PAUSED = 2; 18 | string public constant SUCCESS_MESSAGE = "SUCCESS"; 19 | string public constant FAILURE_NON_WHITELIST_MESSAGE = "The transfer was restricted due to white list configuration."; 20 | string public constant FAILURE_PAUSED_MESSAGE = "The transfer was restricted due to the contract being paused."; 21 | string public constant UNKNOWN_ERROR = "Unknown Error Code"; 22 | 23 | /** 24 | Constructor for the token to set readable details and mint all tokens 25 | to the specified owner. 26 | */ 27 | function initialize (address owner, string memory name, string memory symbol, uint8 decimals, uint256 initialSupply, bool whitelistEnabled) 28 | public 29 | initializer 30 | { 31 | ERC20Detailed.initialize(name, symbol, decimals); 32 | Mintable._mint(msg.sender, owner, initialSupply); 33 | OwnerRole._addOwner(owner); 34 | Whitelistable._setWhitelistEnabled(whitelistEnabled); 35 | } 36 | 37 | /** 38 | Restrict rejectTransferProposals to admins only 39 | */ 40 | function rejectTransferProposal(uint requestId) public onlyEscrower { 41 | Escrowable._rejectTransferProposal(requestId); 42 | } 43 | 44 | /** 45 | Restrict approveTransferProposals to admins only 46 | */ 47 | function approveTransferProposal(uint requestId) public onlyEscrower { 48 | Escrowable._approveTransferProposal(requestId); 49 | } 50 | 51 | /** 52 | Overrides the parent class token transfer function to enforce restrictions. 53 | */ 54 | function transfer (address to, uint256 value) 55 | public 56 | override(IERC20, ERC20) 57 | returns (bool success) 58 | { 59 | success = Escrowable._createTransferProposal(to, value); 60 | } 61 | 62 | /** 63 | Overrides the parent class token transferFrom function to enforce restrictions. 64 | Note that the approved amount of tokens the sender can transfer does not get reimbursed if the 65 | Transfer proposal is rejcted or canceled. 66 | */ 67 | function transferFrom (address from, address to, uint256 value) 68 | public 69 | override(IERC20, ERC20) 70 | returns (bool success) 71 | { 72 | success = Escrowable._createTransferFromProposal(from, to, value); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /example_whitelist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tokensoft/tokensoft_token/5a9387235fd4eccce1b92254e29a488434df72f3/example_whitelist.png -------------------------------------------------------------------------------- /flattened/v1.0/Proxy.v1.0-flat.sol: -------------------------------------------------------------------------------- 1 | // Tokensoft Token Proxy v1.0 2 | 3 | // File: contracts/Proxy.sol 4 | 5 | pragma solidity 0.6.12; 6 | 7 | contract Proxy { 8 | // Code position in storage is keccak256("PROXIABLE") = "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7" 9 | // constructor(bytes memory constructData, address contractLogic) public { 10 | constructor(address contractLogic) public { 11 | // Verify a valid address was passed in 12 | require(contractLogic != address(0), "Contract Logic cannot be 0x0"); 13 | 14 | // save the code address 15 | assembly { // solium-disable-line 16 | sstore(0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7, contractLogic) 17 | } 18 | } 19 | 20 | function() external payable { 21 | assembly { // solium-disable-line 22 | let contractLogic := sload(0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7) 23 | let ptr := mload(0x40) 24 | calldatacopy(ptr, 0x0, calldatasize) 25 | let success := delegatecall(gas, contractLogic, ptr, calldatasize, 0, 0) 26 | let retSz := returndatasize 27 | returndatacopy(ptr, 0, retSz) 28 | switch success 29 | case 0 { 30 | revert(ptr, retSz) 31 | } 32 | default { 33 | return(ptr, retSz) 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /flattened/v2.0/Proxy.v.2.0-flat.sol: -------------------------------------------------------------------------------- 1 | 2 | // File: contracts/Proxy.sol 3 | 4 | pragma solidity 0.6.12; 5 | 6 | contract Proxy { 7 | // Code position in storage is keccak256("PROXIABLE") = "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7" 8 | uint256 constant PROXIABLE_MEM_SLOT = 0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7; 9 | // constructor(bytes memory constructData, address contractLogic) public { 10 | constructor(address contractLogic) public { 11 | // Verify a valid address was passed in 12 | require(contractLogic != address(0), "Contract Logic cannot be 0x0"); 13 | 14 | // save the code address 15 | assembly { // solium-disable-line 16 | sstore(PROXIABLE_MEM_SLOT, contractLogic) 17 | } 18 | } 19 | 20 | fallback() external payable { 21 | assembly { // solium-disable-line 22 | let contractLogic := sload(PROXIABLE_MEM_SLOT) 23 | let ptr := mload(0x40) 24 | calldatacopy(ptr, 0x0, calldatasize()) 25 | let success := delegatecall(gas(), contractLogic, ptr, calldatasize(), 0, 0) 26 | let retSz := returndatasize() 27 | returndatacopy(ptr, 0, retSz) 28 | switch success 29 | case 0 { 30 | revert(ptr, retSz) 31 | } 32 | default { 33 | return(ptr, retSz) 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /funding.json: -------------------------------------------------------------------------------- 1 | { 2 | "opRetro": { 3 | "projectId": "0x3f9e87e6d2e9e7b1bb76389a9b35daf49253558df6d303c7c69580fc9a1a0885" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | /* global artifacts */ 2 | const Migrations = artifacts.require('Migrations') 3 | 4 | module.exports = function (deployer) { 5 | deployer.deploy(Migrations) 6 | } 7 | -------------------------------------------------------------------------------- /migrations/2_deploy_contracts.js: -------------------------------------------------------------------------------- 1 | /* global artifacts */ 2 | const TokenSoftToken = artifacts.require('TokenSoftTokenV2') 3 | const TokenSoftTokenEscrow = artifacts.require('TokenSoftTokenEscrow') 4 | const Proxy = artifacts.require("Proxy"); 5 | 6 | module.exports = async function (deployer, network, accounts) { 7 | await deployer.deploy(TokenSoftToken) 8 | await deployer.deploy(TokenSoftTokenEscrow) 9 | await deployer.deploy(Proxy, TokenSoftToken.address); 10 | await TokenSoftToken.at(Proxy.address) 11 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tokensoft_token", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "truffle-config.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "compile": "truffle compile", 11 | "console": "truffle console", 12 | "test": "npm run compile && scripts/test.sh", 13 | "coverage": "truffle run coverage" 14 | }, 15 | "author": "", 16 | "license": "ISC", 17 | "dependencies": { 18 | "@openzeppelin/test-helpers": "0.5.9", 19 | "bignumber.js": "9.0.1", 20 | "ganache-cli": "^6.9.1", 21 | "solidity-coverage": "0.7.11", 22 | "truffle": "5.1.49", 23 | "truffle-contract-size": "^2.0.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /scripts/genAddOwnerPayload.js: -------------------------------------------------------------------------------- 1 | const abi = require('../build/contracts/TokenSoftToken.json').abi 2 | const Web3 = require('web3') 3 | 4 | // Example script for showing how to generate the data payload for adding an address to a whitelist 5 | const run = async () => { 6 | console.log('Generating data payload to add address to owner') 7 | const web3 = new Web3() 8 | const tokenInst = new web3.eth.Contract( 9 | abi, 10 | '0x4A64515E5E1d1073e83f30cB97BEd20400b66E10' 11 | ) 12 | 13 | let newOwner = '0x1F01c8d636d2072f1948a42c8307311aF021134D' 14 | const payload = await tokenInst.methods.addOwner(newOwner).encodeABI() 15 | 16 | console.log(payload) 17 | console.log('') 18 | 19 | } 20 | 21 | run() -------------------------------------------------------------------------------- /scripts/genAddWhitelistPayload.js: -------------------------------------------------------------------------------- 1 | /* global web3, artifacts */ 2 | var SukuToken = artifacts.require('./SukuToken.sol') 3 | 4 | // Example script for showing how to generate the data payload for adding an address to a whitelist 5 | module.exports = async function (callback) { 6 | console.log('Generating data payload to add address to whitelist') 7 | const tokenInst = new web3.eth.Contract( 8 | SukuToken.abi, 9 | '0x0000000000000000000000000000000000000000' 10 | ) 11 | let payload 12 | try { 13 | let addressToBeWhitelisted = '0x0000000000000000000000000000000000000000' 14 | let whitelistId = 1 15 | payload = await tokenInst.methods.addToWhitelist(addressToBeWhitelisted, whitelistId).encodeABI() 16 | } catch (ex) { 17 | console.log(ex) 18 | } 19 | console.log(payload) 20 | console.log('') 21 | callback() 22 | } 23 | -------------------------------------------------------------------------------- /scripts/test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Exit script as soon as a command fails. 4 | set -o errexit 5 | 6 | # Executes cleanup function at script exit. 7 | trap cleanup EXIT 8 | 9 | cleanup() { 10 | # Kill the ganache instance that we started (if we started one and if it's still running). 11 | if [ -n "$ganache_pid" ] && ps -p $ganache_pid > /dev/null; then 12 | kill -9 $ganache_pid 13 | fi 14 | } 15 | 16 | if [ "$SOLIDITY_COVERAGE" = true ]; then 17 | ganache_port=8555 18 | else 19 | ganache_port=8545 20 | fi 21 | 22 | ganache_running() { 23 | nc -z localhost "$ganache_port" 24 | } 25 | 26 | start_ganache() { 27 | # We define 10 accounts with balance 1M ether, needed for high-value tests. 28 | local accounts=( 29 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501200,1000000000000000000000000" 30 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501201,1000000000000000000000000" 31 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501202,1000000000000000000000000" 32 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501203,1000000000000000000000000" 33 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501204,1000000000000000000000000" 34 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501205,1000000000000000000000000" 35 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501206,1000000000000000000000000" 36 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501207,1000000000000000000000000" 37 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501208,1000000000000000000000000" 38 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501209,1000000000000000000000000" 39 | ) 40 | 41 | if [ "$SOLIDITY_COVERAGE" = true ]; then 42 | node_modules/.bin/testrpc-sc --gasLimit 0xfffffffffff --port "$ganache_port" "${accounts[@]}" > /dev/null & 43 | else 44 | node_modules/.bin/ganache-cli --gasLimit 0xfffffffffff "${accounts[@]}" > /dev/null & 45 | fi 46 | 47 | ganache_pid=$! 48 | } 49 | 50 | if ganache_running; then 51 | echo "Using existing ganache instance" 52 | else 53 | echo "Starting our own ganache instance" 54 | start_ganache 55 | fi 56 | 57 | if [ "$SOLC_NIGHTLY" = true ]; then 58 | echo "Downloading solc nightly" 59 | wget -q https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/bin/soljson-nightly.js -O /tmp/soljson.js && find . -name soljson.js -exec cp /tmp/soljson.js {} \; 60 | fi 61 | 62 | truffle version 63 | 64 | if [ "$SOLIDITY_COVERAGE" = true ]; then 65 | node_modules/.bin/solidity-coverage 66 | 67 | if [ "$CONTINUOUS_INTEGRATION" = true ]; then 68 | cat coverage/lcov.info | node_modules/.bin/coveralls 69 | fi 70 | else 71 | node_modules/.bin/truffle test "$@" 72 | fi -------------------------------------------------------------------------------- /test/1404Restrictions.js: -------------------------------------------------------------------------------- 1 | /* global artifacts contract it assert */ 2 | const TokenSoftToken = artifacts.require('TokenSoftTokenV2') 3 | const Proxy = artifacts.require('Proxy') 4 | 5 | const SUCCESS_CODE = 0 6 | const FAILURE_NON_WHITELIST = 1 7 | const FAILURE_PAUSED = 2 8 | const SUCCESS_MESSAGE = 'SUCCESS' 9 | const FAILURE_NON_WHITELIST_MESSAGE = 'The transfer was restricted due to white list configuration.' 10 | const FAILURE_PAUSED_MESSAGE = "The transfer was restricted due to the contract being paused." 11 | const UNKNOWN_ERROR = 'Unknown Error Code' 12 | 13 | const Constants = require('./Constants') 14 | 15 | contract('1404 Restrictions', (accounts) => { 16 | let tokenInstance, tokenDeploy, proxyInstance 17 | beforeEach(async () => { 18 | tokenDeploy = await TokenSoftToken.new() 19 | proxyInstance = await Proxy.new(tokenDeploy.address) 20 | tokenInstance = await TokenSoftToken.at(proxyInstance.address) 21 | await tokenInstance.initialize( 22 | accounts[0], 23 | Constants.name, 24 | Constants.symbol, 25 | Constants.decimals, 26 | Constants.supply, 27 | true); 28 | }) 29 | 30 | it('should fail with non whitelisted accounts', async () => { 31 | 32 | // Set account 1 as an admin 33 | await tokenInstance.addWhitelister(accounts[1]) 34 | 35 | // Both not on white list - should fail 36 | let failureCode = await tokenInstance.detectTransferRestriction.call(accounts[5], accounts[6], 100) 37 | let failureMessage = await tokenInstance.messageForTransferRestriction(failureCode) 38 | assert.equal(failureCode, FAILURE_NON_WHITELIST, 'Both Non-whitelisted should get failure code') 39 | assert.equal(failureMessage, FAILURE_NON_WHITELIST_MESSAGE, 'Failure message should be valid for restriction') 40 | 41 | // Only one added to white list 20 - should fail 42 | await tokenInstance.addToWhitelist(accounts[5], 20, { from: accounts[1] }) 43 | failureCode = await tokenInstance.detectTransferRestriction.call(accounts[5], accounts[6], 100) 44 | failureMessage = await tokenInstance.messageForTransferRestriction(failureCode) 45 | assert.equal(failureCode, FAILURE_NON_WHITELIST, 'One Non-whitelisted should get failure code') 46 | assert.equal(failureMessage, FAILURE_NON_WHITELIST_MESSAGE, 'Failure message should be valid for restriction') 47 | 48 | // Second added to white list 20 - should still fail 49 | await tokenInstance.addToWhitelist(accounts[6], 20, { from: accounts[1] }) 50 | failureCode = await tokenInstance.detectTransferRestriction.call(accounts[5], accounts[6], 100) 51 | failureMessage = await tokenInstance.messageForTransferRestriction(failureCode) 52 | assert.equal(failureCode, FAILURE_NON_WHITELIST, 'Both in different whitelist should get failure code') 53 | assert.equal(failureMessage, FAILURE_NON_WHITELIST_MESSAGE, 'Failure message should be valid for restriction') 54 | 55 | // Now allow whitelist 20 to send to itself 56 | await tokenInstance.updateOutboundWhitelistEnabled(20, 20, true, { from: accounts[1] }) 57 | 58 | // Should now succeed 59 | failureCode = await tokenInstance.detectTransferRestriction.call(accounts[5], accounts[6], 100) 60 | failureMessage = await tokenInstance.messageForTransferRestriction(failureCode) 61 | assert.equal(failureCode, SUCCESS_CODE, 'Both in same whitelist should pass') 62 | assert.equal(failureMessage, SUCCESS_MESSAGE, 'Should be success') 63 | 64 | // Second moved to whitelist 30 - should fail 65 | await tokenInstance.addToWhitelist(accounts[6], 30, { from: accounts[1] }) 66 | failureCode = await tokenInstance.detectTransferRestriction.call(accounts[5], accounts[6], 100) 67 | failureMessage = await tokenInstance.messageForTransferRestriction(failureCode) 68 | assert.equal(failureCode, FAILURE_NON_WHITELIST, 'Both in different whitelist should get failure code') 69 | assert.equal(failureMessage, FAILURE_NON_WHITELIST_MESSAGE, 'Failure message should be valid for restriction') 70 | 71 | // Allow whitelist 20 to send to 30 72 | await tokenInstance.updateOutboundWhitelistEnabled(20, 30, true, { from: accounts[1] }) 73 | 74 | // Should now succeed 75 | failureCode = await tokenInstance.detectTransferRestriction.call(accounts[5], accounts[6], 100) 76 | failureMessage = await tokenInstance.messageForTransferRestriction(failureCode) 77 | assert.equal(failureCode, SUCCESS_CODE, 'Both in same whitelist should pass') 78 | assert.equal(failureMessage, SUCCESS_MESSAGE, 'Should be success') 79 | 80 | // Reversing directions should fail 81 | failureCode = await tokenInstance.detectTransferRestriction.call(accounts[6], accounts[5], 100) 82 | failureMessage = await tokenInstance.messageForTransferRestriction(failureCode) 83 | assert.equal(failureCode, FAILURE_NON_WHITELIST, 'Both in different whitelist should get failure code') 84 | assert.equal(failureMessage, FAILURE_NON_WHITELIST_MESSAGE, 'Failure message should be valid for restriction') 85 | 86 | // Disable 20 sending to 30 87 | await tokenInstance.updateOutboundWhitelistEnabled(20, 30, false, { from: accounts[1] }) 88 | 89 | // Should fail again 90 | failureCode = await tokenInstance.detectTransferRestriction.call(accounts[5], accounts[6], 100) 91 | failureMessage = await tokenInstance.messageForTransferRestriction(failureCode) 92 | assert.equal(failureCode, FAILURE_NON_WHITELIST, 'Both in different whitelist should get failure code') 93 | assert.equal(failureMessage, FAILURE_NON_WHITELIST_MESSAGE, 'Failure message should be valid for restriction') 94 | 95 | // Move second address back to whitelist 20 - should pass 96 | await tokenInstance.addToWhitelist(accounts[6], 20, { from: accounts[1] }) 97 | failureCode = await tokenInstance.detectTransferRestriction.call(accounts[5], accounts[6], 100) 98 | failureMessage = await tokenInstance.messageForTransferRestriction(failureCode) 99 | assert.equal(failureCode, SUCCESS_CODE, 'Both in same whitelist should pass') 100 | assert.equal(failureMessage, SUCCESS_MESSAGE, 'Should be success') 101 | 102 | // First removed from whitelist 103 | await tokenInstance.removeFromWhitelist(accounts[5], { from: accounts[1] }) 104 | failureCode = await tokenInstance.detectTransferRestriction.call(accounts[5], accounts[6], 100) 105 | failureMessage = await tokenInstance.messageForTransferRestriction(failureCode) 106 | assert.equal(failureCode, FAILURE_NON_WHITELIST, 'Both in different whitelist should get failure code') 107 | assert.equal(failureMessage, FAILURE_NON_WHITELIST_MESSAGE, 'Failure message should be valid for restriction') 108 | }) 109 | 110 | it('Should fail when paused', async () => { 111 | // add account as pauser 112 | await tokenInstance.addPauser(accounts[0]) 113 | 114 | // pause the contract 115 | await tokenInstance.pause() 116 | 117 | const failureCode = await tokenInstance.detectTransferRestriction.call(accounts[7], accounts[8], 100) 118 | const failureMessage = await tokenInstance.messageForTransferRestriction(failureCode) 119 | assert.equal(failureCode, FAILURE_PAUSED, 'Contract paused should get failure code') 120 | assert.equal(failureMessage, FAILURE_PAUSED_MESSAGE, 'Failure message should be valid for restriction') 121 | }) 122 | 123 | it('should allow whitelists to be removed', async () => { 124 | 125 | // Set account 1 as an admin 126 | await tokenInstance.addWhitelister(accounts[1]) 127 | 128 | // Both not on white list 129 | const failureCode = await tokenInstance.detectTransferRestriction.call(accounts[7], accounts[8], 100) 130 | const failureMessage = await tokenInstance.messageForTransferRestriction(failureCode) 131 | assert.equal(failureCode, FAILURE_NON_WHITELIST, 'Both Non-whitelisted should get failure code') 132 | assert.equal(failureMessage, FAILURE_NON_WHITELIST_MESSAGE, 'Failure message should be valid for restriction') 133 | }) 134 | 135 | it('should handle unknown error codes', async () => { 136 | 137 | const failureMessage = await tokenInstance.messageForTransferRestriction(200) 138 | assert.equal(failureMessage, UNKNOWN_ERROR, 'Should be unknown error code for restriction') 139 | }) 140 | }) 141 | -------------------------------------------------------------------------------- /test/Constants.js: -------------------------------------------------------------------------------- 1 | const BigNumber = require('bignumber.js') 2 | 3 | module.exports = { 4 | name: "Tokensoft Token", 5 | symbol: "SOFT", 6 | decimals: 18, 7 | supply: new BigNumber(10).pow(18).multipliedBy(50).multipliedBy(1000000000), 8 | } -------------------------------------------------------------------------------- /test/TokenSoftToken.js: -------------------------------------------------------------------------------- 1 | /* global artifacts contract it assert */ 2 | const BigNumber = require('bignumber.js') 3 | const { expectRevert } = require('@openzeppelin/test-helpers') 4 | 5 | const TokenSoftToken = artifacts.require('TokenSoftTokenV2') 6 | const Proxy = artifacts.require('Proxy') 7 | 8 | const Constants = require('./Constants') 9 | 10 | contract('TokenSoftTokenV2', (accounts) => { 11 | let tokenInstance, tokenDeploy, proxyInstance 12 | 13 | beforeEach(async () => { 14 | tokenDeploy = await TokenSoftToken.new() 15 | proxyInstance = await Proxy.new(tokenDeploy.address) 16 | tokenInstance = await TokenSoftToken.at(proxyInstance.address) 17 | await tokenInstance.initialize( 18 | accounts[0], 19 | Constants.name, 20 | Constants.symbol, 21 | Constants.decimals, 22 | Constants.supply, 23 | true 24 | ); 25 | }) 26 | it('should deploy', async () => { 27 | assert.equal(tokenInstance !== null, true, 'Contract should be deployed') 28 | }) 29 | 30 | it('should not allow a 0x0 address in setting proxy', async () => { 31 | await expectRevert(Proxy.new("0x0000000000000000000000000000000000000000"), "Contract Logic cannot be 0x0") 32 | }) 33 | 34 | it('should have correct details set', async () => { 35 | assert.equal(await tokenInstance.name.call(), Constants.name, 'Name should be set correctly') 36 | assert.equal(await tokenInstance.symbol.call(), Constants.symbol, 'Symbol should be set correctly') 37 | assert.equal(await tokenInstance.decimals.call(), Constants.decimals, 'Decimals should be set correctly') 38 | }) 39 | 40 | it('should mint tokens to owner', async () => { 41 | // Expected amount is decimals of (10^18) time supply of 50 billion 42 | const expectedSupply = Constants.supply 43 | const creatorBalance = new BigNumber(await tokenInstance.balanceOf(accounts[0])) 44 | 45 | // Verify the creator got all the coins 46 | assert.equal(creatorBalance.toFixed(), expectedSupply.toFixed(), 'Creator should have 50 Billion tokens (including decimals)') 47 | 48 | // Verify some other random accounts for kicks 49 | const bad1Balance = new BigNumber(await tokenInstance.balanceOf(accounts[1])) 50 | const bad2Balance = new BigNumber(await tokenInstance.balanceOf(accounts[2])) 51 | const bad3Balance = new BigNumber(await tokenInstance.balanceOf(accounts[3])) 52 | assert.equal(bad1Balance.toFixed(), '0', 'Other accounts should have 0 coins') 53 | assert.equal(bad2Balance.toFixed(), '0', 'Other accounts should have 0 coins') 54 | assert.equal(bad3Balance.toFixed(), '0', 'Other accounts should have 0 coins') 55 | }) 56 | 57 | it('should mint tokens to different owner', async () => { 58 | tokenDeploy = await TokenSoftToken.new() 59 | proxyInstance = await Proxy.new(tokenDeploy.address) 60 | tokenInstance = await TokenSoftToken.at(proxyInstance.address) 61 | await tokenInstance.initialize( 62 | accounts[1], 63 | Constants.name, 64 | Constants.symbol, 65 | Constants.decimals, 66 | Constants.supply, 67 | true 68 | ); 69 | // Expected amount is decimals of (10^18) time supply of 50 billion 70 | const expectedSupply = Constants.supply 71 | const creatorBalance = new BigNumber(await tokenInstance.balanceOf(accounts[1])) 72 | 73 | // Verify the creator got all the coins 74 | assert.equal(creatorBalance.toFixed(), expectedSupply.toFixed(), 'Owner should have 50 Billion tokens (including decimals)') 75 | 76 | // Verify some other random accounts for kicks 77 | const bad1Balance = new BigNumber(await tokenInstance.balanceOf(accounts[0])) 78 | const bad2Balance = new BigNumber(await tokenInstance.balanceOf(accounts[2])) 79 | const bad3Balance = new BigNumber(await tokenInstance.balanceOf(accounts[3])) 80 | assert.equal(bad1Balance.toFixed(), '0', 'Other accounts should have 0 coins') 81 | assert.equal(bad2Balance.toFixed(), '0', 'Other accounts should have 0 coins') 82 | assert.equal(bad3Balance.toFixed(), '0', 'Other accounts should have 0 coins') 83 | }) 84 | }) 85 | -------------------------------------------------------------------------------- /test/Transfers.js: -------------------------------------------------------------------------------- 1 | /* global artifacts contract it assert */ 2 | const { expectRevert } = require('@openzeppelin/test-helpers') 3 | const TokenSoftToken = artifacts.require('TokenSoftTokenV2') 4 | const Proxy = artifacts.require('Proxy') 5 | const Constants = require('./Constants') 6 | 7 | const invalidWhitelistMsg = "The transfer was restricted due to white list configuration." 8 | 9 | contract('Transfers', (accounts) => { 10 | const ownerAccount = accounts[0] 11 | const adminAccount = accounts[1] 12 | const whitelistedAccount = accounts[2] 13 | let tokenInstance, tokenDeploy, proxyInstance 14 | 15 | beforeEach(async () => { 16 | tokenDeploy = await TokenSoftToken.new() 17 | proxyInstance = await Proxy.new(tokenDeploy.address) 18 | tokenInstance = await TokenSoftToken.at(proxyInstance.address) 19 | await tokenInstance.initialize( 20 | accounts[0], 21 | Constants.name, 22 | Constants.symbol, 23 | Constants.decimals, 24 | Constants.supply, 25 | true); 26 | }) 27 | 28 | it('All users should be blocked from sending to non whitelisted non role-assigned accounts', async () => { 29 | await tokenInstance.addWhitelister(adminAccount, { from: ownerAccount }) 30 | await tokenInstance.addToWhitelist(whitelistedAccount, 1, { from: adminAccount }) 31 | 32 | // Sending to non whitelisted account should fail regardless of sender (except owner) 33 | await tokenInstance.transfer(accounts[7], 100, { from: ownerAccount }) 34 | await expectRevert(tokenInstance.transfer(accounts[7], 100, { from: adminAccount }), invalidWhitelistMsg) 35 | await expectRevert(tokenInstance.transfer(accounts[7], 100, { from: whitelistedAccount }), invalidWhitelistMsg) 36 | }) 37 | 38 | it('Initial transfers should fail but succeed after white listing', async () => { 39 | // Set account 1 as an admin 40 | await tokenInstance.addWhitelister(adminAccount, { from: ownerAccount }) 41 | 42 | // Whitelist and send some initial tokens to account 5 43 | await tokenInstance.addToWhitelist(accounts[5], 20, { from: adminAccount }) 44 | await tokenInstance.transfer(accounts[5], 10000, { from: ownerAccount }) 45 | 46 | // Try to send to account 2 47 | await expectRevert(tokenInstance.transfer(accounts[2], 100, { from: accounts[5] }), invalidWhitelistMsg) 48 | 49 | // Approve a transfer from account 5 and then try to spend it from account 2 50 | await tokenInstance.approve(accounts[2], 100, { from: accounts[5] }) 51 | await expectRevert(tokenInstance.transferFrom(accounts[5], accounts[2], 100, { from: accounts[2] }), invalidWhitelistMsg) 52 | 53 | // Try to send to account 2 should still fail 54 | await expectRevert(tokenInstance.transfer(accounts[2], 100, { from: accounts[5] }), invalidWhitelistMsg) 55 | await expectRevert(tokenInstance.transferFrom(accounts[5], accounts[2], 100, { from: accounts[2] }), invalidWhitelistMsg) 56 | 57 | // Move address 2 to whitelist 58 | await tokenInstance.addToWhitelist(accounts[2], 20, { from: accounts[1] }) 59 | 60 | // Try to send to account 2 should still fail 61 | await expectRevert(tokenInstance.transfer(accounts[2], 100, { from: accounts[5] }), invalidWhitelistMsg) 62 | await expectRevert(tokenInstance.transferFrom(accounts[5], accounts[2], 100, { from: accounts[2] }), invalidWhitelistMsg) 63 | 64 | // Now allow whitelist 20 to send to itself 65 | await tokenInstance.updateOutboundWhitelistEnabled(20, 20, true, { from: accounts[1] }) 66 | 67 | // Should succeed 68 | await tokenInstance.transfer(accounts[2], 100, { from: accounts[5] }) 69 | await tokenInstance.transferFrom(accounts[5], accounts[2], 100, { from: accounts[2] }) 70 | 71 | // Now account 2 should have 200 tokens 72 | const balance = await tokenInstance.balanceOf.call(accounts[2]) 73 | assert.equal(balance, '200', 'Transfers should have been sent') 74 | 75 | // Remove account 2 from the white list 76 | await tokenInstance.removeFromWhitelist(accounts[2], { from: accounts[1] }) 77 | 78 | // Should fail trying to send back to account 5 from 2 79 | await expectRevert(tokenInstance.transfer(accounts[5], 100, { from: accounts[2] }), invalidWhitelistMsg) 80 | 81 | // Should fail with approved transfer from going back to account 5 from 2 using approval 82 | await tokenInstance.approve(accounts[5], 100, { from: accounts[2] }) 83 | await expectRevert(tokenInstance.transferFrom(accounts[2], accounts[5], 100, { from: accounts[5] }), invalidWhitelistMsg) 84 | }) 85 | }) 86 | -------------------------------------------------------------------------------- /test/capabilities/Blacklistable.js: -------------------------------------------------------------------------------- 1 | /* global artifacts contract it assert */ 2 | const { expectRevert, expectEvent } = require('@openzeppelin/test-helpers') 3 | const TokenSoftTokenV2 = artifacts.require('TokenSoftTokenV2') 4 | const Proxy = artifacts.require('Proxy') 5 | const Constants = require('../Constants') 6 | 7 | const NO_WHITELIST = 0 8 | 9 | contract('Blacklistable', (accounts) => { 10 | let tokenInstance, tokenDeploy, proxyInstance 11 | 12 | beforeEach(async () => { 13 | tokenDeploy = await TokenSoftTokenV2.new() 14 | proxyInstance = await Proxy.new(tokenDeploy.address) 15 | tokenInstance = await TokenSoftTokenV2.at(proxyInstance.address) 16 | await tokenInstance.initialize( 17 | accounts[0], 18 | Constants.name, 19 | Constants.symbol, 20 | Constants.decimals, 21 | Constants.supply, 22 | false); 23 | 24 | // Enable the blacklist 25 | await tokenInstance.setBlacklistEnabled(true) 26 | }) 27 | 28 | it('should allow adding and removing an address on a blacklist', async () => { 29 | // First allow acct 1 be an administrator 30 | await tokenInstance.addBlacklister(accounts[1], { from: accounts[0] }) 31 | 32 | // Check acct 2 b should not be blacklisted by default 33 | const existingBlacklist = await tokenInstance.addressBlacklists.call(accounts[2]) 34 | assert.equal(existingBlacklist, false, 'Addresses should have no blacklist to start off with') 35 | 36 | // Add the acct 2 to blacklist - using account 1 37 | await tokenInstance.addToBlacklist(accounts[2], { from: accounts[1] }) 38 | 39 | // Validate it got updated 40 | let updatedBlacklist = await tokenInstance.addressBlacklists.call(accounts[2]) 41 | assert.equal(updatedBlacklist, true, 'Addresses should have updated blacklist') 42 | 43 | // Remove the address from list 44 | await tokenInstance.removeFromBlacklist(accounts[2], { from: accounts[1] }) 45 | 46 | // Validate it got updated 47 | updatedBlacklist = await tokenInstance.addressBlacklists.call(accounts[2]) 48 | assert.equal(updatedBlacklist, false, 'Addresses should have been removed from list') 49 | }) 50 | 51 | it('should only allow admins adding or removing on blacklists', async () => { 52 | // Non admin should fail adding to blacklist 53 | await expectRevert( 54 | tokenInstance.addToBlacklist(accounts[2], { from: accounts[4] }), 55 | "BlacklisterRole missing") 56 | 57 | // Now allow acct 4 be an administrator 58 | await tokenInstance.addBlacklister(accounts[4], { from: accounts[0] }) 59 | 60 | // Adding as admin should work 61 | await tokenInstance.addToBlacklist(accounts[2], { from: accounts[4] }) 62 | 63 | // Removing as non-admin should fail 64 | await expectRevert( 65 | tokenInstance.removeFromBlacklist(accounts[2], { from: accounts[8] }), 66 | "BlacklisterRole missing") 67 | 68 | // Removing as admin should work 69 | await tokenInstance.removeFromBlacklist(accounts[2], { from: accounts[4] }) 70 | 71 | // Now remove acct 4 from the admin list 72 | await tokenInstance.removeBlacklister(accounts[4], { from: accounts[0] }) 73 | 74 | // It should fail again now that acct 4 is non-admin 75 | await expectRevert( 76 | tokenInstance.addToBlacklist(accounts[2], { from: accounts[4] }), 77 | "BlacklisterRole missing") 78 | }) 79 | 80 | it('should validate if addresses are not on a blacklist', async () => { 81 | // First allow acct 1 be an administrator 82 | await tokenInstance.addBlacklister(accounts[1], { from: accounts[0] }) 83 | 84 | // Two addresses not on any blacklist 85 | let isValid = await tokenInstance.checkBlacklistAllowed.call(accounts[6], accounts[7]) 86 | assert.equal(isValid, true, 'Two non blacklisted addresses should be valid') 87 | 88 | // Add address 6 89 | await tokenInstance.addToBlacklist(accounts[6], { from: accounts[1] }) 90 | 91 | // Only first address on blacklist should fail 92 | isValid = await tokenInstance.checkBlacklistAllowed.call(accounts[6], accounts[7]) 93 | assert.equal(isValid, false, 'First non blacklisted addresses should not be valid') 94 | 95 | // Remove again 96 | await tokenInstance.removeFromBlacklist(accounts[6], { from: accounts[1] }) 97 | 98 | // Both should pass again 99 | isValid = await tokenInstance.checkBlacklistAllowed.call(accounts[6], accounts[7]) 100 | assert.equal(isValid, true, 'Two non blacklisted addresses should be valid') 101 | 102 | // Add address 7 103 | await tokenInstance.addToBlacklist(accounts[7], { from: accounts[1] }) 104 | 105 | // Only second address on blacklist should fail 106 | isValid = await tokenInstance.checkBlacklistAllowed.call(accounts[6], accounts[7]) 107 | assert.equal(isValid, false, 'Second non blacklisted addresses should not be valid') 108 | 109 | // Remove second addr 110 | await tokenInstance.removeFromBlacklist(accounts[7], { from: accounts[1] }) 111 | 112 | // Both should pass again 113 | isValid = await tokenInstance.checkBlacklistAllowed.call(accounts[6], accounts[7]) 114 | assert.equal(isValid, true, 'Two non blacklisted addresses should be valid') 115 | 116 | // Add both 6 and 7 117 | await tokenInstance.addToBlacklist(accounts[6], { from: accounts[1] }) 118 | await tokenInstance.addToBlacklist(accounts[7], { from: accounts[1] }) 119 | 120 | // Should be invalid 121 | isValid = await tokenInstance.checkBlacklistAllowed.call(accounts[6], accounts[7]) 122 | assert.equal(isValid, false, 'Both on same blacklist should be invalid') 123 | }) 124 | 125 | it('should trigger events', async () => { 126 | // First allow acct 1 to be an administrator 127 | await tokenInstance.addBlacklister(accounts[1], { from: accounts[0] }) 128 | 129 | // Check for initial add 130 | let ret = await tokenInstance.addToBlacklist(accounts[3], { from: accounts[1] }) 131 | expectEvent.inLogs(ret.logs, 'AddressAddedToBlacklist', { addedAddress: accounts[3], addedBy: accounts[1] }) 132 | 133 | // Removing from list should trigger removal event 134 | ret = await tokenInstance.removeFromBlacklist(accounts[3], { from: accounts[1] }) 135 | expectEvent.inLogs(ret.logs, 'AddressRemovedFromBlacklist', { removedAddress: accounts[3], removedBy: accounts[1] }) 136 | 137 | // Check disable event 138 | ret = await tokenInstance.setBlacklistEnabled(false) 139 | expectEvent.inLogs(ret.logs, 'BlacklistEnabledUpdated', { updatedBy: accounts[0], enabled: false }) 140 | 141 | // Check enable event 142 | ret = await tokenInstance.setBlacklistEnabled(true) 143 | expectEvent.inLogs(ret.logs, 'BlacklistEnabledUpdated', { updatedBy: accounts[0], enabled: true }) 144 | }) 145 | 146 | 147 | it('should not allow adding or removing address 0x0', async () => { 148 | // First allow acct 0 be an administrator 149 | await tokenInstance.addBlacklister(accounts[0], { from: accounts[0] }) 150 | 151 | await expectRevert( 152 | tokenInstance.addToBlacklist( 153 | "0x0000000000000000000000000000000000000000", 154 | { from: accounts[0] } 155 | ), 156 | "Cannot add 0x0" 157 | ) 158 | 159 | await expectRevert( 160 | tokenInstance.removeFromBlacklist( 161 | "0x0000000000000000000000000000000000000000", 162 | { from: accounts[0] } 163 | ), 164 | "Cannot remove 0x0" 165 | ) 166 | }) 167 | 168 | it('should allow disabling and re-enabling the blacklist logic', async () => { 169 | // First allow acct 1 be blacklister 170 | await tokenInstance.addBlacklister(accounts[1], { from: accounts[0] }) 171 | 172 | // Send some tokens to account 2 173 | await tokenInstance.transfer(accounts[2], 1000) 174 | 175 | // Verify accounts can transfer 176 | await tokenInstance.transfer(accounts[3], 100, { from: accounts[2] }) 177 | 178 | // Add account 2 to the black list 179 | await tokenInstance.addToBlacklist(accounts[2], { from: accounts[1] }) 180 | 181 | // Verify transfer fails 182 | await expectRevert( 183 | tokenInstance.transfer(accounts[3], 100, { from: accounts[2] }), 184 | "Restricted due to blacklist" 185 | ) 186 | 187 | // Turn it off 188 | await tokenInstance.setBlacklistEnabled(false) 189 | 190 | // Validate it works 191 | await tokenInstance.transfer(accounts[3], 100, { from: accounts[2] }) 192 | 193 | // Turn it on 194 | await tokenInstance.setBlacklistEnabled(true) 195 | 196 | // Verify accounts can't transfer 197 | await expectRevert( 198 | tokenInstance.transfer(accounts[3], 100, { from: accounts[2] }), 199 | "Restricted due to blacklist" 200 | ) 201 | }) 202 | 203 | it('should not allow non-owner disabling the blacklist logic', async () => { 204 | await expectRevert( 205 | tokenInstance.setBlacklistEnabled(false, { from: accounts[2] }), 206 | "OwnerRole: caller does not have the Owner role" 207 | ) 208 | }) 209 | 210 | it('should not allow removing an address that is not blacklisted or adding already blacklisted', async () => { 211 | // First allow acct 1 be blacklister 212 | await tokenInstance.addBlacklister(accounts[1], { from: accounts[0] }) 213 | 214 | await expectRevert( 215 | tokenInstance.removeFromBlacklist(accounts[2], {from: accounts[1]}), 216 | "Not on list" 217 | ) 218 | 219 | // Blacklist acct 3 220 | await tokenInstance.addToBlacklist(accounts[3], {from: accounts[1]}) 221 | // Second time should fail 222 | await expectRevert( 223 | tokenInstance.addToBlacklist(accounts[3], {from: accounts[1]}), 224 | "Already on list" 225 | ) 226 | 227 | }) 228 | }) 229 | -------------------------------------------------------------------------------- /test/capabilities/Burnable.js: -------------------------------------------------------------------------------- 1 | /* global artifacts contract it assert */ 2 | const { expectRevert, expectEvent } = require('@openzeppelin/test-helpers') 3 | const TokenSoftToken = artifacts.require('TokenSoftTokenV2') 4 | const Proxy = artifacts.require('Proxy') 5 | const Constants = require('../Constants') 6 | const BigNumber = require('bignumber.js') 7 | 8 | /** 9 | * Sanity check for transferring ownership. Most logic is fully tested in OpenZeppelin lib. 10 | */ 11 | contract('Burnable', (accounts) => { 12 | // set up account roles 13 | const ownerAccount = accounts[0] 14 | const adminAccount = accounts[1] 15 | const whitelistedAccount = accounts[2] 16 | const nonWhitelistedAccount = accounts[3] 17 | const burneeAccount = accounts[4] 18 | let tokenInstance, tokenDeploy, proxyInstance 19 | 20 | beforeEach(async () => { 21 | tokenDeploy = await TokenSoftToken.new() 22 | proxyInstance = await Proxy.new(tokenDeploy.address) 23 | tokenInstance = await TokenSoftToken.at(proxyInstance.address) 24 | await tokenInstance.initialize( 25 | accounts[0], 26 | Constants.name, 27 | Constants.symbol, 28 | Constants.decimals, 29 | Constants.supply, 30 | true); 31 | }) 32 | 33 | it('Admin should be able to burn tokens from any account', async () => { 34 | // set up the amounts to test 35 | const transferAmount = 100 36 | const burnAmount = 25 37 | const afterBurnAmount = transferAmount - burnAmount 38 | 39 | await tokenInstance.addBurner(adminAccount) 40 | 41 | // transfer tokens from owner account to burnee accounts 42 | await tokenInstance.transfer(burneeAccount, transferAmount, { from: ownerAccount }) 43 | 44 | // get the initial balances of the user and admin account and confirm balances 45 | const initialSupply = await tokenInstance.totalSupply() 46 | const burneeBalance = await tokenInstance.balanceOf(burneeAccount) 47 | assert.equal(burneeBalance, transferAmount, 'User balance should intially be equal to the transfer amount') 48 | 49 | // burn tokens from the user 50 | await tokenInstance.burn(burneeAccount, burnAmount, { from: adminAccount }) 51 | 52 | // get the updated balances for admin and user and confirm they are updated 53 | const postBurnSupply = await tokenInstance.totalSupply() 54 | const burneeBalanceBurned = await tokenInstance.balanceOf(burneeAccount) 55 | assert.equal(burneeBalanceBurned, afterBurnAmount, 'User balance should be reduced after tokens are burnd') 56 | assert.equal(new BigNumber(initialSupply).minus(burnAmount).toFixed(), new BigNumber(postBurnSupply).toFixed(), 'Total supply post mint should be updated with additional minted amount') 57 | }) 58 | 59 | it('Non admins should not be able to burn tokens', async () => { 60 | // set up the amounts to test 61 | const transferAmount = 100 62 | const burnAmount = 25 63 | 64 | // transfer tokens from owner account to burnee account 65 | await tokenInstance.transfer(burneeAccount, transferAmount, { from: ownerAccount }) 66 | 67 | // attempt to burn tokens from owner, whitelisted, and non whitelisted accounts; should all fail 68 | await expectRevert(tokenInstance.burn(burneeAccount, burnAmount, { from: ownerAccount }), "BurnerRole: caller does not have the Burner role") 69 | await expectRevert(tokenInstance.burn(burneeAccount, burnAmount, { from: whitelistedAccount }), "BurnerRole: caller does not have the Burner role") 70 | await expectRevert(tokenInstance.burn(burneeAccount, burnAmount, { from: nonWhitelistedAccount }), "BurnerRole: caller does not have the Burner role") 71 | }) 72 | 73 | it('should emit event when tokens are burnd', async () => { 74 | await tokenInstance.addBurner(adminAccount) 75 | 76 | // set up the amounts to test 77 | const transferAmount = 100 78 | const burnAmount = '25' 79 | 80 | // transfer tokens from owner account to burnee accounts 81 | await tokenInstance.transfer(burneeAccount, transferAmount, { from: ownerAccount }) 82 | 83 | // burn tokens from the user 84 | const { logs } = await tokenInstance.burn(burneeAccount, burnAmount, { from: adminAccount }) 85 | 86 | expectEvent.inLogs(logs, 'Burn', { burner: adminAccount, from: burneeAccount, amount: burnAmount }) 87 | }) 88 | }) 89 | -------------------------------------------------------------------------------- /test/capabilities/Mintable.js: -------------------------------------------------------------------------------- 1 | /* global artifacts contract it assert */ 2 | const { expectRevert, expectEvent } = require('@openzeppelin/test-helpers') 3 | const TokenSoftToken = artifacts.require('TokenSoftTokenV2') 4 | const Proxy = artifacts.require('Proxy') 5 | const Constants = require('../Constants') 6 | const BigNumber = require('bignumber.js') 7 | 8 | /** 9 | * Sanity check for transferring ownership. Most logic is fully tested in OpenZeppelin lib. 10 | */ 11 | contract('Mintable', (accounts) => { 12 | // set up account roles 13 | const ownerAccount = accounts[0] 14 | const adminAccount = accounts[1] 15 | const whitelistedAccount = accounts[2] 16 | const nonWhitelistedAccount = accounts[3] 17 | const minteeAccount = accounts[4] 18 | let tokenInstance, tokenDeploy, proxyInstance 19 | 20 | beforeEach(async () => { 21 | tokenDeploy = await TokenSoftToken.new() 22 | proxyInstance = await Proxy.new(tokenDeploy.address) 23 | tokenInstance = await TokenSoftToken.at(proxyInstance.address) 24 | await tokenInstance.initialize( 25 | accounts[0], 26 | Constants.name, 27 | Constants.symbol, 28 | Constants.decimals, 29 | Constants.supply, 30 | true); 31 | }) 32 | 33 | it('Owner should be able to mint tokenst', async () => { 34 | // Add minter 35 | await tokenInstance.addMinter(ownerAccount) 36 | 37 | // set up the amounts to test 38 | const mintAmount = '100' 39 | 40 | // get initial account balance and token supply 41 | const initialSupply = await tokenInstance.totalSupply() 42 | const accountBalance = await tokenInstance.balanceOf(whitelistedAccount) 43 | assert.equal(0, accountBalance, 'Account should have initial balance of 0') 44 | 45 | // mint tokens 46 | await tokenInstance.mint(whitelistedAccount, mintAmount) 47 | 48 | // confirm account balance and total supply are updated 49 | const postMintSupply = await tokenInstance.totalSupply() 50 | const postMintAccountBalance = await tokenInstance.balanceOf(whitelistedAccount) 51 | assert.equal(mintAmount, postMintAccountBalance, 'Account balance should equal mint amount post mint') 52 | assert.equal(new BigNumber(initialSupply).plus(mintAmount).toFixed(), new BigNumber(postMintSupply).toFixed(), 'Total supply post mint should be updated with additional minted amount') 53 | }) 54 | 55 | it('Non-MinterRole accounts should not be able to mint tokens to any account', async () => { 56 | // set up the amounts to test 57 | const mintAmount = '100' 58 | 59 | // attempt to mint tokens 60 | await expectRevert(tokenInstance.mint(minteeAccount, mintAmount, { from: adminAccount }), "MinterRole: caller does not have the Minter role") 61 | }) 62 | 63 | it('should emit event when tokens are minted', async () => { 64 | // Add minter 65 | await tokenInstance.addMinter(ownerAccount) 66 | 67 | // set up the amounts to test 68 | const mintAmount = '100' 69 | 70 | // mint tokens to mintee account 71 | const { logs } = await tokenInstance.mint(minteeAccount, mintAmount, { from: ownerAccount }) 72 | 73 | expectEvent.inLogs(logs, 'Mint', { minter: ownerAccount, to: minteeAccount, amount: mintAmount }) 74 | }) 75 | }) 76 | -------------------------------------------------------------------------------- /test/capabilities/Pausable.js: -------------------------------------------------------------------------------- 1 | /* global artifacts contract it assert */ 2 | const { expectRevert, expectEvent } = require('@openzeppelin/test-helpers') 3 | const TokenSoftToken = artifacts.require('TokenSoftTokenV2') 4 | const Proxy = artifacts.require('Proxy') 5 | const Constants = require('../Constants') 6 | 7 | /** 8 | * Sanity check for transferring ownership. Most logic is fully tested in OpenZeppelin lib. 9 | */ 10 | contract('Pauseable', (accounts) => { 11 | // set up account roles 12 | const ownerAccount = accounts[0] 13 | const adminAccount = accounts[1] 14 | const whitelistedAccount = accounts[2] 15 | const whitelistedAccount2 = accounts[3] 16 | const nonWhitelistedAccount = accounts[4] 17 | let tokenInstance, tokenDeploy, proxyInstance 18 | 19 | beforeEach(async () => { 20 | tokenDeploy = await TokenSoftToken.new() 21 | proxyInstance = await Proxy.new(tokenDeploy.address) 22 | tokenInstance = await TokenSoftToken.at(proxyInstance.address) 23 | await tokenInstance.initialize( 24 | accounts[0], 25 | Constants.name, 26 | Constants.symbol, 27 | Constants.decimals, 28 | Constants.supply, 29 | true); 30 | }) 31 | 32 | it('Owner should be able to pause contract', async () => { 33 | await tokenInstance.addPauser(ownerAccount) 34 | 35 | // get initial pause state 36 | const isPaused = await tokenInstance.paused() 37 | assert.equal(isPaused, false, 'Contract should not be paused initially') 38 | 39 | // pause the contract 40 | await tokenInstance.pause({ from: ownerAccount }) 41 | 42 | const isPausedPostPause = await tokenInstance.paused() 43 | assert.equal(isPausedPostPause, true, 'Contract should be paused') 44 | }) 45 | 46 | it('Only owner should be able to pause contract', async () => { 47 | // pause the contract 48 | await expectRevert(tokenInstance.pause({ from: adminAccount }), "PauserRole: caller does not have the Pauser role") 49 | await expectRevert(tokenInstance.pause({ from: whitelistedAccount }), "PauserRole: caller does not have the Pauser role") 50 | await expectRevert(tokenInstance.pause({ from: nonWhitelistedAccount }), "PauserRole: caller does not have the Pauser role") 51 | 52 | }) 53 | 54 | it('Paused contract should prevent all transfers', async () => { 55 | // set up the amounts to test 56 | const transferAmount = 100 57 | 58 | // add admin and whitelist accounts 59 | await tokenInstance.addPauser(ownerAccount) 60 | await tokenInstance.addWhitelister(adminAccount, { from: ownerAccount }) 61 | await tokenInstance.addToWhitelist(whitelistedAccount, 1, { from: adminAccount }) 62 | await tokenInstance.addToWhitelist(whitelistedAccount2, 1, { from: adminAccount }) 63 | 64 | // transfer tokens to whitelisted account 65 | await tokenInstance.transfer(whitelistedAccount, transferAmount, { from: ownerAccount }) 66 | await tokenInstance.updateOutboundWhitelistEnabled(1, 1, true, { from: adminAccount }) 67 | 68 | // transfer tokens between whitelisted accounts, should succeed 69 | await tokenInstance.transfer(whitelistedAccount2, transferAmount/2, { from: whitelistedAccount }) 70 | 71 | // pause the contract 72 | await tokenInstance.pause({ from: ownerAccount }) 73 | 74 | // transfers while paused should fail 75 | await expectRevert( 76 | tokenInstance.transfer(whitelistedAccount2, transferAmount/2, { from: whitelistedAccount }), 77 | "The transfer was restricted due to the contract being paused." 78 | ) 79 | await expectRevert( 80 | tokenInstance.transfer(whitelistedAccount, transferAmount, { from: ownerAccount }), 81 | "The transfer was restricted due to the contract being paused." 82 | ) 83 | 84 | }) 85 | 86 | it('should emit event when contract is unpaused', async () => { 87 | await tokenInstance.addPauser(ownerAccount) 88 | 89 | // pause the contract 90 | const { logs } = await tokenInstance.pause({ from: ownerAccount }) 91 | 92 | expectEvent.inLogs(logs, 'Paused', { account: ownerAccount }) 93 | }) 94 | 95 | it('should emit event when contract is unpaused', async () => { 96 | await tokenInstance.addPauser(ownerAccount) 97 | 98 | // pause the contract 99 | await tokenInstance.pause({ from: ownerAccount }) 100 | // unpause the contract 101 | const { logs } = await tokenInstance.unpause({ from: ownerAccount }) 102 | 103 | expectEvent.inLogs(logs, 'Unpaused', { account: ownerAccount }) 104 | }) 105 | 106 | it('should not allow calls when already in the state', async () => { 107 | await tokenInstance.addPauser(ownerAccount) 108 | 109 | // pause the contract and should fail second time 110 | await tokenInstance.pause({ from: ownerAccount }) 111 | await expectRevert( 112 | tokenInstance.pause({ from: ownerAccount }), 113 | "Pausable: paused" 114 | ) 115 | 116 | // pause the contract and should fail second time 117 | await tokenInstance.unpause({ from: ownerAccount }) 118 | await expectRevert( 119 | tokenInstance.unpause({ from: ownerAccount }), 120 | "Pausable: not paused" 121 | ) 122 | }) 123 | }) -------------------------------------------------------------------------------- /test/capabilities/Proxiable.js: -------------------------------------------------------------------------------- 1 | /* global artifacts contract it assert */ 2 | const { expectRevert, expectEvent } = require('@openzeppelin/test-helpers') 3 | const TokenSoftToken = artifacts.require('TokenSoftTokenV2') 4 | const TokenSoftTokenEscrow = artifacts.require('TokenSoftTokenEscrow') 5 | const TokenSoftTokenEscrowNotProxiable = artifacts.require('TokenSoftTokenEscrowNotProxiable') 6 | const Proxy = artifacts.require('Proxy') 7 | const Constants = require('../Constants') 8 | 9 | /** 10 | * Sanity check for transferring ownership. Most logic is fully tested in OpenZeppelin lib. 11 | */ 12 | contract('Upgradeable', (accounts) => { 13 | // set up account roles 14 | const ownerAccount = accounts[0] 15 | const adminAccount = accounts[1] 16 | const whitelistedAccount = accounts[2] 17 | let tokenInstance, tokenEscrowInstance, tokenDeploy, tokenEscrowDeploy, tokenEscrowNotProxiableDeploy, proxyInstance 18 | 19 | beforeEach(async () => { 20 | tokenDeploy = await TokenSoftToken.new() 21 | tokenEscrowDeploy = await TokenSoftTokenEscrow.new() 22 | tokenEscrowNotProxiableDeploy = await TokenSoftTokenEscrowNotProxiable.new() 23 | proxyInstance = await Proxy.new(tokenDeploy.address) 24 | tokenInstance = await TokenSoftToken.at(proxyInstance.address) 25 | tokenEscrowInstance = await TokenSoftTokenEscrow.at(proxyInstance.address) 26 | await tokenInstance.initialize( 27 | accounts[0], 28 | Constants.name, 29 | Constants.symbol, 30 | Constants.decimals, 31 | Constants.supply, 32 | true); 33 | }) 34 | 35 | it('Can upgrade to proxiable contract', async () => { 36 | // update the code address to the escrow logic 37 | await tokenInstance.updateCodeAddress(tokenEscrowDeploy.address) 38 | const logicAddress = await tokenEscrowInstance.getLogicAddress() 39 | assert.equal(logicAddress, tokenEscrowDeploy.address, 'Logic contract address should be changed after update') 40 | }) 41 | 42 | it('Cannot upgrade to non proxiable contract', async () => { 43 | // update the code address to the escrow logic 44 | await expectRevert.unspecified(tokenInstance.updateCodeAddress(tokenEscrowNotProxiableDeploy.address)) 45 | }) 46 | 47 | it('Upgrading contract fires event', async () => { 48 | // update the code address to the escrow logic 49 | const { logs } = await tokenInstance.updateCodeAddress(tokenEscrowDeploy.address) 50 | expectEvent.inLogs(logs, 'CodeAddressUpdated', { 51 | newAddress: tokenEscrowDeploy.address 52 | }) 53 | }) 54 | 55 | it('Contract cannot be upgraded by non owner', async () => { 56 | // update the code address to the escrow logic 57 | await expectRevert( 58 | tokenInstance.updateCodeAddress(tokenEscrowDeploy.address, {from: accounts[1]}), 59 | "OwnerRole: caller does not have the Owner role" 60 | ) 61 | }) 62 | 63 | it('Transfer rules can be upgraded', async () => { 64 | // set up the amounts to test 65 | const transferAmount = 100 66 | 67 | // add account2 to admin role 68 | await tokenInstance.addWhitelister(adminAccount, { from: ownerAccount }) 69 | await tokenInstance.addToWhitelist(whitelistedAccount, 1, { from: adminAccount }) 70 | 71 | // transfer tokens from owner account to revokee accounts 72 | await tokenInstance.transfer(whitelistedAccount, transferAmount, { from: ownerAccount }) 73 | 74 | // get the balance after transfer 75 | const whitelistedBalance = await tokenInstance.balanceOf(whitelistedAccount) 76 | 77 | // update the code address to the escrow logic 78 | await tokenInstance.updateCodeAddress(tokenEscrowDeploy.address) 79 | 80 | // Add admin as escrower 81 | await tokenEscrowInstance.addEscrower(adminAccount) 82 | 83 | // transfer tokens from owner account to revokee accounts 84 | await tokenEscrowInstance.transfer(whitelistedAccount, transferAmount, { from: ownerAccount }) 85 | 86 | const whitelistedBalanceAfterUpdateAndTransfer = await tokenEscrowInstance.balanceOf(whitelistedAccount) 87 | assert.equal(whitelistedBalanceAfterUpdateAndTransfer.toString(), whitelistedBalance.toString(), 'User balance should be the same after update and transfer') 88 | 89 | await tokenEscrowInstance.approveTransferProposal(0, {from: adminAccount}) 90 | const whitelistedBalanceAfterUpdateAndTransferApproval = await tokenEscrowInstance.balanceOf(whitelistedAccount) 91 | assert.notEqual(whitelistedBalanceAfterUpdateAndTransfer.toString(), whitelistedBalanceAfterUpdateAndTransferApproval.toString(), 'User balance should be updated after update and transfer approval') 92 | }) 93 | 94 | it('Balance are maintained after upgrade', async () => { 95 | // set up the amounts to test 96 | const transferAmount = 100 97 | 98 | // add account2 to admin role 99 | await tokenInstance.addWhitelister(adminAccount, { from: ownerAccount }) 100 | await tokenInstance.addToWhitelist(whitelistedAccount, 1, { from: adminAccount }) 101 | 102 | // transfer tokens from owner account to revokee accounts 103 | await tokenInstance.transfer(whitelistedAccount, transferAmount, { from: ownerAccount }) 104 | 105 | // get the balance after transfer 106 | const whitelistedBalance = await tokenInstance.balanceOf(whitelistedAccount) 107 | assert.equal(whitelistedBalance, transferAmount, 'User balance should intially be equal to the transfer amount') 108 | 109 | // update the code address to the escrow logic 110 | await tokenInstance.updateCodeAddress(tokenEscrowDeploy.address) 111 | tokenEscrowInstance = await TokenSoftTokenEscrow.at(proxyInstance.address) 112 | 113 | const whitelistedBalanceAfterUpdate = await tokenEscrowInstance.balanceOf(whitelistedAccount) 114 | // confirm balances are unchanged 115 | assert.equal(whitelistedBalance.toString(), whitelistedBalanceAfterUpdate.toString(), 'User balance should be the same after update') 116 | 117 | }) 118 | 119 | }) -------------------------------------------------------------------------------- /test/capabilities/Revocable.js: -------------------------------------------------------------------------------- 1 | /* global artifacts contract it assert */ 2 | const { expectRevert, expectEvent } = require('@openzeppelin/test-helpers') 3 | const TokenSoftToken = artifacts.require('TokenSoftTokenV2') 4 | const Proxy = artifacts.require('Proxy') 5 | const Constants = require('../Constants') 6 | 7 | /** 8 | * Sanity check for transferring ownership. Most logic is fully tested in OpenZeppelin lib. 9 | */ 10 | contract('Revocable', (accounts) => { 11 | // set up account roles 12 | const ownerAccount = accounts[0] 13 | const adminAccount = accounts[1] 14 | const whitelistedAccount = accounts[2] 15 | const nonWhitelistedAccount = accounts[3] 16 | const revokeeAccount = accounts[4] 17 | let tokenInstance, tokenDeploy, proxyInstance 18 | 19 | beforeEach(async () => { 20 | tokenDeploy = await TokenSoftToken.new() 21 | proxyInstance = await Proxy.new(tokenDeploy.address) 22 | tokenInstance = await TokenSoftToken.at(proxyInstance.address) 23 | await tokenInstance.initialize( 24 | accounts[0], 25 | Constants.name, 26 | Constants.symbol, 27 | Constants.decimals, 28 | Constants.supply, 29 | true); 30 | }) 31 | 32 | it('Admin should be able to revoke tokens from any account', async () => { 33 | // set up the amounts to test 34 | const transferAmount = 100 35 | const revokeAmount = 25 36 | const afterRevokeAmount = transferAmount - revokeAmount 37 | 38 | await tokenInstance.addRevoker(adminAccount) 39 | 40 | // transfer tokens from owner account to revokee accounts 41 | await tokenInstance.transfer(revokeeAccount, transferAmount, { from: ownerAccount }) 42 | 43 | // get the initial balances of the user and admin account and confirm balances 44 | const revokeeBalance = await tokenInstance.balanceOf(revokeeAccount) 45 | const adminBalance = await tokenInstance.balanceOf(adminAccount) 46 | assert.equal(revokeeBalance, transferAmount, 'User balance should intially be equal to the transfer amount') 47 | assert.equal(adminBalance, 0, 'Admin balance should intially be 0') 48 | 49 | // revoke tokens from the user 50 | await tokenInstance.revoke(revokeeAccount, revokeAmount, { from: adminAccount }) 51 | 52 | // get the updated balances for admin and user and confirm they are updated 53 | const revokeeBalanceRevoked = await tokenInstance.balanceOf(revokeeAccount) 54 | const adminBalanceRevoked = await tokenInstance.balanceOf(adminAccount) 55 | assert.equal(revokeeBalanceRevoked, afterRevokeAmount, 'User balance should be reduced after tokens are revoked') 56 | assert.equal(adminBalanceRevoked, revokeAmount, 'Admin balance should be increased after tokens are revoked') 57 | }) 58 | 59 | it('Non admins should not be able to revoke tokens', async () => { 60 | // set up the amounts to test 61 | const transferAmount = 100 62 | const revokeAmount = 25 63 | 64 | // transfer tokens from owner account to revokee account 65 | await tokenInstance.transfer(revokeeAccount, transferAmount, { from: ownerAccount }) 66 | 67 | // attempt to revoke tokens from owner, whitelisted, and non whitelisted accounts; should all fail 68 | await expectRevert(tokenInstance.revoke(revokeeAccount, revokeAmount, { from: ownerAccount }), "RevokerRole: caller does not have the Revoker role") 69 | await expectRevert(tokenInstance.revoke(revokeeAccount, revokeAmount, { from: whitelistedAccount }), "RevokerRole: caller does not have the Revoker role") 70 | await expectRevert(tokenInstance.revoke(revokeeAccount, revokeAmount, { from: nonWhitelistedAccount }), "RevokerRole: caller does not have the Revoker role") 71 | }) 72 | 73 | it('should emit event when tokens are revoked', async () => { 74 | await tokenInstance.addRevoker(adminAccount) 75 | 76 | // set up the amounts to test 77 | const transferAmount = 100 78 | const revokeAmount = '25' 79 | 80 | // transfer tokens from owner account to revokee accounts 81 | await tokenInstance.transfer(revokeeAccount, transferAmount, { from: ownerAccount }) 82 | 83 | // revoke tokens from the user 84 | const { logs } = await tokenInstance.revoke(revokeeAccount, revokeAmount, { from: adminAccount }) 85 | 86 | expectEvent.inLogs(logs, 'Revoke', { revoker: adminAccount, from: revokeeAccount, amount: revokeAmount }) 87 | }) 88 | }) 89 | -------------------------------------------------------------------------------- /test/capabilities/RevocableToAddress.js: -------------------------------------------------------------------------------- 1 | /* global artifacts contract it assert */ 2 | const { expectRevert, expectEvent } = require('@openzeppelin/test-helpers') 3 | const TokenSoftToken = artifacts.require('TokenSoftTokenV2') 4 | const Proxy = artifacts.require('Proxy') 5 | const Constants = require('../Constants') 6 | 7 | /** 8 | * Sanity check for transferring ownership. Most logic is fully tested in OpenZeppelin lib. 9 | */ 10 | contract('RevocableToAddress', (accounts) => { 11 | // set up account roles 12 | const ownerAccount = accounts[0] 13 | const adminAccount = accounts[1] 14 | const whitelistedAccount = accounts[2] 15 | const nonWhitelistedAccount = accounts[3] 16 | const revokeeAccount = accounts[4] 17 | const revokedToAccount = accounts[5] 18 | let tokenInstance, tokenDeploy, proxyInstance 19 | 20 | beforeEach(async () => { 21 | tokenDeploy = await TokenSoftToken.new() 22 | proxyInstance = await Proxy.new(tokenDeploy.address) 23 | tokenInstance = await TokenSoftToken.at(proxyInstance.address) 24 | await tokenInstance.initialize( 25 | accounts[0], 26 | Constants.name, 27 | Constants.symbol, 28 | Constants.decimals, 29 | Constants.supply, 30 | true); 31 | }) 32 | 33 | it('Admin should be able to revoke tokens from any account to another', async () => { 34 | // set up the amounts to test 35 | const transferAmount = 100 36 | const revokeAmount = 25 37 | const afterRevokeAmount = transferAmount - revokeAmount 38 | 39 | await tokenInstance.addRevoker(adminAccount) 40 | 41 | // transfer tokens from owner account to revokee accounts 42 | await tokenInstance.transfer(revokeeAccount, transferAmount, { from: ownerAccount }) 43 | 44 | // get the initial balances of the user and admin account and confirm balances 45 | const revokeeBalance = await tokenInstance.balanceOf(revokeeAccount) 46 | const revokedToBalance = await tokenInstance.balanceOf(revokedToAccount) 47 | assert.equal(revokeeBalance, transferAmount, 'User balance should intially be equal to the transfer amount') 48 | assert.equal(revokedToBalance, 0, 'Target balance should intially be 0') 49 | 50 | // revoke tokens from the user 51 | await tokenInstance.revokeToAddress(revokeeAccount, revokedToAccount, revokeAmount, { from: adminAccount }) 52 | 53 | // get the updated balances for admin and user and confirm they are updated 54 | const revokeeBalanceRevoked = await tokenInstance.balanceOf(revokeeAccount) 55 | const revokedToBalanceAfter = await tokenInstance.balanceOf(revokedToAccount) 56 | assert.equal(revokeeBalanceRevoked, afterRevokeAmount, 'User balance should be reduced after tokens are revoked') 57 | assert.equal(revokedToBalanceAfter, revokeAmount, 'Target balance should be increased after tokens are revoked') 58 | }) 59 | 60 | it('Non admins should not be able to revoke tokens', async () => { 61 | // set up the amounts to test 62 | const transferAmount = 100 63 | const revokeAmount = 25 64 | 65 | // transfer tokens from owner account to revokee account 66 | await tokenInstance.transfer(revokeeAccount, transferAmount, { from: ownerAccount }) 67 | 68 | // attempt to revoke tokens from owner, whitelisted, and non whitelisted accounts; should all fail 69 | await expectRevert(tokenInstance.revokeToAddress(revokeeAccount, revokedToAccount, revokeAmount, { from: ownerAccount }), "RevokerRole: caller does not have the Revoker role") 70 | await expectRevert(tokenInstance.revokeToAddress(revokeeAccount, revokedToAccount, revokeAmount, { from: whitelistedAccount }), "RevokerRole: caller does not have the Revoker role") 71 | await expectRevert(tokenInstance.revokeToAddress(revokeeAccount, revokedToAccount, revokeAmount, { from: nonWhitelistedAccount }), "RevokerRole: caller does not have the Revoker role") 72 | }) 73 | 74 | it('should emit event when tokens are revoked', async () => { 75 | await tokenInstance.addRevoker(adminAccount) 76 | 77 | // set up the amounts to test 78 | const transferAmount = 100 79 | const revokeAmount = '25' 80 | 81 | // transfer tokens from owner account to revokee accounts 82 | await tokenInstance.transfer(revokeeAccount, transferAmount, { from: ownerAccount }) 83 | 84 | // revoke tokens from the user 85 | const { logs } = await tokenInstance.revokeToAddress(revokeeAccount, revokedToAccount, revokeAmount, { from: adminAccount }) 86 | 87 | expectEvent.inLogs(logs, 'RevokeToAddress', { revoker: adminAccount, from: revokeeAccount, to: revokedToAccount, amount: revokeAmount }) 88 | }) 89 | }) 90 | -------------------------------------------------------------------------------- /test/capabilities/Whitelistable.js: -------------------------------------------------------------------------------- 1 | /* global artifacts contract it assert */ 2 | const { expectRevert, expectEvent } = require('@openzeppelin/test-helpers') 3 | const TokenSoftToken = artifacts.require('TokenSoftTokenV2') 4 | const Proxy = artifacts.require('Proxy') 5 | const Constants = require('../Constants') 6 | 7 | const NO_WHITELIST = 0 8 | 9 | contract('Whitelistable', (accounts) => { 10 | let tokenInstance, tokenDeploy, proxyInstance 11 | 12 | beforeEach(async () => { 13 | tokenDeploy = await TokenSoftToken.new() 14 | proxyInstance = await Proxy.new(tokenDeploy.address) 15 | tokenInstance = await TokenSoftToken.at(proxyInstance.address) 16 | await tokenInstance.initialize( 17 | accounts[0], 18 | Constants.name, 19 | Constants.symbol, 20 | Constants.decimals, 21 | Constants.supply, 22 | true); 23 | }) 24 | 25 | it('should allow adding and removing an address to a whitelist', async () => { 26 | // First allow acct 1 be an administrator 27 | await tokenInstance.addWhitelister(accounts[1], { from: accounts[0] }) 28 | 29 | // Check acct 2 whitelist should default to NONE 30 | const existingWhitelist = await tokenInstance.addressWhitelists.call(accounts[2]) 31 | assert.equal(existingWhitelist, NO_WHITELIST, 'Addresses should have no whitelist to start off with') 32 | 33 | // Add the acct 2 to whitelist 10 - using account 1 34 | await tokenInstance.addToWhitelist(accounts[2], 10, { from: accounts[1] }) 35 | 36 | // Validate it got updated 37 | let updatedWhitelist = await tokenInstance.addressWhitelists.call(accounts[2]) 38 | assert.equal(updatedWhitelist, 10, 'Addresses should have updated whitelist') 39 | 40 | // Update the whitelist for acct 2 to id 20 41 | await tokenInstance.addToWhitelist(accounts[2], 20, { from: accounts[1] }) 42 | 43 | // Validate it got updated 44 | updatedWhitelist = await tokenInstance.addressWhitelists.call(accounts[2]) 45 | assert.equal(updatedWhitelist, 20, 'Addresses should have updated whitelist after existing was changed') 46 | 47 | // Remove the address from whitelist 48 | await tokenInstance.removeFromWhitelist(accounts[2], { from: accounts[1] }) 49 | 50 | // Validate it got updated 51 | updatedWhitelist = await tokenInstance.addressWhitelists.call(accounts[2]) 52 | assert.equal(updatedWhitelist, NO_WHITELIST, 'Addresses should have been removed from whitelist') 53 | }) 54 | 55 | it('should only allow admins adding or removing on whitelists', async () => { 56 | // Non admin should fail adding to white list 57 | await expectRevert( 58 | tokenInstance.addToWhitelist(accounts[2], 10, { from: accounts[4] }), 59 | "WhitelisterRole: caller does not have the Whitelister role") 60 | 61 | // Now allow acct 4 be an administrator 62 | await tokenInstance.addWhitelister(accounts[4], { from: accounts[0] }) 63 | 64 | // Adding as admin should work 65 | await tokenInstance.addToWhitelist(accounts[2], 10, { from: accounts[4] }) 66 | 67 | // Removing as non-admin should fail 68 | await expectRevert( 69 | tokenInstance.removeFromWhitelist(accounts[2], { from: accounts[8] }), 70 | "WhitelisterRole: caller does not have the Whitelister role") 71 | 72 | // Removing as admin should work 73 | await tokenInstance.removeFromWhitelist(accounts[2], { from: accounts[4] }) 74 | 75 | // Now remove acct 4 from the admin list 76 | await tokenInstance.removeWhitelister(accounts[4], { from: accounts[0] }) 77 | 78 | // It should fail again now that acct 4 is non-admin 79 | await expectRevert( 80 | tokenInstance.addToWhitelist(accounts[2], 10, { from: accounts[4] }), 81 | "WhitelisterRole: caller does not have the Whitelister role") 82 | }) 83 | 84 | it('should validate if addresses are not on a whitelist', async () => { 85 | // First allow acct 1 be an administrator 86 | await tokenInstance.addWhitelister(accounts[1], { from: accounts[0] }) 87 | 88 | // Allow whitelist 10 to send to self 89 | await tokenInstance.updateOutboundWhitelistEnabled(10, 10, true, { from: accounts[1] }) 90 | 91 | // Two addresses not on any white list 92 | let isValid = await tokenInstance.checkWhitelistAllowed.call(accounts[6], accounts[7]) 93 | assert.equal(isValid, false, 'Two non white listed addresses should not be valid') 94 | 95 | // Add address 6 96 | await tokenInstance.addToWhitelist(accounts[6], 10, { from: accounts[1] }) 97 | 98 | // Only first address on white list should fail 99 | isValid = await tokenInstance.checkWhitelistAllowed.call(accounts[6], accounts[7]) 100 | assert.equal(isValid, false, 'First non white listed addresses should not be valid') 101 | 102 | // Remove again 103 | await tokenInstance.removeFromWhitelist(accounts[6], { from: accounts[1] }) 104 | 105 | // Both should fail again 106 | isValid = await tokenInstance.checkWhitelistAllowed.call(accounts[6], accounts[7]) 107 | assert.equal(isValid, false, 'Two non white listed addresses should not be valid') 108 | 109 | // Add address 7 110 | await tokenInstance.addToWhitelist(accounts[7], 10, { from: accounts[1] }) 111 | 112 | // Only second address on white list should fail 113 | isValid = await tokenInstance.checkWhitelistAllowed.call(accounts[6], accounts[7]) 114 | assert.equal(isValid, false, 'Second non white listed addresses should not be valid') 115 | 116 | // Remove second addr 117 | await tokenInstance.removeFromWhitelist(accounts[7], { from: accounts[1] }) 118 | 119 | // Both should fail again 120 | isValid = await tokenInstance.checkWhitelistAllowed.call(accounts[6], accounts[7]) 121 | assert.equal(isValid, false, 'Two non white listed addresses should not be valid') 122 | 123 | // Add both 6 and 7 124 | await tokenInstance.addToWhitelist(accounts[6], 10, { from: accounts[1] }) 125 | await tokenInstance.addToWhitelist(accounts[7], 10, { from: accounts[1] }) 126 | 127 | // Should be valid 128 | isValid = await tokenInstance.checkWhitelistAllowed.call(accounts[6], accounts[7]) 129 | assert.equal(isValid, true, 'Both on same white list should be valid') 130 | 131 | // Update address 6 to a different white list 132 | await tokenInstance.addToWhitelist(accounts[6], 20, { from: accounts[1] }) 133 | 134 | // Should fail 135 | isValid = await tokenInstance.checkWhitelistAllowed.call(accounts[6], accounts[7]) 136 | assert.equal(isValid, false, 'Two addresses on separate white lists should not be valid') 137 | }) 138 | 139 | it('should trigger events', async () => { 140 | // First allow acct 1 to be an administrator 141 | await tokenInstance.addWhitelister(accounts[1], { from: accounts[0] }) 142 | 143 | // Check for initial add 144 | let ret = await tokenInstance.addToWhitelist(accounts[3], 20, { from: accounts[1] }) 145 | expectEvent.inLogs(ret.logs, 'AddressAddedToWhitelist', { addedAddress: accounts[3], whitelist: '20', addedBy: accounts[1] }) 146 | 147 | // Adding to second whitelist should remove from first and add to second 148 | ret = await tokenInstance.addToWhitelist(accounts[3], 30, { from: accounts[1] }) 149 | expectEvent.inLogs(ret.logs, 'AddressRemovedFromWhitelist', { removedAddress: accounts[3], whitelist: '20', removedBy: accounts[1] }) 150 | expectEvent.inLogs(ret.logs, 'AddressAddedToWhitelist', { addedAddress: accounts[3], whitelist: '30', addedBy: accounts[1] }) 151 | 152 | // Removing from list should just trigger removal 153 | ret = await tokenInstance.removeFromWhitelist(accounts[3], { from: accounts[1] }) 154 | expectEvent.inLogs(ret.logs, 'AddressRemovedFromWhitelist', { removedAddress: accounts[3], whitelist: '30', removedBy: accounts[1] }) 155 | }) 156 | 157 | it('should validate outbound whitelist enabled flag', async () => { 158 | // Allow acct 1 to be an admin 159 | await tokenInstance.addWhitelister(accounts[1], { from: accounts[0] }) 160 | 161 | // Default should be disabled to self 162 | let existingOutboundEnabled = await tokenInstance.outboundWhitelistsEnabled.call(4, 4) 163 | assert.equal(existingOutboundEnabled, false, 'Default outbound should be disabled to self') 164 | 165 | // Default should be disabled to other random ID 166 | existingOutboundEnabled = await tokenInstance.outboundWhitelistsEnabled.call(4, 5) 167 | assert.equal(existingOutboundEnabled, false, 'Default outbound should be disabled to other') 168 | 169 | // Update so 4 is allowed to send to self 170 | await tokenInstance.updateOutboundWhitelistEnabled(4, 4, true, { from: accounts[1] }) 171 | existingOutboundEnabled = await tokenInstance.outboundWhitelistsEnabled.call(4, 4) 172 | assert.equal(existingOutboundEnabled, true, 'Should be enabled') 173 | 174 | // 4 to 5 should still be disabled 175 | existingOutboundEnabled = await tokenInstance.outboundWhitelistsEnabled.call(4, 5) 176 | assert.equal(existingOutboundEnabled, false, 'Should be disabled') 177 | 178 | // Allow 4 to 5 179 | await tokenInstance.updateOutboundWhitelistEnabled(4, 5, true, { from: accounts[1] }) 180 | existingOutboundEnabled = await tokenInstance.outboundWhitelistsEnabled.call(4, 5) 181 | assert.equal(existingOutboundEnabled, true, 'Should be enabled') 182 | 183 | // Backwards should fail 184 | existingOutboundEnabled = await tokenInstance.outboundWhitelistsEnabled.call(5, 4) 185 | assert.equal(existingOutboundEnabled, false, 'Should be disabled') 186 | 187 | // 5 should still not be able to send to self 188 | existingOutboundEnabled = await tokenInstance.outboundWhitelistsEnabled.call(5, 5) 189 | assert.equal(existingOutboundEnabled, false, 'Should be disabled') 190 | 191 | // Disable 4 to 5 192 | await tokenInstance.updateOutboundWhitelistEnabled(4, 5, false, { from: accounts[1] }) 193 | existingOutboundEnabled = await tokenInstance.outboundWhitelistsEnabled.call(4, 5) 194 | assert.equal(existingOutboundEnabled, false, 'Should be disabled') 195 | 196 | // Disable 4 to self 197 | await tokenInstance.updateOutboundWhitelistEnabled(4, 4, false, { from: accounts[1] }) 198 | existingOutboundEnabled = await tokenInstance.outboundWhitelistsEnabled.call(4, 4) 199 | assert.equal(existingOutboundEnabled, false, 'Should be disabled') 200 | }) 201 | 202 | it('should trigger events for whitelist enable/disable', async () => { 203 | await tokenInstance.addWhitelister(accounts[1], { from: accounts[0] }) 204 | 205 | // Verify logs for enabling outbound 206 | let ret = await tokenInstance.updateOutboundWhitelistEnabled(90, 100, true, { from: accounts[1] }) 207 | expectEvent.inLogs(ret.logs, 'OutboundWhitelistUpdated', { updatedBy: accounts[1], sourceWhitelist: '90', destinationWhitelist: '100', from: false, to: true }) 208 | 209 | // Verify logs for disabling outbound 210 | ret = await tokenInstance.updateOutboundWhitelistEnabled(90, 100, false, { from: accounts[1] }) 211 | expectEvent.inLogs(ret.logs, 'OutboundWhitelistUpdated', { updatedBy: accounts[1], sourceWhitelist: '90', destinationWhitelist: '100', from: true, to: false }) 212 | 213 | // Verify doing same thihng 214 | ret = await tokenInstance.updateOutboundWhitelistEnabled(90, 100, false, { from: accounts[1] }) 215 | expectEvent.inLogs(ret.logs, 'OutboundWhitelistUpdated', { updatedBy: accounts[1], sourceWhitelist: '90', destinationWhitelist: '100', from: false, to: false }) 216 | }) 217 | 218 | it('should not allow adding an address to invalid whitelist ID (0)', async () => { 219 | // First allow acct 1 be an administrator 220 | await tokenInstance.addWhitelister(accounts[1], { from: accounts[0] }) 221 | 222 | // Adding acct 2 to whitelist 0 should get rejected 223 | await expectRevert( 224 | tokenInstance.addToWhitelist(accounts[2], NO_WHITELIST, { from: accounts[1] }), 225 | "Invalid whitelist ID supplied" 226 | ) 227 | }) 228 | 229 | it('should not allow adding or removing address 0x0', async () => { 230 | // First allow acct 0 be an administrator 231 | await tokenInstance.addWhitelister(accounts[0], { from: accounts[0] }) 232 | 233 | await expectRevert( 234 | tokenInstance.addToWhitelist( 235 | "0x0000000000000000000000000000000000000000", 236 | 1, 237 | { from: accounts[0] } 238 | ), 239 | "Cannot add address 0x0 to a whitelist." 240 | ) 241 | 242 | await expectRevert( 243 | tokenInstance.removeFromWhitelist( 244 | "0x0000000000000000000000000000000000000000", 245 | { from: accounts[0] } 246 | ), 247 | "Cannot remove address 0x0 from a whitelist." 248 | ) 249 | }) 250 | 251 | it('should allow disabling and re-enabling the whitelist logic', async () => { 252 | // First allow acct 1 be whitelister 253 | await tokenInstance.addWhitelister(accounts[1], { from: accounts[0] }) 254 | 255 | // Send some tokens to account 2 256 | await tokenInstance.transfer(accounts[2], 1000) 257 | 258 | // Verify accounts can't transfer 259 | await expectRevert( 260 | tokenInstance.transfer(accounts[3], 100, { from: accounts[2] }), 261 | "The transfer was restricted due to white list configuration." 262 | ) 263 | 264 | // Turn it off - and verify event 265 | let ret = await tokenInstance.setWhitelistEnabled(false) 266 | expectEvent.inLogs(ret.logs, 'WhitelistEnabledUpdated', { updatedBy: accounts[0], enabled: false }) 267 | 268 | // Validate it works 269 | await tokenInstance.transfer(accounts[3], 100, { from: accounts[2] }) 270 | 271 | // Turn it on - and verify event 272 | ret = await tokenInstance.setWhitelistEnabled(true) 273 | expectEvent.inLogs(ret.logs, 'WhitelistEnabledUpdated', { updatedBy: accounts[0], enabled: true }) 274 | 275 | // Verify accounts can't transfer 276 | await expectRevert( 277 | tokenInstance.transfer(accounts[3], 100, { from: accounts[2] }), 278 | "The transfer was restricted due to white list configuration." 279 | ) 280 | }) 281 | 282 | it('should not allow non-owner disabling the whitelist logic', async () => { 283 | await expectRevert( 284 | tokenInstance.setWhitelistEnabled(false, { from: accounts[2] }), 285 | "OwnerRole: caller does not have the Owner role" 286 | ) 287 | }) 288 | 289 | it('should verify an address is on a valid whitelist when removing', async () => { 290 | // First allow acct 0 be whitelister 291 | await tokenInstance.addWhitelister(accounts[0], { from: accounts[0] }) 292 | 293 | // Try to remove an address that was never added to a whitelist 294 | await expectRevert( 295 | tokenInstance.removeFromWhitelist(accounts[1], { from: accounts[0] }), 296 | "Address cannot be removed from invalid whitelist." 297 | ) 298 | }) 299 | }) 300 | -------------------------------------------------------------------------------- /test/roles/BlacklisterRole.js: -------------------------------------------------------------------------------- 1 | /* global artifacts contract it assert */ 2 | const { expectRevert, expectEvent } = require('@openzeppelin/test-helpers') 3 | const TokenSoftTokenV2 = artifacts.require('TokenSoftTokenV2') 4 | const Proxy = artifacts.require('Proxy') 5 | const Constants = require('../Constants') 6 | 7 | /** 8 | * Sanity checks for managing the Blacklister Role 9 | */ 10 | contract('BlacklisterRole', (accounts) => { 11 | let tokenInstance, tokenDeploy, proxyInstance 12 | 13 | beforeEach(async () => { 14 | tokenDeploy = await TokenSoftTokenV2.new() 15 | proxyInstance = await Proxy.new(tokenDeploy.address) 16 | tokenInstance = await TokenSoftTokenV2.at(proxyInstance.address) 17 | await tokenInstance.initialize( 18 | accounts[0], 19 | Constants.name, 20 | Constants.symbol, 21 | Constants.decimals, 22 | Constants.supply, 23 | true); 24 | }) 25 | 26 | it('should allow an owner to add/remove Blacklisters', async () => { 27 | // Should start out as false 28 | let isBlacklister1 = await tokenInstance.isBlacklister(accounts[1]) 29 | assert.equal(isBlacklister1, false, 'Account 1 should not be a Blacklister by default') 30 | 31 | // Add it and verify 32 | await tokenInstance.addBlacklister(accounts[1]) 33 | isBlacklister1 = await tokenInstance.isBlacklister(accounts[1]) 34 | assert.equal(isBlacklister1, true, 'Account 1 should be a Blacklister') 35 | 36 | // Remove it and verify 37 | await tokenInstance.removeBlacklister(accounts[1]) 38 | isBlacklister1 = await tokenInstance.isBlacklister(accounts[1]) 39 | assert.equal(isBlacklister1, false, 'Account 1 should not be a Blacklister') 40 | }) 41 | 42 | it('should not allow a non owner to add/remove Blacklisters', async () => { 43 | // Prove it can't be added by account 3 44 | await expectRevert(tokenInstance.addBlacklister(accounts[4], { from: accounts[3] }), "OwnerRole: caller does not have the Owner role") 45 | 46 | // Add it with owner 47 | await tokenInstance.addBlacklister(accounts[4]) 48 | 49 | // Prove it can't be removed by account 3 50 | await expectRevert(tokenInstance.removeBlacklister(accounts[4], { from: accounts[3] }), "OwnerRole: caller does not have the Owner role") 51 | 52 | // Remove it with the owner 53 | tokenInstance.removeBlacklister(accounts[4]) 54 | }) 55 | 56 | it('should emit events for adding Blacklisters', async () => { 57 | const { logs } = await tokenInstance.addBlacklister(accounts[3], { from: accounts[0] }) 58 | expectEvent.inLogs(logs, 'BlacklisterAdded', { addedBlacklister: accounts[3], addedBy: accounts[0] }) 59 | }) 60 | 61 | it('should emit events for removing Blacklisters', async () => { 62 | await tokenInstance.addBlacklister(accounts[3]) 63 | const { logs } = await tokenInstance.removeBlacklister(accounts[3]) 64 | 65 | expectEvent.inLogs(logs, 'BlacklisterRemoved', { removedBlacklister: accounts[3], removedBy: accounts[0] }) 66 | }) 67 | 68 | it('owner can add and remove themselves', async () => { 69 | // Add it 70 | await tokenInstance.addBlacklister(accounts[0]) 71 | let isBlacklister1 = await tokenInstance.isBlacklister(accounts[0]) 72 | assert.equal(isBlacklister1, true, 'Account 0 should be a Blacklister') 73 | 74 | // Remove it 75 | tokenInstance.removeBlacklister(accounts[0]) 76 | isBlacklister1 = await tokenInstance.isBlacklister(accounts[0]) 77 | assert.equal(isBlacklister1, false, 'Account 0 should not be a Blacklister') 78 | }) 79 | }) 80 | -------------------------------------------------------------------------------- /test/roles/BurnerRole.js: -------------------------------------------------------------------------------- 1 | /* global artifacts contract it assert */ 2 | const { expectRevert, expectEvent } = require('@openzeppelin/test-helpers') 3 | const TokenSoftToken = artifacts.require('TokenSoftTokenV2') 4 | const Proxy = artifacts.require('Proxy') 5 | const Constants = require('../Constants') 6 | 7 | /** 8 | * Sanity checks for managing the Burner Role 9 | */ 10 | contract('BurnerRole', (accounts) => { 11 | let tokenInstance, tokenDeploy, proxyInstance 12 | 13 | beforeEach(async () => { 14 | tokenDeploy = await TokenSoftToken.new() 15 | proxyInstance = await Proxy.new(tokenDeploy.address) 16 | tokenInstance = await TokenSoftToken.at(proxyInstance.address) 17 | await tokenInstance.initialize( 18 | accounts[0], 19 | Constants.name, 20 | Constants.symbol, 21 | Constants.decimals, 22 | Constants.supply, 23 | true); 24 | }) 25 | 26 | it('should allow an owner to add/remove burners', async () => { 27 | // Should start out as false 28 | let isBurner1 = await tokenInstance.isBurner(accounts[1]) 29 | assert.equal(isBurner1, false, 'Account 1 should not be a burner by default') 30 | 31 | // Add it and verify 32 | await tokenInstance.addBurner(accounts[1]) 33 | isBurner1 = await tokenInstance.isBurner(accounts[1]) 34 | assert.equal(isBurner1, true, 'Account 1 should be a burner') 35 | 36 | // Remove it and verify 37 | await tokenInstance.removeBurner(accounts[1]) 38 | isBurner1 = await tokenInstance.isBurner(accounts[1]) 39 | assert.equal(isBurner1, false, 'Account 1 should not be a burner') 40 | }) 41 | 42 | it('should not allow a non owner to add/remove burners', async () => { 43 | // Prove it can't be added by account 3 44 | await expectRevert(tokenInstance.addBurner(accounts[4], { from: accounts[3] }), "OwnerRole: caller does not have the Owner role") 45 | 46 | // Add it with owner 47 | await tokenInstance.addBurner(accounts[4]) 48 | 49 | // Prove it can't be removed by account 3 50 | await expectRevert(tokenInstance.removeBurner(accounts[4], { from: accounts[3] }), "OwnerRole: caller does not have the Owner role") 51 | 52 | // Remove it with the owner 53 | tokenInstance.removeBurner(accounts[4]) 54 | }) 55 | 56 | it('should emit events for adding burners', async () => { 57 | const { logs } = await tokenInstance.addBurner(accounts[3], { from: accounts[0] }) 58 | expectEvent.inLogs(logs, 'BurnerAdded', { addedBurner: accounts[3], addedBy: accounts[0] }) 59 | }) 60 | 61 | it('should emit events for removing burners', async () => { 62 | await tokenInstance.addBurner(accounts[3]) 63 | const { logs } = await tokenInstance.removeBurner(accounts[3]) 64 | 65 | expectEvent.inLogs(logs, 'BurnerRemoved', { removedBurner: accounts[3], removedBy: accounts[0] }) 66 | }) 67 | 68 | it('owner can add and remove themselves', async () => { 69 | // Add it 70 | await tokenInstance.addBurner(accounts[0]) 71 | let isBurner1 = await tokenInstance.isBurner(accounts[0]) 72 | assert.equal(isBurner1, true, 'Account 0 should be a burner') 73 | 74 | // Remove it 75 | tokenInstance.removeBurner(accounts[0]) 76 | isBurner1 = await tokenInstance.isBurner(accounts[0]) 77 | assert.equal(isBurner1, false, 'Account 0 should not be a burner') 78 | }) 79 | }) 80 | -------------------------------------------------------------------------------- /test/roles/MinterRole.js: -------------------------------------------------------------------------------- 1 | /* global artifacts contract it assert */ 2 | const { expectRevert, expectEvent } = require('@openzeppelin/test-helpers') 3 | const TokenSoftToken = artifacts.require('TokenSoftTokenV2') 4 | const Proxy = artifacts.require('Proxy') 5 | const Constants = require('../Constants') 6 | 7 | /** 8 | * Sanity checks for managing the Minter Role 9 | */ 10 | contract('MinterRole', (accounts) => { 11 | let tokenInstance, tokenDeploy, proxyInstance 12 | 13 | beforeEach(async () => { 14 | tokenDeploy = await TokenSoftToken.new() 15 | proxyInstance = await Proxy.new(tokenDeploy.address) 16 | tokenInstance = await TokenSoftToken.at(proxyInstance.address) 17 | await tokenInstance.initialize( 18 | accounts[0], 19 | Constants.name, 20 | Constants.symbol, 21 | Constants.decimals, 22 | Constants.supply, 23 | true); 24 | }) 25 | 26 | it('should allow an owner to add/remove minters', async () => { 27 | // Should start out as false 28 | let isMinter1 = await tokenInstance.isMinter(accounts[1]) 29 | assert.equal(isMinter1, false, 'Account 1 should not be a minter by default') 30 | 31 | // Add it and verify 32 | await tokenInstance.addMinter(accounts[1]) 33 | isMinter1 = await tokenInstance.isMinter(accounts[1]) 34 | assert.equal(isMinter1, true, 'Account 1 should be a minter') 35 | 36 | // Remove it and verify 37 | await tokenInstance.removeMinter(accounts[1]) 38 | isMinter1 = await tokenInstance.isMinter(accounts[1]) 39 | assert.equal(isMinter1, false, 'Account 1 should not be a minter') 40 | }) 41 | 42 | it('should not allow a non owner to add/remove minters', async () => { 43 | // Prove it can't be added by account 3 44 | await expectRevert(tokenInstance.addMinter(accounts[4], { from: accounts[3] }), "OwnerRole: caller does not have the Owner role") 45 | 46 | // Add it with owner 47 | await tokenInstance.addMinter(accounts[4]) 48 | 49 | // Prove it can't be removed by account 3 50 | await expectRevert(tokenInstance.removeMinter(accounts[4], { from: accounts[3] }), "OwnerRole: caller does not have the Owner role") 51 | 52 | // Remove it with the owner 53 | tokenInstance.removeMinter(accounts[4]) 54 | }) 55 | 56 | it('should emit events for adding minters', async () => { 57 | const { logs } = await tokenInstance.addMinter(accounts[3], { from: accounts[0] }) 58 | expectEvent.inLogs(logs, 'MinterAdded', { addedMinter: accounts[3], addedBy: accounts[0] }) 59 | }) 60 | 61 | it('should emit events for removing minters', async () => { 62 | await tokenInstance.addMinter(accounts[3]) 63 | const { logs } = await tokenInstance.removeMinter(accounts[3]) 64 | 65 | expectEvent.inLogs(logs, 'MinterRemoved', { removedMinter: accounts[3], removedBy: accounts[0] }) 66 | }) 67 | 68 | it('owner can add and remove themselves', async () => { 69 | // Add it 70 | await tokenInstance.addMinter(accounts[0]) 71 | let isMinter1 = await tokenInstance.isMinter(accounts[0]) 72 | assert.equal(isMinter1, true, 'Account 0 should be a minter') 73 | 74 | // Remove it 75 | tokenInstance.removeMinter(accounts[0]) 76 | isMinter1 = await tokenInstance.isMinter(accounts[0]) 77 | assert.equal(isMinter1, false, 'Account 0 should not be a minter') 78 | }) 79 | }) 80 | -------------------------------------------------------------------------------- /test/roles/OwnerRole.js: -------------------------------------------------------------------------------- 1 | /* global artifacts contract it assert */ 2 | const { expectRevert, expectEvent } = require('@openzeppelin/test-helpers') 3 | const TokenSoftToken = artifacts.require('TokenSoftTokenV2') 4 | const Proxy = artifacts.require('Proxy') 5 | const Constants = require('../Constants') 6 | 7 | /** 8 | * Sanity check for transferring ownership. Most logic is fully tested in OpenZeppelin lib. 9 | */ 10 | contract('OwnerRole', (accounts) => { 11 | let tokenInstance, tokenDeploy, proxyInstance 12 | 13 | beforeEach(async () => { 14 | tokenDeploy = await TokenSoftToken.new() 15 | proxyInstance = await Proxy.new(tokenDeploy.address) 16 | tokenInstance = await TokenSoftToken.at(proxyInstance.address) 17 | await tokenInstance.initialize( 18 | accounts[0], 19 | Constants.name, 20 | Constants.symbol, 21 | Constants.decimals, 22 | Constants.supply, 23 | true); 24 | }) 25 | 26 | it('should allow an owner to add/remove owners', async () => { 27 | // Should start out as false 28 | let isOwner1 = await tokenInstance.isOwner(accounts[1]) 29 | assert.equal(isOwner1, false, 'Account 1 should not be an owner by default') 30 | 31 | // Should have been updated 32 | await tokenInstance.addOwner(accounts[1], { from: accounts[0] }) 33 | isOwner1 = await tokenInstance.isOwner(accounts[1]) 34 | assert.equal(isOwner1, true, 'Account 1 should be an owner') 35 | 36 | await tokenInstance.removeOwner(accounts[1], { from: accounts[0] }) 37 | isOwner1 = await tokenInstance.isOwner(accounts[1]) 38 | assert.equal(isOwner1, false, 'Account 1 should not be an owner') 39 | }) 40 | 41 | it('should allow an owner to remove itself', async () => { 42 | await tokenInstance.removeOwner(accounts[0], { from: accounts[0] }) 43 | }) 44 | 45 | it('should not allow a non owner to add/remove owners', async () => { 46 | // Prove it can't be added by account 3 47 | await expectRevert(tokenInstance.addOwner(accounts[4], { from: accounts[3] }), "OwnerRole: caller does not have the Owner role") 48 | 49 | // Verify a 0x0 address can't be added 50 | await expectRevert(tokenInstance.addOwner("0x0000000000000000000000000000000000000000"), "Invalid 0x0 address") 51 | 52 | // Add it with owner 53 | await tokenInstance.addOwner(accounts[4]) 54 | 55 | // Prove it can't be removed by account 3 56 | await expectRevert(tokenInstance.removeOwner(accounts[4], { from: accounts[3] }), "OwnerRole: caller does not have the Owner role") 57 | 58 | // Remove it with the owner 59 | tokenInstance.removeOwner(accounts[4]) 60 | }) 61 | 62 | it('should emit events for adding owners', async () => { 63 | const { logs } = await tokenInstance.addOwner(accounts[3], { from: accounts[0] }) 64 | expectEvent.inLogs(logs, 'OwnerAdded', { addedOwner: accounts[3], addedBy: accounts[0] }) 65 | }) 66 | 67 | it('should emit events for removing owners', async () => { 68 | await tokenInstance.addOwner(accounts[3], { from: accounts[0] }) 69 | const { logs } = await tokenInstance.removeOwner(accounts[3], { from: accounts[0] }) 70 | 71 | expectEvent.inLogs(logs, 'OwnerRemoved', { removedOwner: accounts[3], removedBy: accounts[0] }) 72 | }) 73 | }) 74 | -------------------------------------------------------------------------------- /test/roles/PauserRole.js: -------------------------------------------------------------------------------- 1 | /* global artifacts contract it assert */ 2 | const { expectRevert, expectEvent } = require('@openzeppelin/test-helpers') 3 | const TokenSoftToken = artifacts.require('TokenSoftTokenV2') 4 | const Proxy = artifacts.require('Proxy') 5 | const Constants = require('../Constants') 6 | 7 | /** 8 | * Sanity checks for managing the Pauser Role 9 | */ 10 | contract('PauserRole', (accounts) => { 11 | let tokenInstance, tokenDeploy, proxyInstance 12 | 13 | beforeEach(async () => { 14 | tokenDeploy = await TokenSoftToken.new() 15 | proxyInstance = await Proxy.new(tokenDeploy.address) 16 | tokenInstance = await TokenSoftToken.at(proxyInstance.address) 17 | await tokenInstance.initialize( 18 | accounts[0], 19 | Constants.name, 20 | Constants.symbol, 21 | Constants.decimals, 22 | Constants.supply, 23 | true); 24 | }) 25 | 26 | it('should allow an owner to add/remove pausers', async () => { 27 | // Should start out as false 28 | let isPauser1 = await tokenInstance.isPauser(accounts[1]) 29 | assert.equal(isPauser1, false, 'Account 1 should not be a pauser by default') 30 | 31 | // Add it and verify 32 | await tokenInstance.addPauser(accounts[1]) 33 | isPauser1 = await tokenInstance.isPauser(accounts[1]) 34 | assert.equal(isPauser1, true, 'Account 1 should be a pauser') 35 | 36 | // Remove it and verify 37 | await tokenInstance.removePauser(accounts[1]) 38 | isPauser1 = await tokenInstance.isPauser(accounts[1]) 39 | assert.equal(isPauser1, false, 'Account 1 should not be a pauser') 40 | }) 41 | 42 | it('should not allow a non owner to add/remove pausers', async () => { 43 | // Prove it can't be added by account 3 44 | await expectRevert(tokenInstance.addPauser(accounts[4], { from: accounts[3] }), "OwnerRole: caller does not have the Owner role") 45 | 46 | // Add it with owner 47 | await tokenInstance.addPauser(accounts[4]) 48 | 49 | // Prove it can't be removed by account 3 50 | await expectRevert(tokenInstance.removePauser(accounts[4], { from: accounts[3] }), "OwnerRole: caller does not have the Owner role") 51 | 52 | // Remove it with the owner 53 | tokenInstance.removePauser(accounts[4]) 54 | }) 55 | 56 | it('should emit events for adding pausers', async () => { 57 | const { logs } = await tokenInstance.addPauser(accounts[3], { from: accounts[0] }) 58 | expectEvent.inLogs(logs, 'PauserAdded', { addedPauser: accounts[3], addedBy: accounts[0] }) 59 | }) 60 | 61 | it('should emit events for removing pausers', async () => { 62 | await tokenInstance.addPauser(accounts[3]) 63 | const { logs } = await tokenInstance.removePauser(accounts[3]) 64 | 65 | expectEvent.inLogs(logs, 'PauserRemoved', { removedPauser: accounts[3], removedBy: accounts[0] }) 66 | }) 67 | 68 | it('owner can add and remove themselves', async () => { 69 | // Add it 70 | await tokenInstance.addPauser(accounts[0]) 71 | let isPauser1 = await tokenInstance.isPauser(accounts[0]) 72 | assert.equal(isPauser1, true, 'Account 0 should be a pauser') 73 | 74 | // Remove it 75 | tokenInstance.removePauser(accounts[0]) 76 | isPauser1 = await tokenInstance.isPauser(accounts[0]) 77 | assert.equal(isPauser1, false, 'Account 0 should not be a pauser') 78 | }) 79 | }) 80 | -------------------------------------------------------------------------------- /test/roles/RevokerRole.js: -------------------------------------------------------------------------------- 1 | /* global artifacts contract it assert */ 2 | const { expectRevert, expectEvent } = require('@openzeppelin/test-helpers') 3 | const TokenSoftToken = artifacts.require('TokenSoftTokenV2') 4 | const Proxy = artifacts.require('Proxy') 5 | const Constants = require('../Constants') 6 | 7 | /** 8 | * Sanity checks for managing the Revoker Role 9 | */ 10 | contract('RevokerRole', (accounts) => { 11 | let tokenInstance, tokenDeploy, proxyInstance 12 | 13 | beforeEach(async () => { 14 | tokenDeploy = await TokenSoftToken.new() 15 | proxyInstance = await Proxy.new(tokenDeploy.address) 16 | tokenInstance = await TokenSoftToken.at(proxyInstance.address) 17 | await tokenInstance.initialize( 18 | accounts[0], 19 | Constants.name, 20 | Constants.symbol, 21 | Constants.decimals, 22 | Constants.supply, 23 | true); 24 | }) 25 | 26 | it('should allow an owner to add/remove revokers', async () => { 27 | // Should start out as false 28 | let isRevoker1 = await tokenInstance.isRevoker(accounts[1]) 29 | assert.equal(isRevoker1, false, 'Account 1 should not be a revoker by default') 30 | 31 | // Add it and verify 32 | await tokenInstance.addRevoker(accounts[1]) 33 | isRevoker1 = await tokenInstance.isRevoker(accounts[1]) 34 | assert.equal(isRevoker1, true, 'Account 1 should be a revoker') 35 | 36 | // Remove it and verify 37 | await tokenInstance.removeRevoker(accounts[1]) 38 | isRevoker1 = await tokenInstance.isRevoker(accounts[1]) 39 | assert.equal(isRevoker1, false, 'Account 1 should not be a revoker') 40 | }) 41 | 42 | it('should not allow a non owner to add/remove revokers', async () => { 43 | // Prove it can't be added by account 3 44 | await expectRevert(tokenInstance.addRevoker(accounts[4], { from: accounts[3] }), "OwnerRole: caller does not have the Owner role") 45 | 46 | // Add it with owner 47 | await tokenInstance.addRevoker(accounts[4]) 48 | 49 | // Prove it can't be removed by account 3 50 | await expectRevert(tokenInstance.removeRevoker(accounts[4], { from: accounts[3] }), "OwnerRole: caller does not have the Owner role") 51 | 52 | // Remove it with the owner 53 | tokenInstance.removeRevoker(accounts[4]) 54 | }) 55 | 56 | it('should emit events for adding revokers', async () => { 57 | const { logs } = await tokenInstance.addRevoker(accounts[3], { from: accounts[0] }) 58 | expectEvent.inLogs(logs, 'RevokerAdded', { addedRevoker: accounts[3], addedBy: accounts[0] }) 59 | }) 60 | 61 | it('should emit events for removing revokers', async () => { 62 | await tokenInstance.addRevoker(accounts[3]) 63 | const { logs } = await tokenInstance.removeRevoker(accounts[3]) 64 | 65 | expectEvent.inLogs(logs, 'RevokerRemoved', { removedRevoker: accounts[3], removedBy: accounts[0] }) 66 | }) 67 | 68 | it('owner can add and remove themselves', async () => { 69 | // Add it 70 | await tokenInstance.addRevoker(accounts[0]) 71 | let isRevoker1 = await tokenInstance.isRevoker(accounts[0]) 72 | assert.equal(isRevoker1, true, 'Account 0 should be a revoker') 73 | 74 | // Remove it 75 | tokenInstance.removeRevoker(accounts[0]) 76 | isRevoker1 = await tokenInstance.isRevoker(accounts[0]) 77 | assert.equal(isRevoker1, false, 'Account 0 should not be a revoker') 78 | }) 79 | }) 80 | -------------------------------------------------------------------------------- /test/roles/WhitelisterRole.js: -------------------------------------------------------------------------------- 1 | /* global artifacts contract it assert */ 2 | const { expectRevert, expectEvent } = require('@openzeppelin/test-helpers') 3 | const TokenSoftToken = artifacts.require('TokenSoftTokenV2') 4 | const Proxy = artifacts.require('Proxy') 5 | const Constants = require('../Constants') 6 | 7 | /** 8 | * Sanity checks for managing the Whitelister Role 9 | */ 10 | contract('WhitelisterRole', (accounts) => { 11 | let tokenInstance, tokenDeploy, proxyInstance 12 | 13 | beforeEach(async () => { 14 | tokenDeploy = await TokenSoftToken.new() 15 | proxyInstance = await Proxy.new(tokenDeploy.address) 16 | tokenInstance = await TokenSoftToken.at(proxyInstance.address) 17 | await tokenInstance.initialize( 18 | accounts[0], 19 | Constants.name, 20 | Constants.symbol, 21 | Constants.decimals, 22 | Constants.supply, 23 | true); 24 | }) 25 | 26 | it('should allow an owner to add/remove whitelisters', async () => { 27 | // Should start out as false 28 | let isWhitelister1 = await tokenInstance.isWhitelister(accounts[1]) 29 | assert.equal(isWhitelister1, false, 'Account 1 should not be a whitelister by default') 30 | 31 | // Add it and verify 32 | await tokenInstance.addWhitelister(accounts[1]) 33 | isWhitelister1 = await tokenInstance.isWhitelister(accounts[1]) 34 | assert.equal(isWhitelister1, true, 'Account 1 should be a whitelister') 35 | 36 | // Remove it and verify 37 | await tokenInstance.removeWhitelister(accounts[1]) 38 | isWhitelister1 = await tokenInstance.isWhitelister(accounts[1]) 39 | assert.equal(isWhitelister1, false, 'Account 1 should not be a whitelister') 40 | }) 41 | 42 | it('should not allow a non owner to add/remove whitelisters', async () => { 43 | // Prove it can't be added by account 3 44 | await expectRevert(tokenInstance.addWhitelister(accounts[4], { from: accounts[3] }), "OwnerRole: caller does not have the Owner role") 45 | 46 | // Add it with owner 47 | await tokenInstance.addWhitelister(accounts[4]) 48 | 49 | // Prove it can't be removed by account 3 50 | await expectRevert(tokenInstance.removeWhitelister(accounts[4], { from: accounts[3] }), "OwnerRole: caller does not have the Owner role") 51 | 52 | // Remove it with the owner 53 | tokenInstance.removeWhitelister(accounts[4]) 54 | }) 55 | 56 | it('should emit events for adding whitelisters', async () => { 57 | const { logs } = await tokenInstance.addWhitelister(accounts[3], { from: accounts[0] }) 58 | expectEvent.inLogs(logs, 'WhitelisterAdded', { addedWhitelister: accounts[3], addedBy: accounts[0] }) 59 | }) 60 | 61 | it('should emit events for removing whitelisters', async () => { 62 | await tokenInstance.addWhitelister(accounts[3]) 63 | const { logs } = await tokenInstance.removeWhitelister(accounts[3]) 64 | 65 | expectEvent.inLogs(logs, 'WhitelisterRemoved', { removedWhitelister: accounts[3], removedBy: accounts[0] }) 66 | }) 67 | 68 | it('owner can add and remove themselves', async () => { 69 | // Add it 70 | await tokenInstance.addWhitelister(accounts[0]) 71 | let isWhitelister1 = await tokenInstance.isWhitelister(accounts[0]) 72 | assert.equal(isWhitelister1, true, 'Account 0 should be a whitelister') 73 | 74 | // Remove it 75 | tokenInstance.removeWhitelister(accounts[0]) 76 | isWhitelister1 = await tokenInstance.isWhitelister(accounts[0]) 77 | assert.equal(isWhitelister1, false, 'Account 0 should not be a whitelister') 78 | }) 79 | }) 80 | -------------------------------------------------------------------------------- /truffle-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // Uncommenting the defaults below 3 | // provides for an easier quick-start with Ganache. 4 | // You can also follow this format for other networks; 5 | // see 6 | // for more details on how to specify configuration options! 7 | /* 8 | networks: { 9 | development: { 10 | host: "127.0.0.1", 11 | port: 7545, 12 | network_id: "*" 13 | }, 14 | test: { 15 | host: "127.0.0.1", 16 | port: 7545, 17 | network_id: "*" 18 | } 19 | } 20 | */ 21 | compilers: { 22 | solc: { 23 | version: '0.6.12', 24 | settings: { 25 | optimizer: { 26 | enabled: true, 27 | runs: 200 28 | } 29 | } 30 | } 31 | }, 32 | plugins: ["solidity-coverage", "truffle-contract-size"] 33 | } 34 | --------------------------------------------------------------------------------