├── .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 |
--------------------------------------------------------------------------------