├── .gitignore ├── docs └── images │ ├── permissions.png │ ├── upgradability.png │ └── component_diagram.png ├── migrations ├── 1_initial_migration.js ├── 2_deploy_contracts.js └── 3_deploy_test_tokens.js ├── LICENSE.txt ├── contracts ├── Migrations.sol ├── TestRegulatedToken.sol ├── RegulatorService.sol ├── ServiceRegistry.sol ├── TestRegulatorService.sol ├── RegulatedToken.sol └── TokenRegulatorService.sol ├── package.json ├── .circleci └── config.yml ├── test ├── helpers │ ├── MockRegulatorService.sol │ └── MockRegulatedToken.sol ├── TestRegulatedToken.js ├── helpers.js ├── ServiceRegistry.js ├── TestRegulatorService.js ├── RegulatedToken.js └── TokenRegulatorService.js ├── truffle.js ├── scripts └── mint_test_tokens.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | *.log 4 | -------------------------------------------------------------------------------- /docs/images/permissions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harborhq/r-token/HEAD/docs/images/permissions.png -------------------------------------------------------------------------------- /docs/images/upgradability.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harborhq/r-token/HEAD/docs/images/upgradability.png -------------------------------------------------------------------------------- /docs/images/component_diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harborhq/r-token/HEAD/docs/images/component_diagram.png -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | var Migrations = artifacts.require("./Migrations.sol"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Harbor Platform, Inc. 2 | 3 | Licensed under the Apache License, Version 2.0 (the “License”); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an “AS IS” BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 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 | function Migrations() public { 12 | owner = msg.sender; 13 | } 14 | 15 | function setCompleted(uint completed) restricted public { 16 | last_completed_migration = completed; 17 | } 18 | 19 | function upgrade(address new_address) restricted public { 20 | Migrations upgraded = Migrations(new_address); 21 | upgraded.setCompleted(last_completed_migration); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "r-token", 3 | "version": "1.0.0", 4 | "description": "Proof of concept for RegulatedToken", 5 | "main": "truffle.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "truffle test", 11 | "test:ganache": "yarn ganache-cli -m \"$RTOKEN_DEVELOPMENT_MNEMONIC\" -e 100" 12 | }, 13 | "author": "Bob Remeika", 14 | "license": "MIT", 15 | "devDependencies": { 16 | "ganache-cli": "^6.1.0", 17 | "truffle": "^4.1.5", 18 | "truffle-hdwallet-provider": "^0.0.3", 19 | "web3": "^1.0.0-beta.33" 20 | }, 21 | "dependencies": { 22 | "bignumber.js": "^6.0.0", 23 | "zeppelin-solidity": "^1.4.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | dependencies: 3 | override: 4 | - yarn 5 | jobs: 6 | build: 7 | docker: 8 | # specify the version you desire here 9 | - image: circleci/node:8 10 | working_directory: ~/repo 11 | steps: 12 | - checkout 13 | # Download and cache dependencies 14 | - restore_cache: 15 | keys: 16 | - v1-dependencies-{{ checksum "package.json" }} 17 | # fallback to using the latest cache if no exact match is found 18 | - v1-dependencies- 19 | - run: yarn install 20 | - save_cache: 21 | paths: 22 | - node_modules 23 | key: v1-dependencies-{{ checksum "package.json" }} 24 | - run: 25 | name: ganache-cli 26 | command: yarn test:ganache 27 | background: true 28 | - run: CI=true yarn test --maxWorkers=2 29 | -------------------------------------------------------------------------------- /test/helpers/MockRegulatorService.sol: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (c) 2017 Harbor Platform, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an “AS IS” BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | pragma solidity ^0.4.18; 18 | 19 | import '../../contracts/RegulatorService.sol'; 20 | 21 | contract MockRegulatorService is RegulatorService { 22 | bool public success; 23 | uint8 public reason; 24 | 25 | function setCheckResult(bool _success, uint8 _reason) public { 26 | success = _success; 27 | reason = _reason; 28 | } 29 | 30 | function check(address, address, address, address, uint256) public returns (uint8) { 31 | return reason; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /truffle.js: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (c) 2017 Harbor Platform, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an “AS IS” BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | const Web3 = require('web3'); 18 | const web3 = new Web3(); 19 | const HDWalletProvider = require('truffle-hdwallet-provider'); 20 | 21 | const kovanMnemonic = process.env['RTOKEN_KOVAN_MNEMONIC']; 22 | const infuraKey = process.env['INFURA_KEY']; 23 | 24 | module.exports = { 25 | networks: { 26 | development: { 27 | host: "localhost", 28 | port: 8545, 29 | network_id: "*" // Match any network id 30 | }, 31 | kovan: { 32 | provider: new HDWalletProvider(kovanMnemonic, `https://kovan.infura.io/${infuraKey}`), 33 | network_id: '42' 34 | } 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /migrations/2_deploy_contracts.js: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (c) 2017 Harbor Platform, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an “AS IS” BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | var RegulatedToken = artifacts.require("./RegulatedToken.sol"), 18 | ServiceRegistry = artifacts.require("./ServiceRegistry.sol"), 19 | TokenRegulatorService = artifacts.require("./TokenRegulatorService.sol"); 20 | 21 | module.exports = async function(deployer) { 22 | 23 | deployer.deploy(TokenRegulatorService).then(async () => { 24 | const regulator = await TokenRegulatorService.deployed(); 25 | return deployer.deploy(ServiceRegistry, regulator.address); 26 | }).then(async () => { 27 | const registry = await ServiceRegistry.deployed(); 28 | return deployer.deploy(RegulatedToken, registry.address, "Example", "EXPL"); 29 | }).then(async () => { 30 | const token = await RegulatedToken.deployed(); 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /test/helpers/MockRegulatedToken.sol: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (c) 2017 Harbor Platform, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an “AS IS” BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | pragma solidity ^0.4.18; 18 | 19 | import '../../contracts/RegulatedToken.sol'; 20 | import '../../contracts/RegulatorService.sol'; 21 | 22 | contract MockRegulatedToken is RegulatedToken { 23 | RegulatorService public service; 24 | uint public decimals; 25 | 26 | // 0xffffffff is a test address for ServiceRegistry that is bypassed by our _service() implementation 27 | function MockRegulatedToken(address _service) public 28 | RegulatedToken(ServiceRegistry(0xffffffff), "MockToken", "MTKN") 29 | { 30 | service = RegulatorService(_service); 31 | } 32 | 33 | function setDecimals(uint _decimals) public { 34 | decimals = _decimals; 35 | } 36 | 37 | function _service() constant public returns (RegulatorService) { 38 | return service; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /contracts/TestRegulatedToken.sol: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (c) 2017 Harbor Platform, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an “AS IS” BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | pragma solidity ^0.4.18; 18 | 19 | import './RegulatedToken.sol'; 20 | 21 | /** 22 | * TestRegulatedToken is a RegulatedToken meant for testing purposes. 23 | * Developers can mint an unlimited number of TestRegulatedTokens. 24 | * TestRegulatedToken is meant to be instantiated with a ServiceRegistry 25 | * that points to an instance of TestRegulatorService. 26 | */ 27 | contract TestRegulatedToken is RegulatedToken { 28 | function TestRegulatedToken(ServiceRegistry _registry, string _name, string _symbol) public 29 | RegulatedToken(_registry, _name, _symbol) 30 | { 31 | 32 | } 33 | 34 | /** 35 | * Override zeppelin.MintableToken.mint() without onlyOwner or canMint modifiers. 36 | */ 37 | function mint(address _to, uint256 _amount) public returns (bool) { 38 | totalSupply = totalSupply.add(_amount); 39 | balances[_to] = balances[_to].add(_amount); 40 | Mint(_to, _amount); 41 | Transfer(address(0), _to, _amount); 42 | return true; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /contracts/RegulatorService.sol: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (c) 2017 Harbor Platform, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an “AS IS” BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | pragma solidity ^0.4.18; 18 | 19 | /// @notice Standard interface for `RegulatorService`s 20 | contract RegulatorService { 21 | 22 | /* 23 | * @notice This method *MUST* be called by `RegulatedToken`s during `transfer()` and `transferFrom()`. 24 | * The implementation *SHOULD* check whether or not a transfer can be approved. 25 | * 26 | * @dev This method *MAY* call back to the token contract specified by `_token` for 27 | * more information needed to enforce trade approval. 28 | * 29 | * @param _token The address of the token to be transfered 30 | * @param _spender The address of the spender of the token 31 | * @param _from The address of the sender account 32 | * @param _to The address of the receiver account 33 | * @param _amount The quantity of the token to trade 34 | * 35 | * @return uint8 The reason code: 0 means success. Non-zero values are left to the implementation 36 | * to assign meaning. 37 | */ 38 | function check(address _token, address _spender, address _from, address _to, uint256 _amount) public returns (uint8); 39 | } 40 | -------------------------------------------------------------------------------- /migrations/3_deploy_test_tokens.js: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (c) 2017 Harbor Platform, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an “AS IS” BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | const ServiceRegistry = artifacts.require("./ServiceRegistry.sol"); 18 | const TestRegulatorService = artifacts.require("./TestRegulatorService.sol"); 19 | const TestRegulatedToken = artifacts.require("./TestRegulatedToken.sol"); 20 | 21 | module.exports = async function(deployer) { 22 | const log = deployer.logger.log; 23 | try { 24 | await deployer.deploy(TestRegulatorService); 25 | const regulator = await TestRegulatorService.deployed(); 26 | 27 | await deployer.deploy(ServiceRegistry, regulator.address, {overwrite: false}); 28 | const registry = await ServiceRegistry.deployed(); 29 | 30 | const copperToken = await deployer.new(TestRegulatedToken, registry.address, "Copper Token", "CPPR") 31 | log('copperToken.address ' + copperToken.address); 32 | 33 | const silverToken = await deployer.new(TestRegulatedToken, registry.address, "Silver Token", "SLVR") 34 | log('silverToken.address ' + silverToken.address); 35 | 36 | const goldToken = await deployer.new(TestRegulatedToken, registry.address, "Gold Token", "GOLD") 37 | log('goldToken.address ' + goldToken.address); 38 | } 39 | catch (e) { 40 | log(e); 41 | } 42 | }; 43 | -------------------------------------------------------------------------------- /contracts/ServiceRegistry.sol: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (c) 2017 Harbor Platform, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an “AS IS” BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | pragma solidity ^0.4.18; 18 | 19 | import './RegulatorService.sol'; 20 | import 'zeppelin-solidity/contracts/ownership/Ownable.sol'; 21 | 22 | /// @notice A service that points to a `RegulatorService` 23 | contract ServiceRegistry is Ownable { 24 | address public service; 25 | 26 | /** 27 | * @notice Triggered when service address is replaced 28 | */ 29 | event ReplaceService(address oldService, address newService); 30 | 31 | /** 32 | * @dev Validate contract address 33 | * Credit: https://github.com/Dexaran/ERC223-token-standard/blob/Recommended/ERC223_Token.sol#L107-L114 34 | * 35 | * @param _addr The address of a smart contract 36 | */ 37 | modifier withContract(address _addr) { 38 | uint length; 39 | assembly { length := extcodesize(_addr) } 40 | require(length > 0); 41 | _; 42 | } 43 | 44 | /** 45 | * @notice Constructor 46 | * 47 | * @param _service The address of the `RegulatorService` 48 | * 49 | */ 50 | function ServiceRegistry(address _service) public { 51 | service = _service; 52 | } 53 | 54 | /** 55 | * @notice Replaces the address pointer to the `RegulatorService` 56 | * 57 | * @dev This method is only callable by the contract's owner 58 | * 59 | * @param _service The address of the new `RegulatorService` 60 | */ 61 | function replaceService(address _service) onlyOwner withContract(_service) public { 62 | address oldService = service; 63 | service = _service; 64 | ReplaceService(oldService, service); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /contracts/TestRegulatorService.sol: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (c) 2017 Harbor Platform, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an “AS IS” BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | pragma solidity ^0.4.18; 18 | 19 | import './RegulatorService.sol'; 20 | 21 | /** 22 | * TestRegulatorService is a RegulatorService meant for testing purposes. 23 | * It returns check() reason codes based on the amount of the transaction. 24 | */ 25 | contract TestRegulatorService is RegulatorService { 26 | uint8 constant private SUCCESS = 0; /* 0 <= AMOUNT < 10 */ 27 | uint8 constant private ELOCKED = 1; /* 10 <= AMOUNT < 20 */ 28 | uint8 constant private EDIVIS = 2; /* 20 <= AMOUNT < 30 */ 29 | uint8 constant private ESEND = 3; /* 30 <= AMOUNT < 40 */ 30 | uint8 constant private ERECV = 4; /* 40 <= AMOUNT < 50 */ 31 | 32 | /** 33 | * @notice Checks whether or not a trade should be approved by checking the trade amount 34 | * 35 | * @param _token The address of the token to be transfered 36 | * @param _spender The address of the spender of the token (unused in this implementation) 37 | * @param _from The address of the sender account 38 | * @param _to The address of the receiver account 39 | * @param _amount The quantity of the token to trade 40 | * 41 | * @return `true` if the trade should be approved and `false` if the trade should not be approved 42 | */ 43 | function check(address _token, address _spender, address _from, address _to, uint256 _amount) public returns (uint8) { 44 | if (_amount >= 10 && _amount < 20) { 45 | return ELOCKED; 46 | } 47 | else if (_amount >= 20 && _amount < 30) { 48 | return EDIVIS; 49 | } 50 | else if (_amount >= 30 && _amount < 40) { 51 | return ESEND; 52 | } 53 | else if (_amount >= 40 && _amount < 50) { 54 | return ERECV; 55 | } 56 | return SUCCESS; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /test/TestRegulatedToken.js: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (c) 2017 Harbor Platform, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an “AS IS” BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | const TestRegulatedToken = artifacts.require('./TestRegulatedToken.sol'); 18 | const ServiceRegistry = artifacts.require('./ServiceRegistry.sol'); 19 | const TestRegulatorService = artifacts.require('./TestRegulatorService.sol'); 20 | 21 | contract('TestRegulatedToken', async function(accounts) { 22 | let regulator; 23 | let token; 24 | const owner = accounts[0]; // Owner of the TestRegulatedToken 25 | const notOwner = accounts[1]; // Not the owner of the TestRegulatedToken 26 | const fromOwner = { from: owner }; 27 | const fromNotOwner = { from: notOwner }; 28 | 29 | beforeEach(async () => { 30 | regulator = await TestRegulatorService.new(fromOwner); 31 | 32 | const registry = await ServiceRegistry.new(regulator.address); 33 | 34 | token = await TestRegulatedToken.new(registry.address, 'Test', 'TEST'); 35 | }); 36 | 37 | describe('minting', () => { 38 | beforeEach(async () => { 39 | assert.equal((await token.owner.call()).valueOf(), owner); 40 | assert.equal((await token.balanceOf.call(owner)).valueOf(), 0); 41 | assert.equal((await token.balanceOf.call(notOwner)).valueOf(), 0); 42 | }); 43 | 44 | it('lets people other than the token owner mint', async () => { 45 | await token.mint(notOwner, 100, fromNotOwner); 46 | assert.equal((await token.balanceOf.call(notOwner)).valueOf(), 100); 47 | }); 48 | 49 | it('lets people mint after finishMinting()', async () => { 50 | await token.finishMinting(); 51 | // Owner can still mint 52 | await token.mint(owner, 100, fromOwner); 53 | assert.equal((await token.balanceOf.call(owner)).valueOf(), 100); 54 | // NotOwner can still mint 55 | await token.mint(notOwner, 200, fromNotOwner); 56 | assert.equal((await token.balanceOf.call(notOwner)).valueOf(), 200); 57 | }); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /test/helpers.js: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (c) 2017 Harbor Platform, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an “AS IS” BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/test/helpers/expectThrow.js 18 | exports.expectThrow = async promise => { 19 | try { 20 | await promise; 21 | } catch (error) { 22 | // TODO: Check jump destination to destinguish between a throw 23 | // and an actual invalid jump. 24 | const invalidOpcode = error.message.search('invalid opcode') >= 0; 25 | // TODO: When we contract A calls contract B, and B throws, instead 26 | // of an 'invalid jump', we get an 'out of gas' error. How do 27 | // we distinguish this from an actual out of gas event? (The 28 | // testrpc log actually show an 'invalid jump' event.) 29 | const outOfGas = error.message.search('out of gas') >= 0; 30 | const revert = error.message.search('revert') >= 0; 31 | assert(invalidOpcode || outOfGas || revert, "Expected throw, got '" + error + "' instead"); 32 | return; 33 | } 34 | assert.fail('Expected throw not received'); 35 | }; 36 | 37 | exports.assertEvent = (event, args, assertEqual = assert.deepEqual, timeout = 3000) => { 38 | return new Promise((resolve, reject) => { 39 | const t = setTimeout(() => { 40 | reject(new Error('Timeout while waiting for event')); 41 | }, timeout); 42 | 43 | event.watch((error, response) => { 44 | try { 45 | assertEqual(response.args, args, 'Event argument mismatch'); 46 | resolve(response); 47 | } finally { 48 | clearTimeout(t); 49 | event.stopWatching(); 50 | } 51 | }); 52 | }); 53 | }; 54 | 55 | /** 56 | * @return true if the regulator will allow the trade. 57 | */ 58 | exports.checkResult = async (regulator, tokenAddress, spender, sender, receiver, amount) => { 59 | const reason = await regulator.check.call(tokenAddress, spender, sender, receiver, amount); 60 | return reason == 0; 61 | }; 62 | 63 | /** 64 | * Assert all addresses have the given balance for the given token. 65 | * @param token The token to check balances for. 66 | * @param balances Object where keys are addresses and values are the expected number of tokens for that address. 67 | */ 68 | exports.assertBalances = async (token, balances) => { 69 | for (let addr in balances) { 70 | const expectedBal = balances[addr]; 71 | const actualBal = await token.balanceOf.call(addr); 72 | assert.equal(actualBal.toNumber(), expectedBal); 73 | } 74 | }; 75 | -------------------------------------------------------------------------------- /test/ServiceRegistry.js: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (c) 2017 Harbor Platform, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an “AS IS” BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | const helpers = require('./helpers'); 18 | const RegulatedToken = artifacts.require('./RegulatedToken.sol'); 19 | const ServiceRegistry = artifacts.require('./ServiceRegistry.sol'); 20 | const MockRegulatorService = artifacts.require('../test/helpers/MockRegulatorService.sol'); 21 | 22 | contract('ServiceRegistry', async accounts => { 23 | let owner; 24 | let newOwner; 25 | let hacker; 26 | let participant; 27 | 28 | let service; 29 | let registry; 30 | 31 | beforeEach(async () => { 32 | owner = accounts[0]; 33 | newOwner = accounts[1]; 34 | hacker = accounts[2]; 35 | participant = accounts[3]; 36 | 37 | service = await MockRegulatorService.new({ from: owner }); 38 | registry = await ServiceRegistry.new(service.address, { from: owner }); 39 | }); 40 | 41 | describe('ownership', () => { 42 | it('allows ownership transfer', async () => { 43 | await helpers.expectThrow(registry.transferOwnership(newOwner, { from: hacker })); 44 | await registry.transferOwnership(newOwner, { from: owner }); 45 | 46 | await helpers.expectThrow(registry.transferOwnership(hacker, { from: owner })); 47 | await registry.transferOwnership(hacker, { from: newOwner }); 48 | }); 49 | }); 50 | 51 | describe('replaceService', () => { 52 | let newService; 53 | 54 | beforeEach(async () => { 55 | assert.equal(await registry.owner(), owner); 56 | assert.equal(await registry.service(), service.address); 57 | 58 | newService = await MockRegulatorService.new({ from: owner }); 59 | }); 60 | 61 | it('should allow the owner to replace the service with a contract', async () => { 62 | const event = registry.ReplaceService(); 63 | 64 | await registry.replaceService(newService.address); 65 | assert.equal(await registry.service(), newService.address); 66 | 67 | await helpers.assertEvent(event, { 68 | oldService: service.address, 69 | newService: newService.address, 70 | }); 71 | }); 72 | 73 | it('should NOT allow an invalid address', async () => { 74 | await helpers.expectThrow(registry.replaceService(participant)); 75 | await helpers.expectThrow(registry.replaceService(0)); 76 | assert.equal(await registry.service(), service.address); 77 | }); 78 | 79 | it('should NOT allow anybody except for the owner to replace the service', async () => { 80 | await helpers.expectThrow(registry.replaceService(newService.address, { from: hacker })); 81 | assert.equal(await registry.service(), service.address); 82 | }); 83 | }); 84 | }); 85 | -------------------------------------------------------------------------------- /test/TestRegulatorService.js: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (c) 2017 Harbor Platform, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an “AS IS” BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | const TestRegulatedToken = artifacts.require('./TestRegulatedToken.sol'); 18 | const ServiceRegistry = artifacts.require('./ServiceRegistry.sol'); 19 | const TestRegulatorService = artifacts.require('./TestRegulatorService.sol'); 20 | const checkResult = require('./helpers').checkResult; 21 | const helperAssertBalances = require('./helpers').assertBalances; 22 | 23 | 24 | const SUCCESS = 0; /* 0 <= AMOUNT < 10 */ 25 | const ELOCKED = 1; /* 10 <= AMOUNT < 20 */ 26 | const EDIVIS = 2; /* 20 <= AMOUNT < 30 */ 27 | const ESEND = 3; /* 30 <= AMOUNT < 40 */ 28 | const ERECV = 4; /* 40 <= AMOUNT < 50 */ 29 | 30 | contract('TestRegulatorService', async accounts => { 31 | const owner = accounts[0]; 32 | const spender = owner; 33 | const receiver = accounts[1]; 34 | const fromOwner = { from: owner }; 35 | 36 | let regulator; 37 | let token; 38 | let assertBalances; 39 | 40 | beforeEach(async () => { 41 | regulator = await TestRegulatorService.new({ from: owner }); 42 | const registry = await ServiceRegistry.new(regulator.address); 43 | token = await TestRegulatedToken.new(registry.address, 'Test', 'TEST'); 44 | }); 45 | 46 | describe('check', () => { 47 | it('returns SUCCESS when amount is between zero and ten', async () => { 48 | assert.equal(await regulator.check.call(token.address, spender, owner, receiver, 1), SUCCESS); 49 | }); 50 | 51 | it('returns ELOCKED when amount is between ten and twenty', async () => { 52 | assert.equal(await regulator.check.call(token.address, spender, owner, receiver, 11), ELOCKED); 53 | }); 54 | 55 | it('returns EDIVIS when amount is between twenty and thirty', async () => { 56 | assert.equal(await regulator.check.call(token.address, spender, owner, receiver, 21), EDIVIS); 57 | }); 58 | 59 | it('returns ESEND when amount is between thirty and forty', async () => { 60 | assert.equal(await regulator.check.call(token.address, spender, owner, receiver, 31), ESEND); 61 | }); 62 | 63 | it('returns ERECV when amount is between forty and fifty', async () => { 64 | assert.equal(await regulator.check.call(token.address, spender, owner, receiver, 41), ERECV); 65 | }); 66 | }); 67 | 68 | describe('TestRegulatedToken.transfer() with TestRegulatorService.check()', () => { 69 | beforeEach(async () => { 70 | assertBalances = async (ownerBal, receiverBal) => { 71 | await helperAssertBalances(token, { [owner]: ownerBal, [receiver]: receiverBal }); 72 | } 73 | 74 | await token.mint(owner, 100); 75 | await token.finishMinting(); 76 | 77 | await assertBalances(100, 0); 78 | }); 79 | 80 | describe('when the transfer is simulated to be NOT APPROVED by the regulator(by entering a transfer amount from 10-50)', () => { 81 | beforeEach(async () => { 82 | assert.isFalse(await checkResult(regulator, token.address, owner, owner, receiver, 15)); 83 | }); 84 | 85 | it('does NOT transfer tokens', async () => { 86 | await token.transfer(receiver, 15, fromOwner); 87 | await assertBalances(100, 0); 88 | }); 89 | }); 90 | 91 | describe('when the transfer is simulated to be APPROVED by the regulator(by entering a transfer amount from 0-10)', () => { 92 | beforeEach(async () => { 93 | assert.isTrue(await checkResult(regulator, token.address, owner, owner, receiver, 5)); 94 | }); 95 | 96 | it('transfers tokens', async () => { 97 | await token.transfer(receiver, 5, fromOwner); 98 | await assertBalances(95, 5); 99 | }); 100 | }); 101 | }); 102 | }); 103 | -------------------------------------------------------------------------------- /contracts/RegulatedToken.sol: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (c) 2017 Harbor Platform, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an “AS IS” BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | pragma solidity ^0.4.18; 18 | 19 | import 'zeppelin-solidity/contracts/token/DetailedERC20.sol'; 20 | import 'zeppelin-solidity/contracts/token/MintableToken.sol'; 21 | import './ServiceRegistry.sol'; 22 | import './RegulatorService.sol'; 23 | 24 | /// @notice An ERC-20 token that has the ability to check for trade validity 25 | contract RegulatedToken is DetailedERC20, MintableToken { 26 | 27 | /** 28 | * @notice R-Token decimals setting (used when constructing DetailedERC20) 29 | */ 30 | uint8 constant public RTOKEN_DECIMALS = 18; 31 | 32 | /** 33 | * @notice Triggered when regulator checks pass or fail 34 | */ 35 | event CheckStatus(uint8 reason, address indexed spender, address indexed from, address indexed to, uint256 value); 36 | 37 | /** 38 | * @notice Address of the `ServiceRegistry` that has the location of the 39 | * `RegulatorService` contract responsible for checking trade 40 | * permissions. 41 | */ 42 | ServiceRegistry public registry; 43 | 44 | /** 45 | * @notice Constructor 46 | * 47 | * @param _registry Address of `ServiceRegistry` contract 48 | * @param _name Name of the token: See DetailedERC20 49 | * @param _symbol Symbol of the token: See DetailedERC20 50 | */ 51 | function RegulatedToken(ServiceRegistry _registry, string _name, string _symbol) public 52 | DetailedERC20(_name, _symbol, RTOKEN_DECIMALS) 53 | { 54 | require(_registry != address(0)); 55 | 56 | registry = _registry; 57 | } 58 | 59 | /** 60 | * @notice ERC-20 overridden function that include logic to check for trade validity. 61 | * 62 | * @param _to The address of the receiver 63 | * @param _value The number of tokens to transfer 64 | * 65 | * @return `true` if successful and `false` if unsuccessful 66 | */ 67 | function transfer(address _to, uint256 _value) public returns (bool) { 68 | if (_check(msg.sender, _to, _value)) { 69 | return super.transfer(_to, _value); 70 | } else { 71 | return false; 72 | } 73 | } 74 | 75 | /** 76 | * @notice ERC-20 overridden function that include logic to check for trade validity. 77 | * 78 | * @param _from The address of the sender 79 | * @param _to The address of the receiver 80 | * @param _value The number of tokens to transfer 81 | * 82 | * @return `true` if successful and `false` if unsuccessful 83 | */ 84 | function transferFrom(address _from, address _to, uint256 _value) public returns (bool) { 85 | if (_check(_from, _to, _value)) { 86 | return super.transferFrom(_from, _to, _value); 87 | } else { 88 | return false; 89 | } 90 | } 91 | 92 | /** 93 | * @notice Performs the regulator check 94 | * 95 | * @dev This method raises a CheckStatus event indicating success or failure of the check 96 | * 97 | * @param _from The address of the sender 98 | * @param _to The address of the receiver 99 | * @param _value The number of tokens to transfer 100 | * 101 | * @return `true` if the check was successful and `false` if unsuccessful 102 | */ 103 | function _check(address _from, address _to, uint256 _value) private returns (bool) { 104 | var reason = _service().check(this, msg.sender, _from, _to, _value); 105 | 106 | CheckStatus(reason, msg.sender, _from, _to, _value); 107 | 108 | return reason == 0; 109 | } 110 | 111 | /** 112 | * @notice Retreives the address of the `RegulatorService` that manages this token. 113 | * 114 | * @dev This function *MUST NOT* memoize the `RegulatorService` address. This would 115 | * break the ability to upgrade the `RegulatorService`. 116 | * 117 | * @return The `RegulatorService` that manages this token. 118 | */ 119 | function _service() constant public returns (RegulatorService) { 120 | return RegulatorService(registry.service()); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /scripts/mint_test_tokens.js: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (c) 2017 Harbor Platform, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an “AS IS” BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | /******************************************************************************* 18 | * What: 19 | * Mints TestRegulatorTokens to specified wallet address. 20 | * 21 | * Usage: 22 | * node scripts/mint_test_tokens.js env numberOfTokens tokenSymbol toWalletAddress 23 | * 24 | * Example: 25 | * node scripts/mint_test_tokens.js development 100 CPPR 0xd4afd5525ada1efa8ae661be1a0b373eb9e68498 26 | ******************************************************************************/ 27 | 28 | console.log('STARTING'); 29 | const Web3 = require('web3'); 30 | const TestRegulatedTokenJson = require('../build/contracts/TestRegulatedToken'); 31 | const BigNumber = require('bignumber.js'); 32 | const HDWalletProvider = require('truffle-hdwallet-provider'); 33 | 34 | /******************************************************************************* 35 | * Input validation & Init 36 | ******************************************************************************/ 37 | const ensure = (condition, msg) => { 38 | if (!condition) { 39 | console.log('ERROR:', msg); 40 | process.exit(1); 41 | } 42 | }; 43 | 44 | const env = process.argv[2]; 45 | console.log('env:', env); 46 | ensure(env, 'No env'); 47 | 48 | const numberOfTokens = process.argv[3]; 49 | console.log('numberOfTokens:', numberOfTokens); 50 | ensure(numberOfTokens, 'No numberOfTokens'); 51 | 52 | const tokenSymbol = process.argv[4]; 53 | console.log('tokenSymbol:', tokenSymbol); 54 | ensure(tokenSymbol, 'No tokenSymbol'); 55 | 56 | const mintToWallet = process.argv[5]; 57 | console.log('mintToWallet:', mintToWallet); 58 | ensure(mintToWallet, 'No mintToWallet'); 59 | 60 | const configs = { 61 | development: { 62 | tokenAddresses: { 63 | 'CPPR': '0x8ef59034b52138fbcb1d36607fb5dd665fc7bc1c', 64 | 'SLVR': '0x6d966540214a7a55f61244e346d0ae6033ace4f8', 65 | 'GOLD': '0x3b6b17477763ccb313a55e87fd104c37e3e01f33' 66 | } 67 | }, 68 | kovan: { 69 | tokenAddresses: { 70 | 'CPPR': '0x5cdcc989560693e845060d271399216d8ab00c25', 71 | 'SLVR': '0x9843430d81290c90701bd655a7f19222b1de1f5f', 72 | 'GOLD': '0x88c3f6e2ddd8e65ec548197f2670623c5d876459' 73 | } 74 | } 75 | }; 76 | 77 | const config = configs[env]; 78 | ensure(config, 'No config'); 79 | 80 | const tokenAddress = config.tokenAddresses[tokenSymbol]; 81 | ensure(tokenAddress, 'No tokenAddress'); 82 | 83 | let provider; 84 | if (env === 'kovan') { 85 | const kovanMnemonic = process.env['RTOKEN_KOVAN_MNEMONIC']; 86 | ensure(kovanMnemonic, 'No kovanMnemonic'); 87 | const infuraKey = process.env['INFURA_KEY']; 88 | ensure(infuraKey, 'No infuraKey'); 89 | provider = new HDWalletProvider(kovanMnemonic, `https://kovan.infura.io/${infuraKey}`); 90 | } else { 91 | provider = new Web3.providers.HttpProvider('http://localhost:8545'); 92 | } 93 | const web3 = new Web3(provider); 94 | 95 | /******************************************************************************* 96 | * Actually Mint 97 | ******************************************************************************/ 98 | ;(async () => { 99 | try { 100 | const myAddress = (await web3.eth.getAccounts())[0]; 101 | console.log('myAddress:', myAddress); 102 | 103 | const ethBalance = await web3.eth.getBalance(myAddress); 104 | console.log('myAddress.eth.balance:', web3.utils.fromWei(ethBalance, 'ether')); 105 | 106 | var token = await new web3.eth.Contract(TestRegulatedTokenJson.abi, tokenAddress, {from: myAddress, gas: 5000000}); 107 | 108 | const toMintAmount = web3.utils.toWei(new BigNumber(numberOfTokens).toString(), 'ether'); 109 | console.log(`mintToWallet.${tokenSymbol}.toMintAmount:`, toMintAmount); 110 | 111 | const transaction = await token.methods.mint(mintToWallet, toMintAmount).send(); 112 | console.log('transaction:', transaction.status, transaction.transactionHash); 113 | 114 | const tokenBalance = await token.methods.balanceOf(mintToWallet).call({from: myAddress}) 115 | console.log(`mintToWallet.${tokenSymbol}.balance:`, tokenBalance); 116 | console.log('DONE'); 117 | } 118 | catch (e) { 119 | console.log(e); 120 | } 121 | })(); 122 | -------------------------------------------------------------------------------- /test/RegulatedToken.js: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (c) 2017 Harbor Platform, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an “AS IS” BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | const BigNumber = require('bignumber.js'); 18 | 19 | const helpers = require('./helpers'); 20 | const RegulatedToken = artifacts.require('./RegulatedToken.sol'); 21 | const ServiceRegistry = artifacts.require('./ServiceRegistry.sol'); 22 | const MockRegulatorService = artifacts.require('../test/helpers/MockRegulatorService.sol'); 23 | 24 | contract('RegulatedToken', async function(accounts) { 25 | let regulator; 26 | let token; 27 | let assertBalances; 28 | 29 | const owner = accounts[0]; 30 | const receiver = accounts[1]; 31 | 32 | const fromOwner = { from: owner }; 33 | const fromReceiver = { from: receiver }; 34 | 35 | beforeEach(async () => { 36 | regulator = await MockRegulatorService.new({ from: owner }); 37 | 38 | const registry = await ServiceRegistry.new(regulator.address); 39 | 40 | token = await RegulatedToken.new(registry.address, 'Test', 'TEST'); 41 | 42 | assertBalances = async (ownerBal, receiverBal) => { 43 | await helpers.assertBalances(token, { [owner]: ownerBal, [receiver]: receiverBal }); 44 | } 45 | 46 | await token.mint(owner, 100); 47 | await token.finishMinting(); 48 | 49 | await assertBalances(100, 0); 50 | }); 51 | 52 | const assertCheckStatusEvent = async (event, params) => { 53 | const p = Object.assign({}, params, { 54 | reason: new BigNumber(params.reason), 55 | value: new BigNumber(params.value), 56 | }); 57 | 58 | return helpers.assertEvent(event, p, (expected, actual) => { 59 | assert.equal(expected.reason.valueOf(), actual.reason.valueOf()); 60 | assert.equal(expected.spender, actual.spender); 61 | assert.equal(expected.from, actual.from); 62 | assert.equal(expected.to, actual.to); 63 | assert.equal(expected.value.valueOf(), actual.value.valueOf()); 64 | }); 65 | }; 66 | 67 | describe('constructor', () => { 68 | it('requires a non-zero registry argument', async () => { 69 | await helpers.expectThrow(RegulatedToken.new(0, 'TEST', 'Test')); 70 | }); 71 | }); 72 | 73 | describe('transfer', () => { 74 | describe('when the transfer is NOT approved by the regulator', () => { 75 | beforeEach(async () => { 76 | await regulator.setCheckResult(false, 255); 77 | assert.isFalse(await helpers.checkResult(regulator, token.address, owner, owner, receiver, 0)); 78 | await assertBalances(100, 0); 79 | }); 80 | 81 | it('returns false', async () => { 82 | assert.isFalse(await token.transfer.call(receiver, 100, fromOwner)); 83 | await assertBalances(100, 0); 84 | }); 85 | 86 | it('triggers a CheckStatus event and does NOT transfer funds', async () => { 87 | const event = token.CheckStatus(); 88 | const value = 25; 89 | 90 | await token.transfer(receiver, value, fromOwner); 91 | await assertBalances(100, 0); 92 | await assertCheckStatusEvent(event, { 93 | reason: 255, 94 | spender: owner, 95 | from: owner, 96 | to: receiver, 97 | value, 98 | }); 99 | }); 100 | }); 101 | 102 | describe('when the transfer is approved by the regulator', () => { 103 | beforeEach(async () => { 104 | await regulator.setCheckResult(true, 0); 105 | assert.isTrue(await helpers.checkResult(regulator, token.address, owner, owner, receiver, 0)); 106 | await assertBalances(100, 0); 107 | }); 108 | 109 | it('returns true', async () => { 110 | assert.isTrue(await token.transfer.call(receiver, 100, fromOwner)); 111 | // note: calls don't modify state 112 | await assertBalances(100, 0); 113 | }); 114 | 115 | it('triggers a CheckStatus event and transfers funds', async () => { 116 | const event = token.CheckStatus(); 117 | const value = 25; 118 | 119 | await token.transfer(receiver, value, fromOwner); 120 | await assertBalances(75, value); 121 | await assertCheckStatusEvent(event, { 122 | reason: 0, 123 | spender: owner, 124 | from: owner, 125 | to: receiver, 126 | value, 127 | }); 128 | }); 129 | }); 130 | }); 131 | 132 | describe('transferFrom', () => { 133 | describe('when the transfer is NOT approved by the regulator', () => { 134 | beforeEach(async () => { 135 | await regulator.setCheckResult(false, 255); 136 | 137 | assert.isFalse(await helpers.checkResult(regulator, token.address, owner, owner, receiver, 0)); 138 | await token.approve(receiver, 25, fromOwner); 139 | await assertBalances(100, 0); 140 | }); 141 | 142 | it('returns false', async () => { 143 | assert.isFalse(await token.transferFrom.call(owner, receiver, 20, fromReceiver)); 144 | await assertBalances(100, 0); 145 | }); 146 | 147 | it('triggers a CheckStatus event and does NOT transfer funds', async () => { 148 | const event = token.CheckStatus(); 149 | const value = 25; 150 | 151 | await token.transferFrom(owner, receiver, value, fromOwner); 152 | 153 | await assertBalances(100, 0); 154 | await assertCheckStatusEvent(event, { 155 | reason: 255, 156 | spender: owner, 157 | from: owner, 158 | to: receiver, 159 | value, 160 | }); 161 | }); 162 | }); 163 | 164 | describe('when the transfer is approved by the regulator', () => { 165 | beforeEach(async () => { 166 | await regulator.setCheckResult(true, 0); 167 | 168 | assert.isTrue(await helpers.checkResult(regulator, token.address, owner, owner, receiver, 0)); 169 | await token.approve(receiver, 25, fromOwner); 170 | await assertBalances(100, 0); 171 | }); 172 | 173 | it('returns true', async () => { 174 | assert.isTrue(await token.transferFrom.call(owner, receiver, 25, fromReceiver)); 175 | // note: calls don't modify state 176 | await assertBalances(100, 0); 177 | }); 178 | 179 | it('triggers a CheckStatus event and transfers funds', async () => { 180 | const event = token.CheckStatus(); 181 | const value = 20; 182 | 183 | await token.transferFrom(owner, receiver, 20, fromReceiver); 184 | await assertBalances(80, 20); 185 | await assertCheckStatusEvent(event, { 186 | reason: 0, 187 | spender: receiver, 188 | from: owner, 189 | to: receiver, 190 | value, 191 | }); 192 | 193 | await token.transferFrom(owner, receiver, 5, fromReceiver); 194 | await assertBalances(75, 25); 195 | }); 196 | }); 197 | }); 198 | }); 199 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Regulated Token System (R-Token) 2 | 3 | Smart Contracts for applying regulatory compliance to tokenized securities issuance and trading 4 | 5 | ## Feedback 6 | 7 | We would love your feedback. Have questions, want to contribute, or have general comments? Ping the team and join the conversation at https://gitter.im/harborhq. 8 | 9 | ## Description 10 | 11 | R-Token is a permissioned token on the Ethereum blockchain, enabling token transfers to occur if and only if they are approved by an on-chain Regulator Service. The Regulator Service can be configured to meet relevant securities regulations, Know Your Customer (KYC) policies, Anti-Money Laundering (AML) requirements, tax laws, and more. Implemented with the correct configurations, the R-Token standard makes compliant transfers possible, both on exchanges and person to person, in ICOs and secondary trades, and across jurisdictions. R-Token enables ERC-20 tokens to be used for regulated securities. 12 | 13 | ## How It Works 14 | 15 | R-Token implements ERC-20 methods `transfer()` and `transferFrom()` with an additional check to determine whether or not a transfer should be allowed to proceed. The implementation of `check()` can take many forms, but a default whitelist approach is implemented by `TokenRegulatorService`. Token and participant-level permissions, when used in different combinations, can be used to satisfy multiple regulatory exemptions. The `ServiceRegistry` is included as a mechanism to facilitate upgrading the R-Token check logic as rules change over time. 16 | 17 | ## Components 18 | 19 | * RegulatedToken 20 | * Permissioned ERC-20 smart contract representing ownership of securities 21 | * Compatible with existing wallets and exchanges that support the ERC-20 token standard 22 | * Overrides the existing ERC-20 transfer method to check with an on-chain Regulator Service for trade approval 23 | * RegulatorService 24 | * Contains the permissions necessary for regulatory compliance 25 | * Relies on off-chain trade approver to set and update permissions 26 | * ServiceRegistry 27 | * Accounts for regulatory requirement changes over time 28 | * Routes the R-Token to the correct version of the Regulator Service 29 | 30 | 31 |
32 |
33 |
51 |
52 |
66 |
67 |