├── app ├── images │ └── .gitkeep ├── index.html ├── dapp.css ├── components │ ├── TestStatusNetwork.js │ └── erc20token.js └── dapp.js ├── .gitattributes ├── config ├── pipeline.js ├── webserver.js ├── contracts.js ├── communication.js ├── namesystem.js ├── blockchain.js └── storage.js ├── contracts ├── token │ ├── ApproveAndCallFallBack.sol │ ├── TestToken.sol │ ├── TokenGasRelay.sol │ ├── TokenController.sol │ ├── MiniMeTokenFactory.sol │ ├── ERC20Token.sol │ ├── StandardToken.sol │ ├── ERC20Receiver.sol │ └── MiniMeToken.sol ├── account │ ├── ERC725.sol │ ├── SingletonFactory.sol │ ├── Signer.sol │ ├── UserAccountFactory.sol │ ├── MinimalAccount.sol │ ├── Actor.sol │ ├── Caller.sol │ ├── Creator.sol │ ├── PaymentNetworkActor.sol │ ├── ERC20Caller.sol │ ├── ERC2429.sol │ ├── AccountGasAbstract.sol │ ├── UserAccountInterface.sol │ ├── MultisigAccount.sol │ ├── ActorsAllowed.sol │ ├── Identity.sol │ ├── Account.sol │ ├── UserAccount.sol │ └── MultisigRecovery.sol ├── ens │ ├── ResolverInterface.sol │ └── ENS.sol ├── status │ ├── StatusNetwork.sol │ ├── TestStatusNetwork.sol │ └── SNTController.sol ├── cryptography │ ├── MerkleMultiProofWrapper.sol │ ├── MerkleProof.sol │ ├── MerkleVerifier.sol │ ├── MerkleMultiProof.sol │ └── ECDSA.sol ├── common │ ├── Controlled.sol │ ├── TokenClaimer.sol │ ├── Owned.sol │ ├── SafeMath.sol │ └── BytesLib.sol └── gasrelay │ └── GasRelay.sol ├── embark.json ├── .gitignore ├── package.json ├── README.md ├── test ├── abstract │ ├── controlled.js │ └── erc20tokenspec.js ├── teststatusnetwork.js ├── secretmultisigrecovery.spec.js ├── minimetoken.js └── multimerkleproof.spec.js ├── utils ├── secretMultisig.js ├── merkleTree.js └── testUtils.js └── LICENSE /app/images/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity -------------------------------------------------------------------------------- /config/pipeline.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | typescript: false, 3 | enabled: true 4 | }; -------------------------------------------------------------------------------- /config/webserver.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | enabled: false, 3 | host: "localhost", 4 | openBrowser: false, 5 | port: 8000 6 | }; -------------------------------------------------------------------------------- /contracts/token/ApproveAndCallFallBack.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.5.0 <0.7.0; 2 | 3 | contract ApproveAndCallFallBack { 4 | function receiveApproval(address from, uint256 _amount, address _token, bytes memory _data) public; 5 | } 6 | -------------------------------------------------------------------------------- /config/contracts.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | default: { 3 | dappConnection: [ 4 | "$EMBARK", 5 | "$WEB3", 6 | "ws://localhost:8546", 7 | "http://localhost:8545" 8 | ], 9 | gas: "auto", 10 | deploy: { 11 | 12 | } 13 | }, 14 | development: {}, 15 | privatenet: {}, 16 | }; 17 | -------------------------------------------------------------------------------- /config/communication.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | default: { 3 | enabled: true, 4 | provider: "whisper", 5 | available_providers: ["whisper"], 6 | }, 7 | 8 | development: { 9 | connection: { 10 | host: "localhost", 11 | port: 8546, 12 | type: "ws" 13 | } 14 | }, 15 | testnet: { 16 | }, 17 | livenet: { 18 | }, 19 | rinkeby: { 20 | } 21 | 22 | }; 23 | -------------------------------------------------------------------------------- /contracts/account/ERC725.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.5.0 <0.7.0; 2 | 3 | interface ERC725 { 4 | event DataChanged(bytes32 indexed key, bytes value); 5 | 6 | function execute(bytes calldata _execData) external returns (bool success, bytes memory returndata); 7 | function getData(bytes32 _key) external view returns (bytes memory _value); 8 | function setData(bytes32 _key, bytes calldata _value) external; 9 | } -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Status Network - Test Demo 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /config/namesystem.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | default: { 3 | enabled: true, 4 | available_providers: ["ens"], 5 | provider: "ens" 6 | }, 7 | development: { 8 | register: { 9 | rootDomain: "eth", 10 | subdomains: { 11 | 'embark': '0x1a2f3b98e434c02363f3dac3174af93c1d690914' 12 | } 13 | } 14 | }, 15 | testnet: { 16 | }, 17 | 18 | livenet: { 19 | }, 20 | 21 | rinkeby: { 22 | } 23 | }; -------------------------------------------------------------------------------- /contracts/ens/ResolverInterface.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.5.0 <0.7.0; 2 | 3 | contract ResolverInterface { 4 | function PublicResolver(address ensAddr) public; 5 | function setAddr(bytes32 node, address addr) public; 6 | function setHash(bytes32 node, bytes32 hash) public; 7 | function addr(bytes32 node) public view returns (address); 8 | function hash(bytes32 node) public view returns (bytes32); 9 | function supportsInterface(bytes4 interfaceID) public pure returns (bool); 10 | } 11 | -------------------------------------------------------------------------------- /contracts/token/TestToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.5.0 <0.7.0; 2 | 3 | import "./StandardToken.sol"; 4 | 5 | /** 6 | * @notice ERC20Token for test scripts, can be minted by anyone. 7 | */ 8 | contract TestToken is StandardToken { 9 | 10 | constructor() public { } 11 | 12 | /** 13 | * @notice any caller can mint any `_amount` 14 | * @param _amount how much to be minted 15 | */ 16 | function mint(uint256 _amount) public { 17 | mint(msg.sender, _amount); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /contracts/token/TokenGasRelay.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.5.0 <0.7.0; 2 | 3 | import "./StandardToken.sol"; 4 | 5 | /** 6 | * @notice ERC20Token for test scripts, can be minted by anyone. 7 | */ 8 | contract TokenGasRelay is StandardToken { 9 | 10 | constructor() public { } 11 | 12 | /** 13 | * @notice any caller can mint any `_amount` 14 | * @param _amount how much to be minted 15 | */ 16 | function mint(uint256 _amount) public { 17 | mint(msg.sender, _amount); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /contracts/status/StatusNetwork.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.5.0 <0.7.0; 2 | 3 | import "./SNTController.sol"; 4 | 5 | /** 6 | * @dev Status Network is implemented here 7 | */ 8 | contract StatusNetwork is SNTController { 9 | 10 | /** 11 | * @notice Constructor 12 | * @param _owner Authority address 13 | * @param _snt SNT token 14 | */ 15 | constructor( 16 | address payable _owner, 17 | MiniMeToken _snt 18 | ) 19 | public 20 | SNTController(_owner, _snt) 21 | { } 22 | 23 | } -------------------------------------------------------------------------------- /config/blockchain.js: -------------------------------------------------------------------------------- 1 | // This file contains only the basic configuration you need to run Embark's node 2 | // For additional configurations, see: https://embark.status.im/docs/blockchain_configuration.html 3 | module.exports = { 4 | // default applies to all environments 5 | default: { 6 | enabled: true, 7 | client: "geth" // Can be geth or parity (default:geth) 8 | }, 9 | 10 | development: { 11 | clientConfig: { 12 | miningMode: 'dev' // Mode in which the node mines. Options: dev, auto, always, off 13 | } 14 | } 15 | 16 | }; 17 | -------------------------------------------------------------------------------- /embark.json: -------------------------------------------------------------------------------- 1 | { 2 | "contracts": ["contracts/**"], 3 | "app": { 4 | "js/dapp.js": ["app/dapp.js"], 5 | "index.html": "app/index.html", 6 | "images/": ["app/images/**"], 7 | "css/bootstrap.min.css": "node_modules/bootstrap/dist/css/bootstrap.min.css" 8 | }, 9 | "buildDir": "dist/", 10 | "config": "config/", 11 | "versions": { 12 | "solc": "0.5.8" 13 | }, 14 | "plugins": { 15 | }, 16 | "options": { 17 | "solc": { 18 | "optimize": false, 19 | "optimize-runs": 200 20 | } 21 | }, 22 | "generationDir": "embarkArtifacts" 23 | } 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # embark 7 | .embark 8 | chains.json 9 | .password 10 | flattenedContracts 11 | embarkArtifacts 12 | config/livenet/password 13 | config/production/password 14 | dist 15 | embarkArtifacts 16 | 17 | 18 | # egg-related 19 | viper.egg-info/ 20 | build/ 21 | dist/ 22 | .eggs/ 23 | 24 | # pyenv 25 | .python-version 26 | 27 | # dotenv 28 | .env 29 | 30 | # virtualenv 31 | .venv/ 32 | venv/ 33 | ENV/ 34 | 35 | # Coverage tests 36 | .coverage 37 | .cache/ 38 | coverage/ 39 | coverageEnv/ 40 | coverage.json 41 | 42 | # node 43 | node_modules/ 44 | npm-debug.log 45 | package-lock.json 46 | 47 | # other 48 | .vs/ 49 | bin/ 50 | .trash/ -------------------------------------------------------------------------------- /config/storage.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | default: { 3 | enabled: true, 4 | ipfs_bin: "ipfs", 5 | available_providers: ["ipfs"], 6 | upload: { 7 | provider: "ipfs", 8 | host: "localhost", 9 | port: 5001 10 | }, 11 | dappConnection: [ 12 | { 13 | provider:"ipfs", 14 | host: "localhost", 15 | port: 5001, 16 | getUrl: "http://localhost:8080/ipfs/" 17 | } 18 | ] 19 | }, 20 | development: { 21 | enabled: true, 22 | upload: { 23 | provider: "ipfs", 24 | host: "localhost", 25 | port: 5001, 26 | getUrl: "http://localhost:8080/ipfs/" 27 | } 28 | }, 29 | 30 | testnet: { 31 | }, 32 | 33 | livenet: { 34 | }, 35 | 36 | rinkeby: { 37 | } 38 | }; -------------------------------------------------------------------------------- /contracts/account/SingletonFactory.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.5.0 <0.7.0; 2 | 3 | /** 4 | * @author Ricardo Guilherme Schmidt (Status Research & Development GmbH) 5 | * @notice Singleton Factory (ERC-2470) deploys deterministic addresses based on it's initialization code. 6 | */ 7 | contract SingletonFactory { 8 | /** 9 | * @notice Deploys a deterministic address based on `_initCode`. 10 | * @param _initCode Initialization code. 11 | * @return Created contract address. 12 | */ 13 | function deploy(bytes memory _initCode) 14 | public 15 | returns (address payable createdContract) 16 | { 17 | assembly { 18 | createdContract := create2(0, add(_initCode, 0x20), mload(_initCode), 0) 19 | } 20 | } 21 | // IV is value needed to have a vanity address starting with '0x2470'. 22 | // IV: 0 23 | } -------------------------------------------------------------------------------- /contracts/account/Signer.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.5.0 <0.7.0; 2 | 3 | /** 4 | * @notice ERC-1271: Standard Signature Validation Method for Contracts 5 | */ 6 | contract Signer { 7 | 8 | //bytes4(keccak256("isValidSignature(bytes,bytes)") 9 | bytes4 constant internal MAGICVALUE = 0x20c13b0b; 10 | 11 | /** 12 | * @dev Should return whether the signature provided is valid for the provided data 13 | * @param _data Arbitrary length data signed on the behalf of address(this) 14 | * @param _signature Signature byte array associated with _data 15 | * 16 | * MUST return the bytes4 magic value 0x20c13b0b when function passes. 17 | * MUST NOT modify state (using STATICCALL for solc < 0.5, view modifier for solc > 0.5) 18 | * MUST allow external calls 19 | */ 20 | function isValidSignature( 21 | bytes memory _data, 22 | bytes memory _signature 23 | ) 24 | public 25 | view 26 | returns (bytes4 magicValue); 27 | } -------------------------------------------------------------------------------- /contracts/cryptography/MerkleMultiProofWrapper.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.5.0 <0.7.0; 2 | import "../cryptography/MerkleMultiProof.sol"; 3 | 4 | contract MerkleMultiProofWrapper { 5 | 6 | function calculateMultiMerkleRoot( 7 | bytes32[] memory leafs, 8 | bytes32[] memory proofs, 9 | bool[] memory useProof 10 | ) 11 | public 12 | pure 13 | returns (bytes32) 14 | { 15 | return MerkleMultiProof.calculateMultiMerkleRoot(leafs, proofs, useProof); 16 | } 17 | 18 | function verifyMultiProof( 19 | bytes32 root, 20 | bytes32[] memory leafs, 21 | bytes32[] memory proofs, 22 | bool[] memory useProof 23 | ) 24 | public 25 | pure 26 | returns (bool) 27 | { 28 | return MerkleMultiProof.verifyMultiProof(root, leafs, proofs, useProof); 29 | } 30 | 31 | 32 | function foo() 33 | external 34 | pure 35 | { 36 | 37 | } 38 | 39 | } 40 | 41 | 42 | -------------------------------------------------------------------------------- /contracts/common/Controlled.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.5.0 <0.7.0; 2 | 3 | contract Controlled { 4 | string internal constant ERR_BAD_PARAMETER = "Bad parameter"; 5 | string internal constant ERR_UNAUTHORIZED = "Unauthorized"; 6 | event NewController(address controller); 7 | /// @notice The address of the controller is the only address that can call 8 | /// a function with this modifier 9 | modifier onlyController { 10 | require(msg.sender == controller, "Unauthorized"); 11 | _; 12 | } 13 | 14 | address payable public controller; 15 | 16 | constructor(address payable _initController) internal { 17 | require(_initController != address(0), ERR_BAD_PARAMETER); 18 | controller = _initController; 19 | } 20 | 21 | /// @notice Changes the controller of the contract 22 | /// @param _newController The new controller of the contract 23 | function changeController(address payable _newController) public onlyController { 24 | controller = _newController; 25 | emit NewController(_newController); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "account-contracts", 3 | "version": "0.0.1", 4 | "description": "", 5 | "scripts": { 6 | "start": "npx embark run", 7 | "ci": "npm run qa", 8 | "clean": "npm run reset", 9 | "package": "npm pack", 10 | "qa": "npm-run-all test package", 11 | "reset": "npx embark-reset && npx rimraf embark-*.tgz package", 12 | "test": "npx embark test" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/status-im/account-contracts.git" 17 | }, 18 | "author": "Status Research & Development GMBH", 19 | "license": "CC0-1.0", 20 | "bugs": { 21 | "url": "https://github.com/status-im/account-contracts/issues" 22 | }, 23 | "homepage": "https://github.com/status-im/account-contracts#readme", 24 | "devDependencies": { 25 | "embark": "^5.1.2-nightly.0", 26 | "react": "16.7.0", 27 | "react-bootstrap": "0.32.4", 28 | "react-dom": "16.7.0" 29 | }, 30 | "dependencies": { 31 | "bootstrap": "^4.3.1", 32 | "embarkjs": "^5.1.0", 33 | "embarkjs-ens": "^5.1.0", 34 | "embarkjs-web3": "^5.1.0" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/dapp.css: -------------------------------------------------------------------------------- 1 | 2 | div { 3 | margin: 15px; 4 | } 5 | 6 | .logs { 7 | background-color: black; 8 | font-size: 14px; 9 | color: white; 10 | font-weight: bold; 11 | padding: 10px; 12 | border-radius: 8px; 13 | } 14 | 15 | .tab-content { 16 | border-left: 1px solid #ddd; 17 | border-right: 1px solid #ddd; 18 | border-bottom: 1px solid #ddd; 19 | padding: 10px; 20 | margin: 0px; 21 | } 22 | 23 | .nav-tabs { 24 | margin-bottom: 0; 25 | } 26 | 27 | .status-offline { 28 | vertical-align: middle; 29 | margin-left: 5px; 30 | margin-top: 4px; 31 | width: 12px; 32 | height: 12px; 33 | background: red; 34 | -moz-border-radius: 10px; 35 | -webkit-border-radius: 10px; 36 | border-radius: 10px; 37 | } 38 | 39 | .status-online { 40 | vertical-align: middle; 41 | margin-left: 5px; 42 | margin-top: 4px; 43 | width: 12px; 44 | height: 12px; 45 | background: mediumseagreen; 46 | -moz-border-radius: 10px; 47 | -webkit-border-radius: 10px; 48 | border-radius: 10px; 49 | } 50 | 51 | input.form-control { 52 | margin-right: 5px; 53 | } 54 | 55 | .alert-result { 56 | margin-left: 0; 57 | } 58 | -------------------------------------------------------------------------------- /contracts/ens/ENS.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.5.0 <0.7.0; 2 | 3 | interface ENS { 4 | 5 | // Logged when the owner of a node assigns a new owner to a subnode. 6 | event NewOwner(bytes32 indexed node, bytes32 indexed label, address owner); 7 | 8 | // Logged when the owner of a node transfers ownership to a new account. 9 | event Transfer(bytes32 indexed node, address owner); 10 | 11 | // Logged when the resolver for a node changes. 12 | event NewResolver(bytes32 indexed node, address resolver); 13 | 14 | // Logged when the TTL of a node changes 15 | event NewTTL(bytes32 indexed node, uint64 ttl); 16 | 17 | 18 | function setSubnodeOwner(bytes32 node, bytes32 label, address owner) external; 19 | function setResolver(bytes32 node, address resolver) external; 20 | function setOwner(bytes32 node, address owner) external; 21 | function setTTL(bytes32 node, uint64 ttl) external; 22 | function owner(bytes32 node) external view returns (address); 23 | function resolver(bytes32 node) external view returns (address); 24 | function ttl(bytes32 node) external view returns (uint64); 25 | 26 | } 27 | -------------------------------------------------------------------------------- /contracts/common/TokenClaimer.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.5.0 <0.7.0; 2 | 3 | import "../token/ERC20Token.sol"; 4 | 5 | contract TokenClaimer { 6 | event ClaimedTokens(address indexed _token, address indexed _controller, uint256 _amount); 7 | 8 | function claimTokens(address _token) external; 9 | /** 10 | * @notice This method can be used by the controller to extract mistakenly 11 | * sent tokens to this contract. 12 | * @param _token The address of the token contract that you want to recover 13 | * set to 0 in case you want to extract ether. 14 | */ 15 | function withdrawBalance(address _token, address payable _destination) 16 | internal 17 | { 18 | uint256 balance; 19 | if (_token == address(0)) { 20 | balance = address(this).balance; 21 | address(_destination).transfer(balance); 22 | } else { 23 | ERC20Token token = ERC20Token(_token); 24 | balance = token.balanceOf(address(this)); 25 | token.transfer(_destination, balance); 26 | } 27 | emit ClaimedTokens(_token, _destination, balance); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /contracts/account/UserAccountFactory.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.5.0 <0.7.0; 2 | 3 | import "./Creator.sol"; 4 | import "./UserAccount.sol"; 5 | import "../cryptography/ECDSA.sol"; 6 | 7 | contract UserAccountFactory is Creator { 8 | 9 | constructor() public { 10 | 11 | } 12 | 13 | function create(uint256 _salt) external returns(address) { 14 | // return newUserAccount(_salt, msg.sender); 15 | } 16 | 17 | function createSigned(uint256 _salt, bytes calldata _signature) external returns(address) { 18 | // return newUserAccount(_salt, ECDSA.recover(keccak256(abi.encodePacked(address(this), _salt, msg.sender)), _signature)); 19 | } 20 | 21 | function predictUserAddress(uint256 _salt, address _owner) external view returns(address) { 22 | // return _computeContractAddress(abi.encodePacked(type(UserAccount).creationCode, _owner, address(0), address(0)), bytes32(keccak256(abi.encodePacked(_owner, _salt)))); 23 | } 24 | 25 | function newUserAccount(uint256 _salt, address _owner) internal returns(address) { 26 | //return _create2(0, abi.encodePacked(type(UserAccount).creationCode, _owner, address(0), address(0)), bytes32(keccak256(abi.encodePacked(_owner, _salt)))); 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Account contracts 2 | Smart contracts defining self sovereign accounts. 3 | WIP, currently only a PoC in Solidity. 4 | 5 | ## Features 6 | 7 | - Management key: A simple multisig or single address with full control of Identity 8 | - Action keys: List of addresses allowed to execute calls in behalf of Identity, i.e. allowance tool 9 | - ERC725 v2: ID-Owner (manager) can publish data in own profile (e.g. avatar URI, signed claims, etc). 10 | - ERC2429 Secret multisig recovery: A social recovery tool for secretly selecting other addresses and requesting some of them to recover the identity 11 | - ERC20 approve and call: optimizes approve and call operations to avoid race conditions and gas waste. 12 | - Gas abstract: ID-Owner can execute calls paying with gas stored on the Identity or in the ID-Owner Key. Dapps don't have to be prepared to receive these calls. 13 | - Serverless: Uses Whisper for gas market and Status API for Social Recovery. 14 | - ERC1271: Contracts usually can't sign messages. Dapps that require signatures can validate Multisig of ID-Owner by using this standard. 15 | 16 | ### Usage 17 | ``` 18 | git clone https://github.com/status-im/account-contracts.git 19 | cd account-contracts 20 | npm install 21 | npm start 22 | ``` 23 | 24 | -------------------------------------------------------------------------------- /contracts/common/Owned.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.5.0 <0.7.0; 2 | 3 | /// @dev `Owned` is a base level contract that assigns an `owner` that can be 4 | /// later changed 5 | contract Owned { 6 | event OwnerChanged(address newOwner); 7 | 8 | /// @dev `owner` is the only address that can call a function with this 9 | /// modifier 10 | modifier onlyOwner() { 11 | require(msg.sender == owner, "Unauthorized"); 12 | _; 13 | } 14 | 15 | address payable public owner; 16 | 17 | /// @notice The Constructor assigns the message sender to be `owner` 18 | constructor() internal { 19 | owner = msg.sender; 20 | } 21 | 22 | address payable public newOwner; 23 | 24 | /// @notice `owner` can step down and assign some other address to this role 25 | /// @param _newOwner The address of the new owner. 0x0 can be used to create 26 | /// an unowned neutral vault, however that cannot be undone 27 | function changeOwner(address payable _newOwner) public onlyOwner { 28 | emit OwnerChanged(_newOwner); 29 | newOwner = _newOwner; 30 | } 31 | 32 | 33 | function acceptOwnership() public { 34 | if (msg.sender == newOwner) { 35 | owner = newOwner; 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /test/abstract/controlled.js: -------------------------------------------------------------------------------- 1 | 2 | exports.Test = (Controlled) => { 3 | describe("Controlled", async function() { 4 | this.timeout(0); 5 | var accounts; 6 | before(function(done) { 7 | web3.eth.getAccounts().then(function (res) { 8 | accounts = res; 9 | done(); 10 | }); 11 | }); 12 | 13 | 14 | it("should start with msg.sender as controller", async function() { 15 | var controller = await Controlled.methods.controller().call(); 16 | assert(controller, accounts[0]); 17 | }); 18 | 19 | it("should allow controller to set new controller", async function() { 20 | await Controlled.methods.changeController(accounts[1]).send({from: accounts[0]}); 21 | var controller = await Controlled.methods.controller().call(); 22 | assert(controller, accounts[1]); 23 | }); 24 | 25 | it("should set back to original controller", async function() { 26 | await Controlled.methods.changeController(accounts[0]).send({from: accounts[1]}); 27 | var controller = await Controlled.methods.controller().call(); 28 | assert(controller, accounts[0]); 29 | }); 30 | }); 31 | } -------------------------------------------------------------------------------- /contracts/cryptography/MerkleProof.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.5.0 <0.7.0; 2 | 3 | /** 4 | * @dev These functions deal with verification of Merkle trees (hash trees), 5 | */ 6 | library MerkleProof { 7 | /** 8 | * @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree 9 | * defined by `root`. For this, a `proof` must be provided, containing 10 | * sibling hashes on the branch from the leaf to the root of the tree. Each 11 | * pair of leaves and each pair of pre-images are assumed to be sorted. 12 | */ 13 | function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf) internal pure returns (bool) { 14 | bytes32 computedHash = leaf; 15 | 16 | for (uint256 i = 0; i < proof.length; i++) { 17 | bytes32 proofElement = proof[i]; 18 | 19 | if (computedHash < proofElement) { 20 | // Hash(current computed hash + current element of the proof) 21 | computedHash = keccak256(abi.encodePacked(computedHash, proofElement)); 22 | } else { 23 | // Hash(current element of the proof + current computed hash) 24 | computedHash = keccak256(abi.encodePacked(proofElement, computedHash)); 25 | } 26 | } 27 | 28 | // Check if the computed hash (root) is equal to the provided root 29 | return computedHash == root; 30 | } 31 | } 32 | 33 | -------------------------------------------------------------------------------- /contracts/common/SafeMath.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.5.0 <0.7.0; 2 | 3 | /** 4 | * Math operations with safety checks 5 | */ 6 | library SafeMath { 7 | function mul(uint a, uint b) internal pure returns (uint) { 8 | uint c = a * b; 9 | assert(a == 0 || c / a == b); 10 | return c; 11 | } 12 | 13 | function div(uint a, uint b) internal pure returns (uint) { 14 | // assert(b > 0); // Solidity automatically throws when dividing by 0 15 | uint c = a / b; 16 | // assert(a == b * c + a % b); // There is no case in which this doesn't hold 17 | return c; 18 | } 19 | 20 | function sub(uint a, uint b) internal pure returns (uint) { 21 | assert(b <= a); 22 | return a - b; 23 | } 24 | 25 | function add(uint a, uint b) internal pure returns (uint) { 26 | uint c = a + b; 27 | assert(c >= a); 28 | return c; 29 | } 30 | 31 | function max64(uint64 a, uint64 b) internal pure returns (uint64) { 32 | return a >= b ? a : b; 33 | } 34 | 35 | function min64(uint64 a, uint64 b) internal pure returns (uint64) { 36 | return a < b ? a : b; 37 | } 38 | 39 | function max256(uint256 a, uint256 b) internal pure returns (uint256) { 40 | return a >= b ? a : b; 41 | } 42 | 43 | function min256(uint256 a, uint256 b) internal pure returns (uint256) { 44 | return a < b ? a : b; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /contracts/token/TokenController.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.5.0 <0.7.0; 2 | /** 3 | * @dev The token controller contract must implement these functions 4 | */ 5 | interface TokenController { 6 | /** 7 | * @notice Called when `_owner` sends ether to the MiniMe Token contract 8 | * @param _owner The address that sent the ether to create tokens 9 | * @return True if the ether is accepted, false if it throws 10 | */ 11 | function proxyPayment(address _owner) external payable returns(bool); 12 | 13 | /** 14 | * @notice Notifies the controller about a token transfer allowing the 15 | * controller to react if desired 16 | * @param _from The origin of the transfer 17 | * @param _to The destination of the transfer 18 | * @param _amount The amount of the transfer 19 | * @return False if the controller does not authorize the transfer 20 | */ 21 | function onTransfer(address _from, address _to, uint _amount) external returns(bool); 22 | 23 | /** 24 | * @notice Notifies the controller about an approval allowing the 25 | * controller to react if desired 26 | * @param _owner The address that calls `approve()` 27 | * @param _spender The spender in the `approve()` call 28 | * @param _amount The amount in the `approve()` call 29 | * @return False if the controller does not authorize the approval 30 | */ 31 | function onApprove(address _owner, address _spender, uint _amount) external 32 | returns(bool); 33 | } 34 | -------------------------------------------------------------------------------- /app/components/TestStatusNetwork.js: -------------------------------------------------------------------------------- 1 | import EmbarkJS from 'Embark/EmbarkJS'; 2 | import StatusRoot from 'Embark/contracts/StatusRoot'; 3 | import MiniMeToken from 'Embark/contracts/MiniMeToken'; 4 | import React from 'react'; 5 | import { Form, FormGroup, FormControl, HelpBlock, Button } from 'react-bootstrap'; 6 | import ERC20TokenUI from './erc20token'; 7 | 8 | class TestTokenUI extends React.Component { 9 | 10 | constructor(props) { 11 | super(props); 12 | this.state = { 13 | amountToMint: 100, 14 | } 15 | } 16 | 17 | handleMintAmountChange(e){ 18 | this.setState({amountToMint: e.target.value}); 19 | } 20 | 21 | async mint(e){ 22 | e.preventDefault(); 23 | await EmbarkJS.enableEthereum(); 24 | var value = parseInt(this.state.amountToMint, 10); 25 | StatusRoot.methods.mint(value).send({ gas: 1000000 }) 26 | 27 | console.log(StatusRoot.options.address +".mint("+value+").send({from: " + web3.eth.defaultAccount + "})"); 28 | } 29 | 30 | render(){ 31 | return ( 32 |

Test Status Network

33 |
34 | 35 | this.handleMintAmountChange(e)} /> 39 | 40 | 41 |
42 | 43 | 44 | 45 |
46 | ); 47 | } 48 | } 49 | 50 | export default TestTokenUI; 51 | -------------------------------------------------------------------------------- /contracts/status/TestStatusNetwork.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.5.0 <0.7.0; 2 | 3 | import "./StatusNetwork.sol"; 4 | /** 5 | * @title SNTController 6 | * @author Ricardo Guilherme Schmidt (Status Research & Development GmbH) 7 | * @notice Test net version of SNTController which allow public mint 8 | */ 9 | contract TestStatusNetwork is StatusNetwork { 10 | 11 | bool public open = false; 12 | 13 | /** 14 | * @notice Constructor 15 | * @param _owner Authority address 16 | * @param _snt SNT token 17 | */ 18 | constructor(address payable _owner, MiniMeToken _snt) 19 | public 20 | StatusNetwork(_owner, _snt) 21 | { 22 | 23 | } 24 | 25 | function () external { 26 | _generateTokens(msg.sender, 1000 * (10 ** uint(snt.decimals()))); 27 | } 28 | 29 | function mint(uint256 _amount) external { 30 | _generateTokens(msg.sender, _amount); 31 | } 32 | 33 | function generateTokens(address _who, uint _amount) external { 34 | _generateTokens(_who, _amount); 35 | } 36 | 37 | function destroyTokens(address _who, uint _amount) external onlyOwner { 38 | snt.destroyTokens(_who, _amount); 39 | } 40 | 41 | function setOpen(bool _open) external onlyOwner { 42 | open = _open; 43 | } 44 | 45 | function _generateTokens(address _who, uint _amount) private { 46 | require(msg.sender == owner || open, "Test Mint Disabled"); 47 | address statusNetwork = snt.controller(); 48 | if(statusNetwork == address(this)){ 49 | snt.generateTokens(_who, _amount); 50 | } else { 51 | TestStatusNetwork(statusNetwork).generateTokens(_who, _amount); 52 | } 53 | 54 | } 55 | 56 | 57 | } -------------------------------------------------------------------------------- /contracts/token/MiniMeTokenFactory.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.5.0 <0.7.0; 2 | 3 | import "./MiniMeToken.sol"; 4 | 5 | //////////////// 6 | // MiniMeTokenFactory 7 | //////////////// 8 | 9 | /** 10 | * @dev This contract is used to generate clone contracts from a contract. 11 | * In solidity this is the way to create a contract from a contract of the 12 | * same class 13 | */ 14 | contract MiniMeTokenFactory { 15 | 16 | /** 17 | * @notice Update the DApp by creating a new token with new functionalities 18 | * the msg.sender becomes the controller of this clone token 19 | * @param _parentToken Address of the token being cloned 20 | * @param _snapshotBlock Block of the parent token that will 21 | * determine the initial distribution of the clone token 22 | * @param _tokenName Name of the new token 23 | * @param _decimalUnits Number of decimals of the new token 24 | * @param _tokenSymbol Token Symbol for the new token 25 | * @param _transfersEnabled If true, tokens will be able to be transferred 26 | * @return The address of the new token contract 27 | */ 28 | function createCloneToken( 29 | address _parentToken, 30 | uint _snapshotBlock, 31 | string memory _tokenName, 32 | uint8 _decimalUnits, 33 | string memory _tokenSymbol, 34 | bool _transfersEnabled 35 | ) public returns (MiniMeToken) { 36 | MiniMeToken newToken = new MiniMeToken( 37 | address(this), 38 | _parentToken, 39 | _snapshotBlock, 40 | _tokenName, 41 | _decimalUnits, 42 | _tokenSymbol, 43 | _transfersEnabled 44 | ); 45 | 46 | newToken.changeController(msg.sender); 47 | return newToken; 48 | } 49 | } -------------------------------------------------------------------------------- /contracts/account/MinimalAccount.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.5.0 <0.7.0; 2 | 3 | contract MinimalAccount { 4 | event DataChanged(bytes32 indexed key, bytes value); 5 | event OwnerChanged(address indexed ownerAddress); 6 | address public owner; 7 | mapping(bytes32 => bytes) store; 8 | 9 | modifier onlyOwner { 10 | require(msg.sender == address(owner), "403"); 11 | _; 12 | } 13 | 14 | modifier self { 15 | require(msg.sender == address(this), "403"); 16 | _; 17 | } 18 | 19 | constructor() public { 20 | owner = msg.sender; 21 | } 22 | 23 | function execute( 24 | bytes calldata _execData 25 | ) 26 | external 27 | onlyOwner 28 | returns (bool success, bytes memory returndata) 29 | { 30 | (success, returndata) = address(this).call(_execData); 31 | require(success); 32 | } 33 | 34 | function call( 35 | address _to, 36 | bytes calldata _data 37 | ) 38 | external 39 | self 40 | returns (bool success, bytes memory returndata) 41 | { 42 | (success, returndata) = _to.call(_data); 43 | } 44 | 45 | function setData(bytes32 _key, bytes calldata _value) 46 | external 47 | self 48 | { 49 | store[_key] = _value; 50 | emit DataChanged(_key, _value); 51 | } 52 | 53 | function changeOwner(address newOwner) 54 | external 55 | self 56 | { 57 | require(newOwner != address(0), "Bad parameter"); 58 | owner = newOwner; 59 | emit OwnerChanged(newOwner); 60 | } 61 | 62 | function getData(bytes32 _key) 63 | external 64 | view 65 | returns (bytes memory _value) 66 | { 67 | return store[_key]; 68 | } 69 | } -------------------------------------------------------------------------------- /contracts/account/Actor.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.5.0 <0.7.0; 2 | 3 | /** 4 | * @author Ricardo Guilherme Schmidt (Status Research & Development GmbH) 5 | */ 6 | interface Actor { 7 | 8 | /** 9 | * @notice calls another contract 10 | * @param _to destination of call 11 | * @param _value call ether value (in wei) 12 | * @param _data call data 13 | * @return internal transaction status and returned data 14 | */ 15 | function call( 16 | address _to, 17 | uint256 _value, 18 | bytes calldata _data 19 | ) 20 | external 21 | returns(bool success, bytes memory returndata); 22 | 23 | /** 24 | * @notice Approves `_to` spending ERC20 `_baseToken` a total of `_value` and calls `_to` with `_data`. Useful for a better UX on ERC20 token use, and avoid race conditions. 25 | * @param _baseToken ERC20 token being approved to spend 26 | * @param _to Destination of contract accepting this ERC20 token payments through approve 27 | * @param _value amount of ERC20 being approved 28 | * @param _data abi encoded calldata to be executed in `_to` after approval. 29 | * @return internal transaction status and returned data 30 | */ 31 | function approveAndCall( 32 | address _baseToken, 33 | address _to, 34 | uint256 _value, 35 | bytes calldata _data 36 | ) 37 | external 38 | returns(bool success, bytes memory returndata); 39 | 40 | /** 41 | * @notice creates new contract based on input `_code` and transfer `_value` ETH to this instance 42 | * @param _value amount ether in wei to sent to deployed address at its initialization 43 | * @param _code contract code 44 | * @return creation success status and created contract address 45 | */ 46 | function create( 47 | uint256 _value, 48 | bytes calldata _code 49 | ) 50 | external 51 | returns(address createdContract); 52 | 53 | } -------------------------------------------------------------------------------- /test/teststatusnetwork.js: -------------------------------------------------------------------------------- 1 | const Utils = require('../utils/testUtils'); 2 | const MiniMeToken = require('Embark/contracts/MiniMeToken'); 3 | const TestStatusNetwork = require('Embark/contracts/TestStatusNetwork'); 4 | const ERC20TokenSpec = require('./abstract/erc20tokenspec'); 5 | 6 | config({ 7 | contracts: { 8 | "MiniMeTokenFactory": {}, 9 | "MiniMeToken": { 10 | "args":["$MiniMeTokenFactory", "0x0", "0x0", "Status Test Token", 18, "STT", true], 11 | }, 12 | "TestStatusNetwork": { 13 | "deploy": true, 14 | "args": ["0x0", "$MiniMeToken"], 15 | "onDeploy": [ 16 | "await MiniMeToken.methods.changeController(TestStatusNetwork.address).send()", 17 | "await TestStatusNetwork.methods.setOpen(true).send()", 18 | ] 19 | } 20 | } 21 | }); 22 | 23 | contract("TestStatusNetwork", function() { 24 | this.timeout(0); 25 | var accounts; 26 | before(function(done) { 27 | web3.eth.getAccounts().then(function (res) { 28 | accounts = res; 29 | done(); 30 | }); 31 | }); 32 | 33 | it("should increase totalSupply in mint", async function() { 34 | let initialSupply = await MiniMeToken.methods.totalSupply().call(); 35 | await TestStatusNetwork.methods.mint(100).send(); 36 | let result = await MiniMeToken.methods.totalSupply().call(); 37 | assert.equal(result, +initialSupply+100); 38 | }); 39 | 40 | it("should increase accountBalance in mint", async function() { 41 | let initialBalance = await MiniMeToken.methods.balanceOf(accounts[0]).call(); 42 | await TestStatusNetwork.methods.mint(100).send({from: accounts[0]}); 43 | let result = await MiniMeToken.methods.balanceOf(accounts[0]).call(); 44 | assert.equal(result, +initialBalance+100); 45 | }); 46 | 47 | it("should burn account supply", async function() { 48 | let initialBalance = await MiniMeToken.methods.balanceOf(accounts[0]).call(); 49 | await TestStatusNetwork.methods.destroyTokens(accounts[0], initialBalance).send({from: accounts[0]}); 50 | assert.equal(await MiniMeToken.methods.totalSupply().call(), 0); 51 | assert.equal(await MiniMeToken.methods.balanceOf(accounts[0]).call(), 0); 52 | }) 53 | }); 54 | -------------------------------------------------------------------------------- /contracts/token/ERC20Token.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.5.0 <0.7.0; 2 | 3 | // Abstract contract for the full ERC 20 Token standard 4 | // https://github.com/ethereum/EIPs/issues/20 5 | 6 | interface ERC20Token { 7 | 8 | /** 9 | * @notice send `_value` token to `_to` from `msg.sender` 10 | * @param _to The address of the recipient 11 | * @param _value The amount of token to be transferred 12 | * @return Whether the transfer was successful or not 13 | */ 14 | function transfer(address _to, uint256 _value) external returns (bool success); 15 | 16 | /** 17 | * @notice `msg.sender` approves `_spender` to spend `_value` tokens 18 | * @param _spender The address of the account able to transfer the tokens 19 | * @param _value The amount of tokens to be approved for transfer 20 | * @return Whether the approval was successful or not 21 | */ 22 | function approve(address _spender, uint256 _value) external returns (bool success); 23 | 24 | /** 25 | * @notice send `_value` token to `_to` from `_from` on the condition it is approved by `_from` 26 | * @param _from The address of the sender 27 | * @param _to The address of the recipient 28 | * @param _value The amount of token to be transferred 29 | * @return Whether the transfer was successful or not 30 | */ 31 | function transferFrom(address _from, address _to, uint256 _value) external returns (bool success); 32 | 33 | /** 34 | * @param _owner The address from which the balance will be retrieved 35 | * @return The balance 36 | */ 37 | function balanceOf(address _owner) external view returns (uint256 balance); 38 | 39 | /** 40 | * @param _owner The address of the account owning tokens 41 | * @param _spender The address of the account able to transfer the tokens 42 | * @return Amount of remaining tokens allowed to spent 43 | */ 44 | function allowance(address _owner, address _spender) external view returns (uint256 remaining); 45 | 46 | /** 47 | * @notice return total supply of tokens 48 | */ 49 | function totalSupply() external view returns (uint256 supply); 50 | 51 | event Transfer(address indexed _from, address indexed _to, uint256 _value); 52 | event Approval(address indexed _owner, address indexed _spender, uint256 _value); 53 | } -------------------------------------------------------------------------------- /contracts/account/Caller.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.5.0 <0.7.0; 2 | 3 | /** 4 | * @notice wrapper for _call 5 | */ 6 | contract Caller { 7 | 8 | constructor() internal { 9 | 10 | } 11 | 12 | /** 13 | * @notice calls another contract 14 | * @param _to destination of call 15 | * @param _data call data 16 | * @return internal transaction status and returned data 17 | */ 18 | function _call( 19 | address _to, 20 | bytes memory _data 21 | ) 22 | internal 23 | returns (bool success, bytes memory returndata) 24 | { 25 | (success, returndata) = _to.call(_data); 26 | } 27 | 28 | /** 29 | * @notice calls another contract with explicit value 30 | * @param _to destination of call 31 | * @param _value call ether value (in wei) 32 | * @param _data call data 33 | * @return internal transaction status and returned data 34 | */ 35 | function _call( 36 | address _to, 37 | uint256 _value, 38 | bytes memory _data 39 | ) 40 | internal 41 | returns (bool success, bytes memory returndata) 42 | { 43 | (success, returndata) = _to.call.value(_value)(_data); 44 | } 45 | 46 | /** 47 | * @notice calls another contract with explicit value and limited gas 48 | * @param _to destination of call 49 | * @param _value call ether value (in wei) 50 | * @param _data call data 51 | * @param _gas gas to limit the internal transaction 52 | * @return internal transaction status and returned data 53 | */ 54 | function _call( 55 | address _to, 56 | uint256 _value, 57 | bytes memory _data, 58 | uint256 _gas 59 | ) 60 | internal 61 | returns (bool success, bytes memory returndata) 62 | { 63 | (success, returndata) = _to.call.gas(_gas).value(_value)(_data); 64 | } 65 | 66 | /** 67 | * @notice calls another contract with limited gas 68 | * @param _to destination of call 69 | * @param _data call data 70 | * @param _gas gas to limit the internal transaction 71 | * @return internal transaction status and returned data 72 | */ 73 | function _call( 74 | address _to, 75 | bytes memory _data, 76 | uint256 _gas 77 | ) 78 | internal 79 | returns (bool success, bytes memory returndata) 80 | { 81 | (success, returndata) = _to.call.gas(_gas)(_data); 82 | } 83 | 84 | } -------------------------------------------------------------------------------- /contracts/token/StandardToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.5.0 <0.7.0; 2 | 3 | import "./ERC20Token.sol"; 4 | 5 | contract StandardToken is ERC20Token { 6 | 7 | uint256 public totalSupply; 8 | mapping (address => uint256) balances; 9 | mapping (address => mapping (address => uint256)) allowed; 10 | 11 | constructor() internal { } 12 | 13 | function transfer( 14 | address _to, 15 | uint256 _value 16 | ) 17 | external 18 | returns (bool success) 19 | { 20 | return transfer(msg.sender, _to, _value); 21 | } 22 | 23 | function approve(address _spender, uint256 _value) 24 | external 25 | returns (bool success) 26 | { 27 | allowed[msg.sender][_spender] = _value; 28 | emit Approval(msg.sender, _spender, _value); 29 | return true; 30 | } 31 | 32 | function transferFrom( 33 | address _from, 34 | address _to, 35 | uint256 _value 36 | ) 37 | external 38 | returns (bool success) 39 | { 40 | if (balances[_from] >= _value && 41 | allowed[_from][msg.sender] >= _value && 42 | _value > 0) { 43 | allowed[_from][msg.sender] -= _value; 44 | return transfer(_from, _to, _value); 45 | } else { 46 | return false; 47 | } 48 | } 49 | 50 | function allowance(address _owner, address _spender) 51 | external 52 | view 53 | returns (uint256 remaining) 54 | { 55 | return allowed[_owner][_spender]; 56 | } 57 | 58 | function balanceOf(address _owner) 59 | external 60 | view 61 | returns (uint256 balance) 62 | { 63 | return balances[_owner]; 64 | } 65 | 66 | function mint( 67 | address _to, 68 | uint256 _amount 69 | ) 70 | internal 71 | { 72 | balances[_to] += _amount; 73 | totalSupply += _amount; 74 | emit Transfer(address(0x0), _to, _amount); 75 | } 76 | 77 | function transfer( 78 | address _from, 79 | address _to, 80 | uint256 _value 81 | ) 82 | internal 83 | returns (bool success) 84 | { 85 | if (balances[_from] >= _value && _value > 0) { 86 | balances[_from] -= _value; 87 | balances[_to] += _value; 88 | emit Transfer(_from, _to, _value); 89 | return true; 90 | } else { 91 | return false; 92 | } 93 | } 94 | 95 | 96 | } 97 | -------------------------------------------------------------------------------- /contracts/token/ERC20Receiver.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.5.0 <0.7.0; 2 | 3 | import "./ERC20Token.sol"; 4 | 5 | contract ERC20Receiver { 6 | 7 | event TokenDeposited(address indexed token, address indexed sender, uint256 amount); 8 | event TokenWithdrawn(address indexed token, address indexed sender, uint256 amount); 9 | 10 | mapping (address => mapping(address => uint256)) tokenBalances; 11 | 12 | constructor() public { 13 | 14 | } 15 | 16 | function depositToken( 17 | ERC20Token _token 18 | ) 19 | external 20 | { 21 | _depositToken( 22 | msg.sender, 23 | _token, 24 | _token.allowance( 25 | msg.sender, 26 | address(this) 27 | ) 28 | ); 29 | } 30 | 31 | function withdrawToken( 32 | ERC20Token _token, 33 | uint256 _amount 34 | ) 35 | external 36 | { 37 | _withdrawToken(msg.sender, _token, _amount); 38 | } 39 | 40 | function depositToken( 41 | ERC20Token _token, 42 | uint256 _amount 43 | ) 44 | external 45 | { 46 | require(_token.allowance(msg.sender, address(this)) >= _amount, "Bad argument"); 47 | _depositToken(msg.sender, _token, _amount); 48 | } 49 | 50 | function tokenBalanceOf( 51 | ERC20Token _token, 52 | address _from 53 | ) 54 | external 55 | view 56 | returns(uint256 fromTokenBalance) 57 | { 58 | return tokenBalances[address(_token)][_from]; 59 | } 60 | 61 | function _depositToken( 62 | address _from, 63 | ERC20Token _token, 64 | uint256 _amount 65 | ) 66 | private 67 | { 68 | require(_amount > 0, "Bad argument"); 69 | if (_token.transferFrom(_from, address(this), _amount)) { 70 | tokenBalances[address(_token)][_from] += _amount; 71 | emit TokenDeposited(address(_token), _from, _amount); 72 | } 73 | } 74 | 75 | function _withdrawToken( 76 | address _from, 77 | ERC20Token _token, 78 | uint256 _amount 79 | ) 80 | private 81 | { 82 | require(_amount > 0, "Bad argument"); 83 | require(tokenBalances[address(_token)][_from] >= _amount, "Insufficient funds"); 84 | tokenBalances[address(_token)][_from] -= _amount; 85 | require(_token.transfer(_from, _amount), "Transfer fail"); 86 | emit TokenWithdrawn(address(_token), _from, _amount); 87 | } 88 | 89 | 90 | } -------------------------------------------------------------------------------- /contracts/account/Creator.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.5.0 <0.7.0; 2 | 3 | /** 4 | * @notice wrapper for _create and _create2 5 | */ 6 | contract Creator { 7 | string internal constant ERR_CREATE_FAILED = "Contract creation failed"; 8 | /** 9 | * @dev abstract contract 10 | */ 11 | constructor() internal { 12 | 13 | } 14 | 15 | /** 16 | * @notice creates new contract based on input `_code` and transfer `_value` ETH to this instance 17 | * @param _value amount ether in wei to sent to deployed address at its initialization 18 | * @param _code contract code 19 | * @return created contract address 20 | */ 21 | function _create( 22 | uint _value, 23 | bytes memory _code 24 | ) 25 | internal 26 | returns (address payable createdContract) 27 | { 28 | assembly { 29 | createdContract := create(_value, add(_code, 0x20), mload(_code)) 30 | } 31 | } 32 | 33 | /** 34 | * @notice creates deterministic address with salt `_salt` contract using on input `_code` and transfer `_value` ETH to this instance 35 | * @param _value amount ether in wei to sent to deployed address at its initialization 36 | * @param _code contract code 37 | * @param _salt changes the resulting address 38 | * @return created contract address 39 | */ 40 | function _create2( 41 | uint _value, 42 | bytes memory _code, 43 | bytes32 _salt 44 | ) 45 | internal 46 | returns (address payable createdContract) 47 | { 48 | assembly { 49 | createdContract := create2(_value, add(_code, 0x20), mload(_code), _salt) 50 | } 51 | } 52 | 53 | function _computeContractAddress(bytes memory _code, bytes32 _salt) internal view returns (address _contractAddress) { 54 | bytes32 _data = keccak256( 55 | abi.encodePacked( 56 | bytes1(0xff), 57 | address(this), 58 | _salt, 59 | keccak256(_code) 60 | ) 61 | ); 62 | 63 | _contractAddress = address(bytes20(_data << 96)); 64 | } 65 | 66 | /** 67 | * @dev Internal function to determine if an address is a contract 68 | * @param _target The address being queried 69 | * @return True if `_addr` is a contract 70 | */ 71 | function isContract(address _target) internal view returns(bool result) { 72 | assembly { 73 | result := gt(extcodesize(_target), 0) 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /contracts/cryptography/MerkleVerifier.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.5.0 <0.7.0; 2 | 3 | 4 | library MerkleVerifier { 5 | 6 | function hash_leaf(uint256 value) 7 | internal pure 8 | returns (bytes32 hash) 9 | { 10 | return bytes32(value); 11 | } 12 | 13 | function hash_node(bytes32 left, bytes32 right) 14 | internal pure 15 | returns (bytes32 hash) 16 | { 17 | assembly { 18 | mstore(0x00, left) 19 | mstore(0x20, right) 20 | hash := keccak256(0x00, 0x40) 21 | } 22 | return hash; 23 | } 24 | 25 | // Indices are required to be sorted highest to lowest. 26 | function verify( 27 | bytes32 root, 28 | uint256 depth, 29 | uint256[] memory indices, 30 | bytes32[] memory values, 31 | bytes32[] memory decommitments 32 | ) 33 | internal 34 | pure 35 | { 36 | require(indices.length == values.length, "LENGTH_MISMATCH"); 37 | uint256 n = indices.length; 38 | 39 | // Dynamically allocate index and hash queue 40 | uint256[] memory tree_indices = new uint256[](n + 1); 41 | bytes32[] memory hashes = new bytes32[](n + 1); 42 | uint256 head = 0; 43 | uint256 tail = 0; 44 | uint256 di = 0; 45 | 46 | // Queue the leafs 47 | for(; tail < n; ++tail) { 48 | tree_indices[tail] = 2**depth + indices[tail]; 49 | } 50 | 51 | // Itterate the queue until we hit the root 52 | while (true) { 53 | uint256 index = tree_indices[head]; 54 | bytes32 hash = hashes[head]; 55 | head = (head + 1) % (n + 1); 56 | 57 | // Merkle root 58 | if (index == 1) { 59 | require(hash == root, "INVALID_MERKLE_PROOF"); 60 | return; 61 | 62 | // Even node, take sibbling from decommitments 63 | } else if (index & 1 == 0) { 64 | hash = hash_node(hash, decommitments[di++]); 65 | 66 | // Odd node with sibbling in the queue 67 | } else if (head != tail && tree_indices[head] == index - 1) { 68 | hash = hash_node(hashes[head], hash); 69 | head = (head + 1) % n; 70 | 71 | // Odd node with sibbling from decommitments 72 | } else { 73 | hash = hash_node(decommitments[di++], hash); 74 | 75 | } 76 | tree_indices[tail] = index / 2; 77 | hashes[tail] = hash; 78 | tail = (tail + 1) % (n + 1); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /app/dapp.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import {Tabs, Tab} from 'react-bootstrap'; 4 | 5 | import EmbarkJS from 'Embark/EmbarkJS'; 6 | import TestStatusNetworkUI from './components/TestStatusNetwork'; 7 | 8 | import './dapp.css'; 9 | 10 | class App extends React.Component { 11 | 12 | constructor(props) { 13 | super(props); 14 | 15 | this.handleSelect = this.handleSelect.bind(this); 16 | 17 | this.state = { 18 | error: null, 19 | activeKey: 1, 20 | whisperEnabled: false, 21 | storageEnabled: false, 22 | blockchainEnabled: false 23 | }; 24 | } 25 | 26 | componentDidMount() { 27 | EmbarkJS.onReady((err) => { 28 | this.setState({blockchainEnabled: true}); 29 | if (err) { 30 | // If err is not null then it means something went wrong connecting to ethereum 31 | // you can use this to ask the user to enable metamask for e.g 32 | return this.setState({error: err.message || err}); 33 | } 34 | 35 | EmbarkJS.Messages.Providers.whisper.getWhisperVersion((err, _version) => { 36 | if (err) { 37 | return console.log(err); 38 | } 39 | this.setState({whisperEnabled: true}); 40 | }); 41 | 42 | EmbarkJS.Storage.isAvailable().then((result) => { 43 | this.setState({storageEnabled: result}); 44 | }).catch(() => { 45 | this.setState({storageEnabled: false}); 46 | }); 47 | }); 48 | } 49 | 50 | _renderStatus(title, available) { 51 | let className = available ? 'pull-right status-online' : 'pull-right status-offline'; 52 | return 53 | {title} 54 | 55 | ; 56 | } 57 | 58 | handleSelect(key) { 59 | this.setState({ activeKey: key }); 60 | } 61 | 62 | render() { 63 | const ensEnabled = EmbarkJS.Names.currentNameSystems && EmbarkJS.Names.isAvailable(); 64 | if (this.state.error) { 65 | return (
66 |
Something went wrong connecting to ethereum. Please make sure you have a node running or are using metamask to connect to the ethereum network:
67 |
{this.state.error}
68 |
); 69 | } 70 | return (
71 |

Status Network - Test

72 | 73 | 74 | 75 | 76 | 77 |
); 78 | } 79 | } 80 | 81 | ReactDOM.render(, document.getElementById('app')); 82 | -------------------------------------------------------------------------------- /contracts/cryptography/MerkleMultiProof.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.5.0 <0.7.0; 2 | 3 | /** 4 | * @author Ricardo Guilherme Schmidt (Status Research & Development GmbH) 5 | * @notice based on https://github.com/ethereum/eth2.0-specs/blob/dev/ssz/merkle-proofs.md#merkle-multiproofs but without generalized indexes 6 | */ 7 | library MerkleMultiProof { 8 | 9 | /** 10 | * @notice Calculates a merkle root using multiple leafs at same time 11 | * @param leafs out of order sequence of leafs and it's siblings 12 | * @param proofs out of order sequence of parent proofs 13 | * @param proofFlag flags for using or not proofs while hashing against hashes. 14 | * @return merkle root of tree 15 | */ 16 | function calculateMultiMerkleRoot( 17 | bytes32[] memory leafs, 18 | bytes32[] memory proofs, 19 | bool[] memory proofFlag 20 | ) 21 | internal 22 | pure 23 | returns (bytes32 merkleRoot) 24 | { 25 | uint256 leafsLen = leafs.length; 26 | uint256 totalHashes = proofFlag.length; 27 | bytes32[] memory hashes = new bytes32[](totalHashes); 28 | uint leafPos = 0; 29 | uint hashPos = 0; 30 | uint proofPos = 0; 31 | for(uint256 i = 0; i < totalHashes; i++){ 32 | hashes[i] = hashPair( 33 | proofFlag[i] ? (leafPos < leafsLen ? leafs[leafPos++] : hashes[hashPos++]) : proofs[proofPos++], 34 | leafPos < leafsLen ? leafs[leafPos++] : hashes[hashPos++] 35 | ); 36 | } 37 | 38 | return hashes[totalHashes-1]; 39 | } 40 | 41 | function hashPair(bytes32 a, bytes32 b) private pure returns(bytes32){ 42 | return a < b ? hash_node(a, b) : hash_node(b, a); 43 | } 44 | 45 | function hash_node(bytes32 left, bytes32 right) 46 | private pure 47 | returns (bytes32 hash) 48 | { 49 | assembly { 50 | mstore(0x00, left) 51 | mstore(0x20, right) 52 | hash := keccak256(0x00, 0x40) 53 | } 54 | return hash; 55 | } 56 | 57 | /** 58 | * @notice Check validity of multimerkle proof 59 | * @param root merkle root 60 | * @param leafs out of order sequence of leafs and it's siblings 61 | * @param proofs out of order sequence of parent proofs 62 | * @param proofFlag flags for using or not proofs while hashing against hashes. 63 | */ 64 | function verifyMultiProof( 65 | bytes32 root, 66 | bytes32[] memory leafs, 67 | bytes32[] memory proofs, 68 | bool[] memory proofFlag 69 | ) 70 | internal 71 | pure 72 | returns (bool) 73 | { 74 | return calculateMultiMerkleRoot(leafs, proofs, proofFlag) == root; 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /utils/secretMultisig.js: -------------------------------------------------------------------------------- 1 | const { MerkleTree } = require('./merkleTree.js'); 2 | const { keccak256, bufferToHex, isValidAddress, setLengthLeft } = require('ethereumjs-util'); 3 | const namehash = require('eth-ens-namehash'); 4 | 5 | const MINIMUM_LIST_SIZE = 4096; 6 | const THRESHOLD = 100 * 10**18; 7 | const RECOVERY_ADDRESS = "0x2429242924292429242924292429242924292429"; 8 | const ERC2429 = require('Embark/contracts/MultisigRecovery'); 9 | 10 | export default class SecretMultisig { 11 | 12 | constructor(userAddress, privateHash, addressList) { 13 | if (addressList.length == 0){ 14 | throw new Error("Invalid Address List") 15 | } 16 | 17 | this.elements = addressList.map((v) => this.hashLeaf(v.address, v.weight)); 18 | if(this.elements.length < MINIMUM_LIST_SIZE) { 19 | this.elements.push(... Array.from({length: MINIMUM_LIST_SIZE-this.elements.length}, (v,k) => hashFakeLeaf(privateHash, k))) 20 | } 21 | this.merkleTree = new MerkleTree(this.elements); 22 | this.executeHash = this.hashExecute(privateHash, userAddress); 23 | this.partialReveal = keccak256(this.executeHash); 24 | this.publicHash = keccak256(Buffer.concat( 25 | this.partialReveal, 26 | keccak256(this.merkleTree.getRoot()) 27 | )); 28 | } 29 | 30 | 31 | hashExecute = async (privateHash, userAddress) => keccak256(Buffer.concat( 32 | privateHash, 33 | Buffer,from(ERC2429.address, 'hex'), 34 | setLengthLeft(Buffer.from(Number.toString(await ERC2429.methods.nonce(userAddress).call(), 16), 'hex'), 32) 35 | )); 36 | 37 | 38 | hashLeaf = (ethereumAddress, weight) => keccak256(Buffer.concat( 39 | this.hashAddress(ethereumAddress), 40 | setLengthLeft(Buffer.from(Number.toString(weight, 16), 'hex'), 32) 41 | )); 42 | 43 | hashFakeLeaf = (privateHash, position) => keccak256(Buffer.concat( 44 | privateHash, 45 | setLengthLeft(Buffer.from(Number.toString(position, 16), 'hex'), 32) 46 | )); 47 | 48 | hashAddress = (ethereumAddress) => keccak256( 49 | isValidAddress(ethereumAddress) ? Buffer.concat( 50 | Buffer.from('0x00', 'hex'), 51 | setLengthLeft(Buffer.from(ethereumAddress, 'hex'), 32) 52 | ) : Buffer.concat( 53 | Buffer.from('0x01', 'hex'), 54 | namehash(ethereumAddress) 55 | ) 56 | ); 57 | 58 | hashApproval = (approver_address, calldest, calldata) => keccak256(Buffer.concat( 59 | this.hashAddress(approver_address), 60 | this.hashCall(calldest, calldata) 61 | )); 62 | 63 | 64 | hashCall = (calldest, calldata) => keccak256(Buffer.concat( 65 | this.partialReveal, 66 | Buffer.from(calldest, 'hex'), 67 | Buffer.from(calldata, 'hex') 68 | )); 69 | 70 | } -------------------------------------------------------------------------------- /test/secretmultisigrecovery.spec.js: -------------------------------------------------------------------------------- 1 | const { SecretMultisig } = require('../utils/secretMultisig.js'); 2 | const EmbarkJS = require('Embark/EmbarkJS'); 3 | const MultisigRecovery = require('Embark/contracts/MultisigRecovery') 4 | 5 | let accounts; 6 | let ms; 7 | 8 | config({ 9 | blockchain: { 10 | accounts: [ 11 | { 12 | mnemonic: "candy maple cake sugar pudding cream honey rich smooth crumble sweet treat", 13 | addressIndex: "0", 14 | numAddresses: "102", 15 | balance: "5 ether" 16 | } 17 | ] 18 | }, 19 | namesystem: { 20 | enabled: true, 21 | register: { 22 | rootDomain: "eth", 23 | subdomains: { 24 | 'test': '0x627306090abaB3A6e1400e9345bC60c78a8BEf57' 25 | } 26 | } 27 | }, 28 | contracts: { 29 | deploy: { 30 | "MultisigRecovery": { 31 | args: [ "$ENSRegistry" ] 32 | } 33 | } 34 | } 35 | }, (_err, web3_accounts) => { 36 | accounts = web3_accounts; 37 | ms = new SecretMultisig(accounts[0], "0x0011223344556677889900112233445566778899001122334455667788990011", accounts.slice(1)); 38 | }); 39 | 40 | 41 | contract('SecretMultisigRecovery', function () { 42 | 43 | describe('setup', function () { 44 | it('first time activates immediately', async function () { 45 | 46 | }); 47 | 48 | it('pending setup', async function () { 49 | 50 | }); 51 | 52 | }); 53 | 54 | describe('activate', function () { 55 | it('does not activate during delay time', async function () { 56 | 57 | }); 58 | 59 | it('activates a pending setup', async function () { 60 | 61 | }); 62 | }); 63 | 64 | describe('cancelSetup', function () { 65 | it('cancels when not reached', async function () { 66 | 67 | }); 68 | 69 | it('does not cancel when reached', async function () { 70 | 71 | }); 72 | }); 73 | 74 | describe('approve', function () { 75 | it('using address', async function () { 76 | 77 | }); 78 | it('using ENS', async function () { 79 | let address = await EmbarkJS.Names.resolve('test.eth') 80 | console.log('ENS address', address); 81 | }); 82 | }); 83 | 84 | describe('approvePreSigned', function () { 85 | it('using address', async function () { 86 | 87 | }); 88 | it('using ENS', async function () { 89 | let address = await EmbarkJS.Names.resolve('test.eth') 90 | console.log('ENS address', address); 91 | }); 92 | }); 93 | 94 | describe('execute', function () { 95 | it('executes approved', async function () { 96 | 97 | }); 98 | 99 | it('cant execute with low threshold', async function () { 100 | 101 | }); 102 | 103 | it('cant execute with different calldest', async function () { 104 | 105 | }); 106 | 107 | it('cant execute with different calldata', async function () { 108 | 109 | }); 110 | 111 | }); 112 | 113 | }); 114 | -------------------------------------------------------------------------------- /contracts/account/PaymentNetworkActor.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.5.0 <0.7.0; 2 | 3 | import "../cryptography/ECDSA.sol"; 4 | import "../token/ERC20Token.sol"; 5 | import "./Actor.sol"; 6 | import "../common/Controlled.sol"; 7 | 8 | 9 | interface PaymentNetwork { 10 | function process(ERC20Token token, address from, address to, uint256 value) external; 11 | } 12 | 13 | /** 14 | * @notice Payment Network Actor for Account Contract 15 | * @author Ricardo Guilherme Schmidt (Status Research & Development GmbH) 16 | * @author Andrea Franz (Status Research & Development GmbH) 17 | */ 18 | contract PaymentNetworkActor is Controlled { 19 | PaymentNetwork public paymentNetwork; 20 | address public keycard; 21 | uint256 public nonce; 22 | Settings public settings; 23 | Actor public actor; 24 | 25 | struct Settings { 26 | uint256 maxTxValue; 27 | } 28 | 29 | constructor(Actor _actor, PaymentNetwork _paymentNetwork, address _keycard, uint256 _maxTxValue) public Controller(msg.sender) { 30 | actor = _actor; 31 | keycard = _keycard; 32 | settings.maxTxValue = _maxTxValue; 33 | paymentNetwork = _paymentNetwork; 34 | nonce = 0; 35 | } 36 | 37 | function setKeycard(address _keycard) public onlyController { 38 | keycard = _keycard; 39 | } 40 | 41 | function setSettings(uint256 _maxTxValue) public onlyController { 42 | settings.maxTxValue = _maxTxValue; 43 | } 44 | 45 | function requestPayment( 46 | bytes32 _hashToSign, 47 | bytes memory _signature, 48 | uint256 _nonce, 49 | ERC20Token _token, 50 | address payable _to, 51 | uint256 _value 52 | ) public { 53 | // check that a keycard address has been set 54 | require(keycard != address(0), "keycard address not set"); 55 | 56 | // check that the _hashToSign has been produced with the nonce, to, and value 57 | bytes32 expectedHash = ECDSA.toERC191SignedMessage(address(this), abi.encodePacked(_nonce, _token, _to, _value)); 58 | require(expectedHash == _hashToSign, "signed params are different"); 59 | 60 | // check that the _hashToSign has been signed by the keycard 61 | address signer = ECDSA.recover(_hashToSign, _signature); 62 | require(signer == keycard, "signer is not the keycard"); 63 | 64 | // check that the nonce is valid 65 | require(nonce == _nonce, "invalid nonce"); 66 | 67 | // check that _value is not greater than settings.maxTxValue 68 | require(_value <= settings.maxTxValue, "amount not allowed"); 69 | 70 | // increment nonce 71 | nonce++; 72 | 73 | //calls identity to execute approval of token withdraw by payment network 74 | actor.call(address(_token), 0, abi.encodeWithSelector(_token.approve.selector, paymentNetwork, _value)); 75 | 76 | //calls payment network to process payment 77 | paymentNetwork.process(_token, controller, _to, _value); 78 | } 79 | 80 | } -------------------------------------------------------------------------------- /contracts/account/ERC20Caller.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.5.0 <0.7.0; 2 | 3 | import "../token/ERC20Token.sol"; 4 | 5 | /** 6 | * @notice Enables the use of approve and call for contracts accepting an ERC20 token through approval. 7 | */ 8 | contract ERC20Caller { 9 | 10 | string internal constant ERR_BAD_TOKEN_ADDRESS = "Bad token address"; 11 | string internal constant ERR_BAD_DESTINATION = "Bad destination"; 12 | 13 | /** 14 | * @dev abstract contract 15 | */ 16 | constructor() internal { 17 | 18 | } 19 | 20 | /** 21 | * @notice Approves `_to` spending ERC20 `_baseToken` a total of `_value` and calls `_to` with `_data`. Useful for a better UX on ERC20 token use, and avoid race conditions. 22 | * @param _baseToken ERC20 token being approved to spend 23 | * @param _to Destination of contract accepting this ERC20 token payments through approve 24 | * @param _value amount of ERC20 being approved 25 | * @param _data abi encoded calldata to be executed in `_to` after approval. 26 | * @return internal transaction status and returned data 27 | */ 28 | function _approveAndCall( 29 | address _baseToken, 30 | address _to, 31 | uint256 _value, 32 | bytes memory _data 33 | ) 34 | internal 35 | returns (bool success, bytes memory returndata) 36 | { 37 | require(_baseToken != address(0) && _baseToken != address(this), ERR_BAD_TOKEN_ADDRESS); //_baseToken should be something! 38 | require(_to != address(0) && _to != address(this), ERR_BAD_DESTINATION); //need valid destination 39 | ERC20Token(_baseToken).approve(_to, _value); 40 | (success, returndata) = _to.call(_data); //NO VALUE call 41 | } 42 | 43 | /** 44 | * @notice Approves `_to` spending ERC20 `_baseToken` a total of `_value` and calls `_to` with `_data`. Useful for a better UX on ERC20 token use, and avoid race conditions. 45 | * @param _baseToken ERC20 token being approved to spend 46 | * @param _to Destination of contract accepting this ERC20 token payments through approve 47 | * @param _value amount of ERC20 being approved 48 | * @param _data abi encoded calldata to be executed in `_to` after approval. 49 | * @param _gas gas to limit the internal transaction 50 | * @return internal transaction status and returned data 51 | */ 52 | function _approveAndCall( 53 | address _baseToken, 54 | address _to, 55 | uint256 _value, 56 | bytes memory _data, 57 | uint256 _gas 58 | ) 59 | internal 60 | returns (bool success, bytes memory returndata) 61 | { 62 | require(_baseToken != address(0) && _baseToken != address(this), ERR_BAD_TOKEN_ADDRESS); //_baseToken should be something! 63 | require(_to != address(0) && _to != address(this), ERR_BAD_DESTINATION); //need valid destination 64 | ERC20Token(_baseToken).approve(_to, _value); 65 | (success, returndata) = _to.call.gas(_gas)(_data); //NO VALUE call 66 | } 67 | 68 | } -------------------------------------------------------------------------------- /contracts/status/SNTController.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.5.0 <0.7.0; 2 | 3 | import "../token/TokenController.sol"; 4 | import "../common/Owned.sol"; 5 | import "../common/TokenClaimer.sol"; 6 | import "../token/ERC20Token.sol"; 7 | import "../token/MiniMeToken.sol"; 8 | /** 9 | * @title SNTController 10 | * @author Ricardo Guilherme Schmidt (Status Research & Development GmbH) 11 | * @notice enables economic abstraction for SNT 12 | */ 13 | contract SNTController is TokenController, Owned, TokenClaimer { 14 | 15 | MiniMeToken public snt; 16 | 17 | event ClaimedTokens(address indexed _token, address indexed _controller, uint256 _amount); 18 | event ControllerChanged(address indexed _newController); 19 | 20 | /** 21 | * @notice Constructor 22 | * @param _owner Authority address 23 | * @param _snt SNT token 24 | */ 25 | constructor(address payable _owner, MiniMeToken _snt) internal { 26 | if(_owner != address(0)){ 27 | owner = _owner; 28 | } 29 | snt = _snt; 30 | } 31 | /** 32 | * @notice The owner of this contract can change the controller of the SNT token 33 | * Please, be sure that the owner is a trusted agent or 0x0 address. 34 | * @param _newController The address of the new controller 35 | */ 36 | function changeController(address payable _newController) public onlyOwner { 37 | snt.changeController(_newController); 38 | emit ControllerChanged(_newController); 39 | } 40 | 41 | /** 42 | * @notice This method can be used by the controller to extract mistakenly 43 | * sent tokens to this contract. 44 | * @param _token The address of the token contract that you want to recover 45 | * set to 0 in case you want to extract ether. 46 | */ 47 | function claimTokens(address _token) public onlyOwner { 48 | if (snt.controller() == address(this)) { 49 | snt.claimTokens(_token); 50 | } 51 | withdrawBalance(_token, owner); 52 | } 53 | 54 | /** 55 | * @notice payment by address coming from controlled token 56 | * @dev In between the offering and the network. Default settings for allowing token transfers. 57 | */ 58 | function proxyPayment(address) external payable returns (bool) { 59 | //Uncomment above line when using parameters 60 | //require(msg.sender == address(snt), "Unauthorized"); 61 | return false; 62 | } 63 | 64 | /** 65 | * @notice register and authorizes transfer from token 66 | * @dev called by snt when a transfer is made 67 | */ 68 | function onTransfer(address, address, uint256) external returns (bool) { 69 | //Uncomment above line when using parameters 70 | //require(msg.sender == address(snt), "Unauthorized"); 71 | return true; 72 | } 73 | 74 | /** 75 | * @notice register and authorizes approve from token 76 | * @dev called by snt when an approval is made 77 | */ 78 | function onApprove(address, address, uint256) external returns (bool) { 79 | //Uncomment above line when using parameters 80 | //require(msg.sender == address(snt), "Unauthorized"); 81 | return true; 82 | } 83 | } -------------------------------------------------------------------------------- /contracts/account/ERC2429.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.5.0 <0.7.0; 2 | 3 | interface ERC2429 { 4 | event SetupRequested(address indexed who, uint256 activation); 5 | event Activated(address indexed who); 6 | event Approved(bytes32 indexed approveHash, bytes32 leaf); 7 | event Execution(address indexed who, bool success); 8 | 9 | struct RecoverySet { 10 | bytes32 publicHash; 11 | uint256 setupDelay; 12 | uint256 timestamp; 13 | } 14 | 15 | /** 16 | * @notice Cancels a pending setup of `msg.sender` to change the recovery set parameters 17 | */ 18 | function cancelSetup() 19 | external; 20 | 21 | /** 22 | * @notice Configure recovery set parameters of `msg.sender`. `emit Activated(msg.sender)` if there was no previous setup, or `emit SetupRequested(msg.sender, now()+setupDelay)` when reconfiguring. 23 | * @param _publicHash Hash of `peerHash`. 24 | * @param _setupDelay Delay for changes being activ. 25 | */ 26 | function setup( 27 | bytes32 _publicHash, 28 | uint256 _setupDelay 29 | ) 30 | external; 31 | 32 | /** 33 | * @notice Activate a pending setup of `_who` recovery set parameters. 34 | * @param _who address whih ready setupDelay. 35 | */ 36 | function activate(address _who) 37 | external; 38 | 39 | /** 40 | * @notice Approves a recovery. This method is important for when the address is an contract and dont implements EIP1271. 41 | * @param _approveHash Hash of the recovery call 42 | * @param _ensNode if present, the _proof is checked against _ensNode. 43 | */ 44 | function approve( 45 | bytes32 _approveHash, 46 | bytes32 _ensNode 47 | ) 48 | external; 49 | /** 50 | * @notice Approve a recovery execution using an ethereum signed message. 51 | * @param _signer address of _signature processor. if _signer is a contract, must be ERC1271. 52 | * @param _approveHash Hash of the recovery call 53 | * @param _ensNode if present, the _proof is checked against _ensName. 54 | * @param _signature ERC191 signature 55 | */ 56 | function approvePreSigned( 57 | address _signer, 58 | bytes32 _approveHash, 59 | bytes32 _ensNode, 60 | bytes calldata _signature 61 | ) 62 | external; 63 | 64 | /** 65 | * @notice executes an approved transaction revaling publicHash hash, friends addresses and set new recovery parameters 66 | * @param _executeHash Seed of `peerHash` 67 | * @param _merkleRoot Revealed merkle root 68 | * @param _calldest Address will be called 69 | * @param _calldata Data to be sent 70 | * @param _leafData Pre approved leafhashes and it's weights as siblings ordered by descending weight 71 | * @param _proofs parents proofs 72 | * @param _proofFlags indexes that select the hashing pairs from calldata `_leafHashes` and `_proofs` and from memory `hashes` 73 | */ 74 | function execute( 75 | bytes32 _executeHash, 76 | bytes32 _merkleRoot, 77 | address _calldest, 78 | bytes calldata _calldata, 79 | bytes32[] calldata _leafData, 80 | bytes32[] calldata _proofs, 81 | bool[] calldata _proofFlags 82 | ) 83 | external; 84 | } -------------------------------------------------------------------------------- /contracts/account/AccountGasAbstract.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.5.0 <0.7.0; 2 | 3 | import "./Account.sol"; 4 | import "../gasrelay/GasRelay.sol"; 5 | 6 | /** 7 | * @title AccountGasAbstract 8 | * @author Ricardo Guilherme Schmidt (Status Research & Development GmbH) 9 | * @notice defines account gas abstract 10 | */ 11 | contract AccountGasAbstract is Account, GasRelay { 12 | string internal constant ERR_INVALID_SIGNATURE = "Invalid signature"; 13 | 14 | modifier gasRelay( 15 | bytes memory _execData, 16 | uint256 _gasPrice, 17 | uint256 _gasLimit, 18 | address _gasToken, 19 | address payable _gasRelayer, 20 | bytes memory _signature 21 | ){ 22 | //query current gas available 23 | uint startGas = gasleft(); 24 | 25 | //verify if signatures are valid and came from correct actor; 26 | require( 27 | isValidSignature( 28 | executeGasRelayERC191Msg( 29 | nonce, 30 | _execData, 31 | _gasPrice, 32 | _gasLimit, 33 | _gasToken, 34 | _gasRelayer 35 | ), 36 | _signature 37 | ) == MAGICVALUE, 38 | ERR_INVALID_SIGNATURE 39 | ); 40 | 41 | _; 42 | 43 | //refund gas used using contract held ERC20 tokens or ETH 44 | if (_gasPrice > 0) { 45 | payGasRelayer( 46 | startGas, 47 | _gasPrice, 48 | _gasToken, 49 | _gasRelayer == address(0) ? block.coinbase : _gasRelayer 50 | ); 51 | } 52 | } 53 | 54 | function executeGasRelay( 55 | bytes calldata _execData, 56 | uint256 _gasPrice, 57 | uint256 _gasLimit, 58 | address _gasToken, 59 | address payable _gasRelay, 60 | bytes calldata _signature 61 | ) 62 | external 63 | gasRelay( 64 | _execData, 65 | _gasPrice, 66 | _gasLimit, 67 | _gasToken, 68 | _gasRelay, 69 | _signature 70 | ) 71 | { 72 | address(this).call.gas(_gasLimit)(_execData); 73 | } 74 | 75 | function lastNonce() public view returns (uint256) { 76 | return nonce; 77 | } 78 | 79 | /** 80 | * @notice check gas limit and pays gas to relayer 81 | * @param _startGas gasleft on call start 82 | * @param _gasPrice price in `_gasToken` paid back to `_gasRelayer` per gas unit used 83 | * @param _gasToken token being used for paying `_gasRelayer` 84 | * @param _gasRelayer beneficiary of the payout 85 | */ 86 | function payGasRelayer( 87 | uint256 _startGas, 88 | uint256 _gasPrice, 89 | address _gasToken, 90 | address payable _gasRelayer 91 | ) 92 | internal 93 | { 94 | uint256 _amount = (100000 + (_startGas - gasleft()) * _gasPrice); 95 | if (_gasToken == address(0)) { 96 | _gasRelayer.call.value(_amount)(""); 97 | } else { 98 | ERC20Token(_gasToken).transfer(_gasRelayer, _amount); 99 | } 100 | } 101 | } -------------------------------------------------------------------------------- /contracts/account/UserAccountInterface.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.5.0 <0.7.0; 2 | 3 | import "./ERC725.sol"; 4 | import "./Signer.sol"; 5 | /** 6 | * @author Ricardo Guilherme Schmidt (Status Research & Development GmbH) 7 | * @notice A common user account interface 8 | */ 9 | contract UserAccountInterface is ERC725, Signer { 10 | 11 | /** 12 | * @notice calls another contract 13 | * @param _to destination of call 14 | * @param _value call ether value (in wei) 15 | * @param _data call data 16 | * @return internal transaction status and returned data 17 | */ 18 | function call( 19 | address _to, 20 | uint256 _value, 21 | bytes calldata _data 22 | ) 23 | external 24 | returns(bool success, bytes memory returndata); 25 | 26 | /** 27 | * @notice Approves `_to` spending ERC20 `_baseToken` a total of `_value` and calls `_to` with `_data`. Useful for a better UX on ERC20 token use, and avoid race conditions. 28 | * @param _baseToken ERC20 token being approved to spend 29 | * @param _to Destination of contract accepting this ERC20 token payments through approve 30 | * @param _value amount of ERC20 being approved 31 | * @param _data abi encoded calldata to be executed in `_to` after approval. 32 | * @return internal transaction status and returned data 33 | */ 34 | function approveAndCall( 35 | address _baseToken, 36 | address _to, 37 | uint256 _value, 38 | bytes calldata _data 39 | ) 40 | external 41 | returns(bool success, bytes memory returndata); 42 | 43 | /** 44 | * @notice creates new contract based on input `_code` and transfer `_value` ETH to this instance 45 | * @param _value amount ether in wei to sent to deployed address at its initialization 46 | * @param _code contract code 47 | * @return creation success status and created contract address 48 | */ 49 | function create( 50 | uint256 _value, 51 | bytes calldata _code 52 | ) 53 | external 54 | returns(address createdContract); 55 | 56 | /** 57 | * @notice creates deterministic address contract using on input `_code` and transfer `_value` ETH to this instance 58 | * @param _value amount ether in wei to sent to deployed address at its initialization 59 | * @param _code contract code 60 | * @param _salt changes the resulting address 61 | * @return creation success status and created contract address 62 | */ 63 | function create2( 64 | uint256 _value, 65 | bytes calldata _code, 66 | bytes32 _salt 67 | ) 68 | external 69 | returns(address createdContract); 70 | 71 | /** 72 | * @notice Defines recoveryContract address. 73 | * @param _recovery address of recoveryContract contract 74 | */ 75 | function setRecovery(address _recovery) external; 76 | 77 | /** 78 | * @notice Changes actor contract 79 | * @param _actor Contract which can call actions from this contract 80 | */ 81 | function setActor(address _actor) external; 82 | 83 | /** 84 | * @notice Replace owner address. 85 | * @param newOwner address of externally owned account or ERC1271 contract to control this account 86 | */ 87 | function changeOwner(address newOwner) external; 88 | 89 | /** 90 | * @notice Defines the new owner and disable actor. Can only be called by recovery. 91 | * @param newOwner an ERC1271 contract 92 | */ 93 | function recover(address newOwner) external; 94 | } -------------------------------------------------------------------------------- /contracts/account/MultisigAccount.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.5.0 <0.7.0; 2 | 3 | import "../account/Account.sol"; 4 | import "../cryptography/ECDSA.sol"; 5 | 6 | /** 7 | * @author Ricardo Guilherme Schmidt (Status Research & Development GmbH) 8 | */ 9 | contract MultisigAccount is Account { 10 | string internal constant ERR_BAD_SIGNER = "Bad signer"; 11 | bytes4 internal constant MSG_EXECUTE_PREFIX = bytes4( 12 | keccak256("execute(uint256,address,uint256)") 13 | ); 14 | 15 | uint256 available = 0; 16 | uint256 required = 0; 17 | mapping(address => bool) isKey; 18 | 19 | modifier self { 20 | require(msg.sender == address(this), "Unauthorized"); 21 | _; 22 | } 23 | 24 | constructor(address[] memory _keys, uint256 _required) public { 25 | available = _keys.length; 26 | required = _required; 27 | for(uint i = 0; i < available; i++) { 28 | address key = _keys[i]; 29 | require(isKey[key] == false, "Duplicated"); 30 | isKey[key] = true; 31 | } 32 | } 33 | 34 | function callSigned( 35 | address _to, 36 | uint256 _value, 37 | bytes calldata _data, 38 | bytes calldata _signature 39 | ) 40 | external 41 | returns (bool success, bytes memory returndata) 42 | { 43 | require( 44 | isValidSignature( 45 | abi.encodePacked( 46 | address(this), 47 | MSG_EXECUTE_PREFIX, 48 | nonce, 49 | _to, 50 | _value, 51 | _data 52 | ), 53 | _signature 54 | ) == MAGICVALUE, 55 | ERR_BAD_SIGNER 56 | ); 57 | (success, returndata) = _call(_to, _value, _data); 58 | } 59 | 60 | function setKey(address key, bool isValid) external self { 61 | require(key != address(0), "Invalid address"); 62 | require(isKey[key] != isValid, "Already set"); 63 | isKey[key] = isValid; 64 | isValid ? available++ : available--; 65 | require(available >= required, "Reduce required first"); 66 | } 67 | 68 | function setRequired(uint256 _required) external self { 69 | require(available >= _required, "No enough keys"); 70 | required = _required; 71 | } 72 | 73 | function isValidSignature( 74 | bytes memory _data, 75 | bytes memory _signature 76 | ) 77 | public 78 | view 79 | returns (bytes4 magicValue) 80 | { 81 | magicValue = 0xffffffff; 82 | uint _amountSignatures = _signature.length / 65; 83 | if(_amountSignatures != required) { 84 | return magicValue; 85 | } 86 | 87 | address lastSigner = address(0); 88 | uint8 v; 89 | bytes32 r; 90 | bytes32 s; 91 | bytes32 dataHash = ECDSA.toERC191SignedMessage(address(this), _data); 92 | for (uint256 i = 0; i < _amountSignatures; i++) { 93 | /* solium-disable-next-line security/no-inline-assembly*/ 94 | assembly { 95 | let signaturePos := mul(0x41, i) 96 | r := mload(add(_signature, add(signaturePos, 0x20))) 97 | s := mload(add(_signature, add(signaturePos, 0x40))) 98 | v := and(mload(add(_signature, add(signaturePos, 0x41))), 0xff) 99 | } 100 | address signer = ecrecover(dataHash, v, r, s); 101 | if (signer < lastSigner || !isKey[signer] ) { 102 | return magicValue; 103 | } 104 | 105 | lastSigner = signer; 106 | } 107 | magicValue = MAGICVALUE; 108 | } 109 | 110 | } -------------------------------------------------------------------------------- /contracts/account/ActorsAllowed.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.5.0 <0.7.0; 2 | 3 | import "./UserAccountInterface.sol"; 4 | import "../common/Controlled.sol"; 5 | import "./Actor.sol"; 6 | 7 | /** 8 | * @author Ricardo Guilherme Schmidt (Status Research & Development GmbH) 9 | */ 10 | abstract contract ActorsAllowed is Actor, Controlled { 11 | 12 | address[] public actors; 13 | mapping(address => bool) public isActor; 14 | 15 | modifier onlyActors { 16 | require(isActor[msg.sender], "Unauthorized"); 17 | _; 18 | } 19 | /** 20 | * @notice Adds a new actor that could arbitrarely call external contracts. If specific permission logic (e.g. ACL), it can be implemented in the actor's address contract logic. 21 | * @param newActor a new actor to be added. 22 | */ 23 | function addActor(address newActor) 24 | external 25 | onlyController 26 | { 27 | require(!isActor[newActor], "Already defined"); 28 | actors.push(newActor); 29 | isActor[newActor] = true; 30 | } 31 | 32 | /** 33 | * @notice Removes an actor 34 | * @param index position of actor in the `actors()` array list. 35 | */ 36 | function removeActor(uint256 index) 37 | external 38 | onlyController 39 | { 40 | uint256 lastPos = actors.length-1; 41 | require(index <= lastPos, "Index out of bounds"); 42 | address removing = actors[index]; 43 | isActor[removing] = false; 44 | if(index != lastPos){ 45 | actors[index] = actors[lastPos]; 46 | } 47 | actors.length--; 48 | } 49 | 50 | /** 51 | * @notice calls another contract 52 | * @param _to destination of call 53 | * @param _value call ether value (in wei) 54 | * @param _data call data 55 | * @return internal transaction status and returned data 56 | */ 57 | function call( 58 | address _to, 59 | uint256 _value, 60 | bytes calldata _data 61 | ) 62 | external 63 | onlyActors 64 | returns(bool success, bytes memory returndata) 65 | { 66 | (success, returndata) = UserAccountInterface(controller).call(_to, _value, _data); 67 | } 68 | 69 | /** 70 | * @notice Approves `_to` spending ERC20 `_baseToken` a total of `_value` and calls `_to` with `_data`. Useful for a better UX on ERC20 token use, and avoid race conditions. 71 | * @param _baseToken ERC20 token being approved to spend 72 | * @param _to Destination of contract accepting this ERC20 token payments through approve 73 | * @param _value amount of ERC20 being approved 74 | * @param _data abi encoded calldata to be executed in `_to` after approval. 75 | * @return internal transaction status and returned data 76 | */ 77 | function approveAndCall( 78 | address _baseToken, 79 | address _to, 80 | uint256 _value, 81 | bytes calldata _data 82 | ) 83 | external 84 | onlyActors 85 | returns(bool success, bytes memory returndata) 86 | { 87 | (success, returndata) = UserAccountInterface(controller).approveAndCall(_baseToken, _to, _value, _data); 88 | } 89 | 90 | /** 91 | * @notice creates new contract based on input `_code` and transfer `_value` ETH to this instance 92 | * @param _value amount ether in wei to sent to deployed address at its initialization 93 | * @param _code contract code 94 | * @return creation success status and created contract address 95 | */ 96 | function create( 97 | uint256 _value, 98 | bytes calldata _code 99 | ) 100 | external 101 | onlyActors 102 | returns(address createdContract) 103 | { 104 | createdContract = UserAccountInterface(controller).create(_value, _code); 105 | } 106 | 107 | } -------------------------------------------------------------------------------- /app/components/erc20token.js: -------------------------------------------------------------------------------- 1 | import EmbarkJS from 'Embark/EmbarkJS'; 2 | import ERC20Token from 'Embark/contracts/ERC20Token'; 3 | import React from 'react'; 4 | import { Form, FormGroup, FormControl, HelpBlock, Button } from 'react-bootstrap'; 5 | 6 | class ERC20TokenUI extends React.Component { 7 | 8 | constructor(props) { 9 | super(props); 10 | ERC20Token.options.address = props.address; 11 | this.state = { 12 | balanceOf: 0, 13 | transferTo: "", 14 | transferAmount: 0, 15 | accountBalance: 0, 16 | accountB: web3.eth.defaultAccount, 17 | } 18 | } 19 | 20 | update_transferTo(e){ 21 | this.setState({transferTo: e.target.value}); 22 | } 23 | 24 | update_transferAmount(e){ 25 | this.setState({transferAmount: e.target.value}); 26 | } 27 | 28 | transfer(e){ 29 | var to = this.state.transferTo; 30 | var amount = this.state.transferAmount; 31 | this._addToLog(ERC20Token.options.address+".methods.transfer(" + to + ", "+amount+").send({from: " + web3.eth.defaultAccount + "})"); 32 | var tx = ERC20Token.methods.transfer(to, amount); 33 | tx.estimateGas().then((r) => { 34 | tx.send({gas: r, from: web3.eth.defaultAccount}); 35 | }); 36 | 37 | } 38 | 39 | approve(e){ 40 | var to = this.state.transferTo; 41 | var amount = this.state.transferAmount; 42 | this._addToLog(ERC20Token.options.address+".methods.approve(" + to + ", "+amount+").send({from: " + web3.eth.defaultAccount + "})"); 43 | var tx = ERC20Token.methods.approve(to, amount).send({from: web3.eth.defaultAccount}); 44 | 45 | } 46 | 47 | balanceOf(e){ 48 | e.preventDefault(); 49 | var who = e.target.value; 50 | this._addToLog(ERC20Token.options.address+".methods.balanceOf(" + who + ").call()"); 51 | ERC20Token.methods.balanceOf(who).call() 52 | .then(_value => this.setState({balanceOf: _value})) 53 | } 54 | 55 | getDefaultAccountBalance(){ 56 | this._addToLog(ERC20Token.options.address + ".methods.balanceOf(" + web3.eth.defaultAccount + ").call()"); 57 | ERC20Token.methods.balanceOf(web3.eth.defaultAccount).call() 58 | .then(_value => this.setState({accountBalance: _value})) 59 | } 60 | 61 | _addToLog(txt){ 62 | console.log(txt); 63 | } 64 | 65 | render() { 66 | 67 | return ( 68 | 69 |

Read account token balance

70 |
71 | 72 | 79 | 82 | 83 | 84 |
85 | 86 |

Transfer/Approve token balance

87 |
88 | 89 | 96 | 103 | 104 | 105 | 106 |
107 | 108 |
109 | ); 110 | } 111 | } 112 | 113 | 114 | export default ERC20TokenUI; 115 | -------------------------------------------------------------------------------- /test/abstract/erc20tokenspec.js: -------------------------------------------------------------------------------- 1 | 2 | const ERC20Receiver = require('Embark/contracts/ERC20Receiver'); 3 | 4 | exports.config = { 5 | contracts : { 6 | "ERC20Receiver": { 7 | } 8 | } 9 | } 10 | 11 | exports.Test = (ERC20Token) => { 12 | describe("ERC20Token", function() { 13 | 14 | var accounts; 15 | before(function(done) { 16 | web3.eth.getAccounts().then(function (res) { 17 | accounts = res; 18 | done(); 19 | }); 20 | }); 21 | 22 | it("should transfer 1 token", async function() { 23 | let initialBalance0 = await ERC20Token.methods.balanceOf(accounts[0]).call(); 24 | let initialBalance1 = await ERC20Token.methods.balanceOf(accounts[1]).call(); 25 | await ERC20Token.methods.transfer(accounts[1],1).send({from: accounts[0]}); 26 | let result0 = await ERC20Token.methods.balanceOf(accounts[0]).call(); 27 | let result1 = await ERC20Token.methods.balanceOf(accounts[1]).call(); 28 | 29 | assert.equal(result0, +initialBalance0-1, "account 0 balance unexpected"); 30 | assert.equal(result1, +initialBalance1+1, "account 1 balance unexpected"); 31 | }); 32 | 33 | it("should set approved amount", async function() { 34 | await ERC20Token.methods.approve(accounts[2],10000000).send({from: accounts[0]}); 35 | let result = await ERC20Token.methods.allowance(accounts[0], accounts[2]).call(); 36 | assert.equal(result, 10000000); 37 | }); 38 | 39 | it("should consume allowance amount", async function() { 40 | let initialAllowance = await ERC20Token.methods.allowance(accounts[0], accounts[2]).call(); 41 | await ERC20Token.methods.transferFrom(accounts[0], accounts[0],1).send({from: accounts[2]}); 42 | let result = await ERC20Token.methods.allowance(accounts[0], accounts[2]).call(); 43 | 44 | assert.equal(result, +initialAllowance-1); 45 | }); 46 | 47 | it("should transfer approved amount", async function() { 48 | let initialBalance0 = await ERC20Token.methods.balanceOf(accounts[0]).call(); 49 | let initialBalance1 = await ERC20Token.methods.balanceOf(accounts[1]).call(); 50 | await ERC20Token.methods.transferFrom(accounts[0], accounts[1],1).send({from: accounts[2]}); 51 | let result0 = await ERC20Token.methods.balanceOf(accounts[0]).call(); 52 | let result1 = await ERC20Token.methods.balanceOf(accounts[1]).call(); 53 | 54 | assert.equal(result0, +initialBalance0-1); 55 | assert.equal(result1, +initialBalance1+1); 56 | }); 57 | 58 | 59 | it("should unset approved amount", async function() { 60 | await ERC20Token.methods.approve(accounts[2],0).send({from: accounts[0]}); 61 | let result = await ERC20Token.methods.allowance(accounts[0], accounts[2]).call(); 62 | assert.equal(result, 0); 63 | }); 64 | 65 | it("should deposit approved amount to contract ERC20Receiver", async function() { 66 | //ERC20Receiver = await ERC20Receiver.deploy().send(); 67 | //console.log(ERC20Receiver.address); 68 | await ERC20Token.methods.approve(ERC20Receiver.address, 10).send({from: accounts[0]}); 69 | await ERC20Receiver.methods.depositToken(ERC20Token.address, 10).send({from: accounts[0]}); 70 | let result = await ERC20Receiver.methods.tokenBalanceOf(ERC20Token.address, accounts[0]).call(); 71 | assert.equal(result, 10, "ERC20Receiver.tokenBalanceOf("+ERC20Token.address+","+accounts[0]+") wrong"); 72 | }); 73 | 74 | it("should witdraw approved amount from contract ERC20Receiver", async function() { 75 | let tokenBalance = await ERC20Receiver.methods.tokenBalanceOf(ERC20Token.address, accounts[0]).call(); 76 | await ERC20Receiver.methods.withdrawToken(ERC20Token.address, tokenBalance).send({from: accounts[0]}); 77 | tokenBalance = await ERC20Receiver.methods.tokenBalanceOf(ERC20Token.address, accounts[0]).call(); 78 | assert.equal(tokenBalance, 0, "ERC20Receiver.tokenBalanceOf("+ERC20Token.address+","+accounts[0]+") wrong"); 79 | }); 80 | 81 | //TODO: include checks for expected events fired 82 | 83 | 84 | }); 85 | } -------------------------------------------------------------------------------- /contracts/gasrelay/GasRelay.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.5.0 <0.7.0; 2 | 3 | /** 4 | * @title GasRelay as EIP-1077 5 | * @author Ricardo Guilherme Schmidt (Status Research & Development GmbH) 6 | * @notice gas abstraction interface 7 | */ 8 | contract GasRelay { 9 | 10 | bytes4 internal constant MSG_EXECUTE_GASRELAY_PREFIX = bytes4( 11 | keccak256("executeGasRelay(uint256,bytes,uint256,uint256,address,address)") 12 | ); 13 | 14 | constructor() internal {} 15 | 16 | /** 17 | * @notice executes `_execData` with current `nonce()` and pays `msg.sender` the gas used in specified `_gasToken`. 18 | * @param _execData execution data (anything) 19 | * @param _gasPrice price in `_gasToken` paid back to `msg.sender` per gas unit used 20 | * @param _gasLimit maximum gas of this transacton 21 | * @param _gasToken token being used for paying `msg.sender`, if address(0), ether is used 22 | * @param _signature rsv concatenated ethereum signed message signatures required 23 | */ 24 | function executeGasRelay( 25 | bytes calldata _execData, 26 | uint256 _gasPrice, 27 | uint256 _gasLimit, 28 | address _gasToken, 29 | bytes calldata _signature 30 | ) 31 | external; 32 | 33 | function lastNonce() public view returns (uint nonce); 34 | 35 | /** 36 | * @notice gets ERC191 signing Hash of execute gas relay message 37 | * @param _nonce current account nonce 38 | * @param _execData execution data (anything) 39 | * @param _gasPrice price in `_gasToken` paid back to `_gasRelayer` per gas unit used 40 | * @param _gasLimit maximum gas of this transacton 41 | * @param _gasToken token being used for paying `_gasRelayer` 42 | * @param _gasRelayer beneficiary of gas refund 43 | * @return executeGasRelayERC191Hash the message to be signed 44 | */ 45 | function executeGasRelayERC191Msg( 46 | uint256 _nonce, 47 | bytes memory _execData, 48 | uint256 _gasPrice, 49 | uint256 _gasLimit, 50 | address _gasToken, 51 | address _gasRelayer 52 | ) 53 | public 54 | view 55 | returns (bytes memory) 56 | { 57 | return abi.encodePacked( 58 | toERC191SignedMessage( 59 | address(this), 60 | executeGasRelayMsg( 61 | _nonce, 62 | _execData, 63 | _gasPrice, 64 | _gasLimit, 65 | _gasToken, 66 | _gasRelayer 67 | ) 68 | ) 69 | ); 70 | } 71 | 72 | /** 73 | * @notice get message for executeGasRelay function. 74 | * @param _nonce current account nonce 75 | * @param _execData execution data (anything) 76 | * @param _gasPrice price in `_gasToken` paid back to `_gasRelayer` per gas unit used 77 | * @param _gasLimit maximum gas of this transacton 78 | * @param _gasToken token being used for paying `_gasRelayer` 79 | * @param _gasRelayer beneficiary of gas refund 80 | * @return executeGasRelayMsg the appended message 81 | */ 82 | function executeGasRelayMsg( 83 | uint256 _nonce, 84 | bytes memory _execData, 85 | uint256 _gasPrice, 86 | uint256 _gasLimit, 87 | address _gasToken, 88 | address _gasRelayer 89 | ) 90 | public 91 | pure 92 | returns (bytes memory) 93 | { 94 | return abi.encodePacked( 95 | _getChainID(), 96 | MSG_EXECUTE_GASRELAY_PREFIX, 97 | _nonce, 98 | _execData, 99 | _gasPrice, 100 | _gasLimit, 101 | _gasToken, 102 | _gasRelayer 103 | ); 104 | } 105 | 106 | /** 107 | * @notice get network identification where this contract is running 108 | */ 109 | function _getChainID() internal pure returns (uint256) { 110 | uint256 id = 1; 111 | assembly { 112 | // id := chainid() 113 | } 114 | return id; 115 | } 116 | 117 | /** 118 | * @dev Returns an ERC191 Signed Message, created from a `hash`. This 119 | * replicates the behavior of the 120 | * https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign[`eth_signTypedData`] 121 | * JSON-RPC method. 122 | * 123 | * See {recover}. 124 | */ 125 | function toERC191SignedMessage(address _validator, bytes memory data) internal pure returns (bytes32) { 126 | return keccak256(abi.encodePacked(byte(0x19), byte(0x0), _validator, data)); 127 | } 128 | 129 | } 130 | -------------------------------------------------------------------------------- /contracts/cryptography/ECDSA.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.5.0 <0.7.0; 2 | 3 | /** 4 | * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations. 5 | * 6 | * These functions can be used to verify that a message was signed by the holder 7 | * of the private keys of a given address. 8 | */ 9 | library ECDSA { 10 | /** 11 | * @dev Returns the address that signed a hashed message (`hash`) with 12 | * `signature`. This address can then be used for verification purposes. 13 | * 14 | * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures: 15 | * this function rejects them by requiring the `s` value to be in the lower 16 | * half order, and the `v` value to be either 27 or 28. 17 | * 18 | * NOTE: This call _does not revert_ if the signature is invalid, or 19 | * if the signer is otherwise unable to be retrieved. In those scenarios, 20 | * the zero address is returned. 21 | * 22 | * IMPORTANT: `hash` _must_ be the result of a hash operation for the 23 | * verification to be secure: it is possible to craft signatures that 24 | * recover to arbitrary addresses for non-hashed data. A safe way to ensure 25 | * this is by receiving a hash of the original message (which may otherwise 26 | * be too long), and then calling {toEthSignedMessageHash} on it. 27 | */ 28 | function recover(bytes32 hash, bytes memory signature) internal pure returns (address) { 29 | // Check the signature length 30 | if (signature.length != 65) { 31 | return (address(0)); 32 | } 33 | 34 | // Divide the signature in r, s and v variables 35 | bytes32 r; 36 | bytes32 s; 37 | uint8 v; 38 | 39 | // ecrecover takes the signature parameters, and the only way to get them 40 | // currently is to use assembly. 41 | // solhint-disable-next-line no-inline-assembly 42 | assembly { 43 | r := mload(add(signature, 0x20)) 44 | s := mload(add(signature, 0x40)) 45 | v := byte(0, mload(add(signature, 0x60))) 46 | } 47 | 48 | // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature 49 | // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines 50 | // the valid range for s in (281): 0 < s < secp256k1n ÷ 2 + 1, and for v in (282): v ∈ {27, 28}. Most 51 | // signatures from current libraries generate a unique signature with an s-value in the lower half order. 52 | // 53 | // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value 54 | // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or 55 | // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept 56 | // these malleable signatures as well. 57 | if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) { 58 | return address(0); 59 | } 60 | 61 | if (v != 27 && v != 28) { 62 | return address(0); 63 | } 64 | 65 | // If the signature is valid (and not malleable), return the signer address 66 | return ecrecover(hash, v, r, s); 67 | } 68 | 69 | /** 70 | * @dev Returns an Ethereum Signed Message, created from a `hash`. This 71 | * replicates the behavior of the 72 | * https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign[`personal_sign`] 73 | * JSON-RPC method. 74 | * 75 | * See {recover}. 76 | */ 77 | function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) { 78 | // 32 is the length in bytes of hash, 79 | // enforced by the type signature above 80 | //return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)); 81 | return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)); 82 | } 83 | 84 | /** 85 | * @dev Returns an ERC191 Signed Message, created from a `hash`. This 86 | * replicates the behavior of the 87 | * https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign[`eth_signTypedData`] 88 | * JSON-RPC method. 89 | * 90 | * See {recover}. 91 | */ 92 | function toERC191SignedMessage(address _validator, bytes memory data) internal pure returns (bytes32) { 93 | return keccak256(abi.encodePacked(byte(0x19), byte(0x0), _validator, data)); 94 | } 95 | 96 | function toERC191SignedMessage(byte version, bytes memory versionData, bytes memory data) internal pure returns (bytes32) { 97 | return keccak256(abi.encodePacked(byte(0x19), version, versionData, data)); 98 | } 99 | /** 100 | * @notice Transform public key to address 101 | * @param _publicKey secp256k1 public key 102 | **/ 103 | function toAddress(bytes memory _publicKey) internal pure returns (address payable) { 104 | return address(uint160(uint256(keccak256(_publicKey)))); 105 | } 106 | 107 | 108 | } 109 | 110 | 111 | -------------------------------------------------------------------------------- /test/minimetoken.js: -------------------------------------------------------------------------------- 1 | const utils = require('../utils/testUtils') 2 | 3 | const MiniMeToken = require('Embark/contracts/MiniMeToken'); 4 | const ERC20TokenSpec = require('./abstract/erc20tokenspec'); 5 | const ControlledSpec = require('./abstract/controlled'); 6 | 7 | config({ 8 | contracts: { 9 | "MiniMeTokenFactory": { 10 | }, 11 | "MiniMeToken": { 12 | "args": [ 13 | "$MiniMeTokenFactory", 14 | utils.zeroAddress, 15 | 0, 16 | "TestMiniMeToken", 17 | 18, 18 | "TST", 19 | true 20 | ] 21 | }, 22 | ...ERC20TokenSpec.config.contracts 23 | } 24 | }); 25 | 26 | 27 | contract("MiniMeToken", function() { 28 | this.timeout(0); 29 | var accounts; 30 | before(function(done) { 31 | web3.eth.getAccounts().then(function (res) { 32 | accounts = res; 33 | done(); 34 | }); 35 | }); 36 | var miniMeTokenClone; 37 | const b = []; 38 | 39 | it('should generate tokens for address 1', async () => { 40 | await MiniMeToken.methods.generateTokens(accounts[1], 10).send(); 41 | assert.equal(await MiniMeToken.methods.totalSupply().call(), 10); 42 | assert.equal(await MiniMeToken.methods.balanceOf(accounts[1]).call(), 10); 43 | b[0] = await web3.eth.getBlockNumber(); 44 | }); 45 | 46 | it('should transfer tokens from address 1 to address 3', async () => { 47 | await MiniMeToken.methods.transfer(accounts[3], 1).send({from: accounts[1]}); 48 | assert.equal(await MiniMeToken.methods.totalSupply().call(), 10); 49 | assert.equal(await MiniMeToken.methods.balanceOf(accounts[1]).call(), 9); 50 | assert.equal(await MiniMeToken.methods.balanceOf(accounts[3]).call(), 1); 51 | b[1] = await web3.eth.getBlockNumber(); 52 | }); 53 | 54 | it('should destroy 3 tokens from 1 and 1 from 2', async () => { 55 | await MiniMeToken.methods.destroyTokens(accounts[1], 3).send({ from: accounts[0] }); 56 | assert.equal(await MiniMeToken.methods.totalSupply().call(), 7); 57 | assert.equal(await MiniMeToken.methods.balanceOf(accounts[1]).call(), 6); 58 | b[2] = await web3.eth.getBlockNumber(); 59 | }); 60 | 61 | 62 | it('should create the clone token', async () => { 63 | const miniMeTokenCloneTx = await MiniMeToken.methods.createCloneToken( 64 | 'Clone Token 1', 65 | 18, 66 | 'MMTc', 67 | 0, 68 | true).send({ from: accounts[0]}); 69 | let addr = miniMeTokenCloneTx.events.NewCloneToken.returnValues[0]; 70 | miniMeTokenClone = new web3.eth.Contract(MiniMeToken._jsonInterface, addr); 71 | 72 | b[3] = await web3.eth.getBlockNumber(); 73 | 74 | assert.equal(await miniMeTokenClone.methods.parentToken().call(), MiniMeToken.address); 75 | assert.equal(await miniMeTokenClone.methods.parentSnapShotBlock().call(), b[3]); 76 | assert.equal(await miniMeTokenClone.methods.totalSupply().call(), 7); 77 | assert.equal(await MiniMeToken.methods.balanceOf(accounts[1]).call(), 6); 78 | 79 | assert.equal(await miniMeTokenClone.methods.totalSupplyAt(b[2]).call(), 7); 80 | assert.equal(await miniMeTokenClone.methods.balanceOfAt(accounts[3], b[2]).call(), 1); 81 | }); 82 | 83 | it('should move tokens in the clone token from 2 to 3', async () => { 84 | 85 | await miniMeTokenClone.methods.transfer(accounts[2], 4).send({ from: accounts[1], gas: 1000000 }); 86 | b[4] = await web3.eth.getBlockNumber(); 87 | 88 | assert.equal(await MiniMeToken.methods.balanceOfAt(accounts[1], b[3]).call(), 6); 89 | assert.equal(await MiniMeToken.methods.balanceOfAt(accounts[2], b[3]).call(), 0); 90 | assert.equal(await miniMeTokenClone.methods.totalSupply().call(), 7); 91 | assert.equal(await miniMeTokenClone.methods.balanceOf(accounts[1]).call(), 2); 92 | assert.equal(await miniMeTokenClone.methods.balanceOf(accounts[2]).call(), 4); 93 | assert.equal(await miniMeTokenClone.methods.balanceOfAt(accounts[1], b[3]).call(), 6); 94 | assert.equal(await miniMeTokenClone.methods.balanceOfAt(accounts[2], b[3]).call(), 0); 95 | assert.equal(await miniMeTokenClone.methods.balanceOfAt(accounts[1], b[2]).call(), 6); 96 | assert.equal(await miniMeTokenClone.methods.balanceOfAt(accounts[2], b[2]).call(), 0); 97 | assert.equal(await miniMeTokenClone.methods.totalSupplyAt(b[3]).call(), 7); 98 | assert.equal(await miniMeTokenClone.methods.totalSupplyAt(b[2]).call(), 7); 99 | }); 100 | 101 | it('should create tokens in the child token', async () => { 102 | await miniMeTokenClone.methods.generateTokens(accounts[1], 10).send({ from: accounts[0], gas: 1000000}); 103 | assert.equal(await miniMeTokenClone.methods.totalSupply().call(), 17); 104 | assert.equal(await miniMeTokenClone.methods.balanceOf(accounts[1]).call(), 12); 105 | assert.equal(await miniMeTokenClone.methods.balanceOf(accounts[2]).call(), 4); 106 | }); 107 | 108 | 109 | it("should mint balances for ERC20TokenSpec", async function() { 110 | let initialBalance = 7 * 10 ^ 18; 111 | for(i=0;i=0.5.0 <0.7.0; 2 | 3 | import "./Account.sol"; 4 | import "./ERC725.sol"; 5 | import "../cryptography/ECDSA.sol"; 6 | import "../common/Controlled.sol"; 7 | 8 | /** 9 | * @author Ricardo Guilherme Schmidt (Status Research & Development GmbH) 10 | * @notice Defines an account which can be setup by a owner address (multisig contract), recovered by a recover address (a sort of secret multisig contract), and execute actions from a list of addresses (authorized contracts, extensions, etc) 11 | */ 12 | contract Identity is ERC725, Account, Controlled { 13 | string internal constant ERR_BAD_PARAMETER = "Bad parameter"; 14 | string internal constant ERR_UNAUTHORIZED = "Unauthorized"; 15 | 16 | 17 | mapping(bytes32 => bytes) store; 18 | 19 | constructor(address _controller) public Controlled(_controller) { 20 | 21 | } 22 | 23 | /** 24 | * @notice Add public data to account. Can only be called by management. ERC725 interface. 25 | * @param _key identifier 26 | * @param _value data 27 | */ 28 | function setData(bytes32 _key, bytes calldata _value) 29 | external 30 | onlyController 31 | { 32 | store[_key] = _value; 33 | emit DataChanged(_key, _value); 34 | } 35 | 36 | /** 37 | * @notice ERC725 universal execute interface 38 | * @param _execData data to be executed in this contract 39 | * @return success status and return data 40 | */ 41 | function execute( 42 | bytes calldata _execData 43 | ) 44 | external 45 | onlyController 46 | returns (bool success, bytes memory returndata) 47 | { 48 | (success, returndata) = address(this).delegatecall(_execData); 49 | } 50 | 51 | /** 52 | * @notice calls another contract 53 | * @param _to destination of call 54 | * @param _value call ether value (in wei) 55 | * @param _data call data 56 | * @return internal transaction status and returned data 57 | */ 58 | function call( 59 | address _to, 60 | uint256 _value, 61 | bytes calldata _data 62 | ) 63 | external 64 | onlyController 65 | returns(bool success, bytes memory returndata) 66 | { 67 | (success, returndata) = _call(_to, _value, _data); 68 | } 69 | 70 | /** 71 | * @notice Approves `_to` spending ERC20 `_baseToken` a total of `_value` and calls `_to` with `_data`. Useful for a better UX on ERC20 token use, and avoid race conditions. 72 | * @param _baseToken ERC20 token being approved to spend 73 | * @param _to Destination of contract accepting this ERC20 token payments through approve 74 | * @param _value amount of ERC20 being approved 75 | * @param _data abi encoded calldata to be executed in `_to` after approval. 76 | * @return internal transaction status and returned data 77 | */ 78 | function approveAndCall( 79 | address _baseToken, 80 | address _to, 81 | uint256 _value, 82 | bytes calldata _data 83 | ) 84 | external 85 | onlyController 86 | returns(bool success, bytes memory returndata) 87 | { 88 | (success, returndata) = _approveAndCall(_baseToken, _to, _value, _data); 89 | } 90 | 91 | /** 92 | * @notice creates new contract based on input `_code` and transfer `_value` ETH to this instance 93 | * @param _value amount ether in wei to sent to deployed address at its initialization 94 | * @param _code contract code 95 | * @return created contract address 96 | */ 97 | function create( 98 | uint256 _value, 99 | bytes calldata _code 100 | ) 101 | external 102 | onlyController 103 | returns(address createdContract) 104 | { 105 | (createdContract) = _create(_value, _code); 106 | require(isContract(createdContract), ERR_CREATE_FAILED); 107 | } 108 | 109 | /** 110 | * @notice creates deterministic address contract using on input `_code` and transfer `_value` ETH to this instance 111 | * @param _value amount ether in wei to sent to deployed address at its initialization 112 | * @param _code contract code 113 | * @param _salt changes the resulting address 114 | * @return created contract address 115 | */ 116 | function create2( 117 | uint256 _value, 118 | bytes calldata _code, 119 | bytes32 _salt 120 | ) 121 | external 122 | onlyController 123 | returns(address createdContract) 124 | { 125 | (createdContract) = _create2(_value, _code, _salt); 126 | } 127 | 128 | /** 129 | * @notice Reads data set in this account. ERC725 interface. 130 | * @param _key identifier 131 | * @return data 132 | */ 133 | function getData(bytes32 _key) 134 | external 135 | view 136 | returns (bytes memory _value) 137 | { 138 | return store[_key]; 139 | } 140 | 141 | /** 142 | * @notice checks if owner signed `_data`. ERC1271 interface. 143 | * @param _data Data signed 144 | * @param _signature owner's signature(s) of data 145 | */ 146 | function isValidSignature( 147 | bytes memory _data, 148 | bytes memory _signature 149 | ) 150 | public 151 | view 152 | returns (bytes4 magicValue) 153 | { 154 | if(isContract(controller)){ 155 | return Signer(controller).isValidSignature(_data, _signature); 156 | } else { 157 | return controller == ECDSA.recover(ECDSA.toERC191SignedMessage(address(this), _data), _signature) ? MAGICVALUE : bytes4(0xffffffff); 158 | } 159 | } 160 | } -------------------------------------------------------------------------------- /test/multimerkleproof.spec.js: -------------------------------------------------------------------------------- 1 | const { MerkleTree } = require('../utils/merkleTree.js'); 2 | const MerkleMultiProofWrapper = require('Embark/contracts/MerkleMultiProofWrapper'); 3 | 4 | const merkleTreeSize = 4096; 5 | const leafsSize = 10; 6 | const fuzzyProofChecks = 10; 7 | 8 | const elementsA = Array.from({length: merkleTreeSize}, (v,k) => ""+k); 9 | const merkleTreeA = new MerkleTree(elementsA); 10 | const leafsA = merkleTreeA.getElements(Array.from({length: leafsSize}, (v,k) => ""+k)); 11 | const proofA = merkleTreeA.getMultiProof(leafsA); 12 | const flagsA = merkleTreeA.getProofFlags(leafsA, proofA); 13 | 14 | const elementsB = Array.from({length: merkleTreeSize}, (v,k) => ""+k*2); 15 | const merkleTreeB = new MerkleTree(elementsB); 16 | const leafsB = merkleTreeB.getElements(Array.from({length: leafsSize}, (v,k) => ""+k*2)) 17 | const proofB = merkleTreeB.getMultiProof(leafsB); 18 | const flagsB = merkleTreeB.getProofFlags(leafsB, proofB); 19 | 20 | config({ 21 | contracts: { 22 | deploy: { 23 | "MerkleMultiProofWrapper": { 24 | 25 | } 26 | } 27 | } 28 | }, (_err, web3_accounts) => { 29 | accounts = web3_accounts; 30 | }); 31 | 32 | contract('MultiMerkleProof', function () { 33 | 34 | describe('calculateMultiMerkleRoot', function () { 35 | it('display cost of deploy MerkleMultiProofWrapper', async function () { 36 | await MerkleMultiProofWrapper.methods.foo().send(); 37 | }); 38 | 39 | it('calculate merkle root from leafs, proofs and flags', async function () { 40 | const result = await MerkleMultiProofWrapper.methods.calculateMultiMerkleRoot( 41 | leafsA, 42 | proofA, 43 | flagsA 44 | ).call() 45 | const result2 = await MerkleMultiProofWrapper.methods.calculateMultiMerkleRoot( 46 | leafsB, 47 | proofB, 48 | flagsB 49 | ).call() 50 | assert(result == merkleTreeA.getHexRoot()); 51 | assert(result2 == merkleTreeB.getHexRoot()); 52 | }); 53 | 54 | it('calculate wrong merkle root from wrong proofs and flags', async function () { 55 | var invalid = false; 56 | try { 57 | invalid = merkleTreeA.getHexRoot() != await MerkleMultiProofWrapper.methods.calculateMultiMerkleRoot( 58 | leafsA, 59 | proofB, 60 | flagsB 61 | ).call() 62 | } catch(e) { 63 | invalid = true; 64 | } 65 | assert(invalid); 66 | 67 | try { 68 | invalid = merkleTreeA.getHexRoot() != await MerkleMultiProofWrapper.methods.calculateMultiMerkleRoot( 69 | leafsA, 70 | proofB, 71 | flagsA 72 | ).call() 73 | } catch(e) { 74 | invalid = true; 75 | } 76 | assert(invalid); 77 | 78 | try { 79 | invalid = merkleTreeB.getHexRoot() != await MerkleMultiProofWrapper.methods.calculateMultiMerkleRoot( 80 | leafsB, 81 | proofA, 82 | flagsB 83 | ).call(); 84 | } catch(e) { 85 | invalid = true; 86 | } 87 | 88 | assert(invalid); 89 | try { 90 | invalid = merkleTreeB.getHexRoot() != await MerkleMultiProofWrapper.methods.calculateMultiMerkleRoot( 91 | leafsB, 92 | proofA, 93 | flagsA 94 | ).call() 95 | } catch(e) { 96 | invalid = true; 97 | } 98 | assert(invalid); 99 | }); 100 | 101 | it(`cost of calculate Tree A root for ${leafsA.length} leafs using ${proofA.length} proofs and ${flagsA.length} flags`, async function () { 102 | await MerkleMultiProofWrapper.methods.calculateMultiMerkleRoot( 103 | leafsA, 104 | proofA, 105 | flagsA, 106 | ).send() 107 | }); 108 | 109 | it(`cost of calculate Tree B root for ${leafsB.length} leafs using ${proofB.length} proofs and ${flagsB.length} flags`, async function () { 110 | await MerkleMultiProofWrapper.methods.calculateMultiMerkleRoot( 111 | leafsB, 112 | proofB, 113 | flagsB, 114 | ).send() 115 | }); 116 | 117 | it(`calculate ${fuzzyProofChecks} merkle root from leafs, proofs and flags (fuzzy)`, async function () { 118 | this.timeout(500*fuzzyProofChecks); 119 | for(let j = 0; j < fuzzyProofChecks; j++){ 120 | const leafsFuzzy = merkleTreeA.getElements( 121 | Array.from({length: leafsSize}, () => elementsA[Math.floor(Math.random()*elementsA.length)] ).filter((value, index, self) => self.indexOf(value) === index) 122 | ); 123 | const proofFuzzy = merkleTreeA.getMultiProof(leafsFuzzy); 124 | const flagsFuzzy = merkleTreeA.getProofFlags(leafsFuzzy, proofFuzzy); 125 | const result = await MerkleMultiProofWrapper.methods.calculateMultiMerkleRoot( 126 | leafsFuzzy, 127 | proofFuzzy, 128 | flagsFuzzy 129 | ).call(); 130 | assert(result == merkleTreeA.getHexRoot()); 131 | } 132 | }); 133 | 134 | it(`calculate ${fuzzyProofChecks} wrong merkle root from wrong proofs and flags (fuzzy)`, async function () { 135 | this.timeout(500*fuzzyProofChecks); 136 | for(let j = 0; j < fuzzyProofChecks; j++){ 137 | const leafsFuzzy = merkleTreeB.getElements( 138 | Array.from({length: leafsSize}, () => elementsB[Math.floor(Math.random()*elementsB.length)] ).filter((value, index, self) => self.indexOf(value) === index) 139 | ); 140 | const proofFuzzy = merkleTreeB.getMultiProof(leafsFuzzy); 141 | const flagsFuzzy = merkleTreeB.getProofFlags(leafsFuzzy, proofFuzzy); 142 | var invalid = false; 143 | try { 144 | invalid = merkleTreeA.getHexRoot() != await MerkleMultiProofWrapper.methods.calculateMultiMerkleRoot( 145 | leafsFuzzy, 146 | proofFuzzy, 147 | flagsFuzzy 148 | ).call(); 149 | } catch(e) { 150 | invalid = true; 151 | } 152 | assert(invalid); 153 | } 154 | }); 155 | }); 156 | }); 157 | -------------------------------------------------------------------------------- /utils/merkleTree.js: -------------------------------------------------------------------------------- 1 | const { keccak256, bufferToHex } = require('ethereumjs-util'); 2 | 3 | class MerkleTree { 4 | constructor(elements) { 5 | // Filter empty strings and hash elements 6 | this.elements = elements.filter(el => el).map(el => keccak256(el)); 7 | 8 | // Deduplicate elements 9 | this.elements = this.bufDedup(this.elements); 10 | // Sort elements 11 | this.elements.sort(Buffer.compare); 12 | // Create layers 13 | this.layers = this.getLayers(this.elements); 14 | } 15 | 16 | getProofFlags(els, proofs) { 17 | let ids = els.map((el) => this.bufIndexOf(el, this.elements)).sort((a,b) => a == b ? 0 : a > b ? 1 : -1); 18 | if (!ids.every((idx) => idx != -1)) { 19 | throw new Error("Element does not exist in Merkle tree"); 20 | } 21 | 22 | const tested = []; 23 | const flags = []; 24 | for (let index = 0; index < this.layers.length; index++) { 25 | const layer = this.layers[index]; 26 | ids = ids.reduce((ids, idx) => { 27 | const skipped = tested.includes(layer[idx]); 28 | if(!skipped) { 29 | const pairElement = this.getPairElement(idx, layer); 30 | const proofUsed = proofs.includes(layer[idx]) || proofs.includes(pairElement); 31 | pairElement && flags.push(!proofUsed); 32 | tested.push(layer[idx]); 33 | tested.push(pairElement); 34 | } 35 | ids.push(Math.floor(idx / 2)); 36 | return ids; 37 | }, []) 38 | } 39 | return flags; 40 | } 41 | 42 | getElements(els) { 43 | let ids = els.map((el) => this.bufIndexOf(el, this.elements)); 44 | if (!ids.every((idx) => idx != -1)) { 45 | throw new Error("Element does not exist in Merkle tree"); 46 | } 47 | 48 | const elsH = []; 49 | for (let j = 0; j < ids.length; j++) { 50 | elsH.push(this.layers[0][ids[j]]); 51 | } 52 | return this.bufDedup(elsH).sort(Buffer.compare); 53 | } 54 | 55 | getMultiProof(els) { 56 | let ids = els.map((el) => this.bufIndexOf(el, this.elements)).sort((a,b) => a == b ? 0 : a > b ? 1 : -1); 57 | if (!ids.every((idx) => idx != -1)) { 58 | throw new Error("Element does not exist in Merkle tree"); 59 | } 60 | 61 | const hashes = []; 62 | const proof = []; 63 | var nextIds = []; 64 | 65 | for (let index = 0; index < this.layers.length; index++) { 66 | const layer = this.layers[index]; 67 | for (let j = 0; j < ids.length; j++) { 68 | const idx = ids[j]; 69 | const pairElement = this.getPairElement(idx, layer); 70 | 71 | hashes.push(layer[idx]); 72 | pairElement && proof.push(pairElement) 73 | 74 | nextIds.push(Math.floor(idx / 2)); 75 | } 76 | ids = nextIds.filter((value, index, self) => self.indexOf(value) === index); 77 | nextIds = []; 78 | } 79 | return proof.filter((value) => !hashes.includes(value)); 80 | } 81 | 82 | getHexMultiProof(els) { 83 | const multiProof = this.getMultiProof(els); 84 | 85 | return this.bufArrToHex(multiProof); 86 | } 87 | 88 | getLayers(elements) { 89 | if (elements.length == 0) { 90 | return [[""]]; 91 | } 92 | 93 | const layers = []; 94 | layers.push(elements); 95 | 96 | // Get next layer until we reach the root 97 | while (layers[layers.length - 1].length > 1) { 98 | layers.push(this.getNextLayer(layers[layers.length - 1])); 99 | } 100 | 101 | return layers; 102 | } 103 | 104 | getNextLayer(elements) { 105 | return elements.reduce((layer, el, idx, arr) => { 106 | if (idx % 2 === 0) { 107 | // Hash the current element with its pair element 108 | layer.push(this.combinedHash(el, arr[idx + 1])); 109 | } 110 | 111 | return layer; 112 | }, []); 113 | } 114 | 115 | combinedHash(first, second) { 116 | if (!first) { return second; } 117 | if (!second) { return first; } 118 | 119 | return keccak256(this.sortAndConcat(first, second)); 120 | } 121 | 122 | getRoot() { 123 | return this.layers[this.layers.length - 1][0]; 124 | } 125 | 126 | getHexRoot() { 127 | return bufferToHex(this.getRoot()); 128 | } 129 | 130 | getProof(el) { 131 | let idx = this.bufIndexOf(el, this.elements); 132 | 133 | if (idx === -1) { 134 | throw new Error("Element does not exist in Merkle tree"); 135 | } 136 | 137 | return this.layers.reduce((proof, layer) => { 138 | const pairElement = this.getPairElement(idx, layer); 139 | 140 | if (pairElement) { 141 | proof.push(pairElement); 142 | } 143 | 144 | idx = Math.floor(idx / 2); 145 | 146 | return proof; 147 | }, []); 148 | } 149 | 150 | 151 | getHexProof(el) { 152 | const proof = this.getProof(el); 153 | 154 | return this.bufArrToHex(proof); 155 | } 156 | 157 | getPairElement(idx, layer) { 158 | const pairIdx = idx % 2 === 0 ? idx + 1 : idx - 1; 159 | 160 | if (pairIdx < layer.length) { 161 | return layer[pairIdx]; 162 | } else { 163 | return null; 164 | } 165 | } 166 | 167 | bufIndexOf(el, arr) { 168 | let hash; 169 | 170 | // Convert element to 32 byte hash if it is not one already 171 | if (el.length !== 32 || !Buffer.isBuffer(el)) { 172 | hash = keccak256(el); 173 | } else { 174 | hash = el; 175 | } 176 | 177 | for (let i = 0; i < arr.length; i++) { 178 | if (hash.equals(arr[i])) { 179 | return i; 180 | } 181 | } 182 | 183 | return -1; 184 | } 185 | 186 | bufDedup(elements) { 187 | return elements.filter((el, idx) => { 188 | return this.bufIndexOf(el, elements) === idx; 189 | }); 190 | } 191 | 192 | bufArrToHex(arr) { 193 | if (arr.some(el => !Buffer.isBuffer(el))) { 194 | throw new Error("Array is not an array of buffers"); 195 | } 196 | 197 | return arr.map(el => '0x' + el.toString('hex')); 198 | } 199 | 200 | sortAndConcat(...args) { 201 | return Buffer.concat([...args].sort(Buffer.compare)); 202 | } 203 | } 204 | 205 | exports.MerkleTree = MerkleTree; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | CC0 1.0 Universal 2 | 3 | Statement of Purpose 4 | 5 | The laws of most jurisdictions throughout the world automatically confer 6 | exclusive Copyright and Related Rights (defined below) upon the creator and 7 | subsequent owner(s) (each and all, an "owner") of an original work of 8 | authorship and/or a database (each, a "Work"). 9 | 10 | Certain owners wish to permanently relinquish those rights to a Work for the 11 | purpose of contributing to a commons of creative, cultural and scientific 12 | works ("Commons") that the public can reliably and without fear of later 13 | claims of infringement build upon, modify, incorporate in other works, reuse 14 | and redistribute as freely as possible in any form whatsoever and for any 15 | purposes, including without limitation commercial purposes. These owners may 16 | contribute to the Commons to promote the ideal of a free culture and the 17 | further production of creative, cultural and scientific works, or to gain 18 | reputation or greater distribution for their Work in part through the use and 19 | efforts of others. 20 | 21 | For these and/or other purposes and motivations, and without any expectation 22 | of additional consideration or compensation, the person associating CC0 with a 23 | Work (the "Affirmer"), to the extent that he or she is an owner of Copyright 24 | and Related Rights in the Work, voluntarily elects to apply CC0 to the Work 25 | and publicly distribute the Work under its terms, with knowledge of his or her 26 | Copyright and Related Rights in the Work and the meaning and intended legal 27 | effect of CC0 on those rights. 28 | 29 | 1. Copyright and Related Rights. A Work made available under CC0 may be 30 | protected by copyright and related or neighboring rights ("Copyright and 31 | Related Rights"). Copyright and Related Rights include, but are not limited 32 | to, the following: 33 | 34 | i. the right to reproduce, adapt, distribute, perform, display, communicate, 35 | and translate a Work; 36 | 37 | ii. moral rights retained by the original author(s) and/or performer(s); 38 | 39 | iii. publicity and privacy rights pertaining to a person's image or likeness 40 | depicted in a Work; 41 | 42 | iv. rights protecting against unfair competition in regards to a Work, 43 | subject to the limitations in paragraph 4(a), below; 44 | 45 | v. rights protecting the extraction, dissemination, use and reuse of data in 46 | a Work; 47 | 48 | vi. database rights (such as those arising under Directive 96/9/EC of the 49 | European Parliament and of the Council of 11 March 1996 on the legal 50 | protection of databases, and under any national implementation thereof, 51 | including any amended or successor version of such directive); and 52 | 53 | vii. other similar, equivalent or corresponding rights throughout the world 54 | based on applicable law or treaty, and any national implementations thereof. 55 | 56 | 2. Waiver. To the greatest extent permitted by, but not in contravention of, 57 | applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and 58 | unconditionally waives, abandons, and surrenders all of Affirmer's Copyright 59 | and Related Rights and associated claims and causes of action, whether now 60 | known or unknown (including existing as well as future claims and causes of 61 | action), in the Work (i) in all territories worldwide, (ii) for the maximum 62 | duration provided by applicable law or treaty (including future time 63 | extensions), (iii) in any current or future medium and for any number of 64 | copies, and (iv) for any purpose whatsoever, including without limitation 65 | commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes 66 | the Waiver for the benefit of each member of the public at large and to the 67 | detriment of Affirmer's heirs and successors, fully intending that such Waiver 68 | shall not be subject to revocation, rescission, cancellation, termination, or 69 | any other legal or equitable action to disrupt the quiet enjoyment of the Work 70 | by the public as contemplated by Affirmer's express Statement of Purpose. 71 | 72 | 3. Public License Fallback. Should any part of the Waiver for any reason be 73 | judged legally invalid or ineffective under applicable law, then the Waiver 74 | shall be preserved to the maximum extent permitted taking into account 75 | Affirmer's express Statement of Purpose. In addition, to the extent the Waiver 76 | is so judged Affirmer hereby grants to each affected person a royalty-free, 77 | non transferable, non sublicensable, non exclusive, irrevocable and 78 | unconditional license to exercise Affirmer's Copyright and Related Rights in 79 | the Work (i) in all territories worldwide, (ii) for the maximum duration 80 | provided by applicable law or treaty (including future time extensions), (iii) 81 | in any current or future medium and for any number of copies, and (iv) for any 82 | purpose whatsoever, including without limitation commercial, advertising or 83 | promotional purposes (the "License"). The License shall be deemed effective as 84 | of the date CC0 was applied by Affirmer to the Work. Should any part of the 85 | License for any reason be judged legally invalid or ineffective under 86 | applicable law, such partial invalidity or ineffectiveness shall not 87 | invalidate the remainder of the License, and in such case Affirmer hereby 88 | affirms that he or she will not (i) exercise any of his or her remaining 89 | Copyright and Related Rights in the Work or (ii) assert any associated claims 90 | and causes of action with respect to the Work, in either case contrary to 91 | Affirmer's express Statement of Purpose. 92 | 93 | 4. Limitations and Disclaimers. 94 | 95 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 96 | surrendered, licensed or otherwise affected by this document. 97 | 98 | b. Affirmer offers the Work as-is and makes no representations or warranties 99 | of any kind concerning the Work, express, implied, statutory or otherwise, 100 | including without limitation warranties of title, merchantability, fitness 101 | for a particular purpose, non infringement, or the absence of latent or 102 | other defects, accuracy, or the present or absence of errors, whether or not 103 | discoverable, all to the greatest extent permissible under applicable law. 104 | 105 | c. Affirmer disclaims responsibility for clearing rights of other persons 106 | that may apply to the Work or any use thereof, including without limitation 107 | any person's Copyright and Related Rights in the Work. Further, Affirmer 108 | disclaims responsibility for obtaining any necessary consents, permissions 109 | or other rights required for any use of the Work. 110 | 111 | d. Affirmer understands and acknowledges that Creative Commons is not a 112 | party to this document and has no duty or obligation with respect to this 113 | CC0 or use of the Work. 114 | 115 | For more information, please see 116 | 117 | -------------------------------------------------------------------------------- /contracts/account/Account.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.5.0 <0.7.0; 2 | 3 | import "./Caller.sol"; 4 | import "./ERC20Caller.sol"; 5 | import "./Creator.sol"; 6 | import "./Signer.sol"; 7 | 8 | /** 9 | * @notice Abstract account logic. Tracks nonce and fire events for the internal functions of call, approveAndCall, create and create2. Default function is payable with no events/logic. 10 | */ 11 | abstract contract Account is Caller, ERC20Caller, Creator, Signer { 12 | 13 | event Executed(uint256 nonce, bool success, bytes returndata); 14 | event Deployed(uint256 nonce, bool success, address returnaddress); 15 | 16 | uint256 public nonce; 17 | 18 | /** 19 | * @dev Does nothing, accepts ETH value (payable) 20 | */ 21 | fallback() external payable { 22 | 23 | } 24 | 25 | /** 26 | * @notice calls another contract 27 | * @param _to destination of call 28 | * @param _data call data 29 | * @return internal transaction status and returned data 30 | */ 31 | function _call( 32 | address _to, 33 | bytes memory _data 34 | ) 35 | internal 36 | returns (bool success, bytes memory returndata) 37 | { 38 | uint256 _nonce = nonce++; // Important: Must be incremented always BEFORE external call 39 | (success, returndata) = super._call(_to, _data); //external call 40 | emit Executed(_nonce, success, returndata); 41 | } 42 | 43 | 44 | /** 45 | * @notice calls another contract 46 | * @param _to destination of call 47 | * @param _value call ether value (in wei) 48 | * @param _data call data 49 | * @return internal transaction status and returned data 50 | */ 51 | function _call( 52 | address _to, 53 | uint256 _value, 54 | bytes memory _data 55 | ) 56 | internal 57 | returns (bool success, bytes memory returndata) 58 | { 59 | uint256 _nonce = nonce++; // Important: Must be incremented always BEFORE external call 60 | (success, returndata) = super._call(_to, _value, _data); //external call 61 | emit Executed(_nonce, success, returndata); 62 | } 63 | 64 | /** 65 | * @notice calls another contract with limited gas 66 | * @param _to destination of call 67 | * @param _value call ether value (in wei) 68 | * @param _data call data 69 | * @param _gas gas to limit the internal transaction 70 | * @return internal transaction status and returned data 71 | */ 72 | function _call( 73 | address _to, 74 | uint256 _value, 75 | bytes memory _data, 76 | uint256 _gas 77 | ) 78 | internal 79 | returns (bool success, bytes memory returndata) 80 | { 81 | uint256 _nonce = nonce++; // Important: Must be incremented always BEFORE external call 82 | (success, returndata) = super._call(_to, _value, _data, _gas); //external call 83 | emit Executed(_nonce, success, returndata); 84 | } 85 | 86 | /** 87 | * @notice calls another contract with limited gas 88 | * @param _to destination of call 89 | * @param _data call data 90 | * @param _gas gas to limit the internal transaction 91 | * @return internal transaction status and returned data 92 | */ 93 | function _call( 94 | address _to, 95 | bytes memory _data, 96 | uint256 _gas 97 | ) 98 | internal 99 | returns (bool success, bytes memory returndata) 100 | { 101 | uint256 _nonce = nonce++; // Important: Must be incremented always BEFORE external call 102 | (success, returndata) = super._call(_to, _data, _gas); //external call 103 | emit Executed(_nonce, success, returndata); 104 | } 105 | 106 | /** 107 | * @notice creates new contract based on input `_code` and transfer `_value` ETH to this instance 108 | * @param _value amount ether in wei to sent to deployed address at its initialization 109 | * @param _code contract code 110 | * @return creation success status and created contract address 111 | */ 112 | function _create( 113 | uint _value, 114 | bytes memory _code 115 | ) 116 | internal 117 | returns (address payable createdContract) 118 | { 119 | uint256 _nonce = nonce++; // Important: Must be incremented always BEFORE deploy 120 | createdContract = super._create(_value, _code); 121 | bool success = isContract(createdContract); 122 | emit Deployed(_nonce, success, createdContract); 123 | } 124 | 125 | /** 126 | * @notice creates deterministic address contract using on input `_code` and transfer `_value` ETH to this instance 127 | * @param _value amount ether in wei to sent to deployed address at its initialization 128 | * @param _code contract code 129 | * @param _salt changes the resulting address 130 | * @return creation success status and created contract address 131 | */ 132 | function _create2( 133 | uint _value, 134 | bytes memory _code, 135 | bytes32 _salt 136 | ) 137 | internal 138 | returns (address payable createdContract) 139 | { 140 | uint256 _nonce = nonce++; // Important: Must be incremented always BEFORE deploy 141 | createdContract = super._create2(_value, _code, _salt); 142 | require(isContract(createdContract), ERR_CREATE_FAILED); 143 | emit Deployed(_nonce, true, createdContract); 144 | } 145 | 146 | /** 147 | * @notice Approves `_to` spending ERC20 `_baseToken` a total of `_value` and calls `_to` with `_data`. Useful for a better UX on ERC20 token use, and avoid race conditions. 148 | * @param _baseToken ERC20 token being approved to spend 149 | * @param _to Destination of contract accepting this ERC20 token payments through approve 150 | * @param _value amount of ERC20 being approved 151 | * @param _data abi encoded calldata to be executed in `_to` after approval. 152 | * @return internal transaction status and returned data 153 | */ 154 | function _approveAndCall( 155 | address _baseToken, 156 | address _to, 157 | uint256 _value, 158 | bytes memory _data 159 | ) 160 | internal 161 | returns (bool success, bytes memory returndata) 162 | { 163 | uint256 _nonce = nonce++; // Important: Must be incremented always BEFORE external call 164 | (success, returndata) = super._approveAndCall(_baseToken, _to, _value, _data); 165 | emit Executed(_nonce, success, returndata); 166 | } 167 | 168 | /** 169 | * @notice Approves `_to` spending ERC20 `_baseToken` a total of `_value` and calls `_to` with `_data`. Useful for a better UX on ERC20 token use, and avoid race conditions. 170 | * @param _baseToken ERC20 token being approved to spend 171 | * @param _to Destination of contract accepting this ERC20 token payments through approve 172 | * @param _value amount of ERC20 being approved 173 | * @param _data abi encoded calldata to be executed in `_to` after approval. 174 | * @param _gas gas to limit the internal transaction 175 | * @return internal transaction status and returned data 176 | */ 177 | function _approveAndCall( 178 | address _baseToken, 179 | address _to, 180 | uint256 _value, 181 | bytes memory _data, 182 | uint256 _gas 183 | ) 184 | internal 185 | returns (bool success, bytes memory returndata) 186 | { 187 | uint256 _nonce = nonce++; // Important: Must be incremented always BEFORE external call 188 | (success, returndata) = super._approveAndCall(_baseToken, _to, _value, _data, _gas); 189 | emit Executed(_nonce, success, returndata); 190 | } 191 | 192 | } -------------------------------------------------------------------------------- /contracts/account/UserAccount.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.5.0 <0.7.0; 2 | 3 | import "./UserAccountInterface.sol"; 4 | import "./AccountGasAbstract.sol"; 5 | import "../cryptography/ECDSA.sol"; 6 | 7 | /** 8 | * @author Ricardo Guilherme Schmidt (Status Research & Development GmbH) 9 | * @notice Defines an account which can be setup by a owner address (multisig contract), recovered by a recover address (a sort of secret multisig contract), and execute actions from a list of addresses (authorized contracts, extensions, etc) 10 | */ 11 | contract UserAccount is UserAccountInterface, AccountGasAbstract { 12 | string internal constant ERR_BAD_PARAMETER = "Bad parameter"; 13 | string internal constant ERR_UNAUTHORIZED = "Unauthorized"; 14 | string internal constant ERR_CREATE_FAILED = "Contract creation failed"; 15 | 16 | address public owner; 17 | address public recovery; 18 | address public actor; 19 | 20 | constructor(address _owner, address _actor, address _recovery) public { 21 | require(_owner != address(0), ERR_BAD_PARAMETER); 22 | owner = _owner; 23 | actor = _actor; 24 | recovery = _recovery; 25 | } 26 | 27 | /** 28 | * Allow only calls from itself or directly from owner 29 | */ 30 | modifier management { 31 | require(msg.sender == address(this) || msg.sender == address(owner), ERR_UNAUTHORIZED); 32 | _; 33 | } 34 | 35 | /** 36 | * @dev Allow calls only from actor to external addresses, or any call from owner 37 | */ 38 | modifier authorizedAction(address _to) { 39 | require( 40 | msg.sender == address(this) || ( // self is always authorized 41 | msg.sender == actor && //actor is authorized 42 | _to != address(this) //only for external address 43 | ) || msg.sender == address(owner), //owner is always authorized 44 | ERR_UNAUTHORIZED); 45 | _; 46 | } 47 | 48 | /** 49 | * @notice Defines recovery address. Can only be called by management when no recovery is set, or by recovery. 50 | * @param _recovery address of recovery contract 51 | */ 52 | function setRecovery(address _recovery) 53 | external 54 | { 55 | require( 56 | ( 57 | recovery == address(0) && (msg.sender == address(this) || msg.sender == owner) 58 | ) || msg.sender == recovery, 59 | ERR_UNAUTHORIZED 60 | ); 61 | recovery = _recovery; 62 | } 63 | 64 | /** 65 | * @notice Defines the new owner and disable actor. Can only be called by recovery. 66 | * @param newOwner an ERC1271 contract or externally owned account 67 | */ 68 | function recover(address newOwner) 69 | external 70 | { 71 | require(msg.sender == recovery, ERR_UNAUTHORIZED); 72 | require(newOwner != address(0), ERR_BAD_PARAMETER); 73 | owner = newOwner; 74 | actor = address(0); 75 | } 76 | 77 | /** 78 | * @notice Changes actor contract 79 | * @param _actor Contract which can call actions from this contract 80 | */ 81 | function setActor(address _actor) 82 | external 83 | management 84 | { 85 | actor = _actor; 86 | } 87 | 88 | /** 89 | * @notice Replace owner address. 90 | * @param newOwner address of externally owned account or ERC1271 contract to control this account 91 | */ 92 | function changeOwner(address newOwner) 93 | external 94 | management 95 | { 96 | require(newOwner != address(0), ERR_BAD_PARAMETER); 97 | owner = newOwner; 98 | } 99 | 100 | /** 101 | * @notice 102 | * @param _execData data to be executed in this contract 103 | * @return success status and return data 104 | */ 105 | function execute( 106 | bytes calldata _execData 107 | ) 108 | external 109 | management 110 | returns (bool success, bytes memory returndata) 111 | { 112 | (success, returndata) = address(this).call(_execData); 113 | } 114 | 115 | /** 116 | * @notice calls another contract 117 | * @param _to destination of call 118 | * @param _value call ether value (in wei) 119 | * @param _data call data 120 | * @return internal transaction status and returned data 121 | */ 122 | function call( 123 | address _to, 124 | uint256 _value, 125 | bytes calldata _data 126 | ) 127 | external 128 | authorizedAction(_to) 129 | returns(bool success, bytes memory returndata) 130 | { 131 | (success, returndata) = _call(_to, _value, _data); 132 | } 133 | 134 | /** 135 | * @notice Approves `_to` spending ERC20 `_baseToken` a total of `_value` and calls `_to` with `_data`. Useful for a better UX on ERC20 token use, and avoid race conditions. 136 | * @param _baseToken ERC20 token being approved to spend 137 | * @param _to Destination of contract accepting this ERC20 token payments through approve 138 | * @param _value amount of ERC20 being approved 139 | * @param _data abi encoded calldata to be executed in `_to` after approval. 140 | * @return internal transaction status and returned data 141 | */ 142 | function approveAndCall( 143 | address _baseToken, 144 | address _to, 145 | uint256 _value, 146 | bytes calldata _data 147 | ) 148 | external 149 | authorizedAction(_to) 150 | returns(bool success, bytes memory returndata) 151 | { 152 | (success, returndata) = _approveAndCall(_baseToken, _to, _value, _data); 153 | } 154 | 155 | /** 156 | * @notice creates new contract based on input `_code` and transfer `_value` ETH to this instance 157 | * @param _value amount ether in wei to sent to deployed address at its initialization 158 | * @param _code contract code 159 | * @return created contract address 160 | */ 161 | function create( 162 | uint256 _value, 163 | bytes calldata _code 164 | ) 165 | external 166 | authorizedAction(address(0)) 167 | returns(address createdContract) 168 | { 169 | (createdContract) = _create(_value, _code); 170 | require(isContract(createdContract), ERR_CREATE_FAILED); 171 | } 172 | 173 | /** 174 | * @notice creates deterministic address contract using on input `_code` and transfer `_value` ETH to this instance 175 | * @param _value amount ether in wei to sent to deployed address at its initialization 176 | * @param _code contract code 177 | * @param _salt changes the resulting address 178 | * @return created contract address 179 | */ 180 | function create2( 181 | uint256 _value, 182 | bytes calldata _code, 183 | bytes32 _salt 184 | ) 185 | external 186 | authorizedAction(address(0)) 187 | returns(address createdContract) 188 | { 189 | (createdContract) = _create2(_value, _code, _salt); 190 | require(isContract(createdContract), ERR_CREATE_FAILED); 191 | } 192 | 193 | /** 194 | * @notice checks if owner signed `_data`. ERC1271 interface. 195 | * @param _data Data signed 196 | * @param _signature owner's signature(s) of data 197 | */ 198 | function isValidSignature( 199 | bytes memory _data, 200 | bytes memory _signature 201 | ) 202 | public 203 | view 204 | returns (bytes4 magicValue) 205 | { 206 | if(isContract(owner)){ 207 | return Signer(owner).isValidSignature(_data, _signature); 208 | } else { 209 | return owner == ECDSA.recover(ECDSA.toERC191SignedMessage(address(this), _data), _signature) ? MAGICVALUE : bytes4(0xffffffff); 210 | } 211 | } 212 | } -------------------------------------------------------------------------------- /utils/testUtils.js: -------------------------------------------------------------------------------- 1 | 2 | // This has been tested with the real Ethereum network and Testrpc. 3 | // Copied and edited from: https://gist.github.com/xavierlepretre/d5583222fde52ddfbc58b7cfa0d2d0a9 4 | exports.assertReverts = (contractMethodCall, maxGasAvailable) => { 5 | return new Promise((resolve, reject) => { 6 | try { 7 | resolve(contractMethodCall()) 8 | } catch (error) { 9 | reject(error) 10 | } 11 | }) 12 | .then(tx => { 13 | assert.equal(tx.receipt.gasUsed, maxGasAvailable, "tx successful, the max gas available was not consumed") 14 | }) 15 | .catch(error => { 16 | if ((error + "").indexOf("invalid opcode") < 0 && (error + "").indexOf("out of gas") < 0) { 17 | // Checks if the error is from TestRpc. If it is then ignore it. 18 | // Otherwise relay/throw the error produced by the above assertion. 19 | // Note that no error is thrown when using a real Ethereum network AND the assertion above is true. 20 | throw error 21 | } 22 | }) 23 | } 24 | 25 | exports.listenForEvent = event => new Promise((resolve, reject) => { 26 | event({}, (error, response) => { 27 | if (!error) { 28 | resolve(response.args) 29 | } else { 30 | reject(error) 31 | } 32 | event.stopWatching() 33 | }) 34 | }); 35 | 36 | exports.eventValues = (receipt, eventName) => { 37 | if(receipt.events[eventName]) 38 | return receipt.events[eventName].returnValues; 39 | } 40 | 41 | exports.addressToBytes32 = (address) => { 42 | const stringed = "0000000000000000000000000000000000000000000000000000000000000000" + address.slice(2); 43 | return "0x" + stringed.substring(stringed.length - 64, stringed.length); 44 | } 45 | 46 | 47 | // OpenZeppelin's expectThrow helper - 48 | // Source: https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/test/helpers/expectThrow.js 49 | exports.expectThrow = async promise => { 50 | try { 51 | await promise; 52 | } catch (error) { 53 | // TODO: Check jump destination to destinguish between a throw 54 | // and an actual invalid jump. 55 | const invalidOpcode = error.message.search('invalid opcode') >= 0; 56 | // TODO: When we contract A calls contract B, and B throws, instead 57 | // of an 'invalid jump', we get an 'out of gas' error. How do 58 | // we distinguish this from an actual out of gas event? (The 59 | // testrpc log actually show an 'invalid jump' event.) 60 | const outOfGas = error.message.search('out of gas') >= 0; 61 | const revert = error.message.search('revert') >= 0; 62 | assert( 63 | invalidOpcode || outOfGas || revert, 64 | 'Expected throw, got \'' + error + '\' instead', 65 | ); 66 | return; 67 | } 68 | assert.fail('Expected throw not received'); 69 | }; 70 | 71 | 72 | 73 | exports.assertJump = (error) => { 74 | assert(error.message.search('revert') > -1, 'Revert should happen'); 75 | } 76 | 77 | 78 | var callbackToResolve = function (resolve, reject) { 79 | return function (error, value) { 80 | if (error) { 81 | reject(error); 82 | } else { 83 | resolve(value); 84 | } 85 | }; 86 | }; 87 | 88 | exports.promisify = (func) => 89 | (...args) => { 90 | return new Promise((resolve, reject) => { 91 | const callback = (err, data) => err ? reject(err) : resolve(data); 92 | func.apply(this, [...args, callback]); 93 | }); 94 | } 95 | 96 | 97 | // This has been tested with the real Ethereum network and Testrpc. 98 | // Copied and edited from: https://gist.github.com/xavierlepretre/d5583222fde52ddfbc58b7cfa0d2d0a9 99 | exports.assertReverts = (contractMethodCall, maxGasAvailable) => { 100 | return new Promise((resolve, reject) => { 101 | try { 102 | resolve(contractMethodCall()) 103 | } catch (error) { 104 | reject(error) 105 | } 106 | }) 107 | .then(tx => { 108 | assert.equal(tx.receipt.gasUsed, maxGasAvailable, "tx successful, the max gas available was not consumed") 109 | }) 110 | .catch(error => { 111 | if ((error + "").indexOf("invalid opcode") < 0 && (error + "").indexOf("out of gas") < 0) { 112 | // Checks if the error is from TestRpc. If it is then ignore it. 113 | // Otherwise relay/throw the error produced by the above assertion. 114 | // Note that no error is thrown when using a real Ethereum network AND the assertion above is true. 115 | throw error 116 | } 117 | }) 118 | } 119 | 120 | exports.listenForEvent = event => new Promise((resolve, reject) => { 121 | event({}, (error, response) => { 122 | if (!error) { 123 | resolve(response.args) 124 | } else { 125 | reject(error) 126 | } 127 | event.stopWatching() 128 | }) 129 | }); 130 | 131 | exports.eventValues = (receipt, eventName) => { 132 | if(receipt.events[eventName]) 133 | return receipt.events[eventName].returnValues; 134 | } 135 | 136 | exports.addressToBytes32 = (address) => { 137 | const stringed = "0000000000000000000000000000000000000000000000000000000000000000" + address.slice(2); 138 | return "0x" + stringed.substring(stringed.length - 64, stringed.length); 139 | } 140 | 141 | 142 | // OpenZeppelin's expectThrow helper - 143 | // Source: https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/test/helpers/expectThrow.js 144 | exports.expectThrow = async promise => { 145 | try { 146 | await promise; 147 | } catch (error) { 148 | // TODO: Check jump destination to destinguish between a throw 149 | // and an actual invalid jump. 150 | const invalidOpcode = error.message.search('invalid opcode') >= 0; 151 | // TODO: When we contract A calls contract B, and B throws, instead 152 | // of an 'invalid jump', we get an 'out of gas' error. How do 153 | // we distinguish this from an actual out of gas event? (The 154 | // testrpc log actually show an 'invalid jump' event.) 155 | const outOfGas = error.message.search('out of gas') >= 0; 156 | const revert = error.message.search('revert') >= 0; 157 | assert( 158 | invalidOpcode || outOfGas || revert, 159 | 'Expected throw, got \'' + error + '\' instead', 160 | ); 161 | return; 162 | } 163 | assert.fail('Expected throw not received'); 164 | }; 165 | 166 | exports.assertJump = (error) => { 167 | assert(error.message.search('revert') > -1, 'Revert should happen'); 168 | } 169 | 170 | var callbackToResolve = function (resolve, reject) { 171 | return function (error, value) { 172 | if (error) { 173 | reject(error); 174 | } else { 175 | resolve(value); 176 | } 177 | }; 178 | }; 179 | 180 | exports.promisify = (func) => 181 | (...args) => { 182 | return new Promise((resolve, reject) => { 183 | const callback = (err, data) => err ? reject(err) : resolve(data); 184 | func.apply(this, [...args, callback]); 185 | }); 186 | } 187 | 188 | exports.zeroAddress = '0x0000000000000000000000000000000000000000'; 189 | exports.zeroBytes32 = "0x0000000000000000000000000000000000000000000000000000000000000000"; 190 | exports.timeUnits = { 191 | seconds: 1, 192 | minutes: 60, 193 | hours: 60 * 60, 194 | days: 24 * 60 * 60, 195 | weeks: 7 * 24 * 60 * 60, 196 | years: 365 * 24 * 60 * 60 197 | } 198 | 199 | exports.ensureException = function(error) { 200 | assert(isException(error), error.toString()); 201 | }; 202 | 203 | function isException(error) { 204 | let strError = error.toString(); 205 | return strError.includes('invalid opcode') || strError.includes('invalid JUMP') || strError.includes('revert'); 206 | } 207 | 208 | exports.increaseTime = async (amount) => { 209 | return new Promise(function(resolve, reject) { 210 | web3.currentProvider.sendAsync( 211 | { 212 | jsonrpc: '2.0', 213 | method: 'evm_increaseTime', 214 | params: [+amount], 215 | id: new Date().getSeconds() 216 | }, 217 | async (error) => { 218 | if (error) { 219 | console.log(error); 220 | return reject(err); 221 | } 222 | await web3.currentProvider.sendAsync( 223 | { 224 | jsonrpc: '2.0', 225 | method: 'evm_mine', 226 | params: [], 227 | id: new Date().getSeconds() 228 | }, (error) => { 229 | if (error) { 230 | console.log(error); 231 | return reject(err); 232 | } 233 | resolve(); 234 | } 235 | ) 236 | } 237 | ) 238 | }); 239 | } 240 | -------------------------------------------------------------------------------- /contracts/account/MultisigRecovery.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.5.0 <0.7.0; 2 | 3 | import "../cryptography/MerkleMultiProof.sol"; 4 | import "../cryptography/ECDSA.sol"; 5 | import "../account/Signer.sol"; 6 | import "../ens/ENS.sol"; 7 | import "../ens/ResolverInterface.sol"; 8 | 9 | /** 10 | * @notice Select privately other accounts that will allow the execution of actions (ERC-2429 compilant) 11 | * @author Ricardo Guilherme Schmidt (Status Research & Development GmbH) 12 | * Vitalik Buterin (Ethereum Foundation) 13 | */ 14 | contract MultisigRecovery { 15 | //Needed for EIP-1271 check 16 | bytes4 constant internal EIP1271_MAGICVALUE = 0x20c13b0b; 17 | //threshold constant 18 | uint256 public constant THRESHOLD = 100 * 10^18; 19 | //Needed for ENS leafs 20 | ENS public ens; 21 | //flag for used recoveries (user need to define a different publicHash every execute) 22 | mapping(bytes32 => bool) public used; 23 | //just used offchain 24 | mapping(address => uint256) public nonce; 25 | //flag approvals 26 | mapping(bytes32 => mapping(bytes32 => bool)) public approved; 27 | //storage for pending setup 28 | mapping(address => RecoverySet) public pending; 29 | //storage for active recovery 30 | mapping(address => RecoverySet) public active; 31 | 32 | struct RecoverySet { 33 | bytes32 publicHash; 34 | uint256 setupDelay; 35 | uint256 timestamp; 36 | } 37 | 38 | event SetupRequested(address indexed who, uint256 activation); 39 | event Activated(address indexed who); 40 | event Approved(bytes32 indexed approveHash, bytes32 leaf); 41 | event Execution(address indexed who, bool success); 42 | 43 | /** 44 | * @param _ens Address of ENS Registry 45 | **/ 46 | constructor( 47 | ENS _ens 48 | ) 49 | public 50 | { 51 | ens = _ens; 52 | } 53 | 54 | /** 55 | * @notice Configure recovery parameters of `msg.sender`. `emit Activated(msg.sender)` if there was no previous setup, or `emit SetupRequested(msg.sender, now()+setupDelay)` when reconfiguring. 56 | * @param _publicHash Double hash of executeHash 57 | * @param _setupDelay Delay for changes being active 58 | */ 59 | function setup( 60 | bytes32 _publicHash, 61 | uint256 _setupDelay 62 | ) 63 | external 64 | { 65 | require(!used[_publicHash], "_publicHash already used"); 66 | used[_publicHash] = true; 67 | address who = msg.sender; 68 | RecoverySet memory newSet = RecoverySet(_publicHash, _setupDelay, block.timestamp); 69 | if(active[who].publicHash == bytes32(0)){ 70 | active[who] = newSet; 71 | emit Activated(who); 72 | } else { 73 | require(pending[who].timestamp == 0 || pending[who].timestamp + active[who].setupDelay < block.timestamp, "Waiting activation"); 74 | pending[who] = newSet; 75 | emit SetupRequested(who, block.timestamp + active[who].setupDelay); 76 | } 77 | 78 | } 79 | 80 | /** 81 | * @notice Activate a pending setup of recovery parameters 82 | * @param _who address whih ready setupDelay. 83 | */ 84 | function activate(address _who) 85 | external 86 | { 87 | RecoverySet storage pendingUser = pending[_who]; 88 | require(pendingUser.timestamp > 0, "No pending setup"); 89 | require(pendingUser.timestamp + active[_who].setupDelay > block.timestamp, "Waiting delay"); 90 | active[_who] = pendingUser; 91 | delete pending[_who]; 92 | emit Activated(_who); 93 | } 94 | 95 | /** 96 | * @notice Cancels a pending setup to change the recovery parameters 97 | */ 98 | function cancelSetup() 99 | external 100 | { 101 | RecoverySet storage pendingUser = pending[msg.sender]; 102 | require(pendingUser.timestamp > 0, "No pending setup"); 103 | require(pendingUser.timestamp + active[msg.sender].setupDelay < block.timestamp, "Waiting activation"); 104 | delete pending[msg.sender]; 105 | emit SetupRequested(msg.sender, 0); 106 | } 107 | 108 | /** 109 | * @notice Approves a recovery. This method is important for when the address is an contract and dont implements EIP1271. 110 | * @param _approveHash Hash of the recovery call 111 | * @param _ensNode if present, the _proof is checked against _ensNode. 112 | */ 113 | function approve( 114 | bytes32 _approveHash, 115 | bytes32 _ensNode 116 | ) 117 | external 118 | { 119 | approveExecution(msg.sender, _approveHash, _ensNode); 120 | } 121 | 122 | /** 123 | * @notice Approve a recovery using an ethereum signed message 124 | * @param _signer address of _signature processor. if _signer is a contract, must be ERC1271. 125 | * @param _approveHash Hash of the recovery call 126 | * @param _ensNode if present, the _proof is checked against _ensName. 127 | * @param _signature ERC191 signature 128 | */ 129 | function approvePreSigned( 130 | address _signer, 131 | bytes32 _approveHash, 132 | bytes32 _ensNode, 133 | bytes calldata _signature 134 | ) 135 | external 136 | { 137 | bytes32 signingHash = ECDSA.toERC191SignedMessage(address(this), abi.encodePacked(_getChainID(), _approveHash, _ensNode)); 138 | require(_signer != address(0), "Invalid signer"); 139 | require( 140 | ( 141 | isContract(_signer) && Signer(_signer).isValidSignature(abi.encodePacked(signingHash), _signature) == EIP1271_MAGICVALUE 142 | ) || ECDSA.recover(signingHash, _signature) == _signer, 143 | "Invalid signature"); 144 | approveExecution(_signer, _approveHash, _ensNode); 145 | } 146 | 147 | /** 148 | * @notice executes an approved transaction revaling publicHash hash, friends addresses and set new recovery parameters 149 | * @param _executeHash Seed of `peerHash` 150 | * @param _merkleRoot Revealed merkle root 151 | * @param _calldest Address will be called 152 | * @param _calldata Data to be sent 153 | * @param _leafData Pre approved leafhashes and it's weights as siblings ordered by descending weight 154 | * @param _proofs parents proofs 155 | * @param _proofFlags indexes that select the hashing pairs from calldata `_leafHashes` and `_proofs` and from memory `hashes` 156 | */ 157 | function execute( 158 | bytes32 _executeHash, 159 | bytes32 _merkleRoot, 160 | address _calldest, 161 | bytes calldata _calldata, 162 | bytes32[] calldata _leafData, 163 | bytes32[] calldata _proofs, 164 | bool[] calldata _proofFlags 165 | ) 166 | external 167 | { 168 | bytes32 publicHash = active[_calldest].publicHash; 169 | require(publicHash != bytes32(0), "Recovery not set"); 170 | bytes32 partialReveal = keccak256(abi.encodePacked(_executeHash)); 171 | require( 172 | publicHash == keccak256( 173 | abi.encodePacked(partialReveal, keccak256(abi.encodePacked(_merkleRoot))) 174 | ), "merkleRoot or executeHash is not valid" 175 | ); 176 | bytes32 callHash = keccak256( 177 | abi.encodePacked(partialReveal, _calldest, _calldata) 178 | ); 179 | uint256 weight = 0; 180 | bytes32[] memory leafs = new bytes32[](_proofs.length/2); 181 | for(uint256 i = 0; weight < THRESHOLD; i++){ 182 | bytes32 leafHash = _leafData[i*2]; 183 | uint256 leafWeight = uint256(_leafData[(i*2)+1]); 184 | leafs[i] = keccak256(abi.encodePacked(leafHash,leafWeight)); 185 | bytes32 approveHash = keccak256( 186 | abi.encodePacked( 187 | leafHash, 188 | callHash 189 | )); 190 | require(approved[leafHash][approveHash], "Hash not approved"); 191 | weight += leafWeight; 192 | delete approved[leafHash][approveHash]; 193 | } 194 | require(MerkleMultiProof.verifyMultiProof(_merkleRoot, leafs, _proofs, _proofFlags), "Invalid leafHashes"); 195 | nonce[_calldest]++; 196 | delete active[_calldest]; 197 | delete pending[_calldest]; 198 | bool success; 199 | (success, ) = _calldest.call(_calldata); 200 | emit Execution(_calldest, success); 201 | } 202 | 203 | /** 204 | * @param _signer address of approval signer 205 | * @param _approveHash Hash of the recovery call 206 | * @param _ensNode if present, the _proof is checked against _ensNode. 207 | */ 208 | function approveExecution( 209 | address _signer, 210 | bytes32 _approveHash, 211 | bytes32 _ensNode 212 | ) 213 | internal 214 | { 215 | bool isENS = _ensNode != bytes32(0); 216 | require( 217 | !isENS || ( 218 | _signer == ResolverInterface(ens.resolver(_ensNode)).addr(_ensNode) 219 | ), 220 | "Invalid ENS entry" 221 | ); 222 | bytes32 leaf = keccak256(isENS ? abi.encodePacked(byte(0x01), _ensNode) : abi.encodePacked(byte(0x00), bytes32(uint256(_signer)))); 223 | approved[leaf][_approveHash] = true; 224 | emit Approved(_approveHash, leaf); 225 | } 226 | 227 | /** 228 | * @dev Internal function to determine if an address is a contract 229 | * @param _target The address being queried 230 | * @return True if `_addr` is a contract 231 | */ 232 | function isContract(address _target) internal view returns(bool result) { 233 | assembly { 234 | result := gt(extcodesize(_target), 0) 235 | } 236 | } 237 | 238 | /** 239 | * @notice get network identification where this contract is running 240 | */ 241 | function _getChainID() internal pure returns (uint256) { 242 | uint256 id; 243 | assembly { 244 | //id := chainid() 245 | } 246 | return id; 247 | } 248 | } -------------------------------------------------------------------------------- /contracts/common/BytesLib.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * @title Solidity Bytes Arrays Utils 3 | * @author Gonçalo Sá 4 | * 5 | * @dev Bytes tightly packed arrays utility library for ethereum contracts written in Solidity. 6 | * The library lets you concatenate, slice and type cast bytes arrays both in memory and storage. 7 | */ 8 | pragma solidity >=0.5.0 <0.7.0; 9 | 10 | 11 | library BytesLib { 12 | function concat( 13 | bytes memory _preBytes, 14 | bytes memory _postBytes 15 | ) 16 | internal 17 | pure 18 | returns (bytes memory) 19 | { 20 | bytes memory tempBytes; 21 | 22 | assembly { 23 | // Get a location of some free memory and store it in tempBytes as 24 | // Solidity does for memory variables. 25 | tempBytes := mload(0x40) 26 | 27 | // Store the length of the first bytes array at the beginning of 28 | // the memory for tempBytes. 29 | let length := mload(_preBytes) 30 | mstore(tempBytes, length) 31 | 32 | // Maintain a memory counter for the current write location in the 33 | // temp bytes array by adding the 32 bytes for the array length to 34 | // the starting location. 35 | let mc := add(tempBytes, 0x20) 36 | // Stop copying when the memory counter reaches the length of the 37 | // first bytes array. 38 | let end := add(mc, length) 39 | 40 | for { 41 | // Initialize a copy counter to the start of the _preBytes data, 42 | // 32 bytes into its memory. 43 | let cc := add(_preBytes, 0x20) 44 | } lt(mc, end) { 45 | // Increase both counters by 32 bytes each iteration. 46 | mc := add(mc, 0x20) 47 | cc := add(cc, 0x20) 48 | } { 49 | // Write the _preBytes data into the tempBytes memory 32 bytes 50 | // at a time. 51 | mstore(mc, mload(cc)) 52 | } 53 | 54 | // Add the length of _postBytes to the current length of tempBytes 55 | // and store it as the new length in the first 32 bytes of the 56 | // tempBytes memory. 57 | length := mload(_postBytes) 58 | mstore(tempBytes, add(length, mload(tempBytes))) 59 | 60 | // Move the memory counter back from a multiple of 0x20 to the 61 | // actual end of the _preBytes data. 62 | mc := end 63 | // Stop copying when the memory counter reaches the new combined 64 | // length of the arrays. 65 | end := add(mc, length) 66 | 67 | for { 68 | let cc := add(_postBytes, 0x20) 69 | } lt(mc, end) { 70 | mc := add(mc, 0x20) 71 | cc := add(cc, 0x20) 72 | } { 73 | mstore(mc, mload(cc)) 74 | } 75 | 76 | // Update the free-memory pointer by padding our last write location 77 | // to 32 bytes: add 31 bytes to the end of tempBytes to move to the 78 | // next 32 byte block, then round down to the nearest multiple of 79 | // 32. If the sum of the length of the two arrays is zero then add 80 | // one before rounding down to leave a blank 32 bytes (the length block with 0). 81 | mstore(0x40, and( 82 | add(add(end, iszero(add(length, mload(_preBytes)))), 31), 83 | not(31) // Round down to the nearest 32 bytes. 84 | )) 85 | } 86 | 87 | return tempBytes; 88 | } 89 | 90 | function concatStorage(bytes storage _preBytes, bytes memory _postBytes) internal { 91 | assembly { 92 | // Read the first 32 bytes of _preBytes storage, which is the length 93 | // of the array. (We don't need to use the offset into the slot 94 | // because arrays use the entire slot.) 95 | let fslot := sload(_preBytes_slot) 96 | // Arrays of 31 bytes or less have an even value in their slot, 97 | // while longer arrays have an odd value. The actual length is 98 | // the slot divided by two for odd values, and the lowest order 99 | // byte divided by two for even values. 100 | // If the slot is even, bitwise and the slot with 255 and divide by 101 | // two to get the length. If the slot is odd, bitwise and the slot 102 | // with -1 and divide by two. 103 | let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2) 104 | let mlength := mload(_postBytes) 105 | let newlength := add(slength, mlength) 106 | // slength can contain both the length and contents of the array 107 | // if length < 32 bytes so let's prepare for that 108 | // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage 109 | switch add(lt(slength, 32), lt(newlength, 32)) 110 | case 2 { 111 | // Since the new array still fits in the slot, we just need to 112 | // update the contents of the slot. 113 | // uint256(bytes_storage) = uint256(bytes_storage) + uint256(bytes_memory) + new_length 114 | sstore( 115 | _preBytes_slot, 116 | // all the modifications to the slot are inside this 117 | // next block 118 | add( 119 | // we can just add to the slot contents because the 120 | // bytes we want to change are the LSBs 121 | fslot, 122 | add( 123 | mul( 124 | div( 125 | // load the bytes from memory 126 | mload(add(_postBytes, 0x20)), 127 | // zero all bytes to the right 128 | exp(0x100, sub(32, mlength)) 129 | ), 130 | // and now shift left the number of bytes to 131 | // leave space for the length in the slot 132 | exp(0x100, sub(32, newlength)) 133 | ), 134 | // increase length by the double of the memory 135 | // bytes length 136 | mul(mlength, 2) 137 | ) 138 | ) 139 | ) 140 | } 141 | case 1 { 142 | // The stored value fits in the slot, but the combined value 143 | // will exceed it. 144 | // get the keccak hash to get the contents of the array 145 | mstore(0x0, _preBytes_slot) 146 | let sc := add(keccak256(0x0, 0x20), div(slength, 32)) 147 | 148 | // save new length 149 | sstore(_preBytes_slot, add(mul(newlength, 2), 1)) 150 | 151 | // The contents of the _postBytes array start 32 bytes into 152 | // the structure. Our first read should obtain the `submod` 153 | // bytes that can fit into the unused space in the last word 154 | // of the stored array. To get this, we read 32 bytes starting 155 | // from `submod`, so the data we read overlaps with the array 156 | // contents by `submod` bytes. Masking the lowest-order 157 | // `submod` bytes allows us to add that value directly to the 158 | // stored value. 159 | 160 | let submod := sub(32, slength) 161 | let mc := add(_postBytes, submod) 162 | let end := add(_postBytes, mlength) 163 | let mask := sub(exp(0x100, submod), 1) 164 | 165 | sstore( 166 | sc, 167 | add( 168 | and( 169 | fslot, 170 | 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00 171 | ), 172 | and(mload(mc), mask) 173 | ) 174 | ) 175 | 176 | for { 177 | mc := add(mc, 0x20) 178 | sc := add(sc, 1) 179 | } lt(mc, end) { 180 | sc := add(sc, 1) 181 | mc := add(mc, 0x20) 182 | } { 183 | sstore(sc, mload(mc)) 184 | } 185 | 186 | mask := exp(0x100, sub(mc, end)) 187 | 188 | sstore(sc, mul(div(mload(mc), mask), mask)) 189 | } 190 | default { 191 | // get the keccak hash to get the contents of the array 192 | mstore(0x0, _preBytes_slot) 193 | // Start copying to the last used word of the stored array. 194 | let sc := add(keccak256(0x0, 0x20), div(slength, 32)) 195 | 196 | // save new length 197 | sstore(_preBytes_slot, add(mul(newlength, 2), 1)) 198 | 199 | // Copy over the first `submod` bytes of the new data as in 200 | // case 1 above. 201 | let slengthmod := mod(slength, 32) 202 | let mlengthmod := mod(mlength, 32) 203 | let submod := sub(32, slengthmod) 204 | let mc := add(_postBytes, submod) 205 | let end := add(_postBytes, mlength) 206 | let mask := sub(exp(0x100, submod), 1) 207 | 208 | sstore(sc, add(sload(sc), and(mload(mc), mask))) 209 | 210 | for { 211 | sc := add(sc, 1) 212 | mc := add(mc, 0x20) 213 | } lt(mc, end) { 214 | sc := add(sc, 1) 215 | mc := add(mc, 0x20) 216 | } { 217 | sstore(sc, mload(mc)) 218 | } 219 | 220 | mask := exp(0x100, sub(mc, end)) 221 | 222 | sstore(sc, mul(div(mload(mc), mask), mask)) 223 | } 224 | } 225 | } 226 | 227 | function slice( 228 | bytes memory _bytes, 229 | uint _start, 230 | uint _length 231 | ) 232 | internal 233 | pure 234 | returns (bytes memory) 235 | { 236 | require(_bytes.length >= (_start + _length)); 237 | 238 | bytes memory tempBytes; 239 | 240 | assembly { 241 | switch iszero(_length) 242 | case 0 { 243 | // Get a location of some free memory and store it in tempBytes as 244 | // Solidity does for memory variables. 245 | tempBytes := mload(0x40) 246 | 247 | // The first word of the slice result is potentially a partial 248 | // word read from the original array. To read it, we calculate 249 | // the length of that partial word and start copying that many 250 | // bytes into the array. The first word we copy will start with 251 | // data we don't care about, but the last `lengthmod` bytes will 252 | // land at the beginning of the contents of the new array. When 253 | // we're done copying, we overwrite the full first word with 254 | // the actual length of the slice. 255 | let lengthmod := and(_length, 31) 256 | 257 | // The multiplication in the next line is necessary 258 | // because when slicing multiples of 32 bytes (lengthmod == 0) 259 | // the following copy loop was copying the origin's length 260 | // and then ending prematurely not copying everything it should. 261 | let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod))) 262 | let end := add(mc, _length) 263 | 264 | for { 265 | // The multiplication in the next line has the same exact purpose 266 | // as the one above. 267 | let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start) 268 | } lt(mc, end) { 269 | mc := add(mc, 0x20) 270 | cc := add(cc, 0x20) 271 | } { 272 | mstore(mc, mload(cc)) 273 | } 274 | 275 | mstore(tempBytes, _length) 276 | 277 | //update free-memory pointer 278 | //allocating the array padded to 32 bytes like the compiler does now 279 | mstore(0x40, and(add(mc, 31), not(31))) 280 | } 281 | //if we want a zero-length slice let's just return a zero-length array 282 | default { 283 | tempBytes := mload(0x40) 284 | 285 | mstore(0x40, add(tempBytes, 0x20)) 286 | } 287 | } 288 | 289 | return tempBytes; 290 | } 291 | 292 | function toAddress(bytes memory _bytes, uint _start) internal pure returns (address) { 293 | require(_bytes.length >= (_start + 20)); 294 | address tempAddress; 295 | 296 | assembly { 297 | tempAddress := div(mload(add(add(_bytes, 0x20), _start)), 0x1000000000000000000000000) 298 | } 299 | 300 | return tempAddress; 301 | } 302 | 303 | function toUint8(bytes memory _bytes, uint _start) internal pure returns (uint8) { 304 | require(_bytes.length >= (_start + 1)); 305 | uint8 tempUint; 306 | 307 | assembly { 308 | tempUint := mload(add(add(_bytes, 0x1), _start)) 309 | } 310 | 311 | return tempUint; 312 | } 313 | 314 | function toUint16(bytes memory _bytes, uint _start) internal pure returns (uint16) { 315 | require(_bytes.length >= (_start + 2)); 316 | uint16 tempUint; 317 | 318 | assembly { 319 | tempUint := mload(add(add(_bytes, 0x2), _start)) 320 | } 321 | 322 | return tempUint; 323 | } 324 | 325 | function toUint32(bytes memory _bytes, uint _start) internal pure returns (uint32) { 326 | require(_bytes.length >= (_start + 4)); 327 | uint32 tempUint; 328 | 329 | assembly { 330 | tempUint := mload(add(add(_bytes, 0x4), _start)) 331 | } 332 | 333 | return tempUint; 334 | } 335 | 336 | function toUint64(bytes memory _bytes, uint _start) internal pure returns (uint64) { 337 | require(_bytes.length >= (_start + 8)); 338 | uint64 tempUint; 339 | 340 | assembly { 341 | tempUint := mload(add(add(_bytes, 0x8), _start)) 342 | } 343 | 344 | return tempUint; 345 | } 346 | 347 | function toUint96(bytes memory _bytes, uint _start) internal pure returns (uint96) { 348 | require(_bytes.length >= (_start + 12)); 349 | uint96 tempUint; 350 | 351 | assembly { 352 | tempUint := mload(add(add(_bytes, 0xc), _start)) 353 | } 354 | 355 | return tempUint; 356 | } 357 | 358 | function toUint128(bytes memory _bytes, uint _start) internal pure returns (uint128) { 359 | require(_bytes.length >= (_start + 16)); 360 | uint128 tempUint; 361 | 362 | assembly { 363 | tempUint := mload(add(add(_bytes, 0x10), _start)) 364 | } 365 | 366 | return tempUint; 367 | } 368 | 369 | function toUint(bytes memory _bytes, uint _start) internal pure returns (uint256) { 370 | require(_bytes.length >= (_start + 32)); 371 | uint256 tempUint; 372 | 373 | assembly { 374 | tempUint := mload(add(add(_bytes, 0x20), _start)) 375 | } 376 | 377 | return tempUint; 378 | } 379 | 380 | function toBytes32(bytes memory _bytes, uint _start) internal pure returns (bytes32) { 381 | require(_bytes.length >= (_start + 32)); 382 | bytes32 tempBytes32; 383 | 384 | assembly { 385 | tempBytes32 := mload(add(add(_bytes, 0x20), _start)) 386 | } 387 | 388 | return tempBytes32; 389 | } 390 | 391 | function equal(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bool) { 392 | bool success = true; 393 | 394 | assembly { 395 | let length := mload(_preBytes) 396 | 397 | // if lengths don't match the arrays are not equal 398 | switch eq(length, mload(_postBytes)) 399 | case 1 { 400 | // cb is a circuit breaker in the for loop since there's 401 | // no said feature for inline assembly loops 402 | // cb = 1 - don't breaker 403 | // cb = 0 - break 404 | let cb := 1 405 | 406 | let mc := add(_preBytes, 0x20) 407 | let end := add(mc, length) 408 | 409 | for { 410 | let cc := add(_postBytes, 0x20) 411 | // the next line is the loop condition: 412 | // while(uint(mc < end) + cb == 2) 413 | } eq(add(lt(mc, end), cb), 2) { 414 | mc := add(mc, 0x20) 415 | cc := add(cc, 0x20) 416 | } { 417 | // if any of these checks fails then arrays are not equal 418 | if iszero(eq(mload(mc), mload(cc))) { 419 | // unsuccess: 420 | success := 0 421 | cb := 0 422 | } 423 | } 424 | } 425 | default { 426 | // unsuccess: 427 | success := 0 428 | } 429 | } 430 | 431 | return success; 432 | } 433 | 434 | function equalStorage( 435 | bytes storage _preBytes, 436 | bytes memory _postBytes 437 | ) 438 | internal 439 | view 440 | returns (bool) 441 | { 442 | bool success = true; 443 | 444 | assembly { 445 | // we know _preBytes_offset is 0 446 | let fslot := sload(_preBytes_slot) 447 | // Decode the length of the stored array like in concatStorage(). 448 | let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2) 449 | let mlength := mload(_postBytes) 450 | 451 | // if lengths don't match the arrays are not equal 452 | switch eq(slength, mlength) 453 | case 1 { 454 | // slength can contain both the length and contents of the array 455 | // if length < 32 bytes so let's prepare for that 456 | // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage 457 | if iszero(iszero(slength)) { 458 | switch lt(slength, 32) 459 | case 1 { 460 | // blank the last byte which is the length 461 | fslot := mul(div(fslot, 0x100), 0x100) 462 | 463 | if iszero(eq(fslot, mload(add(_postBytes, 0x20)))) { 464 | // unsuccess: 465 | success := 0 466 | } 467 | } 468 | default { 469 | // cb is a circuit breaker in the for loop since there's 470 | // no said feature for inline assembly loops 471 | // cb = 1 - don't breaker 472 | // cb = 0 - break 473 | let cb := 1 474 | 475 | // get the keccak hash to get the contents of the array 476 | mstore(0x0, _preBytes_slot) 477 | let sc := keccak256(0x0, 0x20) 478 | 479 | let mc := add(_postBytes, 0x20) 480 | let end := add(mc, mlength) 481 | 482 | // the next line is the loop condition: 483 | // while(uint(mc < end) + cb == 2) 484 | for {} eq(add(lt(mc, end), cb), 2) { 485 | sc := add(sc, 1) 486 | mc := add(mc, 0x20) 487 | } { 488 | if iszero(eq(sload(sc), mload(mc))) { 489 | // unsuccess: 490 | success := 0 491 | cb := 0 492 | } 493 | } 494 | } 495 | } 496 | } 497 | default { 498 | // unsuccess: 499 | success := 0 500 | } 501 | } 502 | 503 | return success; 504 | } 505 | } -------------------------------------------------------------------------------- /contracts/token/MiniMeToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.5.0 <0.7.0; 2 | 3 | /* 4 | Copyright 2016, Jordi Baylina 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | */ 19 | /** 20 | * @title MiniMeToken Contract 21 | * @author Jordi Baylina 22 | * @dev This token contract's goal is to make it easy for anyone to clone this 23 | * token using the token distribution at a given block, this will allow DAO's 24 | * and DApps to upgrade their features in a decentralized manner without 25 | * affecting the original token 26 | * @dev It is ERC20 compliant, but still needs to under go further testing. 27 | */ 28 | 29 | import "../common/Controlled.sol"; 30 | import "./TokenController.sol"; 31 | import "./ApproveAndCallFallBack.sol"; 32 | import "./MiniMeTokenFactory.sol"; 33 | 34 | /** 35 | * @dev The actual token contract, the default controller is the msg.sender 36 | * that deploys the contract, so usually this token will be deployed by a 37 | * token controller contract, which Giveth will call a "Campaign" 38 | */ 39 | contract MiniMeToken is Controlled { 40 | 41 | string public name; //The Token's name: e.g. DigixDAO Tokens 42 | uint8 public decimals; //Number of decimals of the smallest unit 43 | string public symbol; //An identifier: e.g. REP 44 | string public version = "MMT_0.1"; //An arbitrary versioning scheme 45 | 46 | /** 47 | * @dev `Checkpoint` is the structure that attaches a block number to a 48 | * given value, the block number attached is the one that last changed the 49 | * value 50 | */ 51 | struct Checkpoint { 52 | 53 | // `fromBlock` is the block number that the value was generated from 54 | uint128 fromBlock; 55 | 56 | // `value` is the amount of tokens at a specific block number 57 | uint128 value; 58 | } 59 | 60 | // `parentToken` is the Token address that was cloned to produce this token; 61 | // it will be 0x0 for a token that was not cloned 62 | MiniMeToken public parentToken; 63 | 64 | // `parentSnapShotBlock` is the block number from the Parent Token that was 65 | // used to determine the initial distribution of the Clone Token 66 | uint public parentSnapShotBlock; 67 | 68 | // `creationBlock` is the block number that the Clone Token was created 69 | uint public creationBlock; 70 | 71 | // `balances` is the map that tracks the balance of each address, in this 72 | // contract when the balance changes the block number that the change 73 | // occurred is also included in the map 74 | mapping (address => Checkpoint[]) balances; 75 | 76 | // `allowed` tracks any extra transfer rights as in all ERC20 tokens 77 | mapping (address => mapping (address => uint256)) allowed; 78 | 79 | // Tracks the history of the `totalSupply` of the token 80 | Checkpoint[] totalSupplyHistory; 81 | 82 | // Flag that determines if the token is transferable or not. 83 | bool public transfersEnabled; 84 | 85 | // The factory used to create new clone tokens 86 | MiniMeTokenFactory public tokenFactory; 87 | 88 | //////////////// 89 | // Constructor 90 | //////////////// 91 | 92 | /** 93 | * @notice Constructor to create a MiniMeToken 94 | * @param _tokenFactory The address of the MiniMeTokenFactory contract that 95 | * will create the Clone token contracts, the token factory needs to be 96 | * deployed first 97 | * @param _parentToken Address of the parent token, set to 0x0 if it is a 98 | * new token 99 | * @param _parentSnapShotBlock Block of the parent token that will 100 | * determine the initial distribution of the clone token, set to 0 if it 101 | * is a new token 102 | * @param _tokenName Name of the new token 103 | * @param _decimalUnits Number of decimals of the new token 104 | * @param _tokenSymbol Token Symbol for the new token 105 | * @param _transfersEnabled If true, tokens will be able to be transferred 106 | */ 107 | constructor( 108 | address _tokenFactory, 109 | address _parentToken, 110 | uint _parentSnapShotBlock, 111 | string memory _tokenName, 112 | uint8 _decimalUnits, 113 | string memory _tokenSymbol, 114 | bool _transfersEnabled 115 | ) 116 | public 117 | Controlled(msg.sender) 118 | { 119 | tokenFactory = MiniMeTokenFactory(_tokenFactory); 120 | name = _tokenName; // Set the name 121 | decimals = _decimalUnits; // Set the decimals 122 | symbol = _tokenSymbol; // Set the symbol 123 | parentToken = MiniMeToken(address(uint160(_parentToken))); 124 | parentSnapShotBlock = _parentSnapShotBlock; 125 | transfersEnabled = _transfersEnabled; 126 | creationBlock = block.number; 127 | } 128 | 129 | 130 | /////////////////// 131 | // ERC20 Methods 132 | /////////////////// 133 | 134 | /** 135 | * @notice Send `_amount` tokens to `_to` from `msg.sender` 136 | * @param _to The address of the recipient 137 | * @param _amount The amount of tokens to be transferred 138 | * @return Whether the transfer was successful or not 139 | */ 140 | function transfer(address _to, uint256 _amount) public returns (bool success) { 141 | require(transfersEnabled, "Transfers disabled"); 142 | return doTransfer(msg.sender, _to, _amount); 143 | } 144 | 145 | /** 146 | * @notice Send `_amount` tokens to `_to` from `_from` on the condition it 147 | * is approved by `_from` 148 | * @param _from The address holding the tokens being transferred 149 | * @param _to The address of the recipient 150 | * @param _amount The amount of tokens to be transferred 151 | * @return True if the transfer was successful 152 | */ 153 | function transferFrom( 154 | address _from, 155 | address _to, 156 | uint256 _amount 157 | ) 158 | public 159 | returns (bool success) 160 | { 161 | 162 | // The controller of this contract can move tokens around at will, 163 | // this is important to recognize! Confirm that you trust the 164 | // controller of this contract, which in most situations should be 165 | // another open source smart contract or 0x0 166 | if (msg.sender != controller) { 167 | require(transfersEnabled, "Transfers disabled"); 168 | 169 | // The standard ERC 20 transferFrom functionality 170 | if (allowed[_from][msg.sender] < _amount) { 171 | return false; 172 | } 173 | allowed[_from][msg.sender] -= _amount; 174 | } 175 | return doTransfer(_from, _to, _amount); 176 | } 177 | 178 | /** 179 | * @dev This is the actual transfer function in the token contract, it can 180 | * only be called by other functions in this contract. 181 | * @param _from The address holding the tokens being transferred 182 | * @param _to The address of the recipient 183 | * @param _amount The amount of tokens to be transferred 184 | * @return True if the transfer was successful 185 | */ 186 | function doTransfer( 187 | address _from, 188 | address _to, 189 | uint _amount 190 | ) 191 | internal 192 | returns(bool) 193 | { 194 | 195 | if (_amount == 0) { 196 | return true; 197 | } 198 | 199 | require(parentSnapShotBlock < block.number, "Invalid block.number"); 200 | 201 | // Do not allow transfer to 0x0 or the token contract itself 202 | require((_to != address(0)) && (_to != address(this)), "Invalid _to"); 203 | 204 | // If the amount being transfered is more than the balance of the 205 | // account the transfer returns false 206 | uint256 previousBalanceFrom = balanceOfAt(_from, block.number); 207 | if (previousBalanceFrom < _amount) { 208 | return false; 209 | } 210 | 211 | // Alerts the token controller of the transfer 212 | if (isContract(controller)) { 213 | require(TokenController(controller).onTransfer(_from, _to, _amount), "Unauthorized transfer"); 214 | } 215 | 216 | // First update the balance array with the new value for the address 217 | // sending the tokens 218 | updateValueAtNow(balances[_from], previousBalanceFrom - _amount); 219 | 220 | // Then update the balance array with the new value for the address 221 | // receiving the tokens 222 | uint256 previousBalanceTo = balanceOfAt(_to, block.number); 223 | require(previousBalanceTo + _amount >= previousBalanceTo, "Balance overflow"); // Check for overflow 224 | updateValueAtNow(balances[_to], previousBalanceTo + _amount); 225 | 226 | // An event to make the transfer easy to find on the blockchain 227 | emit Transfer(_from, _to, _amount); 228 | 229 | return true; 230 | } 231 | 232 | function doApprove( 233 | address _from, 234 | address _spender, 235 | uint256 _amount 236 | ) 237 | internal 238 | returns (bool) 239 | { 240 | require(transfersEnabled, "Transfers disabled"); 241 | 242 | // To change the approve amount you first have to reduce the addresses` 243 | // allowance to zero by calling `approve(_spender,0)` if it is not 244 | // already 0 to mitigate the race condition described here: 245 | // https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 246 | require((_amount == 0) || (allowed[_from][_spender] == 0), "Reset allowance first"); 247 | 248 | // Alerts the token controller of the approve function call 249 | if (isContract(controller)) { 250 | require(TokenController(controller).onApprove(_from, _spender, _amount), "Unauthorized approve"); 251 | } 252 | 253 | allowed[_from][_spender] = _amount; 254 | emit Approval(_from, _spender, _amount); 255 | return true; 256 | } 257 | 258 | /** 259 | * @param _owner The address that's balance is being requested 260 | * @return The balance of `_owner` at the current block 261 | */ 262 | function balanceOf(address _owner) external view returns (uint256 balance) { 263 | return balanceOfAt(_owner, block.number); 264 | } 265 | 266 | /** 267 | * @notice `msg.sender` approves `_spender` to spend `_amount` tokens on 268 | * its behalf. This is a modified version of the ERC20 approve function 269 | * to be a little bit safer 270 | * @param _spender The address of the account able to transfer the tokens 271 | * @param _amount The amount of tokens to be approved for transfer 272 | * @return True if the approval was successful 273 | */ 274 | function approve(address _spender, uint256 _amount) external returns (bool success) { 275 | return doApprove(msg.sender, _spender, _amount); 276 | } 277 | 278 | /** 279 | * @dev This function makes it easy to read the `allowed[]` map 280 | * @param _owner The address of the account that owns the token 281 | * @param _spender The address of the account able to transfer the tokens 282 | * @return Amount of remaining tokens of _owner that _spender is allowed 283 | * to spend 284 | */ 285 | function allowance( 286 | address _owner, 287 | address _spender 288 | ) 289 | external 290 | view 291 | returns (uint256 remaining) 292 | { 293 | return allowed[_owner][_spender]; 294 | } 295 | /** 296 | * @notice `msg.sender` approves `_spender` to send `_amount` tokens on 297 | * its behalf, and then a function is triggered in the contract that is 298 | * being approved, `_spender`. This allows users to use their tokens to 299 | * interact with contracts in one function call instead of two 300 | * @param _spender The address of the contract able to transfer the tokens 301 | * @param _amount The amount of tokens to be approved for transfer 302 | * @return True if the function call was successful 303 | */ 304 | function approveAndCall( 305 | address _spender, 306 | uint256 _amount, 307 | bytes memory _extraData 308 | ) 309 | public 310 | returns (bool success) 311 | { 312 | require(doApprove(msg.sender, _spender, _amount), "Approve failed"); 313 | 314 | ApproveAndCallFallBack(_spender).receiveApproval( 315 | msg.sender, 316 | _amount, 317 | address(this), 318 | _extraData 319 | ); 320 | 321 | return true; 322 | } 323 | 324 | /** 325 | * @dev This function makes it easy to get the total number of tokens 326 | * @return The total number of tokens 327 | */ 328 | function totalSupply() external view returns (uint) { 329 | return totalSupplyAt(block.number); 330 | } 331 | 332 | 333 | //////////////// 334 | // Query balance and totalSupply in History 335 | //////////////// 336 | 337 | /** 338 | * @dev Queries the balance of `_owner` at a specific `_blockNumber` 339 | * @param _owner The address from which the balance will be retrieved 340 | * @param _blockNumber The block number when the balance is queried 341 | * @return The balance at `_blockNumber` 342 | */ 343 | function balanceOfAt( 344 | address _owner, 345 | uint _blockNumber 346 | ) 347 | public 348 | view 349 | returns (uint) 350 | { 351 | 352 | // These next few lines are used when the balance of the token is 353 | // requested before a check point was ever created for this token, it 354 | // requires that the `parentToken.balanceOfAt` be queried at the 355 | // genesis block for that token as this contains initial balance of 356 | // this token 357 | if ((balances[_owner].length == 0) || (balances[_owner][0].fromBlock > _blockNumber)) { 358 | if (address(parentToken) != address(0)) { 359 | return parentToken.balanceOfAt(_owner, min(_blockNumber, parentSnapShotBlock)); 360 | } else { 361 | // Has no parent 362 | return 0; 363 | } 364 | 365 | // This will return the expected balance during normal situations 366 | } else { 367 | return getValueAt(balances[_owner], _blockNumber); 368 | } 369 | } 370 | 371 | /** 372 | * @notice Total amount of tokens at a specific `_blockNumber`. 373 | * @param _blockNumber The block number when the totalSupply is queried 374 | * @return The total amount of tokens at `_blockNumber` 375 | */ 376 | function totalSupplyAt(uint _blockNumber) public view returns(uint) { 377 | 378 | // These next few lines are used when the totalSupply of the token is 379 | // requested before a check point was ever created for this token, it 380 | // requires that the `parentToken.totalSupplyAt` be queried at the 381 | // genesis block for this token as that contains totalSupply of this 382 | // token at this block number. 383 | if ((totalSupplyHistory.length == 0) || (totalSupplyHistory[0].fromBlock > _blockNumber)) { 384 | if (address(parentToken) != address(0)) { 385 | return parentToken.totalSupplyAt(min(_blockNumber, parentSnapShotBlock)); 386 | } else { 387 | return 0; 388 | } 389 | 390 | // This will return the expected totalSupply during normal situations 391 | } else { 392 | return getValueAt(totalSupplyHistory, _blockNumber); 393 | } 394 | } 395 | 396 | //////////////// 397 | // Clone Token Method 398 | //////////////// 399 | 400 | /** 401 | * @notice Creates a new clone token with the initial distribution being 402 | * this token at `snapshotBlock` 403 | * @param _cloneTokenName Name of the clone token 404 | * @param _cloneDecimalUnits Number of decimals of the smallest unit 405 | * @param _cloneTokenSymbol Symbol of the clone token 406 | * @param _snapshotBlock Block when the distribution of the parent token is 407 | * copied to set the initial distribution of the new clone token; 408 | * if the block is zero than the actual block, the current block is used 409 | * @param _transfersEnabled True if transfers are allowed in the clone 410 | * @return The address of the new MiniMeToken Contract 411 | */ 412 | function createCloneToken( 413 | string memory _cloneTokenName, 414 | uint8 _cloneDecimalUnits, 415 | string memory _cloneTokenSymbol, 416 | uint _snapshotBlock, 417 | bool _transfersEnabled 418 | ) 419 | public 420 | returns(address) 421 | { 422 | uint snapshotBlock = _snapshotBlock; 423 | if (snapshotBlock == 0) { 424 | snapshotBlock = block.number; 425 | } 426 | MiniMeToken cloneToken = tokenFactory.createCloneToken( 427 | address(this), 428 | snapshotBlock, 429 | _cloneTokenName, 430 | _cloneDecimalUnits, 431 | _cloneTokenSymbol, 432 | _transfersEnabled 433 | ); 434 | 435 | cloneToken.changeController(msg.sender); 436 | 437 | // An event to make the token easy to find on the blockchain 438 | emit NewCloneToken(address(cloneToken), snapshotBlock); 439 | return address(cloneToken); 440 | } 441 | 442 | //////////////// 443 | // Generate and destroy tokens 444 | //////////////// 445 | 446 | /** 447 | * @notice Generates `_amount` tokens that are assigned to `_owner` 448 | * @param _owner The address that will be assigned the new tokens 449 | * @param _amount The quantity of tokens generated 450 | * @return True if the tokens are generated correctly 451 | */ 452 | function generateTokens( 453 | address _owner, 454 | uint _amount 455 | ) 456 | public 457 | onlyController 458 | returns (bool) 459 | { 460 | uint curTotalSupply = totalSupplyAt(block.number); 461 | require(curTotalSupply + _amount >= curTotalSupply, "Total overflow"); // Check for overflow 462 | uint previousBalanceTo = balanceOfAt(_owner, block.number); 463 | require(previousBalanceTo + _amount >= previousBalanceTo, "Balance overflow"); // Check for overflow 464 | updateValueAtNow(totalSupplyHistory, curTotalSupply + _amount); 465 | updateValueAtNow(balances[_owner], previousBalanceTo + _amount); 466 | emit Transfer(address(0), _owner, _amount); 467 | return true; 468 | } 469 | 470 | /** 471 | * @notice Burns `_amount` tokens from `_owner` 472 | * @param _owner The address that will lose the tokens 473 | * @param _amount The quantity of tokens to burn 474 | * @return True if the tokens are burned correctly 475 | */ 476 | function destroyTokens( 477 | address _owner, 478 | uint _amount 479 | ) 480 | public 481 | onlyController 482 | returns (bool) 483 | { 484 | uint curTotalSupply = totalSupplyAt(block.number); 485 | require(curTotalSupply >= _amount, "No enough supply"); 486 | uint previousBalanceFrom = balanceOfAt(_owner, block.number); 487 | require(previousBalanceFrom >= _amount, "No enough balance"); 488 | updateValueAtNow(totalSupplyHistory, curTotalSupply - _amount); 489 | updateValueAtNow(balances[_owner], previousBalanceFrom - _amount); 490 | emit Transfer(_owner, address(0), _amount); 491 | return true; 492 | } 493 | 494 | //////////////// 495 | // Enable tokens transfers 496 | //////////////// 497 | 498 | /** 499 | * @notice Enables token holders to transfer their tokens freely if true 500 | * @param _transfersEnabled True if transfers are allowed in the clone 501 | */ 502 | function enableTransfers(bool _transfersEnabled) public onlyController { 503 | transfersEnabled = _transfersEnabled; 504 | } 505 | 506 | //////////////// 507 | // Internal helper functions to query and set a value in a snapshot array 508 | //////////////// 509 | 510 | /** 511 | * @dev `getValueAt` retrieves the number of tokens at a given block number 512 | * @param checkpoints The history of values being queried 513 | * @param _block The block number to retrieve the value at 514 | * @return The number of tokens being queried 515 | */ 516 | function getValueAt( 517 | Checkpoint[] storage checkpoints, 518 | uint _block 519 | ) 520 | internal 521 | view 522 | returns (uint) 523 | { 524 | if (checkpoints.length == 0) { 525 | return 0; 526 | } 527 | 528 | // Shortcut for the actual value 529 | if (_block >= checkpoints[checkpoints.length-1].fromBlock) { 530 | return checkpoints[checkpoints.length-1].value; 531 | } 532 | if (_block < checkpoints[0].fromBlock) { 533 | return 0; 534 | } 535 | 536 | // Binary search of the value in the array 537 | uint min = 0; 538 | uint max = checkpoints.length-1; 539 | while (max > min) { 540 | uint mid = (max + min + 1) / 2; 541 | if (checkpoints[mid].fromBlock<=_block) { 542 | min = mid; 543 | } else { 544 | max = mid-1; 545 | } 546 | } 547 | return checkpoints[min].value; 548 | } 549 | 550 | /** 551 | * @dev `updateValueAtNow` used to update the `balances` map and the 552 | * `totalSupplyHistory` 553 | * @param checkpoints The history of data being updated 554 | * @param _value The new number of tokens 555 | */ 556 | function updateValueAtNow(Checkpoint[] storage checkpoints, uint _value) internal { 557 | if ((checkpoints.length == 0) || (checkpoints[checkpoints.length -1].fromBlock < block.number)) { 558 | Checkpoint storage newCheckPoint = checkpoints[checkpoints.length++]; 559 | newCheckPoint.fromBlock = uint128(block.number); 560 | newCheckPoint.value = uint128(_value); 561 | } else { 562 | Checkpoint storage oldCheckPoint = checkpoints[checkpoints.length-1]; 563 | oldCheckPoint.value = uint128(_value); 564 | } 565 | } 566 | 567 | /** 568 | * @dev Internal function to determine if an address is a contract 569 | * @param _addr The address being queried 570 | * @return True if `_addr` is a contract 571 | */ 572 | function isContract(address _addr) internal view returns(bool) { 573 | uint size; 574 | if (_addr == address(0)){ 575 | return false; 576 | } 577 | assembly { 578 | size := extcodesize(_addr) 579 | } 580 | return size>0; 581 | } 582 | 583 | /** 584 | * @dev Helper function to return a min betwen the two uints 585 | */ 586 | function min(uint a, uint b) internal pure returns (uint) { 587 | return a < b ? a : b; 588 | } 589 | 590 | /** 591 | * @notice The fallback function: If the contract's controller has not been 592 | * set to 0, then the `proxyPayment` method is called which relays the 593 | * ether and creates tokens as described in the token controller contract 594 | */ 595 | function () external payable { 596 | require(isContract(controller), "Deposit unallowed"); 597 | require(TokenController(controller).proxyPayment.value(msg.value)(msg.sender), "Deposit denied"); 598 | } 599 | 600 | ////////// 601 | // Safety Methods 602 | ////////// 603 | 604 | /** 605 | * @notice This method can be used by the controller to extract mistakenly 606 | * sent tokens to this contract. 607 | * @param _token The address of the token contract that you want to recover 608 | * set to 0 in case you want to extract ether. 609 | */ 610 | function claimTokens(address _token) public onlyController { 611 | if (_token == address(0)) { 612 | controller.transfer(address(this).balance); 613 | return; 614 | } 615 | 616 | MiniMeToken token = MiniMeToken(address(uint160(_token))); 617 | uint balance = token.balanceOf(address(this)); 618 | token.transfer(controller, balance); 619 | emit ClaimedTokens(_token, controller, balance); 620 | } 621 | 622 | //////////////// 623 | // Events 624 | //////////////// 625 | event ClaimedTokens(address indexed _token, address indexed _controller, uint _amount); 626 | event Transfer(address indexed _from, address indexed _to, uint256 _amount); 627 | event NewCloneToken(address indexed _cloneToken, uint snapshotBlock); 628 | event Approval( 629 | address indexed _owner, 630 | address indexed _spender, 631 | uint256 _amount 632 | ); 633 | 634 | } 635 | --------------------------------------------------------------------------------