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(
+ );
+ }
+ )
+ }
+
+ 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
+
+
+
+ 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
+
+
+ 2. Read your account token balance
+
+
+ 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..923d2c9 100644
--- a/config/contracts.json
+++ b/config/contracts.json
@@ -15,6 +15,9 @@
],
"gas": "auto",
"contracts": {
+ "ERC20Receiver": {
+ "deploy": false
+ }
}
}
}
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/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/StandardToken.sol b/contracts/token/StandardToken.sol
index d42852c..b80c513 100644
--- a/contracts/token/StandardToken.sol
+++ b/contracts/token/StandardToken.sol
@@ -66,7 +66,7 @@ contract StandardToken is ERC20Token {
function totalSupply()
external
view
- returns(uint256 supply)
+ returns(uint256 currentTotalSupply)
{
return supply;
}
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/erc20token.js b/test/erc20token.js
new file mode 100644
index 0000000..3e71542
--- /dev/null
+++ b/test/erc20token.js
@@ -0,0 +1,80 @@
+describe("ERC20Token", async function() {
+ this.timeout(0);
+ var ERC20Token;
+ var accountsArr;
+ before(function(done) {
+ var contractsConfig = {
+ "TestToken": { },
+ "ERC20Receiver": { }
+ };
+ EmbarkSpec.deployAll(contractsConfig, async function(accounts) {
+ ERC20Token = TestToken;
+ accountsArr = accounts;
+ 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();
+ }
+ )
+ }
+ )
+ });
+}