merge master into jr-use-badge-reg

This commit is contained in:
Jannis R 2016-12-14 12:26:18 +01:00
commit 240bab230c
No known key found for this signature in database
GPG Key ID: 0FE83946296A88A5
71 changed files with 925 additions and 607 deletions

35
Cargo.lock generated
View File

@ -530,6 +530,7 @@ dependencies = [
"jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git)", "jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git)",
"jsonrpc-http-server 6.1.1 (git+https://github.com/ethcore/jsonrpc.git)", "jsonrpc-http-server 6.1.1 (git+https://github.com/ethcore/jsonrpc.git)",
"jsonrpc-ipc-server 0.2.4 (git+https://github.com/ethcore/jsonrpc.git)", "jsonrpc-ipc-server 0.2.4 (git+https://github.com/ethcore/jsonrpc.git)",
"jsonrpc-macros 0.1.0 (git+https://github.com/ethcore/jsonrpc.git)",
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"rlp 0.1.0", "rlp 0.1.0",
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
@ -860,10 +861,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "jsonrpc-core" name = "jsonrpc-core"
version = "4.0.0" version = "4.0.0"
source = "git+https://github.com/ethcore/jsonrpc.git#1500da1b9613a0a17fc0109d825f3ccc60199a53" source = "git+https://github.com/ethcore/jsonrpc.git#33262d626a294a00c20435dec331058ba65e224a"
dependencies = [ dependencies = [
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)", "serde 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_codegen 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)", "serde_codegen 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
@ -872,7 +873,7 @@ dependencies = [
[[package]] [[package]]
name = "jsonrpc-http-server" name = "jsonrpc-http-server"
version = "6.1.1" version = "6.1.1"
source = "git+https://github.com/ethcore/jsonrpc.git#1500da1b9613a0a17fc0109d825f3ccc60199a53" source = "git+https://github.com/ethcore/jsonrpc.git#33262d626a294a00c20435dec331058ba65e224a"
dependencies = [ dependencies = [
"hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)", "hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)",
"jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git)", "jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git)",
@ -883,7 +884,7 @@ dependencies = [
[[package]] [[package]]
name = "jsonrpc-ipc-server" name = "jsonrpc-ipc-server"
version = "0.2.4" version = "0.2.4"
source = "git+https://github.com/ethcore/jsonrpc.git#1500da1b9613a0a17fc0109d825f3ccc60199a53" source = "git+https://github.com/ethcore/jsonrpc.git#33262d626a294a00c20435dec331058ba65e224a"
dependencies = [ dependencies = [
"bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
@ -895,10 +896,19 @@ dependencies = [
"slab 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "slab 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "jsonrpc-macros"
version = "0.1.0"
source = "git+https://github.com/ethcore/jsonrpc.git#33262d626a294a00c20435dec331058ba65e224a"
dependencies = [
"jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git)",
"serde 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "jsonrpc-tcp-server" name = "jsonrpc-tcp-server"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/ethcore/jsonrpc.git#1500da1b9613a0a17fc0109d825f3ccc60199a53" source = "git+https://github.com/ethcore/jsonrpc.git#33262d626a294a00c20435dec331058ba65e224a"
dependencies = [ dependencies = [
"bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1290,22 +1300,11 @@ dependencies = [
[[package]] [[package]]
name = "parity-ui-precompiled" name = "parity-ui-precompiled"
version = "1.4.0" version = "1.4.0"
source = "git+https://github.com/ethcore/js-precompiled.git#eb9d978ed5ad1c514b37e89c716f80b3c8d613b5" source = "git+https://github.com/ethcore/js-precompiled.git#175003ae159b126302fd1a90dd875dc86d7adba0"
dependencies = [ dependencies = [
"parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "parking_lot"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
"smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "parking_lot" name = "parking_lot"
version = "0.3.5" version = "0.3.5"
@ -2065,6 +2064,7 @@ dependencies = [
"checksum jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git)" = "<none>" "checksum jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git)" = "<none>"
"checksum jsonrpc-http-server 6.1.1 (git+https://github.com/ethcore/jsonrpc.git)" = "<none>" "checksum jsonrpc-http-server 6.1.1 (git+https://github.com/ethcore/jsonrpc.git)" = "<none>"
"checksum jsonrpc-ipc-server 0.2.4 (git+https://github.com/ethcore/jsonrpc.git)" = "<none>" "checksum jsonrpc-ipc-server 0.2.4 (git+https://github.com/ethcore/jsonrpc.git)" = "<none>"
"checksum jsonrpc-macros 0.1.0 (git+https://github.com/ethcore/jsonrpc.git)" = "<none>"
"checksum jsonrpc-tcp-server 0.1.0 (git+https://github.com/ethcore/jsonrpc.git)" = "<none>" "checksum jsonrpc-tcp-server 0.1.0 (git+https://github.com/ethcore/jsonrpc.git)" = "<none>"
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
"checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" "checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a"
@ -2107,7 +2107,6 @@ dependencies = [
"checksum owning_ref 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8d91377085359426407a287ab16884a0111ba473aa6844ff01d4ec20ce3d75e7" "checksum owning_ref 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8d91377085359426407a287ab16884a0111ba473aa6844ff01d4ec20ce3d75e7"
"checksum parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "98378dec0a185da2b7180308752f0bad73aaa949c3e0a3b0528d0e067945f7ab" "checksum parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "98378dec0a185da2b7180308752f0bad73aaa949c3e0a3b0528d0e067945f7ab"
"checksum parity-ui-precompiled 1.4.0 (git+https://github.com/ethcore/js-precompiled.git)" = "<none>" "checksum parity-ui-precompiled 1.4.0 (git+https://github.com/ethcore/js-precompiled.git)" = "<none>"
"checksum parking_lot 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "968f685642555d2f7e202c48b8b11de80569e9bfea817f7f12d7c61aac62d4e6"
"checksum parking_lot 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "dbc5847584161f273e69edc63c1a86254a22f570a0b5dd87aa6f9773f6f7d125" "checksum parking_lot 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "dbc5847584161f273e69edc63c1a86254a22f570a0b5dd87aa6f9773f6f7d125"
"checksum parking_lot_core 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fb1b97670a2ffadce7c397fb80a3d687c4f3060140b885621ef1653d0e5d5068" "checksum parking_lot_core 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fb1b97670a2ffadce7c397fb80a3d687c4f3060140b885621ef1653d0e5d5068"
"checksum phf 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)" = "447d9d45f2e0b4a9b532e808365abf18fc211be6ca217202fcd45236ef12f026" "checksum phf 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)" = "447d9d45f2e0b4a9b532e808365abf18fc211be6ca217202fcd45236ef12f026"

View File

@ -10,14 +10,15 @@
"durationLimit": "0x0d", "durationLimit": "0x0d",
"blockReward": "0x4563918244F40000", "blockReward": "0x4563918244F40000",
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b", "registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b",
"homesteadTransition": "0x118c30", "homesteadTransition": 1150000,
"eip150Transition": "0x2625a0", "eip150Transition": 2500000,
"eip155Transition": "0x7fffffffffffffff", "eip155Transition": 3000000,
"eip160Transition": "0x7fffffffffffffff", "eip160Transition": 3000000,
"ecip1010PauseTransition": 3000000,
"ecip1010ContinueTransition": 5000000,
"eip161abcTransition": "0x7fffffffffffffff", "eip161abcTransition": "0x7fffffffffffffff",
"eip161dTransition": "0x7fffffffffffffff", "eip161dTransition": "0x7fffffffffffffff"
"ecip1010PauseTransition": "0x2dc6c0",
"ecip1010ContinueTransition": "0x4c4b40"
} }
} }
}, },

View File

@ -9,12 +9,15 @@
"durationLimit": "0x0d", "durationLimit": "0x0d",
"blockReward": "0x4563918244F40000", "blockReward": "0x4563918244F40000",
"registrar": "0x52dff57a8a1532e6afb3dc07e2af58bb9eb05b3d", "registrar": "0x52dff57a8a1532e6afb3dc07e2af58bb9eb05b3d",
"homesteadTransition": "0x789b0", "homesteadTransition": 494000,
"eip150Transition": "0x1b34d8", "eip150Transition": 1783000,
"eip155Transition": 1885000, "eip155Transition": 1915000,
"eip160Transition": 1885000, "eip160Transition": 1915000,
"eip161abcTransition": 1885000, "ecip1010PauseTransition": 1915000,
"eip161dTransition": 1885000 "ecip1010ContinueTransition": 3415000,
"eip161abcTransition": "0x7fffffffffffffff",
"eip161dTransition": "0x7fffffffffffffff"
} }
} }
}, },

@ -1 +1 @@
Subproject commit e8f4624b7f1a15c63674eecf577c7ab76c3b16be Subproject commit 9028c4801fd39fbb71a9796979182549a24e81c8

View File

@ -1,6 +1,6 @@
{ {
"name": "parity.js", "name": "parity.js",
"version": "0.2.119", "version": "0.2.122",
"main": "release/index.js", "main": "release/index.js",
"jsnext:main": "src/index.js", "jsnext:main": "src/index.js",
"author": "Parity Team <admin@parity.io>", "author": "Parity Team <admin@parity.io>",

View File

@ -0,0 +1,33 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { stringify } from 'querystring';
export const postToServer = (query, isTestnet = false) => {
const port = isTestnet ? 28443 : 18443;
query = stringify(query);
return fetch(`https://email-verification.parity.io:${port}/?` + query, {
method: 'POST', mode: 'cors', cache: 'no-store'
})
.then((res) => {
return res.json().then((data) => {
if (res.ok) {
return data.message;
}
throw new Error(data.message || 'unknown error');
});
});
};

View File

@ -0,0 +1,23 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React from 'react';
export default (
<ul>
<li>todo</li>
</ul>
);

View File

@ -15,17 +15,6 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { stringify } from 'querystring'; import { stringify } from 'querystring';
import React from 'react';
export const termsOfService = (
<ul>
<li>This privacy notice relates to your use of the Parity SMS verification service. We take your privacy seriously and deal in an honest, direct and transparent way when it comes to your data.</li>
<li>We collect your phone number when you use this service. This is temporarily kept in memory, and then encrypted and stored in our EU servers. We only retain the cryptographic hash of the number to prevent duplicated accounts. You consent to this use.</li>
<li>You pay a fee for the cost of this service using the account you want to verify.</li>
<li>Your phone number is transmitted to a third party US SMS verification service Twilio for the sole purpose of the SMS verification. You consent to this use. Twilios privacy policy is here: <a href={ 'https://www.twilio.com/legal/privacy/developer' }>https://www.twilio.com/legal/privacy/developer</a>.</li>
<li><i>Parity Technology Limited</i> is registered in England and Wales under company number <code>09760015</code> and complies with the Data Protection Act 1998 (UK). You may contact us via email at <a href={ 'mailto:admin@parity.io' }>admin@parity.io</a>. Our general privacy policy can be found here: <a href={ 'https://ethcore.io/legal.html' }>https://ethcore.io/legal.html</a>.</li>
</ul>
);
export const postToServer = (query, isTestnet = false) => { export const postToServer = (query, isTestnet = false) => {
const port = isTestnet ? 8443 : 443; const port = isTestnet ? 8443 : 443;

View File

@ -0,0 +1,27 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React from 'react';
export default (
<ul>
<li>This privacy notice relates to your use of the Parity SMS verification service. We take your privacy seriously and deal in an honest, direct and transparent way when it comes to your data.</li>
<li>We collect your phone number when you use this service. This is temporarily kept in memory, and then encrypted and stored in our EU servers. We only retain the cryptographic hash of the number to prevent duplicated accounts. You consent to this use.</li>
<li>You pay a fee for the cost of this service using the account you want to verify.</li>
<li>Your phone number is transmitted to a third party US SMS verification service Twilio for the sole purpose of the SMS verification. You consent to this use. Twilios privacy policy is here: <a href={ 'https://www.twilio.com/legal/privacy/developer' }>https://www.twilio.com/legal/privacy/developer</a>.</li>
<li><i>Parity Technology Limited</i> is registered in England and Wales under company number <code>09760015</code> and complies with the Data Protection Act 1998 (UK). You may contact us via email at <a href={ 'mailto:admin@parity.io' }>admin@parity.io</a>. Our general privacy policy can be found here: <a href={ 'https://ethcore.io/legal.html' }>https://ethcore.io/legal.html</a>.</li>
</ul>
);

View File

@ -1,9 +1,30 @@
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import blockies from 'blockies'; import blockies from 'blockies';
// jsdom doesn't have all the browser features, blockies fail
const TEST_ENV = process.env.NODE_ENV === 'test';
export function createIdentityImg (address, scale = 8) { export function createIdentityImg (address, scale = 8) {
return blockies({ return TEST_ENV
seed: (address || '').toLowerCase(), ? ''
size: 8, : blockies({
scale seed: (address || '').toLowerCase(),
}).toDataURL(); size: 8,
scale
}).toDataURL();
} }

View File

@ -0,0 +1 @@
[{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"reverse","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_who","type":"address"},{"name":"_puzzle","type":"bytes32"},{"name":"_emailHash","type":"bytes32"}],"name":"puzzle","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"getAddress","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_emailHash","type":"bytes32"}],"name":"request","outputs":[],"payable":true,"type":"function"},{"constant":false,"inputs":[{"name":"_new","type":"uint256"}],"name":"setFee","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_code","type":"bytes32"}],"name":"confirm","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"drain","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"getUint","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"}],"name":"certified","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"fee","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"get","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"},{"indexed":false,"name":"emailHash","type":"bytes32"}],"name":"Requested","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"},{"indexed":true,"name":"emailHash","type":"bytes32"},{"indexed":false,"name":"puzzle","type":"bytes32"}],"name":"Puzzled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"}],"name":"Confirmed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"}],"name":"Revoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"old","type":"address"},{"indexed":true,"name":"current","type":"address"}],"name":"NewOwner","type":"event"}]

View File

@ -19,6 +19,7 @@ import basiccoin from './basiccoin.json';
import basiccoinmanager from './basiccoinmanager.json'; import basiccoinmanager from './basiccoinmanager.json';
import dappreg from './dappreg.json'; import dappreg from './dappreg.json';
import eip20 from './eip20.json'; import eip20 from './eip20.json';
import emailverification from './email-verification.json';
import gavcoin from './gavcoin.json'; import gavcoin from './gavcoin.json';
import githubhint from './githubhint.json'; import githubhint from './githubhint.json';
import owned from './owned.json'; import owned from './owned.json';
@ -34,6 +35,7 @@ export {
basiccoinmanager, basiccoinmanager,
dappreg, dappreg,
eip20, eip20,
emailverification,
gavcoin, gavcoin,
githubhint, githubhint,
owned, owned,

View File

@ -19,7 +19,7 @@ import Registry from './registry';
import SignatureReg from './signaturereg'; import SignatureReg from './signaturereg';
import TokenReg from './tokenreg'; import TokenReg from './tokenreg';
import GithubHint from './githubhint'; import GithubHint from './githubhint';
import * as smsVerification from './sms-verification'; import * as verification from './verification';
import BadgeReg from './badgereg'; import BadgeReg from './badgereg';
let instance = null; let instance = null;
@ -58,7 +58,11 @@ export default class Contracts {
} }
get smsVerification () { get smsVerification () {
return smsVerification; return verification;
}
get emailVerification () {
return verification;
} }
static create (api) { static create (api) {

View File

@ -58,23 +58,23 @@ export default class DetailsStep extends Component {
<Form> <Form>
{ this.renderWarning() } { this.renderWarning() }
<AddressSelect <AddressSelect
label='from account'
hint='the account to transact with'
value={ fromAddress }
error={ fromAddressError }
accounts={ accounts } accounts={ accounts }
balances={ balances } balances={ balances }
onChange={ onFromAddressChange } /> error={ fromAddressError }
hint='the account to transact with'
label='from account'
onChange={ onFromAddressChange }
value={ fromAddress } />
{ this.renderFunctionSelect() } { this.renderFunctionSelect() }
{ this.renderParameters() } { this.renderParameters() }
<div className={ styles.columns }> <div className={ styles.columns }>
<div> <div>
<Input <Input
label='transaction value (in ETH)'
hint='the amount to send to with the transaction'
value={ amount }
error={ amountError } error={ amountError }
onSubmit={ onAmountChange } /> hint='the amount to send to with the transaction'
label='transaction value (in ETH)'
onSubmit={ onAmountChange }
value={ amount } />
</div> </div>
<div> <div>
<Checkbox <Checkbox

View File

@ -0,0 +1,83 @@
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { mount } from 'enzyme';
import React from 'react';
import sinon from 'sinon';
import { ContextProvider, muiTheme } from '~/ui';
import DetailsStep from './';
import { CONTRACT } from '../executeContract.test.js';
let component;
let onAmountChange;
let onClose;
let onFromAddressChange;
let onFuncChange;
let onGasEditClick;
let onValueChange;
function render (props) {
onAmountChange = sinon.stub();
onClose = sinon.stub();
onFromAddressChange = sinon.stub();
onFuncChange = sinon.stub();
onGasEditClick = sinon.stub();
onValueChange = sinon.stub();
component = mount(
<ContextProvider api={ {} } muiTheme={ muiTheme } store={ {} }>
<DetailsStep
{ ...props }
contract={ CONTRACT }
onAmountChange={ onAmountChange }
onClose={ onClose }
onFromAddressChange={ onFromAddressChange }
onFuncChange={ onFuncChange }
onGasEditClick={ onGasEditClick }
onValueChange={ onValueChange } />
</ContextProvider>
);
return component;
}
describe('modals/ExecuteContract/DetailsStep', () => {
it('renders', () => {
expect(render({ accounts: {}, values: [ true ], valuesError: [ null ] })).to.be.ok;
});
describe('parameter values', () => {
beforeEach(() => {
render({
accounts: {},
func: CONTRACT.functions[0],
values: [ false ],
valuesError: [ null ]
});
});
describe('bool parameters', () => {
it('toggles from false to true', () => {
component.find('DropDownMenu').last().simulate('change', { target: { value: 'true' } });
expect(onValueChange).to.have.been.calledWith(null, 0, true);
});
});
});
});

View File

@ -25,6 +25,7 @@ import ContentClear from 'material-ui/svg-icons/content/clear';
import NavigationArrowBack from 'material-ui/svg-icons/navigation/arrow-back'; import NavigationArrowBack from 'material-ui/svg-icons/navigation/arrow-back';
import NavigationArrowForward from 'material-ui/svg-icons/navigation/arrow-forward'; import NavigationArrowForward from 'material-ui/svg-icons/navigation/arrow-forward';
import { toWei } from '~/api/util/wei';
import { BusyStep, Button, CompletedStep, GasPriceEditor, IdentityIcon, Modal, TxHash } from '~/ui'; import { BusyStep, Button, CompletedStep, GasPriceEditor, IdentityIcon, Modal, TxHash } from '~/ui';
import { MAX_GAS_ESTIMATION } from '~/util/constants'; import { MAX_GAS_ESTIMATION } from '~/util/constants';
import { validateAddress, validateUint } from '~/util/validation'; import { validateAddress, validateUint } from '~/util/validation';
@ -56,12 +57,12 @@ class ExecuteContract extends Component {
} }
static propTypes = { static propTypes = {
isTest: PropTypes.bool,
fromAddress: PropTypes.string,
accounts: PropTypes.object, accounts: PropTypes.object,
balances: PropTypes.object, balances: PropTypes.object,
contract: PropTypes.object, contract: PropTypes.object.isRequired,
fromAddress: PropTypes.string,
gasLimit: PropTypes.object.isRequired, gasLimit: PropTypes.object.isRequired,
isTest: PropTypes.bool,
onClose: PropTypes.func.isRequired, onClose: PropTypes.func.isRequired,
onFromAddressChange: PropTypes.func.isRequired onFromAddressChange: PropTypes.func.isRequired
} }
@ -77,11 +78,11 @@ class ExecuteContract extends Component {
funcError: null, funcError: null,
gasEdit: false, gasEdit: false,
rejected: false, rejected: false,
step: STEP_DETAILS,
sending: false, sending: false,
step: STEP_DETAILS,
txhash: null,
values: [], values: [],
valuesError: [], valuesError: []
txhash: null
} }
componentDidMount () { componentDidMount () {
@ -255,10 +256,6 @@ class ExecuteContract extends Component {
valueError = validateAddress(_value).addressError; valueError = validateAddress(_value).addressError;
break; break;
case 'bool':
value = _value === 'true';
break;
case 'uint': case 'uint':
valueError = validateUint(_value).valueError; valueError = validateUint(_value).valueError;
break; break;
@ -278,13 +275,12 @@ class ExecuteContract extends Component {
} }
estimateGas = (_fromAddress) => { estimateGas = (_fromAddress) => {
const { api } = this.context;
const { fromAddress } = this.props; const { fromAddress } = this.props;
const { amount, func, values } = this.state; const { amount, func, values } = this.state;
const options = { const options = {
gas: MAX_GAS_ESTIMATION, gas: MAX_GAS_ESTIMATION,
from: _fromAddress || fromAddress, from: _fromAddress || fromAddress,
value: api.util.toWei(amount || 0) value: toWei(amount || 0)
}; };
if (!func) { if (!func) {

View File

@ -0,0 +1,69 @@
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { shallow } from 'enzyme';
import React from 'react';
import sinon from 'sinon';
import ExecuteContract from './';
import { CONTRACT, STORE } from './executeContract.test.js';
let component;
let onClose;
let onFromAddressChange;
function render (props) {
onClose = sinon.stub();
onFromAddressChange = sinon.stub();
component = shallow(
<ExecuteContract
{ ...props }
contract={ CONTRACT }
onClose={ onClose }
onFromAddressChange={ onFromAddressChange } />,
{ context: { api: {}, store: STORE } }
).find('ExecuteContract').shallow();
return component;
}
describe('modals/ExecuteContract/DetailsStep', () => {
it('renders', () => {
expect(render({ accounts: {} })).to.be.ok;
});
describe('instance functions', () => {
beforeEach(() => {
render({
accounts: {}
});
});
describe('onValueChange', () => {
it('toggles boolean from false to true', () => {
component.setState({
func: CONTRACT.functions[0],
values: [false]
});
component.instance().onValueChange(null, 0, true);
expect(component.state().values).to.deep.equal([true]);
});
});
});
});

View File

@ -0,0 +1,64 @@
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import BigNumber from 'bignumber.js';
import sinon from 'sinon';
const CONTRACT = {
functions: [
{
name: 'test_a',
signature: 'test_a',
estimateGas: sinon.stub().resolves(new BigNumber(123)),
inputs: [
{
name: 'test_bool',
kind: {
type: 'bool'
}
}
],
abi: {
inputs: [
{
name: 'test_bool',
type: 'bool'
}
]
}
}
]
};
const STORE = {
dispatch: sinon.stub(),
subscribe: sinon.stub(),
getState: () => {
return {
balances: {
balances: {}
},
nodeStatus: {
gasLimit: new BigNumber(123)
}
};
}
};
export {
CONTRACT,
STORE
};

View File

@ -15,10 +15,6 @@
/* along with Parity. If not, see <http://www.gnu.org/licenses/>. /* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/ */
.list li {
padding: .1em 0;
}
.spacing { .spacing {
margin-top: 1.5em; margin-top: 1.5em;
} }

View File

@ -25,41 +25,33 @@ import { fromWei } from '~/api/util/wei';
import { Form, Input } from '~/ui'; import { Form, Input } from '~/ui';
import { nullableProptype } from '~/util/proptypes'; import { nullableProptype } from '~/util/proptypes';
import { termsOfService } from '../../../3rdparty/sms-verification'; import smsTermsOfService from '~/3rdparty/sms-verification/terms-of-service';
import emailTermsOfService from '~/3rdparty/email-verification/terms-of-service';
import { howSMSVerificationWorks, howEmailVerificationWorks } from '../how-it-works';
import styles from './gatherData.css'; import styles from './gatherData.css';
export default class GatherData extends Component { export default class GatherData extends Component {
static propTypes = { static propTypes = {
fee: React.PropTypes.instanceOf(BigNumber), fee: React.PropTypes.instanceOf(BigNumber),
isNumberValid: PropTypes.bool.isRequired, method: PropTypes.string.isRequired,
fields: PropTypes.array.isRequired,
isVerified: nullableProptype(PropTypes.bool.isRequired), isVerified: nullableProptype(PropTypes.bool.isRequired),
hasRequested: nullableProptype(PropTypes.bool.isRequired), hasRequested: nullableProptype(PropTypes.bool.isRequired),
setNumber: PropTypes.func.isRequired,
setConsentGiven: PropTypes.func.isRequired setConsentGiven: PropTypes.func.isRequired
} }
render () { render () {
const { isNumberValid, isVerified } = this.props; const { method, isVerified } = this.props;
const termsOfService = method === 'email' ? emailTermsOfService : smsTermsOfService;
const howItWorks = method === 'email' ? howEmailVerificationWorks : howSMSVerificationWorks;
return ( return (
<Form> <Form>
<p>The following steps will let you prove that you control both an account and a phone number.</p> { howItWorks }
<ol className={ styles.list }>
<li>You send a verification request to a specific contract.</li>
<li>Our server puts a puzzle into this contract.</li>
<li>The code you receive via SMS is the solution to this puzzle.</li>
</ol>
{ this.renderFee() } { this.renderFee() }
{ this.renderCertified() } { this.renderCertified() }
{ this.renderRequested() } { this.renderRequested() }
<Input { this.renderFields() }
label={ 'phone number in international format' }
hint={ 'the SMS will be sent to this number' }
error={ isNumberValid ? null : 'invalid number' }
disabled={ isVerified }
onChange={ this.numberOnChange }
onSubmit={ this.numberOnSubmit }
/>
<Checkbox <Checkbox
className={ styles.spacing } className={ styles.spacing }
label={ 'I agree to the terms and conditions below.' } label={ 'I agree to the terms and conditions below.' }
@ -136,12 +128,28 @@ export default class GatherData extends Component {
); );
} }
numberOnSubmit = (value) => { renderFields () {
this.props.setNumber(value); const { isVerified, fields } = this.props;
}
numberOnChange = (_, value) => { const rendered = fields.map((field) => {
this.props.setNumber(value); const onChange = (_, v) => {
field.onChange(v);
};
const onSubmit = field.onChange;
return (
<Input
key={ field.key }
label={ field.label }
hint={ field.hint }
error={ field.error }
disabled={ isVerified }
onChange={ onChange }
onSubmit={ onSubmit }
/>
);
});
return (<div>{rendered}</div>);
} }
consentOnChange = (_, consentGiven) => { consentOnChange = (_, consentGiven) => {

View File

@ -20,20 +20,25 @@ import { Form, Input } from '~/ui';
export default class QueryCode extends Component { export default class QueryCode extends Component {
static propTypes = { static propTypes = {
number: PropTypes.string.isRequired, receiver: PropTypes.string.isRequired,
hint: PropTypes.string,
isCodeValid: PropTypes.bool.isRequired, isCodeValid: PropTypes.bool.isRequired,
setCode: PropTypes.func.isRequired setCode: PropTypes.func.isRequired
} }
static defaultProps = {
hint: 'Enter the code you received.'
}
render () { render () {
const { number, isCodeValid } = this.props; const { receiver, hint, isCodeValid } = this.props;
return ( return (
<Form> <Form>
<p>The verification code has been sent to { number }.</p> <p>The verification code has been sent to { receiver }.</p>
<Input <Input
label={ 'verification code' } label={ 'verification code' }
hint={ 'Enter the code you received via SMS.' } hint={ hint }
error={ isCodeValid ? null : 'invalid code' } error={ isCodeValid ? null : 'invalid code' }
onChange={ this.onChange } onChange={ this.onChange }
onSubmit={ this.onSubmit } onSubmit={ this.onSubmit }

View File

@ -19,7 +19,7 @@ import React, { Component, PropTypes } from 'react';
import { nullableProptype } from '~/util/proptypes'; import { nullableProptype } from '~/util/proptypes';
import TxHash from '~/ui/TxHash'; import TxHash from '~/ui/TxHash';
import { import {
POSTING_REQUEST, POSTED_REQUEST, REQUESTING_SMS POSTING_REQUEST, POSTED_REQUEST, REQUESTING_CODE
} from '../store'; } from '../store';
import styles from './sendRequest.css'; import styles from './sendRequest.css';
@ -45,9 +45,9 @@ export default class SendRequest extends Component {
</div> </div>
); );
case REQUESTING_SMS: case REQUESTING_CODE:
return ( return (
<p>Requesting an SMS from the Parity server and waiting for the puzzle to be put into the contract.</p> <p>Requesting a code from the Parity server and waiting for the puzzle to be put into the contract.</p>
); );
default: default:

View File

@ -0,0 +1,70 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { observable, computed, action } from 'mobx';
import { sha3 } from '~/api/util/sha3';
import EmailVerificationABI from '~/contracts/abi/email-verification.json';
import VerificationStore, {
LOADING, QUERY_DATA, QUERY_CODE, POSTED_CONFIRMATION, DONE
} from './store';
import { postToServer } from '../../3rdparty/email-verification';
export default class EmailVerificationStore extends VerificationStore {
@observable email = '';
@computed get isEmailValid () {
// See https://davidcel.is/posts/stop-validating-email-addresses-with-regex/
return this.email && this.email.indexOf('@') >= 0;
}
@computed get isStepValid () {
if (this.step === DONE) {
return true;
}
if (this.error) {
return false;
}
switch (this.step) {
case LOADING:
return this.contract && this.fee && this.isVerified !== null && this.hasRequested !== null;
case QUERY_DATA:
return this.isEmailValid && this.consentGiven;
case QUERY_CODE:
return this.requestTx && this.isCodeValid === true;
case POSTED_CONFIRMATION:
return !!this.confirmationTx;
default:
return false;
}
}
constructor (api, account, isTestnet) {
super(api, EmailVerificationABI, 'emailverification3', account, isTestnet);
}
requestValues = () => [ sha3(this.email) ]
@action setEmail = (email) => {
this.email = email;
}
requestCode = () => {
const { email, account, isTestnet } = this;
return postToServer({ email, address: account }, isTestnet);
}
}

View File

@ -0,0 +1,41 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React from 'react';
import styles from './verification.css';
export const howSMSVerificationWorks = (
<div>
<p>The following steps will let you prove that you control both an account and a phone number.</p>
<ol className={ styles.list }>
<li>You send a verification request to a specific contract.</li>
<li>Our server puts a puzzle into this contract.</li>
<li>The code you receive via SMS is the solution to this puzzle.</li>
</ol>
</div>
);
export const howEmailVerificationWorks = (
<div>
<p>The following steps will let you prove that you control both an account and an e-mail address.</p>
<ol className={ styles.list }>
<li>You send a verification request to a specific contract.</li>
<li>Our server puts a puzzle into this contract.</li>
<li>The code you receive via e-mail is the solution to this puzzle.</li>
</ol>
</div>
);

View File

@ -1,4 +1,4 @@
// Copyright 2015, 2016 Parity Technologies (UK) Ltd. // Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity. // This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify // Parity is free software: you can redistribute it and/or modify
@ -14,4 +14,4 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default from './SMSVerification'; export default from './verification';

View File

@ -0,0 +1,67 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { observable, computed, action } from 'mobx';
import phone from 'phoneformat.js';
import SMSVerificationABI from '~/contracts/abi/sms-verification.json';
import VerificationStore, {
LOADING, QUERY_DATA, QUERY_CODE, POSTED_CONFIRMATION, DONE
} from './store';
import { postToServer } from '../../3rdparty/sms-verification';
export default class SMSVerificationStore extends VerificationStore {
@observable number = '';
@computed get isNumberValid () {
return phone.isValidNumber(this.number);
}
@computed get isStepValid () {
if (this.step === DONE) {
return true;
}
if (this.error) {
return false;
}
switch (this.step) {
case LOADING:
return this.contract && this.fee && this.isVerified !== null && this.hasRequested !== null;
case QUERY_DATA:
return this.isNumberValid && this.consentGiven;
case QUERY_CODE:
return this.requestTx && this.isCodeValid === true;
case POSTED_CONFIRMATION:
return !!this.confirmationTx;
default:
return false;
}
}
constructor (api, account, isTestnet) {
super(api, SMSVerificationABI, 'smsverification', account, isTestnet);
}
@action setNumber = (number) => {
this.number = number;
}
requestCode = () => {
const { number, account, isTestnet } = this;
return postToServer({ number, address: account }, isTestnet);
}
}

View File

@ -14,21 +14,19 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { observable, computed, autorun, action } from 'mobx'; import { observable, autorun, action } from 'mobx';
import phone from 'phoneformat.js';
import { sha3 } from '~/api/util/sha3'; import { sha3 } from '~/api/util/sha3';
import Contract from '~/api/contract';
import Contracts from '~/contracts'; import Contracts from '~/contracts';
import { checkIfVerified, checkIfRequested, awaitPuzzle } from '~/contracts/sms-verification'; import { checkIfVerified, checkIfRequested, awaitPuzzle } from '~/contracts/verification';
import { postToServer } from '~/3rdparty/sms-verification';
import { checkIfTxFailed, waitForConfirmations } from '~/util/tx'; import { checkIfTxFailed, waitForConfirmations } from '~/util/tx';
export const LOADING = 'fetching-contract'; export const LOADING = 'fetching-contract';
export const QUERY_DATA = 'query-data'; export const QUERY_DATA = 'query-data';
export const POSTING_REQUEST = 'posting-request'; export const POSTING_REQUEST = 'posting-request';
export const POSTED_REQUEST = 'posted-request'; export const POSTED_REQUEST = 'posted-request';
export const REQUESTING_SMS = 'requesting-sms'; export const REQUESTING_CODE = 'requesting-code';
export const QUERY_CODE = 'query-code'; export const QUERY_CODE = 'query-code';
export const POSTING_CONFIRMATION = 'posting-confirmation'; export const POSTING_CONFIRMATION = 'posting-confirmation';
export const POSTED_CONFIRMATION = 'posted-confirmation'; export const POSTED_CONFIRMATION = 'posted-confirmation';
@ -43,56 +41,30 @@ export default class VerificationStore {
@observable isVerified = null; @observable isVerified = null;
@observable hasRequested = null; @observable hasRequested = null;
@observable consentGiven = false; @observable consentGiven = false;
@observable number = '';
@observable requestTx = null; @observable requestTx = null;
@observable code = ''; @observable code = '';
@observable isCodeValid = null; @observable isCodeValid = null;
@observable confirmationTx = null; @observable confirmationTx = null;
@computed get isNumberValid () { constructor (api, abi, name, account, isTestnet) {
return phone.isValidNumber(this.number);
}
@computed get isStepValid () {
if (this.step === DONE) {
return true;
}
if (this.error) {
return false;
}
switch (this.step) {
case LOADING:
return this.contract && this.fee && this.isVerified !== null && this.hasRequested !== null;
case QUERY_DATA:
return this.isNumberValid && this.consentGiven;
case QUERY_CODE:
return this.requestTx && this.isCodeValid === true;
case POSTED_CONFIRMATION:
return !!this.confirmationTx;
default:
return false;
}
}
constructor (api, account, isTestnet) {
this.api = api; this.api = api;
this.account = account; this.account = account;
this.isTestnet = isTestnet; this.isTestnet = isTestnet;
this.step = LOADING; this.step = LOADING;
Contracts.create(api).registry.getContract('smsverification') Contracts.get().badgeReg.fetchCertifier(name)
.then((contract) => { .then(({ address }) => {
this.contract = contract; this.contract = new Contract(api, abi).at(address);
this.load(); this.load();
}) })
.catch((err) => { .catch((err) => {
console.error('error', err);
this.error = 'Failed to fetch the contract: ' + err.message; this.error = 'Failed to fetch the contract: ' + err.message;
}); });
autorun(() => { autorun(() => {
if (this.error) { if (this.error) {
console.error('sms verification: ' + this.error); console.error('verification: ' + this.error);
} }
}); });
} }
@ -135,10 +107,6 @@ export default class VerificationStore {
}); });
} }
@action setNumber = (number) => {
this.number = number;
}
@action setConsentGiven = (consentGiven) => { @action setConsentGiven = (consentGiven) => {
this.consentGiven = consentGiven; this.consentGiven = consentGiven;
} }
@ -166,19 +134,22 @@ export default class VerificationStore {
}); });
} }
requestValues = () => []
@action sendRequest = () => { @action sendRequest = () => {
const { api, account, contract, fee, number, hasRequested } = this; const { api, account, contract, fee, hasRequested } = this;
const request = contract.functions.find((fn) => fn.name === 'request'); const request = contract.functions.find((fn) => fn.name === 'request');
const options = { from: account, value: fee.toString() }; const options = { from: account, value: fee.toString() };
const values = this.requestValues();
let chain = Promise.resolve(); let chain = Promise.resolve();
if (!hasRequested) { if (!hasRequested) {
this.step = POSTING_REQUEST; this.step = POSTING_REQUEST;
chain = request.estimateGas(options, []) chain = request.estimateGas(options, values)
.then((gas) => { .then((gas) => {
options.gas = gas.mul(1.2).toFixed(0); options.gas = gas.mul(1.2).toFixed(0);
return request.postTransaction(options, []); return request.postTransaction(options, values);
}) })
.then((handle) => { .then((handle) => {
// TODO: The "request rejected" error doesn't have any property to // TODO: The "request rejected" error doesn't have any property to
@ -200,18 +171,15 @@ export default class VerificationStore {
chain chain
.then(() => { .then(() => {
return api.parity.netChain(); this.step = REQUESTING_CODE;
}) return this.requestCode();
.then((chain) => {
this.step = REQUESTING_SMS;
return postToServer({ number, address: account }, this.isTestnet);
}) })
.then(() => awaitPuzzle(api, contract, account)) .then(() => awaitPuzzle(api, contract, account))
.then(() => { .then(() => {
this.step = QUERY_CODE; this.step = QUERY_CODE;
}) })
.catch((err) => { .catch((err) => {
this.error = 'Failed to request a confirmation SMS: ' + err.message; this.error = 'Failed to request a confirmation code: ' + err.message;
}); });
} }

View File

@ -0,0 +1,25 @@
/* Copyright 2015, 2016 Ethcore (UK) Ltd.
/* This file is part of Parity.
/*
/* Parity is free software: you can redistribute it and/or modify
/* it under the terms of the GNU General Public License as published by
/* the Free Software Foundation, either version 3 of the License, or
/* (at your option) any later version.
/*
/* Parity is distributed in the hope that it will be useful,
/* but WITHOUT ANY WARRANTY; without even the implied warranty of
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
/* GNU General Public License for more details.
/*
/* You should have received a copy of the GNU General Public License
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/
.noSpacing {
margin-top: 0;
margin-bottom: 0;
}
.list li {
padding: .1em 0;
}

View File

@ -20,12 +20,27 @@ import DoneIcon from 'material-ui/svg-icons/action/done-all';
import CancelIcon from 'material-ui/svg-icons/content/clear'; import CancelIcon from 'material-ui/svg-icons/content/clear';
import { Button, IdentityIcon, Modal } from '~/ui'; import { Button, IdentityIcon, Modal } from '~/ui';
import RadioButtons from '~/ui/Form/RadioButtons';
import { nullableProptype } from '~/util/proptypes';
import styles from './verification.css';
const methods = {
sms: {
label: 'SMS Verification', key: 0, value: 'sms',
description: (<p className={ styles.noSpacing }>It will be stored on the blockchain that you control a phone number (not <em>which</em>).</p>)
},
email: {
label: 'E-mail Verification', key: 1, value: 'email',
description: (<p className={ styles.noSpacing }>The hash of the e-mail address you prove control over will be stored on the blockchain.</p>)
}
};
import { import {
LOADING, LOADING,
QUERY_DATA, QUERY_DATA,
POSTING_REQUEST, POSTED_REQUEST, POSTING_REQUEST, POSTED_REQUEST,
REQUESTING_SMS, QUERY_CODE, REQUESTING_CODE, QUERY_CODE,
POSTING_CONFIRMATION, POSTED_CONFIRMATION, POSTING_CONFIRMATION, POSTED_CONFIRMATION,
DONE DONE
} from './store'; } from './store';
@ -37,34 +52,44 @@ import SendConfirmation from './SendConfirmation';
import Done from './Done'; import Done from './Done';
@observer @observer
export default class SMSVerification extends Component { export default class Verification extends Component {
static propTypes = { static propTypes = {
store: PropTypes.any.isRequired, store: nullableProptype(PropTypes.object.isRequired),
account: PropTypes.string.isRequired, account: PropTypes.string.isRequired,
onSelectMethod: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired onClose: PropTypes.func.isRequired
} }
static phases = { // mapping (store steps -> steps) static phases = { // mapping (store steps -> steps)
[LOADING]: 0, [LOADING]: 1, [QUERY_DATA]: 1,
[QUERY_DATA]: 1, [POSTING_REQUEST]: 2, [POSTED_REQUEST]: 2, [REQUESTING_CODE]: 2,
[POSTING_REQUEST]: 2, [POSTED_REQUEST]: 2, [REQUESTING_SMS]: 2,
[QUERY_CODE]: 3, [QUERY_CODE]: 3,
[POSTING_CONFIRMATION]: 4, [POSTED_CONFIRMATION]: 4, [POSTING_CONFIRMATION]: 4, [POSTED_CONFIRMATION]: 4,
[DONE]: 5 [DONE]: 5
} }
state = {
method: 'sms'
};
render () { render () {
const phase = SMSVerification.phases[this.props.store.step]; const { store } = this.props;
const { error, isStepValid } = this.props.store; let phase = 0; let error = false; let isStepValid = true;
if (store) {
phase = Verification.phases[store.step];
error = store.error;
isStepValid = store.isStepValid;
}
return ( return (
<Modal <Modal
actions={ this.renderDialogActions(phase, error, isStepValid) } actions={ this.renderDialogActions(phase, error, isStepValid) }
title='verify your account via SMS' title='verify your account'
visible visible
current={ phase } current={ phase }
steps={ ['Prepare', 'Enter Data', 'Request', 'Enter Code', 'Confirm', 'Done!'] } steps={ ['Method', 'Enter Data', 'Request', 'Enter Code', 'Confirm', 'Done!'] }
waiting={ error ? [] : [ 0, 2, 4 ] } waiting={ error ? [] : [ 2, 4 ] }
> >
{ this.renderStep(phase, error) } { this.renderStep(phase, error) }
</Modal> </Modal>
@ -101,6 +126,13 @@ export default class SMSVerification extends Component {
let action = () => {}; let action = () => {};
switch (phase) { switch (phase) {
case 0:
action = () => {
const { onSelectMethod } = this.props;
const { method } = this.state;
onSelectMethod(method);
};
break;
case 1: case 1:
action = store.sendRequest; action = store.sendRequest;
break; break;
@ -133,26 +165,58 @@ export default class SMSVerification extends Component {
return (<p>{ error }</p>); return (<p>{ error }</p>);
} }
const { method } = this.state;
if (phase === 0) {
const values = Object.values(methods);
const value = values.findIndex((v) => v.value === method);
return (
<RadioButtons
value={ value < 0 ? 0 : value }
values={ values }
onChange={ this.selectMethod }
/>
);
}
const { const {
step, step,
fee, number, isNumberValid, isVerified, hasRequested, fee, isVerified, hasRequested,
requestTx, isCodeValid, confirmationTx, requestTx, isCodeValid, confirmationTx,
setCode setCode
} = this.props.store; } = this.props.store;
switch (phase) { switch (phase) {
case 0:
return (
<p>Loading SMS Verification.</p>
);
case 1: case 1:
const { setNumber, setConsentGiven } = this.props.store; if (step === LOADING) {
return (<p>Loading verification data.</p>);
}
const { setConsentGiven } = this.props.store;
const fields = [];
if (method === 'sms') {
fields.push({
key: 'number',
label: 'phone number in international format',
hint: 'the SMS will be sent to this number',
error: this.props.store.isNumberValid ? null : 'invalid number',
onChange: this.props.store.setNumber
});
} else if (method === 'email') {
fields.push({
key: 'email',
label: 'email address',
hint: 'the code will be sent to this address',
error: this.props.store.isEmailValid ? null : 'invalid email',
onChange: this.props.store.setEmail
});
}
return ( return (
<GatherData <GatherData
fee={ fee } isNumberValid={ isNumberValid } method={ method } fields={ fields }
isVerified={ isVerified } hasRequested={ hasRequested } fee={ fee } isVerified={ isVerified } hasRequested={ hasRequested }
setNumber={ setNumber } setConsentGiven={ setConsentGiven } setConsentGiven={ setConsentGiven }
/> />
); );
@ -162,9 +226,19 @@ export default class SMSVerification extends Component {
); );
case 3: case 3:
let receiver, hint;
if (method === 'sms') {
receiver = this.props.store.number;
hint = 'Enter the code you received via SMS.';
} else if (method === 'email') {
receiver = this.props.store.email;
hint = 'Enter the code you received via e-mail.';
}
return ( return (
<QueryCode <QueryCode
number={ number } fee={ fee } isCodeValid={ isCodeValid } receiver={ receiver }
hint={ hint }
isCodeValid={ isCodeValid }
setCode={ setCode } setCode={ setCode }
/> />
); );
@ -183,4 +257,8 @@ export default class SMSVerification extends Component {
return null; return null;
} }
} }
selectMethod = (choice, i) => {
this.setState({ method: choice.value });
}
} }

View File

@ -24,7 +24,7 @@ import EditMeta from './EditMeta';
import ExecuteContract from './ExecuteContract'; import ExecuteContract from './ExecuteContract';
import FirstRun from './FirstRun'; import FirstRun from './FirstRun';
import Shapeshift from './Shapeshift'; import Shapeshift from './Shapeshift';
import SMSVerification from './SMSVerification'; import Verification from './Verification';
import Transfer from './Transfer'; import Transfer from './Transfer';
import PasswordManager from './PasswordManager'; import PasswordManager from './PasswordManager';
import SaveContract from './SaveContract'; import SaveContract from './SaveContract';
@ -42,7 +42,7 @@ export {
ExecuteContract, ExecuteContract,
FirstRun, FirstRun,
Shapeshift, Shapeshift,
SMSVerification, Verification,
Transfer, Transfer,
PasswordManager, PasswordManager,
LoadContract, LoadContract,

View File

@ -46,26 +46,26 @@ export default class Select extends Component {
} }
render () { render () {
const { disabled, error, label, hint, value, children, className, onBlur, onChange, onKeyDown } = this.props; const { children, className, disabled, error, hint, label, onBlur, onChange, onKeyDown, value } = this.props;
return ( return (
<SelectField <SelectField
className={ className }
autoComplete='off' autoComplete='off'
className={ className }
disabled={ disabled } disabled={ disabled }
errorText={ error } errorText={ error }
floatingLabelFixed floatingLabelFixed
floatingLabelText={ label } floatingLabelText={ label }
fullWidth fullWidth
hintText={ hint } hintText={ hint }
name={ NAME_ID }
id={ NAME_ID } id={ NAME_ID }
underlineDisabledStyle={ UNDERLINE_DISABLED } name={ NAME_ID }
underlineStyle={ UNDERLINE_NORMAL }
value={ value }
onBlur={ onBlur } onBlur={ onBlur }
onChange={ onChange } onChange={ onChange }
onKeyDown={ onKeyDown }> onKeyDown={ onKeyDown }
underlineDisabledStyle={ UNDERLINE_DISABLED }
underlineStyle={ UNDERLINE_NORMAL }
value={ value }>
{ children } { children }
</SelectField> </SelectField>
); );

View File

@ -289,9 +289,8 @@ export default class TypedInput extends Component {
return ( return (
<MenuItem <MenuItem
key={ bool } key={ bool }
value={ bool }
label={ bool } label={ bool }
> value={ bool }>
{ bool } { bool }
</MenuItem> </MenuItem>
); );
@ -299,19 +298,23 @@ export default class TypedInput extends Component {
return ( return (
<Select <Select
label={ label }
hint={ hint }
value={ value ? 'true' : 'false' }
error={ error } error={ error }
hint={ hint }
label={ label }
onChange={ this.onChangeBool } onChange={ this.onChangeBool }
> value={
value
? 'true'
: 'false'
}>
{ boolitems } { boolitems }
</Select> </Select>
); );
} }
onChangeBool = (event, _index, value) => { onChangeBool = (event, _index, value) => {
this.props.onChange(value === 'true'); // NOTE: event.target.value added for enzyme simulated event testing
this.props.onChange((value || event.target.value) === 'true');
} }
onEthTypeChange = () => { onEthTypeChange = () => {

View File

@ -0,0 +1,70 @@
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { mount } from 'enzyme';
import React from 'react';
import sinon from 'sinon';
import { ContextProvider, muiTheme } from '~/ui';
import { ABI_TYPES } from '~/util/abi';
import TypedInput from './';
let component;
let onChange;
function render (props) {
onChange = sinon.stub();
component = mount(
<ContextProvider api={ {} } muiTheme={ muiTheme } store={ {} }>
<TypedInput
{ ...props }
onChange={ onChange } />
</ContextProvider>
);
return component;
}
describe('ui/Form/TypedInput', () => {
describe('bool selection', () => {
beforeEach(() => {
render({ param: { type: ABI_TYPES.BOOL } });
});
it('renders', () => {
expect(component).to.be.ok;
});
it('calls onChange when value changes', () => {
component.find('DropDownMenu').simulate('change', { target: { value: 'true' } });
expect(onChange).to.have.been.called;
});
it("calls onChange(true) when value changes to 'true'", () => {
component.find('DropDownMenu').simulate('change', { target: { value: 'true' } });
expect(onChange).to.have.been.calledWith(true);
});
it("calls onChange(false) when value changes to 'false'", () => {
component.find('DropDownMenu').simulate('change', { target: { value: 'false' } });
expect(onChange).to.have.been.calledWith(false);
});
});
});

View File

@ -19,6 +19,8 @@ import { connect } from 'react-redux';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
import ContractIcon from 'material-ui/svg-icons/action/code'; import ContractIcon from 'material-ui/svg-icons/action/code';
import { createIdentityImg } from '~/api/util/identity';
import styles from './identityIcon.css'; import styles from './identityIcon.css';
class IdentityIcon extends Component { class IdentityIcon extends Component {
@ -29,12 +31,12 @@ class IdentityIcon extends Component {
static propTypes = { static propTypes = {
address: PropTypes.string, address: PropTypes.string,
button: PropTypes.bool, button: PropTypes.bool,
className: PropTypes.string,
center: PropTypes.bool, center: PropTypes.bool,
padded: PropTypes.bool, className: PropTypes.string,
inline: PropTypes.bool, inline: PropTypes.bool,
tiny: PropTypes.bool, images: PropTypes.object.isRequired,
images: PropTypes.object.isRequired padded: PropTypes.bool,
tiny: PropTypes.bool
} }
state = { state = {
@ -75,7 +77,7 @@ class IdentityIcon extends Component {
} }
this.setState({ this.setState({
iconsrc: api.util.createIdentityImg(_address, scale) iconsrc: createIdentityImg(_address, scale)
}); });
} }
@ -105,16 +107,20 @@ class IdentityIcon extends Component {
return ( return (
<ContractIcon <ContractIcon
className={ classes } className={ classes }
style={ { width: size, height: size, background: '#eee' } } /> style={ {
width: size,
height: size,
background: '#eee'
} } />
); );
} }
return ( return (
<img <img
className={ classes } className={ classes }
src={ iconsrc } height={ size }
width={ size } width={ size }
height={ size } /> src={ iconsrc } />
); );
} }
} }

View File

@ -23,7 +23,7 @@ import ContentSend from 'material-ui/svg-icons/content/send';
import LockIcon from 'material-ui/svg-icons/action/lock'; import LockIcon from 'material-ui/svg-icons/action/lock';
import VerifyIcon from 'material-ui/svg-icons/action/verified-user'; import VerifyIcon from 'material-ui/svg-icons/action/verified-user';
import { EditMeta, DeleteAccount, Shapeshift, SMSVerification, Transfer, PasswordManager } from '~/modals'; import { EditMeta, DeleteAccount, Shapeshift, Verification, Transfer, PasswordManager } from '~/modals';
import { Actionbar, Button, Page } from '~/ui'; import { Actionbar, Button, Page } from '~/ui';
import shapeshiftBtn from '~/../assets/images/shapeshift-btn.png'; import shapeshiftBtn from '~/../assets/images/shapeshift-btn.png';
@ -33,7 +33,8 @@ import Transactions from './Transactions';
import { setVisibleAccounts } from '~/redux/providers/personalActions'; import { setVisibleAccounts } from '~/redux/providers/personalActions';
import { fetchCertifiers, fetchCertifications } from '~/redux/providers/certifications/actions'; import { fetchCertifiers, fetchCertifications } from '~/redux/providers/certifications/actions';
import VerificationStore from '~/modals/SMSVerification/store'; import SMSVerificationStore from '~/modals/Verification/sms-store';
import EmailVerificationStore from '~/modals/Verification/email-store';
import styles from './account.css'; import styles from './account.css';
@ -76,15 +77,6 @@ class Account extends Component {
if (prevAddress !== nextAddress) { if (prevAddress !== nextAddress) {
this.setVisibleAccounts(nextProps); this.setVisibleAccounts(nextProps);
} }
const { isTestnet } = nextProps;
if (typeof isTestnet === 'boolean' && !this.state.verificationStore) {
const { api } = this.context;
const { address } = nextProps.params;
this.setState({
verificationStore: new VerificationStore(api, address, isTestnet)
});
}
} }
componentWillUnmount () { componentWillUnmount () {
@ -233,8 +225,9 @@ class Account extends Component {
const { address } = this.props.params; const { address } = this.props.params;
return ( return (
<SMSVerification <Verification
store={ store } account={ address } store={ store } account={ address }
onSelectMethod={ this.selectVerificationMethod }
onClose={ this.onVerificationClose } onClose={ this.onVerificationClose }
/> />
); );
@ -308,6 +301,22 @@ class Account extends Component {
this.setState({ showVerificationDialog: true }); this.setState({ showVerificationDialog: true });
} }
selectVerificationMethod = (name) => {
const { isTestnet } = this.props;
if (typeof isTestnet !== 'boolean' || this.state.verificationStore) return;
const { api } = this.context;
const { address } = this.props.params;
let verificationStore = null;
if (name === 'sms') {
verificationStore = new SMSVerificationStore(api, address, isTestnet);
} else if (name === 'email') {
verificationStore = new EmailVerificationStore(api, address, isTestnet);
}
this.setState({ verificationStore });
}
onVerificationClose = () => { onVerificationClose = () => {
this.setState({ showVerificationDialog: false }); this.setState({ showVerificationDialog: false });
} }

View File

@ -49,7 +49,7 @@ impl str::FromStr for SpecType {
let spec = match s { let spec = match s {
"frontier" | "homestead" | "mainnet" => SpecType::Mainnet, "frontier" | "homestead" | "mainnet" => SpecType::Mainnet,
"frontier-dogmatic" | "homestead-dogmatic" | "classic" => SpecType::Classic, "frontier-dogmatic" | "homestead-dogmatic" | "classic" => SpecType::Classic,
"morden" | "testnet" => SpecType::Testnet, "morden" | "testnet" | "classic-testnet" => SpecType::Testnet,
"ropsten" => SpecType::Ropsten, "ropsten" => SpecType::Ropsten,
"olympic" => SpecType::Olympic, "olympic" => SpecType::Olympic,
"expanse" => SpecType::Expanse, "expanse" => SpecType::Expanse,
@ -288,6 +288,8 @@ mod tests {
assert_eq!(SpecType::Testnet, "morden".parse().unwrap()); assert_eq!(SpecType::Testnet, "morden".parse().unwrap());
assert_eq!(SpecType::Ropsten, "ropsten".parse().unwrap()); assert_eq!(SpecType::Ropsten, "ropsten".parse().unwrap());
assert_eq!(SpecType::Olympic, "olympic".parse().unwrap()); assert_eq!(SpecType::Olympic, "olympic".parse().unwrap());
assert_eq!(SpecType::Classic, "classic".parse().unwrap());
assert_eq!(SpecType::Testnet, "classic-testnet".parse().unwrap());
} }
#[test] #[test]

View File

@ -15,6 +15,7 @@ serde_json = "0.8"
jsonrpc-core = { git = "https://github.com/ethcore/jsonrpc.git" } jsonrpc-core = { git = "https://github.com/ethcore/jsonrpc.git" }
jsonrpc-http-server = { git = "https://github.com/ethcore/jsonrpc.git" } jsonrpc-http-server = { git = "https://github.com/ethcore/jsonrpc.git" }
jsonrpc-ipc-server = { git = "https://github.com/ethcore/jsonrpc.git" } jsonrpc-ipc-server = { git = "https://github.com/ethcore/jsonrpc.git" }
jsonrpc-macros = { git = "https://github.com/ethcore/jsonrpc.git" }
ethcore-io = { path = "../util/io" } ethcore-io = { path = "../util/io" }
ethcore-util = { path = "../util" } ethcore-util = { path = "../util" }
ethcore = { path = "../ethcore" } ethcore = { path = "../ethcore" }

View File

@ -42,6 +42,8 @@ extern crate fetch;
extern crate log; extern crate log;
#[macro_use] #[macro_use]
extern crate ethcore_util as util; extern crate ethcore_util as util;
#[macro_use]
extern crate jsonrpc_macros;
#[cfg(test)] #[cfg(test)]
extern crate ethjson; extern crate ethjson;

View File

@ -1,310 +0,0 @@
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Automatically serialize and deserialize parameters around a strongly-typed function.
// because we reuse the type names as idents in the macros as a dirty hack to
// work around `concat_idents!` being unstable.
#![allow(non_snake_case)]
use super::errors;
use jsonrpc_core::{Error, Params, Value, from_params, to_value};
use serde::{Serialize, Deserialize};
/// Auto-generates an RPC trait from trait definition.
///
/// This just copies out all the methods, docs, and adds another
/// function `to_delegate` which will automatically wrap each strongly-typed
/// function in a wrapper which handles parameter and output type serialization.
///
/// RPC functions may come in a couple forms: async and synchronous.
/// These are parsed with the custom `#[rpc]` attribute, which must follow
/// documentation.
///
/// ## The #[rpc] attribute
///
/// Valid forms:
/// - `#[rpc(name = "name_here")]` (a synchronous rpc function which should be bound to the given name)
/// - `#[rpc(async, name = "name_here")]` (an async rpc function which should be bound to the given name)
///
/// Synchronous function format:
/// `fn foo(&self, Param1, Param2, Param3) -> Out`.
///
/// Asynchronous RPC functions must come in this form:
/// `fn foo(&self, Param1, Param2, Param3, Ready<Out>);
///
/// Anything else will be rejected by the code generator.
macro_rules! build_rpc_trait {
// entry-point. todo: make another for traits w/ bounds.
(
$(#[$t_attr: meta])*
pub trait $name: ident {
$(
$( #[doc=$m_doc:expr] )*
#[ rpc( $($t:tt)* ) ]
fn $m_name: ident ( $($p: tt)* ) $( -> Result<$out: ty, Error> )* ;
)*
}
) => {
$(#[$t_attr])*
pub trait $name: Sized + Send + Sync + 'static {
$(
$(#[doc=$m_doc])*
fn $m_name ( $($p)* ) $( -> Result<$out, Error> )* ;
)*
/// Transform this into an `IoDelegate`, automatically wrapping
/// the parameters.
fn to_delegate(self) -> ::jsonrpc_core::IoDelegate<Self> {
let mut del = ::jsonrpc_core::IoDelegate::new(self.into());
$(
build_rpc_trait!(WRAP del =>
( $($t)* )
fn $m_name ( $($p)* ) $( -> Result<$out, Error> )*
);
)*
del
}
}
};
( WRAP $del: expr =>
(name = $name: expr)
fn $method: ident (&self $(, $param: ty)*) -> Result<$out: ty, Error>
) => {
$del.add_method($name, move |base, params| {
(Self::$method as fn(&_ $(, $param)*) -> Result<$out, Error>).wrap_rpc(base, params)
})
};
( WRAP $del: expr =>
(async, name = $name: expr)
fn $method: ident (&self, Ready<$out: ty> $(, $param: ty)*)
) => {
$del.add_async_method($name, move |base, params, ready| {
(Self::$method as fn(&_, Ready<$out> $(, $param)*)).wrap_rpc(base, params, ready)
})
};
}
/// A wrapper type without an implementation of `Deserialize`
/// which allows a special implementation of `Wrap` for functions
/// that take a trailing default parameter.
pub struct Trailing<T: Default + Deserialize>(pub T);
/// A wrapper type for `jsonrpc_core`'s weakly-typed `Ready` struct.
pub struct Ready<T: Serialize> {
inner: ::jsonrpc_core::Ready,
_marker: ::std::marker::PhantomData<T>,
}
impl<T: Serialize> From<::jsonrpc_core::Ready> for Ready<T> {
fn from(ready: ::jsonrpc_core::Ready) -> Self {
Ready { inner: ready, _marker: ::std::marker::PhantomData }
}
}
impl<T: Serialize> Ready<T> {
/// Respond withthe asynchronous result.
pub fn ready(self, result: Result<T, Error>) {
self.inner.ready(result.map(to_value))
}
}
/// Wrapper trait for synchronous RPC functions.
pub trait Wrap<B: Send + Sync + 'static> {
fn wrap_rpc(&self, base: &B, params: Params) -> Result<Value, Error>;
}
/// Wrapper trait for asynchronous RPC functions.
pub trait WrapAsync<B: Send + Sync + 'static> {
fn wrap_rpc(&self, base: &B, params: Params, ready: ::jsonrpc_core::Ready);
}
// special impl for no parameters.
impl<B, OUT> Wrap<B> for fn(&B) -> Result<OUT, Error>
where B: Send + Sync + 'static, OUT: Serialize
{
fn wrap_rpc(&self, base: &B, params: Params) -> Result<Value, Error> {
::v1::helpers::params::expect_no_params(params)
.and_then(|()| (self)(base))
.map(to_value)
}
}
impl<B, OUT> WrapAsync<B> for fn(&B, Ready<OUT>)
where B: Send + Sync + 'static, OUT: Serialize
{
fn wrap_rpc(&self, base: &B, params: Params, ready: ::jsonrpc_core::Ready) {
match ::v1::helpers::params::expect_no_params(params) {
Ok(()) => (self)(base, ready.into()),
Err(e) => ready.ready(Err(e)),
}
}
}
// creates a wrapper implementation which deserializes the parameters,
// calls the function with concrete type, and serializes the output.
macro_rules! wrap {
($($x: ident),+) => {
// synchronous implementation
impl <
BASE: Send + Sync + 'static,
OUT: Serialize,
$($x: Deserialize,)+
> Wrap<BASE> for fn(&BASE, $($x,)+) -> Result<OUT, Error> {
fn wrap_rpc(&self, base: &BASE, params: Params) -> Result<Value, Error> {
from_params::<($($x,)+)>(params).and_then(|($($x,)+)| {
(self)(base, $($x,)+)
}).map(to_value)
}
}
// asynchronous implementation
impl <
BASE: Send + Sync + 'static,
OUT: Serialize,
$($x: Deserialize,)+
> WrapAsync<BASE> for fn(&BASE, Ready<OUT>, $($x,)+ ) {
fn wrap_rpc(&self, base: &BASE, params: Params, ready: ::jsonrpc_core::Ready) {
match from_params::<($($x,)+)>(params) {
Ok(($($x,)+)) => (self)(base, ready.into(), $($x,)+),
Err(e) => ready.ready(Err(e)),
}
}
}
}
}
// special impl for no parameters other than block parameter.
impl<B, OUT, T> Wrap<B> for fn(&B, Trailing<T>) -> Result<OUT, Error>
where B: Send + Sync + 'static, OUT: Serialize, T: Default + Deserialize
{
fn wrap_rpc(&self, base: &B, params: Params) -> Result<Value, Error> {
let len = match params {
Params::Array(ref v) => v.len(),
Params::None => 0,
_ => return Err(errors::invalid_params("not an array", "")),
};
let (id,) = match len {
0 => (T::default(),),
1 => try!(from_params::<(T,)>(params)),
_ => return Err(Error::invalid_params()),
};
(self)(base, Trailing(id)).map(to_value)
}
}
impl<B, OUT, T> WrapAsync<B> for fn(&B, Ready<OUT>, Trailing<T>)
where B: Send + Sync + 'static, OUT: Serialize, T: Default + Deserialize
{
fn wrap_rpc(&self, base: &B, params: Params, ready: ::jsonrpc_core::Ready) {
let len = match params {
Params::Array(ref v) => v.len(),
Params::None => 0,
_ => return ready.ready(Err(errors::invalid_params("not an array", ""))),
};
let id = match len {
0 => Ok((T::default(),)),
1 => from_params::<(T,)>(params),
_ => Err(Error::invalid_params()),
};
match id {
Ok((id,)) => (self)(base, ready.into(), Trailing(id)),
Err(e) => ready.ready(Err(e)),
}
}
}
// similar to `wrap!`, but handles a single default trailing parameter
// accepts an additional argument indicating the number of non-trailing parameters.
macro_rules! wrap_with_trailing {
($num: expr, $($x: ident),+) => {
// synchronous implementation
impl <
BASE: Send + Sync + 'static,
OUT: Serialize,
$($x: Deserialize,)+
TRAILING: Default + Deserialize,
> Wrap<BASE> for fn(&BASE, $($x,)+ Trailing<TRAILING>) -> Result<OUT, Error> {
fn wrap_rpc(&self, base: &BASE, params: Params) -> Result<Value, Error> {
let len = match params {
Params::Array(ref v) => v.len(),
Params::None => 0,
_ => return Err(errors::invalid_params("not an array", "")),
};
let params = match len - $num {
0 => from_params::<($($x,)+)>(params)
.map(|($($x,)+)| ($($x,)+ TRAILING::default())),
1 => from_params::<($($x,)+ TRAILING)>(params)
.map(|($($x,)+ id)| ($($x,)+ id)),
_ => Err(Error::invalid_params()),
};
let ($($x,)+ id) = try!(params);
(self)(base, $($x,)+ Trailing(id)).map(to_value)
}
}
// asynchronous implementation
impl <
BASE: Send + Sync + 'static,
OUT: Serialize,
$($x: Deserialize,)+
TRAILING: Default + Deserialize,
> WrapAsync<BASE> for fn(&BASE, Ready<OUT>, $($x,)+ Trailing<TRAILING>) {
fn wrap_rpc(&self, base: &BASE, params: Params, ready: ::jsonrpc_core::Ready) {
let len = match params {
Params::Array(ref v) => v.len(),
Params::None => 0,
_ => return ready.ready(Err(errors::invalid_params("not an array", ""))),
};
let params = match len - $num {
0 => from_params::<($($x,)+)>(params)
.map(|($($x,)+)| ($($x,)+ TRAILING::default())),
1 => from_params::<($($x,)+ TRAILING)>(params)
.map(|($($x,)+ id)| ($($x,)+ id)),
_ => Err(Error::invalid_params()),
};
match params {
Ok(($($x,)+ id)) => (self)(base, ready.into(), $($x,)+ Trailing(id)),
Err(e) => ready.ready(Err(e))
}
}
}
}
}
wrap!(A, B, C, D, E);
wrap!(A, B, C, D);
wrap!(A, B, C);
wrap!(A, B);
wrap!(A);
wrap_with_trailing!(5, A, B, C, D, E);
wrap_with_trailing!(4, A, B, C, D);
wrap_with_trailing!(3, A, B, C);
wrap_with_trailing!(2, A, B);
wrap_with_trailing!(1, A);

View File

@ -14,14 +14,10 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
#[macro_use]
pub mod auto_args;
#[macro_use] #[macro_use]
pub mod errors; pub mod errors;
pub mod dispatch; pub mod dispatch;
pub mod params;
pub mod block_import; pub mod block_import;
mod poll_manager; mod poll_manager;

View File

@ -1,46 +0,0 @@
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Parameters parsing helpers
use serde;
use jsonrpc_core::{Error, Params, from_params};
use v1::types::BlockNumber;
use v1::helpers::errors;
pub fn expect_no_params(params: Params) -> Result<(), Error> {
match params {
Params::None => Ok(()),
p => Err(errors::invalid_params("No parameters were expected", p)),
}
}
/// Returns number of different parameters in given `Params` object.
pub fn params_len(params: &Params) -> usize {
match params {
&Params::Array(ref vec) => vec.len(),
_ => 0,
}
}
/// Deserialize request parameters with optional third parameter `BlockNumber` defaulting to `BlockNumber::Latest`.
pub fn from_params_default_third<F1, F2>(params: Params) -> Result<(F1, F2, BlockNumber, ), Error> where F1: serde::de::Deserialize, F2: serde::de::Deserialize {
match params_len(&params) {
2 => from_params::<(F1, F2, )>(params).map(|(f1, f2)| (f1, f2, BlockNumber::Latest)),
_ => from_params::<(F1, F2, BlockNumber)>(params)
}
}

View File

@ -27,6 +27,7 @@ use time::get_time;
use ethsync::{SyncProvider}; use ethsync::{SyncProvider};
use ethcore::miner::{MinerService, ExternalMinerService}; use ethcore::miner::{MinerService, ExternalMinerService};
use jsonrpc_core::*; use jsonrpc_core::*;
use jsonrpc_macros::Trailing;
use util::{H256, Address, FixedHash, U256, H64, Uint}; use util::{H256, Address, FixedHash, U256, H64, Uint};
use util::sha3::*; use util::sha3::*;
use util::{FromHex, Mutex}; use util::{FromHex, Mutex};
@ -51,7 +52,6 @@ use v1::types::{
use v1::helpers::{CallRequest as CRequest, errors, limit_logs}; use v1::helpers::{CallRequest as CRequest, errors, limit_logs};
use v1::helpers::dispatch::{dispatch_transaction, default_gas_price}; use v1::helpers::dispatch::{dispatch_transaction, default_gas_price};
use v1::helpers::block_import::is_major_importing; use v1::helpers::block_import::is_major_importing;
use v1::helpers::auto_args::Trailing;
const EXTRA_INFO_PROOF: &'static str = "Object exists in in blockchain (fetched earlier), extra_info is always available if object exists; qed"; const EXTRA_INFO_PROOF: &'static str = "Object exists in in blockchain (fetched earlier), extra_info is always available if object exists; qed";

View File

@ -32,6 +32,7 @@ use ethcore::mode::Mode;
use ethcore::account_provider::AccountProvider; use ethcore::account_provider::AccountProvider;
use jsonrpc_core::Error; use jsonrpc_core::Error;
use jsonrpc_macros::Trailing;
use v1::traits::Parity; use v1::traits::Parity;
use v1::types::{ use v1::types::{
Bytes, U256, H160, H256, H512, Bytes, U256, H160, H256, H512,
@ -41,7 +42,6 @@ use v1::types::{
}; };
use v1::helpers::{errors, SigningQueue, SignerService, NetworkSettings}; use v1::helpers::{errors, SigningQueue, SignerService, NetworkSettings};
use v1::helpers::dispatch::DEFAULT_MAC; use v1::helpers::dispatch::DEFAULT_MAC;
use v1::helpers::auto_args::Trailing;
/// Parity implementation. /// Parity implementation.
pub struct ParityClient<C, M, S: ?Sized> where pub struct ParityClient<C, M, S: ?Sized> where

View File

@ -26,7 +26,7 @@ use fetch::{Client as FetchClient, Fetch};
use util::{Mutex, sha3}; use util::{Mutex, sha3};
use jsonrpc_core::Error; use jsonrpc_core::Error;
use v1::helpers::auto_args::Ready; use jsonrpc_macros::Ready;
use v1::helpers::errors; use v1::helpers::errors;
use v1::traits::ParitySet; use v1::traits::ParitySet;
use v1::types::{Bytes, H160, H256, U256}; use v1::types::{Bytes, H160, H256, U256};

View File

@ -25,7 +25,7 @@ use ethcore::miner::MinerService;
use ethcore::client::MiningBlockChainClient; use ethcore::client::MiningBlockChainClient;
use jsonrpc_core::Error; use jsonrpc_core::Error;
use v1::helpers::auto_args::Ready; use jsonrpc_macros::Ready;
use v1::helpers::{ use v1::helpers::{
errors, dispatch, errors, dispatch,
SigningQueue, ConfirmationPromise, ConfirmationResult, ConfirmationPayload, SignerService SigningQueue, ConfirmationPromise, ConfirmationResult, ConfirmationPayload, SignerService

View File

@ -24,7 +24,7 @@ use ethcore::miner::MinerService;
use ethcore::client::MiningBlockChainClient; use ethcore::client::MiningBlockChainClient;
use jsonrpc_core::Error; use jsonrpc_core::Error;
use v1::helpers::auto_args::Ready; use jsonrpc_macros::Ready;
use v1::helpers::errors; use v1::helpers::errors;
use v1::helpers::dispatch; use v1::helpers::dispatch;
use v1::traits::{EthSigning, ParitySigning}; use v1::traits::{EthSigning, ParitySigning};

View File

@ -18,13 +18,15 @@
use std::sync::{Weak, Arc}; use std::sync::{Weak, Arc};
use jsonrpc_core::*; use jsonrpc_core::*;
use serde;
use rlp::{UntrustedRlp, View}; use rlp::{UntrustedRlp, View};
use ethcore::client::{BlockChainClient, CallAnalytics, TransactionId, TraceId}; use ethcore::client::{BlockChainClient, CallAnalytics, TransactionId, TraceId};
use ethcore::miner::MinerService; use ethcore::miner::MinerService;
use ethcore::transaction::{Transaction as EthTransaction, SignedTransaction, Action}; use ethcore::transaction::{Transaction as EthTransaction, SignedTransaction, Action};
use v1::traits::Traces; use v1::traits::Traces;
use v1::helpers::{errors, CallRequest as CRequest}; use v1::helpers::{errors, CallRequest as CRequest};
use v1::helpers::params::from_params_default_third;
use v1::types::{TraceFilter, LocalizedTrace, BlockNumber, Index, CallRequest, Bytes, TraceResults, H256}; use v1::types::{TraceFilter, LocalizedTrace, BlockNumber, Index, CallRequest, Bytes, TraceResults, H256};
fn to_call_analytics(flags: Vec<String>) -> CallAnalytics { fn to_call_analytics(flags: Vec<String>) -> CallAnalytics {
@ -35,6 +37,22 @@ fn to_call_analytics(flags: Vec<String>) -> CallAnalytics {
} }
} }
/// Returns number of different parameters in given `Params` object.
fn params_len(params: &Params) -> usize {
match params {
&Params::Array(ref vec) => vec.len(),
_ => 0,
}
}
/// Deserialize request parameters with optional third parameter `BlockNumber` defaulting to `BlockNumber::Latest`.
fn from_params_default_third<F1, F2>(params: Params) -> Result<(F1, F2, BlockNumber, ), Error> where F1: serde::de::Deserialize, F2: serde::de::Deserialize {
match params_len(&params) {
2 => from_params::<(F1, F2, )>(params).map(|(f1, f2)| (f1, f2, BlockNumber::Latest)),
_ => from_params::<(F1, F2, BlockNumber)>(params)
}
}
/// Traces api implementation. /// Traces api implementation.
pub struct TracesClient<C, M> where C: BlockChainClient, M: MinerService { pub struct TracesClient<C, M> where C: BlockChainClient, M: MinerService {
client: Weak<C>, client: Weak<C>,

View File

@ -21,7 +21,7 @@ use v1::types::{RichBlock, BlockNumber, Bytes, CallRequest, Filter, FilterChange
use v1::types::{Log, Receipt, SyncStatus, Transaction, Work}; use v1::types::{Log, Receipt, SyncStatus, Transaction, Work};
use v1::types::{H64, H160, H256, U256}; use v1::types::{H64, H160, H256, U256};
use v1::helpers::auto_args::{Trailing, Wrap}; use jsonrpc_macros::Trailing;
build_rpc_trait! { build_rpc_trait! {
/// Eth rpc interface. /// Eth rpc interface.

View File

@ -16,7 +16,8 @@
//! Eth rpc interface. //! Eth rpc interface.
use v1::helpers::auto_args::{WrapAsync, Ready}; use jsonrpc_macros::Ready;
use v1::types::{Bytes, H160, H256, H520, TransactionRequest, RichRawTransaction}; use v1::types::{Bytes, H160, H256, H520, TransactionRequest, RichRawTransaction};
build_rpc_trait! { build_rpc_trait! {

View File

@ -17,8 +17,6 @@
//! Net rpc interface. //! Net rpc interface.
use jsonrpc_core::Error; use jsonrpc_core::Error;
use v1::helpers::auto_args::Wrap;
build_rpc_trait! { build_rpc_trait! {
/// Net rpc interface. /// Net rpc interface.
pub trait Net { pub trait Net {

View File

@ -15,10 +15,12 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Parity-specific rpc interface. //! Parity-specific rpc interface.
use jsonrpc_core::Error;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use v1::helpers::auto_args::{Wrap, Trailing};
use jsonrpc_core::Error;
use jsonrpc_macros::Trailing;
use v1::types::{ use v1::types::{
H160, H256, H512, U256, Bytes, H160, H256, H512, U256, Bytes,
Peers, Transaction, RpcSettings, Histogram, Peers, Transaction, RpcSettings, Histogram,

View File

@ -16,9 +16,8 @@
//! Parity Accounts-related rpc interface. //! Parity Accounts-related rpc interface.
use std::collections::BTreeMap; use std::collections::BTreeMap;
use jsonrpc_core::{Value, Error};
use v1::helpers::auto_args::Wrap; use jsonrpc_core::{Value, Error};
use v1::types::{H160, H256, DappId}; use v1::types::{H160, H256, DappId};
build_rpc_trait! { build_rpc_trait! {

View File

@ -17,8 +17,8 @@
//! Parity-specific rpc interface for operations altering the settings. //! Parity-specific rpc interface for operations altering the settings.
use jsonrpc_core::Error; use jsonrpc_core::Error;
use jsonrpc_macros::Ready;
use v1::helpers::auto_args::{Wrap, WrapAsync, Ready};
use v1::types::{Bytes, H160, H256, U256}; use v1::types::{Bytes, H160, H256, U256};
build_rpc_trait! { build_rpc_trait! {

View File

@ -16,8 +16,8 @@
//! ParitySigning rpc interface. //! ParitySigning rpc interface.
use jsonrpc_core::Error; use jsonrpc_core::Error;
use jsonrpc_macros::Ready;
use v1::helpers::auto_args::{Wrap, WrapAsync, Ready};
use v1::types::{U256, H160, H256, Bytes, ConfirmationResponse, TransactionRequest, Either}; use v1::types::{U256, H160, H256, Bytes, ConfirmationResponse, TransactionRequest, Either};
build_rpc_trait! { build_rpc_trait! {

View File

@ -17,7 +17,6 @@
//! Personal rpc interface. //! Personal rpc interface.
use jsonrpc_core::Error; use jsonrpc_core::Error;
use v1::helpers::auto_args::Wrap;
use v1::types::{U128, H160, H256, TransactionRequest}; use v1::types::{U128, H160, H256, TransactionRequest};
build_rpc_trait! { build_rpc_trait! {

View File

@ -16,12 +16,10 @@
//! RPC interface. //! RPC interface.
use jsonrpc_core::Error;
use v1::helpers::auto_args::Wrap;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use jsonrpc_core::Error;
build_rpc_trait! { build_rpc_trait! {
/// RPC Interface. /// RPC Interface.
pub trait Rpc { pub trait Rpc {

View File

@ -17,10 +17,8 @@
//! Parity Signer-related rpc interface. //! Parity Signer-related rpc interface.
use jsonrpc_core::Error; use jsonrpc_core::Error;
use v1::helpers::auto_args::Wrap;
use v1::types::{U256, Bytes, TransactionModification, ConfirmationRequest, ConfirmationResponse}; use v1::types::{U256, Bytes, TransactionModification, ConfirmationRequest, ConfirmationResponse};
build_rpc_trait! { build_rpc_trait! {
/// Signer extension for confirmations rpc interface. /// Signer extension for confirmations rpc interface.
pub trait Signer { pub trait Signer {

View File

@ -15,8 +15,9 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Traces specific rpc interface. //! Traces specific rpc interface.
use std::sync::Arc; use std::sync::Arc;
use jsonrpc_core::*; use jsonrpc_core::{Params, Value, Error, IoDelegate};
/// Traces specific rpc interface. /// Traces specific rpc interface.
pub trait Traces: Sized + Send + Sync + 'static { pub trait Traces: Sized + Send + Sync + 'static {

View File

@ -17,10 +17,8 @@
//! Web3 rpc interface. //! Web3 rpc interface.
use jsonrpc_core::Error; use jsonrpc_core::Error;
use v1::helpers::auto_args::Wrap;
use v1::types::{H256, Bytes}; use v1::types::{H256, Bytes};
build_rpc_trait! { build_rpc_trait! {
/// Web3 rpc interface. /// Web3 rpc interface.
pub trait Web3 { pub trait Web3 {