├── .soliumignore ├── .gitignore ├── migrations ├── 1_initial_migration.js └── 2_erc1820.js ├── test ├── helpers │ ├── assertOutOfGas.js │ ├── erc1820.js │ ├── assertRevert.js │ ├── asserts.js │ └── evm.js ├── token │ ├── FixedPriceTokenAgent.js │ └── BulkTransfer.js ├── auth │ ├── Permissioned.js │ └── Authorised.js ├── random │ └── ParticipatoryRandom.js ├── tokensrecipient │ ├── Forwarder.js │ ├── DenyAll.js │ ├── DenySpecifiedTokens.js │ ├── AllowSpecifiedTokens.js │ └── DenyLowAmount.js ├── tokenoperator │ ├── BulkSend.js │ ├── VaultRecipient.js │ ├── FixedTimeRelease.js │ ├── FixedTimeLockup.js │ └── FixedAllowance.js ├── storage │ └── PermissionedStorage.js └── tokenssender │ ├── AllowSpecifiedRecipients.js │ ├── DenySpecifiedRecipients.js │ ├── EmitMessage.js │ ├── Lockup.js │ └── SupplementWitholdingAccount.js ├── contracts ├── test │ ├── HelloWorld.sol │ ├── TestERC20Token.sol │ ├── PermissionedTest1.sol │ ├── AuthorisedTest1.sol │ └── MockEnsRegistrar.sol ├── Migrations.sol ├── router │ └── Proxy.sol ├── math │ └── SafeMath.sol ├── token │ ├── IERC20.sol │ ├── BulkTransfer.sol │ ├── ITokenAgent.sol │ ├── ERC777TokensRecipient.sol │ ├── ERC777TokensSender.sol │ ├── IERC777.sol │ ├── FixedPriceTokenAgent.sol │ ├── ITokenStore.sol │ └── DividendTokenStore.sol ├── tokensrecipient │ ├── DenyAll.sol │ ├── AllowSpecifiedTokens.sol │ ├── DenyLowAmount.sol │ ├── DenySpecifiedTokens.sol │ └── Forwarder.sol ├── registry │ └── ERC1820Implementer.sol ├── tokenoperator │ ├── FixedTimeLockup.sol │ ├── BulkSend.sol │ ├── FixedTimeRelease.sol │ ├── VaultRecipient.sol │ ├── FixedPriceSeller.sol │ ├── FixedAllowance.sol │ ├── SignatureAuthority.sol │ ├── MerkleProofAuthority.sol │ └── PaidSignatureAuthority.sol ├── lifecycle │ ├── Redirectable.sol │ └── Pausable.sol ├── tokenssender │ ├── DenySpecifiedRecipients.sol │ ├── AllowSpecifiedRecipients.sol │ ├── OperatorAllowance.sol │ ├── Lockup.sol │ ├── EmitMessage.sol │ ├── SupplementWitholdingAccount.sol │ └── CounterSignature.sol ├── ens │ └── ENSReverseRegister.sol ├── auth │ ├── Permissioned.sol │ └── Authorised.sol └── storage │ └── PermissionedStorage.sol ├── truffle.js ├── package.json ├── .soliumrc.json └── README.md /.soliumignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Vim 2 | *.sw? 3 | 4 | # Truffle 5 | build 6 | 7 | # Node 8 | node_modules 9 | 10 | # Deployment 11 | deploy*.sh 12 | -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | var Migrations = artifacts.require("./Migrations.sol"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /test/helpers/assertOutOfGas.js: -------------------------------------------------------------------------------- 1 | module.exports = function(error) { 2 | if (error.message.search('out of gas') == -1) { 3 | assert.fail('Call expected to run out of gas; error was ' + error); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /test/helpers/erc1820.js: -------------------------------------------------------------------------------- 1 | const ERC1820Registry = artifacts.require('ERC1820Registry'); 2 | 3 | module.exports = { 4 | instance: () => { 5 | return ERC1820Registry.at('0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24'); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /contracts/test/HelloWorld.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | 4 | // Simple hello world contract 5 | contract HelloWorld { 6 | uint256 HELLO_WORLD = 0x4e110; 7 | function hello() public view returns (uint256) { 8 | return HELLO_WORLD; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /contracts/test/TestERC20Token.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import '../../contracts/token/ERC20Token.sol'; 4 | 5 | 6 | // Test token for ERC20-compatible functions 7 | contract TestERC20Token is ERC20Token { 8 | constructor() ERC20Token(1, "TestToken", "TST", 2, 10000, address(0)) public { } 9 | } 10 | -------------------------------------------------------------------------------- /test/helpers/assertRevert.js: -------------------------------------------------------------------------------- 1 | module.exports = function(error, msg) { 2 | if (error.message.search('revert') == -1) { 3 | assert.fail('Call expected to revert; error was ' + error); 4 | } 5 | if (msg) { 6 | if (error.message.search(msg) == -1) { 7 | const actual = error.message.replace('VM Exception while processing transaction: revert ', ''); 8 | assert.equal(actual, msg); 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /truffle.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | networks: { 3 | development: { 4 | host: "localhost", 5 | port: 8545, 6 | network_id: "*" // Match any network id 7 | } 8 | }, 9 | compilers: { 10 | solc: { 11 | settings: { 12 | optimizer: { 13 | enabled: true, 14 | runs: 200 15 | } 16 | } 17 | } 18 | }, 19 | mocha: { 20 | useColors: true 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wealdtech-solidity", 3 | "description": "Solidity contracts provided by Wealdtech", 4 | "version": "0.9.10", 5 | "devDependencies": { 6 | "mocha": "^5.2.0", 7 | "solidity-sha3": "^0.4.1", 8 | "merkletreejs": "^0.0.18" 9 | }, 10 | "dependencies": { 11 | "erc1820": "0.0.2", 12 | "@ensdomains/ens": "0.4.0", 13 | "@ensdomains/resolver": "0.1.6", 14 | "truffle": "^4.1.14", 15 | "truffle-assertions": "^0.6.3", 16 | "openzeppelin-solidity": "^1.12.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.soliumrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "custom-rules-filename": null, 3 | "rules": { 4 | "imports-on-top": true, 5 | "variable-declarations": true, 6 | "array-declarations": true, 7 | "operator-whitespace": true, 8 | "lbrace": true, 9 | "mixedcase": true, 10 | "camelcase": true, 11 | "uppercase": true, 12 | "no-with": true, 13 | "no-empty-blocks": true, 14 | "no-unused-vars": true, 15 | "double-quotes": true, 16 | "blank-lines": true, 17 | "indentation": true, 18 | "whitespace": true, 19 | "deprecated-suicide": true, 20 | "pragma-on-top": true 21 | } 22 | } -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | 4 | contract Migrations { 5 | address public owner; 6 | uint256 public last_completed_migration; 7 | 8 | modifier restricted() { 9 | if (msg.sender == owner) { 10 | _; 11 | } 12 | } 13 | 14 | constructor() public { 15 | owner = msg.sender; 16 | } 17 | 18 | function setCompleted(uint completed) restricted public { 19 | last_completed_migration = completed; 20 | } 21 | 22 | function upgrade(address newAddress) restricted public { 23 | Migrations upgraded = Migrations(newAddress); 24 | upgraded.setCompleted(last_completed_migration); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /contracts/router/Proxy.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | 4 | contract Proxy { 5 | address public target; 6 | 7 | function () external payable { 8 | address _target = target; 9 | assembly { 10 | let _calldata := mload(0x40) 11 | mstore(0x40, add(_calldata, calldatasize)) 12 | calldatacopy(_calldata, 0x0, calldatasize) 13 | switch delegatecall(gas, _target, _calldata, calldatasize, 0, 0) 14 | case 0 { 15 | revert(0, 0) 16 | } 17 | default { 18 | returndatacopy(0, 0, returndatasize) 19 | return(0, returndatasize) 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test/helpers/asserts.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // asserTokenBalances expects a standard(ish) contract and map of account=>balance pairs 3 | assertTokenBalances: async (tokenContract, balances) => { 4 | for (var account in balances) { 5 | // Compare friendly strings to help with error message viewing 6 | assert.equal((await tokenContract.balanceOf(account)).toString(), balances[account].toString()); 7 | } 8 | // Also compare total supply 9 | const totalSupply = Object.values(balances).reduce((a, b) => a.add(b), web3.utils.toBN('0')); 10 | assert.equal((await tokenContract.totalSupply()).toString(), totalSupply.toString(), 'Total supply is incorrect'); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /contracts/test/PermissionedTest1.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import '../../contracts/auth/Permissioned.sol'; 4 | 5 | 6 | // Permissioned contract for testing 7 | contract PermissionedTest1 is Permissioned { 8 | bytes32 constant public PERMS_SET_INT = keccak256("permissioned: set int"); 9 | bytes32 constant public PERMS_SET_BOOL = keccak256("permissioned: set bool"); 10 | 11 | uint256 public intValue = 0; 12 | bool public boolValue = false; 13 | 14 | constructor() public { 15 | intValue = 0; 16 | boolValue = false; 17 | } 18 | 19 | function setInt(uint256 _intValue) public ifPermitted(msg.sender, PERMS_SET_INT) { 20 | intValue = _intValue; 21 | } 22 | 23 | function setBool(bool _boolValue) public ifPermitted(msg.sender, PERMS_SET_BOOL) { 24 | boolValue = _boolValue; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /contracts/math/SafeMath.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | 4 | /** 5 | * @title SafeMath 6 | * @dev Math operations with safety checks that throw on error. 7 | * Lifted directly from the Zeppelin library 8 | */ 9 | library SafeMath { 10 | function mul(uint256 a, uint256 b) internal pure returns (uint256) { 11 | uint256 c = a * b; 12 | assert(a == 0 || c / a == b); 13 | return c; 14 | } 15 | 16 | function div(uint256 a, uint256 b) internal pure returns (uint256) { 17 | // assert(b > 0); // Solidity automatically throws when dividing by 0 18 | uint256 c = a / b; 19 | // assert(a == b * c + a % b); // There is no case in which this doesn't hold 20 | return c; 21 | } 22 | 23 | function sub(uint256 a, uint256 b) internal pure returns (uint256) { 24 | assert(b <= a); 25 | return a - b; 26 | } 27 | 28 | function add(uint256 a, uint256 b) internal pure returns (uint256) { 29 | uint256 c = a + b; 30 | assert(c >= a); 31 | return c; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /contracts/token/IERC20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | 4 | /** 5 | * @title IERC20 6 | * IERC20 is the interface for ERC20-compliant tokens. 7 | * ERC20 is defined at https://github.com/ethereum/EIPs/issues/20 8 | */ 9 | contract IERC20 { 10 | function name() public view returns (string memory _name); 11 | function symbol() public view returns (string memory _symbol); 12 | function decimals() public view returns (uint8 _decimals); 13 | 14 | function totalSupply() public view returns (uint256 _totalSupply); 15 | function balanceOf(address _owner) public view returns (uint256 _balance); 16 | function transfer(address _owner, uint256 _value) public returns (bool _success); 17 | event Transfer(address indexed _from, address indexed _to, uint256 _value); 18 | 19 | function allowance(address _owner, address _spender) public view returns (uint256 _remaining); 20 | function transferFrom(address _owner, address _to, uint256 _value) public returns (bool _success); 21 | function approve(address _spender, uint256 _value) public returns (bool _success); 22 | event Approval(address indexed _owner, address indexed _spender, uint256 _value); 23 | } 24 | -------------------------------------------------------------------------------- /contracts/token/BulkTransfer.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import './IERC20.sol'; 4 | import '../auth/Permissioned.sol'; 5 | 6 | /** 7 | * @title BulkTransfer 8 | * BulkTransfer allows multiple transfers of an ERC-20 token to different 9 | * addresses with a single transaction from outside of the token contract 10 | * itself. 11 | * 12 | * State of this contract: stable; development complete but the code is 13 | * unaudited. and may contain bugs and/or security holes. Use at your own 14 | * risk. 15 | * 16 | * @author Jim McDonald 17 | * @notice If you use this contract please consider donating some Ether or 18 | * some of your ERC-20 token to wsl.wealdtech.eth to support continued 19 | * development of these and future contracts 20 | */ 21 | contract BulkTransfer is Permissioned { 22 | // Permissions for this contract 23 | bytes32 internal constant PERM_TRANSFER = keccak256("bulk transfer: transfer"); 24 | 25 | function bulkTransfer(IERC20 token, address[] memory _addresses, uint256[] memory _amounts) public ifPermitted(msg.sender, PERM_TRANSFER) { 26 | for (uint256 i = 0; i < _addresses.length; i++) { 27 | token.transfer(_addresses[i], _amounts[i]); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /contracts/tokensrecipient/DenyAll.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import '../token/ERC777TokensRecipient.sol'; 4 | import '../registry/ERC1820Implementer.sol'; 5 | 6 | 7 | /** 8 | * @title DenyAll 9 | * 10 | * An ERC777 tokens recipient contract that refuses all tokens. Commonly 11 | * used to stop an address from receiving tokens if they are unable to 12 | * process them for some reason. 13 | * 14 | * State of this contract: stable; development complete but the code is 15 | * unaudited. and may contain bugs and/or security holes. Use at your own 16 | * risk. 17 | * 18 | * @author Jim McDonald 19 | * @notice If you use this contract please consider donating some Ether or 20 | * some of your ERC-777 token to wsl.wealdtech.eth to support continued 21 | * development of these and future contracts 22 | */ 23 | contract DenyAll is ERC777TokensRecipient, ERC1820Implementer { 24 | constructor() public { 25 | implementInterface("ERC777TokensRecipient", false); 26 | } 27 | 28 | function tokensReceived(address operator, address from, address to, uint256 value, bytes calldata data, bytes calldata operatorData) external { 29 | (from, to, value, data, operator, operatorData); 30 | revert(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /contracts/test/AuthorisedTest1.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import '../../contracts/auth/Authorised.sol'; 4 | 5 | 6 | // Authorised contract for testing 7 | contract AuthorisedTest1 is Authorised { 8 | bytes32 constant public ACTION_SET_INT = keccak256("action: set int"); 9 | bytes32 constant public ACTION_SET_INT_ONCE = keccak256("action: set int once"); 10 | 11 | uint256 public intValue = 0; 12 | 13 | function hash() public view returns (bytes32) { 14 | return keccak256(abi.encodePacked(msg.sender, ACTION_SET_INT, uint256(8))); 15 | } 16 | 17 | event Mark(bool); 18 | function setInt(uint256 _intValue, bytes memory _signature) public { 19 | // check general authorisation 20 | if (!authorise(keccak256(abi.encodePacked(msg.sender, ACTION_SET_INT)), _signature, true)) { 21 | // check single-use authorisation 22 | if(!authorise(keccak256(abi.encodePacked(msg.sender, ACTION_SET_INT_ONCE)), _signature, false)) { 23 | // check general authorisation with value 24 | if (!authorise(keccak256(abi.encodePacked(msg.sender, ACTION_SET_INT, _intValue)), _signature, true)) { 25 | // Check single-use authorisation with value 26 | require(authorise(keccak256(abi.encodePacked(msg.sender, ACTION_SET_INT_ONCE, _intValue)), _signature, false)); 27 | } 28 | } 29 | } 30 | intValue = _intValue; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /contracts/registry/ERC1820Implementer.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import 'erc1820/contracts/ERC1820Client.sol'; 4 | import 'erc1820/contracts/ERC1820ImplementerInterface.sol'; 5 | 6 | 7 | /** 8 | * @title ERC1820Implementer 9 | * 10 | * A helper for contracts that implement one or more ERC1820-registered 11 | * interfaces. 12 | * 13 | * @author Jim McDonald 14 | */ 15 | contract ERC1820Implementer is ERC1820Client, ERC1820ImplementerInterface { 16 | mapping(bytes32=>bool) implemented; 17 | 18 | /** 19 | * implementInterface provides an easy way to note support of an interface. 20 | * @param _interface the name of the interface the contract supports 21 | * @param _register if the implementation should be registered with the ERC1820 registry 22 | */ 23 | function implementInterface(string memory _interface, bool _register) public { 24 | bytes32 hash = keccak256(abi.encodePacked(_interface)); 25 | implemented[hash] = true; 26 | if (_register) { 27 | setInterfaceImplementation(_interface, address(this)); 28 | } 29 | } 30 | 31 | /** 32 | * canImplementInterfaceForAddress is the ERC1820 function 33 | */ 34 | function canImplementInterfaceForAddress(bytes32 _interfaceHash, address _addr) external view returns(bytes32) { 35 | (_addr); 36 | if (implemented[_interfaceHash]) { 37 | return ERC1820_ACCEPT_MAGIC; 38 | } else { 39 | return 0; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /contracts/tokenoperator/FixedTimeLockup.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import '../token/IERC777.sol'; 4 | import './FixedTimeRelease.sol'; 5 | import './FixedAllowance.sol'; 6 | 7 | 8 | /** 9 | * @title FixedTimeLockup 10 | * 11 | * An ERC777 token operator contract that releases a limited number of 12 | * tokens for a given (token, holder, transferer) as well as having a 13 | * timestamp lock for the release of the tokens. 14 | * 15 | * This is a combination of the FixedTimeRelease and FixedAllowance 16 | * token operator contracts. 17 | * State of this contract: stable; development complete but the code is 18 | * unaudited. and may contain bugs and/or security holes. Use at your own 19 | * risk. 20 | * 21 | * @author Jim McDonald 22 | * @notice If you use this contract please consider donating some Ether or 23 | * some of your ERC-777 token to wsl.wealdtech.eth to support continued 24 | * development of these and future contracts 25 | */ 26 | contract FixedTimeLockup is FixedTimeRelease, FixedAllowance { 27 | function send(IERC777 _token, address _holder, address _recipient, uint256 _amount) public { 28 | preSend(_token, _holder, msg.sender, _amount); 29 | _token.operatorSend(_holder, _recipient, _amount, "", ""); 30 | } 31 | 32 | function preSend(IERC777 _token, address _holder, address _transferer, uint256 _amount) internal { 33 | FixedTimeRelease.preSend(_token, _holder); 34 | FixedAllowance.preSend(_token, _holder, _transferer, _amount); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /contracts/token/ITokenAgent.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | // Copyright © 2017 Weald Technology Trading Limited 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 | import './IERC20.sol'; 17 | 18 | 19 | /** 20 | * @title ITokenAgent 21 | * ITokenAgent is the interface for contracts that issue tokens from an 22 | * ERC20 source. 23 | * 24 | * State of this contract: stable; development complete but the code is 25 | * unaudited. and may contain bugs and/or security holes. Use at your own 26 | * risk. 27 | * 28 | * @author Jim McDonald 29 | * @notice If you use this contract please consider donating some Ether or 30 | * some of your ERC-20 token to wsl.wealdtech.eth to support continued 31 | * development of these and future contracts 32 | */ 33 | contract ITokenAgent { 34 | /** 35 | * @dev active states if the agent is currently active. 36 | */ 37 | function active() public view returns (bool); 38 | 39 | /** 40 | * @dev provide the number of tokens available. 41 | */ 42 | function tokensAvailable() public view returns (uint256); 43 | } 44 | -------------------------------------------------------------------------------- /contracts/token/ERC777TokensRecipient.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | // Copyright © 2017, 2018 Weald Technology Trading Limited 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 | * @title ERC777TokensRecipient 19 | * ERC777TokensRecipient is the interface for contracts that handle 20 | * receipt of tokens from ERC777 token contracts 21 | * 22 | * @author Jim McDonald 23 | */ 24 | interface ERC777TokensRecipient { 25 | 26 | /** 27 | * Function to act on receipt of tokens for a given contract. 28 | * 29 | * @param operator is the address that carried out the transfer 30 | * @param from is the address from which the tokens have been transferred 31 | * @param to is the address to which the tokens have been transferred 32 | * @param value is the value of tokens transferred 33 | * @param data is data supplied by the user for the transfer 34 | * @param operatorData is data supplied by the operator for the transfer 35 | */ 36 | function tokensReceived(address operator, address from, address to, uint256 value, bytes calldata data, bytes calldata operatorData) external; 37 | } 38 | -------------------------------------------------------------------------------- /contracts/token/ERC777TokensSender.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | // Copyright © 2017, 2018 Weald Technology Trading Limited 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 | * @title ERC777TokensSender 19 | * ERC777TokensSender is the interface for contracts that handle pre-send 20 | * of tokens from ERC777 token contracts 21 | * 22 | * @author Jim McDonald 23 | */ 24 | interface ERC777TokensSender { 25 | 26 | /** 27 | * Function to act prior to send of tokens for a given contract. 28 | * 29 | * @param operator is the address that carried out the transfer 30 | * @param from is the address from which the tokens will be transferred 31 | * @param to is the address to which the tokens will be transferred 32 | * @param amount is the amount of tokens that will be transferred 33 | * @param data is data supplied by the user for the transfer 34 | * @param operatorData is data supplied by the operator for the transfer 35 | */ 36 | function tokensToSend(address operator, address from, address to, uint256 amount, bytes calldata data, bytes calldata operatorData) external; 37 | } 38 | -------------------------------------------------------------------------------- /test/helpers/evm.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | increaseTime: async (addSeconds) => { 3 | // return await web3.currentProvider.send('evm_increaseTime', [addSeconds]); 4 | var res; 5 | await web3.currentProvider.send({ jsonrpc: '2.0', method: 'evm_increaseTime', params: [addSeconds], id: 0}, (err, r) => { res = r }); 6 | while (res == undefined) { 7 | await new Promise(resolve => setTimeout(resolve, 100)); 8 | } 9 | return res; 10 | }, 11 | mine: async () => { 12 | // return web3.currentProvider.send({ jsonrpc: "2.0", method: "evm_mine", params: [], id: 0 }); 13 | var res; 14 | await web3.currentProvider.send({ jsonrpc: '2.0', method: 'evm_mine', params: [], id: 0}, (err, r) => { res = r }); 15 | while (res == undefined) { 16 | await new Promise(resolve => setTimeout(resolve, 100)); 17 | } 18 | return res; 19 | }, 20 | currentOffset: async () => { 21 | // if (typeof web3.currentProvider.sendAsync !== "function") { 22 | // web3.currentProvider.sendAsync = function() { 23 | // return web3.currentProvider.send.apply( 24 | // web3.currentProvider, arguments 25 | // ); 26 | // }; 27 | // } 28 | 29 | var res; 30 | await web3.currentProvider.send({ jsonrpc: '2.0', method: 'evm_increaseTime', params: [0], id: 0}, (err, r) => { res = r }); 31 | while (res == undefined) { 32 | await new Promise(resolve => setTimeout(resolve, 100)); 33 | } 34 | return res; 35 | 36 | // return await web3.currentProvider.send({ jsonrpc: '2.0', method: 'evm_increaseTime', params: [0], id: 0}); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /contracts/token/IERC777.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | 4 | /** 5 | * @title IERC777 6 | * IERC777 is the interface for ERC777-compliant tokens. 7 | * ERC777 is defined at https://github.com/ethereum/EIPs/issues/777 8 | * 9 | * @author Jim McDonald 10 | */ 11 | contract IERC777 { 12 | function name() external view returns (string memory); 13 | function symbol() external view returns (string memory); 14 | function totalSupply() external view returns (uint256); 15 | function balanceOf(address owner) external view returns (uint256); 16 | function granularity() external view returns (uint256); 17 | 18 | function defaultOperators() external view returns (address[] memory); 19 | function authorizeOperator(address operator) external; 20 | function revokeOperator(address operator) external; 21 | function isOperatorFor(address operator, address tokenHolder) external view returns (bool); 22 | 23 | function send(address to, uint256 amount, bytes calldata data) external; 24 | function operatorSend(address from, address to, uint256 amount, bytes calldata data, bytes calldata operatorData) external; 25 | 26 | function burn(uint256 amount, bytes calldata data) external; 27 | function operatorBurn(address from, uint256 amount, bytes calldata data, bytes calldata operatorData) external; 28 | 29 | event Sent(address indexed operator, address indexed from, address indexed to, uint256 amount, bytes data, bytes operatorData); 30 | event Minted(address indexed operator, address indexed to, uint256 amount, bytes data, bytes operatorData); 31 | event Burned(address indexed operator, address indexed from, uint256 amount, bytes data, bytes operatorData); 32 | event AuthorizedOperator(address indexed operator, address indexed tokenHolder); 33 | event RevokedOperator(address indexed operator, address indexed tokenHolder); 34 | } 35 | -------------------------------------------------------------------------------- /contracts/tokenoperator/BulkSend.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import '../token/IERC777.sol'; 4 | 5 | 6 | /** 7 | * @title BulkSend 8 | * 9 | * An ERC777 token operator contract that provides a number of bulk send 10 | * functions 11 | * 12 | * State of this contract: stable; development complete but the code is 13 | * unaudited. and may contain bugs and/or security holes. Use at your own 14 | * risk. 15 | * 16 | * @author Jim McDonald 17 | * @notice If you use this contract please consider donating some Ether or 18 | * some of your ERC-777 token to wsl.wealdtech.eth to support continued 19 | * development of these and future contracts 20 | */ 21 | contract BulkSend { 22 | /** 23 | * Send a given amount of tokens to multiple recipients 24 | * @param _token the address of the token contract 25 | * @param _recipients the list of recipents 26 | * @param _amount the amount of tokens to send to each recipient 27 | * @param _data the data to attach to each send 28 | */ 29 | function send(IERC777 _token, address[] memory _recipients, uint256 _amount, bytes memory _data) public { 30 | for (uint256 i = 0; i < _recipients.length; i++) { 31 | _token.operatorSend(msg.sender, _recipients[i], _amount, _data, ""); 32 | } 33 | } 34 | 35 | /** 36 | * Send individual amounts of tokens to multiple recipients 37 | * @param _token the address of the token contract 38 | * @param _recipients the list of recipents 39 | * @param _amounts the amount of tokens to send to each recipient 40 | * @param _data the data to attach to each send 41 | */ 42 | function sendAmounts(IERC777 _token, address[] memory _recipients, uint256[] memory _amounts, bytes memory _data) public { 43 | for (uint256 i = 0; i < _recipients.length; i++) { 44 | _token.operatorSend(msg.sender, _recipients[i], _amounts[i], _data, ""); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /contracts/tokensrecipient/AllowSpecifiedTokens.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import '../token/ERC777TokensRecipient.sol'; 4 | import '../registry/ERC1820Implementer.sol'; 5 | 6 | 7 | /** 8 | * @title AllowSpecifiedTokens 9 | * 10 | * An ERC777 tokens recipient contract that only allows receipt from 11 | * specified token contracts. Commonly used to keep "clean" addresses 12 | * which cannot be targets of air drops etc. without permission. 13 | * 14 | * State of this contract: stable; development complete but the code is 15 | * unaudited. and may contain bugs and/or security holes. Use at your own 16 | * risk. 17 | * 18 | * @author Jim McDonald 19 | * @notice If you use this contract please consider donating some Ether or 20 | * some of your ERC-777 token to wsl.wealdtech.eth to support continued 21 | * development of these and future contracts 22 | */ 23 | contract AllowSpecifiedTokens is ERC777TokensRecipient, ERC1820Implementer { 24 | // Mapping is recipient=>token=>allowed 25 | mapping(address=>mapping(address=>bool)) allowed; 26 | 27 | // An event emitted when an allowed token is added to the list 28 | event TokenAdded(address recipient, address token); 29 | // An event emitted when an allowed token is removed from the list 30 | event TokenRemoved(address recipient, address token); 31 | 32 | constructor() public { 33 | implementInterface("ERC777TokensRecipient", false); 34 | } 35 | 36 | function addToken(address token) external { 37 | allowed[msg.sender][token] = true; 38 | emit TokenAdded(msg.sender, token); 39 | } 40 | 41 | function removeToken(address token) external { 42 | allowed[msg.sender][token] = false; 43 | emit TokenRemoved(msg.sender, token); 44 | } 45 | 46 | function tokensReceived(address operator, address holder, address recipient, uint256 amount, bytes calldata data, bytes calldata operatorData) external { 47 | (operator, holder, amount, data, operatorData); 48 | require(allowed[recipient][msg.sender], "not allowed to receive that token"); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /contracts/tokensrecipient/DenyLowAmount.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import '../token/ERC777TokensRecipient.sol'; 4 | import '../registry/ERC1820Implementer.sol'; 5 | 6 | 7 | /** 8 | * @title DenyLowAmount 9 | * 10 | * An ERC777 tokens recipient contract that refuses receipt of transfers 11 | * below a specified amount. Commonly used to stop low-value transfers. 12 | * 13 | * State of this contract: stable; development complete but the code is 14 | * unaudited. and may contain bugs and/or security holes. Use at your own 15 | * risk. 16 | * 17 | * @author Jim McDonald 18 | * @notice If you use this contract please consider donating some Ether or 19 | * some of your ERC-777 token to wsl.wealdtech.eth to support continued 20 | * development of these and future contracts 21 | */ 22 | contract DenyLowAmount is ERC777TokensRecipient, ERC1820Implementer { 23 | // recipient=>token=>minimum amount 24 | mapping(address=>mapping(address=>uint256)) minimumAmounts; 25 | 26 | // An event emitted when a minimum transfer amount is set 27 | event MinimumAmountSet(address recipient, address token, uint256 amount); 28 | // An event emitted when a minimum transfer amount is cleared 29 | event MinimumAmountCleared(address recipient, address token); 30 | 31 | constructor() public { 32 | implementInterface("ERC777TokensRecipient", false); 33 | } 34 | 35 | function setMinimumAmount(address token, uint256 amount) external { 36 | minimumAmounts[msg.sender][token] = amount; 37 | emit MinimumAmountSet(msg.sender, token, amount); 38 | } 39 | 40 | function clearMinimumAmount(address token) external { 41 | minimumAmounts[msg.sender][token] = 0; 42 | emit MinimumAmountCleared(msg.sender, token); 43 | } 44 | 45 | function tokensReceived(address operator, address holder, address recipient, uint256 amount, bytes calldata data, bytes calldata operatorData) external { 46 | (operator, holder, data, operatorData); 47 | require(amount > minimumAmounts[recipient][msg.sender], "transfer value too low to be accepted"); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /test/token/FixedPriceTokenAgent.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assertRevert = require('../helpers/assertRevert'); 4 | const FixedPriceTokenAgent = artifacts.require('./token/FixedPriceTokenAgent.sol'); 5 | const TestERC20Token = artifacts.require('./samplecontracts/TestERC20Token.sol'); 6 | 7 | contract('FixedPriceTokenAgent', accounts => { 8 | const tokenOwner = accounts[0]; 9 | const faucetOwner = accounts[1]; 10 | const requestor = accounts[2]; 11 | var token; 12 | var faucet; 13 | 14 | it('can set up the contracts', async() => { 15 | token = await TestERC20Token.new({gas: 10000000}); 16 | await token.activate(); 17 | faucet = await FixedPriceTokenAgent.new(token.address, tokenOwner, 10, { 18 | from: faucetOwner 19 | }); 20 | }); 21 | 22 | it('can approve token transfers by the faucet agent', async() => { 23 | var active = await faucet.active(); 24 | assert.equal(active, false); 25 | await token.approve(faucet.address, 1000, { 26 | from: tokenOwner 27 | }); 28 | const tokens = await token.allowance(tokenOwner, faucet.address); 29 | assert.equal(tokens, 1000); 30 | active = await faucet.active(); 31 | assert.equal(active, true); 32 | }); 33 | 34 | it('rejects requests for too many tokens', async() => { 35 | try { 36 | await faucet.sendTransaction({ 37 | from: requestor, 38 | value: 101 39 | }); 40 | assert.fail(); 41 | } catch (error) { 42 | assertRevert(error); 43 | } 44 | }); 45 | 46 | it('can exchange Ether for tokens', async() => { 47 | await faucet.sendTransaction({ 48 | from: requestor, 49 | value: 10 50 | }); 51 | const tokens = await token.balanceOf(requestor); 52 | assert.equal(tokens, 100); 53 | }); 54 | 55 | it('can be drained', async() => { 56 | await faucet.sendTransaction({ 57 | from: requestor, 58 | value: 90 59 | }); 60 | var active = await faucet.active(); 61 | assert.equal(active, false); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /contracts/tokensrecipient/DenySpecifiedTokens.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import '../token/ERC777TokensRecipient.sol'; 4 | import '../registry/ERC1820Implementer.sol'; 5 | 6 | 7 | /** 8 | * @title DenySpecifiedTokens 9 | * 10 | * An ERC777 tokens recipient contract that denies receipt from 11 | * specified token contracts. Commonly used to reject "junk" tokens. 12 | * 13 | * State of this contract: stable; development complete but the code is 14 | * unaudited. and may contain bugs and/or security holes. Use at your own 15 | * risk. 16 | * 17 | * @author Jim McDonald 18 | * @notice If you use this contract please consider donating some Ether or 19 | * some of your ERC-777 token to wsl.wealdtech.eth to support continued 20 | * development of these and future contracts 21 | */ 22 | contract DenySpecifiedTokens is ERC777TokensRecipient, ERC1820Implementer { 23 | // Mapping is recipient=>token=>disallowed 24 | mapping(address=>mapping(address=>bool)) disallowed; 25 | 26 | // An event emitted when a disallowed token is added to the list 27 | event TokenAdded(address recipient, address token); 28 | // An event emitted when a disallowed token is removed from the list 29 | event TokenRemoved(address recipient, address token); 30 | 31 | constructor() public { 32 | implementInterface("ERC777TokensRecipient", false); 33 | } 34 | 35 | /** 36 | * Add a token to the list of tokens that this address will refuse. 37 | */ 38 | function addToken(address token) external { 39 | disallowed[msg.sender][token] = true; 40 | emit TokenAdded(msg.sender, token); 41 | } 42 | 43 | /** 44 | * Remove a token from the list of tokens that this address will refuse. 45 | */ 46 | function removeToken(address token) external { 47 | disallowed[msg.sender][token] = false; 48 | emit TokenRemoved(msg.sender, token); 49 | } 50 | 51 | function tokensReceived(address operator, address holder, address recipient, uint256 amount, bytes calldata data, bytes calldata operatorData) external { 52 | (operator, holder, amount, data, operatorData); 53 | require(!disallowed[recipient][msg.sender], "token is explicitly disallowed"); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /contracts/lifecycle/Redirectable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | // Copyright © 2017, 2018 Weald Technology Trading Limited 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 | import "../auth/Permissioned.sol"; 17 | 18 | 19 | /** 20 | * @title Redirectable 21 | * Redirectable provides a mechanism for contracts to be able to provide 22 | * potential callees with the address of the contract that should be 23 | * called instead of this one. It is commonly used when a contract has 24 | * been upgraded and should no longer be called. 25 | * 26 | * Calling setRedirect() requires the caller to have the PERM_REDIRECT 27 | * permission. 28 | * 29 | * State of this contract: stable; development complete but the code is 30 | * unaudited. and may contain bugs and/or security holes. Use at your own 31 | * risk. 32 | * 33 | * @author Jim McDonald 34 | * @notice If you use this contract please consider donating some Ether or 35 | * some of your ERC-20 token to wsl.wealdtech.eth to support continued 36 | * development of these and future contracts 37 | */ 38 | contract Redirectable is Permissioned { 39 | event Redirect(address redirect); 40 | 41 | bytes32 public constant PERM_REDIRECT = keccak256("_redirectable"); 42 | 43 | // The address to which calls should be redirected 44 | address public redirect; 45 | 46 | /** 47 | * @dev set the redirect address. 48 | * This can be called multiple times to avoid chaining of this call 49 | * when 50 | */ 51 | function setRedirect(address _redirect) public ifPermitted(msg.sender, PERM_REDIRECT) { 52 | redirect = _redirect; 53 | emit Redirect(redirect); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /test/token/BulkTransfer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assertRevert = require('../helpers/assertRevert'); 4 | const ERC20Token = artifacts.require('./ERC20Token.sol'); 5 | const BulkTransfer = artifacts.require('./BulkTransfer.sol'); 6 | 7 | contract('BulkTransfer', accounts => { 8 | var token; 9 | var bulkTransfer; 10 | 11 | let expectedBalance0 = 100000000000000000000000000; 12 | 13 | it('has an initial balance', async function() { 14 | token = await ERC20Token.new(1, 'Test token', 'TST', 18, '100000000000000000000000000', '0x0000000000000000000000000000000000000000', { 15 | from: accounts[0], 16 | gas: 10000000 17 | }); 18 | await token.activate({ 19 | from: accounts[0] 20 | }); 21 | var balance = await token.balanceOf(accounts[0]); 22 | assert.equal(await token.balanceOf(accounts[0]), expectedBalance0); 23 | 24 | bulkTransfer = await BulkTransfer.new(); 25 | 26 | await token.transfer(bulkTransfer.address, 1001001000); 27 | assert.equal(await token.balanceOf(bulkTransfer.address), 1001001000); 28 | }); 29 | 30 | it('cannot be stolen', async function() { 31 | var addresses = []; 32 | var amounts = []; 33 | addresses.push(accounts[1]); 34 | amounts.push(1000); 35 | try { 36 | await bulkTransfer.bulkTransfer(token.address, addresses, amounts, {from: accounts[1]}); 37 | assert.fail(); 38 | } catch (error) { 39 | assertRevert(error); 40 | } 41 | }); 42 | 43 | it('can bulk transfer', async function() { 44 | var addresses = []; 45 | var amounts = []; 46 | addresses.push(accounts[1]); 47 | amounts.push(1000); 48 | addresses.push(accounts[2]); 49 | amounts.push(1000000); 50 | addresses.push(accounts[3]); 51 | amounts.push(1000000000); 52 | await bulkTransfer.bulkTransfer(token.address, addresses, amounts); 53 | assert.equal(await token.balanceOf(accounts[0]), 99999999999999998998999000); 54 | assert.equal(await token.balanceOf(accounts[1]), 1000); 55 | assert.equal(await token.balanceOf(accounts[2]), 1000000); 56 | assert.equal(await token.balanceOf(accounts[3]), 1000000000); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /test/auth/Permissioned.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assertRevert = require('../helpers/assertRevert'); 4 | const PermissionedTest1 = artifacts.require('samplecontracts/PermissionedTest1.sol'); 5 | 6 | const sha3 = require('solidity-sha3').default; 7 | 8 | const PERMS_SET_INT = sha3('permissioned: set int'); 9 | const PERMS_SET_BOOL = sha3('permissioned: set bool'); 10 | 11 | contract('Permissioned', accounts => { 12 | it('cannot access a method without permission', async function() { 13 | const instance = await PermissionedTest1.new(); 14 | try { 15 | await instance.setBool(true, {from: accounts[1]}); 16 | assert.fail(); 17 | } catch(error) { 18 | assertRevert(error); 19 | } 20 | }); 21 | 22 | it('can access a method with permission', async function() { 23 | const instance = await PermissionedTest1.new(); 24 | await instance.setPermission(accounts[1], PERMS_SET_BOOL, true); 25 | await instance.setBool(true, {from: accounts[1]}); 26 | }); 27 | 28 | it('does not leak permissions across accounts', async function() { 29 | const instance = await PermissionedTest1.new(); 30 | try { 31 | await instance.setBool(true, {from: accounts[2]}); 32 | assert.fail(); 33 | } catch(error) { 34 | assertRevert(error); 35 | } 36 | }); 37 | 38 | it('does not leak permissions across permission IDs', async function() { 39 | const instance = await PermissionedTest1.new(); 40 | try { 41 | await instance.setInt(1, {from: accounts[1]}); 42 | assert.fail(); 43 | } catch(error) { 44 | assertRevert(error); 45 | } 46 | }); 47 | 48 | it('can have permissions revoked', async function() { 49 | const instance = await PermissionedTest1.new(); 50 | await instance.setPermission(accounts[1], PERMS_SET_BOOL, false); 51 | try { 52 | await instance.setBool(true, {from: accounts[1]}); 53 | assert.fail(); 54 | } catch(error) { 55 | assertRevert(error); 56 | } 57 | }); 58 | 59 | it('does not allow permissions to be set by an unauthorised user', async function() { 60 | const instance = await PermissionedTest1.new(); 61 | try { 62 | await instance.setPermission(accounts[0], PERMS_SET_BOOL, true, {from: accounts[1]}); 63 | assert.fail(); 64 | } catch(error) { 65 | assertRevert(error); 66 | } 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /contracts/tokenssender/DenySpecifiedRecipients.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import '../token/ERC777TokensSender.sol'; 4 | import '../registry/ERC1820Implementer.sol'; 5 | 6 | 7 | /** 8 | * @title DenySpecifiedRecipients 9 | * 10 | * An ERC777 tokens sender contract that denies token transfers to 11 | * specified recipients as provided by holders. 12 | * 13 | * State of this contract: stable; development complete but the code is 14 | * unaudited. and may contain bugs and/or security holes. Use at your own 15 | * risk. 16 | * 17 | * @author Jim McDonald 18 | * @notice If you use this contract please consider donating some Ether or 19 | * some of your ERC-777 token to wsl.wealdtech.eth to support continued 20 | * development of these and future contracts 21 | */ 22 | contract DenySpecifiedRecipients is ERC777TokensSender, ERC1820Implementer { 23 | // Mapping is holder=>recipient=>denied 24 | mapping(address=>mapping(address=>bool)) public recipients; 25 | 26 | // An event emitted when a recipient is set 27 | event RecipientSet(address holder, address recipient); 28 | // An event emitted when a recipient is cleared 29 | event RecipientCleared(address holder, address recipient); 30 | 31 | constructor() public { 32 | implementInterface("ERC777TokensSender", false); 33 | } 34 | 35 | /** 36 | * setRecipient sets a recipient to which transfers are denied 37 | */ 38 | function setRecipient(address _recipient) external { 39 | recipients[msg.sender][_recipient] = true; 40 | emit RecipientSet(msg.sender, _recipient); 41 | } 42 | 43 | /** 44 | * clearRecipient removes a recipient to which transfers are denied 45 | */ 46 | function clearRecipient(address _recipient) external { 47 | recipients[msg.sender][_recipient] = false; 48 | emit RecipientCleared(msg.sender, _recipient); 49 | } 50 | 51 | function getRecipient(address _holder, address _recipient) external view returns (bool) { 52 | return recipients[_holder][_recipient]; 53 | } 54 | 55 | function tokensToSend(address operator, address holder, address recipient, uint256 value, bytes calldata data, bytes calldata operatorData) external { 56 | (operator, value, data, operatorData); 57 | 58 | require(!recipients[holder][recipient], "transfers to that recipient are blocked"); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /contracts/tokenssender/AllowSpecifiedRecipients.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import '../token/ERC777TokensSender.sol'; 4 | import '../registry/ERC1820Implementer.sol'; 5 | 6 | 7 | /** 8 | * @title AllowSpecifiedRecipients 9 | * 10 | * An ERC777 tokens sender contract that only allows token transfers to 11 | * specified recipients as provided by holders. 12 | * 13 | * State of this contract: stable; development complete but the code is 14 | * unaudited. and may contain bugs and/or security holes. Use at your own 15 | * risk. 16 | * 17 | * @author Jim McDonald 18 | * @notice If you use this contract please consider donating some Ether or 19 | * some of your ERC-777 token to wsl.wealdtech.eth to support continued 20 | * development of these and future contracts 21 | */ 22 | contract AllowSpecifiedRecipients is ERC777TokensSender, ERC1820Implementer { 23 | // Mapping is holder=>recipient=>allowed 24 | mapping(address=>mapping(address=>bool)) public recipients; 25 | 26 | // An event emitted when a recipient is set 27 | event RecipientSet(address holder, address recipient); 28 | // An event emitted when a recipient is cleared 29 | event RecipientCleared(address holder, address recipient); 30 | 31 | constructor() public { 32 | implementInterface("ERC777TokensSender", false); 33 | } 34 | 35 | /** 36 | * setRecipient sets a recipient to which transfers are allowed 37 | */ 38 | function setRecipient(address _recipient) external { 39 | recipients[msg.sender][_recipient] = true; 40 | emit RecipientSet(msg.sender, _recipient); 41 | } 42 | 43 | /** 44 | * clearRecipient removes a recipient to which transfers are allowed 45 | */ 46 | function clearRecipient(address _recipient) external { 47 | recipients[msg.sender][_recipient] = false; 48 | emit RecipientCleared(msg.sender, _recipient); 49 | } 50 | 51 | function getRecipient(address _holder, address _recipient) external view returns (bool) { 52 | return recipients[_holder][_recipient]; 53 | } 54 | 55 | function tokensToSend(address operator, address holder, address recipient, uint256 value, bytes calldata data, bytes calldata operatorData) external { 56 | (operator, value, data, operatorData); 57 | 58 | require(recipients[holder][recipient], "not allowed to send to that recipient"); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /contracts/tokenssender/OperatorAllowance.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import '../math/SafeMath.sol'; 4 | import '../token/ERC777TokensSender.sol'; 5 | import '../registry/ERC1820Implementer.sol'; 6 | 7 | 8 | /** 9 | * @title OperatorAllowance 10 | * 11 | * An ERC777 tokens sender contract that provides operators with an 12 | * allowance of tokens to send rather than complete control of all 13 | * tokens in the sender's account. 14 | * 15 | * State of this contract: stable; development complete but the code is 16 | * unaudited. and may contain bugs and/or security holes. Use at your own 17 | * risk. 18 | * 19 | * @author Jim McDonald 20 | * @notice If you use this contract please consider donating some Ether or 21 | * some of your ERC-777 token to wsl.wealdtech.eth to support continued 22 | * development of these and future contracts 23 | */ 24 | contract OperatorAllowance is ERC777TokensSender, ERC1820Implementer { 25 | using SafeMath for uint256; 26 | 27 | // Mapping is holder=>operator=>token=>allowance 28 | mapping(address=>mapping(address=>mapping(address=>uint256))) public allowances; 29 | 30 | // An event emitted when an allowance is set 31 | event AllowanceSet(address holder, address operator, address token, uint256 allowance); 32 | 33 | constructor() public { 34 | implementInterface("ERC777TokensSender", false); 35 | } 36 | 37 | /** 38 | * setAllowance sets an allowance. 39 | */ 40 | function setAllowance(address _operator, address _token, uint256 _currentAllowance, uint256 _newAllowance) external { 41 | require(allowances[msg.sender][_operator][_token] == _currentAllowance, "current allowance incorrect"); 42 | allowances[msg.sender][_operator][_token] = _newAllowance; 43 | emit AllowanceSet(msg.sender, _operator, _token, _newAllowance); 44 | } 45 | 46 | /** 47 | * getAllowance gets an allowance. 48 | */ 49 | function getAllowance(address _holder, address _operator, address _token) external view returns (uint256) { 50 | return allowances[_holder][_operator][_token]; 51 | } 52 | 53 | function tokensToSend(address operator, address holder, address recipient, uint256 amount, bytes calldata data, bytes calldata operatorData) external { 54 | (recipient, data, operatorData); 55 | 56 | if (operator == holder) { 57 | // This is a user send not an operator send; ignore 58 | return; 59 | } 60 | 61 | require (allowances[holder][operator][msg.sender] >= amount, "allowance too low"); 62 | allowances[holder][operator][msg.sender] = allowances[holder][operator][msg.sender].sub(amount); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Wealdtech Solidity 2 | 3 | Contracts and contract pieces for Solidity. 4 | 5 | [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/wealdtech/wealdtech-solidity) 6 | 7 | # What is this? 8 | 9 | Wealdtech Solidity provides a number of contracts, some with specific functionality and others complete examples. A brief overview of each contract is provided below; full details of the functionality and operation of each contract is available in the relevant source code. 10 | 11 | ## Authorisation 12 | 13 | ### Permissioned 14 | 15 | Permission structure and modifiers. Permissions are described by the tuple (address, permission id). The permission ID is a keccak256() hash of a developer-selected string. It is recommended that the developer use a (short) prefix for their permissions to avoid clashes, for example a permission might be called "my contract: upgrade". 16 | 17 | ## ENS 18 | 19 | ### ENSReverseRegister 20 | 21 | ENS resolves names to addresses, and addresses to names. But to set the resolution from address to name the transaction must come from the address in question. This contract sets the reverse resolution as part of the contract initialisation. 22 | 23 | ## Lifecycle 24 | 25 | ### Managed 26 | 27 | Managed provides full lifecycle management for contracts. A managed contract provides a number of benefits. The primary one is reducing the number of failed transactions by providing information about the state of the contract prior to sending transactions. This cuts down on unnecessary network operations as well as reducing funds lost to transactions that will not complete successfully. 28 | 29 | ## Token 30 | 31 | ### ITokenStore 32 | 33 | ITokenStore is the interface for storing tokens as part of a token. 34 | 35 | ### SimpleTokenStore 36 | 37 | SimpleTokenStore provides permissioned storage for an ERC-20 contract separate from the contract itself. This separation of token logic and storage allows upgrades to token functionality without requiring expensive copying of the token allocation information. 38 | 39 | ### DividendTokenStore 40 | 41 | DividendTokenStore is an enhancement of the SimpleTokenStore that provides the ability to issue token-based dividends in an efficient manner. 42 | 43 | ### IERC20 44 | 45 | IERC20 is the interface for ERC20-compliant tokens. 46 | 47 | ### Token 48 | 49 | Token is an ERC20-compliant token implementation with significantly upgraded functionality including a separate token store, cheap bulk transfers and easy upgrading. 50 | 51 | ### ITokenAgent 52 | 53 | ITokenAgent is the interface for contracts that issue tokens from an ERC20 source. 54 | 55 | ### FixedPriceTokenAgent 56 | 57 | FixedPriceTokenAgent is a simple token agent that sells its tokens at a fixed exchange rate. 58 | 59 | -------------------------------------------------------------------------------- /contracts/tokensrecipient/Forwarder.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import '../token/ERC777TokensRecipient.sol'; 4 | import '../token/IERC777.sol'; 5 | import '../registry/ERC1820Implementer.sol'; 6 | 7 | 8 | /** 9 | * @title Forwarder 10 | * 11 | * An ERC777 tokens recipient contract that forwards tokens to another 12 | * address. Commonly used to forward received tokens to an aggregate 13 | * store, for example an exchange could forward deposited tokens directly 14 | * to cold storage. 15 | * 16 | * Note that because this contract transfers token on behalf of the 17 | * recipient it requires operator privileges. 18 | * 19 | * State of this contract: stable; development complete but the code is 20 | * unaudited. and may contain bugs and/or security holes. Use at your own 21 | * risk. 22 | * 23 | * @author Jim McDonald 24 | * @notice If you use this contract please consider donating some Ether or 25 | * some of your ERC-777 token to wsl.wealdtech.eth to support continued 26 | * development of these and future contracts 27 | */ 28 | contract Forwarder is ERC777TokensRecipient, ERC1820Implementer { 29 | // recipient=>target 30 | mapping(address=>address) public forwardingAddresses; 31 | 32 | // An event emitted when a forwarding address is set 33 | event ForwardingAddressSet(address recipient, address target); 34 | // An event emitted when a forwarding address is cleared 35 | event ForwardingAddressCleared(address recipient); 36 | 37 | constructor() public { 38 | implementInterface("ERC777TokensRecipient", false); 39 | } 40 | 41 | function setForwarder(address target) external { 42 | forwardingAddresses[msg.sender] = target; 43 | emit ForwardingAddressSet(msg.sender, target); 44 | } 45 | 46 | function clearForwarder() external { 47 | delete(forwardingAddresses[msg.sender]); 48 | emit ForwardingAddressCleared(msg.sender); 49 | } 50 | 51 | function getForwarder(address target) external view returns (address) { 52 | return forwardingAddresses[target]; 53 | } 54 | 55 | /** 56 | * tokensReceived forwards the token if a forwarder is set. 57 | */ 58 | function tokensReceived(address operator, address holder, address recipient, uint256 amount, bytes calldata data, bytes calldata operatorData) external { 59 | (operator, holder, data, operatorData); 60 | if (forwardingAddresses[recipient] != address(0)) { 61 | IERC777 tokenContract = IERC777(msg.sender); 62 | // Transfer the tokens - this throws if it fails 63 | tokenContract.operatorSend(recipient, forwardingAddresses[recipient], amount, "", ""); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /contracts/token/FixedPriceTokenAgent.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | // Copyright © 2017 Weald Technology Trading Limited 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 | import '../math/SafeMath.sol'; 17 | import './ITokenAgent.sol'; 18 | import './IERC20.sol'; 19 | 20 | 21 | /** 22 | * @title FixedPriceTokenAgent 23 | * A simple token agent that sells its tokens at a fixed exchange rate 24 | * of Ether to tokens. 25 | * 26 | * State of this contract: stable; development complete but the code is 27 | * unaudited. and may contain bugs and/or security holes. Use at your own 28 | * risk. 29 | * 30 | * @author Jim McDonald 31 | * @notice If you use this contract please consider donating some Ether or 32 | * some of your ERC-20 token to wsl.wealdtech.eth to support continued 33 | * development of these and future contracts 34 | */ 35 | contract FixedPriceTokenAgent is ITokenAgent { 36 | using SafeMath for uint256; 37 | 38 | // The token being sold 39 | IERC20 token; 40 | 41 | // The provider of the tokens we are selling 42 | address provider; 43 | 44 | // The number of tokens per Wei. 45 | uint256 tokensPerWei; 46 | 47 | constructor(IERC20 _token, address _provider, uint256 _tokensPerWei) public { 48 | token = _token; 49 | provider = _provider; 50 | require(_tokensPerWei > 0); 51 | tokensPerWei = _tokensPerWei; 52 | } 53 | 54 | /** 55 | * @dev active states if the agent is currently active. 56 | */ 57 | function active() public view returns (bool) { 58 | return tokensAvailable() > 0; 59 | } 60 | 61 | /** 62 | * @dev provide the number of tokens available. 63 | */ 64 | function tokensAvailable() public view returns (uint256) { 65 | return token.allowance(provider, address(this)); 66 | } 67 | 68 | /** 69 | * @dev attempt to obtain tokens depending on the amount of funds 70 | * supplied. 71 | */ 72 | function () external payable { 73 | uint256 amount = msg.value.mul(tokensPerWei); 74 | require(amount > 0); 75 | require(amount <= tokensAvailable()); 76 | token.transferFrom(provider, msg.sender, amount); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /contracts/tokenoperator/FixedTimeRelease.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import '../token/IERC777.sol'; 4 | 5 | 6 | /** 7 | * @title FixedTimeRelease 8 | * 9 | * An ERC777 token operator contract that releases all tokens held by an 10 | * address at a fixed date/time. 11 | * 12 | * N.B. this contract will make tokens accessible to anyone after the 13 | * release timestamp. As such it is generally not used by itself but 14 | * combined with another token operator contract such as FixedAllowance 15 | * or SignatureAuthority to provide the required functionality. 16 | * 17 | * State of this contract: stable; development complete but the code is 18 | * unaudited. and may contain bugs and/or security holes. Use at your own 19 | * risk. 20 | * 21 | * @author Jim McDonald 22 | * @notice If you use this contract please consider donating some Ether or 23 | * some of your ERC-777 token to wsl.wealdtech.eth to support continued 24 | * development of these and future contracts 25 | */ 26 | contract FixedTimeRelease { 27 | // Mapping is token=>holder=>release timestamp 28 | mapping(address=>mapping(address=>uint256)) private releaseTimestamps; 29 | 30 | event ReleaseTimestamp(address token, address holder, uint256 expiry); 31 | 32 | /* 33 | * Set the release timestamp for a token 34 | * @param _token the address of the token contract 35 | * @param _timestamp the unix timestamp at which the tokens are released 36 | */ 37 | function setReleaseTimestamp(IERC777 _token, uint256 _timestamp) public { 38 | require(_timestamp >= releaseTimestamps[address(_token)][msg.sender], "cannot bring release time forward"); 39 | releaseTimestamps[address(_token)][msg.sender] = _timestamp; 40 | emit ReleaseTimestamp(address(_token), msg.sender, _timestamp); 41 | } 42 | 43 | /* 44 | * Get the release timestamp for a token 45 | * @param _token the address of the token contract 46 | * @param _holder the address of the holder 47 | * @return the unix timestamp at which the tokens are released 48 | */ 49 | function getReleaseTimestamp(IERC777 _token, address _holder) public view returns (uint256) { 50 | return releaseTimestamps[address(_token)][_holder]; 51 | } 52 | 53 | function send(IERC777 _token, address _holder, address _recipient, uint256 _amount) public { 54 | preSend(_token, _holder); 55 | _token.operatorSend(_holder, _recipient, _amount, "", ""); 56 | } 57 | 58 | function preSend(IERC777 _token, address _holder) internal view { 59 | require(releaseTimestamps[address(_token)][_holder] != 0, "no release time set"); 60 | require(releaseTimestamps[address(_token)][_holder] <= now, "not yet released"); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /contracts/tokenssender/Lockup.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import '../math/SafeMath.sol'; 4 | import '../token/ERC777TokensSender.sol'; 5 | import '../registry/ERC1820Implementer.sol'; 6 | 7 | 8 | /** 9 | * @title Lockup 10 | * 11 | * An ERC777 tokens sender contract that locks up tokens for a given time 12 | * period. 13 | * 14 | * To use this contract a token holder should call setExpiry() to set 15 | * the timestamp of the lockup expiry. 16 | * 17 | * State of this contract: stable; development complete but the code is 18 | * unaudited. and may contain bugs and/or security holes. Use at your own 19 | * risk. 20 | * 21 | * @author Jim McDonald 22 | * @notice If you use this contract please consider donating some Ether or 23 | * some of your ERC-777 token to wsl.wealdtech.eth to support continued 24 | * development of these and future contracts 25 | */ 26 | contract Lockup is ERC777TokensSender, ERC1820Implementer { 27 | using SafeMath for uint256; 28 | 29 | // Mapping is token=>holder=>expiry of lockup 30 | mapping(address=>mapping(address=>uint256)) private expiries; 31 | 32 | event LockupExpires(address token, address holder, uint256 expiry); 33 | 34 | constructor() public { 35 | implementInterface("ERC777TokensSender", false); 36 | } 37 | 38 | /* 39 | * Set the expiry for a token. Once set an expiry cannot be reduced, 40 | * only increased. 41 | * @param _token the address of the token contract 42 | * @param _expiry the unix timestamp at which the lockup expires 43 | */ 44 | function setExpiry(address _token, uint256 _expiry) external { 45 | require(expiries[_token][msg.sender] < _expiry, "not allowed to reduce lockup expiry"); 46 | expiries[_token][msg.sender] = _expiry; 47 | emit LockupExpires(_token, msg.sender, _expiry); 48 | } 49 | 50 | /* 51 | * Get the expiry for a token 52 | * @param _token the address of the token contract 53 | * @param _holder the address of the holder 54 | * @return the unix timestamp at which the lockup expires 55 | */ 56 | function getExpiry(address _token, address _holder) external view returns (uint256) { 57 | return expiries[_token][_holder]; 58 | } 59 | 60 | /** 61 | * This ensures that the lockup for the token has expired and that the 62 | * amount transferred does not exceed the allowance 63 | */ 64 | function tokensToSend(address operator, address holder, address recipient, uint256 amount, bytes calldata data, bytes calldata operatorData) external { 65 | (operator, recipient, amount, data, operatorData); 66 | 67 | require(expiries[msg.sender][holder] != 0, "lockup expiry is not set"); 68 | require(now >= expiries[msg.sender][holder], "lockup has not expired"); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /contracts/tokenoperator/VaultRecipient.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import '../token/IERC777.sol'; 4 | 5 | 6 | /** 7 | * @title VaultRecipient 8 | * 9 | * An ERC777 token operator contract that allows tokens to be sent to a 10 | * pre-defined address if requested by said address. 11 | * 12 | * There is a common tradeoff between ease of use and security with 13 | * private keys. The purpose of this operator is to act as a bridge 14 | * between the two, providing a way for the holder of the high-security 15 | * private key (e.g. on a hardware wallet) to pull tokens to their 16 | * account without the private key of the holder. This way, if the 17 | * lower-security key is inaccessible (e.g. a lost 'phone containing the 18 | * key) the funds can be retrieved. 19 | * 20 | * State of this contract: stable; development complete but the code is 21 | * unaudited. and may contain bugs and/or security holes. Use at your own 22 | * risk. 23 | * 24 | * @author Jim McDonald 25 | * @notice If you use this contract please consider donating some Ether or 26 | * some of your ERC-777 token to wsl.wealdtech.eth to support continued 27 | * development of these and future contracts 28 | */ 29 | contract VaultRecipient { 30 | // Mapping is token=>holder=>vault 31 | mapping(address=>mapping(address=>address)) private vaults; 32 | 33 | event Vault(address token, address holder, address vault); 34 | 35 | /** 36 | * Set a vault address. The vault address has blanket authority to transfer 37 | * tokens from the holder's account. 38 | * @param _token the address of the token contract 39 | * @param _vault the address of the vault 40 | */ 41 | function setVault(IERC777 _token, address _vault) public { 42 | vaults[address(_token)][msg.sender] = _vault; 43 | emit Vault(address(_token), msg.sender, _vault); 44 | } 45 | 46 | function getVault(IERC777 _token, address _holder) public view returns (address) { 47 | return vaults[address(_token)][_holder]; 48 | } 49 | 50 | /** 51 | * Send a given amount of tokens to the vault 52 | * @param _token the address of the token contract 53 | * @param _amount the amount of tokens to send to the vault 54 | * @param _data the data to attach to the send 55 | */ 56 | function send(IERC777 _token, address _holder, uint256 _amount, bytes memory _data) public { 57 | preSend(_token, _holder); 58 | _token.operatorSend(_holder, msg.sender, _amount, _data, ""); 59 | } 60 | 61 | function preSend(IERC777 _token, address _holder) internal view { 62 | address vault = vaults[address(_token)][_holder]; 63 | require(vault != address(0), "vault not configured"); 64 | require(vault == msg.sender, "not the vault account"); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /contracts/tokenssender/EmitMessage.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import '../token/ERC777TokensSender.sol'; 4 | import '../registry/ERC1820Implementer.sol'; 5 | 6 | 7 | /** 8 | * @title EmitMessage 9 | * 10 | * An ERC777 tokens sender contract that provides an additional event 11 | * with holder-defined message on transfer. 12 | * 13 | * State of this contract: stable; development complete but the code is 14 | * unaudited. and may contain bugs and/or security holes. Use at your own 15 | * risk. 16 | * 17 | * @author Jim McDonald 18 | * @notice If you use this contract please consider donating some Ether or 19 | * some of your ERC-777 token to wsl.wealdtech.eth to support continued 20 | * development of these and future contracts 21 | */ 22 | contract EmitMessage is ERC777TokensSender, ERC1820Implementer { 23 | // Holder=>recipient=>log 24 | mapping(address=>mapping(address=>string)) public messages; 25 | 26 | // An event emitted when a message is set 27 | event MessageSet(address holder, address recipient, string message); 28 | // An event emitted when a message is cleared 29 | event MessageCleared(address holder, address recipient); 30 | // The event that is emitted to log a message 31 | event Message(address holder, address recipient, string message); 32 | 33 | constructor() public { 34 | implementInterface("ERC777TokensSender", false); 35 | } 36 | 37 | /** 38 | * setMessage sets a message. If recipient is 0 then this message will 39 | * apply for all sends from this holder. 40 | */ 41 | function setMessage(address _recipient, string calldata _message) external { 42 | messages[msg.sender][_recipient] = _message; 43 | emit MessageSet(msg.sender, _recipient, _message); 44 | } 45 | 46 | /** 47 | * ClearMessage clears a message. 48 | */ 49 | function clearMessage(address _recipient) external { 50 | messages[msg.sender][_recipient] = ""; 51 | emit MessageCleared(msg.sender, _recipient); 52 | } 53 | 54 | function getMessage(address _holder, address _recipient) external view returns (string memory) { 55 | return messages[_holder][_recipient]; 56 | } 57 | 58 | /** 59 | * Emit a message if found 60 | */ 61 | function tokensToSend(address operator, address holder, address recipient, uint256 amount, bytes calldata data, bytes calldata operatorData) external { 62 | (operator, amount, data, operatorData); 63 | 64 | string memory message = messages[holder][recipient]; 65 | if (bytes(message).length > 0) { 66 | emit Message(holder, recipient, message); 67 | } else { 68 | message = messages[holder][address(0)]; 69 | if (bytes(message).length > 0) { 70 | emit Message(holder, recipient, message); 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /contracts/lifecycle/Pausable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | // Copyright © 2017, 2018 Weald Technology Trading Limited 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 | import "../auth/Permissioned.sol"; 17 | 18 | 19 | /** 20 | * @title Pausable 21 | * Pausable provides a toggle for the operation of contract functions. 22 | * This is accomplished through a combination of functions and 23 | * modifiers. The functions pause() and unpause() toggle the internal 24 | * flag, and the modifiers ifPaused and ifNotPaused throw if the flag 25 | * is not in the correct state. 26 | * 27 | * Calling pause() and unpause() requires the caller to have the 28 | * PERM_PAUSE permission. 29 | * 30 | * Note that an attempt to pause() an already-paused contract, or to 31 | * unpause() an unpaused contract, will throw. 32 | * 33 | * State of this contract: stable; development complete but the code is 34 | * unaudited. and may contain bugs and/or security holes. Use at your own 35 | * risk. 36 | * 37 | * @author Jim McDonald 38 | * @notice If you use this contract please consider donating some Ether or 39 | * some of your ERC-20 token to wsl.wealdtech.eth to support continued 40 | * development of these and future contracts 41 | */ 42 | contract Pausable is Permissioned { 43 | event Pause(); 44 | event Unpause(); 45 | 46 | bool public paused = false; 47 | 48 | bytes32 internal constant PERM_PAUSE = keccak256("_pausable"); 49 | 50 | /** 51 | * @dev modifier to continue only if the contract is not paused 52 | */ 53 | modifier ifNotPaused() { 54 | require(!paused); 55 | _; 56 | } 57 | 58 | /** 59 | * @dev modifier to continue only if the contract is paused 60 | */ 61 | modifier ifPaused { 62 | require(paused); 63 | _; 64 | } 65 | 66 | /** 67 | * @dev pause the contract 68 | */ 69 | function pause() public ifPermitted(msg.sender, PERM_PAUSE) ifNotPaused returns (bool) { 70 | paused = true; 71 | emit Pause(); 72 | return true; 73 | } 74 | 75 | /** 76 | * @dev unpause the contract 77 | */ 78 | function unpause() public ifPermitted(msg.sender, PERM_PAUSE) ifPaused returns (bool) { 79 | paused = false; 80 | emit Unpause(); 81 | return true; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /contracts/tokenoperator/FixedPriceSeller.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import '../math/SafeMath.sol'; 4 | import '../token/IERC777.sol'; 5 | 6 | 7 | /** 8 | * @title FixedPriceSeller 9 | * 10 | * An ERC777 token operator contract that sells tokens at a fixed price. 11 | * 12 | * State of this contract: stable; development complete but the code is 13 | * unaudited. and may contain bugs and/or security holes. Use at your own 14 | * risk. 15 | * 16 | * @author Jim McDonald 17 | * @notice If you use this contract please consider donating some Ether or 18 | * some of your ERC-777 token to wsl.wealdtech.eth to support continued 19 | * development of these and future contracts 20 | */ 21 | contract FixedPriceSeller { 22 | using SafeMath for uint256; 23 | 24 | // Mapping is token=>holder=>price per token 25 | mapping(address=>mapping(address=>uint256)) pricePerToken; 26 | 27 | event PricePerToken(address token, address holder, uint256 pricePerToken); 28 | 29 | /** 30 | * Set the price for each token. The price is in Wei, so if for example 31 | * the price is 1 Ether for 1 token then _pricePerToken would be 10^18. 32 | */ 33 | function setPricePerToken(IERC777 _token, uint256 _pricePerToken) public { 34 | pricePerToken[address(_token)][msg.sender] = _pricePerToken; 35 | emit PricePerToken(address(_token), msg.sender, _pricePerToken); 36 | } 37 | 38 | /** 39 | * Get the price for each token. The price is in Wei, so if for example 40 | * the price is 1 Ether for 1 token this would return 10^18. 41 | */ 42 | function getPricePerToken(IERC777 _token, address _holder) public view returns (uint256) { 43 | return pricePerToken[address(_token)][_holder]; 44 | } 45 | 46 | /** 47 | * Send tokens from a holder at their price 48 | */ 49 | function send(IERC777 _token, address payable _holder) public payable { 50 | uint256 amount = preSend(_token, _holder); 51 | _token.operatorSend(_holder, msg.sender, amount, "", ""); 52 | postSend(_holder); 53 | } 54 | 55 | /** 56 | * Checks and state update to carry out prior to sending tokens 57 | */ 58 | function preSend(IERC777 _token, address _holder) internal view returns (uint256) { 59 | require(pricePerToken[address(_token)][_holder] != 0, "not for sale"); 60 | uint256 amount = msg.value.mul(1000000000000000000).div(pricePerToken[address(_token)][_holder]); 61 | require(amount > _token.granularity(), "not enough ether paid"); 62 | uint256 value = amount.mul(pricePerToken[address(_token)][_holder]).div(1000000000000000000); 63 | require(value == msg.value, "non-integer number of tokens purchased"); 64 | return amount; 65 | } 66 | 67 | /** 68 | * State update to carry out after sending tokens 69 | */ 70 | function postSend(address payable _holder) internal { 71 | _holder.transfer(msg.value); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /test/random/ParticipatoryRandom.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assertRevert = require('../helpers/assertRevert.js'); 4 | const sha3 = require('solidity-sha3').default; 5 | 6 | const ParticipatoryRandom = artifacts.require('ParticipatoryRandom'); 7 | 8 | contract('Participatory Random', accounts => { 9 | var instance; 10 | it('instantiates the contract', async function() { 11 | instance = await ParticipatoryRandom.new(); 12 | }); 13 | it('generates source from seed', async function() { 14 | const instanceId = 1; 15 | const rounds = 10; 16 | const seed = '0x0000000000000000000000000000000000000000000000000000000000000002'; 17 | await instance.newInstance(instanceId, rounds); 18 | const contractSource = await instance.generateSourceFromSeed(instanceId, seed); 19 | var manualSource = seed ; 20 | for (let i = 0; i <= rounds; i++) { 21 | manualSource = sha3(manualSource); 22 | } 23 | assert.equal(contractSource, manualSource); 24 | }); 25 | 26 | it('generates values from seed', async function() { 27 | const instanceId = 2; 28 | const rounds = 10; 29 | const seed = '0x0000000000000000000000000000000000000000000000000000000000000001'; 30 | await instance.newInstance(instanceId, rounds); 31 | await instance.setSource(instanceId, await instance.generateSourceFromSeed(instanceId, seed)); 32 | var manualValue = seed ; 33 | for (let round = rounds; round > 0; round--) { 34 | manualValue = sha3(manualValue); 35 | assert.equal(manualValue, await instance.generateValueFromSeed(instanceId, seed, round)); 36 | } 37 | }); 38 | 39 | it('generates a correct random value', async function() { 40 | const instanceId = 3; 41 | const rounds = 10; 42 | const seed1 = '0x0000000000000000000000000000000000000000000000000000000000000001'; 43 | const seed2 = '0x0000000000000000000000000000000000000000000000000000000000000002'; 44 | await instance.newInstance(instanceId, rounds); 45 | await instance.setSource(instanceId, await instance.generateSourceFromSeed(instanceId, seed1)); 46 | await instance.setSource(instanceId, await instance.generateSourceFromSeed(instanceId, seed2, {from: accounts[1]}), {from: accounts[1]}); 47 | const p1r1val = await instance.generateValueFromSeed(instanceId, seed1, 1); 48 | const p2r1val = await instance.generateValueFromSeed(instanceId, seed2, 1); 49 | const generatedValue = await instance.generateRandomValue(instanceId, [accounts[0], accounts[1]], 1, [p1r1val, p2r1val]); 50 | assert.equal(generatedValue, '0xe9437f018e9737338c24adb6a6f2bec0c9da0e09e0b3809a7083af187f581747'); 51 | }); 52 | 53 | it('returns the correct max rounds', async function() { 54 | const instanceId = 4; 55 | const rounds = 123653; 56 | await instance.newInstance(instanceId, rounds); 57 | const cRounds = await instance.getInstanceMaxRounds(instanceId); 58 | assert.equal(cRounds, rounds); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /test/tokensrecipient/Forwarder.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const asserts = require('../helpers/asserts.js'); 4 | const erc1820 = require('../helpers/erc1820.js'); 5 | const truffleAssert = require('truffle-assertions'); 6 | 7 | const ERC777Token = artifacts.require('ERC777Token'); 8 | const Forwarder = artifacts.require('Forwarder'); 9 | 10 | contract('Forwarder', accounts => { 11 | var erc777Instance; 12 | var erc1820Instance; 13 | var instance; 14 | 15 | const granularity = web3.utils.toBN('10000000000000000'); 16 | const initialSupply = granularity.mul(web3.utils.toBN('10000000')); 17 | 18 | let tokenBalances = {}; 19 | tokenBalances[accounts[0]] = web3.utils.toBN(0); 20 | tokenBalances[accounts[1]] = web3.utils.toBN(0); 21 | tokenBalances[accounts[2]] = web3.utils.toBN(0); 22 | 23 | it('sets up', async function() { 24 | erc1820Instance = await erc1820.instance(); 25 | erc777Instance = await ERC777Token.new(1, 'Test token', 'TST', granularity, initialSupply, [], '0x0000000000000000000000000000000000000000', { 26 | from: accounts[0], 27 | gas: 10000000 28 | }); 29 | await erc777Instance.activate({ 30 | from: accounts[0] 31 | }); 32 | tokenBalances[accounts[0]] = initialSupply.clone(); 33 | await asserts.assertTokenBalances(erc777Instance, tokenBalances); 34 | }); 35 | 36 | it('creates the recipient contract', async function() { 37 | instance = await Forwarder.new({ 38 | from: accounts[0] 39 | }); 40 | }); 41 | 42 | it('forwards tokens accordingly', async function() { 43 | // Register the recipient 44 | await erc1820Instance.setInterfaceImplementer(accounts[1], web3.utils.soliditySha3('ERC777TokensRecipient'), instance.address, { 45 | from: accounts[1] 46 | }); 47 | 48 | // Set up forwarding from accounts[1] to accounts[2] 49 | await instance.setForwarder(accounts[2], { 50 | from: accounts[1] 51 | }); 52 | assert.equal(await instance.getForwarder(accounts[1]), accounts[2]); 53 | 54 | // Set up the recipient contract as an operator for accounts[1] 55 | await erc777Instance.authorizeOperator(instance.address, { 56 | from: accounts[1] 57 | }); 58 | 59 | // Transfer tokens from accounts[0] to accounts[1] 60 | const amount = granularity.mul(web3.utils.toBN('10')); 61 | await erc777Instance.send(accounts[1], amount, [], { 62 | from: accounts[0] 63 | }); 64 | tokenBalances[accounts[0]] = tokenBalances[accounts[0]].sub(amount); 65 | tokenBalances[accounts[2]] = tokenBalances[accounts[2]].add(amount); 66 | await asserts.assertTokenBalances(erc777Instance, tokenBalances); 67 | 68 | // Unregister the operator 69 | await erc777Instance.revokeOperator(instance.address, { 70 | from: accounts[0] 71 | }); 72 | 73 | // Unregister the recipient 74 | await erc1820Instance.setInterfaceImplementer(accounts[1], web3.utils.soliditySha3('ERC777TokensRecipient'), '0x0000000000000000000000000000000000000000', { 75 | from: accounts[1] 76 | }); 77 | }); 78 | }); 79 | -------------------------------------------------------------------------------- /test/tokensrecipient/DenyAll.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const asserts = require('../helpers/asserts.js'); 4 | const erc1820 = require('../helpers/erc1820.js'); 5 | const truffleAssert = require('truffle-assertions'); 6 | 7 | const ERC777Token = artifacts.require('ERC777Token'); 8 | const DenyAll = artifacts.require('DenyAll'); 9 | 10 | contract('DenyAll', accounts => { 11 | var erc777Instance; 12 | var erc1820Instance; 13 | var instance; 14 | 15 | const granularity = web3.utils.toBN('10000000000000000'); 16 | const initialSupply = granularity.mul(web3.utils.toBN('10000000')); 17 | 18 | let tokenBalances = {}; 19 | tokenBalances[accounts[0]] = web3.utils.toBN(0); 20 | tokenBalances[accounts[1]] = web3.utils.toBN(0); 21 | 22 | it('sets up', async function() { 23 | erc1820Instance = await erc1820.instance(); 24 | erc777Instance = await ERC777Token.new(1, 'Test token', 'TST', granularity, initialSupply, [], '0x0000000000000000000000000000000000000000', { 25 | from: accounts[0], 26 | gas: 10000000 27 | }); 28 | await erc777Instance.activate({ 29 | from: accounts[0] 30 | }); 31 | tokenBalances[accounts[0]] = initialSupply.clone(); 32 | await asserts.assertTokenBalances(erc777Instance, tokenBalances); 33 | }); 34 | 35 | it('creates the recipient contract', async function() { 36 | instance = await DenyAll.new({ 37 | from: accounts[0] 38 | }); 39 | }); 40 | 41 | it('Denies transfers accordingly', async function() { 42 | // Transfer 100*granularity tokens from accounts[0] to accounts[1] 43 | const amount = granularity.mul(web3.utils.toBN('100')); 44 | await erc777Instance.send(accounts[1], amount, [], { 45 | from: accounts[0] 46 | }); 47 | tokenBalances[accounts[0]] = tokenBalances[accounts[0]].sub(amount); 48 | tokenBalances[accounts[1]] = tokenBalances[accounts[1]].add(amount); 49 | await asserts.assertTokenBalances(erc777Instance, tokenBalances); 50 | 51 | // Register the recipient 52 | await erc1820Instance.setInterfaceImplementer(accounts[1], web3.utils.soliditySha3('ERC777TokensRecipient'), instance.address, { 53 | from: accounts[1] 54 | }); 55 | 56 | // Attempt to transfer tokens to accounts[1] - should fail 57 | await truffleAssert.reverts( 58 | erc777Instance.send(accounts[1], amount, [], { 59 | from: accounts[0] 60 | })); 61 | 62 | // Unregister the recipient 63 | await erc1820Instance.setInterfaceImplementer(accounts[1], web3.utils.soliditySha3('ERC777TokensRecipient'), '0x0000000000000000000000000000000000000000', { 64 | from: accounts[1] 65 | }); 66 | 67 | // Transfer 100*granularity tokens from accounts[0] to accounts[1] 68 | await erc777Instance.send(accounts[1], amount, [], { 69 | from: accounts[0] 70 | }); 71 | tokenBalances[accounts[0]] = tokenBalances[accounts[0]].sub(amount); 72 | tokenBalances[accounts[1]] = tokenBalances[accounts[1]].add(amount); 73 | await asserts.assertTokenBalances(erc777Instance, tokenBalances); 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /contracts/ens/ENSReverseRegister.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | 4 | // Copyright © 2017 Weald Technology Trading Limited 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | // Important parts of the ENS registry contract 18 | contract RegistryRef { 19 | function owner(bytes32 node) public view returns (address); 20 | } 21 | 22 | 23 | // Important parts of the ENS reverse registrar contract 24 | contract ReverseRegistrarRef { 25 | function setName(string memory name) public returns (bytes32 node); 26 | } 27 | 28 | 29 | /** 30 | * @title ENSReverseRegister 31 | * ENS resolves names to addresses, and addresses to names. But to set 32 | * the resolution from address to name the transaction must come from 33 | * the address in question. This contract sets the reverse resolution as 34 | * part of the contract initialisation. 35 | * 36 | * To use this your code should inherit this contract and provide the 37 | * appropriate arguments in its constructor, for example: 38 | * 39 | * contract MyContract is ENSReverseRegister { 40 | * ... 41 | * MyContract(address ens) ENSReverseRegister(ens, "mycontract.eth") { 42 | * ... 43 | * } 44 | * } 45 | * 46 | * Note that for this to work your contract must be given the address of 47 | * the ENS registry. If this is not supplied then this code will not run 48 | * and the reverse entry will not be set in ENS (but it will not throw). 49 | * 50 | * State of this contract: stable; development complete but the code is 51 | * unaudited. and may contain bugs and/or security holes. Use at your own 52 | * risk. 53 | * 54 | * @author Jim McDonald 55 | * @notice If you use this contract please consider donating some Ether or 56 | * some of your ERC-20 token to wsl.wealdtech.eth to support continued 57 | * development of these and future contracts 58 | */ 59 | contract ENSReverseRegister { 60 | /** 61 | * @dev initialise the contract with the address of the reverse registrar 62 | */ 63 | constructor(address registry, string memory name) public { 64 | if (registry != address(0)) { 65 | // Fetch the address of the ENS reverse registrar 66 | // Hex value is namehash('addr.reverse') 67 | address reverseRegistrar = RegistryRef(registry).owner(0x91d1777781884d03a6757a803996e38de2a42967fb37eeaca72729271025a9e2); 68 | // If it exists then set our reverse resolution 69 | if (reverseRegistrar != address(0)) { 70 | ReverseRegistrarRef(reverseRegistrar).setName(name); 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /contracts/tokenoperator/FixedAllowance.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import '../math/SafeMath.sol'; 4 | import '../token/IERC777.sol'; 5 | 6 | 7 | /** 8 | * @title FixedAllowance 9 | * 10 | * An ERC777 token operator contract that releases a limited number of 11 | * tokens for a given (token, holder, transferer). 12 | * 13 | * To use this contract a token holder should first call setAllowance() 14 | * to set the allowance for a given accoutn. That account will then be 15 | * able to transfer away that number of tokens. 16 | * 17 | * State of this contract: stable; development complete but the code is 18 | * unaudited. and may contain bugs and/or security holes. Use at your own 19 | * risk. 20 | * 21 | * @author Jim McDonald 22 | * @notice If you use this contract please consider donating some Ether or 23 | * some of your ERC-777 token to wsl.wealdtech.eth to support continued 24 | * development of these and future contracts 25 | */ 26 | contract FixedAllowance { 27 | using SafeMath for uint256; 28 | 29 | // Mapping is token=>holder=>recipient=>allowance 30 | mapping(address=>mapping(address=>mapping(address=>uint256))) private allowances; 31 | 32 | event Allowance(address token, address holder, address transferer, uint256 amount); 33 | 34 | /** 35 | * Set the allowance for a given (_token, _holder, _recipient) 36 | * @param _token the address of the token contract 37 | * @param _recipient the address of the recipient 38 | * @param _oldAllowance the amount of tokens previously allowed 39 | * @param _allowance the amount of tokens to allow 40 | */ 41 | function setAllowance(IERC777 _token, address _recipient, uint256 _oldAllowance, uint256 _allowance) public { 42 | require(allowances[address(_token)][msg.sender][_recipient] == _oldAllowance, "old allowance does not match current allowance"); 43 | allowances[address(_token)][msg.sender][_recipient] = _allowance; 44 | emit Allowance(address(_token), msg.sender, _recipient, _allowance); 45 | } 46 | 47 | /* 48 | * Get the allowance for a given (_token, _holder, _recipient) 49 | * @param _token the address of the token contract 50 | * @param _holder the address of the holder 51 | * @param _recipient the address of the recipient 52 | * @return the allowance 53 | */ 54 | function getAllowance(IERC777 _token, address _holder, address _recipient) public view returns (uint256) { 55 | return allowances[address(_token)][_holder][_recipient]; 56 | } 57 | 58 | function send(IERC777 _token, address _holder, address _recipient, uint256 _amount) public { 59 | preSend(_token, _holder, msg.sender, _amount); 60 | _token.operatorSend(_holder, _recipient, _amount, "", ""); 61 | } 62 | 63 | /** 64 | * Checks and state update to carry out prior to sending tokens 65 | */ 66 | function preSend(IERC777 _token, address _holder, address _transferer, uint256 _amount) internal { 67 | require(_amount <= allowances[address(_token)][_holder][_transferer], "amount exceeds allowance"); 68 | allowances[address(_token)][_holder][_transferer] = allowances[address(_token)][_holder][_transferer].sub(_amount); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /test/tokenoperator/BulkSend.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const asserts = require('../helpers/asserts.js'); 4 | const truffleAssert = require('truffle-assertions'); 5 | 6 | const ERC777Token = artifacts.require('ERC777Token'); 7 | const BulkSend = artifacts.require('BulkSend'); 8 | 9 | contract('BulkSend', accounts => { 10 | var erc777Instance; 11 | var operator; 12 | 13 | const granularity = web3.utils.toBN('10000000000000000'); 14 | const initialSupply = granularity.mul(web3.utils.toBN('10000000')); 15 | 16 | let tokenBalances = {}; 17 | tokenBalances[accounts[0]] = web3.utils.toBN(0); 18 | tokenBalances[accounts[1]] = web3.utils.toBN(0); 19 | tokenBalances[accounts[2]] = web3.utils.toBN(0); 20 | tokenBalances[accounts[3]] = web3.utils.toBN(0); 21 | 22 | it('sets up', async function() { 23 | operator = await BulkSend.new({ 24 | from: accounts[0] 25 | }); 26 | 27 | erc777Instance = await ERC777Token.new(1, 'Test token', 'TST', granularity, initialSupply, [operator.address], '0x0000000000000000000000000000000000000000', { 28 | from: accounts[0], 29 | gas: 10000000 30 | }); 31 | await erc777Instance.activate({ 32 | from: accounts[0] 33 | }); 34 | tokenBalances[accounts[0]] = initialSupply.clone(); 35 | await asserts.assertTokenBalances(erc777Instance, tokenBalances); 36 | 37 | // accounts[1] is our test source address so send it some tokens 38 | const amount = granularity.mul(web3.utils.toBN('100')); 39 | await erc777Instance.send(accounts[1], amount, [], { 40 | from: accounts[0] 41 | }); 42 | tokenBalances[accounts[0]] = tokenBalances[accounts[0]].sub(amount); 43 | tokenBalances[accounts[1]] = tokenBalances[accounts[1]].add(amount); 44 | await asserts.assertTokenBalances(erc777Instance, tokenBalances); 45 | }); 46 | 47 | it('bulk transfers same amount', async function() { 48 | const amount = granularity.mul(web3.utils.toBN('5')); 49 | 50 | // Send the same amount to multiple accounts 51 | await operator.send(erc777Instance.address, [accounts[2], accounts[3]], amount, [], { 52 | from: accounts[1] 53 | }); 54 | tokenBalances[accounts[1]] = tokenBalances[accounts[1]].sub(amount.mul(web3.utils.toBN('2'))); 55 | tokenBalances[accounts[2]] = tokenBalances[accounts[2]].add(amount); 56 | tokenBalances[accounts[3]] = tokenBalances[accounts[3]].add(amount); 57 | await asserts.assertTokenBalances(erc777Instance, tokenBalances); 58 | }); 59 | 60 | it('bulk transfers different amounts', async function() { 61 | const amount2 = granularity.mul(web3.utils.toBN('8')); 62 | const amount3 = granularity.mul(web3.utils.toBN('12')); 63 | 64 | // Send the same amount to multiple accounts 65 | await operator.sendAmounts(erc777Instance.address, [accounts[2], accounts[3]], [amount2, amount3], [], { 66 | from: accounts[1] 67 | }); 68 | tokenBalances[accounts[1]] = tokenBalances[accounts[1]].sub(amount2).sub(amount3); 69 | tokenBalances[accounts[2]] = tokenBalances[accounts[2]].add(amount2); 70 | tokenBalances[accounts[3]] = tokenBalances[accounts[3]].add(amount3); 71 | await asserts.assertTokenBalances(erc777Instance, tokenBalances); 72 | }); 73 | }); 74 | -------------------------------------------------------------------------------- /contracts/auth/Permissioned.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | 4 | // Copyright © 2017, 2018 Weald Technology Trading Limited 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | /** 18 | * @title Permissioned 19 | * Permission structure and modifiers. Permissions are described by the 20 | * tuple (address, permission id). The permission ID is a keccak256() 21 | * hash of a developer-selected string. It is recommended that the 22 | * developer use a (short) prefix for their permissions to avoid 23 | * clashes, for example a permission might be called 24 | * "my contract: upgrade". 25 | * 26 | * An address must have the superuser permission to alter permissions. 27 | * The creator of the contract is made a superuser when the contract is 28 | * created. Be aware that it is possible for the superuser to remove 29 | * themselves and leave aspects of a contract unable to be altered; this 30 | * is intentional but any such action should be considered carefully. 31 | * 32 | * Note that the prefix of "_" is reserved and should not be used. 33 | * 34 | * Also note that any address with the superuser permission is implicitly 35 | * granted all permissions. 36 | * 37 | * State of this contract: stable; development complete but the code is 38 | * unaudited. and may contain bugs and/or security holes. Use at your own 39 | * risk. 40 | * 41 | * @author Jim McDonald 42 | * @notice If you use this contract please consider donating some Ether or 43 | * some of your ERC-20 token to wsl.wealdtech.eth to support continued 44 | * development of these and future contracts 45 | */ 46 | contract Permissioned { 47 | mapping(address=>mapping(bytes32=>bool)) internal permissions; 48 | 49 | // The superuser permission 50 | bytes32 internal constant PERM_SUPERUSER = keccak256("_superuser"); 51 | 52 | // Emitted whenever a permission is changed 53 | event PermissionChanged(address indexed account, bytes32 indexed permission, bool value); 54 | 55 | /** 56 | * @dev The Permissioned constructor gives the contract creator the 57 | * superuser permission with the ability to change permissions. 58 | */ 59 | constructor() public { 60 | permissions[msg.sender][PERM_SUPERUSER] = true; 61 | emit PermissionChanged(msg.sender, PERM_SUPERUSER, true); 62 | } 63 | 64 | /** 65 | * @dev A modifier that requires the sender to have the presented permission. 66 | */ 67 | modifier ifPermitted(address addr, bytes32 permission) { 68 | require(permissions[addr][permission] || permissions[addr][PERM_SUPERUSER]); 69 | _; 70 | } 71 | 72 | /** 73 | * @dev query a permission for an address. 74 | */ 75 | function isPermitted(address addr, bytes32 permission) public view returns (bool) { 76 | return(permissions[addr][permission] || permissions[addr][PERM_SUPERUSER]); 77 | } 78 | 79 | /** 80 | * @dev Set or reset a permission. 81 | */ 82 | function setPermission(address addr, bytes32 permission, bool allowed) public ifPermitted(msg.sender, PERM_SUPERUSER) { 83 | permissions[addr][permission] = allowed; 84 | emit PermissionChanged(addr, permission, allowed); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /contracts/storage/PermissionedStorage.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | // Copyright © 2017, 2018 Weald Technology Trading Limited 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 | import "../auth/Permissioned.sol"; 17 | 18 | 19 | /** 20 | * @title PermissionedStorage 21 | * Permissioned storage is a general-purpose storage contract holding 22 | * data that can be set by allowed parties. 23 | * 24 | * Calling set*() requires the caller to have the PERM_WRITE 25 | * permission. 26 | * 27 | * Note that permissions are all-or-nothing, so this contract should not 28 | * be shared between multiple parties that might require differing 29 | * permissions. 30 | * 31 | * State of this contract: under active development; code and API 32 | * may change. Use at your own risk. 33 | * 34 | * @author Jim McDonald 35 | * @notice If you use this contract please consider donating some Ether or 36 | * some of your ERC-20 token to wsl.wealdtech.eth to support continued 37 | * development of these and future contracts 38 | */ 39 | contract PermissionedStorage is Permissioned { 40 | bytes32 constant public PERM_WRITE = keccak256("permissionedstorage: write"); 41 | 42 | mapping(bytes32 => uint256) UInt256Storage; 43 | 44 | function getUInt256(bytes32 record) public view returns (uint256) { 45 | return UInt256Storage[record]; 46 | } 47 | 48 | function setUInt256(bytes32 record, uint256 value) public ifPermitted(msg.sender, PERM_WRITE) { 49 | UInt256Storage[record] = value; 50 | } 51 | 52 | mapping(bytes32 => string) StringStorage; 53 | 54 | function getString(bytes32 record) public view returns (string memory) { 55 | return StringStorage[record]; 56 | } 57 | 58 | function setString(bytes32 record, string memory value) public ifPermitted(msg.sender, PERM_WRITE) { 59 | StringStorage[record] = value; 60 | } 61 | 62 | mapping(bytes32 => address) AddressStorage; 63 | 64 | function getAddress(bytes32 record) public view returns (address) { 65 | return AddressStorage[record]; 66 | } 67 | 68 | function setAddress(bytes32 record, address value) public ifPermitted(msg.sender, PERM_WRITE) { 69 | AddressStorage[record] = value; 70 | } 71 | 72 | mapping(bytes32 => bytes) BytesStorage; 73 | 74 | function getBytes(bytes32 record) public view returns (bytes memory) { 75 | return BytesStorage[record]; 76 | } 77 | 78 | function setBytes(bytes32 record, bytes memory value) public ifPermitted(msg.sender, PERM_WRITE) { 79 | BytesStorage[record] = value; 80 | } 81 | 82 | mapping(bytes32 => bool) BooleanStorage; 83 | 84 | function getBoolean(bytes32 record) public view returns (bool) { 85 | return BooleanStorage[record]; 86 | } 87 | 88 | function setBoolean(bytes32 record, bool value) public ifPermitted(msg.sender, PERM_WRITE) { 89 | BooleanStorage[record] = value; 90 | } 91 | 92 | mapping(bytes32 => int256) Int256Storage; 93 | 94 | function getInt256(bytes32 record) public view returns (int256) { 95 | return Int256Storage[record]; 96 | } 97 | 98 | function setInt256(bytes32 record, int256 value) public ifPermitted(msg.sender, PERM_WRITE) { 99 | Int256Storage[record] = value; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /contracts/tokenssender/SupplementWitholdingAccount.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import '../math/SafeMath.sol'; 4 | import '../token/ERC777TokensSender.sol'; 5 | import '../token/IERC777.sol'; 6 | import '../registry/ERC1820Implementer.sol'; 7 | 8 | 9 | /** 10 | * @title SupplementWitholdingAccount 11 | * 12 | * An ERC777 tokens sender contract that supplements a specified account 13 | * with a percentage of tokens transferred. Commony used to divert funds 14 | * to a tax witholding account to avoid over-spending. 15 | * 16 | * For example, if the percentage is set to 15 and 100 tokens are to be 17 | * sent then this will send an additional 15 tokens from the sender to 18 | * the holding account. If sufficient tokens are not available then this 19 | * will revert. 20 | * 21 | * Note that because this contract transfers token on behalf of the 22 | * sender it requires operator privileges. 23 | * 24 | * State of this contract: stable; development complete but the code is 25 | * unaudited. and may contain bugs and/or security holes. Use at your own 26 | * risk. 27 | * 28 | * @author Jim McDonald 29 | * @notice If you use this contract please consider donating some Ether or 30 | * some of your ERC-777 token to wsl.wealdtech.eth to support continued 31 | * development of these and future contracts 32 | */ 33 | contract SupplementWitholdingAccount is ERC777TokensSender, ERC1820Implementer { 34 | using SafeMath for uint256; 35 | 36 | // The account to send the tokens 37 | mapping(address=>address) public accounts; 38 | // The percentage of tokens to send, in 1/10000s of a percentage 39 | mapping(address=>uint16) public percentages; 40 | 41 | // An event emitted when a supplement is set 42 | event SupplementSet(address holder, address target, uint16 percentage); 43 | // An event emitted when a supplement is removed 44 | event SupplementRemoved(address holder); 45 | 46 | constructor() public { 47 | implementInterface("ERC777TokensSender", false); 48 | } 49 | 50 | /** 51 | * setSuplement sets an account and percentage to which to send tokens 52 | * @param _target the address to which to send tokens 53 | * @param _percentage the percentage of additional tokens to send, 54 | * in 1/10000s of a percentage 55 | */ 56 | function setSupplement(address _target, uint16 _percentage) external { 57 | require(_target != address(0), "target address cannot be 0"); 58 | accounts[msg.sender] = _target; 59 | percentages[msg.sender] = _percentage; 60 | emit SupplementSet(msg.sender, _target, _percentage); 61 | } 62 | 63 | /** 64 | * removeSupplement removes a supplement 65 | */ 66 | function removeSupplement() external { 67 | accounts[msg.sender] = address(0); 68 | percentages[msg.sender] = 0; 69 | emit SupplementRemoved(msg.sender); 70 | } 71 | 72 | function tokensToSend(address operator, address holder, address recipient, uint256 value, bytes calldata data, bytes calldata operatorData) external { 73 | (operator); 74 | 75 | require(accounts[holder] != address(0), "target address not set"); 76 | 77 | // Ignore tokens already being sent to the target account 78 | if (recipient == accounts[holder]) { 79 | return; 80 | } 81 | 82 | IERC777 tokenContract = IERC777(msg.sender); 83 | // Calculate the additional tokens to send 84 | uint256 supplement = value.mul(uint256(percentages[holder])).div(uint256(10000)); 85 | // Round up in the case of the value being an odd granularity 86 | uint256 granularity = tokenContract.granularity(); 87 | if (supplement % granularity != 0) { 88 | supplement = (supplement.div(granularity)+1).mul(granularity); 89 | } 90 | // Transfer the tokens - this throws if it fails 91 | tokenContract.operatorSend(holder, accounts[holder], supplement, data, operatorData); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /test/storage/PermissionedStorage.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assertRevert = require('../helpers/assertRevert'); 4 | const PermissionedStorage = artifacts.require('./PermissionedStorage.sol'); 5 | 6 | const sha3 = require('solidity-sha3').default; 7 | 8 | contract('Permissioned Storage', accounts => { 9 | var instance; 10 | 11 | it('can initialise permissioned storage', async() => { 12 | instance = await PermissionedStorage.new({ from: accounts[0] }); 13 | assert.notEqual(instance, 0); 14 | }); 15 | 16 | it('can read and write a string', async() => { 17 | const key = sha3("test string"); 18 | const value = "Hello, world"; 19 | 20 | const result1 = await instance.getString(key, { from: accounts[0] }); 21 | assert.equal(result1, ""); 22 | 23 | await instance.setString(key, value, { from: accounts[0] }); 24 | const result2 = await instance.getString(key, { from: accounts[0] }); 25 | assert.equal(result2, value); 26 | 27 | await instance.setString(key, "", { from: accounts[0] }); 28 | const result3 = await instance.getString(key, { from: accounts[0] }); 29 | assert.equal(result3, ""); 30 | }); 31 | 32 | it('can read and write a boolean', async() => { 33 | const key = sha3("test boolean"); 34 | const value = true; 35 | 36 | await instance.setBoolean(key, false, { from: accounts[0] }); 37 | const result1 = await instance.getBoolean(key, { from: accounts[0] }); 38 | assert.equal(result1, false); 39 | 40 | await instance.setBoolean(key, value, { from: accounts[0] }); 41 | const result2 = await instance.getBoolean(key, { from: accounts[0] }); 42 | assert.equal(result2, value); 43 | 44 | await instance.setBoolean(key, false, { from: accounts[0] }); 45 | const result3 = await instance.getBoolean(key, { from: accounts[0] }); 46 | assert.equal(result3, false); 47 | }); 48 | 49 | it('can read and write a uint256', async() => { 50 | const key = sha3("test uint256"); 51 | const value = 12345; 52 | 53 | const result1 = await instance.getUInt256(key, { from: accounts[0] }); 54 | assert.equal(result1, 0); 55 | 56 | await instance.setUInt256(key, value, { from: accounts[0] }); 57 | const result2 = await instance.getUInt256(key, { from: accounts[0] }); 58 | assert.equal(result2, value); 59 | 60 | await instance.setUInt256(key, 0, { from: accounts[0] }); 61 | const result3 = await instance.getUInt256(key, { from: accounts[0] }); 62 | assert.equal(result3, 0); 63 | }); 64 | 65 | it('can read and write an int256', async() => { 66 | const key = sha3("test int256"); 67 | const value = -12345; 68 | 69 | const result1 = await instance.getInt256(key, { from: accounts[0] }); 70 | assert.equal(result1, 0); 71 | 72 | await instance.setInt256(key, value, { from: accounts[0] }); 73 | const result2 = await instance.getInt256(key, { from: accounts[0] }); 74 | assert.equal(result2, value); 75 | 76 | await instance.setInt256(key, 0, { from: accounts[0] }); 77 | const result3 = await instance.getInt256(key, { from: accounts[0] }); 78 | assert.equal(result3, 0); 79 | }); 80 | 81 | it('can read and write an address', async() => { 82 | const key = sha3("test address"); 83 | const value = accounts[1]; 84 | 85 | const result1 = await instance.getAddress(key, { from: accounts[0] }); 86 | assert.equal(result1, 0); 87 | 88 | await instance.setAddress(key, value, { from: accounts[0] }); 89 | const result2 = await instance.getAddress(key, { from: accounts[0] }); 90 | assert.equal(result2, value); 91 | 92 | await instance.setAddress(key, '0x0000000000000000000000000000000000000000', { from: accounts[0] }); 93 | const result3 = await instance.getAddress(key, { from: accounts[0] }); 94 | assert.equal(result3, 0); 95 | }); 96 | 97 | it('does not allow unauthorised writes', async() => { 98 | const key = sha3("test unauthorised"); 99 | 100 | try { 101 | await instance.setBoolean(key, true, { from: accounts[1] }); 102 | assert.fail(); 103 | } catch (error) { 104 | assertRevert(error); 105 | } 106 | }); 107 | }); 108 | -------------------------------------------------------------------------------- /contracts/token/ITokenStore.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | // Copyright © 2017 Weald Technology Trading Limited 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 | import '../auth/Permissioned.sol'; 17 | 18 | 19 | /** 20 | * @title ITokenStore 21 | * ITokenStore is the interface for storing tokens as part of a token 22 | * contract. 23 | * 24 | * State of this contract: stable; development complete but the code is 25 | * unaudited. and may contain bugs and/or security holes. Use at your own 26 | * risk. 27 | * 28 | * @author Jim McDonald 29 | * @notice If you use this contract please consider donating some Ether or 30 | * some of your ERC-20 token to wsl.wealdtech.eth to support continued 31 | * development of these and future contracts 32 | */ 33 | contract ITokenStore is Permissioned { 34 | 35 | // Common variables for all token stores 36 | uint256 public totalSupply; 37 | 38 | /** 39 | * @dev Mint tokens and allocate them to a recipient. 40 | */ 41 | function mint(address _recipient, uint256 _amount) public; 42 | 43 | /** 44 | * @dev Transfer tokens directly from owner to recipient, bypassing 45 | * allowances. 46 | */ 47 | function transfer(address _owner, address _recipient, uint256 _amount) public; 48 | 49 | /** 50 | * @dev Obtain a balance. 51 | */ 52 | function balanceOf(address _account) public view returns (uint256); 53 | 54 | /** 55 | * @dev Set an allowance for a (sender, recipient) pair 56 | * The amount of funds must not be more than the sender's current 57 | * balance. 58 | * Note that it is not permitted to change an allocation from a 59 | * non-zero number to another non-zero number, due to potential race 60 | * conditions with transactions. Allocations must always go from 61 | * zero to non-zero, or non-zero to zero. 62 | */ 63 | function setAllowance(address _sender, address _recipient, uint256 _amount) public; 64 | 65 | /** 66 | * @dev Use up some or all of an allocation of tokens. 67 | * Note that this allows third-party transfer of tokens, such that 68 | * if A gives B an allowance of 10 tokens it is possible for B to 69 | * transfer those 10 tokens directly from A to C. 70 | */ 71 | function useAllowance(address _owner, address _allowanceHolder, address _recipient, uint256 _amount) public; 72 | 73 | /** 74 | * @dev Obtain an allowance. 75 | * Note that it is possible for the allowance to be higher than the 76 | * owner's balance, so if using this information to consider if an 77 | * address can pay a certain amount it is important to check using 78 | * both the values obtain from this and balanceOf(). 79 | */ 80 | function allowanceOf(address _owner, address _recipient) public view returns (uint256); 81 | 82 | /** 83 | * @dev Add a token dividend. 84 | * A token dividend is a number of tokens transferred from an owner 85 | * to be shared amongst all existing token holders in proportion to 86 | * their existing holdings. 87 | */ 88 | // function addTokenDividend(address _owner, uint256 _amount) public; 89 | 90 | /** 91 | * @dev Synchronise the data for an account. 92 | * This function must be called before any non-constant operation to 93 | * view or alter the named account is undertaken, otherwise users risk 94 | * obtaining incorrect information. 95 | * @param _account The account to synchronise 96 | */ 97 | function sync(address _account) public; 98 | } 99 | -------------------------------------------------------------------------------- /contracts/tokenoperator/SignatureAuthority.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import '../token/IERC777.sol'; 4 | 5 | 6 | /** 7 | * @title SignatureAuthority 8 | * 9 | * An ERC777 token operator contract that requires a signature from the 10 | * holder to allow the transfer to take place. 11 | * 12 | * State of this contract: stable; development complete but the code is 13 | * unaudited. and may contain bugs and/or security holes. Use at your own 14 | * risk. 15 | * 16 | * @author Jim McDonald 17 | * @notice If you use this contract please consider donating some Ether or 18 | * some of your ERC-777 token to wsl.wealdtech.eth to support continued 19 | * development of these and future contracts 20 | */ 21 | contract SignatureAuthority { 22 | // Mapping is hash=>used, to stop replays 23 | mapping(bytes32=>bool) private usedHashes; 24 | 25 | /** 26 | * send tokens from one account to another using the signature as the authority. 27 | * The signature is created by the holder and signs a hash of the 28 | * (_token, _holder, _recipient, _amount, _nonce) tuple as created by hashForSend(). 29 | * 30 | * @param _token the address of the token contract 31 | * @param _holder the holder of the tokens 32 | * @param _recipient the recipient of the tokens 33 | * @param _amount the number of tokens to send 34 | * @param _data the data field for the operatorSend operation, supplied by the authority 35 | * @param _nonce a unique field for a given (_token, _holder, _recipient, _amount, _nonce) supplied by the authority 36 | * @param _signature the signature supplied by the authority 37 | */ 38 | function send(IERC777 _token, address _holder, address _recipient, uint256 _amount, bytes memory _data, uint256 _nonce, bytes memory _signature) public { 39 | preSend(_token, _holder, _recipient, _amount, _data, _nonce, _signature); 40 | _token.operatorSend(_holder, _recipient, _amount, _data, ""); 41 | } 42 | 43 | function preSend(IERC777 _token, address _holder, address _recipient, uint256 _amount, bytes memory _data, uint256 _nonce, bytes memory _signature) internal { 44 | // Ensure that signature contains the correct number of bytes 45 | require(_signature.length == 65, "length of signature incorrect"); 46 | 47 | bytes32 hash = hashForSend(_token, _holder, _recipient, _amount, _data, _nonce); 48 | require(!usedHashes[hash], "tokens already sent"); 49 | 50 | address signatory = signer(hash, _signature); 51 | require(signatory != address(0), "signatory is invalid"); 52 | require(signatory == _holder, "signatory is not the holder"); 53 | usedHashes[hash] = true; 54 | } 55 | 56 | /** 57 | * This generates the hash that is signed by the holder to authorise a send. 58 | * 59 | * @param _token the address of the token contract 60 | * @param _holder the holder of the tokens 61 | * @param _recipient the recipient of the tokens 62 | * @param _amount the number of tokens to send 63 | * @param _data the data field for the operatorSend operation, supplied by the authority 64 | * @param _nonce a unique field for a given (_token, _holder, _recipient, _amount, _nonce) supplied by the authority 65 | */ 66 | function hashForSend(IERC777 _token, address _holder, address _recipient, uint256 _amount, bytes memory _data, uint256 _nonce) public pure returns (bytes32) { 67 | return keccak256(abi.encodePacked(_token, _holder, _recipient, _amount, _data, _nonce)); 68 | } 69 | 70 | /** 71 | * This obtains the signer of a hash given its signature. 72 | * Note that a returned address of 0 means that the signature is invalid, 73 | * and should be treated as such. 74 | */ 75 | function signer(bytes32 _hash, bytes memory _signature) private pure returns (address) { 76 | bytes32 r; 77 | bytes32 s; 78 | uint8 v; 79 | 80 | assembly { 81 | r := mload(add(_signature, 32)) 82 | s := mload(add(_signature, 64)) 83 | v := and(mload(add(_signature, 65)), 255) 84 | } 85 | 86 | if (v < 27) { 87 | v += 27; 88 | } 89 | 90 | require(v == 27 || v == 28, "signature is invavlid"); 91 | 92 | bytes memory prefix = "\x19Ethereum Signed Message:\n32"; 93 | bytes32 prefixedHash = keccak256(abi.encodePacked(prefix, _hash)); 94 | return ecrecover(prefixedHash, v, r, s); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /test/tokensrecipient/DenySpecifiedTokens.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const asserts = require('../helpers/asserts.js'); 4 | const erc1820 = require('../helpers/erc1820.js'); 5 | const truffleAssert = require('truffle-assertions'); 6 | 7 | const ERC777Token = artifacts.require('ERC777Token'); 8 | const DenySpecifiedTokens = artifacts.require('DenySpecifiedTokens'); 9 | 10 | contract('DenySpecifiedTokens', accounts => { 11 | var erc777Instance; 12 | var erc1820Instance; 13 | var instance; 14 | 15 | const granularity = web3.utils.toBN('10000000000000000'); 16 | const initialSupply = granularity.mul(web3.utils.toBN('10000000')); 17 | 18 | let tokenBalances = {}; 19 | tokenBalances[accounts[0]] = web3.utils.toBN(0); 20 | tokenBalances[accounts[1]] = web3.utils.toBN(0); 21 | 22 | it('sets up', async function() { 23 | erc1820Instance = await erc1820.instance(); 24 | erc777Instance = await ERC777Token.new(1, 'Test token', 'TST', granularity, initialSupply, [], '0x0000000000000000000000000000000000000000', { 25 | from: accounts[0], 26 | gas: 10000000 27 | }); 28 | await erc777Instance.activate({ 29 | from: accounts[0] 30 | }); 31 | tokenBalances[accounts[0]] = initialSupply.clone(); 32 | await asserts.assertTokenBalances(erc777Instance, tokenBalances); 33 | }); 34 | 35 | it('creates the recipient contract', async function() { 36 | instance = await DenySpecifiedTokens.new({ 37 | from: accounts[0] 38 | }); 39 | }); 40 | 41 | it('denies transfers accordingly', async function() { 42 | // Transfer 100*granularity tokens from accounts[0] to accounts[1] 43 | const amount = granularity.mul(web3.utils.toBN('100')); 44 | await erc777Instance.send(accounts[1], amount, [], { 45 | from: accounts[0] 46 | }); 47 | tokenBalances[accounts[0]] = tokenBalances[accounts[0]].sub(amount); 48 | tokenBalances[accounts[1]] = tokenBalances[accounts[1]].add(amount); 49 | await asserts.assertTokenBalances(erc777Instance, tokenBalances); 50 | 51 | // Register the recipient 52 | await erc1820Instance.setInterfaceImplementer(accounts[1], web3.utils.soliditySha3('ERC777TokensRecipient'), instance.address, { 53 | from: accounts[1] 54 | }); 55 | 56 | // Transfer 100*granularity tokens from accounts[0] to accounts[1] 57 | await erc777Instance.send(accounts[1], amount, [], { 58 | from: accounts[0] 59 | }); 60 | tokenBalances[accounts[0]] = tokenBalances[accounts[0]].sub(amount); 61 | tokenBalances[accounts[1]] = tokenBalances[accounts[1]].add(amount); 62 | await asserts.assertTokenBalances(erc777Instance, tokenBalances); 63 | 64 | // Deny transfers from token 65 | await instance.addToken(erc777Instance.address, { 66 | from: accounts[1] 67 | }); 68 | 69 | // Attempt to transfer tokens to accounts[1] - should fail 70 | truffleAssert.reverts( 71 | erc777Instance.send(accounts[1], amount, [], { 72 | from: accounts[0] 73 | }), 'token is explicitly disallowed'); 74 | 75 | // Clear denial 76 | await instance.removeToken(erc777Instance.address, { 77 | from: accounts[1] 78 | }); 79 | 80 | // Transfer 100*granularity tokens from accounts[0] to accounts[1] 81 | await erc777Instance.send(accounts[1], amount, [], { 82 | from: accounts[0] 83 | }); 84 | tokenBalances[accounts[0]] = tokenBalances[accounts[0]].sub(amount); 85 | tokenBalances[accounts[1]] = tokenBalances[accounts[1]].add(amount); 86 | await asserts.assertTokenBalances(erc777Instance, tokenBalances); 87 | 88 | // Unregister the recipient 89 | await erc1820Instance.setInterfaceImplementer(accounts[1], web3.utils.soliditySha3('ERC777TokensRecipient'), '0x0000000000000000000000000000000000000000', { 90 | from: accounts[1] 91 | }); 92 | 93 | // Transfer 100*granularity tokens from accounts[0] to accounts[1] 94 | await erc777Instance.send(accounts[1], amount, [], { 95 | from: accounts[0] 96 | }); 97 | tokenBalances[accounts[0]] = tokenBalances[accounts[0]].sub(amount); 98 | tokenBalances[accounts[1]] = tokenBalances[accounts[1]].add(amount); 99 | await asserts.assertTokenBalances(erc777Instance, tokenBalances); 100 | }); 101 | }); 102 | -------------------------------------------------------------------------------- /test/tokenssender/AllowSpecifiedRecipients.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const asserts = require('../helpers/asserts.js'); 4 | const erc1820 = require('../helpers/erc1820.js'); 5 | const truffleAssert = require('truffle-assertions'); 6 | 7 | const ERC777Token = artifacts.require('ERC777Token'); 8 | const AllowSpecifiedRecipients = artifacts.require('AllowSpecifiedRecipients'); 9 | 10 | contract('AllowSpecifiedRecipients', accounts => { 11 | var erc777Instance; 12 | var erc1820Instance; 13 | var instance; 14 | 15 | const granularity = web3.utils.toBN('10000000000000000'); 16 | const initialSupply = granularity.mul(web3.utils.toBN('10000000')); 17 | 18 | let tokenBalances = {}; 19 | tokenBalances[accounts[0]] = web3.utils.toBN(0); 20 | tokenBalances[accounts[1]] = web3.utils.toBN(0); 21 | tokenBalances[accounts[2]] = web3.utils.toBN(0); 22 | 23 | it('sets up', async function() { 24 | erc1820Instance = await erc1820.instance(); 25 | erc777Instance = await ERC777Token.new(1, 'Test token', 'TST', granularity, initialSupply, [], '0x0000000000000000000000000000000000000000', { 26 | from: accounts[0], 27 | gas: 10000000 28 | }); 29 | await erc777Instance.activate({ 30 | from: accounts[0] 31 | }); 32 | tokenBalances[accounts[0]] = tokenBalances[accounts[0]].add(initialSupply); 33 | await asserts.assertTokenBalances(erc777Instance, tokenBalances); 34 | 35 | // accounts[1] is our test source address so send it some tokens 36 | const amount = granularity.mul(web3.utils.toBN('100')); 37 | await erc777Instance.send(accounts[1], amount, [], { 38 | from: accounts[0] 39 | }); 40 | tokenBalances[accounts[0]] = tokenBalances[accounts[0]].sub(amount); 41 | tokenBalances[accounts[1]] = tokenBalances[accounts[1]].add(amount); 42 | await asserts.assertTokenBalances(erc777Instance, tokenBalances); 43 | }); 44 | 45 | it('creates the sender contract', async function() { 46 | instance = await AllowSpecifiedRecipients.new({ 47 | from: accounts[0] 48 | }); 49 | }); 50 | 51 | it('handles recipients accordingly', async function() { 52 | // Register the sender 53 | await erc1820Instance.setInterfaceImplementer(accounts[1], web3.utils.soliditySha3('ERC777TokensSender'), instance.address, { 54 | from: accounts[1] 55 | }); 56 | 57 | // Attempt to tranfer tokens to accounts[2] - should fail 58 | const amount = granularity.mul(web3.utils.toBN('5')); 59 | await truffleAssert.reverts( 60 | erc777Instance.send(accounts[2], amount, [], { 61 | from: accounts[1] 62 | }), 'not allowed to send to that recipient'); 63 | 64 | // Set up a recipient for accounts[2] 65 | await instance.setRecipient(accounts[2], { 66 | from: accounts[1] 67 | }); 68 | assert.equal(await instance.getRecipient(accounts[1], accounts[2]), true); 69 | 70 | // Transfer tokens to accounts[2] 71 | await erc777Instance.send(accounts[2], amount, [], { 72 | from: accounts[1] 73 | }); 74 | tokenBalances[accounts[1]] = tokenBalances[accounts[1]].sub(amount); 75 | tokenBalances[accounts[2]] = tokenBalances[accounts[2]].add(amount); 76 | await asserts.assertTokenBalances(erc777Instance, tokenBalances); 77 | 78 | // Attempt to tranfer tokens to accounts[3] - should fail 79 | await truffleAssert.reverts( 80 | erc777Instance.send(accounts[3], amount, [], { 81 | from: accounts[1] 82 | }), 'not allowed to send to that recipient'); 83 | 84 | // Clear recipient for accounts[2] 85 | await instance.clearRecipient(accounts[2], { 86 | from: accounts[1] 87 | }); 88 | assert.equal(await instance.getRecipient(accounts[1], accounts[2]), false); 89 | 90 | // Attempt to tranfer tokens to accounts[2] - should fail 91 | await truffleAssert.reverts( 92 | erc777Instance.send(accounts[2], amount, [], { 93 | from: accounts[1] 94 | }), 'not allowed to send to that recipient'); 95 | 96 | // Unregister the sender 97 | await erc1820Instance.setInterfaceImplementer(accounts[1], web3.utils.soliditySha3('ERC777TokensSender'), '0x0000000000000000000000000000000000000000', { 98 | from: accounts[1] 99 | }); 100 | }); 101 | }); 102 | -------------------------------------------------------------------------------- /contracts/tokenoperator/MerkleProofAuthority.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import '../token/IERC777.sol'; 4 | 5 | 6 | /** 7 | * @title MerkleProofAuthority 8 | * 9 | * An ERC777 token operator contract that requires a merkle proof from 10 | * holder to allow the transfer to take place. 11 | * 12 | * State of this contract: stable; development complete but the code is 13 | * unaudited. and may contain bugs and/or security holes. Use at your own 14 | * risk. 15 | * 16 | * @author Jim McDonald 17 | * @notice If you use this contract please consider donating some Ether or 18 | * some of your ERC-777 token to wsl.wealdtech.eth to support continued 19 | * development of these and future contracts 20 | */ 21 | contract MerkleProofAuthority { 22 | // Mapping is token=>holder=>merkle root 23 | mapping(address=>mapping(address=>bytes32)) private roots; 24 | 25 | // Mapping is hash=>used, to stop replays 26 | mapping(bytes32=>bool) private usedHashes; 27 | 28 | // Event emitted when a root is set 29 | event Root(address token, address holder, bytes32 root); 30 | 31 | /** 32 | * Set the merkle root for a holder of a token. 33 | * @param _token the address of the token contract 34 | * @param _root the root of the merkle tree 35 | */ 36 | function setRoot(address _token, bytes32 _root) public { 37 | roots[_token][msg.sender] = _root; 38 | emit Root(_token, msg.sender, _root); 39 | } 40 | 41 | function getRoot(address _token, address _holder) public view returns (bytes32) { 42 | return roots[_token][_holder]; 43 | } 44 | 45 | /** 46 | * send tokens from one account to another using a merkle proof as the authority. 47 | * The signature is created by the holder and signs a hash of the 48 | * (_token, _holder, _recipient, _amount, _nonce) tuple as created by hashForSend(). 49 | * 50 | * @param _token the address of the token contract 51 | * @param _holder the holder of the tokens 52 | * @param _recipient the recipient of the tokens 53 | * @param _amount the number of tokens to send 54 | * @param _data the data field for the operatorSend operation, supplied by the authority 55 | * @param _nonce a unique field for a given (_token, _holder, _recipient, _amount, _nonce) supplied by the authority 56 | * @param _path the path of the leaf through the merkle tree to the root 57 | * @param _proof the other hashes that form the merkle tree to the root 58 | */ 59 | function send(IERC777 _token, address _holder, address _recipient, uint256 _amount, bytes memory _data, uint256 _nonce, uint256 _path, bytes32[] memory _proof) public { 60 | bytes32 hash = hashForSend(_token, _holder, _recipient, _amount, _data, _nonce); 61 | require(!usedHashes[hash], "tokens already sent"); 62 | 63 | require(prove(hash, _path, _proof, roots[address(_token)][_holder]), "merkle proof invalid"); 64 | usedHashes[hash] = true; 65 | 66 | _token.operatorSend(_holder, _recipient, _amount, _data, ""); 67 | } 68 | 69 | /** 70 | * This generates the hash that is signed by the holder to authorise a send. 71 | * 72 | * @param _token the address of the token contract 73 | * @param _holder the holder of the tokens 74 | * @param _recipient the recipient of the tokens 75 | * @param _amount the number of tokens to send 76 | * @param _data the data field for the operatorSend operation, supplied by the authority 77 | * @param _nonce a unique field for a given (_token, _holder, _recipient, _amount, _nonce) supplied by the authority 78 | */ 79 | function hashForSend(IERC777 _token, address _holder, address _recipient, uint256 _amount, bytes memory _data, uint256 _nonce) public pure returns (bytes32) { 80 | return keccak256(abi.encodePacked(_token, _holder, _recipient, _amount, _data, _nonce)); 81 | } 82 | 83 | /** 84 | * Prove that a given leaf is part of a merkle tree 85 | * @param _leaf the leaf to prove 86 | * @param _path the path to follow 87 | * @param _proof the intermediate nodes 88 | * @param _root the root 89 | */ 90 | function prove(bytes32 _leaf, uint256 _path, bytes32[] memory _proof, bytes32 _root) private pure returns (bool) { 91 | bytes32 hash = _leaf; 92 | for (uint256 i = 0; i < _proof.length; i++) { 93 | if ((_path & 0x01) == 1) { 94 | hash = keccak256(abi.encodePacked(hash, _proof[i])); 95 | } else { 96 | hash = keccak256(abi.encodePacked(_proof[i], hash)); 97 | } 98 | _path = _path >> 1; 99 | } 100 | return (hash == _root); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /contracts/tokenssender/CounterSignature.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import '../math/SafeMath.sol'; 4 | import '../token/ERC777TokensSender.sol'; 5 | import '../registry/ERC1820Implementer.sol'; 6 | 7 | 8 | /** 9 | * @title CounterSignature 10 | * 11 | * An ERC777 tokens sender contract that requires a counter-signature 12 | * from an approved address to allow a transfer to proceed. 13 | * 14 | * Note that the contract only allows one counter-signatory for each 15 | * holder. 16 | * 17 | * State of this contract: stable; development complete but the code is 18 | * unaudited. and may contain bugs and/or security holes. Use at your own 19 | * risk. 20 | * 21 | * @author Jim McDonald 22 | * @notice If you use this contract please consider donating some Ether or 23 | * some of your ERC-777 token to wsl.wealdtech.eth to support continued 24 | * development of these and future contracts 25 | */ 26 | contract CounterSignature is ERC777TokensSender, ERC1820Implementer { 27 | using SafeMath for uint256; 28 | 29 | // Mapping is holder=>counter signatory 30 | mapping(address=>address) public counterSignatories; 31 | 32 | // Mapping is hash=>used, to stop replays 33 | mapping(bytes32=>bool) private usedHashes; 34 | 35 | // An event emitted when a counter-signatory is set 36 | event CounterSignatorySet(address holder, address signatory); 37 | // An event emitted when a counter-signatory is cleared 38 | event CounterSignatoryCleared(address holder); 39 | 40 | constructor() public { 41 | implementInterface("ERC777TokensSender", false); 42 | } 43 | 44 | /** 45 | * setCounterSignatory sets a counter-signatory. 46 | */ 47 | function setCounterSignatory(address _counterSignatory) external { 48 | counterSignatories[msg.sender] = _counterSignatory; 49 | emit CounterSignatorySet(msg.sender, _counterSignatory); 50 | } 51 | 52 | /** 53 | * clearCounterSignatory clears a counter-signatory. 54 | */ 55 | function clearCounterSignatory() external { 56 | counterSignatories[msg.sender] = address(0); 57 | emit CounterSignatoryCleared(msg.sender); 58 | } 59 | 60 | /** 61 | * getCounterSignatory gets a counter-signatory. 62 | */ 63 | function getCounterSignatory(address _holder) external view returns (address) { 64 | return counterSignatories[_holder]; 65 | } 66 | 67 | /** 68 | * This expects operatorData to contain the signature (65 bytes) 69 | */ 70 | function tokensToSend(address operator, address holder, address recipient, uint256 amount, bytes calldata data, bytes calldata operatorData) external { 71 | 72 | // Ensure that operatorData contains the correct number of bytes 73 | require(operatorData.length == 65, "length of operator data incorrect"); 74 | 75 | // Token, operator, holder, recipient, amount 76 | bytes32 hash = hashForCounterSignature(msg.sender, operator, holder, recipient, amount, data); 77 | require(!usedHashes[hash], "tokens already sent"); 78 | 79 | address counterSignatory = signer(hash, operatorData); 80 | require(counterSignatory != address(0), "signatory is invalid"); 81 | require(counterSignatory == counterSignatories[holder], "signatory is not a valid countersignatory"); 82 | usedHashes[hash] = true; 83 | } 84 | 85 | /** 86 | * This generates the hash for the counter-signature 87 | */ 88 | function hashForCounterSignature(address token, address operator, address holder, address recipient, uint256 amount, bytes memory data) public view returns (bytes32) { 89 | return keccak256(abi.encodePacked(token, address(this), operator, holder, recipient, amount, data)); 90 | } 91 | 92 | /** 93 | * This obtains the signer of a hash given its signature. 94 | * Note that a returned value of 0 means that the signature is invalid, 95 | * and should be treated as such. 96 | */ 97 | function signer(bytes32 _hash, bytes memory _signature) private pure returns (address) { 98 | bytes32 r; 99 | bytes32 s; 100 | uint8 v; 101 | 102 | assembly { 103 | r := mload(add(_signature, 32)) 104 | s := mload(add(_signature, 64)) 105 | v := and(mload(add(_signature, 65)), 255) 106 | } 107 | 108 | if (v < 27) { 109 | v += 27; 110 | } 111 | 112 | if (v != 27 && v != 28) { 113 | return address(0); 114 | } 115 | 116 | bytes memory prefix = "\x19Ethereum Signed Message:\n32"; 117 | bytes32 prefixedHash = keccak256(abi.encodePacked(prefix, _hash)); 118 | return ecrecover(prefixedHash, v, r, s); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /test/tokensrecipient/AllowSpecifiedTokens.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assertRevert = require('../helpers/assertRevert.js'); 4 | const erc1820 = require('../helpers/erc1820.js'); 5 | 6 | const ERC777Token = artifacts.require('ERC777Token'); 7 | const AllowSpecifiedTokens = artifacts.require('AllowSpecifiedTokens'); 8 | 9 | contract('AllowSpecifiedTokens', accounts => { 10 | var erc777Instance; 11 | var erc1820Instance; 12 | var instance; 13 | 14 | let expectedBalances = [ 15 | web3.utils.toBN(0), 16 | web3.utils.toBN(0), 17 | web3.utils.toBN(0), 18 | web3.utils.toBN(0), 19 | web3.utils.toBN(0) 20 | ]; 21 | const granularity = web3.utils.toBN('10000000000000000'); 22 | const initialSupply = granularity.mul(web3.utils.toBN('10000000')); 23 | 24 | // Helper to confirm that balances are as expected 25 | async function confirmBalances() { 26 | for (var i = 0; i < expectedBalances.length; i++) { 27 | assert.equal((await erc777Instance.balanceOf(accounts[i])).toString(10), expectedBalances[i].toString(10), 'Balance of account ' + i + ' is incorrect'); 28 | } 29 | // Also confirm total supply 30 | assert.equal((await erc777Instance.totalSupply()).toString(), expectedBalances.reduce((a, b) => a.add(b), web3.utils.toBN('0')).toString(), 'Total supply is incorrect'); 31 | } 32 | 33 | it('sets up', async function() { 34 | erc1820Instance = await erc1820.instance(); 35 | erc777Instance = await ERC777Token.new(1, 'Test token', 'TST', granularity, initialSupply, [], '0x0000000000000000000000000000000000000000', { 36 | from: accounts[0], 37 | gas: 10000000 38 | }); 39 | await erc777Instance.activate({ 40 | from: accounts[0] 41 | }); 42 | expectedBalances[0] = initialSupply.mul(web3.utils.toBN('1')); 43 | await confirmBalances(); 44 | }); 45 | 46 | it('creates the recipient contract', async function() { 47 | instance = await AllowSpecifiedTokens.new({ 48 | from: accounts[0] 49 | }); 50 | }); 51 | 52 | it('allows transfers accordingly', async function() { 53 | // Transfer 100*granularity tokens from accounts[0] to accounts[1] 54 | const amount = granularity.mul(web3.utils.toBN('100')); 55 | await erc777Instance.send(accounts[1], amount, [], { 56 | from: accounts[0] 57 | }); 58 | expectedBalances[0] = expectedBalances[0].sub(amount); 59 | expectedBalances[1] = expectedBalances[1].add(amount); 60 | await confirmBalances(); 61 | 62 | // Register the recipient 63 | await erc1820Instance.setInterfaceImplementer(accounts[1], web3.utils.soliditySha3('ERC777TokensRecipient'), instance.address, { 64 | from: accounts[1] 65 | }); 66 | 67 | // Attempt to transfer tokens to accounts[1] - should fail 68 | try { 69 | await erc777Instance.send(accounts[1], amount, [], { 70 | from: accounts[0] 71 | }); 72 | assert.fail(); 73 | } catch (error) { 74 | assertRevert(error); 75 | } 76 | 77 | // Allow transfers from token 78 | await instance.addToken(erc777Instance.address, { 79 | from: accounts[1] 80 | }); 81 | 82 | // Transfer 100*granularity tokens from accounts[0] to accounts[1] 83 | await erc777Instance.send(accounts[1], amount, [], { 84 | from: accounts[0] 85 | }); 86 | expectedBalances[0] = expectedBalances[0].sub(amount); 87 | expectedBalances[1] = expectedBalances[1].add(amount); 88 | await confirmBalances(); 89 | 90 | // Clear allowance 91 | await instance.removeToken(erc777Instance.address, { 92 | from: accounts[1] 93 | }); 94 | 95 | // Attempt to transfer tokens to accounts[1] - should fail 96 | try { 97 | await erc777Instance.send(accounts[1], amount, [], { 98 | from: accounts[0] 99 | }); 100 | assert.fail(); 101 | } catch (error) { 102 | assertRevert(error); 103 | } 104 | 105 | // Unregister the recipient 106 | await erc1820Instance.setInterfaceImplementer(accounts[1], web3.utils.soliditySha3('ERC777TokensRecipient'), '0x0000000000000000000000000000000000000000', { 107 | from: accounts[1] 108 | }); 109 | 110 | // Transfer 100*granularity tokens from accounts[0] to accounts[1] 111 | await erc777Instance.send(accounts[1], amount, [], { 112 | from: accounts[0] 113 | }); 114 | expectedBalances[0] = expectedBalances[0].sub(amount); 115 | expectedBalances[1] = expectedBalances[1].add(amount); 116 | await confirmBalances(); 117 | }); 118 | }); 119 | -------------------------------------------------------------------------------- /test/tokenssender/DenySpecifiedRecipients.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const asserts = require('../helpers/asserts.js'); 4 | const erc1820 = require('../helpers/erc1820.js'); 5 | const truffleAssert = require('truffle-assertions'); 6 | 7 | const ERC777Token = artifacts.require('ERC777Token'); 8 | const DenySpecifiedRecipients = artifacts.require('DenySpecifiedRecipients'); 9 | 10 | contract('DenySpecifiedRecipients', accounts => { 11 | var erc777Instance; 12 | var erc1820Instance; 13 | var instance; 14 | 15 | const granularity = web3.utils.toBN('10000000000000000'); 16 | const initialSupply = granularity.mul(web3.utils.toBN('10000000')); 17 | 18 | let tokenBalances = {}; 19 | tokenBalances[accounts[0]] = web3.utils.toBN(0); 20 | tokenBalances[accounts[1]] = web3.utils.toBN(0); 21 | tokenBalances[accounts[2]] = web3.utils.toBN(0); 22 | tokenBalances[accounts[3]] = web3.utils.toBN(0); 23 | 24 | it('sets up', async function() { 25 | erc1820Instance = await erc1820.instance(); 26 | erc777Instance = await ERC777Token.new(1, 'Test token', 'TST', granularity, initialSupply, [], '0x0000000000000000000000000000000000000000', { 27 | from: accounts[0], 28 | gas: 10000000 29 | }); 30 | await erc777Instance.activate({ 31 | from: accounts[0] 32 | }); 33 | tokenBalances[accounts[0]] = tokenBalances[accounts[0]].add(initialSupply); 34 | await asserts.assertTokenBalances(erc777Instance, tokenBalances); 35 | 36 | 37 | // accounts[1] is our test source address so send it some tokens 38 | const amount = granularity.mul(web3.utils.toBN('100')); 39 | await erc777Instance.send(accounts[1], amount, [], { 40 | from: accounts[0] 41 | }); 42 | tokenBalances[accounts[0]] = tokenBalances[accounts[0]].sub(amount); 43 | tokenBalances[accounts[1]] = tokenBalances[accounts[1]].add(amount); 44 | await asserts.assertTokenBalances(erc777Instance, tokenBalances); 45 | }); 46 | 47 | it('creates the sender contract', async function() { 48 | instance = await DenySpecifiedRecipients.new({ 49 | from: accounts[0] 50 | }); 51 | }); 52 | 53 | it('handles recipients accordingly', async function() { 54 | // Register the sender 55 | await erc1820Instance.setInterfaceImplementer(accounts[1], web3.utils.soliditySha3('ERC777TokensSender'), instance.address, { 56 | from: accounts[1] 57 | }); 58 | 59 | // Transfer tokens to accounts[2] 60 | const amount = granularity.mul(web3.utils.toBN('5')); 61 | await erc777Instance.send(accounts[2], amount, [], { 62 | from: accounts[1] 63 | }); 64 | tokenBalances[accounts[1]] = tokenBalances[accounts[1]].sub(amount); 65 | tokenBalances[accounts[2]] = tokenBalances[accounts[2]].add(amount); 66 | await asserts.assertTokenBalances(erc777Instance, tokenBalances); 67 | 68 | // Set up a recipient for accounts[2] 69 | await instance.setRecipient(accounts[2], { 70 | from: accounts[1] 71 | }); 72 | assert.equal(await instance.getRecipient(accounts[1], accounts[2]), true); 73 | 74 | // Attempt to tranfer tokens to accounts[2] - should fail 75 | await truffleAssert.reverts( 76 | erc777Instance.send(accounts[2], amount, [], { 77 | from: accounts[1] 78 | }), 'transfers to that recipient are blocked'); 79 | 80 | // Transfer tokens to accounts[3] 81 | await erc777Instance.send(accounts[3], granularity.mul(web3.utils.toBN('5')), [], { 82 | from: accounts[1] 83 | }); 84 | tokenBalances[accounts[1]] = tokenBalances[accounts[1]].sub(amount); 85 | tokenBalances[accounts[3]] = tokenBalances[accounts[3]].add(amount); 86 | await asserts.assertTokenBalances(erc777Instance, tokenBalances); 87 | 88 | // Clear recipient for accounts[2] 89 | await instance.clearRecipient(accounts[2], { 90 | from: accounts[1] 91 | }); 92 | assert.equal(await instance.getRecipient(accounts[1], accounts[2]), false); 93 | 94 | // Transfer tokens to accounts[2] 95 | await erc777Instance.send(accounts[2], amount, [], { 96 | from: accounts[1] 97 | }); 98 | tokenBalances[accounts[1]] = tokenBalances[accounts[1]].sub(amount); 99 | tokenBalances[accounts[2]] = tokenBalances[accounts[2]].add(amount); 100 | await asserts.assertTokenBalances(erc777Instance, tokenBalances); 101 | 102 | // Unregister the sender 103 | await erc1820Instance.setInterfaceImplementer(accounts[1], web3.utils.soliditySha3('ERC777TokensSender'), '0x0000000000000000000000000000000000000000', { 104 | from: accounts[1] 105 | }); 106 | }); 107 | }); 108 | -------------------------------------------------------------------------------- /contracts/tokenoperator/PaidSignatureAuthority.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import '../token/IERC777.sol'; 4 | 5 | 6 | /** 7 | * @title PaidSignatureAuthority 8 | * 9 | * An ERC777 token operator contract that requires a signature from the 10 | * holder to allow the transfer to take place, and provides a fee to the 11 | * the submitter for submitting the operation 12 | * 13 | * State of this contract: stable; development complete but the code is 14 | * unaudited. and may contain bugs and/or security holes. Use at your own 15 | * risk. 16 | * 17 | * @author Jim McDonald 18 | * @notice If you use this contract please consider donating some Ether or 19 | * some of your ERC-777 token to wsl.wealdtech.eth to support continued 20 | * development of these and future contracts 21 | */ 22 | contract PaidSignatureAuthority { 23 | // Mapping is hash=>used, to stop replays 24 | mapping(bytes32=>bool) private usedHashes; 25 | 26 | /** 27 | * send tokens from one account to another using the signature as the authority. 28 | * The signature is created by the holder and signs a hash of the 29 | * (_token, _holder, _recipient, _amount, _fee, _nonce) tuple as created by hashForSend(). 30 | * 31 | * @param _token the address of the token contract 32 | * @param _holder the holder of the tokens 33 | * @param _recipient the recipient of the tokens 34 | * @param _amount the number of tokens to send to the recipient 35 | * @param _fee the number of tokens to send to the submitter. The fee can be 0 36 | * @param _data the data field for the operatorSend operation, supplied by the authority 37 | * @param _nonce a unique field for a given (_token, _holder, _recipient, _amount, _nonce) supplied by the authority 38 | * @param _signature the signature supplied by the authority 39 | */ 40 | function send(IERC777 _token, address _holder, address _recipient, uint256 _amount, uint256 _fee, bytes memory _data, uint256 _nonce, bytes memory _signature) public { 41 | preSend(_token, _holder, _recipient, _amount, _fee, _data, _nonce, _signature); 42 | if (_fee != 0) { 43 | _token.operatorSend(_holder, msg.sender, _fee, _data, "Transfer fee"); 44 | } 45 | _token.operatorSend(_holder, _recipient, _amount, _data, ""); 46 | } 47 | 48 | function preSend(IERC777 _token, address _holder, address _recipient, uint256 _amount, uint256 _fee, bytes memory _data, uint256 _nonce, bytes memory _signature) internal { 49 | // Ensure that signature contains the correct number of bytes 50 | require(_signature.length == 65, "length of signature incorrect"); 51 | 52 | bytes32 hash = hashForSend(_token, _holder, _recipient, _amount, _fee, _data, _nonce); 53 | require(!usedHashes[hash], "tokens already sent"); 54 | 55 | address signatory = signer(hash, _signature); 56 | require(signatory != address(0), "signatory is invalid"); 57 | require(signatory == _holder, "signatory is not the holder"); 58 | usedHashes[hash] = true; 59 | } 60 | 61 | /** 62 | * This generates the hash that is signed by the holder to authorise a send. 63 | * 64 | * @param _token the address of the token contract 65 | * @param _holder the holder of the tokens 66 | * @param _recipient the recipient of the tokens 67 | * @param _amount the number of tokens to send to the recipient 68 | * @param _fee the number of tokens to send to the submitter 69 | * @param _data the data field for the operatorSend operation, supplied by the authority 70 | * @param _nonce a unique field for a given (_token, _holder, _recipient, _amount, _nonce) supplied by the authority 71 | */ 72 | function hashForSend(IERC777 _token, address _holder, address _recipient, uint256 _amount, uint256 _fee, bytes memory _data, uint256 _nonce) public pure returns (bytes32) { 73 | return keccak256(abi.encodePacked(_token, _holder, _recipient, _amount, _fee, _data, _nonce)); 74 | } 75 | 76 | /** 77 | * This obtains the signer of a hash given its signature. 78 | * Note that a returned address of 0 means that the signature is invalid, 79 | * and should be treated as such. 80 | */ 81 | function signer(bytes32 _hash, bytes memory _signature) private pure returns (address) { 82 | bytes32 r; 83 | bytes32 s; 84 | uint8 v; 85 | 86 | assembly { 87 | r := mload(add(_signature, 32)) 88 | s := mload(add(_signature, 64)) 89 | v := and(mload(add(_signature, 65)), 255) 90 | } 91 | 92 | if (v < 27) { 93 | v += 27; 94 | } 95 | 96 | require(v == 27 || v == 28, "signature is invavlid"); 97 | 98 | bytes memory prefix = "\x19Ethereum Signed Message:\n32"; 99 | bytes32 prefixedHash = keccak256(abi.encodePacked(prefix, _hash)); 100 | return ecrecover(prefixedHash, v, r, s); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /test/tokenssender/EmitMessage.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const erc1820 = require('../helpers/erc1820.js'); 4 | const truffleAssert = require('truffle-assertions'); 5 | 6 | const ERC777Token = artifacts.require('ERC777Token'); 7 | const EmitMessage = artifacts.require('EmitMessage'); 8 | 9 | contract('EmitMessage', accounts => { 10 | var erc777Instance; 11 | var erc1820Instance; 12 | var instance; 13 | 14 | const granularity = web3.utils.toBN('10000000000000000'); 15 | const initialSupply = granularity.mul(web3.utils.toBN('10000000')); 16 | 17 | it('sets up', async function() { 18 | erc1820Instance = await erc1820.instance(); 19 | erc777Instance = await ERC777Token.new(1, 'Test token', 'TST', granularity, initialSupply, [], '0x0000000000000000000000000000000000000000', { 20 | from: accounts[0], 21 | gas: 10000000 22 | }); 23 | await erc777Instance.activate({ 24 | from: accounts[0] 25 | }); 26 | 27 | // accounts[1] is our test source address so send it some tokens 28 | await erc777Instance.send(accounts[1], granularity.mul(web3.utils.toBN('100')), [], { 29 | from: accounts[0] 30 | }); 31 | }); 32 | 33 | it('creates the sender contract', async function() { 34 | instance = await EmitMessage.new({ 35 | from: accounts[0] 36 | }); 37 | }); 38 | 39 | it('generates messages accordingly', async function() { 40 | // Register the sender 41 | await erc1820Instance.setInterfaceImplementer(accounts[1], web3.utils.soliditySha3('ERC777TokensSender'), instance.address, { 42 | from: accounts[1] 43 | }); 44 | 45 | // Transfer tokens to accounts[2] 46 | var tx = await erc777Instance.send(accounts[2], granularity.mul(web3.utils.toBN('5')), [], { 47 | from: accounts[1] 48 | }); 49 | 50 | // Set up a message for accounts[1] -> accounts[2] 51 | await instance.setMessage(accounts[2], 'Transfer to account 2', { 52 | from: accounts[1] 53 | }); 54 | assert.equal(await instance.getMessage(accounts[1], accounts[2]), 'Transfer to account 2'); 55 | 56 | // Transfer tokens to accounts[2] 57 | var tx = await erc777Instance.send(accounts[2], granularity.mul(web3.utils.toBN('5')), [], { 58 | from: accounts[1] 59 | }); 60 | 61 | // Ensure the message is present 62 | assert.equal(2, tx.receipt.rawLogs.length); 63 | var found = false; 64 | for (var i = 0; i < tx.receipt.rawLogs.length; i++) { 65 | if (tx.receipt.rawLogs[i].topics[0] == web3.utils.soliditySha3('Message(address,address,string)') && 66 | tx.receipt.rawLogs[i].data.slice(258,300) == '5472616e7366657220746f206163636f756e742032') { 67 | found = true; 68 | } 69 | } 70 | assert.equal(true, found); 71 | 72 | // Set up a message for accounts[1] 73 | await instance.setMessage('0x0000000000000000000000000000000000000000', 'Transfer from account 1', { 74 | from: accounts[1] 75 | }); 76 | assert.equal(await instance.getMessage(accounts[1], '0x0000000000000000000000000000000000000000'), 'Transfer from account 1'); 77 | 78 | // Transfer tokens to accounts[2] 79 | var tx = await erc777Instance.send(accounts[2], granularity.mul(web3.utils.toBN('5')), [], { 80 | from: accounts[1] 81 | }); 82 | 83 | // Ensure the specific message is present 84 | assert.equal(2, tx.receipt.rawLogs.length); 85 | found = false; 86 | for (var i = 0; i < tx.receipt.rawLogs.length; i++) { 87 | if (tx.receipt.rawLogs[i].topics[0] == web3.utils.soliditySha3('Message(address,address,string)') && 88 | tx.receipt.rawLogs[i].data.slice(258,300) == '5472616e7366657220746f206163636f756e742032') { 89 | found = true; 90 | } 91 | } 92 | assert.equal(true, found); 93 | 94 | // Transfer tokens to accounts[3] 95 | var tx = await erc777Instance.send(accounts[3], granularity.mul(web3.utils.toBN('5')), [], { 96 | from: accounts[1] 97 | }); 98 | 99 | // Ensure the general message is present 100 | assert.equal(2, tx.receipt.rawLogs.length); 101 | found = false; 102 | for (var i = 0; i < tx.receipt.rawLogs.length; i++) { 103 | if (tx.receipt.rawLogs[i].topics[0] == web3.utils.soliditySha3('Message(address,address,string)') && 104 | tx.receipt.rawLogs[i].data.slice(258,304) == '5472616e736665722066726f6d206163636f756e742031') { 105 | found = true; 106 | } 107 | } 108 | assert.equal(true, found); 109 | 110 | // Unregister the sender 111 | await erc1820Instance.setInterfaceImplementer(accounts[1], web3.utils.soliditySha3('ERC777TokensSender'), '0x0000000000000000000000000000000000000000', { 112 | from: accounts[1] 113 | }); 114 | }); 115 | }); 116 | -------------------------------------------------------------------------------- /test/auth/Authorised.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assertRevert = require('../helpers/assertRevert'); 4 | const AuthorisedTest1 = artifacts.require('samplecontracts/AuthorisedTest1.sol'); 5 | 6 | const sha3 = require('solidity-sha3').default; 7 | 8 | contract('Authorised', accounts => { 9 | var instance; 10 | 11 | it('can create a contract instance', async function() { 12 | instance = await AuthorisedTest1.new(); 13 | }); 14 | 15 | it('cannot carry out an authorised action', async function() { 16 | try { 17 | await instance.setInt(5, '0x', {from: accounts[1]}); 18 | assert.fail(); 19 | } catch(error) { 20 | assertRevert(error); 21 | } 22 | }); 23 | 24 | it('can authorise an action with suitable signature', async function() { 25 | // Create and sign an action allowing accounts[1] to set int values 26 | const actionHash = sha3(accounts[1], await instance.ACTION_SET_INT()); 27 | const signature = await web3.eth.sign(actionHash, accounts[0]); 28 | 29 | await instance.setInt(5, signature, {from: accounts[1]}); 30 | assert.equal(await instance.intValue(), 5); 31 | await instance.setInt(6, signature, {from: accounts[1]}); 32 | assert.equal(await instance.intValue(), 6); 33 | }); 34 | 35 | it('can only carry out an unrepeatable action once', async function() { 36 | // Create and sign an action allowing accounts[1] to set int value once only 37 | const actionHash = sha3(accounts[1], await instance.ACTION_SET_INT_ONCE()); 38 | const signature = await web3.eth.sign(actionHash, accounts[0]); 39 | 40 | await instance.setInt(7, signature, {from: accounts[1]}); 41 | assert.equal(await instance.intValue(), 7); 42 | try { 43 | await instance.setInt(8, signature, {from: accounts[1]}); 44 | assert.fail(); 45 | } catch(error) { 46 | assertRevert(error); 47 | } 48 | }); 49 | 50 | it('can authorise an action with a value', async function() { 51 | // Create and sign an action allowing accounts[1] to set a specific int value 52 | const actionHash = sha3(accounts[1], await instance.ACTION_SET_INT(), '0x0000000000000000000000000000000000000000000000000000000000000008'); 53 | const signature = await web3.eth.sign(actionHash, accounts[0]); 54 | 55 | await instance.setInt(8, signature, {from: accounts[1]}); 56 | assert.equal(await instance.intValue(), 8); 57 | try { 58 | await instance.setInt(9, signature, {from: accounts[1]}); 59 | assert.fail(); 60 | } catch(error) { 61 | assertRevert(error); 62 | } 63 | }); 64 | 65 | it('can only carry out an unrepeatable action with a value once', async function() { 66 | // Create and sign an action allowing accounts[1] to set a specific int value once 67 | const actionHash = sha3(accounts[1], await instance.ACTION_SET_INT_ONCE(), '0x0000000000000000000000000000000000000000000000000000000000000009'); 68 | const signature = await web3.eth.sign(actionHash, accounts[0]); 69 | 70 | await instance.setInt(9, signature, {from: accounts[1]}); 71 | assert.equal(await instance.intValue(), 9); 72 | try { 73 | await instance.setInt(9, signature, {from: accounts[1]}); 74 | assert.fail(); 75 | } catch(error) { 76 | assertRevert(error); 77 | } 78 | }); 79 | 80 | it('can add another authoriser', async function() { 81 | // Create and sign an action allowing accounts[1] to set int values 82 | const actionHash = sha3(accounts[1], await instance.ACTION_SET_INT()); 83 | const signature = await web3.eth.sign(actionHash, accounts[2]); 84 | 85 | // Ensure the signature is not accepted as the signer is not an authoriser 86 | try { 87 | await instance.setInt(10, signature, {from: accounts[1]}); 88 | assert.fail(); 89 | } catch(error) { 90 | assertRevert(error); 91 | } 92 | 93 | // Add accounts[2] to the list of authorisers 94 | await instance.setPermission(accounts[2], await instance.PERM_AUTHORISER(), true); 95 | 96 | // Ensure the signature is now accepted 97 | await instance.setInt(10, signature, {from: accounts[1]}); 98 | assert.equal(await instance.intValue(), 10); 99 | }); 100 | 101 | it('can remove an authoriser', async function() { 102 | // Create and sign an action allowing accounts[1] to set int values 103 | const actionHash = sha3(accounts[1], await instance.ACTION_SET_INT()); 104 | const signature = await web3.eth.sign(actionHash, accounts[2]); 105 | 106 | // Ensure the signature is accepted 107 | await instance.setInt(11, signature, {from: accounts[1]}); 108 | assert.equal(await instance.intValue(), 11); 109 | 110 | // Remove accounts[2] from the list of authorisers 111 | await instance.setPermission(accounts[2], await instance.PERM_AUTHORISER(), false); 112 | 113 | // Ensure the signature is not accepted as the signer is no longer an authoriser 114 | try { 115 | await instance.setInt(12, signature, {from: accounts[1]}); 116 | assert.fail(); 117 | } catch(error) { 118 | assertRevert(error); 119 | } 120 | }); 121 | }); 122 | -------------------------------------------------------------------------------- /test/tokensrecipient/DenyLowAmount.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const asserts = require('../helpers/asserts.js'); 4 | const erc1820 = require('../helpers/erc1820.js'); 5 | const truffleAssert = require('truffle-assertions'); 6 | 7 | const ERC777Token = artifacts.require('ERC777Token'); 8 | const DenyLowAmount = artifacts.require('DenyLowAmount'); 9 | 10 | contract('DenyLowAmount', accounts => { 11 | var erc777Instance; 12 | var erc1820Instance; 13 | var instance; 14 | 15 | const granularity = web3.utils.toBN('10000000000000000'); 16 | const initialSupply = granularity.mul(web3.utils.toBN('10000000')); 17 | 18 | let tokenBalances = {}; 19 | tokenBalances[accounts[0]] = web3.utils.toBN(0); 20 | tokenBalances[accounts[1]] = web3.utils.toBN(0); 21 | 22 | it('sets up', async function() { 23 | erc1820Instance = await erc1820.instance(); 24 | erc777Instance = await ERC777Token.new(1, 'Test token', 'TST', granularity, initialSupply, [], '0x0000000000000000000000000000000000000000', { 25 | from: accounts[0], 26 | gas: 10000000 27 | }); 28 | await erc777Instance.activate({ 29 | from: accounts[0] 30 | }); 31 | tokenBalances[accounts[0]] = initialSupply.clone(); 32 | await asserts.assertTokenBalances(erc777Instance, tokenBalances); 33 | }); 34 | 35 | it('creates the recipient contract', async function() { 36 | instance = await DenyLowAmount.new({ 37 | from: accounts[0] 38 | }); 39 | }); 40 | 41 | it('denies low-amount transfers accordingly', async function() { 42 | // Transfer 100*granularity tokens from accounts[0] to accounts[1] 43 | const amount = granularity.mul(web3.utils.toBN('100')); 44 | const lowAmount = granularity.mul(web3.utils.toBN('5')); 45 | await erc777Instance.send(accounts[1], amount, [], { 46 | from: accounts[0] 47 | }); 48 | tokenBalances[accounts[0]] = tokenBalances[accounts[0]].sub(amount); 49 | tokenBalances[accounts[1]] = tokenBalances[accounts[1]].add(amount); 50 | await asserts.assertTokenBalances(erc777Instance, tokenBalances); 51 | 52 | // Register the recipient 53 | await erc1820Instance.setInterfaceImplementer(accounts[1], web3.utils.soliditySha3('ERC777TokensRecipient'), instance.address, { 54 | from: accounts[1] 55 | }); 56 | 57 | // Transfer 100*granularity tokens from accounts[0] to accounts[1] 58 | await erc777Instance.send(accounts[1], amount, [], { 59 | from: accounts[0] 60 | }); 61 | tokenBalances[accounts[0]] = tokenBalances[accounts[0]].sub(amount); 62 | tokenBalances[accounts[1]] = tokenBalances[accounts[1]].add(amount); 63 | await asserts.assertTokenBalances(erc777Instance, tokenBalances); 64 | 65 | // Deny transfers less than 10*granularity from token 66 | await instance.setMinimumAmount(erc777Instance.address, granularity.mul(web3.utils.toBN('10')), { 67 | from: accounts[1] 68 | }); 69 | 70 | // Attempt to transfer granularity*5 tokens to accounts[1] - should fail 71 | truffleAssert.reverts( 72 | erc777Instance.send(accounts[1], lowAmount, [], { 73 | from: accounts[0] 74 | }), 'transfer value too low to be accepted'); 75 | 76 | // Transfer 100*granularity tokens from accounts[0] to accounts[1] 77 | await erc777Instance.send(accounts[1], amount, [], { 78 | from: accounts[0] 79 | }); 80 | tokenBalances[accounts[0]] = tokenBalances[accounts[0]].sub(amount); 81 | tokenBalances[accounts[1]] = tokenBalances[accounts[1]].add(amount); 82 | await asserts.assertTokenBalances(erc777Instance, tokenBalances); 83 | 84 | // Clear limit 85 | await instance.clearMinimumAmount(erc777Instance.address, { 86 | from: accounts[1] 87 | }); 88 | 89 | // Transfer 5*granularity tokens from accounts[0] to accounts[1] 90 | await erc777Instance.send(accounts[1], lowAmount, [], { 91 | from: accounts[0] 92 | }); 93 | tokenBalances[accounts[0]] = tokenBalances[accounts[0]].sub(lowAmount); 94 | tokenBalances[accounts[1]] = tokenBalances[accounts[1]].add(lowAmount); 95 | await asserts.assertTokenBalances(erc777Instance, tokenBalances); 96 | 97 | // Unregister the recipient 98 | await erc1820Instance.setInterfaceImplementer(accounts[1], web3.utils.soliditySha3('ERC777TokensRecipient'), '0x0000000000000000000000000000000000000000', { 99 | from: accounts[1] 100 | }); 101 | 102 | // Transfer 100*granularity tokens from accounts[0] to accounts[1] 103 | await erc777Instance.send(accounts[1], amount, [], { 104 | from: accounts[0] 105 | }); 106 | tokenBalances[accounts[0]] = tokenBalances[accounts[0]].sub(amount); 107 | tokenBalances[accounts[1]] = tokenBalances[accounts[1]].add(amount); 108 | await asserts.assertTokenBalances(erc777Instance, tokenBalances); 109 | }); 110 | }); 111 | -------------------------------------------------------------------------------- /test/tokenoperator/VaultRecipient.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const asserts = require('../helpers/asserts.js'); 4 | const truffleAssert = require('truffle-assertions'); 5 | 6 | const ERC777Token = artifacts.require('ERC777Token'); 7 | const VaultRecipient = artifacts.require('VaultRecipient'); 8 | 9 | contract('VaultRecipient', accounts => { 10 | var erc777Instance; 11 | var operator; 12 | 13 | const granularity = web3.utils.toBN('10000000000000000'); 14 | const initialSupply = granularity.mul(web3.utils.toBN('10000000')); 15 | 16 | let tokenBalances = {}; 17 | tokenBalances[accounts[0]] = web3.utils.toBN(0); 18 | tokenBalances[accounts[1]] = web3.utils.toBN(0); 19 | tokenBalances[accounts[2]] = web3.utils.toBN(0); 20 | 21 | it('sets up', async function() { 22 | erc777Instance = await ERC777Token.new(1, 'Test token', 'TST', granularity, initialSupply, [], '0x0000000000000000000000000000000000000000', { 23 | from: accounts[0], 24 | gas: 10000000 25 | }); 26 | await erc777Instance.activate({ 27 | from: accounts[0] 28 | }); 29 | tokenBalances[accounts[0]] = tokenBalances[accounts[0]].add(initialSupply); 30 | await asserts.assertTokenBalances(erc777Instance, tokenBalances); 31 | 32 | // accounts[1] is our test source address so send it some tokens 33 | const amount = granularity.mul(web3.utils.toBN('100')); 34 | await erc777Instance.send(accounts[1], amount, [], { 35 | from: accounts[0] 36 | }); 37 | tokenBalances[accounts[0]] = tokenBalances[accounts[0]].sub(amount); 38 | tokenBalances[accounts[1]] = tokenBalances[accounts[1]].add(amount); 39 | await asserts.assertTokenBalances(erc777Instance, tokenBalances); 40 | }); 41 | 42 | it('creates the operator contract', async function() { 43 | operator = await VaultRecipient.new({ 44 | from: accounts[0] 45 | }); 46 | }); 47 | 48 | it('does not send when not set up', async function() { 49 | const amount = granularity.mul(web3.utils.toBN('5')); 50 | 51 | // Attempt to transfer tokens as accounts[2] from accounts[1] to accounts[2] 52 | await truffleAssert.reverts( 53 | operator.send(erc777Instance.address, accounts[1], amount, [], { 54 | from: accounts[2] 55 | }), 'vault not configured'); 56 | 57 | // Authorise the operator for accounts[1] 58 | assert.equal(await erc777Instance.isOperatorFor(operator.address, accounts[1]), false); 59 | await erc777Instance.authorizeOperator(operator.address, { 60 | from: accounts[1] 61 | }); 62 | assert.equal(await erc777Instance.isOperatorFor(operator.address, accounts[1]), true); 63 | 64 | // Attempt to transfer tokens as accounts[2] from accounts[1] to accounts[2] 65 | await truffleAssert.reverts( 66 | operator.send(erc777Instance.address, accounts[1], amount, [], { 67 | from: accounts[2] 68 | }), 'vault not configured'); 69 | }); 70 | 71 | it('transfers when set up', async function() { 72 | // Set accounts[2] as account[1]'s vault 73 | await operator.setVault(erc777Instance.address, accounts[2], { 74 | from: accounts[1] 75 | }); 76 | 77 | const vaultAccount = await operator.getVault(erc777Instance.address, accounts[1]); 78 | assert.equal(vaultAccount, accounts[2]); 79 | 80 | // Transfer tokens as accounts[2] from accounts[1] to accounts[2] 81 | const amount = granularity.mul(web3.utils.toBN('5')); 82 | await operator.send(erc777Instance.address, accounts[1], amount, [], { 83 | from: accounts[2] 84 | }); 85 | tokenBalances[accounts[1]] = tokenBalances[accounts[1]].sub(amount); 86 | tokenBalances[accounts[2]] = tokenBalances[accounts[2]].add(amount); 87 | await asserts.assertTokenBalances(erc777Instance, tokenBalances); 88 | }); 89 | 90 | it('does not transfer to other account', async function() { 91 | const amount = granularity.mul(web3.utils.toBN('100')); 92 | // Attempt to transfer tokens as accounts[3] from accounts[1] to accounts[3] 93 | await truffleAssert.reverts( 94 | operator.send(erc777Instance.address, accounts[1], amount, [], { 95 | from: accounts[3] 96 | }), 'not the vault account'); 97 | }); 98 | 99 | it('does not work when de-registered', async function() { 100 | await erc777Instance.revokeOperator(operator.address, { 101 | from: accounts[1] 102 | }); 103 | assert.equal(await erc777Instance.isOperatorFor(operator.address, accounts[1]), false); 104 | 105 | const amount = granularity.mul(web3.utils.toBN('5')); 106 | // Attempt to transfer tokens - should fail as deregistered 107 | await truffleAssert.reverts( 108 | operator.send(erc777Instance.address, accounts[1], amount, [], { 109 | from: accounts[2] 110 | }), 'not allowed to send'); 111 | }); 112 | }); 113 | -------------------------------------------------------------------------------- /contracts/token/DividendTokenStore.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | // Copyright © 2017, 2018 Weald Technology Trading Limited 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 | import '../math/SafeMath.sol'; 17 | import './SimpleTokenStore.sol'; 18 | 19 | 20 | /** 21 | * @title DividendTokenStore 22 | * DividendTokenStore is an enhancement of the SimpleTokenStore that 23 | * provides the ability to issue token-based dividends in an efficient 24 | * manner. 25 | * 26 | * The dividend token store allows multiple dividends to be added. Each 27 | * dividend is defined by the number of tokens to be shared between the 28 | * exisiting token holders (including the holder who issued the dividend) 29 | * on a pro rata basis, and the total supply of tokens (minus dividends) 30 | * at the time the dividend was issued. 31 | * 32 | * Outstanding dividends are added to balances whenever that balance is 33 | * addressed, ensuring that balances are always up-to-date whilst 34 | * reducing the number of transactions required to do so to a minimum. 35 | * 36 | * This contract has individual permissions for each major operation. 37 | * In addition to those in SimpleTokenStore these are: 38 | * - PERM_ISSUE_DIVIDEND: permission to issue a dividend 39 | * 40 | * State of this contract: stable; development complete but the code is 41 | * unaudited. and may contain bugs and/or security holes. Use at your own 42 | * risk. 43 | * 44 | * @author Jim McDonald 45 | * @notice If you use this contract please consider donating some Ether or 46 | * some of your ERC-20 token to wsl.wealdtech.eth to support continued 47 | * development of these and future contracts 48 | */ 49 | contract DividendTokenStore is SimpleTokenStore { 50 | using SafeMath for uint256; 51 | 52 | // nextDividend keeps track of the next dividend for each account 53 | mapping(address=>uint256) private nextDividends; 54 | 55 | // Dividend tracks the number of tokens issued in the dividend and the 56 | // supply at the time (minus the dividend itself) 57 | struct Dividend { 58 | uint256 amount; 59 | uint256 supply; 60 | } 61 | Dividend[] private dividends; 62 | // The address holding dividends that have yet to be paid out 63 | address private DIVIDEND_ADDRESS = address(0x01); 64 | 65 | // Permissions for each operation 66 | bytes32 internal constant PERM_ISSUE_DIVIDEND = keccak256("token storage: issue dividend"); 67 | 68 | /** 69 | * @dev obtain the dividend(s) owing to a given account. 70 | */ 71 | function dividendsOwing(address _account) internal view returns(uint256) { 72 | uint256 initialBalance = balances[_account]; 73 | uint256 balance = initialBalance; 74 | // Iterate over all outstanding dividends 75 | uint256 nextDividend = nextDividends[_account]; 76 | for (uint256 currentDividend = nextDividend; currentDividend < dividends.length; currentDividend++) { 77 | balance = balance.add(balance.mul(dividends[currentDividend].amount).div(dividends[currentDividend].supply)); 78 | } 79 | 80 | return balance - initialBalance; 81 | } 82 | 83 | /** 84 | * @dev Synchronise the data for an account. 85 | * This function must be called before any non-constant operation to 86 | * view or alter the named account is undertaken, otherwise users risk 87 | * obtaining incorrect information. 88 | * @param _account The account to synchronise 89 | */ 90 | function sync(address _account) public { 91 | uint256 accountDividend = dividendsOwing(_account); 92 | if (accountDividend > 0) { 93 | transfer(DIVIDEND_ADDRESS, _account, accountDividend); 94 | nextDividends[_account] = dividends.length; 95 | } 96 | } 97 | 98 | /** 99 | * @dev Obtain a balance. 100 | */ 101 | function balanceOf(address _account) public view returns (uint256 balance) { 102 | return balances[_account] + dividendsOwing(_account); 103 | } 104 | 105 | /** 106 | * @dev issue a dividend. 107 | * This issues a dividend from the given sender for the given amount. 108 | * It shared the amount out fairly between all participants. 109 | */ 110 | function issueDividend(address _sender, uint256 _amount) public ifPermitted(msg.sender, PERM_ISSUE_DIVIDEND) { 111 | dividends.push(Dividend({amount: _amount, supply: totalSupply - _amount})); 112 | transfer(_sender, DIVIDEND_ADDRESS, _amount); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /test/tokenoperator/FixedTimeRelease.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const asserts = require('../helpers/asserts.js'); 4 | const evm = require('../helpers/evm.js'); 5 | const truffleAssert = require('truffle-assertions'); 6 | 7 | const ERC777Token = artifacts.require('ERC777Token'); 8 | const FixedTimeRelease = artifacts.require('FixedTimeRelease'); 9 | 10 | contract('FixedTimeRelease', accounts => { 11 | var erc777Instance; 12 | var operator; 13 | 14 | const granularity = web3.utils.toBN('10000000000000000'); 15 | const initialSupply = granularity.mul(web3.utils.toBN('10000000')); 16 | 17 | let tokenBalances = {}; 18 | tokenBalances[accounts[0]] = web3.utils.toBN(0); 19 | tokenBalances[accounts[1]] = web3.utils.toBN(0); 20 | tokenBalances[accounts[2]] = web3.utils.toBN(0); 21 | 22 | it('sets up', async function() { 23 | erc777Instance = await ERC777Token.new(1, 'Test token', 'TST', granularity, initialSupply, [], '0x0000000000000000000000000000000000000000', { 24 | from: accounts[0], 25 | gas: 10000000 26 | }); 27 | await erc777Instance.activate({ 28 | from: accounts[0] 29 | }); 30 | tokenBalances[accounts[0]] = initialSupply.clone(); 31 | await asserts.assertTokenBalances(erc777Instance, tokenBalances); 32 | 33 | // accounts[1] is our test source address so send it some tokens 34 | const amount = granularity.mul(web3.utils.toBN('100')); 35 | await erc777Instance.send(accounts[1], amount, [], { 36 | from: accounts[0] 37 | }); 38 | tokenBalances[accounts[0]] = tokenBalances[accounts[0]].sub(amount); 39 | tokenBalances[accounts[1]] = tokenBalances[accounts[1]].add(amount); 40 | await asserts.assertTokenBalances(erc777Instance, tokenBalances); 41 | }); 42 | 43 | it('creates the operator contract', async function() { 44 | operator = await FixedTimeRelease.new({ 45 | from: accounts[0] 46 | }); 47 | }); 48 | 49 | it('does not send when not set up', async function() { 50 | const amount = granularity.mul(web3.utils.toBN('5')); 51 | 52 | // Attempt to transfer tokens as accounts[3] from accounts[1] to accounts[2] 53 | await truffleAssert.reverts( 54 | operator.send(erc777Instance.address, accounts[1], accounts[2], amount, { 55 | from: accounts[3] 56 | }), 'no release time set'); 57 | }); 58 | 59 | it('does not transfer before the release date', async function() { 60 | // Set up the contract as an operator for accounts[1] 61 | await erc777Instance.authorizeOperator(operator.address, { 62 | from: accounts[1] 63 | }); 64 | 65 | const now = Math.round(new Date().getTime() / 1000) + (await evm.currentOffset()).result; 66 | const expiry = now + 60; 67 | // Allow everyone to empty the account after expiry 68 | await operator.setReleaseTimestamp(erc777Instance.address, expiry, { 69 | from: accounts[1] 70 | }); 71 | 72 | const amount = granularity.mul(web3.utils.toBN('5')); 73 | // Attempt to transfer tokens as accounts[3] from accounts[1] to accounts[2] 74 | await truffleAssert.reverts( 75 | operator.send(erc777Instance.address, accounts[1], accounts[2], amount, { 76 | from: accounts[3] 77 | }), 'not yet released'); 78 | }); 79 | 80 | it('cannot bring the release date forward', async function() { 81 | var expiry = await operator.getReleaseTimestamp(erc777Instance.address, accounts[1]); 82 | expiry = expiry.sub(web3.utils.toBN('1')); 83 | 84 | await truffleAssert.reverts( 85 | operator.setReleaseTimestamp(erc777Instance.address, expiry, { 86 | from: accounts[1] 87 | }), 'cannot bring release time forward'); 88 | }); 89 | 90 | it('transfers after the release date', async function() { 91 | // Expiry is 1 minute so go past that 92 | await evm.increaseTime(61); 93 | await evm.mine(); 94 | 95 | const amount = granularity.mul(web3.utils.toBN('5')); 96 | // Transfer tokens as accounts[3] from accounts[1] to accounts[2] 97 | await operator.send(erc777Instance.address, accounts[1], accounts[2], amount, { 98 | from: accounts[3] 99 | }); 100 | tokenBalances[accounts[1]] = tokenBalances[accounts[1]].sub(amount); 101 | tokenBalances[accounts[2]] = tokenBalances[accounts[2]].add(amount); 102 | await asserts.assertTokenBalances(erc777Instance, tokenBalances); 103 | }); 104 | 105 | it('does not work when de-registered', async function() { 106 | await erc777Instance.revokeOperator(operator.address, { 107 | from: accounts[1] 108 | }); 109 | assert.equal(await erc777Instance.isOperatorFor(operator.address, accounts[1]), false); 110 | 111 | const amount = granularity.mul(web3.utils.toBN('5')); 112 | await truffleAssert.reverts( 113 | operator.send(erc777Instance.address, accounts[1], accounts[2], amount, { 114 | from: accounts[3] 115 | }), 'not allowed to send'); 116 | }); 117 | }); 118 | -------------------------------------------------------------------------------- /test/tokenoperator/FixedTimeLockup.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const asserts = require('../helpers/asserts.js'); 4 | const evm = require('../helpers/evm.js'); 5 | const truffleAssert = require('truffle-assertions'); 6 | 7 | const ERC777Token = artifacts.require('ERC777Token'); 8 | const FixedTimeLockup = artifacts.require('FixedTimeLockup'); 9 | 10 | contract('FixedTimeLockup', accounts => { 11 | var erc777Instance; 12 | var operator; 13 | 14 | const granularity = web3.utils.toBN('10000000000000000'); 15 | const initialSupply = granularity.mul(web3.utils.toBN('10000000')); 16 | 17 | let tokenBalances = {}; 18 | tokenBalances[accounts[0]] = web3.utils.toBN(0); 19 | tokenBalances[accounts[1]] = web3.utils.toBN(0); 20 | tokenBalances[accounts[2]] = web3.utils.toBN(0); 21 | 22 | it('sets up', async function() { 23 | erc777Instance = await ERC777Token.new(1, 'Test token', 'TST', granularity, initialSupply, [], '0x0000000000000000000000000000000000000000', { 24 | from: accounts[0], 25 | gas: 10000000 26 | }); 27 | await erc777Instance.activate({ 28 | from: accounts[0] 29 | }); 30 | tokenBalances[accounts[0]] = initialSupply.clone(); 31 | await asserts.assertTokenBalances(erc777Instance, tokenBalances); 32 | 33 | // accounts[1] is our test source address so send it some tokens 34 | const amount = granularity.mul(web3.utils.toBN('100')); 35 | await erc777Instance.send(accounts[1], amount, [], { 36 | from: accounts[0] 37 | }); 38 | tokenBalances[accounts[0]] = tokenBalances[accounts[0]].sub(amount); 39 | tokenBalances[accounts[1]] = tokenBalances[accounts[1]].add(amount); 40 | await asserts.assertTokenBalances(erc777Instance, tokenBalances); 41 | }); 42 | 43 | it('creates the operator contract', async function() { 44 | operator = await FixedTimeLockup.new({ 45 | from: accounts[0] 46 | }); 47 | }); 48 | 49 | it('does not send when not set up', async function() { 50 | const amount = granularity.mul(web3.utils.toBN('5')); 51 | 52 | // Attempt to transfer tokens as accounts[3] from accounts[1] to accounts[2] 53 | await truffleAssert.reverts( 54 | operator.send(erc777Instance.address, accounts[1], accounts[2], amount, { 55 | from: accounts[3] 56 | }), 'no release time set'); 57 | }); 58 | 59 | it('does not transfer before the release date', async function() { 60 | // Set up the contract as an operator for accounts[1] 61 | await erc777Instance.authorizeOperator(operator.address, { 62 | from: accounts[1] 63 | }); 64 | 65 | const now = Math.round(new Date().getTime() / 1000) + (await evm.currentOffset()).result; 66 | const expiry = now + 60; 67 | // Allow everyone to empty the account after expiry 68 | await operator.setReleaseTimestamp(erc777Instance.address, expiry, { 69 | from: accounts[1] 70 | }); 71 | 72 | const amount = granularity.mul(web3.utils.toBN('5')); 73 | await truffleAssert.reverts( 74 | operator.send(erc777Instance.address, accounts[1], accounts[2], amount, { 75 | from: accounts[3] 76 | }), 'not yet released'); 77 | }); 78 | 79 | it('does not transfer without an allowance', async function() { 80 | // Expiry is 1 minute so go past that 81 | await evm.increaseTime(61); 82 | await evm.mine(); 83 | 84 | // But we don't have an allowance so expect this to fail 85 | const amount = granularity.mul(web3.utils.toBN('5')); 86 | // Attempt to transfer tokens as accounts[3] from accounts[1] to accounts[2] 87 | await truffleAssert.reverts( 88 | operator.send(erc777Instance.address, accounts[1], accounts[2], amount, { 89 | from: accounts[3] 90 | }), 'amount exceeds allowance'); 91 | }); 92 | 93 | it('transfers after the release date and within the allowance', async function() { 94 | // Set the allowance 95 | await operator.setAllowance(erc777Instance.address, accounts[3], 0, granularity.mul(web3.utils.toBN('100')), { 96 | from: accounts[1] 97 | }); 98 | 99 | const amount = granularity.mul(web3.utils.toBN('5')); 100 | // Transfer tokens as accounts[3] from accounts[1] to accounts[2] 101 | await operator.send(erc777Instance.address, accounts[1], accounts[2], amount, { 102 | from: accounts[3] 103 | }); 104 | tokenBalances[accounts[1]] = tokenBalances[accounts[1]].sub(amount); 105 | tokenBalances[accounts[2]] = tokenBalances[accounts[2]].add(amount); 106 | await asserts.assertTokenBalances(erc777Instance, tokenBalances); 107 | }); 108 | 109 | it('does not work when de-registered', async function() { 110 | await erc777Instance.revokeOperator(operator.address, { 111 | from: accounts[1] 112 | }); 113 | assert.equal(await erc777Instance.isOperatorFor(operator.address, accounts[1]), false); 114 | 115 | const amount = granularity.mul(web3.utils.toBN('5')); 116 | await truffleAssert.reverts( 117 | operator.send(erc777Instance.address, accounts[1], accounts[2], amount, { 118 | from: accounts[3] 119 | }), 'not allowed to send'); 120 | }); 121 | }); 122 | -------------------------------------------------------------------------------- /test/tokenssender/Lockup.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const asserts = require('../helpers/asserts.js'); 4 | const erc1820 = require('../helpers/erc1820.js'); 5 | const evm = require('../helpers/evm.js'); 6 | const truffleAssert = require('truffle-assertions'); 7 | 8 | const ERC777Token = artifacts.require('ERC777Token'); 9 | const Lockup = artifacts.require('Lockup'); 10 | 11 | contract('Lockup', accounts => { 12 | var erc777Instance; 13 | var erc1820Instance; 14 | var instance; 15 | 16 | const granularity = web3.utils.toBN('10000000000000000'); 17 | const initialSupply = granularity.mul(web3.utils.toBN('10000000')); 18 | 19 | let tokenBalances = {}; 20 | tokenBalances[accounts[0]] = web3.utils.toBN(0); 21 | tokenBalances[accounts[1]] = web3.utils.toBN(0); 22 | tokenBalances[accounts[2]] = web3.utils.toBN(0); 23 | 24 | it('sets up', async function() { 25 | erc1820Instance = await erc1820.instance(); 26 | erc777Instance = await ERC777Token.new(1, 'Test token', 'TST', granularity, initialSupply, [], '0x0000000000000000000000000000000000000000', { 27 | from: accounts[0], 28 | gas: 10000000 29 | }); 30 | await erc777Instance.activate({ 31 | from: accounts[0] 32 | }); 33 | tokenBalances[accounts[0]] = tokenBalances[accounts[0]].add(initialSupply); 34 | await asserts.assertTokenBalances(erc777Instance, tokenBalances); 35 | 36 | // accounts[1] is our test source address so send it some tokens 37 | const amount = granularity.mul(web3.utils.toBN('100')); 38 | await erc777Instance.send(accounts[1], amount, [], { 39 | from: accounts[0] 40 | }); 41 | tokenBalances[accounts[0]] = tokenBalances[accounts[0]].sub(amount); 42 | tokenBalances[accounts[1]] = tokenBalances[accounts[1]].add(amount); 43 | await asserts.assertTokenBalances(erc777Instance, tokenBalances); 44 | }); 45 | 46 | it('creates the sender contract', async function() { 47 | instance = await Lockup.new({ 48 | from: accounts[0] 49 | }); 50 | 51 | // Expiry is set to 1 day after the current time 52 | const now = Math.round(new Date().getTime() / 1000) + (await evm.currentOffset()).result; 53 | const expiry = now + 86400; 54 | await instance.setExpiry(erc777Instance.address, expiry, { 55 | from: accounts[1] 56 | }); 57 | }); 58 | 59 | it('sets up the operator', async function() { 60 | // Register the sender 61 | await erc1820Instance.setInterfaceImplementer(accounts[1], web3.utils.soliditySha3('ERC777TokensSender'), instance.address, { 62 | from: accounts[1] 63 | }); 64 | 65 | // Make accounts[3] an operator for accounts[1] 66 | await erc777Instance.authorizeOperator(accounts[3], { 67 | from: accounts[1] 68 | }); 69 | assert.equal(await erc777Instance.isOperatorFor(accounts[3], accounts[1]), true); 70 | }); 71 | 72 | it('does not transfer before the lockup expires', async function() { 73 | const amount = granularity.mul(web3.utils.toBN('10')); 74 | await truffleAssert.reverts( 75 | erc777Instance.send(accounts[2], amount, [], { 76 | from: accounts[1] 77 | }), 'lockup has not expired'); 78 | await truffleAssert.reverts( 79 | erc777Instance.operatorSend(accounts[1], accounts[2], amount, [], [], { 80 | from: accounts[3] 81 | }), 'lockup has not expired'); 82 | }); 83 | 84 | it('transfers after the lockup expires', async function() { 85 | // Go past expiry 86 | await evm.increaseTime(86401); 87 | await evm.mine(); 88 | 89 | const amount = granularity.mul(web3.utils.toBN('10')); 90 | await erc777Instance.send(accounts[2], amount, [], { 91 | from: accounts[1] 92 | }); 93 | tokenBalances[accounts[1]] = tokenBalances[accounts[1]].sub(amount); 94 | tokenBalances[accounts[2]] = tokenBalances[accounts[2]].add(amount); 95 | await asserts.assertTokenBalances(erc777Instance, tokenBalances); 96 | 97 | await erc777Instance.operatorSend(accounts[1], accounts[2], amount, [], [], { 98 | from: accounts[1] 99 | }); 100 | tokenBalances[accounts[1]] = tokenBalances[accounts[1]].sub(amount); 101 | tokenBalances[accounts[2]] = tokenBalances[accounts[2]].add(amount); 102 | await asserts.assertTokenBalances(erc777Instance, tokenBalances); 103 | }); 104 | 105 | it('does not transfer more than the allowance', async function() { 106 | const amount = granularity.mul(web3.utils.toBN('105')); 107 | await truffleAssert.reverts( 108 | erc777Instance.send(accounts[2], amount, [], { 109 | from: accounts[1] 110 | }), 'not enough tokens in holder\'s account'); 111 | await truffleAssert.reverts( 112 | erc777Instance.operatorSend(accounts[1], accounts[2], amount, [], [], { 113 | from: accounts[3] 114 | }), 'not enough tokens in holder\'s account'); 115 | }); 116 | 117 | it('de-registers', async function() { 118 | await erc777Instance.revokeOperator(accounts[3], { 119 | from: accounts[1] 120 | }); 121 | assert.equal(await erc777Instance.isOperatorFor(accounts[3], accounts[1]), false); 122 | }); 123 | }); 124 | -------------------------------------------------------------------------------- /test/tokenoperator/FixedAllowance.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const asserts = require('../helpers/asserts.js'); 4 | const truffleAssert = require('truffle-assertions'); 5 | 6 | const ERC777Token = artifacts.require('ERC777Token'); 7 | const FixedAllowance = artifacts.require('FixedAllowance'); 8 | 9 | contract('FixedAllowance', accounts => { 10 | var erc777Instance; 11 | var operator; 12 | 13 | const granularity = web3.utils.toBN('10000000000000000'); 14 | const initialSupply = granularity.mul(web3.utils.toBN('10000000')); 15 | 16 | let tokenBalances = {}; 17 | tokenBalances[accounts[0]] = web3.utils.toBN(0); 18 | tokenBalances[accounts[1]] = web3.utils.toBN(0); 19 | tokenBalances[accounts[2]] = web3.utils.toBN(0); 20 | 21 | it('sets up', async function() { 22 | erc777Instance = await ERC777Token.new(1, 'Test token', 'TST', granularity, initialSupply, [], '0x0000000000000000000000000000000000000000', { 23 | from: accounts[0], 24 | gas: 10000000 25 | }); 26 | await erc777Instance.activate({ 27 | from: accounts[0] 28 | }); 29 | tokenBalances[accounts[0]] = tokenBalances[accounts[0]].add(initialSupply); 30 | await asserts.assertTokenBalances(erc777Instance, tokenBalances); 31 | 32 | // accounts[1] is our test source address so send it some tokens 33 | const amount = granularity.mul(web3.utils.toBN('100')); 34 | await erc777Instance.send(accounts[1], amount, [], { 35 | from: accounts[0] 36 | }); 37 | tokenBalances[accounts[0]] = tokenBalances[accounts[0]].sub(amount); 38 | tokenBalances[accounts[1]] = tokenBalances[accounts[1]].add(amount); 39 | await asserts.assertTokenBalances(erc777Instance, tokenBalances); 40 | }); 41 | 42 | it('creates the operator contract', async function() { 43 | operator = await FixedAllowance.new({ 44 | from: accounts[0] 45 | }); 46 | }); 47 | 48 | it('does not send when not set up', async function() { 49 | const amount = granularity.mul(web3.utils.toBN('5')); 50 | 51 | await operator.setAllowance(erc777Instance.address, accounts[3], 0, granularity.mul(web3.utils.toBN('100')), { 52 | from: accounts[1] 53 | }); 54 | 55 | // Attempt to transfer tokens as accounts[3] from accounts[1] to accounts[2] 56 | await truffleAssert.reverts( 57 | operator.send(erc777Instance.address, accounts[1], accounts[2], amount, { 58 | from: accounts[3] 59 | }), 'not allowed to send'); 60 | 61 | await operator.setAllowance(erc777Instance.address, accounts[3], granularity.mul(web3.utils.toBN('100')), 0, { 62 | from: accounts[1] 63 | }); 64 | }); 65 | 66 | it('does not transfer without an allowance', async function() { 67 | // Set up the contract as an operator for accounts[1] 68 | await erc777Instance.authorizeOperator(operator.address, { 69 | from: accounts[1] 70 | }); 71 | assert.equal(await erc777Instance.isOperatorFor(operator.address, accounts[1]), true); 72 | 73 | const amount = granularity.mul(web3.utils.toBN('5')); 74 | // Attempt to transfer tokens as accounts[3] from accounts[1] to accounts[3] 75 | await truffleAssert.reverts( 76 | operator.send(erc777Instance.address, accounts[1], accounts[2], amount, { 77 | from: accounts[3] 78 | }), 'amount exceeds allowance'); 79 | }); 80 | 81 | it('transfers when set up', async function() { 82 | // Set up an allowance for accounts[3] to withdraw from accounts[1] 83 | await operator.setAllowance(erc777Instance.address, accounts[3], 0, granularity.mul(web3.utils.toBN('100')), { 84 | from: accounts[1] 85 | }); 86 | 87 | // Transfer tokens as accounts[3] from accounts[1] to accounts[2] 88 | const amount = granularity.mul(web3.utils.toBN('5')); 89 | await operator.send(erc777Instance.address, accounts[1], accounts[2], amount, { 90 | from: accounts[3] 91 | }); 92 | tokenBalances[accounts[1]] = tokenBalances[accounts[1]].sub(amount); 93 | tokenBalances[accounts[2]] = tokenBalances[accounts[2]].add(amount); 94 | await asserts.assertTokenBalances(erc777Instance, tokenBalances); 95 | }); 96 | 97 | it('does not transfer more than allowed', async function() { 98 | const amount = granularity.mul(web3.utils.toBN('100')); 99 | // Attempt to transfer tokens as accounts[3] from accounts[1] to accounts[2] 100 | await truffleAssert.reverts( 101 | operator.send(erc777Instance.address, accounts[1], accounts[2], amount, { 102 | from: accounts[3] 103 | }), 'amount exceeds allowance'); 104 | }); 105 | 106 | it('does not work when de-registered', async function() { 107 | await erc777Instance.revokeOperator(operator.address, { 108 | from: accounts[1] 109 | }); 110 | assert.equal(await erc777Instance.isOperatorFor(operator.address, accounts[1]), false); 111 | 112 | const amount = granularity.mul(web3.utils.toBN('5')); 113 | // Attempt to transfer tokens - should fail as deregistered 114 | await truffleAssert.reverts( 115 | operator.send(erc777Instance.address, accounts[1], accounts[2], amount, { 116 | from: accounts[3] 117 | }), 'not allowed to send'); 118 | }); 119 | }); 120 | -------------------------------------------------------------------------------- /migrations/2_erc1820.js: -------------------------------------------------------------------------------- 1 | const erc1820Addr = '0xa990077c3205cbDf861e17Fa532eeB069cE9fF96'; 2 | 3 | module.exports = async function(deployer) { 4 | const accounts = await web3.eth.getAccounts(); 5 | const account = accounts[9]; 6 | 7 | // Need to give the ERC1820 address some funds so that it can deploy the contract 8 | await web3.eth.sendTransaction({from: account, to: erc1820Addr, value: web3.utils.toWei('0.1', 'ether')}); 9 | // Now deploy the contract (if we haven't already) 10 | const nonce = await web3.eth.getTransactionCount(erc1820Addr); 11 | if (nonce == 0) { 12 | await web3.eth.sendSignedTransaction('0xf90a388085174876e800830c35008080b909e5608060405234801561001057600080fd5b506109c5806100206000396000f3fe608060405234801561001057600080fd5b50600436106100a5576000357c010000000000000000000000000000000000000000000000000000000090048063a41e7d5111610078578063a41e7d51146101d4578063aabbb8ca1461020a578063b705676514610236578063f712f3e814610280576100a5565b806329965a1d146100aa5780633d584063146100e25780635df8122f1461012457806365ba36c114610152575b600080fd5b6100e0600480360360608110156100c057600080fd5b50600160a060020a038135811691602081013591604090910135166102b6565b005b610108600480360360208110156100f857600080fd5b5035600160a060020a0316610570565b60408051600160a060020a039092168252519081900360200190f35b6100e06004803603604081101561013a57600080fd5b50600160a060020a03813581169160200135166105bc565b6101c26004803603602081101561016857600080fd5b81019060208101813564010000000081111561018357600080fd5b82018360208201111561019557600080fd5b803590602001918460018302840111640100000000831117156101b757600080fd5b5090925090506106b3565b60408051918252519081900360200190f35b6100e0600480360360408110156101ea57600080fd5b508035600160a060020a03169060200135600160e060020a0319166106ee565b6101086004803603604081101561022057600080fd5b50600160a060020a038135169060200135610778565b61026c6004803603604081101561024c57600080fd5b508035600160a060020a03169060200135600160e060020a0319166107ef565b604080519115158252519081900360200190f35b61026c6004803603604081101561029657600080fd5b508035600160a060020a03169060200135600160e060020a0319166108aa565b6000600160a060020a038416156102cd57836102cf565b335b9050336102db82610570565b600160a060020a031614610339576040805160e560020a62461bcd02815260206004820152600f60248201527f4e6f7420746865206d616e616765720000000000000000000000000000000000604482015290519081900360640190fd5b6103428361092a565b15610397576040805160e560020a62461bcd02815260206004820152601a60248201527f4d757374206e6f7420626520616e204552433136352068617368000000000000604482015290519081900360640190fd5b600160a060020a038216158015906103b85750600160a060020a0382163314155b156104ff5760405160200180807f455243313832305f4143434550545f4d4147494300000000000000000000000081525060140190506040516020818303038152906040528051906020012082600160a060020a031663249cb3fa85846040518363ffffffff167c01000000000000000000000000000000000000000000000000000000000281526004018083815260200182600160a060020a0316600160a060020a031681526020019250505060206040518083038186803b15801561047e57600080fd5b505afa158015610492573d6000803e3d6000fd5b505050506040513d60208110156104a857600080fd5b5051146104ff576040805160e560020a62461bcd02815260206004820181905260248201527f446f6573206e6f7420696d706c656d656e742074686520696e74657266616365604482015290519081900360640190fd5b600160a060020a03818116600081815260208181526040808320888452909152808220805473ffffffffffffffffffffffffffffffffffffffff19169487169485179055518692917f93baa6efbd2244243bfee6ce4cfdd1d04fc4c0e9a786abd3a41313bd352db15391a450505050565b600160a060020a03818116600090815260016020526040812054909116151561059a5750806105b7565b50600160a060020a03808216600090815260016020526040902054165b919050565b336105c683610570565b600160a060020a031614610624576040805160e560020a62461bcd02815260206004820152600f60248201527f4e6f7420746865206d616e616765720000000000000000000000000000000000604482015290519081900360640190fd5b81600160a060020a031681600160a060020a0316146106435780610646565b60005b600160a060020a03838116600081815260016020526040808220805473ffffffffffffffffffffffffffffffffffffffff19169585169590951790945592519184169290917f605c2dbf762e5f7d60a546d42e7205dcb1b011ebc62a61736a57c9089d3a43509190a35050565b600082826040516020018083838082843780830192505050925050506040516020818303038152906040528051906020012090505b92915050565b6106f882826107ef565b610703576000610705565b815b600160a060020a03928316600081815260208181526040808320600160e060020a031996909616808452958252808320805473ffffffffffffffffffffffffffffffffffffffff19169590971694909417909555908152600284528181209281529190925220805460ff19166001179055565b600080600160a060020a038416156107905783610792565b335b905061079d8361092a565b156107c357826107ad82826108aa565b6107b85760006107ba565b815b925050506106e8565b600160a060020a0390811660009081526020818152604080832086845290915290205416905092915050565b6000808061081d857f01ffc9a70000000000000000000000000000000000000000000000000000000061094c565b909250905081158061082d575080155b1561083d576000925050506106e8565b61084f85600160e060020a031961094c565b909250905081158061086057508015155b15610870576000925050506106e8565b61087a858561094c565b909250905060018214801561088f5750806001145b1561089f576001925050506106e8565b506000949350505050565b600160a060020a0382166000908152600260209081526040808320600160e060020a03198516845290915281205460ff1615156108f2576108eb83836107ef565b90506106e8565b50600160a060020a03808316600081815260208181526040808320600160e060020a0319871684529091529020549091161492915050565b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff161590565b6040517f01ffc9a7000000000000000000000000000000000000000000000000000000008082526004820183905260009182919060208160248189617530fa90519096909550935050505056fea165627a7a72305820377f4a2d4301ede9949f163f319021a6e9c687c292a5e2b2c4734c126b524e6c00291ba01820182018201820182018201820182018201820182018201820182018201820a01820182018201820182018201820182018201820182018201820182018201820', {from: erc1820Addr}); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /contracts/auth/Authorised.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import './Permissioned.sol'; 4 | 5 | 6 | // Copyright © 2018 Weald Technology Trading Limited 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | 19 | /** 20 | * @title Authorised 21 | * Authorisation is a mechanism to allow arbitrary operations to take 22 | * place within a smart contract by providing a signature that can be 23 | * recognised as authorising the requested action. 24 | * 25 | * Actions are described by a contract-specific n-tuple. At a minimum 26 | * the action should include the contract, address and action identifier 27 | * which it is authorising. If the authorisation is specific to a sender 28 | * then their address should be present as well. Any other data relevant 29 | * to the action should also be included. 30 | * 31 | * The action is hashed and signed by an authoriser to produce the 32 | * authorising signature. This signature can be checked using either the 33 | * modifier `ifAuthorised` or the function `authorise`. 34 | * 35 | * Note that actions may or may not be repeatable. An example of a 36 | * repeatable action might be a right-to-use action. An example of an 37 | * unrepeatable action might be a payment action. The authorisation 38 | * checks can be used to enforce single-use or repeatble use as required. 39 | * 40 | * Authorised builds upon `Permissioned`, so any number of address may be 41 | * authorisers. Authorisers use the permission PERM_AUTHORISER, and can 42 | * be set using `setPermission()`; see `Permissioned` for more details. 43 | * 44 | * State of this contract: stable; development complete but the code is 45 | * unaudited. and may contain bugs and/or security holes. Use at your own 46 | * risk. 47 | * 48 | * @author Jim McDonald 49 | * @notice If you use this contract please consider donating some Ether or some 50 | * of your token to wsl.wealdtech.eth to support continued development 51 | * of these and future contracts 52 | */ 53 | contract Authorised is Permissioned { 54 | // Record of one-off hashes that have been authorised 55 | mapping(bytes32=>bool) private usedHashes; 56 | 57 | // Permissions 58 | bytes32 public constant PERM_AUTHORISER = keccak256("authorised: authoriser"); 59 | 60 | /** 61 | * @dev A modifier that requires the sender to have the presented authorisation. 62 | * @param _reusable By default hashes can only be used once. if this is true 63 | * then it can be reused 64 | */ 65 | modifier ifAuthorised(bytes32 _actionHash, bytes memory _signature, bool _reusable) { 66 | require(authorise(_actionHash, _signature, _reusable)); 67 | _; 68 | } 69 | 70 | /** 71 | * @dev Authorise an action. 72 | * If a non-reusable action is authorised it is consumed. 73 | * @return true if the action is authorised. 74 | */ 75 | function authorise(bytes32 _actionHash, bytes memory _signature, bool _reusable) internal returns (bool) { 76 | if (!_reusable) { 77 | if (usedHashes[_actionHash] == true) { 78 | // Cannot re-use 79 | return false; 80 | } 81 | } 82 | address signer = obtainSigner(_actionHash, _signature); 83 | if (signer == address(0)) { 84 | return false; 85 | } 86 | 87 | bool permitted = isPermitted(signer, PERM_AUTHORISER); 88 | if (!_reusable) { 89 | if (permitted) { 90 | usedHashes[_actionHash] = true; 91 | } 92 | } 93 | return permitted; 94 | } 95 | 96 | /** 97 | * @dev Check if a signature can authorise the hash of an action, without 98 | * consuming the authorisation. 99 | */ 100 | function checkAuthorisation(bytes32 _actionHash, bytes memory _signature) public view returns (bool) { 101 | if (usedHashes[_actionHash] == true) { 102 | return false; 103 | } 104 | address signer = obtainSigner(_actionHash, _signature); 105 | if (signer == address(0)) { 106 | return false; 107 | } 108 | return isPermitted(signer, PERM_AUTHORISER); 109 | } 110 | 111 | /** 112 | * @dev Obtain the signer address from a signature. 113 | * Note that it is possible for the signer to be returned as 0x00 114 | */ 115 | function obtainSigner(bytes32 _actionHash, bytes memory _signature) private pure returns (address) { 116 | bytes32 r; 117 | bytes32 s; 118 | uint8 v; 119 | 120 | if (_signature.length != 65) { 121 | return address(0); 122 | } 123 | 124 | assembly { 125 | r := mload(add(_signature, 32)) 126 | s := mload(add(_signature, 64)) 127 | v := and(mload(add(_signature, 65)), 255) 128 | } 129 | 130 | if (v < 27) { 131 | v += 27; 132 | } 133 | 134 | if (v != 27 && v != 28) { 135 | return address(0); 136 | } 137 | 138 | bytes memory prefix = "\x19Ethereum Signed Message:\n32"; 139 | bytes32 prefixedHash = keccak256(abi.encodePacked(prefix, _actionHash)); 140 | return ecrecover(prefixedHash, v, r, s); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /test/tokenssender/SupplementWitholdingAccount.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const asserts = require('../helpers/asserts.js'); 4 | const erc1820 = require('../helpers/erc1820.js'); 5 | const truffleAssert = require('truffle-assertions'); 6 | 7 | const ERC777Token = artifacts.require('ERC777Token'); 8 | const SupplementWitholdingAccount = artifacts.require('SupplementWitholdingAccount'); 9 | 10 | contract('SupplementWitholdingAccount', accounts => { 11 | var erc777Instance; 12 | var erc1820Instance; 13 | var instance; 14 | 15 | const granularity = web3.utils.toBN('10000000000000000'); 16 | const initialSupply = granularity.mul(web3.utils.toBN('10000000')); 17 | 18 | let tokenBalances = {}; 19 | tokenBalances[accounts[0]] = web3.utils.toBN(0); 20 | tokenBalances[accounts[1]] = web3.utils.toBN(0); 21 | tokenBalances[accounts[2]] = web3.utils.toBN(0); 22 | tokenBalances[accounts[3]] = web3.utils.toBN(0); 23 | 24 | it('sets up', async function() { 25 | erc1820Instance = await erc1820.instance(); 26 | erc777Instance = await ERC777Token.new(1, 'Test token', 'TST', granularity, initialSupply, [], '0x0000000000000000000000000000000000000000', { 27 | from: accounts[0], 28 | gas: 10000000 29 | }); 30 | await erc777Instance.activate({ 31 | from: accounts[0] 32 | }); 33 | tokenBalances[accounts[0]] = tokenBalances[accounts[0]].add(initialSupply); 34 | await asserts.assertTokenBalances(erc777Instance, tokenBalances); 35 | 36 | // accounts[1] is our test source address so send it some tokens 37 | const amount = granularity.mul(web3.utils.toBN('200')); 38 | await erc777Instance.send(accounts[1], amount, [], { 39 | from: accounts[0] 40 | }); 41 | tokenBalances[accounts[0]] = tokenBalances[accounts[0]].sub(amount); 42 | tokenBalances[accounts[1]] = tokenBalances[accounts[1]].add(amount); 43 | await asserts.assertTokenBalances(erc777Instance, tokenBalances); 44 | }); 45 | 46 | it('creates the sender contract', async function() { 47 | instance = await SupplementWitholdingAccount.new({ 48 | from: accounts[0] 49 | }); 50 | }); 51 | 52 | it('supplements tokens accordingly', async function() { 53 | // Register the sender 54 | await erc1820Instance.setInterfaceImplementer(accounts[1], web3.utils.soliditySha3('ERC777TokensSender'), instance.address, { 55 | from: accounts[1] 56 | }); 57 | 58 | // Set up 15% witholding from accounts[1] to accounts[3] 59 | await instance.setSupplement(accounts[3], 1500, { 60 | from: accounts[1] 61 | }); 62 | 63 | // Set up the sender contract as an operator for accounts[1] 64 | await erc777Instance.authorizeOperator(instance.address, { 65 | from: accounts[1] 66 | }); 67 | 68 | // Transfer 100*granularity tokens from accounts[1] to accounts[2] 69 | const amount = granularity.mul(web3.utils.toBN('100')); 70 | await erc777Instance.send(accounts[2], amount, [], { 71 | from: accounts[1] 72 | }); 73 | tokenBalances[accounts[1]] = tokenBalances[accounts[1]].sub(amount); 74 | tokenBalances[accounts[2]] = tokenBalances[accounts[2]].add(amount); 75 | const witholdingAmount =granularity.mul(web3.utils.toBN('15')); 76 | tokenBalances[accounts[1]] = tokenBalances[accounts[1]].sub(witholdingAmount); 77 | tokenBalances[accounts[3]] = tokenBalances[accounts[3]].add(witholdingAmount); 78 | await asserts.assertTokenBalances(erc777Instance, tokenBalances); 79 | 80 | // Unregister the operator 81 | await erc777Instance.revokeOperator(instance.address, { 82 | from: accounts[1] 83 | }); 84 | 85 | // Unregister the sender 86 | await erc1820Instance.setInterfaceImplementer(accounts[1], web3.utils.soliditySha3('ERC777TokensSender'), '0x0000000000000000000000000000000000000000', { 87 | from: accounts[1] 88 | }); 89 | }); 90 | 91 | it('supplements odd-granularity values accordingly', async function() { 92 | // Register the sender 93 | await erc1820Instance.setInterfaceImplementer(accounts[1], web3.utils.soliditySha3('ERC777TokensSender'), instance.address, { 94 | from: accounts[1] 95 | }); 96 | 97 | // Set up 15% witholding from accounts[1] to accounts[3] 98 | await instance.setSupplement(accounts[3], 1500, { 99 | from: accounts[1] 100 | }); 101 | 102 | // Set up the sender contract as an operator for accounts[1] 103 | await erc777Instance.authorizeOperator(instance.address, { 104 | from: accounts[1] 105 | }); 106 | 107 | // Transfer 10*granularity tokens from accounts[1] to accounts[2] 108 | const amount = granularity.mul(web3.utils.toBN('10')); 109 | await erc777Instance.send(accounts[2], amount, [], { 110 | from: accounts[1] 111 | }); 112 | tokenBalances[accounts[1]] = tokenBalances[accounts[1]].sub(amount); 113 | tokenBalances[accounts[2]] = tokenBalances[accounts[2]].add(amount); 114 | // Witholding should round up the 1.5 to 2 for granularity... 115 | const witholdingAmount =granularity.mul(web3.utils.toBN('2')); 116 | tokenBalances[accounts[1]] = tokenBalances[accounts[1]].sub(witholdingAmount); 117 | tokenBalances[accounts[3]] = tokenBalances[accounts[3]].add(witholdingAmount); 118 | await asserts.assertTokenBalances(erc777Instance, tokenBalances); 119 | 120 | // Unregister the operator 121 | await erc777Instance.revokeOperator(instance.address, { 122 | from: accounts[1] 123 | }); 124 | 125 | // Unregister the sender 126 | await erc1820Instance.setInterfaceImplementer(accounts[1], web3.utils.soliditySha3('ERC777TokensSender'), '0x0000000000000000000000000000000000000000', { 127 | from: accounts[1] 128 | }); 129 | }); 130 | }); 131 | -------------------------------------------------------------------------------- /contracts/test/MockEnsRegistrar.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | 4 | contract AbstractENS { 5 | function setSubnodeOwner(bytes32 node, bytes32 hash, address owner) public; 6 | function setOwner(bytes32 node, address owner) public; 7 | function setResolver(bytes32 node, address resolver) public; 8 | function owner(bytes32 node) public returns (address); 9 | } 10 | 11 | 12 | contract Deed { 13 | address public registrar; 14 | address payable constant BURN = address(0xdead); 15 | uint public creationDate; 16 | address payable public owner; 17 | address public previousOwner; 18 | uint public value; 19 | event OwnerChanged(address newOwner); 20 | event DeedClosed(); 21 | bool active; 22 | 23 | modifier onlyRegistrar { 24 | require(msg.sender == registrar); 25 | _; 26 | } 27 | 28 | modifier onlyActive { 29 | require(active); 30 | _; 31 | } 32 | 33 | constructor(address payable _owner) public payable { 34 | owner = _owner; 35 | registrar = msg.sender; 36 | creationDate = now; 37 | active = true; 38 | value = msg.value; 39 | } 40 | 41 | function setOwner(address payable newOwner) public onlyRegistrar { 42 | require(newOwner != address(0)); 43 | previousOwner = owner; // This allows contracts to check who sent them the ownership 44 | owner = newOwner; 45 | emit OwnerChanged(newOwner); 46 | } 47 | 48 | function setRegistrar(address newRegistrar) public onlyRegistrar { 49 | registrar = newRegistrar; 50 | } 51 | 52 | function setBalance(uint newValue, bool throwOnFailure) public onlyRegistrar onlyActive { 53 | // Check if it has enough balance to set the value 54 | require(value >= newValue); 55 | value = newValue; 56 | // Send the difference to the owner 57 | if (!owner.send(address(this).balance - newValue) && throwOnFailure) { 58 | revert(); 59 | } 60 | } 61 | 62 | /** 63 | * @dev Close a deed and refund a specified fraction of the bid value 64 | * 65 | * @param refundRatio The amount*1/1000 to refund 66 | */ 67 | function closeDeed(uint refundRatio) public onlyRegistrar onlyActive { 68 | active = false; 69 | assert(BURN.send(((1000 - refundRatio) * address(this).balance)/1000)); 70 | emit DeedClosed(); 71 | destroyDeed(); 72 | } 73 | 74 | /** 75 | * @dev Close a deed and refund a specified fraction of the bid value 76 | */ 77 | function destroyDeed() public { 78 | require(!active); 79 | 80 | // Instead of selfdestruct(owner), invoke owner fallback function to allow 81 | // owner to log an event if desired; but owner should also be aware that 82 | // its fallback function can also be invoked by setBalance 83 | if (owner.send(address(this).balance)) { 84 | selfdestruct(BURN); 85 | } 86 | } 87 | } 88 | 89 | 90 | // A mock ENS registrar that acts as FIFS but contains deeds 91 | contract MockEnsRegistrar { 92 | AbstractENS public ens; 93 | bytes32 public rootNode; 94 | 95 | mapping (bytes32 => Entry) _entries; 96 | 97 | enum Mode { Open, Auction, Owned, Forbidden, Reveal, NotYetAvailable } 98 | 99 | struct Entry { 100 | Deed deed; 101 | uint registrationDate; 102 | uint value; 103 | uint highestBid; 104 | } 105 | 106 | // Payable for easy funding 107 | constructor(AbstractENS _ens, bytes32 _rootNode) public payable { 108 | ens = _ens; 109 | rootNode = _rootNode; 110 | } 111 | 112 | modifier onlyOwner(bytes32 hash) { 113 | require(msg.sender == _entries[hash].deed.owner()); 114 | _; 115 | } 116 | 117 | modifier onlyUnregistered(bytes32 hash) { 118 | require(_entries[hash].deed == Deed(0)); 119 | _; 120 | } 121 | 122 | function register(bytes32 hash) public payable onlyUnregistered(hash) { 123 | _entries[hash].deed = (new Deed).value(msg.value)(msg.sender); 124 | _entries[hash].registrationDate = now; 125 | _entries[hash].value = msg.value; 126 | _entries[hash].highestBid = msg.value; 127 | ens.setSubnodeOwner(rootNode, hash, msg.sender); 128 | } 129 | 130 | function state(bytes32 hash) public view returns (Mode) { 131 | if (_entries[hash].registrationDate > 0) { 132 | return Mode.Owned; 133 | } else { 134 | return Mode.Open; 135 | } 136 | } 137 | 138 | function entries(bytes32 hash) public view returns (Mode, address, uint, uint, uint) { 139 | Entry storage h = _entries[hash]; 140 | return (state(hash), address(h.deed), h.registrationDate, h.value, h.highestBid); 141 | } 142 | 143 | function deed(bytes32 hash) public view returns (address) { 144 | return address(_entries[hash].deed); 145 | } 146 | 147 | function transfer(bytes32 hash, address payable newOwner) onlyOwner(hash) public { 148 | require(newOwner != address(0)); 149 | 150 | _entries[hash].deed.setOwner(newOwner); 151 | ens.setSubnodeOwner(rootNode, hash, newOwner); 152 | } 153 | 154 | // This allows anyone to invalidate any entry. It's purely for testing 155 | // purposes and should never be seen in a live contract. 156 | function invalidate(bytes32 hash) public { 157 | Entry storage h = _entries[hash]; 158 | _tryEraseSingleNode(hash); 159 | _entries[hash].deed.closeDeed(0); 160 | h.value = 0; 161 | h.highestBid = 0; 162 | h.deed = Deed(0); 163 | } 164 | 165 | function _tryEraseSingleNode(bytes32 label) internal { 166 | if (ens.owner(rootNode) == address(this)) { 167 | ens.setSubnodeOwner(rootNode, label, address(this)); 168 | bytes32 node = keccak256(abi.encodePacked(rootNode, label)); 169 | ens.setResolver(node, address(0)); 170 | ens.setOwner(node, address(0)); 171 | } 172 | } 173 | } 174 | --------------------------------------------------------------------------------