diff --git a/.gitignore b/.gitignore index 33d2b94..5c20c22 100644 --- a/.gitignore +++ b/.gitignore @@ -36,6 +36,7 @@ coverage.json # node node_modules/ npm-debug.log +package-lock.json # other .vs/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..fc827f3 --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +# status.im contracts + +Usage: + ``` + npm install -g embark + git clone https://github.com/status-im/contracts.git + cd contracts + npm install + embark simulator + embark test + embark run + ``` + +| Contract | Deploy | Test | UI | +| -------------------------------------- | ------ | ---- | --- | +| token/TestToken | Yes | Yes | Yes | +| token/ERC20Token | No | Yes | Yes | \ No newline at end of file diff --git a/app/components/accountlist.css b/app/components/accountlist.css new file mode 100644 index 0000000..35b9ddd --- /dev/null +++ b/app/components/accountlist.css @@ -0,0 +1,37 @@ +.identicon { + border-radius: 50%; +} + +.selectedIdenticon { + border-radius: 50%; + overflow: hidden; + float: left; + margin: 7px 0; +} + +.accountHexString { + margin-left: 7px; + width: 267px; + overflow: hidden; + text-overflow: ellipsis; + display:inline-block; +} + +.accountBalance { + margin-left: 10px; + overflow: hidden; + display: inline-block; + width:77px; + text-align: center; + text-overflow: ellipsis; +} + +.accountList { + float: left; + margin-left: 10px; +} + +.account { + display: flex; + align-items: center; +} \ No newline at end of file diff --git a/app/components/accountlist.js b/app/components/accountlist.js new file mode 100644 index 0000000..fbd3cc7 --- /dev/null +++ b/app/components/accountlist.js @@ -0,0 +1,112 @@ +import web3 from "Embark/web3" +import EmbarkJS from 'Embark/EmbarkJS'; +import React from 'react'; +import { Nav, MenuItem , NavDropdown} from 'react-bootstrap'; +import Blockies from 'react-blockies'; + +import './accountlist.css'; + +class AccList extends React.Component { + + constructor(props) { + super(props); + this.state = { + classNameNavDropdown: props.classNameNavDropdown, + defaultAccount: "0x0000000000000000000000000000000000000000", + addresses: [], + balances: [] + } + __embarkContext.execWhenReady(() => { + this.load() + }); + } + + + load() { + web3.eth.getAccounts((err, addresses) => { + if (addresses) { + var defaultAccount = web3.eth.defaultAccount; + if(!defaultAccount){ + web3.eth.defaultAccount = addresses[0]; + } + + var balances = []; + balances.length == addresses.length; + addresses.forEach((address, index) => { + web3.eth.getBalance(address, 'latest', (err, balance) => { + balances[index] = balance; + if(index+1 == balances.length){ + this.setState({ + balances: balances + }); + } + }) + }) + this.setState({ + defaultAccount: defaultAccount, + addresses: addresses + }); + + } else { + console.log("No addresses available."); + } + + }) + } + setDefaultAccount(index) { + var defaultAcc = this.state.addresses[index]; + if(defaultAcc){ + web3.eth.defaultAccount = defaultAcc; + this.setState({defaultAccount: defaultAcc }); + } else { + console.log("invalid account") + } + } + + render() { + + var accsTitle; + var accsList = []; + if (this.state.addresses) { + accsTitle = this.state.defaultAccount; + this.state.addresses.forEach( + (name, index) => { + accsList.push( + this.setDefaultAccount(index) }> +
+
+ +
+
+ {name} +
+
+ Ξ {this.state.balances[index] / (10**18)} +
+
+
); + } + ) + } + + return ( + +
+
+ +
+
+ +
+
+
+ ) + } + + } + + export default AccList; \ No newline at end of file diff --git a/app/components/erc20token.js b/app/components/erc20token.js new file mode 100644 index 0000000..6695adf --- /dev/null +++ b/app/components/erc20token.js @@ -0,0 +1,131 @@ +import EmbarkJS from 'Embark/EmbarkJS'; +import ERC20Token from 'Embark/contracts/ERC20Token'; +import React from 'react'; +import { Form, FormGroup, FormControl, HelpBlock, Button } from 'react-bootstrap'; + +class ERC20TokenUI extends React.Component { + + constructor(props) { + super(props); + this.state = { + + balanceOf: 0, + transferTo: "", + transferAmount: 0, + logs: [] + } + } + + contractAddress(e){ + e.preventDefault(); + var tokenAddress = e.target.value; + ERC20Token.options.address = tokenAddress; + } + + update_transferTo(e){ + this.setState({transferTo: e.target.value}); + } + + update_transferAmount(e){ + this.setState({transferAmount: e.target.value}); + } + + 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+")"); + } + + approve(e){ + var to = this.state.transferTo; + var amount = this.state.transferAmount; + 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 + ")"); + } + + + _addToLog(txt){ + this.state.logs.push(txt); + this.setState({logs: this.state.logs}); + } + + render(){ + return ( + +

Set token contract address

+
+ + this.contractAddress(e)} /> + +
+ + +

Read account token balance

+
+ + + + + +
+ +

Transfer/Approve token balance

+
+ + + + + + +
+ +

Contract Calls

+

Javascript calls being made:

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

{item}

) + } +
+
+ ); + } + } + + export default ERC20TokenUI; \ No newline at end of file diff --git a/app/components/testtoken.js b/app/components/testtoken.js new file mode 100644 index 0000000..3618caa --- /dev/null +++ b/app/components/testtoken.js @@ -0,0 +1,88 @@ +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/components/topnavbar.js b/app/components/topnavbar.js new file mode 100644 index 0000000..d8bc689 --- /dev/null +++ b/app/components/topnavbar.js @@ -0,0 +1,33 @@ +import EmbarkJS from 'Embark/EmbarkJS'; +import React from 'react'; +import { Navbar, NavItem, Nav, MenuItem , NavDropdown} from 'react-bootstrap'; +import AccountList from './accountList'; + +class TopNavbar extends React.Component { + + constructor(props) { + super(props); + this.state = { + + } + + } + + render(){ + + return ( + + + + + Status.im Demo + + + + + + ); + } + } + + export default TopNavbar; \ No newline at end of file diff --git a/app/dapp.css b/app/dapp.css new file mode 100644 index 0000000..0fd02eb --- /dev/null +++ b/app/dapp.css @@ -0,0 +1,63 @@ +.navbar { + +} + +.accounts { + float: right; + margin-right: 17px; + font-family: monospace; +} + +.identicon { + border-radius: 50%; +} + + +.logs { + background-color: black; + font-size: 14px; + color: white; + font-weight: bold; + padding: 10px; + border-radius: 8px; +} + +.tab-content { + border-left: 1px solid #ddd; + border-right: 1px solid #ddd; + border-bottom: 1px solid #ddd; + padding: 10px; + margin: 0px; +} + +.nav-tabs { + margin-bottom: 0; +} + +.status-offline { + vertical-align: middle; + margin-left: 5px; + margin-top: 4px; + width: 12px; + height: 12px; + background: red; + -moz-border-radius: 10px; + -webkit-border-radius: 10px; + border-radius: 10px; +} + +.status-online { + vertical-align: middle; + margin-left: 5px; + margin-top: 4px; + width: 12px; + height: 12px; + background: mediumseagreen; + -moz-border-radius: 10px; + -webkit-border-radius: 10px; + border-radius: 10px; +} + +input.form-control { + margin: 5px; +} \ No newline at end of file diff --git a/app/dapp.js b/app/dapp.js new file mode 100644 index 0000000..a0744b3 --- /dev/null +++ b/app/dapp.js @@ -0,0 +1,51 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Tabs, Tab } from 'react-bootstrap'; + +import EmbarkJS from 'Embark/EmbarkJS'; +import TopNavbar from './components/topnavbar'; +import TestTokenUI from './components/testtoken'; +import ERC20TokenUI from './components/erc20token'; + +import './dapp.css'; + +class App extends React.Component { + + constructor(props) { + super(props); + + + } + + componentDidMount(){ + __embarkContext.execWhenReady(() => { + + }); + } + + + _renderStatus(title, available) { + let className = available ? 'pull-right status-online' : 'pull-right status-offline'; + return + {title} + + ; + } + + render(){ + return ( +
+ + + + + + + + + +
); + } +} + +ReactDOM.render(, document.getElementById('app')); diff --git a/app/index.html b/app/index.html new file mode 100644 index 0000000..1fb8607 --- /dev/null +++ b/app/index.html @@ -0,0 +1,12 @@ + + + Status.im - Contracts + + + + +
+
+ + + diff --git a/config/blockchain.json b/config/blockchain.json index 9dccb92..638c816 100644 --- a/config/blockchain.json +++ b/config/blockchain.json @@ -9,12 +9,12 @@ "maxpeers": 0, "rpcHost": "localhost", "rpcPort": 8545, - "rpcCorsDomain": "http://localhost:8000", + "rpcCorsDomain": "auto", "account": { "password": "config/development/password" }, "targetGasLimit": 8000000, - "wsOrigins": "http://localhost:8000", + "wsOrigins": "auto", "wsRPC": true, "wsHost": "localhost", "wsPort": 8546, diff --git a/config/contracts.json b/config/contracts.json index 3ebb190..87eb4db 100644 --- a/config/contracts.json +++ b/config/contracts.json @@ -15,6 +15,15 @@ ], "gas": "auto", "contracts": { + "ERC20Receiver": { + "deploy": false + }, + "MiniMeToken": { + "deploy": false + }, + "MiniMeTokenFactory": { + "deploy": true + } } } } diff --git a/config/development/genesis.json b/config/development/genesis.json index 4b6ce0d..1a9501b 100644 --- a/config/development/genesis.json +++ b/config/development/genesis.json @@ -1,6 +1,8 @@ { "config": { - "homesteadBlock": 1 + "homesteadBlock": 1, + "byzantiumBlock": 1, + "daoForkSupport": true }, "nonce": "0x0000000000000042", "difficulty": "0x0", diff --git a/contracts/common/Controlled.sol b/contracts/common/Controlled.sol index d2773f3..7b615b6 100644 --- a/contracts/common/Controlled.sol +++ b/contracts/common/Controlled.sol @@ -10,7 +10,7 @@ contract Controlled { address public controller; - constructor() public { + constructor() internal { controller = msg.sender; } diff --git a/contracts/common/MessageSigned.sol b/contracts/common/MessageSigned.sol new file mode 100644 index 0000000..5a5a262 --- /dev/null +++ b/contracts/common/MessageSigned.sol @@ -0,0 +1,77 @@ +pragma solidity ^0.4.21; + +/** + * @notice Uses ethereum signed messages + */ +contract MessageSigned { + + constructor() internal { + + } + + /** + * @notice recovers address who signed the message + * @param _signHash operation ethereum signed message hash + * @param _messageSignature message `_signHash` signature + */ + function recoverAddress( + bytes32 _signHash, + bytes _messageSignature + ) + pure + internal + returns(address) + { + uint8 v; + bytes32 r; + bytes32 s; + (v,r,s) = signatureSplit(_messageSignature); + return ecrecover( + _signHash, + v, + r, + s + ); + } + + /** + * @notice Hash a hash with `"\x19Ethereum Signed Message:\n32"` + * @param _hash Sign to hash. + * @return signHash Hash to be signed. + */ + function getSignHash( + bytes32 _hash + ) + pure + internal + returns (bytes32 signHash) + { + signHash = keccak256("\x19Ethereum Signed Message:\n32", _hash); + } + + /** + * @dev divides bytes signature into `uint8 v, bytes32 r, bytes32 s` + */ + function signatureSplit(bytes _signature) + pure + internal + returns (uint8 v, bytes32 r, bytes32 s) + { + // The signature format is a compact form of: + // {bytes32 r}{bytes32 s}{uint8 v} + // Compact means, uint8 is not padded to 32 bytes. + assembly { + r := mload(add(_signature, 32)) + s := mload(add(_signature, 64)) + // Here we are loading the last 32 bytes, including 31 bytes + // of 's'. There is no 'mload8' to do this. + // + // 'byte' is not working due to the Solidity parser, so lets + // use the second best option, 'and' + v := and(mload(add(_signature, 65)), 0xff) + } + + require(v == 27 || v == 28); + } + +} \ No newline at end of file diff --git a/contracts/common/Owned.sol b/contracts/common/Owned.sol index 177bf0f..18f03e7 100644 --- a/contracts/common/Owned.sol +++ b/contracts/common/Owned.sol @@ -14,7 +14,7 @@ contract Owned { address public owner; /// @notice The Constructor assigns the message sender to be `owner` - constructor() public { + constructor() internal { owner = msg.sender; } diff --git a/contracts/token/ApproveAndCallFallBack.sol b/contracts/token/ApproveAndCallFallBack.sol index 01bbf9b..864b988 100644 --- a/contracts/token/ApproveAndCallFallBack.sol +++ b/contracts/token/ApproveAndCallFallBack.sol @@ -1,5 +1,10 @@ -pragma solidity ^0.4.14; +pragma solidity ^0.4.23; -contract ApproveAndCallFallBack { - function receiveApproval(address from, uint256 _amount, address _token, bytes _data) public ; +interface ApproveAndCallFallBack { + function receiveApproval( + address from, + uint256 _amount, + address _token, + bytes _data + ) external; } diff --git a/contracts/token/ERC20Receiver.sol b/contracts/token/ERC20Receiver.sol new file mode 100644 index 0000000..9a6af70 --- /dev/null +++ b/contracts/token/ERC20Receiver.sol @@ -0,0 +1,90 @@ +pragma solidity ^0.4.23; + +import "./ERC20Token.sol"; + +contract ERC20Receiver { + + event TokenDeposited(address indexed token, address indexed sender, uint256 amount); + event TokenWithdrawn(address indexed token, address indexed sender, uint256 amount); + + mapping (address => mapping(address => uint256)) tokenBalances; + + constructor() public { + + } + + function depositToken( + ERC20Token _token + ) + external + { + _depositToken( + msg.sender, + _token, + _token.allowance( + msg.sender, + address(this) + ) + ); + } + + function withdrawToken( + ERC20Token _token, + uint256 _amount + ) + external + { + _withdrawToken(msg.sender, _token, _amount); + } + + function depositToken( + ERC20Token _token, + uint256 _amount + ) + external + { + require(_token.allowance(msg.sender, address(this)) >= _amount); + _depositToken(msg.sender, _token, _amount); + } + + function tokenBalanceOf( + ERC20Token _token, + address _from + ) + external + view + returns(uint256 fromTokenBalance) + { + return tokenBalances[address(_token)][_from]; + } + + function _depositToken( + address _from, + ERC20Token _token, + uint256 _amount + ) + private + { + require(_amount > 0); + if (_token.transferFrom(_from, address(this), _amount)) { + tokenBalances[address(_token)][_from] += _amount; + emit TokenDeposited(address(_token), _from, _amount); + } + } + + function _withdrawToken( + address _from, + ERC20Token _token, + uint256 _amount + ) + private + { + require(_amount > 0); + require(tokenBalances[address(_token)][_from] >= _amount); + tokenBalances[address(_token)][_from] -= _amount; + require(_token.transfer(_from, _amount)); + emit TokenWithdrawn(address(_token), _from, _amount); + } + + +} \ No newline at end of file diff --git a/contracts/token/ERC20Token.sol b/contracts/token/ERC20Token.sol index c210342..ff8de15 100644 --- a/contracts/token/ERC20Token.sol +++ b/contracts/token/ERC20Token.sol @@ -1,48 +1,52 @@ -pragma solidity ^0.4.17; +pragma solidity ^0.4.23; // Abstract contract for the full ERC 20 Token standard // https://github.com/ethereum/EIPs/issues/20 -contract ERC20Token { - /* This is a slight change to the ERC20 base standard. - function totalSupply() constant returns (uint256 supply); - is replaced with: - uint256 public totalSupply; - This automatically creates a getter function for the totalSupply. - This is moved to the base contract since public getter functions are not - currently recognised as an implementation of the matching abstract - function by the compiler. - */ - /// total amount of tokens - uint256 public totalSupply; +interface ERC20Token { - /// @notice send `_value` token to `_to` from `msg.sender` - /// @param _to The address of the recipient - /// @param _value The amount of token to be transferred - /// @return Whether the transfer was successful or not - function transfer(address _to, uint256 _value) public returns (bool success); + /** + * @notice send `_value` token to `_to` from `msg.sender` + * @param _to The address of the recipient + * @param _value The amount of token to be transferred + * @return Whether the transfer was successful or not + */ + function transfer(address _to, uint256 _value) external returns (bool success); - /// @notice `msg.sender` approves `_spender` to spend `_value` tokens - /// @param _spender The address of the account able to transfer the tokens - /// @param _value The amount of tokens to be approved for transfer - /// @return Whether the approval was successful or not - function approve(address _spender, uint256 _value) public returns (bool success); + /** + * @notice `msg.sender` approves `_spender` to spend `_value` tokens + * @param _spender The address of the account able to transfer the tokens + * @param _value The amount of tokens to be approved for transfer + * @return Whether the approval was successful or not + */ + function approve(address _spender, uint256 _value) external returns (bool success); - /// @notice send `_value` token to `_to` from `_from` on the condition it is approved by `_from` - /// @param _from The address of the sender - /// @param _to The address of the recipient - /// @param _value The amount of token to be transferred - /// @return Whether the transfer was successful or not - function transferFrom(address _from, address _to, uint256 _value) public returns (bool success); + /** + * @notice send `_value` token to `_to` from `_from` on the condition it is approved by `_from` + * @param _from The address of the sender + * @param _to The address of the recipient + * @param _value The amount of token to be transferred + * @return Whether the transfer was successful or not + */ + function transferFrom(address _from, address _to, uint256 _value) external returns (bool success); - /// @param _owner The address from which the balance will be retrieved - /// @return The balance - function balanceOf(address _owner) public view returns (uint256 balance); + /** + * @param _owner The address from which the balance will be retrieved + * @return The balance + */ + function balanceOf(address _owner) external view returns (uint256 balance); - /// @param _owner The address of the account owning tokens - /// @param _spender The address of the account able to transfer the tokens - /// @return Amount of remaining tokens allowed to spent - function allowance(address _owner, address _spender) public view returns (uint256 remaining); + /** + * @param _owner The address of the account owning tokens + * @param _spender The address of the account able to transfer the tokens + * @return Amount of remaining tokens allowed to spent + */ + function allowance(address _owner, address _spender) external view returns (uint256 remaining); + + /** + * @notice return total supply of tokens + */ + function totalSupply() external view returns (uint256 supply); event Transfer(address indexed _from, address indexed _to, uint256 _value); event Approval(address indexed _owner, address indexed _spender, uint256 _value); diff --git a/contracts/token/MiniMeToken.sol b/contracts/token/MiniMeToken.sol index bf4378b..67b2c56 100644 --- a/contracts/token/MiniMeToken.sol +++ b/contracts/token/MiniMeToken.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.4.6; +pragma solidity ^0.4.23; /* Copyright 2016, Jordi Baylina @@ -16,14 +16,15 @@ pragma solidity ^0.4.6; 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. +/** + * @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"; @@ -31,9 +32,11 @@ import "./ApproveAndCallFallBack.sol"; import "./MiniMeTokenInterface.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" +/** + * @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 MiniMeTokenInterface, Controlled { string public name; //The Token's name: e.g. DigixDAO Tokens @@ -41,10 +44,11 @@ contract MiniMeToken is MiniMeTokenInterface, Controlled { 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 + /** + * @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 @@ -86,20 +90,22 @@ contract MiniMeToken is MiniMeTokenInterface, Controlled { // 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 - function MiniMeToken( + /** + * @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, @@ -110,6 +116,7 @@ contract MiniMeToken is MiniMeTokenInterface, Controlled { ) public { + require(_tokenFactory != address(0)); //if not set, clone feature will not work properly tokenFactory = MiniMeTokenFactory(_tokenFactory); name = _tokenName; // Set the name decimals = _decimalUnits; // Set the decimals @@ -125,21 +132,25 @@ contract MiniMeToken is MiniMeTokenInterface, Controlled { // 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 + /** + * @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); 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 + /** + * @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, @@ -165,12 +176,14 @@ contract MiniMeToken is MiniMeTokenInterface, Controlled { 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 + /** + * @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, @@ -180,106 +193,124 @@ contract MiniMeToken is MiniMeTokenInterface, Controlled { returns(bool) { - if (_amount == 0) { - return true; - } + if (_amount == 0) { + return true; + } - require(parentSnapShotBlock < block.number); + require(parentSnapShotBlock < block.number); - // Do not allow transfer to 0x0 or the token contract itself - require((_to != 0) && (_to != address(this))); + // Do not allow transfer to 0x0 or the token contract itself + require((_to != 0) && (_to != address(this))); - // If the amount being transfered is more than the balance of the - // account the transfer returns false - var previousBalanceFrom = balanceOfAt(_from, block.number); - if (previousBalanceFrom < _amount) { - return false; - } + // 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)); - } + // Alerts the token controller of the transfer + if (isContract(controller)) { + require(TokenController(controller).onTransfer(_from, _to, _amount)); + } - // First update the balance array with the new value for the address - // sending the tokens - updateValueAtNow(balances[_from], previousBalanceFrom - _amount); + // 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 - var previousBalanceTo = balanceOfAt(_to, block.number); - require(previousBalanceTo + _amount >= previousBalanceTo); // Check for overflow - updateValueAtNow(balances[_to], previousBalanceTo + _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); // Check for overflow + updateValueAtNow(balances[_to], previousBalanceTo + _amount); - // An event to make the transfer easy to find on the blockchain - Transfer(_from, _to, _amount); + // An event to make the transfer easy to find on the blockchain + emit Transfer(_from, _to, _amount); - return true; + 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) public constant 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) public returns (bool success) { + function doApprove( + address _from, + address _spender, + uint256 _amount + ) + internal + returns (bool) + { require(transfersEnabled); // 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[msg.sender][_spender] == 0)); + require((_amount == 0) || (allowed[_from][_spender] == 0)); // Alerts the token controller of the approve function call if (isContract(controller)) { - require(TokenController(controller).onApprove(msg.sender, _spender, _amount)); + require(TokenController(controller).onApprove(_from, _spender, _amount)); } - allowed[msg.sender][_spender] = _amount; - Approval(msg.sender, _spender, _amount); + allowed[_from][_spender] = _amount; + emit Approval(_from, _spender, _amount); return true; } - /// @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 + /** + * @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) { + 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 ) - public - constant + 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 + /** + * @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 _extraData ) - public + external returns (bool success) { - require(approve(_spender, _amount)); + require(doApprove(msg.sender, _spender, _amount)); ApproveAndCallFallBack(_spender).receiveApproval( msg.sender, @@ -291,9 +322,11 @@ contract MiniMeToken is MiniMeTokenInterface, Controlled { return true; } - /// @dev This function makes it easy to get the total number of tokens - /// @return The total number of tokens - function totalSupply() public constant returns (uint) { + /** + * @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); } @@ -302,16 +335,18 @@ contract MiniMeToken is MiniMeTokenInterface, Controlled { // 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` + /** + * @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 - constant + view returns (uint) { @@ -320,8 +355,7 @@ contract MiniMeToken is MiniMeTokenInterface, Controlled { // 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 ((balances[_owner].length == 0) || (balances[_owner][0].fromBlock > _blockNumber)) { if (address(parentToken) != 0) { return parentToken.balanceOfAt(_owner, min(_blockNumber, parentSnapShotBlock)); } else { @@ -335,18 +369,19 @@ contract MiniMeToken is MiniMeTokenInterface, Controlled { } } - /// @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 constant returns(uint) { + /** + * @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 ((totalSupplyHistory.length == 0) || (totalSupplyHistory[0].fromBlock > _blockNumber)) { if (address(parentToken) != 0) { return parentToken.totalSupplyAt(min(_blockNumber, parentSnapShotBlock)); } else { @@ -363,16 +398,18 @@ contract MiniMeToken is MiniMeTokenInterface, Controlled { // 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 + /** + * @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 _cloneTokenName, uint8 _cloneDecimalUnits, @@ -383,12 +420,13 @@ contract MiniMeToken is MiniMeTokenInterface, Controlled { public returns(address) { - if (_snapshotBlock == 0) { - _snapshotBlock = block.number; + uint snapshotBlock = _snapshotBlock; + if (snapshotBlock == 0) { + snapshotBlock = block.number; } MiniMeToken cloneToken = tokenFactory.createCloneToken( this, - _snapshotBlock, + snapshotBlock, _cloneTokenName, _cloneDecimalUnits, _cloneTokenSymbol, @@ -398,18 +436,20 @@ contract MiniMeToken is MiniMeTokenInterface, Controlled { cloneToken.changeController(msg.sender); // An event to make the token easy to find on the blockchain - NewCloneToken(address(cloneToken), _snapshotBlock); + 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 + + /** + * @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 @@ -418,21 +458,22 @@ contract MiniMeToken is MiniMeTokenInterface, Controlled { onlyController returns (bool) { - uint curTotalSupply = totalSupply(); + uint curTotalSupply = totalSupplyAt(block.number); require(curTotalSupply + _amount >= curTotalSupply); // Check for overflow - uint previousBalanceTo = balanceOf(_owner); + uint previousBalanceTo = balanceOfAt(_owner, block.number); require(previousBalanceTo + _amount >= previousBalanceTo); // Check for overflow updateValueAtNow(totalSupplyHistory, curTotalSupply + _amount); updateValueAtNow(balances[_owner], previousBalanceTo + _amount); - Transfer(0, _owner, _amount); + emit Transfer(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 + /** + * @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 @@ -441,13 +482,13 @@ contract MiniMeToken is MiniMeTokenInterface, Controlled { onlyController returns (bool) { - uint curTotalSupply = totalSupply(); + uint curTotalSupply = totalSupplyAt(block.number); require(curTotalSupply >= _amount); - uint previousBalanceFrom = balanceOf(_owner); + uint previousBalanceFrom = balanceOfAt(_owner, block.number); require(previousBalanceFrom >= _amount); updateValueAtNow(totalSupplyHistory, curTotalSupply - _amount); updateValueAtNow(balances[_owner], previousBalanceFrom - _amount); - Transfer(_owner, 0, _amount); + emit Transfer(_owner, 0, _amount); return true; } @@ -455,9 +496,10 @@ contract MiniMeToken is MiniMeTokenInterface, Controlled { // Enable tokens transfers //////////////// - - /// @notice Enables token holders to transfer their tokens freely if true - /// @param _transfersEnabled True if transfers are allowed in the clone + /** + * @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; } @@ -466,20 +508,22 @@ contract MiniMeToken is MiniMeTokenInterface, Controlled { // 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 + /** + * @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 ) - constant + view internal returns (uint) { if (checkpoints.length == 0) { - return 0; + return 0; } // Shortcut for the actual value @@ -504,44 +548,54 @@ contract MiniMeToken is MiniMeTokenInterface, Controlled { 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 + /** + * @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); - } + 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) constant internal returns(bool) { + /** + * @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 == 0){ + if (_addr == 0) { return false; } assembly { size := extcodesize(_addr) } - return size>0; + return size > 0; } - /// @dev Helper function to return a min betwen the two uints + /** + * @dev Helper function to return a min betwen the two uints + */ function min(uint a, uint b) internal 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 + /** + * @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 () public payable { require(isContract(controller)); require(TokenController(controller).proxyPayment.value(msg.value)(msg.sender)); @@ -551,20 +605,22 @@ contract MiniMeToken is MiniMeTokenInterface, Controlled { // 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. + /** + * @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 == 0x0) { - controller.transfer(this.balance); + controller.transfer(address(this).balance); return; } MiniMeToken token = MiniMeToken(_token); - uint balance = token.balanceOf(this); + uint balance = token.balanceOf(address(this)); token.transfer(controller, balance); - ClaimedTokens(_token, controller, balance); + emit ClaimedTokens(_token, controller, balance); } //////////////// @@ -572,11 +628,11 @@ contract MiniMeToken is MiniMeTokenInterface, Controlled { //////////////// 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 NewCloneToken(address indexed _cloneToken, uint snapshotBlock); event Approval( address indexed _owner, address indexed _spender, uint256 _amount - ); + ); } \ No newline at end of file diff --git a/contracts/token/MiniMeTokenFactory.sol b/contracts/token/MiniMeTokenFactory.sol index a57eea4..3793f09 100644 --- a/contracts/token/MiniMeTokenFactory.sol +++ b/contracts/token/MiniMeTokenFactory.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.4.11; +pragma solidity ^0.4.23; import "./MiniMeToken.sol"; @@ -6,21 +6,25 @@ 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 +/** + * @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 + /** + * @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, diff --git a/contracts/token/MiniMeTokenInterface.sol b/contracts/token/MiniMeTokenInterface.sol index e315956..8e7b3d0 100644 --- a/contracts/token/MiniMeTokenInterface.sol +++ b/contracts/token/MiniMeTokenInterface.sol @@ -1,68 +1,39 @@ -pragma solidity ^0.4.17; +pragma solidity ^0.4.23; -contract MiniMeTokenInterface { +import "./ERC20Token.sol"; - event Transfer(address indexed _from, address indexed _to, uint256 _value); - event Approval(address indexed _owner, address indexed _spender, uint256 _value); - - /// @dev This function makes it easy to get the total number of tokens - /// @return The total number of tokens - function totalSupply() public constant returns (uint); - /// @param _owner The address from which the balance will be retrieved - /// @return The balance - function balanceOf(address _owner) public constant returns (uint256 balance); +contract MiniMeTokenInterface is ERC20Token { - /// @notice send `_value` token to `_to` from `msg.sender` - /// @param _to The address of the recipient - /// @param _value The amount of token to be transferred - /// @return Whether the transfer was successful or not - function transfer(address _to, uint256 _value) public returns (bool success); - - /// @notice send `_value` token to `_to` from `_from` on the condition it is approved by `_from` - /// @param _from The address of the sender - /// @param _to The address of the recipient - /// @param _value The amount of token to be transferred - /// @return Whether the transfer was successful or not - function transferFrom(address _from, address _to, uint256 _value) public returns (bool success); - - /// @notice `msg.sender` approves `_spender` to spend `_value` tokens - /// @param _spender The address of the account able to transfer the tokens - /// @param _value The amount of tokens to be approved for transfer - /// @return Whether the approval was successful or not - function approve(address _spender, uint256 _value) public returns (bool success); - - /// @param _owner The address of the account owning tokens - /// @param _spender The address of the account able to transfer the tokens - /// @return Amount of remaining tokens allowed to spent - function allowance(address _owner, address _spender) public constant returns (uint256 remaining); - - /// @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 + /** + * @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 _extraData ) - public + external returns (bool success); - - /// @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 + /** + * @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 _cloneTokenName, uint8 _cloneDecimalUnits, @@ -72,11 +43,13 @@ contract MiniMeTokenInterface { ) public returns(address); - - /// @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 + + /** + * @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 @@ -84,10 +57,12 @@ contract MiniMeTokenInterface { public returns (bool); - /// @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 + /** + * @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 @@ -95,22 +70,26 @@ contract MiniMeTokenInterface { public returns (bool); - - /// @notice Enables token holders to transfer their tokens freely if true - /// @param _transfersEnabled True if transfers are allowed in the clone + /** + * @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; - - /// @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. + /** + * @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; - /// @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` + /** + * @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 @@ -119,11 +98,11 @@ contract MiniMeTokenInterface { constant returns (uint); - /// @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 constant returns(uint); - - + /** + * @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); } \ No newline at end of file diff --git a/contracts/token/StandardToken.sol b/contracts/token/StandardToken.sol index 10ca74a..b80c513 100644 --- a/contracts/token/StandardToken.sol +++ b/contracts/token/StandardToken.sol @@ -4,23 +4,24 @@ import "./ERC20Token.sol"; contract StandardToken is ERC20Token { + uint256 private supply; mapping (address => uint256) balances; mapping (address => mapping (address => uint256)) allowed; constructor() internal { } - + function transfer( address _to, uint256 _value ) - public + external returns (bool success) { return transfer(msg.sender, _to, _value); } function approve(address _spender, uint256 _value) - public + external returns (bool success) { allowed[msg.sender][_spender] = _value; @@ -33,7 +34,7 @@ contract StandardToken is ERC20Token { address _to, uint256 _value ) - public + external returns (bool success) { if (balances[_from] >= _value && @@ -47,7 +48,7 @@ contract StandardToken is ERC20Token { } function allowance(address _owner, address _spender) - public + external view returns (uint256 remaining) { @@ -55,12 +56,31 @@ contract StandardToken is ERC20Token { } function balanceOf(address _owner) - public + external view returns (uint256 balance) { return balances[_owner]; } + + function totalSupply() + external + view + returns(uint256 currentTotalSupply) + { + return supply; + } + + function mint( + address _to, + uint256 _amount + ) + internal + { + balances[_to] += _amount; + supply += _amount; + emit Transfer(0x0, _to, _amount); + } function transfer( address _from, diff --git a/contracts/token/TestToken.sol b/contracts/token/TestToken.sol new file mode 100644 index 0000000..2afff21 --- /dev/null +++ b/contracts/token/TestToken.sol @@ -0,0 +1,19 @@ +pragma solidity ^0.4.23; + +import "./StandardToken.sol"; + +/** + * @notice ERC20Token for test scripts, can be minted by anyone. + */ +contract TestToken is StandardToken { + + constructor() public { } + + /** + * @notice any caller can mint any `_amount` + * @param _amount how much to be minted + */ + function mint(uint256 _amount) public { + mint(msg.sender, _amount); + } +} diff --git a/contracts/token/TokenController.sol b/contracts/token/TokenController.sol index 9dd7f91..9b9d4f0 100644 --- a/contracts/token/TokenController.sol +++ b/contracts/token/TokenController.sol @@ -1,26 +1,33 @@ -pragma solidity ^0.4.14; +pragma solidity ^0.4.23; +/** + * @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); -/// @dev The token controller contract must implement these functions -contract 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) 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 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) 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) + /** + * @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 ba742a4..7bd3d48 100644 --- a/embark.json +++ b/embark.json @@ -1,10 +1,17 @@ { "contracts": ["contracts/**"], + "app": { + "js/dapp.js": ["app/dapp.js"], + "index.html": "app/index.html", + "images/": ["app/images/**"] + }, "buildDir": "dist/", "config": "config/", - "plugins": { - }, "versions": { - "solc": "0.4.23" + "web3": "1.0.0-beta", + "solc": "0.4.23", + "ipfs-api": "17.2.4" + }, + "plugins": { } } diff --git a/package.json b/package.json index 7cf6084..fb83081 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "name": "status-contracts", - "version": "1.0.0", + "version": "0.0.1", + "description": "", "scripts": { "solidity-coverage": "./node_modules/.bin/solidity-coverage", "test": "embark test" @@ -15,13 +16,10 @@ "url": "https://github.com/status-im/contracts/issues" }, "homepage": "https://github.com/status-im/contracts#readme", - "devDependencies": { - "solidity-coverage": "^0.5.0", - "elliptic": "^6.4.0" - }, "dependencies": { - "embark": "^2.7.0", - "elliptic-curve": "^0.1.0", - "ethereumjs-util": "^5.1.5" + "react": "^16.3.2", + "react-blockies": "^1.3.0", + "react-bootstrap": "^0.32.1", + "react-dom": "^16.3.2" } } diff --git a/test/controlled.js b/test/controlled.js new file mode 100644 index 0000000..d2b14b4 --- /dev/null +++ b/test/controlled.js @@ -0,0 +1,29 @@ + +exports.Test = (contractsConfig, afterDeploy) => { + + describe("Controlled", async function() { + this.timeout(0); + var Controlled; + var accountsArr; + before(function(done) { + EmbarkSpec.deployAll(contractsConfig, async function(accounts) { + Controlled = Contract; + accountsArr = accounts; + await afterDeploy(accounts, Contract); + done() + }); + }); + + + it("should start with msg.sender as controller", async function() { + var controller = await Controlled.methods.controller().call(); + assert(controller, accountsArr[0]); + }); + + it("should allow controller to set new controller", async function() { + await Controlled.methods.changeController(accountsArr[1]).send({from: accountsArr[0]}); + var controller = await Controlled.methods.controller().call(); + assert(controller, accountsArr[1]); + }); + }); +} \ No newline at end of file diff --git a/test/erc20token.js b/test/erc20token.js new file mode 100644 index 0000000..6889734 --- /dev/null +++ b/test/erc20token.js @@ -0,0 +1,78 @@ + +exports.Test = (contractsConfig, afterDeploy) => { + describe("ERC20Token", async function() { + this.timeout(0); + var ERC20Token; + var accountsArr; + before(function(done) { + contractsConfig["ERC20Receiver"] = {}; + EmbarkSpec.deployAll(contractsConfig, async function(accounts) { + ERC20Token = Contract; + accountsArr = accounts; + await afterDeploy(accounts, Contract); + done() + }); + }); + + it("should transfer 1 token", async function() { + let initialBalance0 = await ERC20Token.methods.balanceOf(accountsArr[0]).call(); + let initialBalance1 = await ERC20Token.methods.balanceOf(accountsArr[1]).call(); + await ERC20Token.methods.transfer(accountsArr[1],1).send({from: accountsArr[0]}); + let result0 = await ERC20Token.methods.balanceOf(accountsArr[0]).call(); + let result1 = await ERC20Token.methods.balanceOf(accountsArr[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(accountsArr[2],10000000).send({from: accountsArr[0]}); + let result = await ERC20Token.methods.allowance(accountsArr[0], accountsArr[2]).call(); + assert.equal(result, 10000000); + }); + + it("should consume allowance amount", async function() { + let initialAllowance = await ERC20Token.methods.allowance(accountsArr[0], accountsArr[2]).call(); + await ERC20Token.methods.transferFrom(accountsArr[0], accountsArr[0],1).send({from: accountsArr[2]}); + let result = await ERC20Token.methods.allowance(accountsArr[0], accountsArr[2]).call(); + + assert.equal(result, +initialAllowance-1); + }); + + it("should transfer approved amount", async function() { + let initialBalance0 = await ERC20Token.methods.balanceOf(accountsArr[0]).call(); + let initialBalance1 = await ERC20Token.methods.balanceOf(accountsArr[1]).call(); + await ERC20Token.methods.transferFrom(accountsArr[0], accountsArr[1],1).send({from: accountsArr[2]}); + let result0 = await ERC20Token.methods.balanceOf(accountsArr[0]).call(); + let result1 = await ERC20Token.methods.balanceOf(accountsArr[1]).call(); + + assert.equal(result0, +initialBalance0-1); + assert.equal(result1, +initialBalance1+1); + }); + + + it("should unset approved amount", async function() { + await ERC20Token.methods.approve(accountsArr[2],0).send({from: accountsArr[0]}); + let result = await ERC20Token.methods.allowance(accountsArr[0], accountsArr[2]).call(); + assert.equal(result, 0); + }); + + it("should deposit approved amount to contract ERC20Receiver", async function() { + await ERC20Token.methods.approve(ERC20Receiver.address,10).send({from: accountsArr[0]}); + await ERC20Receiver.methods.depositToken(ERC20Token.address).send({from: accountsArr[0]}); + let result = await ERC20Receiver.methods.tokenBalanceOf(ERC20Token.address, accountsArr[0]).call(); + assert.equal(result, 10, "ERC20Receiver.tokenBalanceOf("+ERC20Token.address+","+accountsArr[0]+") wrong"); + }); + + it("should witdraw approved amount from contract ERC20Receiver", async function() { + let tokenBalance = await ERC20Receiver.methods.tokenBalanceOf(ERC20Token.address, accountsArr[0]).call(); + await ERC20Receiver.methods.withdrawToken(ERC20Token.address, tokenBalance).send({from: accountsArr[0]}); + tokenBalance = await ERC20Receiver.methods.tokenBalanceOf(ERC20Token.address, accountsArr[0]).call(); + assert.equal(tokenBalance, 0, "ERC20Receiver.tokenBalanceOf("+ERC20Token.address+","+accountsArr[0]+") wrong"); + }); + + //TODO: include checks for expected events fired + + + }); +} \ No newline at end of file diff --git a/test/minimetoken.js b/test/minimetoken.js new file mode 100644 index 0000000..d911652 --- /dev/null +++ b/test/minimetoken.js @@ -0,0 +1,135 @@ +const utils = require('../utils/testUtils') +const ERC20Token = require('./erc20token'); +const Controlled = require('./controlled'); + +describe("MiniMeToken", async function() { + this.timeout(0); + var accounts; + var miniMeTokenClone; + const b = []; + + before(function(done) { + var contractsConfig = { + "MiniMeTokenFactory": { + }, + "MiniMeToken": { + "args": [ + "$MiniMeTokenFactory", + utils.zeroAddress, + 0, + "TestMiniMeToken", + 18, + "TST", + true + ] + } + }; + EmbarkSpec.deployAll(contractsConfig, async function(accountsArr) { + accounts = accountsArr + done() + }); + }); + + + it('should generate tokens for address 1', async () => { + 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.raw.topics[1]; + addr = `0x${addr.slice(26)}`; + addr = web3.utils.toChecksumAddress(addr); + 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); + }); + + var erc20tokenConfig = { + "MiniMeTokenFactory": { + }, + "Contract": { + "instanceOf" : "MiniMeToken", + "args": [ + "$MiniMeTokenFactory", + utils.zeroAddress, + 0, + "TestMiniMeToken", + 18, + "TST", + true + ] + } + } + + ERC20Token.Test(erc20tokenConfig, async function (accounts, MiniMeToken) { + for(i=0;i (...args) => { return new Promise((resolve, reject) => { @@ -95,3 +93,147 @@ exports.promisify = (func) => }); } + +// This has been tested with the real Ethereum network and Testrpc. +// Copied and edited from: https://gist.github.com/xavierlepretre/d5583222fde52ddfbc58b7cfa0d2d0a9 +exports.assertReverts = (contractMethodCall, maxGasAvailable) => { + return new Promise((resolve, reject) => { + try { + resolve(contractMethodCall()) + } catch (error) { + reject(error) + } + }) + .then(tx => { + assert.equal(tx.receipt.gasUsed, maxGasAvailable, "tx successful, the max gas available was not consumed") + }) + .catch(error => { + if ((error + "").indexOf("invalid opcode") < 0 && (error + "").indexOf("out of gas") < 0) { + // Checks if the error is from TestRpc. If it is then ignore it. + // Otherwise relay/throw the error produced by the above assertion. + // Note that no error is thrown when using a real Ethereum network AND the assertion above is true. + throw error + } + }) +} + +exports.listenForEvent = event => new Promise((resolve, reject) => { + event({}, (error, response) => { + if (!error) { + resolve(response.args) + } else { + reject(error) + } + event.stopWatching() + }) +}); + +exports.eventValues = (receipt, eventName) => { + if(receipt.events[eventName]) + return receipt.events[eventName].returnValues; +} + +exports.addressToBytes32 = (address) => { + const stringed = "0000000000000000000000000000000000000000000000000000000000000000" + address.slice(2); + return "0x" + stringed.substring(stringed.length - 64, stringed.length); +} + + +// OpenZeppelin's expectThrow helper - +// Source: https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/test/helpers/expectThrow.js +exports.expectThrow = async promise => { + try { + await promise; + } catch (error) { + // TODO: Check jump destination to destinguish between a throw + // and an actual invalid jump. + const invalidOpcode = error.message.search('invalid opcode') >= 0; + // TODO: When we contract A calls contract B, and B throws, instead + // of an 'invalid jump', we get an 'out of gas' error. How do + // we distinguish this from an actual out of gas event? (The + // testrpc log actually show an 'invalid jump' event.) + const outOfGas = error.message.search('out of gas') >= 0; + const revert = error.message.search('revert') >= 0; + assert( + invalidOpcode || outOfGas || revert, + 'Expected throw, got \'' + error + '\' instead', + ); + return; + } + assert.fail('Expected throw not received'); + }; + +exports.assertJump = (error) => { + assert(error.message.search('revert') > -1, 'Revert should happen'); +} + +var callbackToResolve = function (resolve, reject) { + return function (error, value) { + if (error) { + reject(error); + } else { + resolve(value); + } + }; +}; + +exports.promisify = (func) => + (...args) => { + return new Promise((resolve, reject) => { + const callback = (err, data) => err ? reject(err) : resolve(data); + func.apply(this, [...args, callback]); + }); + } + +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()); +}; + +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() + }, + (error) => { + if (error) { + console.log(error); + return reject(err); + } + web3.currentProvider.sendAsync( + { + jsonrpc: '2.0', + method: 'evm_mine', + params: [], + id: new Date().getSeconds() + }, (error) => { + if (error) { + console.log(error); + return reject(err); + } + resolve(); + } + ) + } + ) + }); +}