diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..8aa924d --- /dev/null +++ b/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": ["@babel/preset-env"] +} \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..f8ab647 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,9 @@ +{ + "extends": "airbnb", + "plugins": [ + "react" + ], + "rules": { + "react/prop-types": 0 + } +} diff --git a/.gitignore b/.gitignore index 5c20c22..2b75c2d 100644 --- a/.gitignore +++ b/.gitignore @@ -6,8 +6,7 @@ __pycache__/ # embark .embark/ chains.json -config/production/password -config/livenet/password +.password # egg-related viper.egg-info/ diff --git a/README.md b/README.md index c767e1f..03c5c16 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ # status.im contracts - +Requires https://github.com/creationix/nvm Usage: ``` + nvm install v8.9.4 + nvm use v8.9.4 npm install -g embark git clone https://github.com/status-im/contracts.git cd contracts @@ -16,4 +18,4 @@ Usage: | token/TestToken | Yes | Yes | Yes | | token/ERC20Token | No | Yes | Yes | | deploy/Instance | No | No | No | -| deploy/Factory | No | No | No | \ No newline at end of file +| deploy/Factory | No | No | No | diff --git a/app/components/TestStatusNetwork.js b/app/components/TestStatusNetwork.js new file mode 100644 index 0000000..e974e51 --- /dev/null +++ b/app/components/TestStatusNetwork.js @@ -0,0 +1,50 @@ +import EmbarkJS from 'Embark/EmbarkJS'; +import StatusRoot from 'Embark/contracts/StatusRoot'; +import MiniMeToken from 'Embark/contracts/MiniMeToken'; +import React from 'react'; +import { Form, FormGroup, FormControl, HelpBlock, Button } from 'react-bootstrap'; +import ERC20TokenUI from './erc20token'; + +class TestTokenUI extends React.Component { + + constructor(props) { + super(props); + this.state = { + amountToMint: 100, + } + } + + handleMintAmountChange(e){ + this.setState({amountToMint: e.target.value}); + } + + async mint(e){ + e.preventDefault(); + await EmbarkJS.enableEthereum(); + var value = parseInt(this.state.amountToMint, 10); + StatusRoot.methods.mint(value).send({ gas: 1000000 }) + + console.log(StatusRoot.options.address +".mint("+value+").send({from: " + web3.eth.defaultAccount + "})"); + } + + render(){ + return ( +

Test Status Network

+
+ + this.handleMintAmountChange(e)} /> + + +
+ + + +
+ ); + } + } + +export default TestTokenUI; diff --git a/app/components/erc20token.js b/app/components/erc20token.js index 6695adf..7b8ffdf 100644 --- a/app/components/erc20token.js +++ b/app/components/erc20token.js @@ -7,21 +7,16 @@ class ERC20TokenUI extends React.Component { constructor(props) { super(props); + ERC20Token.options.address = props.address; this.state = { - balanceOf: 0, transferTo: "", transferAmount: 0, - logs: [] + accountBalance: 0, + accountB: web3.eth.defaultAccount, } } - contractAddress(e){ - e.preventDefault(); - var tokenAddress = e.target.value; - ERC20Token.options.address = tokenAddress; - } - update_transferTo(e){ this.setState({transferTo: e.target.value}); } @@ -33,99 +28,87 @@ class ERC20TokenUI extends React.Component { transfer(e){ var to = this.state.transferTo; var amount = this.state.transferAmount; - var tx = ERC20Token.methods.transfer(to, amount).send({from: web3.eth.defaultAccount}); - this._addToLog(ERC20Token.options.address+".transfer(" + to + ", "+amount+")"); + this._addToLog(ERC20Token.options.address+".methods.transfer(" + to + ", "+amount+").send({from: " + web3.eth.defaultAccount + "})"); + var tx = ERC20Token.methods.transfer(to, amount); + tx.estimateGas().then((r) => { + tx.send({gas: r, from: web3.eth.defaultAccount}); + }); + } approve(e){ var to = this.state.transferTo; var amount = this.state.transferAmount; + this._addToLog(ERC20Token.options.address+".methods.approve(" + to + ", "+amount+").send({from: " + web3.eth.defaultAccount + "})"); var tx = ERC20Token.methods.approve(to, amount).send({from: web3.eth.defaultAccount}); - this._addToLog(ERC20Token.options.address+".approve(" + to + ", "+amount+")"); + } balanceOf(e){ e.preventDefault(); var who = e.target.value; - if (EmbarkJS.isNewWeb3()) { - ERC20Token.methods.balanceOf(who).call() - .then(_value => this.setState({balanceOf: _value})) - - } else { - ERC20Token.balanceOf(who) - .then(_value => this.x({balanceOf: _value})); - } - this._addToLog(ERC20Token.options.address+".balanceOf(" + who + ")"); + this._addToLog(ERC20Token.options.address+".methods.balanceOf(" + who + ").call()"); + ERC20Token.methods.balanceOf(who).call() + .then(_value => this.setState({balanceOf: _value})) } + getDefaultAccountBalance(){ + this._addToLog(ERC20Token.options.address + ".methods.balanceOf(" + web3.eth.defaultAccount + ").call()"); + ERC20Token.methods.balanceOf(web3.eth.defaultAccount).call() + .then(_value => this.setState({accountBalance: _value})) + } _addToLog(txt){ - this.state.logs.push(txt); - this.setState({logs: this.state.logs}); + console.log(txt); } - render(){ - return ( - -

Set token contract address

-
- + render() { + + return ( + +

Read account token balance

+ + + - + defaultValue={this.state.accountB} + onChange={(e) => this.balanceOf(e)} /> + + + +
+ - -

Read account token balance

-
- - - - - -
- -

Transfer/Approve token balance

-
- - - - - - -
- -

Contract Calls

-

Javascript calls being made:

-
- { - this.state.logs.map((item, i) =>

{item}

) - } -
+

Transfer/Approve token balance

+
+ + + + + + +
+
- ); - } + ); } +} - export default ERC20TokenUI; \ No newline at end of file + +export default ERC20TokenUI; diff --git a/app/components/testtoken.js b/app/components/testtoken.js deleted file mode 100644 index 3618caa..0000000 --- a/app/components/testtoken.js +++ /dev/null @@ -1,88 +0,0 @@ -import EmbarkJS from 'Embark/EmbarkJS'; -import TestToken from 'Embark/contracts/TestToken'; -import React from 'react'; -import { Form, FormGroup, FormControl, HelpBlock, Button } from 'react-bootstrap'; - -class TestTokenUI extends React.Component { - - constructor(props) { - super(props); - this.state = { - amountToMint: 100, - accountBalance: 0, - accountB: web3.eth.defaultAccount, - balanceOf: 0, - logs: [] - } - } - - handleMintAmountChange(e){ - this.setState({amountToMint: e.target.value}); - } - - mint(e){ - e.preventDefault(); - - var value = parseInt(this.state.amountToMint, 10); - - if (EmbarkJS.isNewWeb3()) { - TestToken.methods.mint(value).send({from: web3.eth.defaultAccount}); - } else { - TestToken.mint(value); - this._addToLog("#blockchain", "TestToken.mint(" + value + ")"); - } - this._addToLog(TestToken.options.address +".mint("+value+").send({from: " + web3.eth.defaultAccount + "})"); - } - - getBalance(e){ - e.preventDefault(); - - if (EmbarkJS.isNewWeb3()) { - TestToken.methods.balanceOf(web3.eth.defaultAccount).call() - .then(_value => this.setState({accountBalance: _value})) - } else { - TestToken.balanceOf(web3.eth.defaultAccount) - .then(_value => this.x({valueGet: _value})) - } - this._addToLog(TestToken.options.address + ".balanceOf(" + web3.eth.defaultAccount + ")"); - } - - _addToLog(txt){ - this.state.logs.push(txt); - this.setState({logs: this.state.logs}); - } - - render(){ - return ( -

1. Mint Test Token

-
- - this.handleMintAmountChange(e)} /> - - -
- -

2. Read your account token balance

-
- - Your test token balance is {this.state.accountBalance} - - -
- -

3. Contract Calls

-

Javascript calls being made:

-
- { - this.state.logs.map((item, i) =>

{item}

) - } -
-
- ); - } - } - - export default TestTokenUI; \ No newline at end of file diff --git a/app/dapp.css b/app/dapp.css index 49f1976..0fd02eb 100644 --- a/app/dapp.css +++ b/app/dapp.css @@ -1,8 +1,18 @@ - -div { - margin: 15px; +.navbar { + } +.accounts { + float: right; + margin-right: 17px; + font-family: monospace; +} + +.identicon { + border-radius: 50%; +} + + .logs { background-color: black; font-size: 14px; diff --git a/app/dapp.js b/app/dapp.js index 6df03e3..b2473b5 100644 --- a/app/dapp.js +++ b/app/dapp.js @@ -1,14 +1,11 @@ import React from 'react'; -import ReactDOM from 'react-dom'; import { Tabs, Tab } from 'react-bootstrap'; - import EmbarkJS from 'Embark/EmbarkJS'; -import TestTokenUI from './components/testtoken'; -import ERC20TokenUI from './components/erc20token'; +import TestStatusNetworkUI from './components/TestStatusNetwork'; import './dapp.css'; -class App extends React.Component { +class DApp extends React.Component { constructor(props) { super(props); @@ -17,13 +14,11 @@ class App extends React.Component { } componentDidMount(){ - __embarkContext.execWhenReady(() => { - - }); + } - _renderStatus(title, available){ + _renderStatus(title, available) { let className = available ? 'pull-right status-online' : 'pull-right status-offline'; return {title} @@ -32,17 +27,16 @@ class App extends React.Component { } render(){ - return (

Status.im Contracts

- - - - - - - - -
); + return ( +
+ + + + + + +
); } } -ReactDOM.render(, document.getElementById('app')); +export default DApp; diff --git a/app/index.html b/app/index.html index 1fb8607..43438ee 100644 --- a/app/index.html +++ b/app/index.html @@ -7,6 +7,6 @@
- + diff --git a/app/index.js b/app/index.js new file mode 100644 index 0000000..7b8b2b4 --- /dev/null +++ b/app/index.js @@ -0,0 +1,9 @@ +import React from 'react'; +import { render } from 'react-dom'; +import DApp from './dapp'; +import './dapp.css'; + +render( + , + document.getElementById('app') +); diff --git a/config/blockchain.js b/config/blockchain.js new file mode 100644 index 0000000..a32183c --- /dev/null +++ b/config/blockchain.js @@ -0,0 +1,73 @@ +module.exports = { + development: { + enabled: true, + networkType: "custom", // Can be: testnet, rinkeby, livenet or custom, in which case, it will use the specified networkId + networkId: "1337", // Network id used when networkType is custom + isDev: true, // Uses and ephemeral proof-of-authority network with a pre-funded developer account, mining enabled + genesisBlock: "config/development/genesis.json", // Genesis block to initiate on first creation of a development node + datadir: ".embark/development/datadir", // Data directory for the databases and keystore + mineWhenNeeded: true, // Uses our custom script (if isDev is false) to mine only when needed + nodiscover: true, // Disables the peer discovery mechanism (manual peer addition) + maxpeers: 0, // Maximum number of network peers (network disabled if set to 0) (default: 25) + rpcHost: "localhost", // HTTP-RPC server listening interface (default: "localhost") + rpcPort: 8545, // HTTP-RPC server listening port (default: 8545) + rpcCorsDomain: "auto", // Comma separated list of domains from which to accept cross origin requests (browser enforced) + // When set to "auto", Embark will automatically set the cors to the address of the webserver + proxy: true, // Proxy is used to present meaningful information about transactions + accounts: [ + { + nodeAccounts: true, + password: "config/development/.password" + } + ], + targetGasLimit: 8000000, // Target gas limit sets the artificial target gas floor for the blocks to mine + wsRPC: true, // Enable the WS-RPC server + wsOrigins: "auto", // Origins from which to accept websockets requests + // When set to "auto", Embark will automatically set the cors to the address of the webserver + wsHost: "localhost", // WS-RPC server listening interface (default: "localhost") + wsPort: 8546, // WS-RPC server listening port (default: 8546) + simulatorBlocktime: 0 // Specify blockTime in seconds for automatic mining. Default is 0 and no auto-mining. + }, + testnet: { + enabled: true, + networkType: "testnet", + syncMode: "light", + rpcHost: "localhost", + rpcPort: 8545, + rpcCorsDomain: "http://localhost:8000", + accounts: [ + { + nodeAccounts: true, + password: "config/testnet/.password" + } + ], + }, + livenet: { + enabled: false, + networkType: "livenet", + syncMode: "light", + rpcHost: "localhost", + rpcPort: 8545, + rpcCorsDomain: "http://localhost:8000", + accounts: [ + { + nodeAccounts: true, + password: "config/livenet/.password" + } + ], + }, + rinkeby: { + enabled: true, + networkType: "rinkeby", + syncMode: "light", + rpcHost: "localhost", + rpcPort: 8545, + rpcCorsDomain: "http://localhost:8000", + accounts: [ + { + nodeAccounts: true, + password: "config/rinkeby/.password" + } + ], + } +}; diff --git a/config/blockchain.json b/config/blockchain.json deleted file mode 100644 index 638c816..0000000 --- a/config/blockchain.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "development": { - "enabled": true, - "networkType": "custom", - "genesisBlock": "config/development/genesis.json", - "datadir": ".embark/development/datadir", - "mineWhenNeeded": true, - "nodiscover": true, - "maxpeers": 0, - "rpcHost": "localhost", - "rpcPort": 8545, - "rpcCorsDomain": "auto", - "account": { - "password": "config/development/password" - }, - "targetGasLimit": 8000000, - "wsOrigins": "auto", - "wsRPC": true, - "wsHost": "localhost", - "wsPort": 8546, - "simulatorMnemonic": "example exile argue silk regular smile grass bomb merge arm assist farm", - "simulatorBlocktime": 0 - }, - "testnet": { - "enabled": true, - "networkType": "testnet", - "light": true, - "rpcHost": "localhost", - "rpcPort": 8545, - "rpcCorsDomain": "http://localhost:8000", - "account": { - "password": "config/testnet/password" - } - }, - "livenet": { - "enabled": true, - "networkType": "livenet", - "light": true, - "rpcHost": "localhost", - "rpcPort": 8545, - "rpcCorsDomain": "http://localhost:8000", - "account": { - "password": "config/livenet/password" - } - }, - "privatenet": { - "enabled": true, - "networkType": "custom", - "rpcHost": "localhost", - "rpcPort": 8545, - "rpcCorsDomain": "http://localhost:8000", - "datadir": "yourdatadir", - "networkId": "123", - "bootnodes": "" - } -} diff --git a/config/communication.js b/config/communication.js new file mode 100644 index 0000000..8c4d1f9 --- /dev/null +++ b/config/communication.js @@ -0,0 +1,12 @@ +module.exports = { + default: { + enabled: true, + provider: "whisper", // Communication provider. Currently, Embark only supports whisper + available_providers: ["whisper"], // Array of available providers + connection: { + host: "localhost", // Host of the blockchain node + port: 8546, // Port of the blockchain node + type: "ws" // Type of connection (ws or rpc) + } + } +}; diff --git a/config/communication.json b/config/communication.json deleted file mode 100644 index 80ec2f8..0000000 --- a/config/communication.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "default": { - "enabled": true, - "provider": "whisper", - "available_providers": ["whisper", "orbit"], - "connection": { - "host": "localhost", - "port": 8546, - "type": "ws" - } - } -} diff --git a/config/contracts.js b/config/contracts.js new file mode 100644 index 0000000..3840741 --- /dev/null +++ b/config/contracts.js @@ -0,0 +1,83 @@ +module.exports = { + // default applies to all environments + default: { + // Blockchain node to deploy the contracts + deployment: { + host: "localhost", // Host of the blockchain node + port: 8545, // Port of the blockchain node + type: "rpc" // Type of connection (ws or rpc), + }, + // order of connections the dapp should connect to + dappConnection: [ + "$WEB3", // uses pre existing web3 object if available (e.g in Mist) + "ws://localhost:8546", + "http://localhost:8545" + ], + gas: "auto", + strategy: "explicit", + contracts: { + "MiniMeTokenFactory": { + "deploy": true + }, + "MiniMeToken": { + "deploy": true, + "args":["$MiniMeTokenFactory", "0x0", "0x0", "Status Test Token", 18, "STT", true], + }, + "StatusRoot": { + "instanceOf": "TestStatusNetwork", + "deploy": true, + "args": ["0x0", "$MiniMeToken"], + "onDeploy": [ + "await MiniMeToken.methods.changeController(StatusRoot.address).send()", + "await StatusRoot.methods.setOpen(true).send()", + ] + } + } + }, + + development: { + deployment: { + accounts: [ + { + privateKey: "b2ab40d549e67ba67f278781fec03b3a90515ad4d0c898a6326dd958de1e46fa", + balance: "5 ether" // You can set the balance of the account in the dev environment + // Balances are in Wei, but you can specify the unit with its name + } + ] + } + }, + testnet: { + contracts: { + "MiniMeTokenFactory": { + "deploy": false, + "address": "0x6bFa86A71A7DBc68566d5C741F416e3009804279" + }, + "MiniMeToken": { + "deploy": false, + "address": "0xc55cF4B03948D7EBc8b9E8BAD92643703811d162" + }, + "StatusRoot": { + "instanceOf": "TestStatusNetwork", + "deploy": false, + "address": "0x34358C45FbA99ef9b78cB501584E8cBFa6f85Cef" + } + } + }, + rinkeby: { + contracts: { + "MiniMeTokenFactory": { + "deploy": false, + "address": "0x5bA5C786845CaacD45f5952E1135F4bFB8855469" + }, + "MiniMeToken": { + "deploy": false, + "address": "0x43d5adC3B49130A575ae6e4b00dFa4BC55C71621" + }, + "StatusRoot": { + "instanceOf": "TestStatusNetwork", + "deploy": false, + "address": "0xEdEB948dE35C6ac414359f97329fc0b4be70d3f1" + } + } + } +} diff --git a/config/contracts.json b/config/contracts.json deleted file mode 100644 index 1e209c6..0000000 --- a/config/contracts.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "default": { - "versions": { - "web3.js": "1.0.0-beta", - "solc": "0.4.23" - }, - "deployment": { - "host": "localhost", - "port": 8545, - "type": "rpc" - }, - "dappConnection": [ - "$WEB3", - "http://localhost:8545" - ], - "gas": "auto", - "contracts": { - "ERC20Receiver": { - "deploy": false - }, - "Factory": { - "deploy": false - }, - "Instance": { - "deploy": false - }, - "UpdatableInstance": { - "deploy": false - } - } - } -} diff --git a/config/development/password b/config/development/password deleted file mode 100644 index c747d67..0000000 --- a/config/development/password +++ /dev/null @@ -1 +0,0 @@ -dev_password diff --git a/config/namesystem.js b/config/namesystem.js new file mode 100644 index 0000000..10e20d6 --- /dev/null +++ b/config/namesystem.js @@ -0,0 +1,6 @@ +module.exports = { + default: { + available_providers: ["ens"], + provider: "ens" + } +}; diff --git a/config/storage.js b/config/storage.js new file mode 100644 index 0000000..92e9192 --- /dev/null +++ b/config/storage.js @@ -0,0 +1,59 @@ +module.exports = { + // default applies to all environments + default: { + enabled: true, + ipfs_bin: "ipfs", + available_providers: ["ipfs"], + upload: { + provider: "ipfs", + host: "localhost", + port: 5001 + }, + dappConnection: [ + { + provider:"ipfs", + host: "localhost", + port: 5001, + getUrl: "http://localhost:8080/ipfs/" + } + ] + // Configuration to start Swarm in the same terminal as `embark run` + /*,account: { + address: "YOUR_ACCOUNT_ADDRESS", // Address of account accessing Swarm + password: "PATH/TO/PASSWORD/FILE" // File containing the password of the account + }, + swarmPath: "PATH/TO/SWARM/EXECUTABLE" // Path to swarm executable (default: swarm)*/ + }, + + // default environment, merges with the settings in default + // assumed to be the intended environment by `embark run` + development: { + enabled: true, + upload: { + provider: "ipfs", + host: "localhost", + port: 5001, + getUrl: "http://localhost:8080/ipfs/" + } + }, + + // merges with the settings in default + // used with "embark run privatenet" + privatenet: { + }, + + // merges with the settings in default + // used with "embark run testnet" + testnet: { + }, + + // merges with the settings in default + // used with "embark run livenet" + livenet: { + }, + + // you can name an environment with specific settings and then specify with + // "embark run custom_name" + //custom_name: { + //} +}; diff --git a/config/storage.json b/config/storage.json deleted file mode 100644 index b286558..0000000 --- a/config/storage.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "default": { - "versions": { - "ipfs-api": "17.2.4" - }, - "enabled": true, - "ipfs_bin": "ipfs", - "provider": "ipfs", - "available_providers": ["ipfs"], - "host": "localhost", - "port": 5001 - }, - "development": { - "enabled": true, - "provider": "ipfs", - "host": "localhost", - "port": 5001, - "getUrl": "http://localhost:8080/ipfs/" - } -} diff --git a/config/testnet/password b/config/testnet/password deleted file mode 100644 index 414f849..0000000 --- a/config/testnet/password +++ /dev/null @@ -1 +0,0 @@ -test_password diff --git a/config/webserver.js b/config/webserver.js new file mode 100644 index 0000000..1814065 --- /dev/null +++ b/config/webserver.js @@ -0,0 +1,5 @@ +module.exports = { + enabled: true, + host: "localhost", + port: 8000 +}; diff --git a/config/webserver.json b/config/webserver.json deleted file mode 100644 index c28a311..0000000 --- a/config/webserver.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "enabled": true, - "host": "localhost", - "port": 8000 -} diff --git a/contracts/common/Controlled.sol b/contracts/common/Controlled.sol index 7b615b6..219de3b 100644 --- a/contracts/common/Controlled.sol +++ b/contracts/common/Controlled.sol @@ -1,14 +1,14 @@ -pragma solidity ^0.4.23; +pragma solidity >=0.5.0 <0.6.0; contract Controlled { /// @notice The address of the controller is the only address that can call /// a function with this modifier modifier onlyController { - require(msg.sender == controller); + require(msg.sender == controller, "Unauthorized"); _; } - address public controller; + address payable public controller; constructor() internal { controller = msg.sender; @@ -16,7 +16,7 @@ contract Controlled { /// @notice Changes the controller of the contract /// @param _newController The new controller of the contract - function changeController(address _newController) public onlyController { + function changeController(address payable _newController) public onlyController { controller = _newController; } } diff --git a/contracts/common/MessageSigned.sol b/contracts/common/MessageSigned.sol index 5a5a262..7b7c8d0 100644 --- a/contracts/common/MessageSigned.sol +++ b/contracts/common/MessageSigned.sol @@ -1,13 +1,11 @@ -pragma solidity ^0.4.21; +pragma solidity >=0.5.0 <0.6.0; /** * @notice Uses ethereum signed messages */ contract MessageSigned { - constructor() internal { - - } + constructor() internal {} /** * @notice recovers address who signed the message @@ -16,10 +14,10 @@ contract MessageSigned { */ function recoverAddress( bytes32 _signHash, - bytes _messageSignature + bytes memory _messageSignature ) - pure internal + pure returns(address) { uint8 v; @@ -42,19 +40,19 @@ contract MessageSigned { function getSignHash( bytes32 _hash ) - pure internal + pure returns (bytes32 signHash) { - signHash = keccak256("\x19Ethereum Signed Message:\n32", _hash); + signHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", _hash)); } /** * @dev divides bytes signature into `uint8 v, bytes32 r, bytes32 s` */ - function signatureSplit(bytes _signature) - pure + function signatureSplit(bytes memory _signature) internal + pure returns (uint8 v, bytes32 r, bytes32 s) { // The signature format is a compact form of: @@ -71,7 +69,7 @@ contract MessageSigned { v := and(mload(add(_signature, 65)), 0xff) } - require(v == 27 || v == 28); + require(v == 27 || v == 28, "Bad signature"); } } \ No newline at end of file diff --git a/contracts/common/Owned.sol b/contracts/common/Owned.sol index 18f03e7..ab6075a 100644 --- a/contracts/common/Owned.sol +++ b/contracts/common/Owned.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.4.23; +pragma solidity >=0.5.0 <0.6.0; /// @dev `Owned` is a base level contract that assigns an `owner` that can be /// later changed @@ -7,23 +7,23 @@ contract Owned { /// @dev `owner` is the only address that can call a function with this /// modifier modifier onlyOwner() { - require(msg.sender == owner); + require(msg.sender == owner, "Unauthorized"); _; } - address public owner; + address payable public owner; /// @notice The Constructor assigns the message sender to be `owner` constructor() internal { owner = msg.sender; } - address public newOwner; + address payable public newOwner; /// @notice `owner` can step down and assign some other address to this role /// @param _newOwner The address of the new owner. 0x0 can be used to create /// an unowned neutral vault, however that cannot be undone - function changeOwner(address _newOwner) public onlyOwner { + function changeOwner(address payable _newOwner) public onlyOwner { newOwner = _newOwner; } diff --git a/contracts/common/SafeMath.sol b/contracts/common/SafeMath.sol index 0b5602d..bdd7298 100644 --- a/contracts/common/SafeMath.sol +++ b/contracts/common/SafeMath.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.4.23; +pragma solidity >=0.5.0 <0.6.0; /** * Math operations with safety checks diff --git a/contracts/status/SNTController.sol b/contracts/status/SNTController.sol new file mode 100644 index 0000000..9a476ea --- /dev/null +++ b/contracts/status/SNTController.sol @@ -0,0 +1,90 @@ +pragma solidity >=0.5.0 <0.6.0; + +import "../token/TokenController.sol"; +import "../common/Owned.sol"; +import "../token/ERC20Token.sol"; +import "../token/MiniMeToken.sol"; +/** + * @title SNTController + * @author Ricardo Guilherme Schmidt (Status Research & Development GmbH) + * @notice enables economic abstraction for SNT + */ +contract SNTController is TokenController, Owned { + + MiniMeToken public snt; + + event ClaimedTokens(address indexed _token, address indexed _controller, uint256 _amount); + event ControllerChanged(address indexed _newController); + + /** + * @notice Constructor + * @param _owner Authority address + * @param _snt SNT token + */ + constructor(address payable _owner, MiniMeToken _snt) internal { + if(_owner != address(0)){ + owner = _owner; + } + snt = _snt; + } + /** + * @notice The owner of this contract can change the controller of the SNT token + * Please, be sure that the owner is a trusted agent or 0x0 address. + * @param _newController The address of the new controller + */ + function changeController(address payable _newController) public onlyOwner { + snt.changeController(_newController); + emit ControllerChanged(_newController); + } + + /** + * @notice This method can be used by the controller to extract mistakenly + * sent tokens to this contract. + * @param _token The address of the token contract that you want to recover + * set to 0 in case you want to extract ether. + */ + function claimTokens(address _token) public onlyOwner { + if (snt.controller() == address(this)) { + snt.claimTokens(_token); + } + if (_token == address(0)) { + address(owner).transfer(address(this).balance); + return; + } + + ERC20Token token = ERC20Token(_token); + uint256 balance = token.balanceOf(address(this)); + token.transfer(owner, balance); + emit ClaimedTokens(_token, owner, balance); + } + + /** + * @notice payment by address coming from controlled token + * @dev In between the offering and the network. Default settings for allowing token transfers. + */ + function proxyPayment(address) external payable returns (bool) { + //Uncomment above line when using parameters + //require(msg.sender == address(snt), "Unauthorized"); + return false; + } + + /** + * @notice register and authorizes transfer from token + * @dev called by snt when a transfer is made + */ + function onTransfer(address, address, uint256) external returns (bool) { + //Uncomment above line when using parameters + //require(msg.sender == address(snt), "Unauthorized"); + return true; + } + + /** + * @notice register and authorizes approve from token + * @dev called by snt when an approval is made + */ + function onApprove(address, address, uint256) external returns (bool) { + //Uncomment above line when using parameters + //require(msg.sender == address(snt), "Unauthorized"); + return true; + } +} \ No newline at end of file diff --git a/contracts/status/StatusNetwork.sol b/contracts/status/StatusNetwork.sol new file mode 100644 index 0000000..e7357be --- /dev/null +++ b/contracts/status/StatusNetwork.sol @@ -0,0 +1,23 @@ +pragma solidity >=0.5.0 <0.6.0; + +import "./SNTController.sol"; + +/** + * @dev Status Network is implemented here + */ +contract StatusNetwork is SNTController { + + /** + * @notice Constructor + * @param _owner Authority address + * @param _snt SNT token + */ + constructor( + address payable _owner, + MiniMeToken _snt + ) + public + SNTController(_owner, _snt) + { } + +} \ No newline at end of file diff --git a/contracts/status/TestStatusNetwork.sol b/contracts/status/TestStatusNetwork.sol new file mode 100644 index 0000000..da32ead --- /dev/null +++ b/contracts/status/TestStatusNetwork.sol @@ -0,0 +1,55 @@ +pragma solidity >=0.5.0 <0.6.0; + +import "./StatusNetwork.sol"; +/** + * @title SNTController + * @author Ricardo Guilherme Schmidt (Status Research & Development GmbH) + * @notice Test net version of SNTController which allow public mint + */ +contract TestStatusNetwork is StatusNetwork { + + bool public open = false; + + /** + * @notice Constructor + * @param _owner Authority address + * @param _snt SNT token + */ + constructor(address payable _owner, MiniMeToken _snt) + public + StatusNetwork(_owner, _snt) + { } + + function () external { + _generateTokens(msg.sender, 1000 * (10 ** uint(snt.decimals()))); + } + + function mint(uint256 _amount) external { + _generateTokens(msg.sender, _amount); + } + + function generateTokens(address _who, uint _amount) external { + _generateTokens(_who, _amount); + } + + function destroyTokens(address _who, uint _amount) external onlyOwner { + snt.destroyTokens(_who, _amount); + } + + function setOpen(bool _open) external onlyOwner { + open = _open; + } + + function _generateTokens(address _who, uint _amount) private { + require(msg.sender == owner || open, "Test Mint Disabled"); + address statusNetwork = snt.controller(); + if(statusNetwork == address(this)){ + snt.generateTokens(_who, _amount); + } else { + TestStatusNetwork(statusNetwork).generateTokens(_who, _amount); + } + + } + + +} \ No newline at end of file diff --git a/contracts/token/ApproveAndCallFallBack.sol b/contracts/token/ApproveAndCallFallBack.sol new file mode 100644 index 0000000..0c83ccb --- /dev/null +++ b/contracts/token/ApproveAndCallFallBack.sol @@ -0,0 +1,5 @@ +pragma solidity >=0.5.0 <0.6.0; + +contract ApproveAndCallFallBack { + function receiveApproval(address from, uint256 _amount, address _token, bytes memory _data) public; +} diff --git a/contracts/token/ERC20Receiver.sol b/contracts/token/ERC20Receiver.sol index 9a6af70..b72ef7f 100644 --- a/contracts/token/ERC20Receiver.sol +++ b/contracts/token/ERC20Receiver.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.4.23; +pragma solidity >=0.5.0 <0.6.0; import "./ERC20Token.sol"; @@ -43,7 +43,7 @@ contract ERC20Receiver { ) external { - require(_token.allowance(msg.sender, address(this)) >= _amount); + require(_token.allowance(msg.sender, address(this)) >= _amount, "Bad argument"); _depositToken(msg.sender, _token, _amount); } @@ -65,7 +65,7 @@ contract ERC20Receiver { ) private { - require(_amount > 0); + require(_amount > 0, "Bad argument"); if (_token.transferFrom(_from, address(this), _amount)) { tokenBalances[address(_token)][_from] += _amount; emit TokenDeposited(address(_token), _from, _amount); @@ -79,10 +79,10 @@ contract ERC20Receiver { ) private { - require(_amount > 0); - require(tokenBalances[address(_token)][_from] >= _amount); + require(_amount > 0, "Bad argument"); + require(tokenBalances[address(_token)][_from] >= _amount, "Insufficient funds"); tokenBalances[address(_token)][_from] -= _amount; - require(_token.transfer(_from, _amount)); + require(_token.transfer(_from, _amount), "Transfer fail"); emit TokenWithdrawn(address(_token), _from, _amount); } diff --git a/contracts/token/ERC20Token.sol b/contracts/token/ERC20Token.sol index ff8de15..72dc3e2 100644 --- a/contracts/token/ERC20Token.sol +++ b/contracts/token/ERC20Token.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.4.23; +pragma solidity >=0.5.0 <0.6.0; // Abstract contract for the full ERC 20 Token standard // https://github.com/ethereum/EIPs/issues/20 diff --git a/contracts/token/MiniMeToken.sol b/contracts/token/MiniMeToken.sol new file mode 100644 index 0000000..7151b7b --- /dev/null +++ b/contracts/token/MiniMeToken.sol @@ -0,0 +1,633 @@ +pragma solidity >=0.5.0 <0.6.0; + +/* + Copyright 2016, Jordi Baylina + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ +/** + * @title MiniMeToken Contract + * @author Jordi Baylina + * @dev This token contract's goal is to make it easy for anyone to clone this + * token using the token distribution at a given block, this will allow DAO's + * and DApps to upgrade their features in a decentralized manner without + * affecting the original token + * @dev It is ERC20 compliant, but still needs to under go further testing. + */ + +import "../common/Controlled.sol"; +import "./TokenController.sol"; +import "./ApproveAndCallFallBack.sol"; +import "./MiniMeTokenFactory.sol"; + +/** + * @dev The actual token contract, the default controller is the msg.sender + * that deploys the contract, so usually this token will be deployed by a + * token controller contract, which Giveth will call a "Campaign" + */ +contract MiniMeToken is Controlled { + + string public name; //The Token's name: e.g. DigixDAO Tokens + uint8 public decimals; //Number of decimals of the smallest unit + string public symbol; //An identifier: e.g. REP + string public version = "MMT_0.1"; //An arbitrary versioning scheme + + /** + * @dev `Checkpoint` is the structure that attaches a block number to a + * given value, the block number attached is the one that last changed the + * value + */ + struct Checkpoint { + + // `fromBlock` is the block number that the value was generated from + uint128 fromBlock; + + // `value` is the amount of tokens at a specific block number + uint128 value; + } + + // `parentToken` is the Token address that was cloned to produce this token; + // it will be 0x0 for a token that was not cloned + MiniMeToken public parentToken; + + // `parentSnapShotBlock` is the block number from the Parent Token that was + // used to determine the initial distribution of the Clone Token + uint public parentSnapShotBlock; + + // `creationBlock` is the block number that the Clone Token was created + uint public creationBlock; + + // `balances` is the map that tracks the balance of each address, in this + // contract when the balance changes the block number that the change + // occurred is also included in the map + mapping (address => Checkpoint[]) balances; + + // `allowed` tracks any extra transfer rights as in all ERC20 tokens + mapping (address => mapping (address => uint256)) allowed; + + // Tracks the history of the `totalSupply` of the token + Checkpoint[] totalSupplyHistory; + + // Flag that determines if the token is transferable or not. + bool public transfersEnabled; + + // The factory used to create new clone tokens + MiniMeTokenFactory public tokenFactory; + +//////////////// +// Constructor +//////////////// + + /** + * @notice Constructor to create a MiniMeToken + * @param _tokenFactory The address of the MiniMeTokenFactory contract that + * will create the Clone token contracts, the token factory needs to be + * deployed first + * @param _parentToken Address of the parent token, set to 0x0 if it is a + * new token + * @param _parentSnapShotBlock Block of the parent token that will + * determine the initial distribution of the clone token, set to 0 if it + * is a new token + * @param _tokenName Name of the new token + * @param _decimalUnits Number of decimals of the new token + * @param _tokenSymbol Token Symbol for the new token + * @param _transfersEnabled If true, tokens will be able to be transferred + */ + constructor( + address _tokenFactory, + address _parentToken, + uint _parentSnapShotBlock, + string memory _tokenName, + uint8 _decimalUnits, + string memory _tokenSymbol, + bool _transfersEnabled + ) + public + { + tokenFactory = MiniMeTokenFactory(_tokenFactory); + name = _tokenName; // Set the name + decimals = _decimalUnits; // Set the decimals + symbol = _tokenSymbol; // Set the symbol + parentToken = MiniMeToken(address(uint160(_parentToken))); + parentSnapShotBlock = _parentSnapShotBlock; + transfersEnabled = _transfersEnabled; + creationBlock = block.number; + } + + +/////////////////// +// ERC20 Methods +/////////////////// + + /** + * @notice Send `_amount` tokens to `_to` from `msg.sender` + * @param _to The address of the recipient + * @param _amount The amount of tokens to be transferred + * @return Whether the transfer was successful or not + */ + function transfer(address _to, uint256 _amount) public returns (bool success) { + require(transfersEnabled, "Transfers disabled"); + return doTransfer(msg.sender, _to, _amount); + } + + /** + * @notice Send `_amount` tokens to `_to` from `_from` on the condition it + * is approved by `_from` + * @param _from The address holding the tokens being transferred + * @param _to The address of the recipient + * @param _amount The amount of tokens to be transferred + * @return True if the transfer was successful + */ + function transferFrom( + address _from, + address _to, + uint256 _amount + ) + public + returns (bool success) + { + + // The controller of this contract can move tokens around at will, + // this is important to recognize! Confirm that you trust the + // controller of this contract, which in most situations should be + // another open source smart contract or 0x0 + if (msg.sender != controller) { + require(transfersEnabled, "Transfers disabled"); + + // The standard ERC 20 transferFrom functionality + if (allowed[_from][msg.sender] < _amount) { + return false; + } + allowed[_from][msg.sender] -= _amount; + } + return doTransfer(_from, _to, _amount); + } + + /** + * @dev This is the actual transfer function in the token contract, it can + * only be called by other functions in this contract. + * @param _from The address holding the tokens being transferred + * @param _to The address of the recipient + * @param _amount The amount of tokens to be transferred + * @return True if the transfer was successful + */ + function doTransfer( + address _from, + address _to, + uint _amount + ) + internal + returns(bool) + { + + if (_amount == 0) { + return true; + } + + require(parentSnapShotBlock < block.number, "Invalid block.number"); + + // Do not allow transfer to 0x0 or the token contract itself + require((_to != address(0)) && (_to != address(this)), "Invalid _to"); + + // If the amount being transfered is more than the balance of the + // account the transfer returns false + uint256 previousBalanceFrom = balanceOfAt(_from, block.number); + if (previousBalanceFrom < _amount) { + return false; + } + + // Alerts the token controller of the transfer + if (isContract(controller)) { + require(TokenController(controller).onTransfer(_from, _to, _amount), "Unauthorized transfer"); + } + + // First update the balance array with the new value for the address + // sending the tokens + updateValueAtNow(balances[_from], previousBalanceFrom - _amount); + + // Then update the balance array with the new value for the address + // receiving the tokens + uint256 previousBalanceTo = balanceOfAt(_to, block.number); + require(previousBalanceTo + _amount >= previousBalanceTo, "Balance overflow"); // Check for overflow + updateValueAtNow(balances[_to], previousBalanceTo + _amount); + + // An event to make the transfer easy to find on the blockchain + emit Transfer(_from, _to, _amount); + + return true; + } + + function doApprove( + address _from, + address _spender, + uint256 _amount + ) + internal + returns (bool) + { + require(transfersEnabled, "Transfers disabled"); + + // To change the approve amount you first have to reduce the addresses` + // allowance to zero by calling `approve(_spender,0)` if it is not + // already 0 to mitigate the race condition described here: + // https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + require((_amount == 0) || (allowed[_from][_spender] == 0), "Reset allowance first"); + + // Alerts the token controller of the approve function call + if (isContract(controller)) { + require(TokenController(controller).onApprove(_from, _spender, _amount), "Unauthorized approve"); + } + + allowed[_from][_spender] = _amount; + emit Approval(_from, _spender, _amount); + return true; + } + + /** + * @param _owner The address that's balance is being requested + * @return The balance of `_owner` at the current block + */ + function balanceOf(address _owner) external view returns (uint256 balance) { + return balanceOfAt(_owner, block.number); + } + + /** + * @notice `msg.sender` approves `_spender` to spend `_amount` tokens on + * its behalf. This is a modified version of the ERC20 approve function + * to be a little bit safer + * @param _spender The address of the account able to transfer the tokens + * @param _amount The amount of tokens to be approved for transfer + * @return True if the approval was successful + */ + function approve(address _spender, uint256 _amount) external returns (bool success) { + return doApprove(msg.sender, _spender, _amount); + } + + /** + * @dev This function makes it easy to read the `allowed[]` map + * @param _owner The address of the account that owns the token + * @param _spender The address of the account able to transfer the tokens + * @return Amount of remaining tokens of _owner that _spender is allowed + * to spend + */ + function allowance( + address _owner, + address _spender + ) + external + view + returns (uint256 remaining) + { + return allowed[_owner][_spender]; + } + /** + * @notice `msg.sender` approves `_spender` to send `_amount` tokens on + * its behalf, and then a function is triggered in the contract that is + * being approved, `_spender`. This allows users to use their tokens to + * interact with contracts in one function call instead of two + * @param _spender The address of the contract able to transfer the tokens + * @param _amount The amount of tokens to be approved for transfer + * @return True if the function call was successful + */ + function approveAndCall( + address _spender, + uint256 _amount, + bytes memory _extraData + ) + public + returns (bool success) + { + require(doApprove(msg.sender, _spender, _amount), "Approve failed"); + + ApproveAndCallFallBack(_spender).receiveApproval( + msg.sender, + _amount, + address(this), + _extraData + ); + + return true; + } + + /** + * @dev This function makes it easy to get the total number of tokens + * @return The total number of tokens + */ + function totalSupply() external view returns (uint) { + return totalSupplyAt(block.number); + } + + +//////////////// +// Query balance and totalSupply in History +//////////////// + + /** + * @dev Queries the balance of `_owner` at a specific `_blockNumber` + * @param _owner The address from which the balance will be retrieved + * @param _blockNumber The block number when the balance is queried + * @return The balance at `_blockNumber` + */ + function balanceOfAt( + address _owner, + uint _blockNumber + ) + public + view + returns (uint) + { + + // These next few lines are used when the balance of the token is + // requested before a check point was ever created for this token, it + // requires that the `parentToken.balanceOfAt` be queried at the + // genesis block for that token as this contains initial balance of + // this token + if ((balances[_owner].length == 0) || (balances[_owner][0].fromBlock > _blockNumber)) { + if (address(parentToken) != address(0)) { + return parentToken.balanceOfAt(_owner, min(_blockNumber, parentSnapShotBlock)); + } else { + // Has no parent + return 0; + } + + // This will return the expected balance during normal situations + } else { + return getValueAt(balances[_owner], _blockNumber); + } + } + + /** + * @notice Total amount of tokens at a specific `_blockNumber`. + * @param _blockNumber The block number when the totalSupply is queried + * @return The total amount of tokens at `_blockNumber` + */ + function totalSupplyAt(uint _blockNumber) public view returns(uint) { + + // These next few lines are used when the totalSupply of the token is + // requested before a check point was ever created for this token, it + // requires that the `parentToken.totalSupplyAt` be queried at the + // genesis block for this token as that contains totalSupply of this + // token at this block number. + if ((totalSupplyHistory.length == 0) || (totalSupplyHistory[0].fromBlock > _blockNumber)) { + if (address(parentToken) != address(0)) { + return parentToken.totalSupplyAt(min(_blockNumber, parentSnapShotBlock)); + } else { + return 0; + } + + // This will return the expected totalSupply during normal situations + } else { + return getValueAt(totalSupplyHistory, _blockNumber); + } + } + +//////////////// +// Clone Token Method +//////////////// + + /** + * @notice Creates a new clone token with the initial distribution being + * this token at `snapshotBlock` + * @param _cloneTokenName Name of the clone token + * @param _cloneDecimalUnits Number of decimals of the smallest unit + * @param _cloneTokenSymbol Symbol of the clone token + * @param _snapshotBlock Block when the distribution of the parent token is + * copied to set the initial distribution of the new clone token; + * if the block is zero than the actual block, the current block is used + * @param _transfersEnabled True if transfers are allowed in the clone + * @return The address of the new MiniMeToken Contract + */ + function createCloneToken( + string memory _cloneTokenName, + uint8 _cloneDecimalUnits, + string memory _cloneTokenSymbol, + uint _snapshotBlock, + bool _transfersEnabled + ) + public + returns(address) + { + uint snapshotBlock = _snapshotBlock; + if (snapshotBlock == 0) { + snapshotBlock = block.number; + } + MiniMeToken cloneToken = tokenFactory.createCloneToken( + address(this), + snapshotBlock, + _cloneTokenName, + _cloneDecimalUnits, + _cloneTokenSymbol, + _transfersEnabled + ); + + cloneToken.changeController(msg.sender); + + // An event to make the token easy to find on the blockchain + emit NewCloneToken(address(cloneToken), snapshotBlock); + return address(cloneToken); + } + +//////////////// +// Generate and destroy tokens +//////////////// + + /** + * @notice Generates `_amount` tokens that are assigned to `_owner` + * @param _owner The address that will be assigned the new tokens + * @param _amount The quantity of tokens generated + * @return True if the tokens are generated correctly + */ + function generateTokens( + address _owner, + uint _amount + ) + public + onlyController + returns (bool) + { + uint curTotalSupply = totalSupplyAt(block.number); + require(curTotalSupply + _amount >= curTotalSupply, "Total overflow"); // Check for overflow + uint previousBalanceTo = balanceOfAt(_owner, block.number); + require(previousBalanceTo + _amount >= previousBalanceTo, "Balance overflow"); // Check for overflow + updateValueAtNow(totalSupplyHistory, curTotalSupply + _amount); + updateValueAtNow(balances[_owner], previousBalanceTo + _amount); + emit Transfer(address(0), _owner, _amount); + return true; + } + + /** + * @notice Burns `_amount` tokens from `_owner` + * @param _owner The address that will lose the tokens + * @param _amount The quantity of tokens to burn + * @return True if the tokens are burned correctly + */ + function destroyTokens( + address _owner, + uint _amount + ) + public + onlyController + returns (bool) + { + uint curTotalSupply = totalSupplyAt(block.number); + require(curTotalSupply >= _amount, "No enough supply"); + uint previousBalanceFrom = balanceOfAt(_owner, block.number); + require(previousBalanceFrom >= _amount, "No enough balance"); + updateValueAtNow(totalSupplyHistory, curTotalSupply - _amount); + updateValueAtNow(balances[_owner], previousBalanceFrom - _amount); + emit Transfer(_owner, address(0), _amount); + return true; + } + +//////////////// +// Enable tokens transfers +//////////////// + + /** + * @notice Enables token holders to transfer their tokens freely if true + * @param _transfersEnabled True if transfers are allowed in the clone + */ + function enableTransfers(bool _transfersEnabled) public onlyController { + transfersEnabled = _transfersEnabled; + } + +//////////////// +// Internal helper functions to query and set a value in a snapshot array +//////////////// + + /** + * @dev `getValueAt` retrieves the number of tokens at a given block number + * @param checkpoints The history of values being queried + * @param _block The block number to retrieve the value at + * @return The number of tokens being queried + */ + function getValueAt( + Checkpoint[] storage checkpoints, + uint _block + ) + internal + view + returns (uint) + { + if (checkpoints.length == 0) { + return 0; + } + + // Shortcut for the actual value + if (_block >= checkpoints[checkpoints.length-1].fromBlock) { + return checkpoints[checkpoints.length-1].value; + } + if (_block < checkpoints[0].fromBlock) { + return 0; + } + + // Binary search of the value in the array + uint min = 0; + uint max = checkpoints.length-1; + while (max > min) { + uint mid = (max + min + 1) / 2; + if (checkpoints[mid].fromBlock<=_block) { + min = mid; + } else { + max = mid-1; + } + } + return checkpoints[min].value; + } + + /** + * @dev `updateValueAtNow` used to update the `balances` map and the + * `totalSupplyHistory` + * @param checkpoints The history of data being updated + * @param _value The new number of tokens + */ + function updateValueAtNow(Checkpoint[] storage checkpoints, uint _value) internal { + if ((checkpoints.length == 0) || (checkpoints[checkpoints.length -1].fromBlock < block.number)) { + Checkpoint storage newCheckPoint = checkpoints[checkpoints.length++]; + newCheckPoint.fromBlock = uint128(block.number); + newCheckPoint.value = uint128(_value); + } else { + Checkpoint storage oldCheckPoint = checkpoints[checkpoints.length-1]; + oldCheckPoint.value = uint128(_value); + } + } + + /** + * @dev Internal function to determine if an address is a contract + * @param _addr The address being queried + * @return True if `_addr` is a contract + */ + function isContract(address _addr) internal view returns(bool) { + uint size; + if (_addr == address(0)){ + return false; + } + assembly { + size := extcodesize(_addr) + } + return size>0; + } + + /** + * @dev Helper function to return a min betwen the two uints + */ + function min(uint a, uint b) internal pure returns (uint) { + return a < b ? a : b; + } + + /** + * @notice The fallback function: If the contract's controller has not been + * set to 0, then the `proxyPayment` method is called which relays the + * ether and creates tokens as described in the token controller contract + */ + function () external payable { + require(isContract(controller), "Deposit unallowed"); + require(TokenController(controller).proxyPayment.value(msg.value)(msg.sender), "Deposit denied"); + } + +////////// +// Safety Methods +////////// + + /** + * @notice This method can be used by the controller to extract mistakenly + * sent tokens to this contract. + * @param _token The address of the token contract that you want to recover + * set to 0 in case you want to extract ether. + */ + function claimTokens(address _token) public onlyController { + if (_token == address(0)) { + controller.transfer(address(this).balance); + return; + } + + MiniMeToken token = MiniMeToken(address(uint160(_token))); + uint balance = token.balanceOf(address(this)); + token.transfer(controller, balance); + emit ClaimedTokens(_token, controller, balance); + } + +//////////////// +// Events +//////////////// + event ClaimedTokens(address indexed _token, address indexed _controller, uint _amount); + event Transfer(address indexed _from, address indexed _to, uint256 _amount); + event NewCloneToken(address indexed _cloneToken, uint snapshotBlock); + event Approval( + address indexed _owner, + address indexed _spender, + uint256 _amount + ); + +} diff --git a/contracts/token/MiniMeTokenFactory.sol b/contracts/token/MiniMeTokenFactory.sol new file mode 100644 index 0000000..d8e7bbe --- /dev/null +++ b/contracts/token/MiniMeTokenFactory.sol @@ -0,0 +1,49 @@ +pragma solidity >=0.5.0 <0.6.0; + +import "./MiniMeToken.sol"; + +//////////////// +// MiniMeTokenFactory +//////////////// + +/** + * @dev This contract is used to generate clone contracts from a contract. + * In solidity this is the way to create a contract from a contract of the + * same class + */ +contract MiniMeTokenFactory { + + /** + * @notice Update the DApp by creating a new token with new functionalities + * the msg.sender becomes the controller of this clone token + * @param _parentToken Address of the token being cloned + * @param _snapshotBlock Block of the parent token that will + * determine the initial distribution of the clone token + * @param _tokenName Name of the new token + * @param _decimalUnits Number of decimals of the new token + * @param _tokenSymbol Token Symbol for the new token + * @param _transfersEnabled If true, tokens will be able to be transferred + * @return The address of the new token contract + */ + function createCloneToken( + address _parentToken, + uint _snapshotBlock, + string memory _tokenName, + uint8 _decimalUnits, + string memory _tokenSymbol, + bool _transfersEnabled + ) public returns (MiniMeToken) { + MiniMeToken newToken = new MiniMeToken( + address(this), + _parentToken, + _snapshotBlock, + _tokenName, + _decimalUnits, + _tokenSymbol, + _transfersEnabled + ); + + newToken.changeController(msg.sender); + return newToken; + } +} \ No newline at end of file diff --git a/contracts/token/StandardToken.sol b/contracts/token/StandardToken.sol index b80c513..484f5a8 100644 --- a/contracts/token/StandardToken.sol +++ b/contracts/token/StandardToken.sol @@ -1,10 +1,10 @@ -pragma solidity ^0.4.23; +pragma solidity >=0.5.0 <0.6.0; import "./ERC20Token.sol"; contract StandardToken is ERC20Token { - uint256 private supply; + uint256 public totalSupply; mapping (address => uint256) balances; mapping (address => mapping (address => uint256)) allowed; @@ -63,14 +63,6 @@ contract StandardToken is ERC20Token { return balances[_owner]; } - function totalSupply() - external - view - returns(uint256 currentTotalSupply) - { - return supply; - } - function mint( address _to, uint256 _amount @@ -78,8 +70,8 @@ contract StandardToken is ERC20Token { internal { balances[_to] += _amount; - supply += _amount; - emit Transfer(0x0, _to, _amount); + totalSupply += _amount; + emit Transfer(address(0x0), _to, _amount); } function transfer( diff --git a/contracts/token/TestToken.sol b/contracts/token/TestToken.sol index 2afff21..6107bfd 100644 --- a/contracts/token/TestToken.sol +++ b/contracts/token/TestToken.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.4.23; +pragma solidity >=0.5.0 <0.6.0; import "./StandardToken.sol"; diff --git a/contracts/token/TokenController.sol b/contracts/token/TokenController.sol new file mode 100644 index 0000000..6eab146 --- /dev/null +++ b/contracts/token/TokenController.sol @@ -0,0 +1,33 @@ +pragma solidity >=0.5.0 <0.6.0; +/** + * @dev The token controller contract must implement these functions + */ +interface TokenController { + /** + * @notice Called when `_owner` sends ether to the MiniMe Token contract + * @param _owner The address that sent the ether to create tokens + * @return True if the ether is accepted, false if it throws + */ + function proxyPayment(address _owner) external payable returns(bool); + + /** + * @notice Notifies the controller about a token transfer allowing the + * controller to react if desired + * @param _from The origin of the transfer + * @param _to The destination of the transfer + * @param _amount The amount of the transfer + * @return False if the controller does not authorize the transfer + */ + function onTransfer(address _from, address _to, uint _amount) external returns(bool); + + /** + * @notice Notifies the controller about an approval allowing the + * controller to react if desired + * @param _owner The address that calls `approve()` + * @param _spender The spender in the `approve()` call + * @param _amount The amount in the `approve()` call + * @return False if the controller does not authorize the approval + */ + function onApprove(address _owner, address _spender, uint _amount) external + returns(bool); +} diff --git a/embark.json b/embark.json index 7bd3d48..a86fd78 100644 --- a/embark.json +++ b/embark.json @@ -2,16 +2,17 @@ "contracts": ["contracts/**"], "app": { "js/dapp.js": ["app/dapp.js"], + "js/index.js": ["app/index.js"], "index.html": "app/index.html", "images/": ["app/images/**"] - }, +}, "buildDir": "dist/", "config": "config/", "versions": { "web3": "1.0.0-beta", - "solc": "0.4.23", - "ipfs-api": "17.2.4" + "solc": "0.5.4", + "ipfs-api": "17.2.4", + "p-iteration": "1.1.7" }, - "plugins": { - } + "plugins": {} } diff --git a/package.json b/package.json index 9acb074..97a35d5 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "description": "", "scripts": { "solidity-coverage": "./node_modules/.bin/solidity-coverage", - "test": "embark test" + "test": "embark test", + "lint": "eslint" }, "repository": { "type": "git", @@ -16,9 +17,17 @@ "url": "https://github.com/status-im/contracts/issues" }, "homepage": "https://github.com/status-im/contracts#readme", - "dependencies": { + "devDependencies": { + "@babel/core": "^7.0.0", + "@babel/preset-env": "^7.2.3" + }, + "dependencies": { + "elliptic-curve": "^0.1.0", + "ethereumjs-util": "^5.1.5", "react": "^16.3.2", - "react-bootstrap": "^0.32.1", - "react-dom": "^16.3.2" + "react-blockies": "^1.4.0", + "react-bootstrap": "0.32.1", + "react-dom": "^16.3.2", + "web3": "^1.0.0-beta.34" } } diff --git a/test/abstract/controlled.js b/test/abstract/controlled.js new file mode 100644 index 0000000..95e0197 --- /dev/null +++ b/test/abstract/controlled.js @@ -0,0 +1,31 @@ + +exports.Test = (Controlled) => { + describe("Controlled", async function() { + this.timeout(0); + var accounts; + before(function(done) { + web3.eth.getAccounts().then(function (res) { + accounts = res; + done(); + }); + }); + + + it("should start with msg.sender as controller", async function() { + var controller = await Controlled.methods.controller().call(); + assert(controller, accounts[0]); + }); + + it("should allow controller to set new controller", async function() { + await Controlled.methods.changeController(accounts[1]).send({from: accounts[0]}); + var controller = await Controlled.methods.controller().call(); + assert(controller, accounts[1]); + }); + + it("should set back to original controller", async function() { + await Controlled.methods.changeController(accounts[0]).send({from: accounts[1]}); + var controller = await Controlled.methods.controller().call(); + assert(controller, accounts[0]); + }); + }); +} \ No newline at end of file diff --git a/test/abstract/erc20tokenspec.js b/test/abstract/erc20tokenspec.js new file mode 100644 index 0000000..e5f0637 --- /dev/null +++ b/test/abstract/erc20tokenspec.js @@ -0,0 +1,85 @@ + +const ERC20Receiver = require('Embark/contracts/ERC20Receiver'); + +exports.config = { + contracts : { + "ERC20Receiver": { + } + } +} + +exports.Test = (ERC20Token) => { + describe("ERC20Token", function() { + + var accounts; + before(function(done) { + web3.eth.getAccounts().then(function (res) { + accounts = res; + done(); + }); + }); + + it("should transfer 1 token", async function() { + let initialBalance0 = await ERC20Token.methods.balanceOf(accounts[0]).call(); + let initialBalance1 = await ERC20Token.methods.balanceOf(accounts[1]).call(); + await ERC20Token.methods.transfer(accounts[1],1).send({from: accounts[0]}); + let result0 = await ERC20Token.methods.balanceOf(accounts[0]).call(); + let result1 = await ERC20Token.methods.balanceOf(accounts[1]).call(); + + assert.equal(result0, +initialBalance0-1, "account 0 balance unexpected"); + assert.equal(result1, +initialBalance1+1, "account 1 balance unexpected"); + }); + + it("should set approved amount", async function() { + await ERC20Token.methods.approve(accounts[2],10000000).send({from: accounts[0]}); + let result = await ERC20Token.methods.allowance(accounts[0], accounts[2]).call(); + assert.equal(result, 10000000); + }); + + it("should consume allowance amount", async function() { + let initialAllowance = await ERC20Token.methods.allowance(accounts[0], accounts[2]).call(); + await ERC20Token.methods.transferFrom(accounts[0], accounts[0],1).send({from: accounts[2]}); + let result = await ERC20Token.methods.allowance(accounts[0], accounts[2]).call(); + + assert.equal(result, +initialAllowance-1); + }); + + it("should transfer approved amount", async function() { + let initialBalance0 = await ERC20Token.methods.balanceOf(accounts[0]).call(); + let initialBalance1 = await ERC20Token.methods.balanceOf(accounts[1]).call(); + await ERC20Token.methods.transferFrom(accounts[0], accounts[1],1).send({from: accounts[2]}); + let result0 = await ERC20Token.methods.balanceOf(accounts[0]).call(); + let result1 = await ERC20Token.methods.balanceOf(accounts[1]).call(); + + assert.equal(result0, +initialBalance0-1); + assert.equal(result1, +initialBalance1+1); + }); + + + it("should unset approved amount", async function() { + await ERC20Token.methods.approve(accounts[2],0).send({from: accounts[0]}); + let result = await ERC20Token.methods.allowance(accounts[0], accounts[2]).call(); + assert.equal(result, 0); + }); + + it("should deposit approved amount to contract ERC20Receiver", async function() { + //ERC20Receiver = await ERC20Receiver.deploy().send(); + //console.log(ERC20Receiver.address); + await ERC20Token.methods.approve(ERC20Receiver.address, 10).send({from: accounts[0]}); + await ERC20Receiver.methods.depositToken(ERC20Token.address, 10).send({from: accounts[0]}); + let result = await ERC20Receiver.methods.tokenBalanceOf(ERC20Token.address, accounts[0]).call(); + assert.equal(result, 10, "ERC20Receiver.tokenBalanceOf("+ERC20Token.address+","+accounts[0]+") wrong"); + }); + + it("should witdraw approved amount from contract ERC20Receiver", async function() { + let tokenBalance = await ERC20Receiver.methods.tokenBalanceOf(ERC20Token.address, accounts[0]).call(); + await ERC20Receiver.methods.withdrawToken(ERC20Token.address, tokenBalance).send({from: accounts[0]}); + tokenBalance = await ERC20Receiver.methods.tokenBalanceOf(ERC20Token.address, accounts[0]).call(); + assert.equal(tokenBalance, 0, "ERC20Receiver.tokenBalanceOf("+ERC20Token.address+","+accounts[0]+") wrong"); + }); + + //TODO: include checks for expected events fired + + + }); +} \ No newline at end of file diff --git a/test/erc20token.js b/test/erc20token.js deleted file mode 100644 index 3a0c0c0..0000000 --- a/test/erc20token.js +++ /dev/null @@ -1,81 +0,0 @@ -describe("ERC20Token", async function() { - this.timeout(0); - var ERC20Token; - var accountsArr; - before(function(done) { - this.timeout(0); - var contractsConfig = { - "TestToken": { }, - "ERC20Receiver": { } - }; - EmbarkSpec.deployAll(contractsConfig, async function(accounts) { - ERC20Token = TestToken; - accountsArr = accounts; - for(i=0;i { + await MiniMeToken.methods.generateTokens(accounts[1], 10).send(); + assert.equal(await MiniMeToken.methods.totalSupply().call(), 10); + assert.equal(await MiniMeToken.methods.balanceOf(accounts[1]).call(), 10); + b[0] = await web3.eth.getBlockNumber(); + }); + + it('should transfer tokens from address 1 to address 3', async () => { + await MiniMeToken.methods.transfer(accounts[3], 1).send({from: accounts[1]}); + assert.equal(await MiniMeToken.methods.totalSupply().call(), 10); + assert.equal(await MiniMeToken.methods.balanceOf(accounts[1]).call(), 9); + assert.equal(await MiniMeToken.methods.balanceOf(accounts[3]).call(), 1); + b[1] = await web3.eth.getBlockNumber(); + }); + + it('should destroy 3 tokens from 1 and 1 from 2', async () => { + await MiniMeToken.methods.destroyTokens(accounts[1], 3).send({ from: accounts[0] }); + assert.equal(await MiniMeToken.methods.totalSupply().call(), 7); + assert.equal(await MiniMeToken.methods.balanceOf(accounts[1]).call(), 6); + b[2] = await web3.eth.getBlockNumber(); + }); + + + it('should create the clone token', async () => { + const miniMeTokenCloneTx = await MiniMeToken.methods.createCloneToken( + 'Clone Token 1', + 18, + 'MMTc', + 0, + true).send({ from: accounts[0]}); + let addr = miniMeTokenCloneTx.events.NewCloneToken.returnValues[0]; + miniMeTokenClone = new web3.eth.Contract(MiniMeToken._jsonInterface, addr); + + b[3] = await web3.eth.getBlockNumber(); + + assert.equal(await miniMeTokenClone.methods.parentToken().call(), MiniMeToken.address); + assert.equal(await miniMeTokenClone.methods.parentSnapShotBlock().call(), b[3]); + assert.equal(await miniMeTokenClone.methods.totalSupply().call(), 7); + assert.equal(await MiniMeToken.methods.balanceOf(accounts[1]).call(), 6); + + assert.equal(await miniMeTokenClone.methods.totalSupplyAt(b[2]).call(), 7); + assert.equal(await miniMeTokenClone.methods.balanceOfAt(accounts[3], b[2]).call(), 1); + }); + + it('should move tokens in the clone token from 2 to 3', async () => { + + await miniMeTokenClone.methods.transfer(accounts[2], 4).send({ from: accounts[1], gas: 1000000 }); + b[4] = await web3.eth.getBlockNumber(); + + assert.equal(await MiniMeToken.methods.balanceOfAt(accounts[1], b[3]).call(), 6); + assert.equal(await MiniMeToken.methods.balanceOfAt(accounts[2], b[3]).call(), 0); + assert.equal(await miniMeTokenClone.methods.totalSupply().call(), 7); + assert.equal(await miniMeTokenClone.methods.balanceOf(accounts[1]).call(), 2); + assert.equal(await miniMeTokenClone.methods.balanceOf(accounts[2]).call(), 4); + assert.equal(await miniMeTokenClone.methods.balanceOfAt(accounts[1], b[3]).call(), 6); + assert.equal(await miniMeTokenClone.methods.balanceOfAt(accounts[2], b[3]).call(), 0); + assert.equal(await miniMeTokenClone.methods.balanceOfAt(accounts[1], b[2]).call(), 6); + assert.equal(await miniMeTokenClone.methods.balanceOfAt(accounts[2], b[2]).call(), 0); + assert.equal(await miniMeTokenClone.methods.totalSupplyAt(b[3]).call(), 7); + assert.equal(await miniMeTokenClone.methods.totalSupplyAt(b[2]).call(), 7); + }); + + it('should create tokens in the child token', async () => { + await miniMeTokenClone.methods.generateTokens(accounts[1], 10).send({ from: accounts[0], gas: 1000000}); + assert.equal(await miniMeTokenClone.methods.totalSupply().call(), 17); + assert.equal(await miniMeTokenClone.methods.balanceOf(accounts[1]).call(), 12); + assert.equal(await miniMeTokenClone.methods.balanceOf(accounts[2]).call(), 4); + }); + + + it("should mint balances for ERC20TokenSpec", async function() { + let initialBalance = 7 * 10 ^ 18; + for(i=0;i exports.zeroAddress = '0x0000000000000000000000000000000000000000'; exports.zeroBytes32 = "0x0000000000000000000000000000000000000000000000000000000000000000"; +exports.timeUnits = { + seconds: 1, + minutes: 60, + hours: 60 * 60, + days: 24 * 60 * 60, + weeks: 7 * 24 * 60 * 60, + years: 365 * 24 * 60 * 60 +} + exports.ensureException = function(error) { assert(isException(error), error.toString()); }; @@ -195,3 +204,36 @@ function isException(error) { let strError = error.toString(); return strError.includes('invalid opcode') || strError.includes('invalid JUMP') || strError.includes('revert'); } + +exports.increaseTime = async (amount) => { + return new Promise(function(resolve, reject) { + web3.currentProvider.sendAsync( + { + jsonrpc: '2.0', + method: 'evm_increaseTime', + params: [+amount], + id: new Date().getSeconds() + }, + async (error) => { + if (error) { + console.log(error); + return reject(err); + } + await web3.currentProvider.sendAsync( + { + jsonrpc: '2.0', + method: 'evm_mine', + params: [], + id: new Date().getSeconds() + }, (error) => { + if (error) { + console.log(error); + return reject(err); + } + resolve(); + } + ) + } + ) + }); +}