├── 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 |
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 |
85 |
86 | Transfer/Approve token balance
87 |
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 |
--------------------------------------------------------------------------------