diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d193039b5..a11cbc2a6 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,8 +1,8 @@ stages: - test - js-build - - push-release - build + - push-release variables: GIT_DEPTH: "3" SIMPLECOV: "true" @@ -499,8 +499,7 @@ docker-build: before_script: - docker info script: - - cd docker/hub - - if [ "$CI_BUILD_REF_NAME" == "nightly" ]; then DOCKER_TAG="latest"; else DOCKER_TAG=$CI_BUILD_REF_NAME; fi + - if [ "$CI_BUILD_REF_NAME" == "beta-release" ]; then DOCKER_TAG="latest"; else DOCKER_TAG=$CI_BUILD_REF_NAME; fi - docker login -u $Docker_Hub_User -p $Docker_Hub_Pass - sh scripts/docker-build.sh $DOCKER_TAG tags: diff --git a/Cargo.lock b/Cargo.lock index 46b1ed27c..6924cfe00 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9,22 +9,22 @@ dependencies = [ "daemonize 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "docopt 0.6.80 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "ethcore 1.6.0", - "ethcore-dapps 1.6.0", - "ethcore-devtools 1.6.0", - "ethcore-io 1.6.0", - "ethcore-ipc 1.6.0", + "ethcore 1.7.0", + "ethcore-dapps 1.7.0", + "ethcore-devtools 1.7.0", + "ethcore-io 1.7.0", + "ethcore-ipc 1.7.0", "ethcore-ipc-hypervisor 1.2.0", - "ethcore-ipc-nano 1.6.0", + "ethcore-ipc-nano 1.7.0", "ethcore-ipc-tests 0.1.0", - "ethcore-light 1.6.0", - "ethcore-logger 1.6.0", - "ethcore-rpc 1.6.0", + "ethcore-light 1.7.0", + "ethcore-logger 1.7.0", + "ethcore-rpc 1.7.0", "ethcore-secretstore 1.0.0", - "ethcore-signer 1.6.0", - "ethcore-stratum 1.6.0", - "ethcore-util 1.6.0", - "ethsync 1.6.0", + "ethcore-signer 1.7.0", + "ethcore-stratum 1.7.0", + "ethcore-util 1.7.0", + "ethsync 1.7.0", "evmbin 0.1.0", "fdlimit 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)", @@ -33,12 +33,12 @@ dependencies = [ "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "num_cpus 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "number_prefix 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", - "parity-hash-fetch 1.6.0", - "parity-ipfs-api 1.6.0", + "parity-hash-fetch 1.7.0", + "parity-ipfs-api 1.7.0", "parity-local-store 0.1.0", "parity-reactor 0.1.0", "parity-rpc-client 1.4.0", - "parity-updater 1.6.0", + "parity-updater 1.7.0", "regex 0.1.68 (registry+https://github.com/rust-lang/crates.io-index)", "rlp 0.1.0", "rpassword 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -344,7 +344,7 @@ dependencies = [ [[package]] name = "eth-secp256k1" version = "0.5.6" -source = "git+https://github.com/ethcore/rust-secp256k1#edab95f5569e4fb97579dc8947be96e7ac789c16" +source = "git+https://github.com/ethcore/rust-secp256k1#98ad9b9ecae44a563efdd64273bcebc6b4ed81c6" dependencies = [ "arrayvec 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", "gcc 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)", @@ -367,7 +367,7 @@ dependencies = [ [[package]] name = "ethash" -version = "1.6.0" +version = "1.7.0" dependencies = [ "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -377,7 +377,7 @@ dependencies = [ [[package]] name = "ethcore" -version = "1.6.0" +version = "1.7.0" dependencies = [ "bit-set 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "bloomchain 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -386,20 +386,20 @@ dependencies = [ "crossbeam 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "ethabi 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "ethash 1.6.0", + "ethash 1.7.0", "ethcore-bloom-journal 0.1.0", - "ethcore-devtools 1.6.0", - "ethcore-io 1.6.0", - "ethcore-ipc 1.6.0", - "ethcore-ipc-codegen 1.6.0", - "ethcore-ipc-nano 1.6.0", - "ethcore-stratum 1.6.0", - "ethcore-util 1.6.0", + "ethcore-devtools 1.7.0", + "ethcore-io 1.7.0", + "ethcore-ipc 1.7.0", + "ethcore-ipc-codegen 1.7.0", + "ethcore-ipc-nano 1.7.0", + "ethcore-stratum 1.7.0", + "ethcore-util 1.7.0", "ethjson 0.1.0", "ethkey 0.2.0", "ethstore 0.1.0", - "evmjit 1.6.0", - "hardware-wallet 1.6.0", + "evmjit 1.7.0", + "hardware-wallet 1.7.0", "hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)", "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "linked-hash-map 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -436,14 +436,14 @@ dependencies = [ [[package]] name = "ethcore-dapps" -version = "1.6.0" +version = "1.7.0" dependencies = [ "base32 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "ethcore-devtools 1.6.0", - "ethcore-rpc 1.6.0", - "ethcore-util 1.6.0", + "ethcore-devtools 1.7.0", + "ethcore-rpc 1.7.0", + "ethcore-util 1.7.0", "fetch 0.1.0", "futures 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)", @@ -454,9 +454,9 @@ dependencies = [ "mime 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "mime_guess 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "parity-hash-fetch 1.6.0", + "parity-hash-fetch 1.7.0", "parity-reactor 0.1.0", - "parity-ui 1.6.0", + "parity-ui 1.7.0", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "serde 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -470,14 +470,14 @@ dependencies = [ [[package]] name = "ethcore-devtools" -version = "1.6.0" +version = "1.7.0" dependencies = [ "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "ethcore-io" -version = "1.6.0" +version = "1.7.0" dependencies = [ "crossbeam 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -488,17 +488,17 @@ dependencies = [ [[package]] name = "ethcore-ipc" -version = "1.6.0" +version = "1.7.0" dependencies = [ - "ethcore-devtools 1.6.0", - "ethcore-util 1.6.0", + "ethcore-devtools 1.7.0", + "ethcore-util 1.7.0", "nanomsg 0.5.1 (git+https://github.com/ethcore/nanomsg.rs.git)", "semver 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "ethcore-ipc-codegen" -version = "1.6.0" +version = "1.7.0" dependencies = [ "aster 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)", "quasi 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -511,9 +511,9 @@ dependencies = [ name = "ethcore-ipc-hypervisor" version = "1.2.0" dependencies = [ - "ethcore-ipc 1.6.0", - "ethcore-ipc-codegen 1.6.0", - "ethcore-ipc-nano 1.6.0", + "ethcore-ipc 1.7.0", + "ethcore-ipc-codegen 1.7.0", + "ethcore-ipc-nano 1.7.0", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "nanomsg 0.5.1 (git+https://github.com/ethcore/nanomsg.rs.git)", "semver 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -522,9 +522,9 @@ dependencies = [ [[package]] name = "ethcore-ipc-nano" -version = "1.6.0" +version = "1.7.0" dependencies = [ - "ethcore-ipc 1.6.0", + "ethcore-ipc 1.7.0", "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "nanomsg 0.5.1 (git+https://github.com/ethcore/nanomsg.rs.git)", @@ -534,11 +534,11 @@ dependencies = [ name = "ethcore-ipc-tests" version = "0.1.0" dependencies = [ - "ethcore-devtools 1.6.0", - "ethcore-ipc 1.6.0", - "ethcore-ipc-codegen 1.6.0", - "ethcore-ipc-nano 1.6.0", - "ethcore-util 1.6.0", + "ethcore-devtools 1.7.0", + "ethcore-ipc 1.7.0", + "ethcore-ipc-codegen 1.7.0", + "ethcore-ipc-nano 1.7.0", + "ethcore-util 1.7.0", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "nanomsg 0.5.1 (git+https://github.com/ethcore/nanomsg.rs.git)", "semver 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -546,14 +546,14 @@ dependencies = [ [[package]] name = "ethcore-light" -version = "1.6.0" +version = "1.7.0" dependencies = [ - "ethcore 1.6.0", - "ethcore-io 1.6.0", - "ethcore-ipc 1.6.0", - "ethcore-ipc-codegen 1.6.0", - "ethcore-network 1.6.0", - "ethcore-util 1.6.0", + "ethcore 1.7.0", + "ethcore-io 1.7.0", + "ethcore-ipc 1.7.0", + "ethcore-ipc-codegen 1.7.0", + "ethcore-network 1.7.0", + "ethcore-util 1.7.0", "futures 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "itertools 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -566,10 +566,10 @@ dependencies = [ [[package]] name = "ethcore-logger" -version = "1.6.0" +version = "1.7.0" dependencies = [ "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "ethcore-util 1.6.0", + "ethcore-util 1.7.0", "isatty 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -579,13 +579,13 @@ dependencies = [ [[package]] name = "ethcore-network" -version = "1.6.0" +version = "1.7.0" dependencies = [ "ansi_term 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "ethcore-devtools 1.6.0", - "ethcore-io 1.6.0", - "ethcore-util 1.6.0", + "ethcore-devtools 1.7.0", + "ethcore-io 1.7.0", + "ethcore-util 1.7.0", "ethcrypto 0.1.0", "ethkey 0.2.0", "igd 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -604,21 +604,21 @@ dependencies = [ [[package]] name = "ethcore-rpc" -version = "1.6.0" +version = "1.7.0" dependencies = [ "clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)", - "ethash 1.6.0", - "ethcore 1.6.0", - "ethcore-devtools 1.6.0", - "ethcore-io 1.6.0", - "ethcore-ipc 1.6.0", - "ethcore-light 1.6.0", - "ethcore-util 1.6.0", + "ethash 1.7.0", + "ethcore 1.7.0", + "ethcore-devtools 1.7.0", + "ethcore-io 1.7.0", + "ethcore-ipc 1.7.0", + "ethcore-light 1.7.0", + "ethcore-util 1.7.0", "ethcrypto 0.1.0", "ethjson 0.1.0", "ethkey 0.2.0", "ethstore 0.1.0", - "ethsync 1.6.0", + "ethsync 1.7.0", "fetch 0.1.0", "futures 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "jsonrpc-core 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)", @@ -628,7 +628,7 @@ dependencies = [ "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "order-stat 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "parity-reactor 0.1.0", - "parity-updater 1.6.0", + "parity-updater 1.7.0", "rlp 0.1.0", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "semver 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -644,11 +644,11 @@ dependencies = [ name = "ethcore-secretstore" version = "1.0.0" dependencies = [ - "ethcore-devtools 1.6.0", - "ethcore-ipc 1.6.0", - "ethcore-ipc-codegen 1.6.0", - "ethcore-ipc-nano 1.6.0", - "ethcore-util 1.6.0", + "ethcore-devtools 1.7.0", + "ethcore-ipc 1.7.0", + "ethcore-ipc-codegen 1.7.0", + "ethcore-ipc-nano 1.7.0", + "ethcore-util 1.7.0", "ethcrypto 0.1.0", "ethkey 0.2.0", "hyper 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)", @@ -659,18 +659,18 @@ dependencies = [ [[package]] name = "ethcore-signer" -version = "1.6.0" +version = "1.7.0" dependencies = [ "clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "ethcore-devtools 1.6.0", - "ethcore-io 1.6.0", - "ethcore-rpc 1.6.0", - "ethcore-util 1.6.0", + "ethcore-devtools 1.7.0", + "ethcore-io 1.7.0", + "ethcore-rpc 1.7.0", + "ethcore-util 1.7.0", "jsonrpc-core 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "parity-ui 1.6.0", + "parity-ui 1.7.0", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "ws 0.5.3 (git+https://github.com/ethcore/ws-rs.git?branch=mio-upstream-stable)", @@ -678,14 +678,14 @@ dependencies = [ [[package]] name = "ethcore-stratum" -version = "1.6.0" +version = "1.7.0" dependencies = [ "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "ethcore-devtools 1.6.0", - "ethcore-ipc 1.6.0", - "ethcore-ipc-codegen 1.6.0", - "ethcore-ipc-nano 1.6.0", - "ethcore-util 1.6.0", + "ethcore-devtools 1.7.0", + "ethcore-ipc 1.7.0", + "ethcore-ipc-codegen 1.7.0", + "ethcore-ipc-nano 1.7.0", + "ethcore-util 1.7.0", "futures 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "jsonrpc-core 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)", "jsonrpc-macros 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)", @@ -699,7 +699,7 @@ dependencies = [ [[package]] name = "ethcore-util" -version = "1.6.0" +version = "1.7.0" dependencies = [ "ansi_term 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "arrayvec 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", @@ -709,7 +709,7 @@ dependencies = [ "eth-secp256k1 0.5.6 (git+https://github.com/ethcore/rust-secp256k1)", "ethcore-bigint 0.1.2", "ethcore-bloom-journal 0.1.0", - "ethcore-devtools 1.6.0", + "ethcore-devtools 1.7.0", "heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "itertools 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -748,7 +748,7 @@ dependencies = [ name = "ethjson" version = "0.1.0" dependencies = [ - "ethcore-util 1.6.0", + "ethcore-util 1.7.0", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "serde 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -775,8 +775,8 @@ name = "ethstore" version = "0.1.0" dependencies = [ "docopt 0.6.80 (registry+https://github.com/rust-lang/crates.io-index)", - "ethcore-devtools 1.6.0", - "ethcore-util 1.6.0", + "ethcore-devtools 1.7.0", + "ethcore-util 1.7.0", "ethcrypto 0.1.0", "ethkey 0.2.0", "itertools 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)", @@ -797,19 +797,19 @@ dependencies = [ [[package]] name = "ethsync" -version = "1.6.0" +version = "1.7.0" dependencies = [ "clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "ethcore 1.6.0", - "ethcore-devtools 1.6.0", - "ethcore-io 1.6.0", - "ethcore-ipc 1.6.0", - "ethcore-ipc-codegen 1.6.0", - "ethcore-ipc-nano 1.6.0", - "ethcore-light 1.6.0", - "ethcore-network 1.6.0", - "ethcore-util 1.6.0", + "ethcore 1.7.0", + "ethcore-devtools 1.7.0", + "ethcore-io 1.7.0", + "ethcore-ipc 1.7.0", + "ethcore-ipc-codegen 1.7.0", + "ethcore-ipc-nano 1.7.0", + "ethcore-light 1.7.0", + "ethcore-network 1.7.0", + "ethcore-util 1.7.0", "ethkey 0.2.0", "heapsize 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)", @@ -825,14 +825,14 @@ name = "evmbin" version = "0.1.0" dependencies = [ "docopt 0.6.80 (registry+https://github.com/rust-lang/crates.io-index)", - "ethcore 1.6.0", - "ethcore-util 1.6.0", + "ethcore 1.7.0", + "ethcore-util 1.7.0", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "evmjit" -version = "1.6.0" +version = "1.7.0" dependencies = [ "tiny-keccak 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -913,7 +913,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "hardware-wallet" -version = "1.6.0" +version = "1.7.0" dependencies = [ "ethcore-bigint 0.1.2", "ethkey 0.2.0", @@ -976,7 +976,7 @@ dependencies = [ [[package]] name = "hyper" version = "0.10.0-a.0" -source = "git+https://github.com/ethcore/hyper#2e6702984f4f9e99fe251537a755aff0badc0b3a" +source = "git+https://github.com/ethcore/hyper#453c683b52208fefc32d29e4ac7c863439b2321f" dependencies = [ "cookie 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "httparse 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1050,11 +1050,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "ipc-common-types" -version = "1.6.0" +version = "1.7.0" dependencies = [ - "ethcore-ipc 1.6.0", - "ethcore-ipc-codegen 1.6.0", - "ethcore-util 1.6.0", + "ethcore-ipc 1.7.0", + "ethcore-ipc-codegen 1.7.0", + "ethcore-util 1.7.0", "semver 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1618,10 +1618,10 @@ dependencies = [ [[package]] name = "parity-hash-fetch" -version = "1.6.0" +version = "1.7.0" dependencies = [ "ethabi 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "ethcore-util 1.6.0", + "ethcore-util 1.7.0", "fetch 0.1.0", "futures 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1634,11 +1634,11 @@ dependencies = [ [[package]] name = "parity-ipfs-api" -version = "1.6.0" +version = "1.7.0" dependencies = [ "cid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "ethcore 1.6.0", - "ethcore-util 1.6.0", + "ethcore 1.7.0", + "ethcore-util 1.7.0", "hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)", "jsonrpc-http-server 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)", "mime 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1650,9 +1650,9 @@ dependencies = [ name = "parity-local-store" version = "0.1.0" dependencies = [ - "ethcore 1.6.0", - "ethcore-io 1.6.0", - "ethcore-util 1.6.0", + "ethcore 1.7.0", + "ethcore-io 1.7.0", + "ethcore-util 1.7.0", "ethkey 0.2.0", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "rlp 0.1.0", @@ -1673,9 +1673,9 @@ dependencies = [ name = "parity-rpc-client" version = "1.4.0" dependencies = [ - "ethcore-rpc 1.6.0", - "ethcore-signer 1.6.0", - "ethcore-util 1.6.0", + "ethcore-rpc 1.7.0", + "ethcore-signer 1.7.0", + "ethcore-util 1.7.0", "futures 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "jsonrpc-core 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1689,7 +1689,7 @@ dependencies = [ [[package]] name = "parity-ui" -version = "1.6.0" +version = "1.7.0" dependencies = [ "parity-ui-dev 1.4.0", "parity-ui-precompiled 1.4.0 (git+https://github.com/ethcore/js-precompiled.git)", @@ -1706,24 +1706,24 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#aaee793907e4ff61082d83ff44733363dfff6eae" +source = "git+https://github.com/ethcore/js-precompiled.git#9eef2b78d363560fe942062caaaa7f6b1d64dd17" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "parity-updater" -version = "1.6.0" +version = "1.7.0" dependencies = [ "ethabi 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "ethcore 1.6.0", - "ethcore-ipc 1.6.0", - "ethcore-ipc-codegen 1.6.0", - "ethcore-util 1.6.0", - "ethsync 1.6.0", - "ipc-common-types 1.6.0", + "ethcore 1.7.0", + "ethcore-ipc 1.7.0", + "ethcore-ipc-codegen 1.7.0", + "ethcore-util 1.7.0", + "ethsync 1.7.0", + "ipc-common-types 1.7.0", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", - "parity-hash-fetch 1.6.0", + "parity-hash-fetch 1.7.0", "parity-reactor 0.1.0", "target_info 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1992,8 +1992,8 @@ name = "rpc-cli" version = "1.4.0" dependencies = [ "ethcore-bigint 0.1.2", - "ethcore-rpc 1.6.0", - "ethcore-util 1.6.0", + "ethcore-rpc 1.7.0", + "ethcore-util 1.7.0", "futures 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "parity-rpc-client 1.4.0", "rpassword 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/README.md b/README.md index 260566c5e..2511e7a4f 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,11 @@ $ cargo build --release ``` This will produce an executable in the `./target/release` subdirectory. +Note: if cargo fails to parse manifest try: +```bash +$ ~/.cargo/bin/cargo build --release +``` ---- ## Simple one-line installer for Mac and Ubuntu diff --git a/dapps/Cargo.toml b/dapps/Cargo.toml index 2c99dde4f..508fbc1a0 100644 --- a/dapps/Cargo.toml +++ b/dapps/Cargo.toml @@ -1,7 +1,7 @@ [package] description = "Parity Dapps crate" name = "ethcore-dapps" -version = "1.6.0" +version = "1.7.0" license = "GPL-3.0" authors = ["Parity Technologies "] diff --git a/dapps/js-glue/Cargo.toml b/dapps/js-glue/Cargo.toml index 1074330be..b53b158c7 100644 --- a/dapps/js-glue/Cargo.toml +++ b/dapps/js-glue/Cargo.toml @@ -1,7 +1,7 @@ [package] description = "Base Package for all Parity built-in dapps" name = "parity-dapps-glue" -version = "1.6.0" +version = "1.7.0" license = "GPL-3.0" authors = ["Parity Technologies "] build = "build.rs" diff --git a/dapps/src/api/api.rs b/dapps/src/api/api.rs index b521c0ba1..9106e0d70 100644 --- a/dapps/src/api/api.rs +++ b/dapps/src/api/api.rs @@ -39,7 +39,11 @@ pub struct RestApi { impl RestApi { pub fn new(cors_domains: Vec, endpoints: Arc, fetcher: Arc) -> Box { Box::new(RestApi { - cors_domains: Some(cors_domains.into_iter().map(AccessControlAllowOrigin::Value).collect()), + cors_domains: Some(cors_domains.into_iter().map(|domain| match domain.as_ref() { + "all" | "*" | "any" => AccessControlAllowOrigin::Any, + "null" => AccessControlAllowOrigin::Null, + other => AccessControlAllowOrigin::Value(other.into()), + }).collect()), endpoints: endpoints, fetcher: fetcher, }) diff --git a/dapps/src/lib.rs b/dapps/src/lib.rs index 3f26e82a9..30c62a031 100644 --- a/dapps/src/lib.rs +++ b/dapps/src/lib.rs @@ -111,6 +111,7 @@ pub struct ServerBuilder { web_proxy_tokens: Arc, signer_address: Option<(String, u16)>, allowed_hosts: Option>, + extra_cors: Option>, remote: Remote, fetch: Option, } @@ -126,6 +127,7 @@ impl ServerBuilder { web_proxy_tokens: Arc::new(|_| false), signer_address: None, allowed_hosts: Some(vec![]), + extra_cors: None, remote: remote, fetch: None, } @@ -143,6 +145,7 @@ impl ServerBuilder { web_proxy_tokens: self.web_proxy_tokens, signer_address: self.signer_address, allowed_hosts: self.allowed_hosts, + extra_cors: self.extra_cors, remote: self.remote, fetch: Some(fetch), } @@ -174,6 +177,13 @@ impl ServerBuilder { self } + /// Extra cors headers. + /// `None` - no additional CORS URLs + pub fn extra_cors_headers(mut self, cors: Option>) -> Self { + self.extra_cors = cors; + self + } + /// Change extra dapps paths (apart from `dapps_path`) pub fn extra_dapps>(mut self, extra_dapps: &[P]) -> Self { self.extra_dapps = extra_dapps.iter().map(|p| p.as_ref().to_owned()).collect(); @@ -187,6 +197,7 @@ impl ServerBuilder { Server::start_http( addr, self.allowed_hosts, + self.extra_cors, NoAuth, handler, self.dapps_path, @@ -207,6 +218,7 @@ impl ServerBuilder { Server::start_http( addr, self.allowed_hosts, + self.extra_cors, HttpBasicAuth::single_user(username, password), handler, self.dapps_path, @@ -251,8 +263,8 @@ impl Server { } /// Returns a list of CORS domains for API endpoint. - fn cors_domains(signer_address: Option<(String, u16)>) -> Vec { - match signer_address { + fn cors_domains(signer_address: Option<(String, u16)>, extra_cors: Option>) -> Vec { + let basic_cors = match signer_address { Some(signer_address) => vec![ format!("http://{}{}", HOME_PAGE, DAPPS_DOMAIN), format!("http://{}{}:{}", HOME_PAGE, DAPPS_DOMAIN, signer_address.1), @@ -260,15 +272,20 @@ impl Server { format!("https://{}{}", HOME_PAGE, DAPPS_DOMAIN), format!("https://{}{}:{}", HOME_PAGE, DAPPS_DOMAIN, signer_address.1), format!("https://{}", address(&signer_address)), - ], None => vec![], + }; + + match extra_cors { + None => basic_cors, + Some(extra_cors) => basic_cors.into_iter().chain(extra_cors).collect(), } } fn start_http>( addr: &SocketAddr, hosts: Option>, + extra_cors: Option>, authorization: A, handler: RpcHandler, dapps_path: PathBuf, @@ -297,7 +314,7 @@ impl Server { remote.clone(), fetch.clone(), )); - let cors_domains = Self::cors_domains(signer_address.clone()); + let cors_domains = Self::cors_domains(signer_address.clone(), extra_cors); let special = Arc::new({ let mut special = HashMap::new(); @@ -413,8 +430,9 @@ mod util_tests { // given // when - let none = Server::cors_domains(None); - let some = Server::cors_domains(Some(("127.0.0.1".into(), 18180))); + let none = Server::cors_domains(None, None); + let some = Server::cors_domains(Some(("127.0.0.1".into(), 18180)), None); + let extra = Server::cors_domains(None, Some(vec!["all".to_owned()])); // then assert_eq!(none, Vec::::new()); @@ -425,7 +443,7 @@ mod util_tests { "https://parity.web3.site".into(), "https://parity.web3.site:18180".into(), "https://127.0.0.1:18180".into() - ]); + assert_eq!(extra, vec!["all".to_owned()]); } } diff --git a/dapps/src/tests/api.rs b/dapps/src/tests/api.rs index 0930aa0ce..1b9f64b7f 100644 --- a/dapps/src/tests/api.rs +++ b/dapps/src/tests/api.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use tests::helpers::{serve, serve_with_registrar, request, assert_security_headers}; +use tests::helpers::{serve, serve_with_registrar, serve_extra_cors, request, assert_security_headers}; #[test] fn should_return_error() { @@ -212,3 +212,25 @@ fn should_return_signer_port_cors_headers_for_home_parity_with_port() { ); } +#[test] +fn should_return_extra_cors_headers() { + // given + let server = serve_extra_cors(Some(vec!["all".to_owned()])); + + // when + let response = request(server, + "\ + POST /api/ping HTTP/1.1\r\n\ + Host: localhost:8080\r\n\ + Origin: http://somedomain.io\r\n\ + Connection: close\r\n\ + \r\n\ + {} + " + ); + + // then + response.assert_status("HTTP/1.1 200 OK"); + response.assert_header("Access-Control-Allow-Origin", "http://somedomain.io"); +} + diff --git a/dapps/src/tests/helpers/mod.rs b/dapps/src/tests/helpers/mod.rs index 9df98c343..d1a1e9900 100644 --- a/dapps/src/tests/helpers/mod.rs +++ b/dapps/src/tests/helpers/mod.rs @@ -109,6 +109,10 @@ pub fn serve_hosts(hosts: Option>) -> ServerLoop { init_server(|builder| builder.allowed_hosts(hosts), Default::default(), Remote::new_sync()).0 } +pub fn serve_extra_cors(extra_cors: Option>) -> ServerLoop { + init_server(|builder| builder.allowed_hosts(None).extra_cors_headers(extra_cors), Default::default(), Remote::new_sync()).0 +} + pub fn serve_with_registrar() -> (ServerLoop, Arc) { init_server(|builder| builder.allowed_hosts(None), Default::default(), Remote::new_sync()) } diff --git a/dapps/ui/Cargo.toml b/dapps/ui/Cargo.toml index e17948204..2ab128ad5 100644 --- a/dapps/ui/Cargo.toml +++ b/dapps/ui/Cargo.toml @@ -3,7 +3,7 @@ description = "Ethcore Parity UI" homepage = "http://parity.io" license = "GPL-3.0" name = "parity-ui" -version = "1.6.0" +version = "1.7.0" authors = ["Parity Technologies "] [build-dependencies] diff --git a/db/Cargo.toml b/db/Cargo.toml index fcceaa17d..a3fe0804c 100644 --- a/db/Cargo.toml +++ b/db/Cargo.toml @@ -3,7 +3,7 @@ description = "Ethcore Database" homepage = "http://parity.io" license = "GPL-3.0" name = "ethcore-db" -version = "1.6.0" +version = "1.7.0" authors = ["Parity Technologies "] build = "build.rs" diff --git a/devtools/Cargo.toml b/devtools/Cargo.toml index 0cf5a6b2e..8759b81c9 100644 --- a/devtools/Cargo.toml +++ b/devtools/Cargo.toml @@ -3,7 +3,7 @@ description = "Ethcore development/test/build tools" homepage = "http://parity.io" license = "GPL-3.0" name = "ethcore-devtools" -version = "1.6.0" +version = "1.7.0" authors = ["Parity Technologies "] [dependencies] diff --git a/docker/hub/Dockerfile b/docker/hub/Dockerfile index 8973ed63f..81ec92133 100644 --- a/docker/hub/Dockerfile +++ b/docker/hub/Dockerfile @@ -1,6 +1,10 @@ FROM ubuntu:14.04 MAINTAINER Parity Technologies WORKDIR /build +#ENV for build TAG +ARG BUILD_TAG +ENV BUILD_TAG ${BUILD_TAG:-master} +RUN echo $BUILD_TAG # install tools and dependencies RUN apt-get update && \ apt-get install -y --force-yes --no-install-recommends \ @@ -47,7 +51,7 @@ RUN apt-get update && \ cd /build&&git clone https://github.com/ethcore/parity && \ cd parity && \ git pull&& \ - git checkout $CI_BUILD_REF_NAME && \ + git checkout $BUILD_TAG && \ cargo build --verbose --release --features final && \ #ls /build/parity/target/release/parity && \ strip /build/parity/target/release/parity && \ diff --git a/ethash/Cargo.toml b/ethash/Cargo.toml index 36909a525..8be24f9ae 100644 --- a/ethash/Cargo.toml +++ b/ethash/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ethash" -version = "1.6.0" +version = "1.7.0" authors = ["Parity Technologies "] [lib] diff --git a/ethcore/Cargo.toml b/ethcore/Cargo.toml index c8a1c7fb5..64010fadf 100644 --- a/ethcore/Cargo.toml +++ b/ethcore/Cargo.toml @@ -3,7 +3,7 @@ description = "Ethcore library" homepage = "http://parity.io" license = "GPL-3.0" name = "ethcore" -version = "1.6.0" +version = "1.7.0" authors = ["Parity Technologies "] build = "build.rs" diff --git a/ethcore/light/Cargo.toml b/ethcore/light/Cargo.toml index cab75a36a..6f95d8a0e 100644 --- a/ethcore/light/Cargo.toml +++ b/ethcore/light/Cargo.toml @@ -3,7 +3,7 @@ description = "Parity Light Client Implementation" homepage = "http://parity.io" license = "GPL-3.0" name = "ethcore-light" -version = "1.6.0" +version = "1.7.0" authors = ["Parity Technologies "] build = "build.rs" diff --git a/ethcore/res/authority_round.json b/ethcore/res/authority_round.json index ac7eb5041..dba7e28a5 100644 --- a/ethcore/res/authority_round.json +++ b/ethcore/res/authority_round.json @@ -33,7 +33,7 @@ "timestamp": "0x00", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "extraData": "0x", - "gasLimit": "0x2fefd8" + "gasLimit": "0x222222" }, "accounts": { "0000000000000000000000000000000000000001": { "balance": "1", "nonce": "1048576", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } }, diff --git a/ethcore/res/ethereum/classic.json b/ethcore/res/ethereum/classic.json index b165fe169..33c954f2c 100644 --- a/ethcore/res/ethereum/classic.json +++ b/ethcore/res/ethereum/classic.json @@ -48,18 +48,12 @@ "stateRoot": "0xd7f8974fb5ac78d9ac099b9ad5018bedc2ce0a72dad1827a1709da30580f0544" }, "nodes": [ - "enode://08c7ee6a4f861ff0664a49532bcc86de1363acd608999d1b76609bb9bc278649906f069057630fd9493924a368b5d1dc9b8f8bf13ac26df72512f6d1fabd8c95@45.32.7.81:30303", "enode://e809c4a2fec7daed400e5e28564e23693b23b2cc5a019b612505631bbe7b9ccf709c1796d2a3d29ef2b045f210caf51e3c4f5b6d3587d43ad5d6397526fa6179@174.112.32.157:30303", - "enode://687be94c3a7beaa3d2fde82fa5046cdeb3e8198354e05b29d6e0d4e276713e3707ac10f784a7904938b06b46c764875c241b0337dd853385a4d8bfcbf8190647@95.183.51.229:30303", "enode://6e538e7c1280f0a31ff08b382db5302480f775480b8e68f8febca0ceff81e4b19153c6f8bf60313b93bef2cc34d34e1df41317de0ce613a201d1660a788a03e2@52.206.67.235:30303", - "enode://ca5ae4eca09ba6787e29cf6d86f7634d07aae6b9e6317a59aff675851c0bf445068173208cf8ef7f5cd783d8e29b85b2fa3fa358124cf0546823149724f9bde1@138.68.1.16:30303", - "enode://217ebe27e89bf4fec8ce06509323ff095b1014378deb75ab2e5f6759a4e8750a3bd8254b8c6833136e4d5e58230d65ee8ab34a5db5abf0640408c4288af3c8a7@188.138.1.237:30303", - "enode://fa20444ef991596ce99b81652ac4e61de1eddc4ff21d3cd42762abd7ed47e7cf044d3c9ccddaf6035d39725e4eb372806787829ccb9a08ec7cb71883cb8c3abd@50.149.116.182:30303", - "enode://4bd6a4df3612c718333eb5ea7f817923a8cdf1bed89cee70d1710b45a0b6b77b2819846440555e451a9b602ad2efa2d2facd4620650249d8468008946887820a@71.178.232.20:30304", - "enode://921cf8e4c345fe8db913c53964f9cadc667644e7f20195a0b7d877bd689a5934e146ff2c2259f1bae6817b6585153a007ceb67d260b720fa3e6fc4350df25c7f@51.255.49.170:30303", - "enode://ffea3b01c000cdd89e1e9229fea3e80e95b646f9b2aa55071fc865e2f19543c9b06045cc2e69453e6b78100a119e66be1b5ad50b36f2ffd27293caa28efdd1b2@128.199.93.177:3030", - "enode://ee3da491ce6a155eb132708eb0e8d04b0637926ec0ae1b79e63fc97cb9fc3818f49250a0ae0d7f79ed62b66ec677f408c4e01741504dc7a051e274f1e803d454@91.121.65.179:40404", - "enode://48e063a6cf5f335b1ef2ed98126bf522cf254396f850c7d442fe2edbbc23398787e14cd4de7968a00175a82762de9cbe9e1407d8ccbcaeca5004d65f8398d759@159.203.255.59:30303" + "enode://5fbfb426fbb46f8b8c1bd3dd140f5b511da558cd37d60844b525909ab82e13a25ee722293c829e52cb65c2305b1637fa9a2ea4d6634a224d5f400bfe244ac0de@162.243.55.45:30303", + "enode://42d8f29d1db5f4b2947cd5c3d76c6d0d3697e6b9b3430c3d41e46b4bb77655433aeedc25d4b4ea9d8214b6a43008ba67199374a9b53633301bca0cd20c6928ab@104.155.176.151:30303", + "enode://814920f1ec9510aa9ea1c8f79d8b6e6a462045f09caa2ae4055b0f34f7416fca6facd3dd45f1cf1673c0209e0503f02776b8ff94020e98b6679a0dc561b4eba0@104.154.136.117:30303", + "enode://72e445f4e89c0f476d404bc40478b0df83a5b500d2d2e850e08eb1af0cd464ab86db6160d0fde64bd77d5f0d33507ae19035671b3c74fec126d6e28787669740@104.198.71.200:30303" ], "accounts": { "0000000000000000000000000000000000000001": { "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } }, diff --git a/ethcore/res/ethereum/frontier.json b/ethcore/res/ethereum/foundation.json similarity index 99% rename from ethcore/res/ethereum/frontier.json rename to ethcore/res/ethereum/foundation.json index 2ab8bbe5b..7338b2f2b 100644 --- a/ethcore/res/ethereum/frontier.json +++ b/ethcore/res/ethereum/foundation.json @@ -1,5 +1,5 @@ { - "name": "Frontier/Homestead", + "name": "Foundation", "dataDir": "ethereum", "engine": { "Ethash": { @@ -9,7 +9,7 @@ "difficultyBoundDivisor": "0x0800", "durationLimit": "0x0d", "blockReward": "0x4563918244F40000", - "registrar" : "0x3bb2bb5c6c9c9b7f4ef430b47dc7e026310042ea", + "registrar" : "0xe3389675d0338462dC76C6f9A3e432550c36A142", "homesteadTransition": "0x118c30", "daoHardforkTransition": "0x1d4c00", "daoHardforkBeneficiary": "0xbf4ed7b27f1d666546e30d74d50d173d20bca754", diff --git a/ethcore/res/ethereum/kovan.json b/ethcore/res/ethereum/kovan.json new file mode 100644 index 000000000..e9c059aaa --- /dev/null +++ b/ethcore/res/ethereum/kovan.json @@ -0,0 +1,59 @@ +{ + "name": "Kovan", + "dataDir": "kovan", + "engine": { + "authorityRound": { + "params": { + "gasLimitBoundDivisor": "0x400", + "registrar" : "0xfAb104398BBefbd47752E7702D9fE23047E1Bca3", + "stepDuration": "4", + "blockReward": "0x4563918244F40000", + "validators" : { + "list": [ + "0x00D6Cc1BA9cf89BD2e58009741f4F7325BAdc0ED", + "0x00427feae2419c15b89d1c21af10d1b6650a4d3d", + "0x4Ed9B08e6354C70fE6F8CB0411b0d3246b424d6c", + "0x0020ee4Be0e2027d76603cB751eE069519bA81A1", + + "0x0010f94b296a852aaac52ea6c5ac72e03afd032d", + + "0x007733a1FE69CF3f2CF989F81C7b4cAc1693387A", + "0x00E6d2b931F55a3f1701c7389d592a7778897879", + "0x00e4a10650e5a6D6001C38ff8E64F97016a1645c", + + "0x00a0a24b9f0e5ec7aa4c7389b8302fd0123194de" + ] + } + } + } + }, + "params": { + "maximumExtraDataSize": "0x20", + "minGasLimit": "0x1388", + "networkID" : "0x2A" + }, + "genesis": { + "seal": { + "authorityRound": { + "step": "0x0", + "signature": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + } + }, + "difficulty": "0x20000", + "gasLimit": "0x5B8D80" + }, + "accounts": { + "0x0000000000000000000000000000000000000001": { "balance": "1", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } }, + "0x0000000000000000000000000000000000000002": { "balance": "1", "builtin": { "name": "sha256", "pricing": { "linear": { "base": 60, "word": 12 } } } }, + "0x0000000000000000000000000000000000000003": { "balance": "1", "builtin": { "name": "ripemd160", "pricing": { "linear": { "base": 600, "word": 120 } } } }, + "0x0000000000000000000000000000000000000004": { "balance": "1", "builtin": { "name": "identity", "pricing": { "linear": { "base": 15, "word": 3 } } } }, + "0x00521965e7bd230323c423d96c657db5b79d099f": { "balance": "1606938044258990275541962092341162602522202993782792835301376" } + }, + "nodes": [ + "enode://c005dd308256c60fab247813d8bf6d6e81f9cd354287837eb1c2fcf294adaa913a3208e88900ef5c55a8cba7042c301d80503edec2ad3f92a72e241ee6743854@192.241.230.87:30303", + "enode://48caeceb2724f2f71406990aa81efe87f8c53f26441d891473da2ae50cc138f238addc0e46b5aee240db55de8c711daac53d7b32a3f13e30edb86a3ca7c2700b@138.68.143.220:30303", + "enode://85705212fd28ebdd56669fb55e958feb9d81f74fe76c82f867564b6c2995e69f596df0f588eba16f1a43b69ce06485d68231a0c83fed8469b41eba0e390c126f@139.59.146.42:30303", + "enode://2aa81bd0a761cd4f02c934dcf3f81c5b65953e51ab5ba03ceb1f125eb06418a1cdffb1c9d01871aa7bd456f3fce35e745608189ad1164f72b2161634b0c3f6ea@188.166.240.190:30303", + "enode://c5900cdd6d20795d58372f42dfbab9d664c27bb97e9c27972741942736e919122f9bac28e74cbc58e4ff195475ea90d9880b71a37af5b5a8cb41d843f765cff8@174.138.79.48:30303" + ] +} diff --git a/ethcore/res/ethereum/morden.json b/ethcore/res/ethereum/morden.json index 5b64b63da..22f253bf8 100644 --- a/ethcore/res/ethereum/morden.json +++ b/ethcore/res/ethereum/morden.json @@ -48,7 +48,10 @@ }, "nodes": [ "enode://e731347db0521f3476e6bbbb83375dcd7133a1601425ebd15fd10f3835fd4c304fba6282087ca5a0deeafadf0aa0d4fd56c3323331901c1f38bd181c283e3e35@128.199.55.137:30303", - "enode://ceb5c0f85eb994dbe9693bf46d99b03f6b838d17cc74e68d5eb003171ff39e5f120b17f965b267c319303f94d80b9d994b77062fb1486d76ce95d9f3d8fe1cb4@46.101.122.141:30303" + "enode://ceb5c0f85eb994dbe9693bf46d99b03f6b838d17cc74e68d5eb003171ff39e5f120b17f965b267c319303f94d80b9d994b77062fb1486d76ce95d9f3d8fe1cb4@46.101.122.141:30303", + "enode://fb28713820e718066a2f5df6250ae9d07cff22f672dbf26be6c75d088f821a9ad230138ba492c533a80407d054b1436ef18e951bb65e6901553516c8dffe8ff0@104.155.176.151:30304", + "enode://afdc6076b9bf3e7d3d01442d6841071e84c76c73a7016cb4f35c0437df219db38565766234448f1592a07ba5295a867f0ce87b359bf50311ed0b830a2361392d@104.154.136.117:30403", + "enode://21101a9597b79e933e17bc94ef3506fe99a137808907aa8fefa67eea4b789792ad11fb391f38b00087f8800a2d3dff011572b62a31232133dd1591ac2d1502c8@104.198.71.200:30403" ], "accounts": { "0000000000000000000000000000000000000001": { "balance": "1", "nonce": "1048576", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } }, diff --git a/ethcore/res/instant_seal.json b/ethcore/res/instant_seal.json index b2d119883..c791b9a2d 100644 --- a/ethcore/res/instant_seal.json +++ b/ethcore/res/instant_seal.json @@ -1,7 +1,11 @@ { "name": "DevelopmentChain", "engine": { - "instantSeal": null + "instantSeal": { + "params": { + "registrar": "0x0000000000000000000000000000000000000005" + } + } }, "params": { "accountStartNonce": "0x0", @@ -25,6 +29,7 @@ "0000000000000000000000000000000000000002": { "balance": "1", "builtin": { "name": "sha256", "pricing": { "linear": { "base": 60, "word": 12 } } } }, "0000000000000000000000000000000000000003": { "balance": "1", "builtin": { "name": "ripemd160", "pricing": { "linear": { "base": 600, "word": 120 } } } }, "0000000000000000000000000000000000000004": { "balance": "1", "builtin": { "name": "identity", "pricing": { "linear": { "base": 15, "word": 3 } } } }, + "0000000000000000000000000000000000000005": { "balance": "1", "constructor": "0x606060405233600060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550670de0b6b3a764000060035534610000575b612904806100666000396000f3006060604052361561013c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806306b2ff471461014157806313af40351461018c57806319362a28146101bf5780633f3935d114610248578063432ced04146102b75780634f39ca59146102eb5780636795dbcd1461032457806369fe0e2d146103c857806379ce9fac146103fd5780638da5cb5b1461045557806390b97fc1146104a457806392698814146105245780639890220b1461055d578063ac4e73f914610584578063ac72c12014610612578063c3a358251461064b578063ddca3f43146106c3578063deb931a2146106e6578063df57b74214610747578063e30bd740146107a8578063eadf976014610862578063ef5454d6146108e7578063f25eb5c114610975578063f6d339e414610984575b610000565b3461000057610172600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610a1f565b604051808215151515815260200191505060405180910390f35b34610000576101bd600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610a81565b005b346100005761022e60048080356000191690602001909190803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509190803560001916906020019091905050610ba2565b604051808215151515815260200191505060405180910390f35b346100005761029d600480803590602001908201803590602001908080601f01602080910402602001604051908101604052809392919081815260200183838082843782019150505050505091905050610dc9565b604051808215151515815260200191505060405180910390f35b6102d1600480803560001916906020019091905050611035565b604051808215151515815260200191505060405180910390f35b346100005761030a60048080356000191690602001909190505061115f565b604051808215151515815260200191505060405180910390f35b346100005761038660048080356000191690602001909190803590602001908201803590602001908080601f01602080910402602001604051908101604052809392919081815260200183838082843782019150505050505091905050611378565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34610000576103e3600480803590602001909190505061140d565b604051808215151515815260200191505060405180910390f35b346100005761043b60048080356000191690602001909190803573ffffffffffffffffffffffffffffffffffffffff169060200190919050506114b4565b604051808215151515815260200191505060405180910390f35b34610000576104626115fb565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b346100005761050660048080356000191690602001909190803590602001908201803590602001908080601f01602080910402602001604051908101604052809392919081815260200183838082843782019150505050505091905050611621565b60405180826000191660001916815260200191505060405180910390f35b34610000576105436004808035600019169060200190919050506116b2565b604051808215151515815260200191505060405180910390f35b346100005761056a611715565b604051808215151515815260200191505060405180910390f35b34610000576105f8600480803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050611824565b604051808215151515815260200191505060405180910390f35b3461000057610631600480803560001916906020019091905050611d8b565b604051808215151515815260200191505060405180910390f35b34610000576106ad60048080356000191690602001909190803590602001908201803590602001908080601f01602080910402602001604051908101604052809392919081815260200183838082843782019150505050505091905050611dee565b6040518082815260200191505060405180910390f35b34610000576106d0611e83565b6040518082815260200191505060405180910390f35b3461000057610705600480803560001916906020019091905050611e89565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b3461000057610766600480803560001916906020019091905050611ed2565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34610000576107d9600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050611f1b565b6040518080602001828103825283818151815260200191508051906020019080838360008314610828575b80518252602083111561082857602082019150602081019050602083039250610804565b505050905090810190601f1680156108545780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34610000576108cd60048080356000191690602001909190803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509190803590602001909190505061200c565b604051808215151515815260200191505060405180910390f35b346100005761095b600480803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050612236565b604051808215151515815260200191505060405180910390f35b3461000057610982612425565b005b3461000057610a0560048080356000191690602001909190803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050612698565b604051808215151515815260200191505060405180910390f35b60006000600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020805460018160011615610100020316600290049050141590505b919050565b600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141515610add57610b9f565b8073ffffffffffffffffffffffffffffffffffffffff16600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f70aea8d848e8a90fb7661b227dc522eb6395c3dac71b63cb59edd5c9899b236460405180905060405180910390a380600060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505b5b50565b6000833373ffffffffffffffffffffffffffffffffffffffff1660016000836000191660001916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16141515610c1d57610dc1565b82600160008760001916600019168152602001908152602001600020600201856040518082805190602001908083835b60208310610c705780518252602082019150602081019050602083039250610c4d565b6001836020036101000a03801982511681845116808217855250505050505090500191505090815260200160405180910390208160001916905550836040518082805190602001908083835b60208310610cdf5780518252602082019150602081019050602083039250610cbc565b6001836020036101000a038019825116818451168082178552505050505050905001915050604051809103902085600019167fb829c3e412537bbe794c048ccb9e4605bb4aaaa8e4d4c15c1a6e0c2adc1716ea866040518080602001828103825283818151815260200191508051906020019080838360008314610d82575b805182526020831115610d8257602082019150602081019050602083039250610d5e565b505050905090810190601f168015610dae5780820380516001836020036101000a031916815260200191505b509250505060405180910390a3600191505b5b509392505050565b6000813373ffffffffffffffffffffffffffffffffffffffff1660016000836040518082805190602001908083835b60208310610e1b5780518252602082019150602081019050602083039250610df8565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405180910390206000191660001916815260200190815260200160002060010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16141515610ea45761102f565b82600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000209080519060200190828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10610f2d57805160ff1916838001178555610f5b565b82800160010185558215610f5b579182015b82811115610f5a578251825591602001919060010190610f3f565b5b509050610f8091905b80821115610f7c576000816000905550600101610f64565b5090565b50503373ffffffffffffffffffffffffffffffffffffffff16836040518082805190602001908083835b60208310610fcd5780518252602082019150602081019050602083039250610faa565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405180910390207f098ae8581bb8bd9af1beaf7f2e9f51f31a8e5a8bfada4e303a645d71d9c9192060405180905060405180910390a3600191505b5b50919050565b600081600060016000836000191660001916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614151561109b57611159565b6003543410156110aa57611158565b3360016000856000191660001916815260200190815260200160002060000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055503373ffffffffffffffffffffffffffffffffffffffff1683600019167f4963513eca575aba66fdcd25f267aae85958fe6fb97e75fa25d783f1a091a22160405180905060405180910390a3600191505b5b5b50919050565b6000813373ffffffffffffffffffffffffffffffffffffffff1660016000836000191660001916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161415156111da57611372565b6002600060016000866000191660001916815260200190815260200160002060010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020805460018160011615610100020316600290046000825580601f1061127c57506112b3565b601f0160209004906000526020600020908101906112b291905b808211156112ae576000816000905550600101611296565b5090565b5b5060016000846000191660001916815260200190815260200160002060006000820160006101000a81549073ffffffffffffffffffffffffffffffffffffffff02191690556001820160006101000a81549073ffffffffffffffffffffffffffffffffffffffff021916905550503373ffffffffffffffffffffffffffffffffffffffff1683600019167fef1961b4d2909dc23643b309bfe5c3e5646842d98c3a58517037ef3871185af360405180905060405180910390a3600191505b5b50919050565b6000600160008460001916600019168152602001908152602001600020600201826040518082805190602001908083835b602083106113cc57805182526020820191506020810190506020830392506113a9565b6001836020036101000a0380198251168184511680821785525050505050509050019150509081526020016040518091039020546001900490505b92915050565b6000600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561146b576114af565b816003819055507f6bbc57480a46553fa4d156ce702beef5f3ad66303b0ed1a5d4cb44966c6584c3826040518082815260200191505060405180910390a1600190505b5b919050565b6000823373ffffffffffffffffffffffffffffffffffffffff1660016000836000191660001916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614151561152f576115f4565b8260016000866000191660001916815260200190815260200160002060000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1685600019167f7b97c62130aa09acbbcbf7482630e756592496f1759eaf702f469cf64dfb779460405180905060405180910390a4600191505b5b5092915050565b600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000600160008460001916600019168152602001908152602001600020600201826040518082805190602001908083835b602083106116755780518252602082019150602081019050602083039250611652565b6001836020036101000a03801982511681845116808217855250505050505090500191505090815260200160405180910390205490505b92915050565b6000600060016000846000191660001916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16141590505b919050565b6000600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561177357611821565b7fdef931299fe61d176f949118058530c1f3f539dcb6950b4e372c9b835c33ca073073ffffffffffffffffffffffffffffffffffffffff16316040518082815260200191505060405180910390a13373ffffffffffffffffffffffffffffffffffffffff166108fc3073ffffffffffffffffffffffffffffffffffffffff16319081150290604051809050600060405180830381858888f19350505050151561181b57610000565b600190505b5b90565b60006000836040518082805190602001908083835b6020831061185c5780518252602082019150602081019050602083039250611839565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405180910390203373ffffffffffffffffffffffffffffffffffffffff1660016000836000191660001916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614151561190157611d83565b846040518082805190602001908083835b602083106119355780518252602082019150602081019050602083039250611912565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405180910390209150600060016000846000191660001916815260200190815260200160002060010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614158015611ab4575081600019166002600060016000866000191660001916815260200190815260200160002060010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206040518082805460018160011615610100020316600290048015611aa15780601f10611a7f576101008083540402835291820191611aa1565b820191906000526020600020905b815481529060010190602001808311611a8d575b5050915050604051809103902060001916145b15611c79576002600060016000856000191660001916815260200190815260200160002060010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020805460018160011615610100020316600290046000825580601f10611b5b5750611b92565b601f016020900490600052602060002090810190611b9191905b80821115611b8d576000816000905550600101611b75565b5090565b5b5060016000836000191660001916815260200190815260200160002060010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16856040518082805190602001908083835b60208310611c1c5780518252602082019150602081019050602083039250611bf9565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405180910390207f12491ad95fd945e444d88a894ffad3c21959880a4dcd8af99d4ae4ffc71d4abd60405180905060405180910390a35b8360016000846000191660001916815260200190815260200160002060010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508373ffffffffffffffffffffffffffffffffffffffff16856040518082805190602001908083835b60208310611d215780518252602082019150602081019050602083039250611cfe565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405180910390207f728435a0031f6a04538fcdd24922a7e06bc7bc945db03e83d22122d1bc5f28df60405180905060405180910390a3600192505b5b505092915050565b6000600060016000846000191660001916815260200190815260200160002060010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16141590505b919050565b6000600160008460001916600019168152602001908152602001600020600201826040518082805190602001908083835b60208310611e425780518252602082019150602081019050602083039250611e1f565b6001836020036101000a0380198251168184511680821785525050505050509050019150509081526020016040518091039020546001900490505b92915050565b60035481565b600060016000836000191660001916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690505b919050565b600060016000836000191660001916815260200190815260200160002060010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690505b919050565b6020604051908101604052806000815250600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015611fff5780601f10611fd457610100808354040283529160200191611fff565b820191906000526020600020905b815481529060010190602001808311611fe257829003601f168201915b505050505090505b919050565b6000833373ffffffffffffffffffffffffffffffffffffffff1660016000836000191660001916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161415156120875761222e565b82600102600160008760001916600019168152602001908152602001600020600201856040518082805190602001908083835b602083106120dd57805182526020820191506020810190506020830392506120ba565b6001836020036101000a03801982511681845116808217855250505050505090500191505090815260200160405180910390208160001916905550836040518082805190602001908083835b6020831061214c5780518252602082019150602081019050602083039250612129565b6001836020036101000a038019825116818451168082178552505050505050905001915050604051809103902085600019167fb829c3e412537bbe794c048ccb9e4605bb4aaaa8e4d4c15c1a6e0c2adc1716ea8660405180806020018281038252838181518152602001915080519060200190808383600083146121ef575b8051825260208311156121ef576020820191506020810190506020830392506121cb565b505050905090810190601f16801561221b5780820380516001836020036101000a031916815260200191505b509250505060405180910390a3600191505b5b509392505050565b6000600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415156122945761241f565b82600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000209080519060200190828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061231d57805160ff191683800117855561234b565b8280016001018555821561234b579182015b8281111561234a57825182559160200191906001019061232f565b5b50905061237091905b8082111561236c576000816000905550600101612354565b5090565b50508173ffffffffffffffffffffffffffffffffffffffff16836040518082805190602001908083835b602083106123bd578051825260208201915060208101905060208303925061239a565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405180910390207f098ae8581bb8bd9af1beaf7f2e9f51f31a8e5a8bfada4e303a645d71d9c9192060405180905060405180910390a3600190505b5b92915050565b3373ffffffffffffffffffffffffffffffffffffffff16600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060405180828054600181600116156101000203166002900480156124d65780601f106124b45761010080835404028352918201916124d6565b820191906000526020600020905b8154815290600101906020018083116124c2575b505091505060405180910390207f12491ad95fd945e444d88a894ffad3c21959880a4dcd8af99d4ae4ffc71d4abd60405180905060405180910390a360016000600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060405180828054600181600116156101000203166002900480156125b05780601f1061258e5761010080835404028352918201916125b0565b820191906000526020600020905b81548152906001019060200180831161259c575b505091505060405180910390206000191660001916815260200190815260200160002060010160006101000a81549073ffffffffffffffffffffffffffffffffffffffff0219169055600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020805460018160011615610100020316600290046000825580601f1061265d5750612694565b601f01602090049060005260206000209081019061269391905b8082111561268f576000816000905550600101612677565b5090565b5b505b565b6000833373ffffffffffffffffffffffffffffffffffffffff1660016000836000191660001916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16141515612713576128d0565b8273ffffffffffffffffffffffffffffffffffffffff16600102600160008760001916600019168152602001908152602001600020600201856040518082805190602001908083835b6020831061277f578051825260208201915060208101905060208303925061275c565b6001836020036101000a03801982511681845116808217855250505050505090500191505090815260200160405180910390208160001916905550836040518082805190602001908083835b602083106127ee57805182526020820191506020810190506020830392506127cb565b6001836020036101000a038019825116818451168082178552505050505050905001915050604051809103902085600019167fb829c3e412537bbe794c048ccb9e4605bb4aaaa8e4d4c15c1a6e0c2adc1716ea866040518080602001828103825283818151815260200191508051906020019080838360008314612891575b8051825260208311156128915760208201915060208101905060208303925061286d565b505050905090810190601f1680156128bd5780820380516001836020036101000a031916815260200191505b509250505060405180910390a3600191505b5b5093925050505600a165627a7a7230582066b2da4773a0f1d81efe071c66b51c46868a871661efd18c0f629353ff4c1f9b0029" }, "0x00a329c0648769a73afac7f9381e08fb43dbea72": { "balance": "1606938044258990275541962092341162602522202993782792835301376" } } } diff --git a/ethcore/res/tendermint.json b/ethcore/res/tendermint.json index 83372fea5..642f5b385 100644 --- a/ethcore/res/tendermint.json +++ b/ethcore/res/tendermint.json @@ -38,7 +38,7 @@ "timestamp": "0x00", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "extraData": "0x", - "gasLimit": "0x2fefd8" + "gasLimit": "0x222222" }, "accounts": { "0000000000000000000000000000000000000001": { "balance": "1", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } }, diff --git a/ethcore/res/validator_contract.json b/ethcore/res/validator_contract.json index 33fdf4c4f..6c2f87758 100644 --- a/ethcore/res/validator_contract.json +++ b/ethcore/res/validator_contract.json @@ -27,7 +27,7 @@ "timestamp": "0x00", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "extraData": "0x", - "gasLimit": "0x2fefd8" + "gasLimit": "0x222222" }, "accounts": { "0000000000000000000000000000000000000001": { "balance": "1", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } }, diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 54e433a72..d15e9e6aa 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -253,7 +253,7 @@ impl Client { if let Some(reg_addr) = client.additional_params().get("registrar").and_then(|s| Address::from_str(s).ok()) { trace!(target: "client", "Found registrar at {}", reg_addr); let weak = Arc::downgrade(&client); - let registrar = Registry::new(reg_addr, move |a, d| weak.upgrade().ok_or("No client!".into()).and_then(|c| c.call_contract(a, d))); + let registrar = Registry::new(reg_addr, move |a, d| weak.upgrade().ok_or("No client!".into()).and_then(|c| c.call_contract(BlockId::Latest, a, d))); *client.registrar.lock() = Some(registrar); } Ok(client) @@ -1428,7 +1428,7 @@ impl BlockChainClient for Client { } } - fn call_contract(&self, address: Address, data: Bytes) -> Result { + fn call_contract(&self, block_id: BlockId, address: Address, data: Bytes) -> Result { let from = Address::default(); let transaction = Transaction { nonce: self.latest_nonce(&from), @@ -1439,7 +1439,7 @@ impl BlockChainClient for Client { data: data, }.fake_sign(from); - self.call(&transaction, BlockId::Latest, Default::default()) + self.call(&transaction, block_id, Default::default()) .map_err(|e| format!("{:?}", e)) .map(|executed| { executed.output @@ -1612,7 +1612,6 @@ impl ::client::ProvingBlockChainClient for Client { _ => return Some(state.drop().1.extract_proof()), } } - } impl Drop for Client { diff --git a/ethcore/src/client/registry.rs b/ethcore/src/client/registry.rs index 9e60a1251..c8f750576 100644 --- a/ethcore/src/client/registry.rs +++ b/ethcore/src/client/registry.rs @@ -1,264 +1,338 @@ // Autogenerated from JSON contract definition using Rust contract convertor. - +// Command line: --name=Registry --jsonabi=/Users/gav/registry.abi +#![allow(unused_imports)] use std::string::String; use std::result::Result; use std::fmt; use {util, ethabi}; -use util::FixedHash; -use util::Uint; +use util::{FixedHash, Uint}; pub struct Registry { contract: ethabi::Contract, pub address: util::Address, - do_call: Box) -> Result, String> + Send + 'static>, + do_call: Box) -> Result, String> + Send + Sync + 'static>, } impl Registry { - pub fn new(address: util::Address, do_call: F) -> Self where F: Fn(util::Address, Vec) -> Result, String> + Send + 'static { + pub fn new(address: util::Address, do_call: F) -> Self + where F: Fn(util::Address, Vec) -> Result, String> + Send + Sync + 'static { Registry { - contract: ethabi::Contract::new(ethabi::Interface::load(b"[{\"constant\":false,\"inputs\":[{\"name\":\"_new\",\"type\":\"address\"}],\"name\":\"setOwner\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_name\",\"type\":\"string\"}],\"name\":\"confirmReverse\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"}],\"name\":\"reserve\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"},{\"name\":\"_key\",\"type\":\"string\"},{\"name\":\"_value\",\"type\":\"bytes32\"}],\"name\":\"set\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"}],\"name\":\"drop\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"},{\"name\":\"_key\",\"type\":\"string\"}],\"name\":\"getAddress\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_amount\",\"type\":\"uint256\"}],\"name\":\"setFee\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"},{\"name\":\"_to\",\"type\":\"address\"}],\"name\":\"transfer\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"}],\"name\":\"reserved\",\"outputs\":[{\"name\":\"reserved\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"drain\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_name\",\"type\":\"string\"},{\"name\":\"_who\",\"type\":\"address\"}],\"name\":\"proposeReverse\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"},{\"name\":\"_key\",\"type\":\"string\"}],\"name\":\"getUint\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"},{\"name\":\"_key\",\"type\":\"string\"}],\"name\":\"get\",\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"fee\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"}],\"name\":\"getOwner\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"address\"}],\"name\":\"reverse\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"},{\"name\":\"_key\",\"type\":\"string\"},{\"name\":\"_value\",\"type\":\"uint256\"}],\"name\":\"setUint\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"removeReverse\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"},{\"name\":\"_key\",\"type\":\"string\"},{\"name\":\"_value\",\"type\":\"address\"}],\"name\":\"setAddress\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Drained\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"FeeChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"name\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"Reserved\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"name\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"oldOwner\",\"type\":\"address\"},{\"indexed\":true,\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"Transferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"name\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"Dropped\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"name\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"name\":\"key\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"plainKey\",\"type\":\"string\"}],\"name\":\"DataChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"name\",\"type\":\"string\"},{\"indexed\":true,\"name\":\"reverse\",\"type\":\"address\"}],\"name\":\"ReverseProposed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"name\",\"type\":\"string\"},{\"indexed\":true,\"name\":\"reverse\",\"type\":\"address\"}],\"name\":\"ReverseConfirmed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"name\",\"type\":\"string\"},{\"indexed\":true,\"name\":\"reverse\",\"type\":\"address\"}],\"name\":\"ReverseRemoved\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"old\",\"type\":\"address\"},{\"indexed\":true,\"name\":\"current\",\"type\":\"address\"}],\"name\":\"NewOwner\",\"type\":\"event\"}]").expect("JSON is autogenerated; qed")), + contract: ethabi::Contract::new(ethabi::Interface::load(b"[{\"constant\":true,\"inputs\":[{\"name\":\"_data\",\"type\":\"address\"}],\"name\":\"canReverse\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_new\",\"type\":\"address\"}],\"name\":\"setOwner\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"},{\"name\":\"_key\",\"type\":\"string\"},{\"name\":\"_value\",\"type\":\"bytes32\"}],\"name\":\"setData\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_name\",\"type\":\"string\"}],\"name\":\"confirmReverse\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"}],\"name\":\"reserve\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"payable\":true,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"}],\"name\":\"drop\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"},{\"name\":\"_key\",\"type\":\"string\"}],\"name\":\"getAddress\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_amount\",\"type\":\"uint256\"}],\"name\":\"setFee\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"},{\"name\":\"_to\",\"type\":\"address\"}],\"name\":\"transfer\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"},{\"name\":\"_key\",\"type\":\"string\"}],\"name\":\"getData\",\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"}],\"name\":\"reserved\",\"outputs\":[{\"name\":\"reserved\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"drain\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_name\",\"type\":\"string\"},{\"name\":\"_who\",\"type\":\"address\"}],\"name\":\"proposeReverse\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"}],\"name\":\"hasReverse\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"},{\"name\":\"_key\",\"type\":\"string\"}],\"name\":\"getUint\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"fee\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"}],\"name\":\"getOwner\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"}],\"name\":\"getReverse\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_data\",\"type\":\"address\"}],\"name\":\"reverse\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"},{\"name\":\"_key\",\"type\":\"string\"},{\"name\":\"_value\",\"type\":\"uint256\"}],\"name\":\"setUint\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_name\",\"type\":\"string\"},{\"name\":\"_who\",\"type\":\"address\"}],\"name\":\"confirmReverseAs\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"removeReverse\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_name\",\"type\":\"bytes32\"},{\"name\":\"_key\",\"type\":\"string\"},{\"name\":\"_value\",\"type\":\"address\"}],\"name\":\"setAddress\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"}]").expect("JSON is autogenerated; qed")), address: address, do_call: Box::new(do_call), } } fn as_string(e: T) -> String { format!("{:?}", e) } + /// Auto-generated from: `{"constant":true,"inputs":[{"name":"_data","type":"address"}],"name":"canReverse","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"}` + #[allow(dead_code)] + pub fn can_reverse(&self, _data: &util::Address) -> Result + { + let call = self.contract.function("canReverse".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![ethabi::Token::Address(_data.clone().0)] + ).map_err(Self::as_string)?; + let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + let mut result = output.into_iter().rev().collect::>(); + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_bool().ok_or("Invalid type returned")?; r })) + } + /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"}` #[allow(dead_code)] - pub fn set_owner(&self, _new: &util::Address) -> Result<(), String> { + pub fn set_owner(&self, _new: &util::Address) -> Result<(), String> + { let call = self.contract.function("setOwner".into()).map_err(Self::as_string)?; let data = call.encode_call( vec![ethabi::Token::Address(_new.clone().0)] ).map_err(Self::as_string)?; call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; - Ok(()) + Ok(()) + } + + /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"bytes32"}],"name":"setData","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"}` + #[allow(dead_code)] + pub fn set_data(&self, _name: &util::H256, _key: &str, _value: &util::H256) -> Result + { + let call = self.contract.function("setData".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![ethabi::Token::FixedBytes(_name.as_ref().to_owned()), ethabi::Token::String(_key.to_owned()), ethabi::Token::FixedBytes(_value.as_ref().to_owned())] + ).map_err(Self::as_string)?; + let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + let mut result = output.into_iter().rev().collect::>(); + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_bool().ok_or("Invalid type returned")?; r })) } /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_name","type":"string"}],"name":"confirmReverse","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"}` #[allow(dead_code)] - pub fn confirm_reverse(&self, _name: &str) -> Result { + pub fn confirm_reverse(&self, _name: &str) -> Result + { let call = self.contract.function("confirmReverse".into()).map_err(Self::as_string)?; let data = call.encode_call( vec![ethabi::Token::String(_name.to_owned())] ).map_err(Self::as_string)?; let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_bool().ok_or("Invalid type returned")?; r })) + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_bool().ok_or("Invalid type returned")?; r })) } - /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_name","type":"bytes32"}],"name":"reserve","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"}` + /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_name","type":"bytes32"}],"name":"reserve","outputs":[{"name":"success","type":"bool"}],"payable":true,"type":"function"}` #[allow(dead_code)] - pub fn reserve(&self, _name: &util::H256) -> Result { + pub fn reserve(&self, _name: &util::H256) -> Result + { let call = self.contract.function("reserve".into()).map_err(Self::as_string)?; let data = call.encode_call( vec![ethabi::Token::FixedBytes(_name.as_ref().to_owned())] ).map_err(Self::as_string)?; let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_bool().ok_or("Invalid type returned")?; r })) - } - - /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"bytes32"}],"name":"set","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"}` - #[allow(dead_code)] - pub fn set(&self, _name: &util::H256, _key: &str, _value: &util::H256) -> Result { - let call = self.contract.function("set".into()).map_err(Self::as_string)?; - let data = call.encode_call( - vec![ethabi::Token::FixedBytes(_name.as_ref().to_owned()), ethabi::Token::String(_key.to_owned()), ethabi::Token::FixedBytes(_value.as_ref().to_owned())] - ).map_err(Self::as_string)?; - let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; - let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_bool().ok_or("Invalid type returned")?; r })) + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_bool().ok_or("Invalid type returned")?; r })) } /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_name","type":"bytes32"}],"name":"drop","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"}` #[allow(dead_code)] - pub fn drop(&self, _name: &util::H256) -> Result { + pub fn drop(&self, _name: &util::H256) -> Result + { let call = self.contract.function("drop".into()).map_err(Self::as_string)?; let data = call.encode_call( vec![ethabi::Token::FixedBytes(_name.as_ref().to_owned())] ).map_err(Self::as_string)?; let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_bool().ok_or("Invalid type returned")?; r })) + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_bool().ok_or("Invalid type returned")?; r })) } /// Auto-generated from: `{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getAddress","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"}` #[allow(dead_code)] - pub fn get_address(&self, _name: &util::H256, _key: &str) -> Result { + pub fn get_address(&self, _name: &util::H256, _key: &str) -> Result + { let call = self.contract.function("getAddress".into()).map_err(Self::as_string)?; let data = call.encode_call( vec![ethabi::Token::FixedBytes(_name.as_ref().to_owned()), ethabi::Token::String(_key.to_owned())] ).map_err(Self::as_string)?; let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_address().ok_or("Invalid type returned")?; util::Address::from(r) })) + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_address().ok_or("Invalid type returned")?; util::Address::from(r) })) } - /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_amount","type":"uint256"}],"name":"setFee","outputs":[],"payable":false,"type":"function"}` + /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_amount","type":"uint256"}],"name":"setFee","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"}` #[allow(dead_code)] - pub fn set_fee(&self, _amount: util::U256) -> Result<(), String> { + pub fn set_fee(&self, _amount: util::U256) -> Result + { let call = self.contract.function("setFee".into()).map_err(Self::as_string)?; let data = call.encode_call( vec![ethabi::Token::Uint({ let mut r = [0u8; 32]; _amount.to_big_endian(&mut r); r })] ).map_err(Self::as_string)?; - call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; - - Ok(()) + let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + let mut result = output.into_iter().rev().collect::>(); + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_bool().ok_or("Invalid type returned")?; r })) } /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_to","type":"address"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"}` #[allow(dead_code)] - pub fn transfer(&self, _name: &util::H256, _to: &util::Address) -> Result { + pub fn transfer(&self, _name: &util::H256, _to: &util::Address) -> Result + { let call = self.contract.function("transfer".into()).map_err(Self::as_string)?; let data = call.encode_call( vec![ethabi::Token::FixedBytes(_name.as_ref().to_owned()), ethabi::Token::Address(_to.clone().0)] ).map_err(Self::as_string)?; let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_bool().ok_or("Invalid type returned")?; r })) + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_bool().ok_or("Invalid type returned")?; r })) } /// Auto-generated from: `{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"}` #[allow(dead_code)] - pub fn owner(&self) -> Result { + pub fn owner(&self) -> Result + { let call = self.contract.function("owner".into()).map_err(Self::as_string)?; let data = call.encode_call( vec![] ).map_err(Self::as_string)?; let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_address().ok_or("Invalid type returned")?; util::Address::from(r) })) + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_address().ok_or("Invalid type returned")?; util::Address::from(r) })) + } + + /// Auto-generated from: `{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getData","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"}` + #[allow(dead_code)] + pub fn get_data(&self, _name: &util::H256, _key: &str) -> Result + { + let call = self.contract.function("getData".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![ethabi::Token::FixedBytes(_name.as_ref().to_owned()), ethabi::Token::String(_key.to_owned())] + ).map_err(Self::as_string)?; + let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + let mut result = output.into_iter().rev().collect::>(); + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_fixed_bytes().ok_or("Invalid type returned")?; util::H256::from_slice(r.as_ref()) })) } /// Auto-generated from: `{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"reserved","outputs":[{"name":"reserved","type":"bool"}],"payable":false,"type":"function"}` #[allow(dead_code)] - pub fn reserved(&self, _name: &util::H256) -> Result { + pub fn reserved(&self, _name: &util::H256) -> Result + { let call = self.contract.function("reserved".into()).map_err(Self::as_string)?; let data = call.encode_call( vec![ethabi::Token::FixedBytes(_name.as_ref().to_owned())] ).map_err(Self::as_string)?; let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_bool().ok_or("Invalid type returned")?; r })) + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_bool().ok_or("Invalid type returned")?; r })) } - /// Auto-generated from: `{"constant":false,"inputs":[],"name":"drain","outputs":[],"payable":false,"type":"function"}` + /// Auto-generated from: `{"constant":false,"inputs":[],"name":"drain","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"}` #[allow(dead_code)] - pub fn drain(&self) -> Result<(), String> { + pub fn drain(&self) -> Result + { let call = self.contract.function("drain".into()).map_err(Self::as_string)?; let data = call.encode_call( vec![] ).map_err(Self::as_string)?; - call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; - - Ok(()) + let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + let mut result = output.into_iter().rev().collect::>(); + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_bool().ok_or("Invalid type returned")?; r })) } /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_name","type":"string"},{"name":"_who","type":"address"}],"name":"proposeReverse","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"}` #[allow(dead_code)] - pub fn propose_reverse(&self, _name: &str, _who: &util::Address) -> Result { + pub fn propose_reverse(&self, _name: &str, _who: &util::Address) -> Result + { let call = self.contract.function("proposeReverse".into()).map_err(Self::as_string)?; let data = call.encode_call( vec![ethabi::Token::String(_name.to_owned()), ethabi::Token::Address(_who.clone().0)] ).map_err(Self::as_string)?; let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_bool().ok_or("Invalid type returned")?; r })) + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_bool().ok_or("Invalid type returned")?; r })) + } + + /// Auto-generated from: `{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"hasReverse","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"}` + #[allow(dead_code)] + pub fn has_reverse(&self, _name: &util::H256) -> Result + { + let call = self.contract.function("hasReverse".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![ethabi::Token::FixedBytes(_name.as_ref().to_owned())] + ).map_err(Self::as_string)?; + let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + let mut result = output.into_iter().rev().collect::>(); + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_bool().ok_or("Invalid type returned")?; r })) } /// Auto-generated from: `{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getUint","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"}` #[allow(dead_code)] - pub fn get_uint(&self, _name: &util::H256, _key: &str) -> Result { + pub fn get_uint(&self, _name: &util::H256, _key: &str) -> Result + { let call = self.contract.function("getUint".into()).map_err(Self::as_string)?; let data = call.encode_call( vec![ethabi::Token::FixedBytes(_name.as_ref().to_owned()), ethabi::Token::String(_key.to_owned())] ).map_err(Self::as_string)?; let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_uint().ok_or("Invalid type returned")?; util::U256::from(r.as_ref()) })) - } - - /// Auto-generated from: `{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"get","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"}` - #[allow(dead_code)] - pub fn get(&self, _name: &util::H256, _key: &str) -> Result { - let call = self.contract.function("get".into()).map_err(Self::as_string)?; - let data = call.encode_call( - vec![ethabi::Token::FixedBytes(_name.as_ref().to_owned()), ethabi::Token::String(_key.to_owned())] - ).map_err(Self::as_string)?; - let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; - let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_fixed_bytes().ok_or("Invalid type returned")?; util::H256::from_slice(r.as_ref()) })) + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_uint().ok_or("Invalid type returned")?; util::U256::from(r.as_ref()) })) } /// Auto-generated from: `{"constant":true,"inputs":[],"name":"fee","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"}` #[allow(dead_code)] - pub fn fee(&self) -> Result { + pub fn fee(&self) -> Result + { let call = self.contract.function("fee".into()).map_err(Self::as_string)?; let data = call.encode_call( vec![] ).map_err(Self::as_string)?; let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_uint().ok_or("Invalid type returned")?; util::U256::from(r.as_ref()) })) + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_uint().ok_or("Invalid type returned")?; util::U256::from(r.as_ref()) })) } /// Auto-generated from: `{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"getOwner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"}` #[allow(dead_code)] - pub fn get_owner(&self, _name: &util::H256) -> Result { + pub fn get_owner(&self, _name: &util::H256) -> Result + { let call = self.contract.function("getOwner".into()).map_err(Self::as_string)?; let data = call.encode_call( vec![ethabi::Token::FixedBytes(_name.as_ref().to_owned())] ).map_err(Self::as_string)?; let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_address().ok_or("Invalid type returned")?; util::Address::from(r) })) + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_address().ok_or("Invalid type returned")?; util::Address::from(r) })) } - /// Auto-generated from: `{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"reverse","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"}` + /// Auto-generated from: `{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"getReverse","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"}` #[allow(dead_code)] - pub fn reverse(&self, _1: &util::Address) -> Result { - let call = self.contract.function("reverse".into()).map_err(Self::as_string)?; + pub fn get_reverse(&self, _name: &util::H256) -> Result + { + let call = self.contract.function("getReverse".into()).map_err(Self::as_string)?; let data = call.encode_call( - vec![ethabi::Token::Address(_1.clone().0)] + vec![ethabi::Token::FixedBytes(_name.as_ref().to_owned())] ).map_err(Self::as_string)?; let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_string().ok_or("Invalid type returned")?; r })) + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_address().ok_or("Invalid type returned")?; util::Address::from(r) })) + } + + /// Auto-generated from: `{"constant":true,"inputs":[{"name":"_data","type":"address"}],"name":"reverse","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"}` + #[allow(dead_code)] + pub fn reverse(&self, _data: &util::Address) -> Result + { + let call = self.contract.function("reverse".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![ethabi::Token::Address(_data.clone().0)] + ).map_err(Self::as_string)?; + let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + let mut result = output.into_iter().rev().collect::>(); + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_string().ok_or("Invalid type returned")?; r })) } /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"uint256"}],"name":"setUint","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"}` #[allow(dead_code)] - pub fn set_uint(&self, _name: &util::H256, _key: &str, _value: util::U256) -> Result { + pub fn set_uint(&self, _name: &util::H256, _key: &str, _value: util::U256) -> Result + { let call = self.contract.function("setUint".into()).map_err(Self::as_string)?; let data = call.encode_call( vec![ethabi::Token::FixedBytes(_name.as_ref().to_owned()), ethabi::Token::String(_key.to_owned()), ethabi::Token::Uint({ let mut r = [0u8; 32]; _value.to_big_endian(&mut r); r })] ).map_err(Self::as_string)?; let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_bool().ok_or("Invalid type returned")?; r })) + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_bool().ok_or("Invalid type returned")?; r })) + } + + /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_name","type":"string"},{"name":"_who","type":"address"}],"name":"confirmReverseAs","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"}` + #[allow(dead_code)] + pub fn confirm_reverse_as(&self, _name: &str, _who: &util::Address) -> Result + { + let call = self.contract.function("confirmReverseAs".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![ethabi::Token::String(_name.to_owned()), ethabi::Token::Address(_who.clone().0)] + ).map_err(Self::as_string)?; + let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + let mut result = output.into_iter().rev().collect::>(); + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_bool().ok_or("Invalid type returned")?; r })) } /// Auto-generated from: `{"constant":false,"inputs":[],"name":"removeReverse","outputs":[],"payable":false,"type":"function"}` #[allow(dead_code)] - pub fn remove_reverse(&self) -> Result<(), String> { + pub fn remove_reverse(&self) -> Result<(), String> + { let call = self.contract.function("removeReverse".into()).map_err(Self::as_string)?; let data = call.encode_call( vec![] ).map_err(Self::as_string)?; call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; - Ok(()) + Ok(()) } /// Auto-generated from: `{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"address"}],"name":"setAddress","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"}` #[allow(dead_code)] - pub fn set_address(&self, _name: &util::H256, _key: &str, _value: &util::Address) -> Result { + pub fn set_address(&self, _name: &util::H256, _key: &str, _value: &util::Address) -> Result + { let call = self.contract.function("setAddress".into()).map_err(Self::as_string)?; let data = call.encode_call( vec![ethabi::Token::FixedBytes(_name.as_ref().to_owned()), ethabi::Token::String(_key.to_owned()), ethabi::Token::Address(_value.clone().0)] ).map_err(Self::as_string)?; let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_bool().ok_or("Invalid type returned")?; r })) + Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_bool().ok_or("Invalid type returned")?; r })) } -} \ No newline at end of file +} + diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index 79783175d..32706fd76 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -732,7 +732,7 @@ impl BlockChainClient for TestBlockChainClient { } } - fn call_contract(&self, _address: Address, _data: Bytes) -> Result { Ok(vec![]) } + fn call_contract(&self, _id: BlockId, _address: Address, _data: Bytes) -> Result { Ok(vec![]) } fn transact_contract(&self, address: Address, data: Bytes) -> Result { let transaction = Transaction { diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index 145398ef6..4407b232f 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -256,7 +256,7 @@ pub trait BlockChainClient : Sync + Send { fn pruning_info(&self) -> PruningInfo; /// Like `call`, but with various defaults. Designed to be used for calling contracts. - fn call_contract(&self, address: Address, data: Bytes) -> Result; + fn call_contract(&self, id: BlockId, address: Address, data: Bytes) -> Result; /// Import a transaction: used for misbehaviour reporting. fn transact_contract(&self, address: Address, data: Bytes) -> Result; diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index e4efdaea1..03b5d785f 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round.rs @@ -47,6 +47,8 @@ pub struct AuthorityRoundParams { pub step_duration: Duration, /// Block reward. pub block_reward: U256, + /// Namereg contract address. + pub registrar: Address, /// Starting step, pub start_step: Option, /// Valid validators. @@ -60,6 +62,7 @@ impl From for AuthorityRoundParams { step_duration: Duration::from_secs(p.step_duration.into()), validators: p.validators, block_reward: p.block_reward.map_or_else(U256::zero, Into::into), + registrar: p.registrar.map_or_else(Address::new, Into::into), start_step: p.start_step.map(Into::into), } } @@ -71,6 +74,7 @@ pub struct AuthorityRound { params: CommonParams, gas_limit_bound_divisor: U256, block_reward: U256, + registrar: Address, step_duration: Duration, builtins: BTreeMap, transition_service: IoService<()>, @@ -79,6 +83,8 @@ pub struct AuthorityRound { client: RwLock>>, signer: EngineSigner, validators: Box, + /// Is this Engine just for testing (prevents step calibration). + calibrate_step: bool, } fn header_step(header: &Header) -> Result { @@ -109,6 +115,7 @@ impl AuthorityRound { params: params, gas_limit_bound_divisor: our_params.gas_limit_bound_divisor, block_reward: our_params.block_reward, + registrar: our_params.registrar, step_duration: our_params.step_duration, builtins: builtins, transition_service: IoService::<()>::start()?, @@ -117,6 +124,7 @@ impl AuthorityRound { client: RwLock::new(None), signer: Default::default(), validators: new_validator_set(our_params.validators), + calibrate_step: our_params.start_step.is_none(), }); // Do not initialize timeouts for tests. if should_timeout { @@ -126,6 +134,12 @@ impl AuthorityRound { Ok(engine) } + fn calibrate_step(&self) { + if self.calibrate_step { + self.step.store((unix_now().as_secs() / self.step_duration.as_secs()) as usize, AtomicOrdering::SeqCst); + } + } + fn remaining_step_duration(&self) -> Duration { let now = unix_now(); let step_end = self.step_duration * (self.step.load(AtomicOrdering::SeqCst) as u32 + 1); @@ -136,12 +150,22 @@ impl AuthorityRound { } } - fn step_proposer(&self, step: usize) -> Address { - self.validators.get(step) + fn step_proposer(&self, bh: &H256, step: usize) -> Address { + self.validators.get(bh, step) } - fn is_step_proposer(&self, step: usize, address: &Address) -> bool { - self.step_proposer(step) == *address + fn is_step_proposer(&self, bh: &H256, step: usize, address: &Address) -> bool { + self.step_proposer(bh, step) == *address + } + + fn is_future_step(&self, step: usize) -> bool { + if step > self.step.load(AtomicOrdering::SeqCst) + 1 { + // Make absolutely sure that the step is correct. + self.calibrate_step(); + step > self.step.load(AtomicOrdering::SeqCst) + 1 + } else { + false + } } } @@ -176,11 +200,16 @@ impl IoHandler<()> for TransitionHandler { impl Engine for AuthorityRound { fn name(&self) -> &str { "AuthorityRound" } + fn version(&self) -> SemanticVersion { SemanticVersion::new(1, 0, 0) } + /// Two fields - consensus step and the corresponding proposer signature. fn seal_fields(&self) -> usize { 2 } fn params(&self) -> &CommonParams { &self.params } + + fn additional_params(&self) -> HashMap { hash_map!["registrar".to_owned() => self.registrar.hex()] } + fn builtins(&self) -> &BTreeMap { &self.builtins } fn step(&self) { @@ -221,7 +250,7 @@ impl Engine for AuthorityRound { } fn seals_internally(&self) -> Option { - Some(self.validators.contains(&self.signer.address())) + Some(self.signer.address() != Address::default()) } /// Attempt to seal the block internally. @@ -232,7 +261,7 @@ impl Engine for AuthorityRound { if self.proposed.load(AtomicOrdering::SeqCst) { return Seal::None; } let header = block.header(); let step = self.step.load(AtomicOrdering::SeqCst); - if self.is_step_proposer(step, header.author()) { + if self.is_step_proposer(header.parent_hash(), step, header.author()) { if let Ok(signature) = self.signer.sign(header.bare_hash()) { trace!(target: "engine", "generate_seal: Issuing a block for step {}.", step); self.proposed.store(true, AtomicOrdering::SeqCst); @@ -271,32 +300,32 @@ impl Engine for AuthorityRound { } } - /// Check if the signature belongs to the correct proposer. - fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { - let header_step = header_step(header)?; - // Give one step slack if step is lagging, double vote is still not possible. - if header_step <= self.step.load(AtomicOrdering::SeqCst) + 1 { - let proposer_signature = header_signature(header)?; - let correct_proposer = self.step_proposer(header_step); - if verify_address(&correct_proposer, &proposer_signature, &header.bare_hash())? { + fn verify_block_unordered(&self, _header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { Ok(()) - } else { - trace!(target: "engine", "verify_block_unordered: bad proposer for step: {}", header_step); - Err(EngineError::NotProposer(Mismatch { expected: correct_proposer, found: header.author().clone() }))? - } - } else { + } + + /// Do the validator and gas limit validation. + fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> Result<(), Error> { + let step = header_step(header)?; + // Give one step slack if step is lagging, double vote is still not possible. + if self.is_future_step(step) { trace!(target: "engine", "verify_block_unordered: block from the future"); self.validators.report_benign(header.author()); Err(BlockError::InvalidSeal)? + } else { + let proposer_signature = header_signature(header)?; + let correct_proposer = self.step_proposer(header.parent_hash(), step); + if !verify_address(&correct_proposer, &proposer_signature, &header.bare_hash())? { + trace!(target: "engine", "verify_block_unordered: bad proposer for step: {}", step); + Err(EngineError::NotProposer(Mismatch { expected: correct_proposer, found: header.author().clone() }))? + } } - } - fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> Result<(), Error> { + // Do not calculate difficulty for genesis blocks. if header.number() == 0 { return Err(From::from(BlockError::RidiculousNumber(OutOfBounds { min: Some(1), max: None, found: header.number() }))); } - let step = header_step(header)?; // Check if parent is from a previous step. if step == header_step(parent)? { trace!(target: "engine", "Multiple blocks proposed for step {}.", step); @@ -384,7 +413,7 @@ mod tests { let mut header: Header = Header::default(); header.set_seal(vec![encode(&H520::default()).to_vec()]); - let verify_result = engine.verify_block_unordered(&header, None); + let verify_result = engine.verify_block_family(&header, &Default::default(), None); assert!(verify_result.is_err()); } @@ -422,10 +451,14 @@ mod tests { #[test] fn proposer_switching() { - let mut header: Header = Header::default(); let tap = AccountProvider::transient_provider(); let addr = tap.insert_account(Secret::from_slice(&"0".sha3()).unwrap(), "0").unwrap(); - + let mut parent_header: Header = Header::default(); + parent_header.set_seal(vec![encode(&0usize).to_vec()]); + parent_header.set_gas_limit(U256::from_str("222222").unwrap()); + let mut header: Header = Header::default(); + header.set_number(1); + header.set_gas_limit(U256::from_str("222222").unwrap()); header.set_author(addr); let engine = Spec::new_test_round().engine; @@ -434,17 +467,22 @@ mod tests { // Two validators. // Spec starts with step 2. header.set_seal(vec![encode(&2usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]); - assert!(engine.verify_block_seal(&header).is_err()); + assert!(engine.verify_block_family(&header, &parent_header, None).is_err()); header.set_seal(vec![encode(&1usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]); - assert!(engine.verify_block_seal(&header).is_ok()); + assert!(engine.verify_block_family(&header, &parent_header, None).is_ok()); } #[test] fn rejects_future_block() { - let mut header: Header = Header::default(); let tap = AccountProvider::transient_provider(); let addr = tap.insert_account(Secret::from_slice(&"0".sha3()).unwrap(), "0").unwrap(); + let mut parent_header: Header = Header::default(); + parent_header.set_seal(vec![encode(&0usize).to_vec()]); + parent_header.set_gas_limit(U256::from_str("222222").unwrap()); + let mut header: Header = Header::default(); + header.set_number(1); + header.set_gas_limit(U256::from_str("222222").unwrap()); header.set_author(addr); let engine = Spec::new_test_round().engine; @@ -453,8 +491,8 @@ mod tests { // Two validators. // Spec starts with step 2. header.set_seal(vec![encode(&1usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]); - assert!(engine.verify_block_seal(&header).is_ok()); + assert!(engine.verify_block_family(&header, &parent_header, None).is_ok()); header.set_seal(vec![encode(&5usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]); - assert!(engine.verify_block_seal(&header).is_err()); + assert!(engine.verify_block_family(&header, &parent_header, None).is_err()); } } diff --git a/ethcore/src/engines/basic_authority.rs b/ethcore/src/engines/basic_authority.rs index 50051bf7e..34b89b2d6 100644 --- a/ethcore/src/engines/basic_authority.rs +++ b/ethcore/src/engines/basic_authority.rs @@ -104,14 +104,14 @@ impl Engine for BasicAuthority { } fn seals_internally(&self) -> Option { - Some(self.validators.contains(&self.signer.address())) + Some(self.signer.address() != Address::default()) } /// Attempt to seal the block internally. fn generate_seal(&self, block: &ExecutedBlock) -> Seal { let header = block.header(); let author = header.author(); - if self.validators.contains(author) { + if self.validators.contains(header.parent_hash(), author) { // account should be pernamently unlocked, otherwise sealing will fail if let Ok(signature) = self.signer.sign(header.bare_hash()) { return Seal::Regular(vec![::rlp::encode(&(&H520::from(signature) as &[u8])).to_vec()]); @@ -133,20 +133,20 @@ impl Engine for BasicAuthority { Ok(()) } - fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> { - use rlp::{UntrustedRlp, View}; - - // check the signature is legit. - let sig = UntrustedRlp::new(&header.seal()[0]).as_val::()?; - let signer = public_to_address(&recover(&sig.into(), &header.bare_hash())?); - if !self.validators.contains(&signer) { - return Err(BlockError::InvalidSeal)?; - } + fn verify_block_unordered(&self, _header: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> { Ok(()) } fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> { - // we should not calculate difficulty for genesis blocks + use rlp::{UntrustedRlp, View}; + // Check if the signature belongs to a validator, can depend on parent state. + let sig = UntrustedRlp::new(&header.seal()[0]).as_val::()?; + let signer = public_to_address(&recover(&sig.into(), &header.bare_hash())?); + if !self.validators.contains(header.parent_hash(), &signer) { + return Err(BlockError::InvalidSeal)?; + } + + // Do not calculate difficulty for genesis blocks. if header.number() == 0 { return Err(From::from(BlockError::RidiculousNumber(OutOfBounds { min: Some(1), max: None, found: header.number() }))); } @@ -239,7 +239,7 @@ mod tests { let mut header: Header = Header::default(); header.set_seal(vec![::rlp::encode(&H520::default()).to_vec()]); - let verify_result = engine.verify_block_unordered(&header, None); + let verify_result = engine.verify_block_family(&header, &Default::default(), None); assert!(verify_result.is_err()); } diff --git a/ethcore/src/engines/instant_seal.rs b/ethcore/src/engines/instant_seal.rs index d2deea702..45bede9f4 100644 --- a/ethcore/src/engines/instant_seal.rs +++ b/ethcore/src/engines/instant_seal.rs @@ -15,7 +15,7 @@ // along with Parity. If not, see . use std::collections::BTreeMap; -use util::Address; +use util::{Address, HashMap}; use builtin::Builtin; use engines::{Engine, Seal}; use env_info::EnvInfo; @@ -26,14 +26,16 @@ use block::ExecutedBlock; /// An engine which does not provide any consensus mechanism, just seals blocks internally. pub struct InstantSeal { params: CommonParams, + registrar: Address, builtins: BTreeMap, } impl InstantSeal { /// Returns new instance of InstantSeal with default VM Factory - pub fn new(params: CommonParams, builtins: BTreeMap) -> Self { + pub fn new(params: CommonParams, registrar: Address, builtins: BTreeMap) -> Self { InstantSeal { params: params, + registrar: registrar, builtins: builtins, } } @@ -48,6 +50,10 @@ impl Engine for InstantSeal { &self.params } + fn additional_params(&self) -> HashMap { + hash_map!["registrar".to_owned() => self.registrar.hex()] + } + fn builtins(&self) -> &BTreeMap { &self.builtins } @@ -76,9 +82,9 @@ mod tests { fn instant_can_seal() { let spec = Spec::new_instant(); let engine = &*spec.engine; - let genesis_header = spec.genesis_header(); let mut db_result = get_temp_state_db(); let db = spec.ensure_db_good(db_result.take(), &Default::default()).unwrap(); + let genesis_header = spec.genesis_header(); let last_hashes = Arc::new(vec![genesis_header.hash()]); let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, Address::default(), (3141562.into(), 31415620.into()), vec![]).unwrap(); let b = b.close_and_lock(); diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index 2320f49a0..2cc1ff21f 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -166,7 +166,9 @@ pub trait Engine : Sync + Send { } /// The network ID that transactions should be signed with. - fn signing_network_id(&self, _env_info: &EnvInfo) -> Option { None } + fn signing_network_id(&self, _env_info: &EnvInfo) -> Option { + Some(self.params().chain_id) + } /// Verify the seal of a block. This is an auxilliary method that actually just calls other `verify_` methods /// to get the job done. By default it must pass `verify_basic` and `verify_block_unordered`. If more or fewer diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 1c9edd33b..aac101447 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -78,6 +78,7 @@ pub struct Tendermint { step_service: IoService, client: RwLock>>, block_reward: U256, + registrar: Address, /// Blockchain height. height: AtomicUsize, /// Consensus view. @@ -94,6 +95,8 @@ pub struct Tendermint { last_lock: AtomicUsize, /// Bare hash of the proposed block, used for seal submission. proposal: RwLock>, + /// Hash of the proposal parent block. + proposal_parent: RwLock, /// Set used to determine the current validators. validators: Box, } @@ -109,14 +112,16 @@ impl Tendermint { client: RwLock::new(None), step_service: IoService::::start()?, block_reward: our_params.block_reward, + registrar: our_params.registrar, height: AtomicUsize::new(1), view: AtomicUsize::new(0), step: RwLock::new(Step::Propose), - votes: VoteCollector::default(), + votes: Default::default(), signer: Default::default(), lock_change: RwLock::new(None), last_lock: AtomicUsize::new(0), proposal: RwLock::new(None), + proposal_parent: Default::default(), validators: new_validator_set(our_params.validators), }); let handler = TransitionHandler::new(Arc::downgrade(&engine) as Weak, Box::new(our_params.timeouts)); @@ -230,7 +235,7 @@ impl Tendermint { let height = self.height.load(AtomicOrdering::SeqCst); if let Some(block_hash) = *self.proposal.read() { // Generate seal and remove old votes. - if self.is_signer_proposer() { + if self.is_signer_proposer(&*self.proposal_parent.read()) { let proposal_step = VoteStep::new(height, view, Step::Propose); let precommit_step = VoteStep::new(proposal_step.height, proposal_step.view, Step::Precommit); if let Some(seal) = self.votes.seal_signatures(proposal_step, precommit_step, &block_hash) { @@ -252,23 +257,23 @@ impl Tendermint { } fn is_authority(&self, address: &Address) -> bool { - self.validators.contains(address) + self.validators.contains(&*self.proposal_parent.read(), address) } fn is_above_threshold(&self, n: usize) -> bool { - n > self.validators.count() * 2/3 + n > self.validators.count(&*self.proposal_parent.read()) * 2/3 } /// Find the designated for the given view. - fn view_proposer(&self, height: Height, view: View) -> Address { + fn view_proposer(&self, bh: &H256, height: Height, view: View) -> Address { let proposer_nonce = height + view; trace!(target: "engine", "Proposer nonce: {}", proposer_nonce); - self.validators.get(proposer_nonce) + self.validators.get(bh, proposer_nonce) } /// Check if address is a proposer for given view. - fn is_view_proposer(&self, height: Height, view: View, address: &Address) -> Result<(), EngineError> { - let proposer = self.view_proposer(height, view); + fn is_view_proposer(&self, bh: &H256, height: Height, view: View, address: &Address) -> Result<(), EngineError> { + let proposer = self.view_proposer(bh, height, view); if proposer == *address { Ok(()) } else { @@ -277,8 +282,8 @@ impl Tendermint { } /// Check if current signer is the current proposer. - fn is_signer_proposer(&self) -> bool { - let proposer = self.view_proposer(self.height.load(AtomicOrdering::SeqCst), self.view.load(AtomicOrdering::SeqCst)); + fn is_signer_proposer(&self, bh: &H256) -> bool { + let proposer = self.view_proposer(bh, self.height.load(AtomicOrdering::SeqCst), self.view.load(AtomicOrdering::SeqCst)); self.signer.is_address(&proposer) } @@ -369,14 +374,20 @@ impl Tendermint { impl Engine for Tendermint { fn name(&self) -> &str { "Tendermint" } + fn version(&self) -> SemanticVersion { SemanticVersion::new(1, 0, 0) } + /// (consensus view, proposal signature, authority signatures) fn seal_fields(&self) -> usize { 3 } fn params(&self) -> &CommonParams { &self.params } + + fn additional_params(&self) -> HashMap { hash_map!["registrar".to_owned() => self.registrar.hex()] } + fn builtins(&self) -> &BTreeMap { &self.builtins } fn maximum_uncle_count(&self) -> usize { 0 } + fn maximum_uncle_age(&self) -> usize { 0 } /// Additional engine-specific information for the user/developer concerning `header`. @@ -411,7 +422,7 @@ impl Engine for Tendermint { /// Should this node participate. fn seals_internally(&self) -> Option { - Some(self.is_authority(&self.signer.address())) + Some(self.signer.address() != Address::default()) } /// Attempt to seal generate a proposal seal. @@ -419,7 +430,7 @@ impl Engine for Tendermint { let header = block.header(); let author = header.author(); // Only proposer can generate seal if None was generated. - if !self.is_signer_proposer() || self.proposal.read().is_some() { + if !self.is_signer_proposer(header.parent_hash()) || self.proposal.read().is_some() { return Seal::None; } @@ -433,6 +444,7 @@ impl Engine for Tendermint { self.votes.vote(ConsensusMessage::new(signature, height, view, Step::Propose, bh), author); // Remember proposal for later seal submission. *self.proposal.write() = bh; + *self.proposal_parent.write() = header.parent_hash().clone(); Seal::Proposal(vec![ ::rlp::encode(&view).to_vec(), ::rlp::encode(&signature).to_vec(), @@ -497,7 +509,12 @@ impl Engine for Tendermint { } - fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { + fn verify_block_unordered(&self, _header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { + Ok(()) + } + + /// Verify validators and gas limit. + fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> Result<(), Error> { let proposal = ConsensusMessage::new_proposal(header)?; let proposer = proposal.verify()?; if !self.is_authority(&proposer) { @@ -514,7 +531,7 @@ impl Engine for Tendermint { Some(a) => a, None => public_to_address(&recover(&precommit.signature.into(), &precommit_hash)?), }; - if !self.validators.contains(&address) { + if !self.validators.contains(header.parent_hash(), &address) { Err(EngineError::NotAuthorized(address.to_owned()))? } @@ -537,12 +554,9 @@ impl Engine for Tendermint { found: signatures_len }))?; } - self.is_view_proposer(proposal.vote_step.height, proposal.vote_step.view, &proposer)?; + self.is_view_proposer(header.parent_hash(), proposal.vote_step.height, proposal.vote_step.view, &proposer)?; } - Ok(()) - } - fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> Result<(), Error> { if header.number() == 0 { Err(BlockError::RidiculousNumber(OutOfBounds { min: Some(1), max: None, found: header.number() }))?; } @@ -587,6 +601,7 @@ impl Engine for Tendermint { debug!(target: "engine", "Received a new proposal {:?} from {}.", proposal.vote_step, proposer); if self.is_view(&proposal) { *self.proposal.write() = proposal.block_hash.clone(); + *self.proposal_parent.write() = header.parent_hash().clone(); } self.votes.vote(proposal, &proposer); true @@ -599,7 +614,7 @@ impl Engine for Tendermint { trace!(target: "engine", "Propose timeout."); if self.proposal.read().is_none() { // Report the proposer if no proposal was received. - let current_proposer = self.view_proposer(self.height.load(AtomicOrdering::SeqCst), self.view.load(AtomicOrdering::SeqCst)); + let current_proposer = self.view_proposer(&*self.proposal_parent.read(), self.height.load(AtomicOrdering::SeqCst), self.view.load(AtomicOrdering::SeqCst)); self.validators.report_benign(¤t_proposer); } Step::Prevote @@ -757,20 +772,25 @@ mod tests { let (spec, tap) = setup(); let engine = spec.engine; - let mut header = Header::default(); - let validator = insert_and_unlock(&tap, "0"); - header.set_author(validator); - let seal = proposal_seal(&tap, &header, 0); - header.set_seal(seal); - // Good proposer. - assert!(engine.verify_block_unordered(&header.clone(), None).is_ok()); + let mut parent_header: Header = Header::default(); + parent_header.set_gas_limit(U256::from_str("222222").unwrap()); + let mut header = Header::default(); + header.set_number(1); + header.set_gas_limit(U256::from_str("222222").unwrap()); let validator = insert_and_unlock(&tap, "1"); header.set_author(validator); let seal = proposal_seal(&tap, &header, 0); header.set_seal(seal); + // Good proposer. + assert!(engine.verify_block_family(&header, &parent_header, None).is_ok()); + + let validator = insert_and_unlock(&tap, "0"); + header.set_author(validator); + let seal = proposal_seal(&tap, &header, 0); + header.set_seal(seal); // Bad proposer. - match engine.verify_block_unordered(&header, None) { + match engine.verify_block_family(&header, &parent_header, None) { Err(Error::Engine(EngineError::NotProposer(_))) => {}, _ => panic!(), } @@ -780,7 +800,7 @@ mod tests { let seal = proposal_seal(&tap, &header, 0); header.set_seal(seal); // Not authority. - match engine.verify_block_unordered(&header, None) { + match engine.verify_block_family(&header, &parent_header, None) { Err(Error::Engine(EngineError::NotAuthorized(_))) => {}, _ => panic!(), }; @@ -792,19 +812,24 @@ mod tests { let (spec, tap) = setup(); let engine = spec.engine; + let mut parent_header: Header = Header::default(); + parent_header.set_gas_limit(U256::from_str("222222").unwrap()); + let mut header = Header::default(); + header.set_number(2); + header.set_gas_limit(U256::from_str("222222").unwrap()); let proposer = insert_and_unlock(&tap, "1"); header.set_author(proposer); let mut seal = proposal_seal(&tap, &header, 0); - let vote_info = message_info_rlp(&VoteStep::new(0, 0, Step::Precommit), Some(header.bare_hash())); + let vote_info = message_info_rlp(&VoteStep::new(2, 0, Step::Precommit), Some(header.bare_hash())); let signature1 = tap.sign(proposer, None, vote_info.sha3()).unwrap(); seal[2] = ::rlp::encode(&vec![H520::from(signature1.clone())]).to_vec(); header.set_seal(seal.clone()); // One good signature is not enough. - match engine.verify_block_unordered(&header, None) { + match engine.verify_block_family(&header, &parent_header, None) { Err(Error::Engine(EngineError::BadSealFieldSize(_))) => {}, _ => panic!(), } @@ -815,7 +840,7 @@ mod tests { seal[2] = ::rlp::encode(&vec![H520::from(signature1.clone()), H520::from(signature0.clone())]).to_vec(); header.set_seal(seal.clone()); - assert!(engine.verify_block_unordered(&header, None).is_ok()); + assert!(engine.verify_block_family(&header, &parent_header, None).is_ok()); let bad_voter = insert_and_unlock(&tap, "101"); let bad_signature = tap.sign(bad_voter, None, vote_info.sha3()).unwrap(); @@ -824,7 +849,7 @@ mod tests { header.set_seal(seal); // One good and one bad signature. - match engine.verify_block_unordered(&header, None) { + match engine.verify_block_family(&header, &parent_header, None) { Err(Error::Engine(EngineError::NotAuthorized(_))) => {}, _ => panic!(), }; diff --git a/ethcore/src/engines/tendermint/params.rs b/ethcore/src/engines/tendermint/params.rs index caff8cee8..e38013188 100644 --- a/ethcore/src/engines/tendermint/params.rs +++ b/ethcore/src/engines/tendermint/params.rs @@ -17,7 +17,7 @@ //! Tendermint specific parameters. use ethjson; -use util::{U256, Uint}; +use util::{U256, Uint, Address, FixedHash}; use time::Duration; use super::super::transition::Timeouts; use super::Step; @@ -33,6 +33,8 @@ pub struct TendermintParams { pub timeouts: TendermintTimeouts, /// Block reward. pub block_reward: U256, + /// Namereg contract address. + pub registrar: Address, } /// Base timeout of each step in ms. @@ -88,6 +90,7 @@ impl From for TendermintParams { commit: p.timeout_commit.map_or(dt.commit, to_duration), }, block_reward: p.block_reward.map_or_else(U256::zero, Into::into), + registrar: p.registrar.map_or_else(Address::new, Into::into), } } } diff --git a/ethcore/src/engines/validator_set/contract.rs b/ethcore/src/engines/validator_set/contract.rs index 81bd6b089..91fbf5fab 100644 --- a/ethcore/src/engines/validator_set/contract.rs +++ b/ethcore/src/engines/validator_set/contract.rs @@ -26,30 +26,30 @@ use super::safe_contract::ValidatorSafeContract; /// The validator contract should have the following interface: /// [{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"","type":"address[]"}],"payable":false,"type":"function"}] pub struct ValidatorContract { - validators: Arc, + validators: ValidatorSafeContract, provider: RwLock>, } impl ValidatorContract { pub fn new(contract_address: Address) -> Self { ValidatorContract { - validators: Arc::new(ValidatorSafeContract::new(contract_address)), + validators: ValidatorSafeContract::new(contract_address), provider: RwLock::new(None), } } } -impl ValidatorSet for Arc { - fn contains(&self, address: &Address) -> bool { - self.validators.contains(address) +impl ValidatorSet for ValidatorContract { + fn contains(&self, bh: &H256, address: &Address) -> bool { + self.validators.contains(bh, address) } - fn get(&self, nonce: usize) -> Address { - self.validators.get(nonce) + fn get(&self, bh: &H256, nonce: usize) -> Address { + self.validators.get(bh, nonce) } - fn count(&self) -> usize { - self.validators.count() + fn count(&self, bh: &H256) -> usize { + self.validators.count(bh) } fn report_malicious(&self, address: &Address) { @@ -144,6 +144,7 @@ mod tests { use header::Header; use account_provider::AccountProvider; use miner::MinerService; + use types::ids::BlockId; use client::BlockChainClient; use tests::helpers::generate_dummy_client_with_spec_and_accounts; use super::super::ValidatorSet; @@ -154,8 +155,9 @@ mod tests { let client = generate_dummy_client_with_spec_and_accounts(Spec::new_validator_contract, None); let vc = Arc::new(ValidatorContract::new(Address::from_str("0000000000000000000000000000000000000005").unwrap())); vc.register_contract(Arc::downgrade(&client)); - assert!(vc.contains(&Address::from_str("7d577a597b2742b498cb5cf0c26cdcd726d39e6e").unwrap())); - assert!(vc.contains(&Address::from_str("82a978b3f5962a5b0957d9ee9eef472ee55b42f1").unwrap())); + let last_hash = client.best_block_header().hash(); + assert!(vc.contains(&last_hash, &Address::from_str("7d577a597b2742b498cb5cf0c26cdcd726d39e6e").unwrap())); + assert!(vc.contains(&last_hash, &Address::from_str("82a978b3f5962a5b0957d9ee9eef472ee55b42f1").unwrap())); } #[test] @@ -171,18 +173,21 @@ mod tests { client.miner().set_engine_signer(v1, "".into()).unwrap(); let mut header = Header::default(); - let seal = encode(&vec!(5u8)).to_vec(); - header.set_seal(vec!(seal)); + let seal = vec![encode(&5u8).to_vec(), encode(&(&H520::default() as &[u8])).to_vec()]; + header.set_seal(seal); header.set_author(v1); - header.set_number(1); + header.set_number(2); + header.set_parent_hash(client.chain_info().best_block_hash); + // `reportBenign` when the designated proposer releases block from the future (bad clock). - assert!(client.engine().verify_block_unordered(&header, None).is_err()); + assert!(client.engine().verify_block_family(&header, &header, None).is_err()); // Seal a block. client.engine().step(); assert_eq!(client.chain_info().best_block_number, 1); // Check if the unresponsive validator is `disliked`. - assert_eq!(client.call_contract(validator_contract, "d8f2e0bf".from_hex().unwrap()).unwrap().to_hex(), "0000000000000000000000007d577a597b2742b498cb5cf0c26cdcd726d39e6e"); + assert_eq!(client.call_contract(BlockId::Latest, validator_contract, "d8f2e0bf".from_hex().unwrap()).unwrap().to_hex(), "0000000000000000000000007d577a597b2742b498cb5cf0c26cdcd726d39e6e"); // Simulate a misbehaving validator by handling a double proposal. + let header = client.best_block_header().decode(); assert!(client.engine().verify_block_family(&header, &header, None).is_err()); // Seal a block. client.engine().step(); diff --git a/ethcore/src/engines/validator_set/mod.rs b/ethcore/src/engines/validator_set/mod.rs index 43a6f71d1..3e86c357f 100644 --- a/ethcore/src/engines/validator_set/mod.rs +++ b/ethcore/src/engines/validator_set/mod.rs @@ -21,7 +21,7 @@ mod safe_contract; mod contract; use std::sync::Weak; -use util::{Address, Arc}; +use util::{Address, H256}; use ethjson::spec::ValidatorSet as ValidatorSpec; use client::Client; use self::simple_list::SimpleList; @@ -32,18 +32,18 @@ use self::safe_contract::ValidatorSafeContract; pub fn new_validator_set(spec: ValidatorSpec) -> Box { match spec { ValidatorSpec::List(list) => Box::new(SimpleList::new(list.into_iter().map(Into::into).collect())), - ValidatorSpec::SafeContract(address) => Box::new(Arc::new(ValidatorSafeContract::new(address.into()))), - ValidatorSpec::Contract(address) => Box::new(Arc::new(ValidatorContract::new(address.into()))), + ValidatorSpec::SafeContract(address) => Box::new(ValidatorSafeContract::new(address.into())), + ValidatorSpec::Contract(address) => Box::new(ValidatorContract::new(address.into())), } } pub trait ValidatorSet { /// Checks if a given address is a validator. - fn contains(&self, address: &Address) -> bool; + fn contains(&self, bh: &H256, address: &Address) -> bool; /// Draws an validator nonce modulo number of validators. - fn get(&self, nonce: usize) -> Address; + fn get(&self, bh: &H256, nonce: usize) -> Address; /// Returns the current number of validators. - fn count(&self) -> usize; + fn count(&self, bh: &H256) -> usize; /// Notifies about malicious behaviour. fn report_malicious(&self, _validator: &Address) {} /// Notifies about benign misbehaviour. diff --git a/ethcore/src/engines/validator_set/safe_contract.rs b/ethcore/src/engines/validator_set/safe_contract.rs index 1a068d858..0a0eaecfd 100644 --- a/ethcore/src/engines/validator_set/safe_contract.rs +++ b/ethcore/src/engines/validator_set/safe_contract.rs @@ -17,17 +17,23 @@ /// Validator set maintained in a contract, updated using `getValidators` method. use std::sync::Weak; +use ethabi; use util::*; +use util::cache::MemoryLruCache; +use types::ids::BlockId; use client::{Client, BlockChainClient}; -use client::chain_notify::ChainNotify; use super::ValidatorSet; use super::simple_list::SimpleList; +const MEMOIZE_CAPACITY: usize = 500; +const CONTRACT_INTERFACE: &'static [u8] = b"[{\"constant\":true,\"inputs\":[],\"name\":\"getValidators\",\"outputs\":[{\"name\":\"\",\"type\":\"address[]\"}],\"payable\":false,\"type\":\"function\"}]"; +const GET_VALIDATORS: &'static str = "getValidators"; + /// The validator contract should have the following interface: /// [{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"","type":"address[]"}],"payable":false,"type":"function"}] pub struct ValidatorSafeContract { pub address: Address, - validators: RwLock, + validators: RwLock>, provider: RwLock>, } @@ -35,102 +41,127 @@ impl ValidatorSafeContract { pub fn new(contract_address: Address) -> Self { ValidatorSafeContract { address: contract_address, - validators: Default::default(), + validators: RwLock::new(MemoryLruCache::new(MEMOIZE_CAPACITY)), provider: RwLock::new(None), } } - /// Queries the state and updates the set of validators. - pub fn update(&self) { + /// Queries the state and gets the set of validators. + fn get_list(&self, block_hash: H256) -> Option { if let Some(ref provider) = *self.provider.read() { - match provider.get_validators() { + match provider.get_validators(BlockId::Hash(block_hash)) { Ok(new) => { debug!(target: "engine", "Set of validators obtained: {:?}", new); - *self.validators.write() = SimpleList::new(new); + Some(SimpleList::new(new)) + }, + Err(s) => { + debug!(target: "engine", "Set of validators could not be updated: {}", s); + None }, - Err(s) => warn!(target: "engine", "Set of validators could not be updated: {}", s), } } else { - warn!(target: "engine", "Set of validators could not be updated: no provider contract.") + warn!(target: "engine", "Set of validators could not be updated: no provider contract."); + None } } } -/// Checks validators on every block. -impl ChainNotify for ValidatorSafeContract { - fn new_blocks( - &self, - _: Vec, - _: Vec, - enacted: Vec, - _: Vec, - _: Vec, - _: Vec, - _duration: u64) { - if !enacted.is_empty() { - self.update(); - } - } -} - -impl ValidatorSet for Arc { - fn contains(&self, address: &Address) -> bool { - self.validators.read().contains(address) +impl ValidatorSet for ValidatorSafeContract { + fn contains(&self, block_hash: &H256, address: &Address) -> bool { + let mut guard = self.validators.write(); + let maybe_existing = guard + .get_mut(block_hash) + .map(|list| list.contains(block_hash, address)); + maybe_existing + .unwrap_or_else(|| self + .get_list(block_hash.clone()) + .map_or(false, |list| { + let contains = list.contains(block_hash, address); + guard.insert(block_hash.clone(), list); + contains + })) } - fn get(&self, nonce: usize) -> Address { - self.validators.read().get(nonce) + fn get(&self, block_hash: &H256, nonce: usize) -> Address { + let mut guard = self.validators.write(); + let maybe_existing = guard + .get_mut(block_hash) + .map(|list| list.get(block_hash, nonce)); + maybe_existing + .unwrap_or_else(|| self + .get_list(block_hash.clone()) + .map_or_else(Default::default, |list| { + let address = list.get(block_hash, nonce); + guard.insert(block_hash.clone(), list); + address + })) } - fn count(&self) -> usize { - self.validators.read().count() + fn count(&self, block_hash: &H256) -> usize { + let mut guard = self.validators.write(); + let maybe_existing = guard + .get_mut(block_hash) + .map(|list| list.count(block_hash)); + maybe_existing + .unwrap_or_else(|| self + .get_list(block_hash.clone()) + .map_or_else(usize::max_value, |list| { + let address = list.count(block_hash); + guard.insert(block_hash.clone(), list); + address + })) } fn register_contract(&self, client: Weak) { - if let Some(c) = client.upgrade() { - c.add_notify(self.clone()); - } - { - *self.provider.write() = Some(provider::Contract::new(self.address, move |a, d| client.upgrade().ok_or("No client!".into()).and_then(|c| c.call_contract(a, d)))); - } - self.update(); + trace!(target: "engine", "Setting up contract caller."); + let contract = ethabi::Contract::new(ethabi::Interface::load(CONTRACT_INTERFACE).expect("JSON interface is valid; qed")); + let call = contract.function(GET_VALIDATORS.into()).expect("Method name is valid; qed"); + let data = call.encode_call(vec![]).expect("get_validators does not take any arguments; qed"); + let contract_address = self.address.clone(); + let do_call = move |id| client + .upgrade() + .ok_or("No client!".into()) + .and_then(|c| c.call_contract(id, contract_address.clone(), data.clone())) + .map(|raw_output| call.decode_output(raw_output).expect("ethabi is correct; qed")); + *self.provider.write() = Some(provider::Contract::new(do_call)); } } mod provider { - // Autogenerated from JSON contract definition using Rust contract convertor. - #![allow(unused_imports)] use std::string::String; use std::result::Result; - use std::fmt; use {util, ethabi}; - use util::{FixedHash, Uint}; + use types::ids::BlockId; pub struct Contract { - contract: ethabi::Contract, - address: util::Address, - do_call: Box) -> Result, String> + Send + Sync + 'static>, + do_call: Box Result, String> + Send + Sync + 'static>, } + impl Contract { - pub fn new(address: util::Address, do_call: F) -> Self where F: Fn(util::Address, Vec) -> Result, String> + Send + Sync + 'static { + pub fn new(do_call: F) -> Self where F: Fn(BlockId) -> Result, String> + Send + Sync + 'static { Contract { - contract: ethabi::Contract::new(ethabi::Interface::load(b"[{\"constant\":true,\"inputs\":[],\"name\":\"getValidators\",\"outputs\":[{\"name\":\"\",\"type\":\"address[]\"}],\"payable\":false,\"type\":\"function\"}]").expect("JSON is autogenerated; qed")), - address: address, do_call: Box::new(do_call), } } - fn as_string(e: T) -> String { format!("{:?}", e) } - - /// Auto-generated from: `{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"","type":"address[]"}],"payable":false,"type":"function"}` - #[allow(dead_code)] - pub fn get_validators(&self) -> Result, String> { - let call = self.contract.function("getValidators".into()).map_err(Self::as_string)?; - let data = call.encode_call( - vec![] - ).map_err(Self::as_string)?; - let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; - let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_array().and_then(|v| v.into_iter().map(|a| a.to_address()).collect::>>()).ok_or("Invalid type returned")?; r.into_iter().map(|a| util::Address::from(a)).collect::>() })) + + /// Gets validators from contract with interface: `{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"","type":"address[]"}],"payable":false,"type":"function"}` + pub fn get_validators(&self, id: BlockId) -> Result, String> { + Ok((self.do_call)(id)? + .into_iter() + .rev() + .collect::>() + .pop() + .expect("get_validators returns one argument; qed") + .to_array() + .and_then(|v| v + .into_iter() + .map(|a| a.to_address()) + .collect::>>()) + .expect("get_validators returns a list of addresses; qed") + .into_iter() + .map(util::Address::from) + .collect::>() + ) } } } @@ -138,13 +169,14 @@ mod provider { #[cfg(test)] mod tests { use util::*; + use types::ids::BlockId; use spec::Spec; use account_provider::AccountProvider; use transaction::{Transaction, Action}; use client::{BlockChainClient, EngineClient}; use ethkey::Secret; use miner::MinerService; - use tests::helpers::generate_dummy_client_with_spec_and_accounts; + use tests::helpers::{generate_dummy_client_with_spec_and_accounts, generate_dummy_client_with_spec_and_data}; use super::super::ValidatorSet; use super::ValidatorSafeContract; @@ -153,12 +185,13 @@ mod tests { let client = generate_dummy_client_with_spec_and_accounts(Spec::new_validator_safe_contract, None); let vc = Arc::new(ValidatorSafeContract::new(Address::from_str("0000000000000000000000000000000000000005").unwrap())); vc.register_contract(Arc::downgrade(&client)); - assert!(vc.contains(&Address::from_str("7d577a597b2742b498cb5cf0c26cdcd726d39e6e").unwrap())); - assert!(vc.contains(&Address::from_str("82a978b3f5962a5b0957d9ee9eef472ee55b42f1").unwrap())); + let last_hash = client.best_block_header().hash(); + assert!(vc.contains(&last_hash, &Address::from_str("7d577a597b2742b498cb5cf0c26cdcd726d39e6e").unwrap())); + assert!(vc.contains(&last_hash, &Address::from_str("82a978b3f5962a5b0957d9ee9eef472ee55b42f1").unwrap())); } #[test] - fn updates_validators() { + fn knows_validators() { let tap = Arc::new(AccountProvider::transient_provider()); let s0 = Secret::from_slice(&"1".sha3()).unwrap(); let v0 = tap.insert_account(s0.clone(), "").unwrap(); @@ -212,5 +245,14 @@ mod tests { client.update_sealing(); // Able to seal again. assert_eq!(client.chain_info().best_block_number, 3); + + // Check syncing. + let sync_client = generate_dummy_client_with_spec_and_data(Spec::new_validator_safe_contract, 0, 0, &[]); + sync_client.engine().register_client(Arc::downgrade(&sync_client)); + for i in 1..4 { + sync_client.import_block(client.block(BlockId::Number(i)).unwrap().into_inner()).unwrap(); + } + sync_client.flush_queue(); + assert_eq!(sync_client.chain_info().best_block_number, 3); } } diff --git a/ethcore/src/engines/validator_set/simple_list.rs b/ethcore/src/engines/validator_set/simple_list.rs index 3ba574b5a..2d7687979 100644 --- a/ethcore/src/engines/validator_set/simple_list.rs +++ b/ethcore/src/engines/validator_set/simple_list.rs @@ -16,7 +16,7 @@ /// Preconfigured validator list. -use util::Address; +use util::{H256, Address, HeapSizeOf}; use super::ValidatorSet; #[derive(Debug, PartialEq, Eq, Default)] @@ -34,16 +34,22 @@ impl SimpleList { } } +impl HeapSizeOf for SimpleList { + fn heap_size_of_children(&self) -> usize { + self.validators.heap_size_of_children() + self.validator_n.heap_size_of_children() + } +} + impl ValidatorSet for SimpleList { - fn contains(&self, address: &Address) -> bool { + fn contains(&self, _bh: &H256, address: &Address) -> bool { self.validators.contains(address) } - fn get(&self, nonce: usize) -> Address { + fn get(&self, _bh: &H256, nonce: usize) -> Address { self.validators.get(nonce % self.validator_n).expect("There are validator_n authorities; taking number modulo validator_n gives number in validator_n range; qed").clone() } - fn count(&self) -> usize { + fn count(&self, _bh: &H256) -> usize { self.validator_n } } @@ -60,9 +66,9 @@ mod tests { let a1 = Address::from_str("cd1722f3947def4cf144679da39c4c32bdc35681").unwrap(); let a2 = Address::from_str("0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6").unwrap(); let list = SimpleList::new(vec![a1.clone(), a2.clone()]); - assert!(list.contains(&a1)); - assert_eq!(list.get(0), a1); - assert_eq!(list.get(1), a2); - assert_eq!(list.get(2), a1); + assert!(list.contains(&Default::default(), &a1)); + assert_eq!(list.get(&Default::default(), 0), a1); + assert_eq!(list.get(&Default::default(), 1), a2); + assert_eq!(list.get(&Default::default(), 2), a1); } } diff --git a/ethcore/src/ethereum/mod.rs b/ethcore/src/ethereum/mod.rs index b15c9e4de..ce8b84b31 100644 --- a/ethcore/src/ethereum/mod.rs +++ b/ethcore/src/ethereum/mod.rs @@ -30,46 +30,52 @@ pub use self::denominations::*; use super::spec::*; /// Most recent fork block that we support on Mainnet. -pub const FORK_SUPPORTED_FRONTIER: u64 = 2675000; +pub const FORK_SUPPORTED_FOUNDATION: u64 = 2675000; /// Most recent fork block that we support on Ropsten. pub const FORK_SUPPORTED_ROPSTEN: u64 = 10; +/// Most recent fork block that we support on Kovan. +pub const FORK_SUPPORTED_KOVAN: u64 = 0; + fn load(b: &[u8]) -> Spec { Spec::load(b).expect("chain spec is invalid") } -/// Create a new Olympic chain spec. +/// Create a new Foundation Olympic chain spec. pub fn new_olympic() -> Spec { load(include_bytes!("../../res/ethereum/olympic.json")) } -/// Create a new Frontier mainnet chain spec. -pub fn new_frontier() -> Spec { load(include_bytes!("../../res/ethereum/frontier.json")) } +/// Create a new Foundation Mainnet chain spec. +pub fn new_foundation() -> Spec { load(include_bytes!("../../res/ethereum/foundation.json")) } -/// Create a new Frontier mainnet chain spec without the DAO hardfork. +/// Create a new Classic Mainnet chain spec without the DAO hardfork. pub fn new_classic() -> Spec { load(include_bytes!("../../res/ethereum/classic.json")) } -/// Create a new Frontier mainnet chain spec without the DAO hardfork. +/// Create a new Expanse mainnet chain spec. pub fn new_expanse() -> Spec { load(include_bytes!("../../res/ethereum/expanse.json")) } -/// Create a new Frontier chain spec as though it never changes to Homestead. +/// Create a new Kovan testnet chain spec. +pub fn new_kovan() -> Spec { load(include_bytes!("../../res/ethereum/kovan.json")) } + +/// Create a new Foundation Frontier-era chain spec as though it never changes to Homestead. pub fn new_frontier_test() -> Spec { load(include_bytes!("../../res/ethereum/frontier_test.json")) } -/// Create a new Homestead chain spec as though it never changed from Frontier. +/// Create a new Foundation Homestead-era chain spec as though it never changed from Frontier. pub fn new_homestead_test() -> Spec { load(include_bytes!("../../res/ethereum/homestead_test.json")) } -/// Create a new Homestead-EIP150 chain spec as though it never changed from Homestead/Frontier. +/// Create a new Foundation Homestead-EIP150-era chain spec as though it never changed from Homestead/Frontier. pub fn new_eip150_test() -> Spec { load(include_bytes!("../../res/ethereum/eip150_test.json")) } -/// Create a new Homestead-EIP150 chain spec as though it never changed from Homestead/Frontier. +/// Create a new Foundation Homestead-EIP161-era chain spec as though it never changed from Homestead/Frontier. pub fn new_eip161_test() -> Spec { load(include_bytes!("../../res/ethereum/eip161_test.json")) } -/// Create a new Frontier/Homestead/DAO chain spec with transition points at #5 and #8. +/// Create a new Foundation Frontier/Homestead/DAO chain spec with transition points at #5 and #8. pub fn new_transition_test() -> Spec { load(include_bytes!("../../res/ethereum/transition_test.json")) } -/// Create a new Frontier main net chain spec without genesis accounts. +/// Create a new Foundation Mainnet chain spec without genesis accounts. pub fn new_mainnet_like() -> Spec { load(include_bytes!("../../res/ethereum/frontier_like_test.json")) } -/// Create a new Ropsten chain spec. +/// Create a new Foundation Ropsten chain spec. pub fn new_ropsten() -> Spec { load(include_bytes!("../../res/ethereum/ropsten.json")) } /// Create a new Morden chain spec. @@ -112,7 +118,7 @@ mod tests { #[test] fn frontier() { - let frontier = new_frontier(); + let frontier = new_foundation(); assert_eq!(frontier.state_root(), "d7f8974fb5ac78d9ac099b9ad5018bedc2ce0a72dad1827a1709da30580f0544".into()); let genesis = frontier.genesis_block(); diff --git a/ethcore/src/migrations/mod.rs b/ethcore/src/migrations/mod.rs index b9a00a15e..6cc4a13a8 100644 --- a/ethcore/src/migrations/mod.rs +++ b/ethcore/src/migrations/mod.rs @@ -28,4 +28,4 @@ mod v10; pub use self::v10::ToV10; mod v11; -pub use self::v11::ToV11; +pub use self::v11::TO_V11; diff --git a/ethcore/src/migrations/v11.rs b/ethcore/src/migrations/v11.rs index 8795cf364..e33de6170 100644 --- a/ethcore/src/migrations/v11.rs +++ b/ethcore/src/migrations/v11.rs @@ -14,33 +14,13 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . - //! Adds a seventh column for node information. -use util::kvdb::Database; -use util::migration::{Batch, Config, Error, Migration, Progress}; -use std::sync::Arc; +use util::migration::ChangeColumns; -/// Copies over data for all existing columns. -#[derive(Default)] -pub struct ToV11(Progress); - - -impl Migration for ToV11 { - fn pre_columns(&self) -> Option { Some(6) } - fn columns(&self) -> Option { Some(7) } - - fn version(&self) -> u32 { 11 } - - fn migrate(&mut self, source: Arc, config: &Config, dest: &mut Database, col: Option) -> Result<(), Error> { - // just copy everything over. - let mut batch = Batch::new(config, col); - - for (key, value) in source.iter(col) { - self.0.tick(); - batch.insert(key.to_vec(), value.to_vec(), dest)? - } - - batch.commit(dest) - } -} +/// The migration from v10 to v11. +pub const TO_V11: ChangeColumns = ChangeColumns { + pre_columns: Some(6), + post_columns: Some(7), + version: 11, +}; diff --git a/ethcore/src/miner/mod.rs b/ethcore/src/miner/mod.rs index 74e1cb598..403aca760 100644 --- a/ethcore/src/miner/mod.rs +++ b/ethcore/src/miner/mod.rs @@ -32,7 +32,7 @@ //! use ethcore::miner::{Miner, MinerService}; //! //! fn main() { -//! let miner: Miner = Miner::with_spec(ðereum::new_frontier()); +//! let miner: Miner = Miner::with_spec(ðereum::new_foundation()); //! // get status //! assert_eq!(miner.status().transactions_in_pending_queue, 0); //! diff --git a/ethcore/src/miner/service_transaction_checker.rs b/ethcore/src/miner/service_transaction_checker.rs index f3281dade..5a7ab04e9 100644 --- a/ethcore/src/miner/service_transaction_checker.rs +++ b/ethcore/src/miner/service_transaction_checker.rs @@ -14,6 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +use types::ids::BlockId; use client::MiningBlockChainClient; use transaction::SignedTransaction; use util::{U256, Uint, Mutex}; @@ -45,7 +46,7 @@ impl ServiceTransactionChecker { debug_assert_eq!(tx.gas_price, U256::zero()); if let Some(ref contract) = *self.contract.lock() { - let do_call = |a, d| client.call_contract(a, d); + let do_call = |a, d| client.call_contract(BlockId::Latest, a, d); contract.certified(&do_call, &tx.sender()) } else { Err("contract is not configured".to_owned()) diff --git a/ethcore/src/spec/spec.rs b/ethcore/src/spec/spec.rs index 67e21208b..078908db4 100644 --- a/ethcore/src/spec/spec.rs +++ b/ethcore/src/spec/spec.rs @@ -160,7 +160,7 @@ impl Spec { fn engine(engine_spec: ethjson::spec::Engine, params: CommonParams, builtins: BTreeMap) -> Arc { match engine_spec { ethjson::spec::Engine::Null => Arc::new(NullEngine::new(params, builtins)), - ethjson::spec::Engine::InstantSeal => Arc::new(InstantSeal::new(params, builtins)), + ethjson::spec::Engine::InstantSeal(instant) => Arc::new(InstantSeal::new(params, instant.params.registrar.map_or_else(Address::new, Into::into), builtins)), ethjson::spec::Engine::Ethash(ethash) => Arc::new(ethereum::Ethash::new(params, From::from(ethash.params), builtins)), ethjson::spec::Engine::BasicAuthority(basic_authority) => Arc::new(BasicAuthority::new(params, From::from(basic_authority.params), builtins)), ethjson::spec::Engine::AuthorityRound(authority_round) => AuthorityRound::new(params, From::from(authority_round.params), builtins).expect("Failed to start AuthorityRound consensus engine."), diff --git a/ethkey/src/lib.rs b/ethkey/src/lib.rs index 9c0c7907d..60039b671 100644 --- a/ethkey/src/lib.rs +++ b/ethkey/src/lib.rs @@ -44,6 +44,8 @@ pub trait Generator { fn generate(self) -> Result; } +pub mod math; + pub use self::brain::Brain; pub use self::error::Error; pub use self::keypair::{KeyPair, public_to_address}; diff --git a/ethkey/src/math.rs b/ethkey/src/math.rs new file mode 100644 index 000000000..45e5d04e6 --- /dev/null +++ b/ethkey/src/math.rs @@ -0,0 +1,66 @@ +// Copyright 2015-2017 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 . + +use super::{SECP256K1, Public, Secret, Error}; +use secp256k1::key; +use secp256k1::constants::{GENERATOR_X, GENERATOR_Y}; + +/// Inplace multiply public key by secret key (EC point * scalar) +pub fn public_mul_secret(public: &mut Public, secret: &Secret) -> Result<(), Error> { + let key_secret = secret.to_secp256k1_secret()?; + let mut key_public = to_secp256k1_public(public)?; + key_public.mul_assign(&SECP256K1, &key_secret)?; + set_public(public, &key_public); + Ok(()) +} + +/// Inplace add one public key to another (EC point + EC point) +pub fn public_add(public: &mut Public, other: &Public) -> Result<(), Error> { + let mut key_public = to_secp256k1_public(public)?; + let other_public = to_secp256k1_public(other)?; + key_public.add_assign(&SECP256K1, &other_public)?; + set_public(public, &key_public); + Ok(()) +} + +/// Return base point of secp256k1 +pub fn generation_point() -> Public { + let mut public_sec_raw = [0u8; 65]; + public_sec_raw[0] = 4; + public_sec_raw[1..33].copy_from_slice(&GENERATOR_X); + public_sec_raw[33..65].copy_from_slice(&GENERATOR_Y); + + let public_key = key::PublicKey::from_slice(&SECP256K1, &public_sec_raw) + .expect("constructing using predefined constants; qed"); + let mut public = Public::default(); + set_public(&mut public, &public_key); + public +} + +fn to_secp256k1_public(public: &Public) -> Result { + let public_data = { + let mut temp = [4u8; 65]; + (&mut temp[1..65]).copy_from_slice(&public[0..64]); + temp + }; + + Ok(key::PublicKey::from_slice(&SECP256K1, &public_data)?) +} + +fn set_public(public: &mut Public, key_public: &key::PublicKey) { + let key_public_serialized = key_public.serialize_vec(&SECP256K1, false); + public.copy_from_slice(&key_public_serialized[1..65]); +} diff --git a/ethkey/src/secret.rs b/ethkey/src/secret.rs index 61f4cd184..d8696ef73 100644 --- a/ethkey/src/secret.rs +++ b/ethkey/src/secret.rs @@ -19,7 +19,7 @@ use std::ops::Deref; use std::str::FromStr; use secp256k1::key; use bigint::hash::H256; -use {Error}; +use {Error, SECP256K1}; #[derive(Clone, PartialEq, Eq)] pub struct Secret { @@ -45,6 +45,68 @@ impl Secret { let secret = key::SecretKey::from_slice(&super::SECP256K1, key)?; Ok(secret.into()) } + + /// Inplace add one secret key to another (scalar + scalar) + pub fn add(&mut self, other: &Secret) -> Result<(), Error> { + let mut key_secret = self.to_secp256k1_secret()?; + let other_secret = other.to_secp256k1_secret()?; + key_secret.add_assign(&SECP256K1, &other_secret)?; + + *self = key_secret.into(); + Ok(()) + } + + /// Inplace subtract one secret key from another (scalar - scalar) + pub fn sub(&mut self, other: &Secret) -> Result<(), Error> { + let mut key_secret = self.to_secp256k1_secret()?; + let mut other_secret = other.to_secp256k1_secret()?; + other_secret.mul_assign(&SECP256K1, &key::MINUS_ONE_KEY)?; + key_secret.add_assign(&SECP256K1, &other_secret)?; + + *self = key_secret.into(); + Ok(()) + } + + /// Inplace multiply one secret key to another (scalar * scalar) + pub fn mul(&mut self, other: &Secret) -> Result<(), Error> { + let mut key_secret = self.to_secp256k1_secret()?; + let other_secret = other.to_secp256k1_secret()?; + key_secret.mul_assign(&SECP256K1, &other_secret)?; + + *self = key_secret.into(); + Ok(()) + } + + /// Inplace inverse secret key (1 / scalar) + pub fn inv(&mut self) -> Result<(), Error> { + let mut key_secret = self.to_secp256k1_secret()?; + key_secret.inv_assign(&SECP256K1)?; + + *self = key_secret.into(); + Ok(()) + } + + /// Compute power of secret key inplace (secret ^ pow). + /// This function is not intended to be used with large powers. + pub fn pow(&mut self, pow: usize) -> Result<(), Error> { + match pow { + 0 => *self = key::ONE_KEY.into(), + 1 => (), + _ => { + let c = self.clone(); + for _ in 1..pow { + self.mul(&c)?; + } + }, + } + + Ok(()) + } + + /// Create `secp256k1::key::SecretKey` based on this secret + pub fn to_secp256k1_secret(&self) -> Result { + Ok(key::SecretKey::from_slice(&SECP256K1, &self[..])?) + } } impl FromStr for Secret { diff --git a/evmjit/Cargo.toml b/evmjit/Cargo.toml index 0d5d16ea1..2f84a7efd 100644 --- a/evmjit/Cargo.toml +++ b/evmjit/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evmjit" -version = "1.6.0" +version = "1.7.0" authors = ["Parity Technologies "] [lib] diff --git a/hash-fetch/Cargo.toml b/hash-fetch/Cargo.toml index f5f31c0a0..d24315eb0 100644 --- a/hash-fetch/Cargo.toml +++ b/hash-fetch/Cargo.toml @@ -3,7 +3,7 @@ description = "Fetching hash-addressed content." homepage = "http://parity.io" license = "GPL-3.0" name = "parity-hash-fetch" -version = "1.6.0" +version = "1.7.0" authors = ["Parity Technologies "] [dependencies] diff --git a/hw/Cargo.toml b/hw/Cargo.toml index 39baa675d..19b680a55 100644 --- a/hw/Cargo.toml +++ b/hw/Cargo.toml @@ -3,7 +3,7 @@ description = "Hardware wallet support." homepage = "http://parity.io" license = "GPL-3.0" name = "hardware-wallet" -version = "1.6.0" +version = "1.7.0" authors = ["Parity Technologies "] [dependencies] diff --git a/ipc-common-types/Cargo.toml b/ipc-common-types/Cargo.toml index 844962b2d..1eb4a4d0a 100644 --- a/ipc-common-types/Cargo.toml +++ b/ipc-common-types/Cargo.toml @@ -1,7 +1,7 @@ [package] description = "Types that implement IPC and are common to multiple modules." name = "ipc-common-types" -version = "1.6.0" +version = "1.7.0" license = "GPL-3.0" authors = ["Parity Technologies "] build = "build.rs" diff --git a/ipc/codegen/Cargo.toml b/ipc/codegen/Cargo.toml index a257fd98c..63e0fc7c6 100644 --- a/ipc/codegen/Cargo.toml +++ b/ipc/codegen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ethcore-ipc-codegen" -version = "1.6.0" +version = "1.7.0" authors = ["Parity Technologies "] license = "GPL-3.0" description = "Macros to auto-generate implementations for ipc call" diff --git a/ipc/nano/Cargo.toml b/ipc/nano/Cargo.toml index 07ebf41ef..9948820fe 100644 --- a/ipc/nano/Cargo.toml +++ b/ipc/nano/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ethcore-ipc-nano" -version = "1.6.0" +version = "1.7.0" authors = ["Parity Technologies "] license = "GPL-3.0" diff --git a/ipc/rpc/Cargo.toml b/ipc/rpc/Cargo.toml index fdfc4ba80..d8be8b444 100644 --- a/ipc/rpc/Cargo.toml +++ b/ipc/rpc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ethcore-ipc" -version = "1.6.0" +version = "1.7.0" authors = ["Parity Technologies "] license = "GPL-3.0" diff --git a/ipfs/Cargo.toml b/ipfs/Cargo.toml index 992ee0710..b7b84b66b 100644 --- a/ipfs/Cargo.toml +++ b/ipfs/Cargo.toml @@ -1,7 +1,7 @@ [package] description = "Parity IPFS-compatible API" name = "parity-ipfs-api" -version = "1.6.0" +version = "1.7.0" license = "GPL-3.0" authors = ["Parity Technologies "] diff --git a/js/.gitignore b/js/.gitignore index 555c4b4bb..786a10498 100644 --- a/js/.gitignore +++ b/js/.gitignore @@ -8,3 +8,4 @@ docs .happypack .npmjs .eslintcache +yarn.lock diff --git a/js/package.json b/js/package.json index 0b5cdbaaf..85d906b88 100644 --- a/js/package.json +++ b/js/package.json @@ -1,15 +1,16 @@ { "name": "parity.js", - "version": "0.3.105", + "version": "1.7.3", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", "maintainers": [ "Jaco Greeff", - "Nicolas Gotchac", + "Nicolas Gotchac" + ], + "contributors": [ "Jannis Redmann" ], - "contributors": [], "license": "GPL-3.0", "repository": { "type": "git", @@ -56,28 +57,28 @@ "prepush": "npm run lint:cached" }, "devDependencies": { - "babel-cli": "6.22.2", - "babel-core": "6.22.1", + "babel-cli": "6.23.0", + "babel-core": "6.23.1", "babel-eslint": "7.1.1", - "babel-loader": "6.2.10", + "babel-loader": "6.3.2", "babel-plugin-lodash": "3.2.11", "babel-plugin-react-intl": "2.3.1", "babel-plugin-recharts": "1.1.0", - "babel-plugin-transform-class-properties": "6.22.0", + "babel-plugin-transform-class-properties": "6.23.0", "babel-plugin-transform-decorators-legacy": "1.3.4", - "babel-plugin-transform-object-rest-spread": "6.22.0", - "babel-plugin-transform-react-remove-prop-types": "0.3.0", - "babel-plugin-transform-runtime": "6.22.0", + "babel-plugin-transform-object-rest-spread": "6.23.0", + "babel-plugin-transform-react-remove-prop-types": "0.3.2", + "babel-plugin-transform-runtime": "6.23.0", "babel-plugin-webpack-alias": "2.1.2", - "babel-polyfill": "6.22.0", - "babel-preset-env": "1.1.8", + "babel-polyfill": "6.23.0", + "babel-preset-env": "1.1.9", "babel-preset-es2015": "6.22.0", "babel-preset-es2016": "6.22.0", "babel-preset-es2017": "6.22.0", - "babel-preset-react": "6.22.0", + "babel-preset-react": "6.23.0", "babel-preset-stage-0": "6.22.0", - "babel-register": "6.22.0", - "babel-runtime": "6.22.0", + "babel-register": "6.23.0", + "babel-runtime": "6.23.0", "chai": "3.5.0", "chai-as-promised": "6.0.0", "chai-enzyme": "0.6.1", @@ -85,62 +86,62 @@ "circular-dependency-plugin": "2.0.0", "copy-webpack-plugin": "4.0.1", "core-js": "2.4.1", - "coveralls": "2.11.15", + "coveralls": "2.11.16", "css-loader": "0.26.1", "ejs-loader": "0.3.0", "ejsify": "1.0.0", - "enzyme": "2.7.0", - "eslint": "3.11.1", + "enzyme": "2.7.1", + "eslint": "3.16.1", "eslint-config-semistandard": "7.0.0", "eslint-config-standard": "6.2.1", "eslint-config-standard-react": "4.2.0", - "eslint-plugin-promise": "3.4.0", - "eslint-plugin-react": "6.8.0", + "eslint-plugin-promise": "3.4.2", + "eslint-plugin-react": "6.10.0", "eslint-plugin-standard": "2.0.1", - "express": "4.14.0", + "express": "4.14.1", "extract-loader": "0.1.0", "extract-text-webpack-plugin": "2.0.0-beta.4", - "file-loader": "0.9.0", - "happypack": "3.0.2", + "file-loader": "0.10.0", + "happypack": "3.0.3", "html-loader": "0.4.4", - "html-webpack-plugin": "2.24.1", + "html-webpack-plugin": "2.28.0", "http-proxy-middleware": "0.17.3", - "husky": "0.11.9", + "husky": "0.13.1", "ignore-styles": "5.0.1", - "image-webpack-loader": "3.1.0", + "image-webpack-loader": "3.2.0", "istanbul": "1.0.0-alpha.2", - "jsdom": "9.9.1", + "jsdom": "9.11.0", "json-loader": "0.5.4", "mocha": "3.2.0", "mock-local-storage": "1.0.2", "mock-socket": "6.0.4", - "nock": "9.0.2", - "postcss-import": "9.0.0", - "postcss-loader": "1.2.1", + "nock": "9.0.7", + "postcss-import": "9.1.0", + "postcss-loader": "1.3.2", "postcss-nested": "1.0.0", "postcss-simple-vars": "3.0.0", "progress": "1.1.8", - "progress-bar-webpack-plugin": "1.9.1", + "progress-bar-webpack-plugin": "1.9.3", "raw-loader": "0.5.1", - "react-addons-perf": "15.4.1", - "react-addons-test-utils": "15.4.1", + "react-addons-perf": "15.4.2", + "react-addons-test-utils": "15.4.2", "react-hot-loader": "3.0.0-beta.6", "react-intl-aggregate-webpack-plugin": "0.0.1", "rucksack-css": "0.9.1", - "script-ext-html-webpack-plugin": "1.3.5", - "serviceworker-webpack-plugin": "0.1.7", - "sinon": "1.17.6", + "script-ext-html-webpack-plugin": "1.7.1", + "serviceworker-webpack-plugin": "0.2.0", + "sinon": "1.17.7", "sinon-as-promised": "4.0.2", "sinon-chai": "2.8.0", "style-loader": "0.13.1", - "stylelint": "7.7.0", - "stylelint-config-standard": "15.0.1", + "stylelint": "7.9.0", + "stylelint-config-standard": "16.0.0", "to-source": "2.0.3", "url-loader": "0.5.7", "webpack": "2.2.1", - "webpack-dev-middleware": "1.9.0", + "webpack-dev-middleware": "1.10.1", "webpack-error-notification": "0.1.6", - "webpack-hot-middleware": "2.14.0", + "webpack-hot-middleware": "2.17.1", "websocket": "1.0.24", "yargs": "6.6.0" }, @@ -153,7 +154,7 @@ "debounce": "1.0.0", "es6-error": "4.0.0", "es6-promise": "4.0.5", - "ethereumjs-tx": "1.1.4", + "ethereumjs-tx": "1.2.5", "eventemitter3": "2.0.2", "file-saver": "1.3.3", "flat": "2.0.1", @@ -200,6 +201,9 @@ "scryptsy": "2.0.0", "solc": "ngotchac/solc-js", "store": "1.3.20", + "u2f-api": "0.0.9", + "u2f-api-polyfill": "0.4.3", + "uglify-js": "2.8.2", "useragent.js": "0.5.6", "utf8": "2.1.2", "valid-url": "1.0.9", diff --git a/js/src/3rdparty/etherscan/account.js b/js/src/3rdparty/etherscan/account.js index 8a8f4b1fc..7a6844759 100644 --- a/js/src/3rdparty/etherscan/account.js +++ b/js/src/3rdparty/etherscan/account.js @@ -21,15 +21,15 @@ const PAGE_SIZE = 25; import util from '../../api/util'; import { call } from './call'; -function _call (method, params, test) { - return call('account', method, params, test); +function _call (method, params, test, netVersion) { + return call('account', method, params, test, netVersion); } -function balance (address, test = false) { +function balance (address, test, netVersion) { return _call('balance', { address: address, tag: 'latest' - }, test).then((balance) => { + }, test, netVersion).then((balance) => { // same format as balancemulti below return { account: address, @@ -38,21 +38,21 @@ function balance (address, test = false) { }); } -function balances (addresses, test = false) { +function balances (addresses, test, netVersion) { return _call('balancemulti', { address: addresses.join(','), tag: 'latest' - }, test); + }, test, netVersion); } -function transactions (address, page, test = false) { +function transactions (address, page, test, netVersion) { // page offset from 0 return _call('txlist', { address: address, offset: PAGE_SIZE, page: (page || 0) + 1, sort: 'desc' - }, test).then((transactions) => { + }, test, netVersion).then((transactions) => { return transactions.map((tx) => { return { blockNumber: new BigNumber(tx.blockNumber || 0), @@ -67,9 +67,9 @@ function transactions (address, page, test = false) { } const account = { - balance: balance, - balances: balances, - transactions: transactions + balance, + balances, + transactions }; export { account }; diff --git a/js/src/3rdparty/etherscan/call.js b/js/src/3rdparty/etherscan/call.js index 3c3d1ef06..6b72e1bea 100644 --- a/js/src/3rdparty/etherscan/call.js +++ b/js/src/3rdparty/etherscan/call.js @@ -23,14 +23,32 @@ const options = { } }; -export function call (module, action, _params, test) { - const host = test ? 'testnet.etherscan.io' : 'api.etherscan.io'; +export function call (module, action, _params, test, netVersion) { + let prefix = 'api.'; + + switch (netVersion) { + case '2': + case '3': + prefix = 'testnet.'; + break; + + case '42': + prefix = 'kovan.'; + break; + + case '0': + default: + if (test) { + prefix = 'testnet.'; + } + break; + } const query = stringify(Object.assign({ module, action }, _params || {})); - return fetch(`https://${host}/api?${query}`, options) + return fetch(`https://${prefix}etherscan.io/api?${query}`, options) .then((response) => { if (!response.ok) { throw { code: response.status, message: response.statusText }; // eslint-disable-line diff --git a/js/src/3rdparty/etherscan/helpers.spec.js b/js/src/3rdparty/etherscan/helpers.spec.js index aeb6ef230..fa29c3d97 100644 --- a/js/src/3rdparty/etherscan/helpers.spec.js +++ b/js/src/3rdparty/etherscan/helpers.spec.js @@ -19,8 +19,8 @@ import { stringify } from 'qs'; import { url } from './links'; -function mockget (requests, test) { - let scope = nock(url(test)); +function mockget (requests, test, netVersion) { + let scope = nock(url(test, netVersion)); requests.forEach((request) => { scope = scope diff --git a/js/src/3rdparty/etherscan/links.js b/js/src/3rdparty/etherscan/links.js index 59ad51de7..8c9101268 100644 --- a/js/src/3rdparty/etherscan/links.js +++ b/js/src/3rdparty/etherscan/links.js @@ -14,14 +14,35 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -export const url = (isTestnet = false) => { - return `https://${isTestnet ? 'testnet.' : ''}etherscan.io`; +// NOTE: Keep 'isTestnet' for backwards library compatibility +export const url = (isTestnet = false, netVersion = '0') => { + let prefix = ''; + + switch (netVersion) { + case '2': + case '3': + prefix = 'testnet.'; + break; + + case '42': + prefix = 'kovan.'; + break; + + case '0': + default: + if (isTestnet) { + prefix = 'testnet.'; + } + break; + } + + return `https://${prefix}etherscan.io`; }; -export const txLink = (hash, isTestnet = false) => { - return `${url(isTestnet)}/tx/${hash}`; +export const txLink = (hash, isTestnet = false, netVersion = '0') => { + return `${url(isTestnet, netVersion)}/tx/${hash}`; }; -export const addressLink = (address, isTestnet = false) => { - return `${url(isTestnet)}/address/${address}`; +export const addressLink = (address, isTestnet = false, netVersion = '0') => { + return `${url(isTestnet, netVersion)}/address/${address}`; }; diff --git a/js/src/3rdparty/ledger/index.js b/js/src/3rdparty/ledger/index.js index 3759b4bd5..a5b876dca 100644 --- a/js/src/3rdparty/ledger/index.js +++ b/js/src/3rdparty/ledger/index.js @@ -14,12 +14,4 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import Ledger3 from './vendor/ledger3'; -import LedgerEth from './vendor/ledger-eth'; - -export function create () { - const ledger = new Ledger3('w0w'); - const app = new LedgerEth(ledger); - - return app; -} +export default from './ledger'; diff --git a/js/src/3rdparty/ledger/ledger.js b/js/src/3rdparty/ledger/ledger.js new file mode 100644 index 000000000..13a671998 --- /dev/null +++ b/js/src/3rdparty/ledger/ledger.js @@ -0,0 +1,136 @@ +// Copyright 2015-2017 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 . + +import 'u2f-api-polyfill'; + +import BigNumber from 'bignumber.js'; +import Transaction from 'ethereumjs-tx'; +import u2fapi from 'u2f-api'; + +import Ledger3 from './vendor/ledger3'; +import LedgerEth from './vendor/ledger-eth'; + +const LEDGER_PATH_ETC = "44’/60’/160720'/0'/0"; +const LEDGER_PATH_ETH = "44'/60'/0'/0"; +const SCRAMBLE_KEY = 'w0w'; + +function numberToHex (number) { + return `0x${new BigNumber(number).toString(16)}`; +} + +export default class Ledger { + constructor (api, ledger) { + this._api = api; + this._ledger = ledger; + + this._isSupported = false; + + this.checkJSSupport(); + } + + // FIXME: Until we have https support from Parity u2f will not work. Here we mark it completely + // as unsupported until a full end-to-end environment is available. + get isSupported () { + return false && this._isSupported; + } + + checkJSSupport () { + return u2fapi + .isSupported() + .then((isSupported) => { + console.log('Ledger:checkJSSupport', isSupported); + + this._isSupported = isSupported; + }); + } + + getAppConfiguration () { + return new Promise((resolve, reject) => { + this._ledger.getAppConfiguration((response, error) => { + if (error) { + reject(error); + return; + } + + resolve(response); + }); + }); + } + + scan () { + return new Promise((resolve, reject) => { + this._ledger.getAddress(LEDGER_PATH_ETH, (response, error) => { + if (error) { + reject(error); + return; + } + + resolve([response.address]); + }, true, false); + }); + } + + signTransaction (transaction) { + return this._api.net.version().then((_chainId) => { + return new Promise((resolve, reject) => { + const chainId = new BigNumber(_chainId).toNumber(); + const tx = new Transaction({ + data: transaction.data || transaction.input, + gasPrice: numberToHex(transaction.gasPrice), + gasLimit: numberToHex(transaction.gasLimit), + nonce: numberToHex(transaction.nonce), + to: transaction.to ? transaction.to.toLowerCase() : undefined, + value: numberToHex(transaction.value), + v: Buffer.from([chainId]), // pass the chainId to the ledger + r: Buffer.from([]), + s: Buffer.from([]) + }); + const rawTransaction = tx.serialize().toString('hex'); + + this._ledger.signTransaction(LEDGER_PATH_ETH, rawTransaction, (response, error) => { + if (error) { + reject(error); + return; + } + + tx.v = Buffer.from(response.v, 'hex'); + tx.r = Buffer.from(response.r, 'hex'); + tx.s = Buffer.from(response.s, 'hex'); + + if (chainId !== Math.floor((tx.v[0] - 35) / 2)) { + reject(new Error('Invalid EIP155 signature received from Ledger.')); + return; + } + + resolve(`0x${tx.serialize().toString('hex')}`); + }); + }); + }); + } + + static create (api, ledger) { + if (!ledger) { + ledger = new LedgerEth(new Ledger3(SCRAMBLE_KEY)); + } + + return new Ledger(api, ledger); + } +} + +export { + LEDGER_PATH_ETC, + LEDGER_PATH_ETH +}; diff --git a/js/src/3rdparty/ledger/ledger.spec.js b/js/src/3rdparty/ledger/ledger.spec.js new file mode 100644 index 000000000..406a4bfcd --- /dev/null +++ b/js/src/3rdparty/ledger/ledger.spec.js @@ -0,0 +1,120 @@ +// Copyright 2015-2017 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 . + +import sinon from 'sinon'; + +import Ledger from './'; + +const TEST_ADDRESS = '0x63Cf90D3f0410092FC0fca41846f596223979195'; + +let api; +let ledger; +let vendor; + +function createApi () { + api = { + net: { + version: sinon.stub().resolves('2') + } + }; + + return api; +} + +function createVendor (error = null) { + vendor = { + getAddress: (path, callback) => { + callback({ + address: TEST_ADDRESS + }, error); + }, + getAppConfiguration: (callback) => { + callback({}, error); + }, + signTransaction: (path, rawTransaction, callback) => { + callback({ + v: [39], + r: [0], + s: [0] + }, error); + } + }; + + return vendor; +} + +function create (error) { + ledger = new Ledger(createApi(), createVendor(error)); + + return ledger; +} + +describe('3rdparty/ledger', () => { + beforeEach(() => { + create(); + + sinon.spy(vendor, 'getAddress'); + sinon.spy(vendor, 'getAppConfiguration'); + sinon.spy(vendor, 'signTransaction'); + }); + + afterEach(() => { + vendor.getAddress.restore(); + vendor.getAppConfiguration.restore(); + vendor.signTransaction.restore(); + }); + + describe('getAppConfiguration', () => { + beforeEach(() => { + return ledger.getAppConfiguration(); + }); + + it('calls into getAppConfiguration', () => { + expect(vendor.getAppConfiguration).to.have.been.called; + }); + }); + + describe('scan', () => { + beforeEach(() => { + return ledger.scan(); + }); + + it('calls into getAddress', () => { + expect(vendor.getAddress).to.have.been.called; + }); + }); + + describe('signTransaction', () => { + beforeEach(() => { + return ledger.signTransaction({ + data: '0x0', + gasPrice: 20000000, + gasLimit: 1000000, + nonce: 2, + to: '0x63Cf90D3f0410092FC0fca41846f596223979195', + value: 1 + }); + }); + + it('retrieves chainId via API', () => { + expect(api.net.version).to.have.been.called; + }); + + it('calls into signTransaction', () => { + expect(vendor.signTransaction).to.have.been.called; + }); + }); +}); diff --git a/js/src/abi/util/signature.js b/js/src/abi/util/signature.js index ccc7cc062..86ed6f265 100644 --- a/js/src/abi/util/signature.js +++ b/js/src/abi/util/signature.js @@ -21,8 +21,9 @@ export function eventSignature (eventName, params) { const { strName, name } = parseName(eventName); const types = (params || []).map(fromParamType).join(','); const id = `${strName}(${types})`; + const signature = strName ? keccak_256(id) : ''; - return { id, name, signature: keccak_256(id) }; + return { id, name, signature }; } export function methodSignature (methodName, params) { diff --git a/js/src/abi/util/signature.spec.js b/js/src/abi/util/signature.spec.js index 1e9b7a9ee..118ebf4e5 100644 --- a/js/src/abi/util/signature.spec.js +++ b/js/src/abi/util/signature.spec.js @@ -46,7 +46,7 @@ describe('abi/util/signature', () => { expect(eventSignature(undefined, [])).to.deep.equal({ id: '()', name: undefined, - signature: '861731d50c3880a2ca1994d5ec287b94b2f4bd832a67d3e41c08177bdd5674fe' + signature: '' }); }); @@ -54,7 +54,7 @@ describe('abi/util/signature', () => { expect(eventSignature(undefined, undefined)).to.deep.equal({ id: '()', name: undefined, - signature: '861731d50c3880a2ca1994d5ec287b94b2f4bd832a67d3e41c08177bdd5674fe' + signature: '' }); }); }); @@ -96,7 +96,7 @@ describe('abi/util/signature', () => { expect(methodSignature(undefined, [])).to.deep.equal({ id: '()', name: undefined, - signature: '861731d5' + signature: '' }); }); @@ -104,7 +104,7 @@ describe('abi/util/signature', () => { expect(methodSignature(undefined, undefined)).to.deep.equal({ id: '()', name: undefined, - signature: '861731d5' + signature: '' }); }); }); diff --git a/js/src/api/contract/contract.js b/js/src/api/contract/contract.js index 570c36287..dd36afead 100644 --- a/js/src/api/contract/contract.js +++ b/js/src/api/contract/contract.js @@ -107,34 +107,26 @@ export default class Contract { }); } - deploy (options, values, statecb) { - const setState = (state) => { - if (!statecb) { - return; - } - - return statecb(null, state); - }; - - setState({ state: 'estimateGas' }); + deploy (options, values, statecb = () => {}) { + statecb(null, { state: 'estimateGas' }); return this .deployEstimateGas(options, values) .then(([gasEst, gas]) => { options.gas = gas.toFixed(0); - setState({ state: 'postTransaction', gas }); + statecb(null, { state: 'postTransaction', gas }); - const _options = this._encodeOptions(this.constructors[0], options, values); + const encodedOptions = this._encodeOptions(this.constructors[0], options, values); return this._api.parity - .postTransaction(_options) + .postTransaction(encodedOptions) .then((requestId) => { - setState({ state: 'checkRequest', requestId }); + statecb(null, { state: 'checkRequest', requestId }); return this._pollCheckRequest(requestId); }) .then((txhash) => { - setState({ state: 'getTransactionReceipt', txhash }); + statecb(null, { state: 'getTransactionReceipt', txhash }); return this._pollTransactionReceipt(txhash, gas); }) .then((receipt) => { @@ -142,23 +134,23 @@ export default class Contract { throw new Error(`Contract not deployed, gasUsed == ${gas.toFixed(0)}`); } - setState({ state: 'hasReceipt', receipt }); + statecb(null, { state: 'hasReceipt', receipt }); this._receipt = receipt; this._address = receipt.contractAddress; return this._address; - }); - }) - .then((address) => { - setState({ state: 'getCode' }); - return this._api.eth.getCode(this._address); - }) - .then((code) => { - if (code === '0x') { - throw new Error('Contract not deployed, getCode returned 0x'); - } + }) + .then((address) => { + statecb(null, { state: 'getCode' }); + return this._api.eth.getCode(this._address); + }) + .then((code) => { + if (code === '0x') { + throw new Error('Contract not deployed, getCode returned 0x'); + } - setState({ state: 'completed' }); - return this._address; + statecb(null, { state: 'completed' }); + return this._address; + }); }); } diff --git a/js/src/api/format/output.js b/js/src/api/format/output.js index 094cda25a..952002b60 100644 --- a/js/src/api/format/output.js +++ b/js/src/api/format/output.js @@ -128,6 +128,18 @@ export function outLog (log) { return log; } +export function outHwAccountInfo (infos) { + return Object + .keys(infos) + .reduce((ret, _address) => { + const address = outAddress(_address); + + ret[address] = infos[_address]; + + return ret; + }, {}); +} + export function outNumber (number) { return new BigNumber(number || 0); } diff --git a/js/src/api/format/output.spec.js b/js/src/api/format/output.spec.js index 151353453..c23751670 100644 --- a/js/src/api/format/output.spec.js +++ b/js/src/api/format/output.spec.js @@ -16,7 +16,7 @@ import BigNumber from 'bignumber.js'; -import { outBlock, outAccountInfo, outAddress, outChainStatus, outDate, outHistogram, outNumber, outPeer, outPeers, outReceipt, outRecentDapps, outSyncing, outTransaction, outTrace, outVaultMeta } from './output'; +import { outBlock, outAccountInfo, outAddress, outChainStatus, outDate, outHistogram, outHwAccountInfo, outNumber, outPeer, outPeers, outReceipt, outRecentDapps, outSyncing, outTransaction, outTrace, outVaultMeta } from './output'; import { isAddress, isBigNumber, isInstanceOf } from '../../../test/types'; describe('api/format/output', () => { @@ -163,6 +163,16 @@ describe('api/format/output', () => { }); }); + describe('outHwAccountInfo', () => { + it('returns objects with formatted addresses', () => { + expect(outHwAccountInfo( + { '0x63cf90d3f0410092fc0fca41846f596223979195': { manufacturer: 'mfg', name: 'type' } } + )).to.deep.equal({ + '0x63Cf90D3f0410092FC0fca41846f596223979195': { manufacturer: 'mfg', name: 'type' } + }); + }); + }); + describe('outNumber', () => { it('returns a BigNumber equalling the value', () => { const bn = outNumber('0x123456'); diff --git a/js/src/api/rpc/parity/parity.js b/js/src/api/rpc/parity/parity.js index 027d367d3..31de948dc 100644 --- a/js/src/api/rpc/parity/parity.js +++ b/js/src/api/rpc/parity/parity.js @@ -15,7 +15,7 @@ // along with Parity. If not, see . import { inAddress, inAddresses, inData, inHex, inNumber16, inOptions, inBlockNumber } from '../../format/input'; -import { outAccountInfo, outAddress, outAddresses, outChainStatus, outHistogram, outNumber, outPeers, outRecentDapps, outTransaction, outVaultMeta } from '../../format/output'; +import { outAccountInfo, outAddress, outAddresses, outChainStatus, outHistogram, outHwAccountInfo, outNumber, outPeers, outRecentDapps, outTransaction, outVaultMeta } from '../../format/output'; export default class Parity { constructor (transport) { @@ -200,6 +200,12 @@ export default class Parity { .then(outVaultMeta); } + hardwareAccountsInfo () { + return this._transport + .execute('parity_hardwareAccountsInfo') + .then(outHwAccountInfo); + } + hashContent (url) { return this._transport .execute('parity_hashContent', url); diff --git a/js/src/api/util/encode.js b/js/src/api/util/encode.js index d727d1e63..5b5fb5eac 100644 --- a/js/src/api/util/encode.js +++ b/js/src/api/util/encode.js @@ -34,7 +34,5 @@ export function abiEncode (methodName, inputTypes, data) { }) }, data); - return methodName === null - ? `0x${result.substr(10)}` - : result; + return result; } diff --git a/js/src/contracts/abi/index.js b/js/src/contracts/abi/index.js index 7a959a2ef..8985d869e 100644 --- a/js/src/contracts/abi/index.js +++ b/js/src/contracts/abi/index.js @@ -14,34 +14,18 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import badgereg from './badgereg.json'; -import basiccoin from './basiccoin.json'; -import basiccoinmanager from './basiccoinmanager.json'; -import dappreg from './dappreg.json'; -import eip20 from './eip20.json'; -import emailverification from './email-verification.json'; -import gavcoin from './gavcoin.json'; -import githubhint from './githubhint.json'; -import owned from './owned.json'; -import registry from './registry.json'; -import signaturereg from './signaturereg.json'; -import smsverification from './sms-verification.json'; -import tokenreg from './tokenreg.json'; -import wallet from './wallet.json'; - -export { - badgereg, - basiccoin, - basiccoinmanager, - dappreg, - eip20, - emailverification, - gavcoin, - githubhint, - owned, - registry, - signaturereg, - smsverification, - tokenreg, - wallet -}; +export badgereg from './badgereg.json'; +export basiccoin from './basiccoin.json'; +export basiccoinmanager from './basiccoinmanager.json'; +export dappreg from './dappreg.json'; +export eip20 from './eip20.json'; +export emailverification from './email-verification.json'; +export gavcoin from './gavcoin.json'; +export githubhint from './githubhint.json'; +export owned from './owned.json'; +export registry from './registry.json'; +export registry2 from './registry2.json'; +export signaturereg from './signaturereg.json'; +export smsverification from './sms-verification.json'; +export tokenreg from './tokenreg.json'; +export wallet from './wallet.json'; diff --git a/js/src/contracts/abi/old-wallet.json b/js/src/contracts/abi/old-wallet.json new file mode 100644 index 000000000..930069742 --- /dev/null +++ b/js/src/contracts/abi/old-wallet.json @@ -0,0 +1,466 @@ +[ + { + "constant": false, + "inputs": [ + { + "name": "_owner", + "type": "address" + } + ], + "name": "removeOwner", + "outputs": [], + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_addr", + "type": "address" + } + ], + "name": "isOwner", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "m_numOwners", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "m_lastDay", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "resetSpentToday", + "outputs": [], + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "m_spentToday", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_owner", + "type": "address" + } + ], + "name": "addOwner", + "outputs": [], + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "m_required", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_h", + "type": "bytes32" + } + ], + "name": "confirm", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_newLimit", + "type": "uint256" + } + ], + "name": "setDailyLimit", + "outputs": [], + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_to", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + }, + { + "name": "_data", + "type": "bytes" + } + ], + "name": "execute", + "outputs": [ + { + "name": "_r", + "type": "bytes32" + } + ], + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_operation", + "type": "bytes32" + } + ], + "name": "revoke", + "outputs": [], + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_newRequired", + "type": "uint256" + } + ], + "name": "changeRequirement", + "outputs": [], + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_operation", + "type": "bytes32" + }, + { + "name": "_owner", + "type": "address" + } + ], + "name": "hasConfirmed", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "ownerIndex", + "type": "uint256" + } + ], + "name": "getOwner", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_to", + "type": "address" + } + ], + "name": "kill", + "outputs": [], + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_from", + "type": "address" + }, + { + "name": "_to", + "type": "address" + } + ], + "name": "changeOwner", + "outputs": [], + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "m_dailyLimit", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "type": "function" + }, + { + "inputs": [ + { + "name": "_owners", + "type": "address[]" + }, + { + "name": "_required", + "type": "uint256" + }, + { + "name": "_daylimit", + "type": "uint256" + } + ], + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "name": "operation", + "type": "bytes32" + } + ], + "name": "Confirmation", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "name": "operation", + "type": "bytes32" + } + ], + "name": "Revoke", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "oldOwner", + "type": "address" + }, + { + "indexed": false, + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnerChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnerAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "oldOwner", + "type": "address" + } + ], + "name": "OwnerRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "newRequirement", + "type": "uint256" + } + ], + "name": "RequirementChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "_from", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + } + ], + "name": "Deposit", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + }, + { + "indexed": false, + "name": "to", + "type": "address" + }, + { + "indexed": false, + "name": "data", + "type": "bytes" + } + ], + "name": "SingleTransact", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "name": "operation", + "type": "bytes32" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + }, + { + "indexed": false, + "name": "to", + "type": "address" + }, + { + "indexed": false, + "name": "data", + "type": "bytes" + } + ], + "name": "MultiTransact", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "operation", + "type": "bytes32" + }, + { + "indexed": false, + "name": "initiator", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + }, + { + "indexed": false, + "name": "to", + "type": "address" + }, + { + "indexed": false, + "name": "data", + "type": "bytes" + } + ], + "name": "ConfirmationNeeded", + "type": "event" + } +] diff --git a/js/src/contracts/abi/registry2.json b/js/src/contracts/abi/registry2.json new file mode 100644 index 000000000..922b9b7e2 --- /dev/null +++ b/js/src/contracts/abi/registry2.json @@ -0,0 +1 @@ +[{"constant":true,"inputs":[{"name":"_data","type":"address"}],"name":"canReverse","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"bytes32"}],"name":"setData","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"string"}],"name":"confirmReverse","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"}],"name":"reserve","outputs":[{"name":"success","type":"bool"}],"payable":true,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"}],"name":"drop","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getAddress","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_amount","type":"uint256"}],"name":"setFee","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_to","type":"address"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getData","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"reserved","outputs":[{"name":"reserved","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"drain","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"string"},{"name":"_who","type":"address"}],"name":"proposeReverse","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"hasReverse","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getUint","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"fee","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"getOwner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"getReverse","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_data","type":"address"}],"name":"reverse","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"uint256"}],"name":"setUint","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"string"},{"name":"_who","type":"address"}],"name":"confirmReverseAs","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"removeReverse","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"address"}],"name":"setAddress","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"Drained","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"FeeChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"string"},{"indexed":true,"name":"reverse","type":"address"}],"name":"ReverseProposed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"string"},{"indexed":true,"name":"reverse","type":"address"}],"name":"ReverseConfirmed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"string"},{"indexed":true,"name":"reverse","type":"address"}],"name":"ReverseRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"bytes32"},{"indexed":true,"name":"owner","type":"address"}],"name":"Reserved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"bytes32"},{"indexed":true,"name":"oldOwner","type":"address"},{"indexed":true,"name":"newOwner","type":"address"}],"name":"Transferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"bytes32"},{"indexed":true,"name":"owner","type":"address"}],"name":"Dropped","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"bytes32"},{"indexed":true,"name":"key","type":"string"},{"indexed":false,"name":"plainKey","type":"string"}],"name":"DataChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"old","type":"address"},{"indexed":true,"name":"current","type":"address"}],"name":"NewOwner","type":"event"}] diff --git a/js/src/contracts/abi/wallet.json b/js/src/contracts/abi/wallet.json index 8048d239c..752407e62 100644 --- a/js/src/contracts/abi/wallet.json +++ b/js/src/contracts/abi/wallet.json @@ -1 +1,476 @@ -[{"constant":false,"inputs":[{"name":"_owner","type":"address"}],"name":"removeOwner","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_addr","type":"address"}],"name":"isOwner","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":true,"inputs":[],"name":"m_numOwners","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[],"name":"m_lastDay","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[],"name":"resetSpentToday","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"m_spentToday","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[{"name":"_owner","type":"address"}],"name":"addOwner","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"m_required","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[{"name":"_h","type":"bytes32"}],"name":"confirm","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":false,"inputs":[{"name":"_newLimit","type":"uint256"}],"name":"setDailyLimit","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"},{"name":"_data","type":"bytes"}],"name":"execute","outputs":[{"name":"_r","type":"bytes32"}],"type":"function"},{"constant":false,"inputs":[{"name":"_operation","type":"bytes32"}],"name":"revoke","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_newRequired","type":"uint256"}],"name":"changeRequirement","outputs":[],"type":"function"},{"constant":true,"inputs":[{"name":"_operation","type":"bytes32"},{"name":"_owner","type":"address"}],"name":"hasConfirmed","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":true,"inputs":[{"name":"ownerIndex","type":"uint256"}],"name":"getOwner","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"}],"name":"kill","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"}],"name":"changeOwner","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"m_dailyLimit","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"inputs":[{"name":"_owners","type":"address[]"},{"name":"_required","type":"uint256"},{"name":"_daylimit","type":"uint256"}],"type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"operation","type":"bytes32"}],"name":"Confirmation","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"operation","type":"bytes32"}],"name":"Revoke","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oldOwner","type":"address"},{"indexed":false,"name":"newOwner","type":"address"}],"name":"OwnerChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"newOwner","type":"address"}],"name":"OwnerAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oldOwner","type":"address"}],"name":"OwnerRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"newRequirement","type":"uint256"}],"name":"RequirementChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_from","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"to","type":"address"},{"indexed":false,"name":"data","type":"bytes"}],"name":"SingleTransact","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"operation","type":"bytes32"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"to","type":"address"},{"indexed":false,"name":"data","type":"bytes"}],"name":"MultiTransact","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"operation","type":"bytes32"},{"indexed":false,"name":"initiator","type":"address"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"to","type":"address"},{"indexed":false,"name":"data","type":"bytes"}],"name":"ConfirmationNeeded","type":"event"}] +[ + { + "constant": false, + "inputs": [ + { + "name": "_owner", + "type": "address" + } + ], + "name": "removeOwner", + "outputs": [], + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_addr", + "type": "address" + } + ], + "name": "isOwner", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "m_numOwners", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "m_lastDay", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "resetSpentToday", + "outputs": [], + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "m_spentToday", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_owner", + "type": "address" + } + ], + "name": "addOwner", + "outputs": [], + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "m_required", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_h", + "type": "bytes32" + } + ], + "name": "confirm", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_newLimit", + "type": "uint256" + } + ], + "name": "setDailyLimit", + "outputs": [], + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_to", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + }, + { + "name": "_data", + "type": "bytes" + } + ], + "name": "execute", + "outputs": [ + { + "name": "_r", + "type": "bytes32" + } + ], + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_operation", + "type": "bytes32" + } + ], + "name": "revoke", + "outputs": [], + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_newRequired", + "type": "uint256" + } + ], + "name": "changeRequirement", + "outputs": [], + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_operation", + "type": "bytes32" + }, + { + "name": "_owner", + "type": "address" + } + ], + "name": "hasConfirmed", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "ownerIndex", + "type": "uint256" + } + ], + "name": "getOwner", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_to", + "type": "address" + } + ], + "name": "kill", + "outputs": [], + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_from", + "type": "address" + }, + { + "name": "_to", + "type": "address" + } + ], + "name": "changeOwner", + "outputs": [], + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "m_dailyLimit", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "type": "function" + }, + { + "inputs": [ + { + "name": "_owners", + "type": "address[]" + }, + { + "name": "_required", + "type": "uint256" + }, + { + "name": "_daylimit", + "type": "uint256" + } + ], + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "name": "operation", + "type": "bytes32" + } + ], + "name": "Confirmation", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "name": "operation", + "type": "bytes32" + } + ], + "name": "Revoke", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "oldOwner", + "type": "address" + }, + { + "indexed": false, + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnerChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnerAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "oldOwner", + "type": "address" + } + ], + "name": "OwnerRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "newRequirement", + "type": "uint256" + } + ], + "name": "RequirementChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "_from", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + } + ], + "name": "Deposit", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + }, + { + "indexed": false, + "name": "to", + "type": "address" + }, + { + "indexed": false, + "name": "data", + "type": "bytes" + }, + { + "indexed": false, + "name": "created", + "type": "address" + } + ], + "name": "SingleTransact", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "name": "operation", + "type": "bytes32" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + }, + { + "indexed": false, + "name": "to", + "type": "address" + }, + { + "indexed": false, + "name": "data", + "type": "bytes" + }, + { + "indexed": false, + "name": "created", + "type": "address" + } + ], + "name": "MultiTransact", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "operation", + "type": "bytes32" + }, + { + "indexed": false, + "name": "initiator", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + }, + { + "indexed": false, + "name": "to", + "type": "address" + }, + { + "indexed": false, + "name": "data", + "type": "bytes" + } + ], + "name": "ConfirmationNeeded", + "type": "event" + } +] diff --git a/js/src/contracts/code/wallet.js b/js/src/contracts/code/wallet.js index 10e2c5699..7b172c3f1 100644 --- a/js/src/contracts/code/wallet.js +++ b/js/src/contracts/code/wallet.js @@ -15,15 +15,16 @@ // along with Parity. If not, see . /** - * @version Solidity v0.4.6 + * @version Solidity v0.4.9 - Optimized * @from https://github.com/ethcore/parity/blob/63137b15482344ff9df634c086abaabed452eadc/js/src/contracts/snippets/enhanced-wallet.sol - * @date 09-Dec-2016 @ 16h00 UTC + * @date 07-Mar-2017 @ 16h00 UTC */ -export const wallet = '0x6060604052346100005760405161041b38038061041b83398101604090815281516020830151918301519201915b604080517f696e697457616c6c657428616464726573735b5d2c75696e743235362c75696e81527f7432353629000000000000000000000000000000000000000000000000000000602080830191909152915190819003602501902084516000829052909173__WalletLibrary_________________________91600281019160049182010290819038829003903960006000600483016000866127105a03f45b505050505050505b610337806100e46000396000f36060604052361561006c5760e060020a60003504632f54bf6e81146101245780634123cb6b146101485780635237509314610167578063659010e714610186578063746c9171146101a5578063c2cf7326146101c4578063c41a360a146101eb578063f1736d8614610217575b6101225b60003411156100c15760408051600160a060020a033316815234602082015281517fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c929181900390910190a161011e565b600036111561011e5773__WalletLibrary_________________________600160a060020a0316600036600060405160200152604051808383808284378201915050925050506020604051808303818560325a03f4156100005750505b5b5b565b005b3461000057610134600435610236565b604080519115158252519081900360200190f35b3461000057610155610297565b60408051918252519081900360200190f35b346100005761015561029d565b60408051918252519081900360200190f35b34610000576101556102a3565b60408051918252519081900360200190f35b34610000576101556102a9565b60408051918252519081900360200190f35b34610000576101346004356024356102af565b604080519115158252519081900360200190f35b34610000576101fb600435610311565b60408051600160a060020a039092168252519081900360200190f35b3461000057610155610331565b60408051918252519081900360200190f35b600073__WalletLibrary_________________________600160a060020a0316600036600060405160200152604051808383808284378201915050925050506020604051808303818560325a03f4156100005750506040515190505b919050565b60015481565b60045481565b60035481565b60005481565b600073__WalletLibrary_________________________600160a060020a0316600036600060405160200152604051808383808284378201915050925050506020604051808303818560325a03f4156100005750506040515190505b92915050565b6000600582600101610100811015610000570160005b505490505b919050565b6002548156'; -export const walletLibrary = '0x606060405234610000575b611381806100186000396000f3606060405236156100da5760e060020a6000350463173825d981146100df5780632f54bf6e146100f157806352375093146101155780635c52c2f514610134578063659010e7146101435780637065cb4814610162578063797af627146101745780639da5e0eb14610198578063b20d30a9146101aa578063b61d27f6146101bc578063b75c7dc614610227578063ba51a6df14610239578063c2cf73261461024b578063c57c5f6014610272578063cbf0b0c0146102c6578063e46dcfeb146102d8578063f00d4b5d14610331578063f1736d8614610346575b610000565b34610000576100ef600435610365565b005b3461000057610101600435610452565b604080519115158252519081900360200190f35b3461000057610122610473565b60408051918252519081900360200190f35b34610000576100ef610479565b005b34610000576101226104b0565b60408051918252519081900360200190f35b34610000576100ef6004356104b6565b005b34610000576101016004356105a5565b604080519115158252519081900360200190f35b34610000576100ef60043561081e565b005b34610000576100ef600435610832565b005b3461000057604080516020600460443581810135601f810184900484028501840190955284845261010194823594602480359560649492939190920191819084018382808284375094965061086a95505050505050565b604080519115158252519081900360200190f35b34610000576100ef600435610bcc565b005b34610000576100ef600435610c77565b005b3461000057610101600435602435610cf9565b604080519115158252519081900360200190f35b34610000576100ef6004808035906020019082018035906020019080806020026020016040519081016040528093929190818152602001838360200280828437509496505093359350610d4e92505050565b005b34610000576100ef600435610e13565b005b34610000576100ef60048080359060200190820180359060200190808060200260200160405190810160405280939291908181526020018383602002808284375094965050843594602001359350610e5192505050565b005b34610000576100ef600435602435610e6a565b005b3461000057610122610f63565b60408051918252519081900360200190f35b600060003660405180838380828437820191505092505050604051809103902061038e81610f69565b1561044b57600160a060020a0383166000908152610105602052604090205491508115156103bb5761044b565b60016001540360005411156103cf5761044b565b6000600583610100811015610000570160005b5055600160a060020a03831660009081526101056020526040812055610406611108565b61040e6111dc565b60408051600160a060020a038516815290517f58619076adf5bb0943d100ef88d52d7c3fd691b19d3a9071b555b651fbf418da9181900360200190a15b5b5b505050565b600160a060020a03811660009081526101056020526040812054115b919050565b60045481565b6000366040518083838082843782019150509250505060405180910390206104a081610f69565b156104ab5760006003555b5b5b50565b60035481565b6000366040518083838082843782019150509250505060405180910390206104dd81610f69565b1561059f576104eb82610452565b156104f55761059f565b6104fd611108565b60015460fa9010610510576105106111dc565b5b60015460fa90106105215761059f565b60018054810190819055600160a060020a03831690600590610100811015610000570160005b5055600154600160a060020a03831660008181526101056020908152604091829020939093558051918252517f994a936646fe87ffe4f1e469d3d6aa417d6b855598397f323de5b449f765f0c3929181900390910190a15b5b5b5050565b6000816105b181610f69565b156108155760008381526101086020526040902054600160a060020a0316156108155760008381526101086020526040908190208054600180830154935160029384018054600160a060020a0390941695949093919283928592918116156101000260001901160480156106665780601f1061063b57610100808354040283529160200191610666565b820191906000526020600020905b81548152906001019060200180831161064957829003601f168201915b505091505060006040518083038185876185025a03f15050506000848152610108602090815260409182902060018082015482548551600160a060020a033381811683529682018c905296810183905295166060860181905260a06080870181815260029586018054958616156101000260001901909516959095049087018190527fe7c957c06e9a662c1a6c77366179f5b702b97651dc28eee7d5bf1dff6e40bb4a975094958a95929491939290919060c08301908490801561076b5780601f106107405761010080835404028352916020019161076b565b820191906000526020600020905b81548152906001019060200180831161074e57829003601f168201915b5050965050505050505060405180910390a1600083815261010860205260408120805473ffffffffffffffffffffffffffffffffffffffff19168155600180820183905560028083018054858255939493909281161561010002600019011604601f8190106107da575061080c565b601f01602090049060005260206000209081019061080c91905b8082111561080857600081556001016107f4565b5090565b5b505050600191505b5b5b5b50919050565b600281905561082b61130b565b6004555b50565b60003660405180838380828437820191505092505050604051809103902061085981610f69565b1561059f5760028290555b5b5b5050565b6000600061087733610452565b15610bc05761088584611315565b156109bc577f92ca3a80853e6663fa31fa10b99225f18d4902939b4c53a9caae9043f6efd004338587866040518085600160a060020a0316815260200184815260200183600160a060020a03168152602001806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156109335780820380516001836020036101000a031916815260200191505b509550505050505060405180910390a184600160a060020a03168484604051808280519060200190808383829060006004602084601f0104600302600f01f150905090810190601f16801561099c5780820380516001836020036101000a031916815260200191505b5091505060006040518083038185876185025a03f1925050509150610bc0565b600036436040518084848082843782019150508281526020019350505050604051809103902090506109ed816105a5565b158015610a10575060008181526101086020526040902054600160a060020a0316155b15610bc057600081815261010860209081526040822080546c01000000000000000000000000808a020473ffffffffffffffffffffffffffffffffffffffff199091161781556001808201889055865160029283018054818752958590209095601f9381161561010002600019011693909304820184900483019390929190880190839010610aaa57805160ff1916838001178555610ad7565b82800160010185558215610ad7579182015b82811115610ad7578251825591602001919060010190610abc565b5b50610af89291505b8082111561080857600081556001016107f4565b5090565b50507f1733cbb53659d713b79580f79f3f9ff215f78a7c7aa45890f3b89fc5cddfbf328133868887604051808660001916815260200185600160a060020a0316815260200184815260200183600160a060020a03168152602001806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f168015610bae5780820380516001836020036101000a031916815260200191505b50965050505050505060405180910390a15b5b5b5b5b509392505050565b600160a060020a033316600090815261010560205260408120549080821515610bf457610c70565b50506000828152610106602052604081206001810154600284900a929083161115610c705780546001908101825581018054839003905560408051600160a060020a03331681526020810186905281517fc7fb647e59b18047309aa15aad418e5d7ca96d173ad704f1031a2c3d7591734b929181900390910190a15b5b50505050565b600036604051808383808284378201915050925050506040518091039020610c9e81610f69565b1561059f57600154821115610cb25761059f565b6000829055610cbf611108565b6040805183815290517facbdb084c721332ac59f9b8e392196c9eb0e4932862da8eb9beaf0dad4f550da9181900360200190a15b5b5b5050565b600082815261010660209081526040808320600160a060020a038516845261010590925282205482811515610d315760009350610d45565b8160020a9050808360010154166000141593505b50505092915050565b815160019081019055600033600160a060020a03166006825b505550600160a060020a033316600090815261010560205260408120600190558181555b825181101561044b57828181518110156100005790602001906020020151600160a060020a0316600582600201610100811015610000570160005b5081905550806002016101056000858481518110156100005790602001906020020151600160a060020a03168152602001908152602001600020819055505b600101610d8b565b5b505050565b600036604051808383808284378201915050925050506040518091039020610e3a81610f69565b1561059f5781600160a060020a0316ff5b5b5b5050565b610e5b8383610d4e565b61044b8161081e565b5b505050565b6000600036604051808383808284378201915050925050506040518091039020610e9381610f69565b15610c7057610ea183610452565b15610eab57610c70565b600160a060020a038416600090815261010560205260409020549150811515610ed357610c70565b610edb611108565b82600160a060020a0316600583610100811015610000570160005b5055600160a060020a0380851660008181526101056020908152604080832083905593871680835291849020869055835192835282015281517fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c929181900390910190a15b5b5b50505050565b60025481565b600160a060020a033316600090815261010560205260408120548180821515610f91576110fe565b60008581526101066020526040902080549092501515611025576000805483556001808401919091556101078054918201808255828015829011610ffa57600083815260209020610ffa9181019083015b8082111561080857600081556001016107f4565b5090565b5b50505060028301819055610107805487929081101561000057906000526020600020900160005b50555b8260020a905080826001015416600014156110fe5760408051600160a060020a03331681526020810187905281517fe1c52dc63b719ade82e8bea94cc41a0d5d28e4aaf536adb5e9cccc9ff8c1aeda929181900390910190a18154600190116110eb5760008581526101066020526040902060020154610107805490919081101561000057906000526020600020900160005b5060009081905585815261010660205260408120818155600180820183905560029091019190915593506110fe566110fe565b8154600019018255600182018054821790555b5b5b505050919050565b6101075460005b818110156111855761010781815481101561000057906000526020600020900160005b50541561117c57610106600061010783815481101561000057906000526020600020900160005b505481526020810191909152604001600090812081815560018101829055600201555b5b60010161110f565b610107805460008083559190915261044b907f47c4908e245f386bfc1825973249847f4053a761ddb4880ad63c323a7b5a2a25908101905b8082111561080857600081556001016107f4565b5090565b5b505b5050565b60015b6001548110156104ab575b6001548110801561120c5750600581610100811015610000570160005b505415155b15611219576001016111ea565b5b600160015411801561123e57506005600154610100811015610000570160005b5054155b15611252576001805460001901905561121a565b6001548110801561127657506005600154610100811015610000570160005b505415155b80156112925750600581610100811015610000570160005b5054155b15611302576005600154610100811015610000570160005b5054600582610100811015610000570160005b5055806101056000600583610100811015610000570160005b505481526020019081526020016000208190555060006005600154610100811015610000570160005b50555b6111df565b5b50565b6201518042045b90565b600061132033610452565b1561046e5760045461133061130b565b111561134757600060035561134361130b565b6004555b600354828101108015906113615750600254826003540111155b1561137657506003805482019055600161046e565b5060005b5b5b91905056'; +export const wallet = '0x6060604052341561000c57fe5b60405161048538038061048583398101604090815281516020830151918301519201915b604080517f696e697457616c6c657428616464726573735b5d2c75696e743235362c75696e81527f7432353629000000000000000000000000000000000000000000000000000000602080830191909152915190819003602501902084516000829052909173_____________WalletLibrary______________91600281019160049182010290819038829003903960006000600483016000866127105a03f45b505050505050505b61039d806100e86000396000f300606060405236156100725763ffffffff60e060020a6000350416632f54bf6e811461012d5780634123cb6b1461015d578063523750931461017f578063659010e7146101a1578063746c9171146101c3578063c2cf7326146101e5578063c41a360a14610218578063f1736d8614610247575b61012b5b60003411156100c75760408051600160a060020a033316815234602082015281517fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c929181900390910190a1610127565b60003611156101275773_____________WalletLibrary______________600160a060020a0316600036600060405160200152604051808383808284378201915050925050506020604051808303818560325a03f4151561012457fe5b50505b5b5b565b005b341561013557fe5b610149600160a060020a0360043516610269565b604080519115158252519081900360200190f35b341561016557fe5b61016d6102cd565b60408051918252519081900360200190f35b341561018757fe5b61016d6102d3565b60408051918252519081900360200190f35b34156101a957fe5b61016d6102d9565b60408051918252519081900360200190f35b34156101cb57fe5b61016d6102df565b60408051918252519081900360200190f35b34156101ed57fe5b610149600435600160a060020a03602435166102e5565b604080519115158252519081900360200190f35b341561022057fe5b61022b60043561034a565b60408051600160a060020a039092168252519081900360200190f35b341561024f57fe5b61016d61036b565b60408051918252519081900360200190f35b600073_____________WalletLibrary______________600160a060020a0316600036600060405160200152604051808383808284378201915050925050506020604051808303818560325a03f415156102bf57fe5b50506040515190505b919050565b60015481565b60045481565b60035481565b60005481565b600073_____________WalletLibrary______________600160a060020a0316600036600060405160200152604051808383808284378201915050925050506020604051808303818560325a03f4151561033b57fe5b50506040515190505b92915050565b6000600560018301610100811061035d57fe5b0160005b505490505b919050565b600254815600a165627a7a723058204a75c2f5c8009054bd9e9998e8bb6f4bca0b201484709f357b482793957c47130029'; +export const walletLibrary = '0x6060604052341561000c57fe5b5b6116d88061001c6000396000f300606060405236156101015763ffffffff60e060020a600035041663173825d981146101575780632f54bf6e146101755780634123cb6b146101a557806352375093146101c75780635c52c2f5146101e9578063659010e7146101fb5780637065cb481461021d578063746c91711461023b578063797af6271461025d5780639da5e0eb14610284578063b20d30a914610299578063b61d27f6146102ae578063b75c7dc6146102ec578063ba51a6df14610301578063c2cf732614610316578063c41a360a14610349578063c57c5f6014610378578063cbf0b0c0146103cf578063e46dcfeb146103ed578063f00d4b5d14610449578063f1736d861461046d575b6101555b60003411156101525760408051600160a060020a033316815234602082015281517fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c929181900390910190a15b5b565b005b341561015f57fe5b610155600160a060020a036004351661048f565b005b341561017d57fe5b610191600160a060020a036004351661057d565b604080519115158252519081900360200190f35b34156101ad57fe5b6101b561059e565b60408051918252519081900360200190f35b34156101cf57fe5b6101b56105a4565b60408051918252519081900360200190f35b34156101f157fe5b6101556105aa565b005b341561020357fe5b6101b56105e1565b60408051918252519081900360200190f35b341561022557fe5b610155600160a060020a03600435166105e7565b005b341561024357fe5b6101b56106d7565b60408051918252519081900360200190f35b341561026557fe5b6101916004356106dd565b604080519115158252519081900360200190f35b341561028c57fe5b610155600435610a2f565b005b34156102a157fe5b610155600435610a43565b005b34156102b657fe5b6101b560048035600160a060020a0316906024803591604435918201910135610a7b565b60408051918252519081900360200190f35b34156102f457fe5b610155600435610d5d565b005b341561030957fe5b610155600435610e08565b005b341561031e57fe5b610191600435600160a060020a0360243516610e8a565b604080519115158252519081900360200190f35b341561035157fe5b61035c600435610edf565b60408051600160a060020a039092168252519081900360200190f35b341561038057fe5b6101556004808035906020019082018035906020019080806020026020016040519081016040528093929190818152602001838360200280828437509496505093359350610f0092505050565b005b34156103d757fe5b610155600160a060020a0360043516610fd4565b005b34156103f557fe5b6101556004808035906020019082018035906020019080806020026020016040519081016040528093929190818152602001838360200280828437509496505084359460200135935061101292505050565b005b341561045157fe5b610155600160a060020a036004358116906024351661102b565b005b341561047557fe5b6101b5611125565b60408051918252519081900360200190f35b60006000366040518083838082843782019150509250505060405180910390206104b88161112b565b1561057657600160a060020a0383166000908152610105602052604090205491508115156104e557610576565b60016001540360005411156104f957610576565b6000600583610100811061050957fe5b0160005b5055600160a060020a03831660009081526101056020526040812055610531611296565b610539611386565b60408051600160a060020a038516815290517f58619076adf5bb0943d100ef88d52d7c3fd691b19d3a9071b555b651fbf418da9181900360200190a15b5b5b505050565b600160a060020a03811660009081526101056020526040812054115b919050565b60015481565b60045481565b6000366040518083838082843782019150509250505060405180910390206105d18161112b565b156105dc5760006003555b5b5b50565b60035481565b60003660405180838380828437820191505092505050604051809103902061060e8161112b565b156106d15761061c8261057d565b15610626576106d1565b61062e611296565b60015460fa901061064157610641611386565b5b60015460fa9010610652576106d1565b60018054810190819055600160a060020a03831690600590610100811061067557fe5b0160005b5055600154600160a060020a03831660008181526101056020908152604091829020939093558051918252517f994a936646fe87ffe4f1e469d3d6aa417d6b855598397f323de5b449f765f0c3929181900390910190a15b5b5b5050565b60005481565b60006000826106eb8161112b565b15610a255760008481526101086020526040902054600160a060020a031615158061072757506000848152610108602052604090206001015415155b80610754575060008481526101086020526040902060029081015461010060018216150260001901160415155b15610a255760008481526101086020526040902054600160a060020a0316151561082c57600084815261010860209081526040918290206001808201546002928301805486516000199482161561010002949094011693909304601f810185900485028301850190955284825261082594909391929183018282801561081b5780601f106107f05761010080835404028352916020019161081b565b820191906000526020600020905b8154815290600101906020018083116107fe57829003601f168201915b50505050506114c2565b91506108e3565b60008481526101086020526040908190208054600180830154935160029384018054600160a060020a0390941695949093919283928592918116156101000260001901160480156108be5780601f10610893576101008083540402835291602001916108be565b820191906000526020600020905b8154815290600101906020018083116108a157829003601f168201915b505091505060006040518083038185876185025a03f19250505015156108e357610000565b5b6000848152610108602090815260409182902060018082015482548551600160a060020a033381811683529682018c90529681018390529086166060820181905295881660a082015260c06080820181815260029586018054958616156101000260001901909516959095049082018190527fe3a3a4111a84df27d76b68dc721e65c7711605ea5eee4afd3a9c58195217365c968b959394909390928a9290919060e0830190859080156109d95780601f106109ae576101008083540402835291602001916109d9565b820191906000526020600020905b8154815290600101906020018083116109bc57829003601f168201915b505097505050505050505060405180910390a16000848152610108602052604081208054600160a060020a03191681556001810182905590610a1e6002830182611557565b5050600192505b5b5b5b5050919050565b6002819055610a3c6114dc565b6004555b50565b600036604051808383808284378201915050925050506040518091039020610a6a8161112b565b156106d15760028290555b5b5b5050565b60006000610a883361057d565b15610d505782158015610a9f5750610a9f856114eb565b5b80610aad57506000546001145b15610bef57600160a060020a0386161515610b0357610afc8585858080601f016020809104026020016040519081016040528093929190818152602001838380828437506114c2945050505050565b9050610b43565b85600160a060020a03168585856040518083838082843782019150509250505060006040518083038185876185025a03f1925050501515610b4357610000565b5b7f9738cd1a8777c86b011f7b01d87d484217dc6ab5154a9d41eda5d14af8caf2923386888787866040518087600160a060020a0316600160a060020a0316815260200186815260200185600160a060020a0316600160a060020a031681526020018060200183600160a060020a0316600160a060020a0316815260200182810382528585828181526020019250808284376040519201829003995090975050505050505050a1610d50565b600036436040518084848082843791909101928352505060408051602092819003830190206000818152610108909352912054909450600160a060020a0316159150508015610c4e575060008281526101086020526040902060010154155b8015610c7b5750600082815261010860205260409020600290810154610100600182161502600019011604155b15610cbf576000828152610108602052604090208054600160a060020a031916600160a060020a03881617815560018101869055610cbd90600201858561159f565b505b610cc8826106dd565b1515610d505760408051838152600160a060020a033381811660208401529282018890528816606082015260a0608082018181529082018690527f1733cbb53659d713b79580f79f3f9ff215f78a7c7aa45890f3b89fc5cddfbf32928592909189918b918a918a9160c082018484808284376040519201829003995090975050505050505050a15b5b5b5b5b50949350505050565b600160a060020a033316600090815261010560205260408120549080821515610d8557610e01565b50506000828152610106602052604081206001810154600284900a929083161115610e015780546001908101825581018054839003905560408051600160a060020a03331681526020810186905281517fc7fb647e59b18047309aa15aad418e5d7ca96d173ad704f1031a2c3d7591734b929181900390910190a15b5b50505050565b600036604051808383808284378201915050925050506040518091039020610e2f8161112b565b156106d157600154821115610e43576106d1565b6000829055610e50611296565b6040805183815290517facbdb084c721332ac59f9b8e392196c9eb0e4932862da8eb9beaf0dad4f550da9181900360200190a15b5b5b5050565b600082815261010660209081526040808320600160a060020a038516845261010590925282205482811515610ec25760009350610ed6565b8160020a9050808360010154166000141593505b50505092915050565b60006005600183016101008110610ef257fe5b0160005b505490505b919050565b815160019081018155600090600160a060020a033316906005905b0160005b505550600160a060020a033316600090815261010560205260408120600190555b8251811015610fc9578281815181101515610f5757fe5b60209081029091010151600160a060020a03166005600283016101008110610f7b57fe5b0160005b50819055508060020161010560008584815181101515610f9b57fe5b90602001906020020151600160a060020a03168152602001908152602001600020819055505b600101610f40565b60008290555b505050565b600036604051808383808284378201915050925050506040518091039020610ffb8161112b565b156106d15781600160a060020a0316ff5b5b5b5050565b61101b81610a2f565b6105768383610f00565b5b505050565b60006000366040518083838082843782019150509250505060405180910390206110548161112b565b15610e01576110628361057d565b1561106c57610e01565b600160a060020a03841660009081526101056020526040902054915081151561109457610e01565b61109c611296565b600160a060020a03831660058361010081106110b457fe5b0160005b5055600160a060020a0380851660008181526101056020908152604080832083905593871680835291849020869055835192835282015281517fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c929181900390910190a15b5b5b50505050565b60025481565b600160a060020a0333166000908152610105602052604081205481808215156111535761128c565b600085815261010660205260409020805490925015156111b65760008054835560018084019190915561010780549161118e9190830161161e565b60028301819055610107805487929081106111a557fe5b906000526020600020900160005b50555b8260020a9050808260010154166000141561128c5760408051600160a060020a03331681526020810187905281517fe1c52dc63b719ade82e8bea94cc41a0d5d28e4aaf536adb5e9cccc9ff8c1aeda929181900390910190a181546001901161127957600085815261010660205260409020600201546101078054909190811061123c57fe5b906000526020600020900160005b50600090819055858152610106602052604081208181556001808201839055600290910191909155935061128c565b8154600019018255600182018054821790555b5b5b505050919050565b6101075460005b81811015611374576101086000610107838154811015156112ba57fe5b906000526020600020900160005b50548152602081019190915260400160009081208054600160a060020a031916815560018101829055906112ff6002830182611557565b505061010780548290811061131057fe5b906000526020600020900160005b50541561136b5761010660006101078381548110151561133a57fe5b906000526020600020900160005b505481526020810191909152604001600090812081815560018101829055600201555b5b60010161129d565b6106d16101076000611648565b5b5050565b60015b6001548110156105dc575b600154811080156113b7575060058161010081106113ae57fe5b0160005b505415155b156113c457600101611394565b5b60016001541180156113eb575060015460059061010081106113e357fe5b0160005b5054155b156113ff57600180546000190190556113c4565b600154811080156114255750600154600590610100811061141c57fe5b0160005b505415155b80156114425750600581610100811061143a57fe5b0160005b5054155b156114b957600154600590610100811061145857fe5b0160005b5054600582610100811061146c57fe5b0160005b5055806101056000600583610100811061148657fe5b0160005b505481526020019081526020016000208190555060006005600154610100811015156114b257fe5b0160005b50555b611389565b5b50565b600081516020830184f09050803b15610000575b92915050565b600062015180425b0490505b90565b60006114f63361057d565b15610599576004546115066114dc565b111561151d5760006003556115196114dc565b6004555b600354828101108015906115375750600254826003540111155b1561154c575060038054820190556001610599565b5060005b5b5b919050565b50805460018160011615610100020316600290046000825580601f1061157d57506105dc565b601f0160209004906000526020600020908101906105dc919061166a565b5b50565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106115e05782800160ff1982351617855561160d565b8280016001018555821561160d579182015b8281111561160d5782358255916020019190600101906115f2565b5b5061161a92915061166a565b5090565b8154818355818115116105765760008381526020902061057691810190830161166a565b5b505050565b50805460008255906000526020600020908101906105dc919061166a565b5b50565b6114e891905b8082111561161a5760008155600101611670565b5090565b90565b6114e891905b8082111561161a5760008155600101611670565b5090565b905600a165627a7a723058206560ca68304798da7e3be68397368a30b63db1453ff138ff8f765e80080025af0029'; +export const walletLibraryABI = '[{"constant":false,"inputs":[{"name":"_owner","type":"address"}],"name":"removeOwner","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_addr","type":"address"}],"name":"isOwner","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"m_numOwners","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"m_lastDay","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"resetSpentToday","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"m_spentToday","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_owner","type":"address"}],"name":"addOwner","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"m_required","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_h","type":"bytes32"}],"name":"confirm","outputs":[{"name":"o_success","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_limit","type":"uint256"}],"name":"initDaylimit","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_newLimit","type":"uint256"}],"name":"setDailyLimit","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"},{"name":"_data","type":"bytes"}],"name":"execute","outputs":[{"name":"o_hash","type":"bytes32"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_operation","type":"bytes32"}],"name":"revoke","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_newRequired","type":"uint256"}],"name":"changeRequirement","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_operation","type":"bytes32"},{"name":"_owner","type":"address"}],"name":"hasConfirmed","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"ownerIndex","type":"uint256"}],"name":"getOwner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_owners","type":"address[]"},{"name":"_required","type":"uint256"}],"name":"initMultiowned","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"}],"name":"kill","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_owners","type":"address[]"},{"name":"_required","type":"uint256"},{"name":"_daylimit","type":"uint256"}],"name":"initWallet","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"}],"name":"changeOwner","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"m_dailyLimit","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"payable":true,"type":"fallback"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"operation","type":"bytes32"}],"name":"Confirmation","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"operation","type":"bytes32"}],"name":"Revoke","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oldOwner","type":"address"},{"indexed":false,"name":"newOwner","type":"address"}],"name":"OwnerChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"newOwner","type":"address"}],"name":"OwnerAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oldOwner","type":"address"}],"name":"OwnerRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"newRequirement","type":"uint256"}],"name":"RequirementChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_from","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"to","type":"address"},{"indexed":false,"name":"data","type":"bytes"},{"indexed":false,"name":"created","type":"address"}],"name":"SingleTransact","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"operation","type":"bytes32"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"to","type":"address"},{"indexed":false,"name":"data","type":"bytes"},{"indexed":false,"name":"created","type":"address"}],"name":"MultiTransact","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"operation","type":"bytes32"},{"indexed":false,"name":"initiator","type":"address"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"to","type":"address"},{"indexed":false,"name":"data","type":"bytes"}],"name":"ConfirmationNeeded","type":"event"}]'; export const walletSourceURL = 'https://github.com/ethcore/parity/blob/63137b15482344ff9df634c086abaabed452eadc/js/src/contracts/snippets/enhanced-wallet.sol'; -export const walletLibraryRegKey = 'walletLibrary'; +export const walletLibraryRegKey = 'walletLibrary.v.2'; // Used if no Wallet Library found in registry... -// Compiled from `wallet.sol` using Solidity v0.4.6 -export const fullWalletCode = '0x606060405234610000576040516113bb3803806113bb83398101604090815281516020830151918301519201915b805b83835b815160019081019055600033600160a060020a03166003825b505550600160a060020a033316600090815261010260205260408120600190555b82518110156100ee57828181518110156100005790602001906020020151600160a060020a0316600282600201610100811015610000570160005b5081905550806002016101026000858481518110156100005790602001906020020151600160a060020a03168152602001908152602001600020819055505b60010161006c565b60008290555b50505061010581905561011264010000000061127861012182021704565b610107555b505b50505061012b565b6201518042045b90565b611282806101396000396000f3606060405236156100da5760e060020a6000350463173825d981146101305780632f54bf6e146101425780634123cb6b1461016657806352375093146101855780635c52c2f5146101a4578063659010e7146101b35780637065cb48146101d2578063746c9171146101e4578063797af62714610203578063b20d30a914610227578063b61d27f614610239578063b75c7dc61461026b578063ba51a6df1461027d578063c2cf73261461028f578063c41a360a146102b6578063cbf0b0c0146102e2578063f00d4b5d146102f4578063f1736d8614610309575b61012e5b600034111561012b5760408051600160a060020a033316815234602082015281517fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c929181900390910190a15b5b565b005b346100005761012e600435610328565b005b3461000057610152600435610415565b604080519115158252519081900360200190f35b3461000057610173610436565b60408051918252519081900360200190f35b346100005761017361043c565b60408051918252519081900360200190f35b346100005761012e610443565b005b346100005761017361047b565b60408051918252519081900360200190f35b346100005761012e600435610482565b005b3461000057610173610571565b60408051918252519081900360200190f35b3461000057610152600435610577565b604080519115158252519081900360200190f35b346100005761012e6004356107e3565b005b34610000576101736004803590602480359160443591820191013561081c565b60408051918252519081900360200190f35b346100005761012e600435610ab3565b005b346100005761012e600435610b5e565b005b3461000057610152600435602435610be0565b604080519115158252519081900360200190f35b34610000576102c6600435610c35565b60408051600160a060020a039092168252519081900360200190f35b346100005761012e600435610c55565b005b346100005761012e600435602435610c93565b005b3461000057610173610d8c565b60408051918252519081900360200190f35b600060003660405180838380828437820191505092505050604051809103902061035181610d93565b1561040e57600160a060020a03831660009081526101026020526040902054915081151561037e5761040e565b60016001540360005411156103925761040e565b6000600283610100811015610000570160005b5055600160a060020a038316600090815261010260205260408120556103c9610f32565b6103d1611002565b60408051600160a060020a038516815290517f58619076adf5bb0943d100ef88d52d7c3fd691b19d3a9071b555b651fbf418da9181900360200190a15b5b5b505050565b600160a060020a03811660009081526101026020526040812054115b919050565b60015481565b6101075481565b60003660405180838380828437820191505092505050604051809103902061046a81610d93565b15610476576000610106555b5b5b50565b6101065481565b6000366040518083838082843782019150509250505060405180910390206104a981610d93565b1561056b576104b782610415565b156104c15761056b565b6104c9610f32565b60015460fa90106104dc576104dc611002565b5b60015460fa90106104ed5761056b565b60018054810190819055600160a060020a03831690600290610100811015610000570160005b5055600154600160a060020a03831660008181526101026020908152604091829020939093558051918252517f994a936646fe87ffe4f1e469d3d6aa417d6b855598397f323de5b449f765f0c3929181900390910190a15b5b5b5050565b60005481565b60008161058381610d93565b156107da5760008381526101086020526040902054600160a060020a0316156107da5760008381526101086020526040908190208054600180830154935160029384018054600160a060020a0390941695949093919283928592918116156101000260001901160480156106385780601f1061060d57610100808354040283529160200191610638565b820191906000526020600020905b81548152906001019060200180831161061b57829003601f168201915b505091505060006040518083038185876185025a03f15050506000848152610108602090815260409182902060018082015482548551600160a060020a033381811683529682018c905296810183905295166060860181905260a06080870181815260029586018054958616156101000260001901909516959095049087018190527fe7c957c06e9a662c1a6c77366179f5b702b97651dc28eee7d5bf1dff6e40bb4a975094958a95929491939290919060c08301908490801561073d5780601f106107125761010080835404028352916020019161073d565b820191906000526020600020905b81548152906001019060200180831161072057829003601f168201915b5050965050505050505060405180910390a16000838152610108602052604081208054600160a060020a0319168155600180820183905560028083018054858255939493909281161561010002600019011604601f81901061079f57506107d1565b601f0160209004906000526020600020908101906107d191905b808211156107cd57600081556001016107b9565b5090565b5b505050600191505b5b5b5b50919050565b60003660405180838380828437820191505092505050604051809103902061080a81610d93565b1561056b576101058290555b5b5b5050565b600061082733610415565b15610aa85761083584611131565b156108f3577f92ca3a80853e6663fa31fa10b99225f18d4902939b4c53a9caae9043f6efd00433858786866040518086600160a060020a0316815260200185815260200184600160a060020a0316815260200180602001828103825284848281815260200192508082843760405192018290039850909650505050505050a184600160a060020a03168484846040518083838082843782019150509250505060006040518083038185876185025a03f15060009350610aa892505050565b6000364360405180848480828437820191505082815260200193505050506040518091039020905061092481610577565b158015610947575060008181526101086020526040902054600160a060020a0316155b15610aa857600081815261010860209081526040822080546c01000000000000000000000000808a0204600160a060020a0319909116178155600180820188905560029182018054818652948490209094601f928116156101000260001901169290920481019290920481019185919087908390106109d15782800160ff198235161785556109fe565b828001600101855582156109fe579182015b828111156109fe5782358255916020019190600101906109e3565b5b50610a1f9291505b808211156107cd57600081556001016107b9565b5090565b50507f1733cbb53659d713b79580f79f3f9ff215f78a7c7aa45890f3b89fc5cddfbf32813386888787604051808760001916815260200186600160a060020a0316815260200185815260200184600160a060020a031681526020018060200182810382528484828181526020019250808284376040519201829003995090975050505050505050a15b5b5b5b949350505050565b600160a060020a033316600090815261010260205260408120549080821515610adb57610b57565b50506000828152610103602052604081206001810154600284900a929083161115610b575780546001908101825581018054839003905560408051600160a060020a03331681526020810186905281517fc7fb647e59b18047309aa15aad418e5d7ca96d173ad704f1031a2c3d7591734b929181900390910190a15b5b50505050565b600036604051808383808284378201915050925050506040518091039020610b8581610d93565b1561056b57600154821115610b995761056b565b6000829055610ba6610f32565b6040805183815290517facbdb084c721332ac59f9b8e392196c9eb0e4932862da8eb9beaf0dad4f550da9181900360200190a15b5b5b5050565b600082815261010360209081526040808320600160a060020a038516845261010290925282205482811515610c185760009350610c2c565b8160020a9050808360010154166000141593505b50505092915050565b6000600282600101610100811015610000570160005b505490505b919050565b600036604051808383808284378201915050925050506040518091039020610c7c81610d93565b1561056b5781600160a060020a0316ff5b5b5b5050565b6000600036604051808383808284378201915050925050506040518091039020610cbc81610d93565b15610b5757610cca83610415565b15610cd457610b57565b600160a060020a038416600090815261010260205260409020549150811515610cfc57610b57565b610d04610f32565b82600160a060020a0316600283610100811015610000570160005b5055600160a060020a0380851660008181526101026020908152604080832083905593871680835291849020869055835192835282015281517fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c929181900390910190a15b5b5b50505050565b6101055481565b600160a060020a033316600090815261010260205260408120548180821515610dbb57610f28565b60008581526101036020526040902080549092501515610e4f576000805483556001808401919091556101048054918201808255828015829011610e2457600083815260209020610e249181019083015b808211156107cd57600081556001016107b9565b5090565b5b50505060028301819055610104805487929081101561000057906000526020600020900160005b50555b8260020a90508082600101541660001415610f285760408051600160a060020a03331681526020810187905281517fe1c52dc63b719ade82e8bea94cc41a0d5d28e4aaf536adb5e9cccc9ff8c1aeda929181900390910190a1815460019011610f155760008581526101036020526040902060020154610104805490919081101561000057906000526020600020900160005b506000908190558581526101036020526040812081815560018082018390556002909101919091559350610f2856610f28565b8154600019018255600182018054821790555b5b5b505050919050565b6101045460005b81811015610ff557610108600061010483815481101561000057906000526020600020900160005b50548152602081019190915260400160009081208054600160a060020a0319168155600180820183905560028083018054858255939493909281161561010002600019011604601f819010610fb65750610fe8565b601f016020900490600052602060002090810190610fe891905b808211156107cd57600081556001016107b9565b5090565b5b5050505b600101610f39565b61056b6111a4565b5b5050565b60015b600154811015610476575b600154811080156110325750600281610100811015610000570160005b505415155b1561103f57600101611010565b5b600160015411801561106457506002600154610100811015610000570160005b5054155b156110785760018054600019019055611040565b6001548110801561109c57506002600154610100811015610000570160005b505415155b80156110b85750600281610100811015610000570160005b5054155b15611128576002600154610100811015610000570160005b5054600282610100811015610000570160005b5055806101026000600283610100811015610000570160005b505481526020019081526020016000208190555060006002600154610100811015610000570160005b50555b611005565b5b50565b600061113c33610415565b15610431576101075461114d611278565b111561116657600061010655611161611278565b610107555b610106548281011080159061118357506101055482610106540111155b1561119957506101068054820190556001610431565b5060005b5b5b919050565b6101045460005b818110156112215761010481815481101561000057906000526020600020900160005b50541561121857610103600061010483815481101561000057906000526020600020900160005b505481526020810191909152604001600090812081815560018101829055600201555b5b6001016111ab565b610104805460008083559190915261040e907f4c0be60200faa20559308cb7b5a1bb3255c16cb1cab91f525b5ae7a03d02fabe908101905b808211156107cd57600081556001016107b9565b5090565b5b505b5050565b6201518042045b9056'; +// Compiled from `wallet.sol` using Solidity v0.4.9 - Optimized +export const fullWalletCode = '0x6060604052341561000c57fe5b60405161166d38038061166d83398101604090815281516020830151918301519201915b805b83835b815160019081018155600090600160a060020a033316906002905b0160005b505550600160a060020a033316600090815261010260205260408120600190555b82518110156100fd57828181518110151561008c57fe5b60209081029091010151600160a060020a0316600282810161010081106100af57fe5b0160005b508190555080600201610102600085848151811015156100cf57fe5b90602001906020020151600160a060020a03168152602001908152602001600020819055505b600101610075565b60008290555b50505061010581905561012164010000000061138f61013082021704565b610107555b505b50505061013f565b600062015180425b0490505b90565b61151f8061014e6000396000f300606060405236156100e05763ffffffff60e060020a600035041663173825d981146101365780632f54bf6e146101545780634123cb6b1461018457806352375093146101a65780635c52c2f5146101c8578063659010e7146101da5780637065cb48146101fc578063746c91711461021a578063797af6271461023c578063b20d30a914610263578063b61d27f614610278578063b75c7dc6146102b6578063ba51a6df146102cb578063c2cf7326146102e0578063c41a360a14610313578063cbf0b0c014610342578063f00d4b5d14610360578063f1736d8614610384575b6101345b60003411156101315760408051600160a060020a033316815234602082015281517fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c929181900390910190a15b5b565b005b341561013e57fe5b610134600160a060020a03600435166103a6565b005b341561015c57fe5b610170600160a060020a0360043516610494565b604080519115158252519081900360200190f35b341561018c57fe5b6101946104b5565b60408051918252519081900360200190f35b34156101ae57fe5b6101946104bb565b60408051918252519081900360200190f35b34156101d057fe5b6101346104c2565b005b34156101e257fe5b6101946104fa565b60408051918252519081900360200190f35b341561020457fe5b610134600160a060020a0360043516610501565b005b341561022257fe5b6101946105f1565b60408051918252519081900360200190f35b341561024457fe5b6101706004356105f7565b604080519115158252519081900360200190f35b341561026b57fe5b610134600435610949565b005b341561028057fe5b61019460048035600160a060020a0316906024803591604435918201910135610982565b60408051918252519081900360200190f35b34156102be57fe5b610134600435610c64565b005b34156102d357fe5b610134600435610d0f565b005b34156102e857fe5b610170600435600160a060020a0360243516610d91565b604080519115158252519081900360200190f35b341561031b57fe5b610326600435610de6565b60408051600160a060020a039092168252519081900360200190f35b341561034a57fe5b610134600160a060020a0360043516610e07565b005b341561036857fe5b610134600160a060020a0360043581169060243516610e45565b005b341561038c57fe5b610194610f3f565b60408051918252519081900360200190f35b60006000366040518083838082843782019150509250505060405180910390206103cf81610f46565b1561048d57600160a060020a0383166000908152610102602052604090205491508115156103fc5761048d565b60016001540360005411156104105761048d565b6000600283610100811061042057fe5b0160005b5055600160a060020a038316600090815261010260205260408120556104486110b1565b610450611132565b60408051600160a060020a038516815290517f58619076adf5bb0943d100ef88d52d7c3fd691b19d3a9071b555b651fbf418da9181900360200190a15b5b5b505050565b600160a060020a03811660009081526101026020526040812054115b919050565b60015481565b6101075481565b6000366040518083838082843782019150509250505060405180910390206104e981610f46565b156104f5576000610106555b5b5b50565b6101065481565b60003660405180838380828437820191505092505050604051809103902061052881610f46565b156105eb5761053682610494565b15610540576105eb565b6105486110b1565b60015460fa901061055b5761055b611132565b5b60015460fa901061056c576105eb565b60018054810190819055600160a060020a03831690600290610100811061058f57fe5b0160005b5055600154600160a060020a03831660008181526101026020908152604091829020939093558051918252517f994a936646fe87ffe4f1e469d3d6aa417d6b855598397f323de5b449f765f0c3929181900390910190a15b5b5b5050565b60005481565b600060008261060581610f46565b1561093f5760008481526101086020526040902054600160a060020a031615158061064157506000848152610108602052604090206001015415155b8061066e575060008481526101086020526040902060029081015461010060018216150260001901160415155b1561093f5760008481526101086020526040902054600160a060020a0316151561074657600084815261010860209081526040918290206001808201546002928301805486516000199482161561010002949094011693909304601f810185900485028301850190955284825261073f9490939192918301828280156107355780601f1061070a57610100808354040283529160200191610735565b820191906000526020600020905b81548152906001019060200180831161071857829003601f168201915b505050505061126e565b91506107fd565b60008481526101086020526040908190208054600180830154935160029384018054600160a060020a0390941695949093919283928592918116156101000260001901160480156107d85780601f106107ad576101008083540402835291602001916107d8565b820191906000526020600020905b8154815290600101906020018083116107bb57829003601f168201915b505091505060006040518083038185876185025a03f19250505015156107fd57610000565b5b6000848152610108602090815260409182902060018082015482548551600160a060020a033381811683529682018c90529681018390529086166060820181905295881660a082015260c06080820181815260029586018054958616156101000260001901909516959095049082018190527fe3a3a4111a84df27d76b68dc721e65c7711605ea5eee4afd3a9c58195217365c968b959394909390928a9290919060e0830190859080156108f35780601f106108c8576101008083540402835291602001916108f3565b820191906000526020600020905b8154815290600101906020018083116108d657829003601f168201915b505097505050505050505060405180910390a16000848152610108602052604081208054600160a060020a03191681556001810182905590610938600283018261139e565b5050600192505b5b5b5b5050919050565b60003660405180838380828437820191505092505050604051809103902061097081610f46565b156105eb576101058290555b5b5b5050565b6000600061098f33610494565b15610c5757821580156109a657506109a685611288565b5b806109b457506000546001145b15610af657600160a060020a0386161515610a0a57610a038585858080601f0160208091040260200160405190810160405280939291908181526020018383808284375061126e945050505050565b9050610a4a565b85600160a060020a03168585856040518083838082843782019150509250505060006040518083038185876185025a03f1925050501515610a4a57610000565b5b7f9738cd1a8777c86b011f7b01d87d484217dc6ab5154a9d41eda5d14af8caf2923386888787866040518087600160a060020a0316600160a060020a0316815260200186815260200185600160a060020a0316600160a060020a031681526020018060200183600160a060020a0316600160a060020a0316815260200182810382528585828181526020019250808284376040519201829003995090975050505050505050a1610c57565b600036436040518084848082843791909101928352505060408051602092819003830190206000818152610108909352912054909450600160a060020a0316159150508015610b55575060008281526101086020526040902060010154155b8015610b825750600082815261010860205260409020600290810154610100600182161502600019011604155b15610bc6576000828152610108602052604090208054600160a060020a031916600160a060020a03881617815560018101869055610bc49060020185856113e6565b505b610bcf826105f7565b1515610c575760408051838152600160a060020a033381811660208401529282018890528816606082015260a0608082018181529082018690527f1733cbb53659d713b79580f79f3f9ff215f78a7c7aa45890f3b89fc5cddfbf32928592909189918b918a918a9160c082018484808284376040519201829003995090975050505050505050a15b5b5b5b5b50949350505050565b600160a060020a033316600090815261010260205260408120549080821515610c8c57610d08565b50506000828152610103602052604081206001810154600284900a929083161115610d085780546001908101825581018054839003905560408051600160a060020a03331681526020810186905281517fc7fb647e59b18047309aa15aad418e5d7ca96d173ad704f1031a2c3d7591734b929181900390910190a15b5b50505050565b600036604051808383808284378201915050925050506040518091039020610d3681610f46565b156105eb57600154821115610d4a576105eb565b6000829055610d576110b1565b6040805183815290517facbdb084c721332ac59f9b8e392196c9eb0e4932862da8eb9beaf0dad4f550da9181900360200190a15b5b5b5050565b600082815261010360209081526040808320600160a060020a038516845261010290925282205482811515610dc95760009350610ddd565b8160020a9050808360010154166000141593505b50505092915050565b60006002600183016101008110610df957fe5b0160005b505490505b919050565b600036604051808383808284378201915050925050506040518091039020610e2e81610f46565b156105eb5781600160a060020a0316ff5b5b5b5050565b6000600036604051808383808284378201915050925050506040518091039020610e6e81610f46565b15610d0857610e7c83610494565b15610e8657610d08565b600160a060020a038416600090815261010260205260409020549150811515610eae57610d08565b610eb66110b1565b600160a060020a0383166002836101008110610ece57fe5b0160005b5055600160a060020a0380851660008181526101026020908152604080832083905593871680835291849020869055835192835282015281517fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c929181900390910190a15b5b5b50505050565b6101055481565b600160a060020a033316600090815261010260205260408120548180821515610f6e576110a7565b60008581526101036020526040902080549092501515610fd157600080548355600180840191909155610104805491610fa991908301611465565b6002830181905561010480548792908110610fc057fe5b906000526020600020900160005b50555b8260020a905080826001015416600014156110a75760408051600160a060020a03331681526020810187905281517fe1c52dc63b719ade82e8bea94cc41a0d5d28e4aaf536adb5e9cccc9ff8c1aeda929181900390910190a181546001901161109457600085815261010360205260409020600201546101048054909190811061105757fe5b906000526020600020900160005b5060009081905585815261010360205260408120818155600180820183905560029091019190915593506110a7565b8154600019018255600182018054821790555b5b5b505050919050565b6101045460005b81811015611125576101086000610104838154811015156110d557fe5b906000526020600020900160005b50548152602081019190915260400160009081208054600160a060020a0319168155600181018290559061111a600283018261139e565b50505b6001016110b8565b6105eb6112fb565b5b5050565b60015b6001548110156104f5575b600154811080156111635750600281610100811061115a57fe5b0160005b505415155b1561117057600101611140565b5b60016001541180156111975750600154600290610100811061118f57fe5b0160005b5054155b156111ab5760018054600019019055611170565b600154811080156111d1575060015460029061010081106111c857fe5b0160005b505415155b80156111ee575060028161010081106111e657fe5b0160005b5054155b1561126557600154600290610100811061120457fe5b0160005b5054600282610100811061121857fe5b0160005b5055806101026000600283610100811061123257fe5b0160005b5054815260200190815260200160002081905550600060026001546101008110151561125e57fe5b0160005b50555b611135565b5b50565b600081516020830184f09050803b15610000575b92915050565b600061129333610494565b156104b057610107546112a461138f565b11156112bd576000610106556112b861138f565b610107555b61010654828101108015906112da57506101055482610106540111155b156112f0575061010680548201905560016104b0565b5060005b5b5b919050565b6101045460005b8181101561137d5761010480548290811061131957fe5b906000526020600020900160005b5054156113745761010360006101048381548110151561134357fe5b906000526020600020900160005b505481526020810191909152604001600090812081815560018101829055600201555b5b600101611302565b6105eb610104600061148f565b5b5050565b600062015180425b0490505b90565b50805460018160011615610100020316600290046000825580601f106113c457506104f5565b601f0160209004906000526020600020908101906104f591906114b1565b5b50565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106114275782800160ff19823516178555611454565b82800160010185558215611454579182015b82811115611454578235825591602001919060010190611439565b5b506114619291506114b1565b5090565b81548183558181151161048d5760008381526020902061048d9181019083016114b1565b5b505050565b50805460008255906000526020600020908101906104f591906114b1565b5b50565b61139b91905b8082111561146157600081556001016114b7565b5090565b90565b61139b91905b8082111561146157600081556001016114b7565b5090565b905600a165627a7a723058203a7ac7072dc640002704b704af82b742650362cd55debf72fca105c2b916e01d0029'; diff --git a/js/src/contracts/snippets/enhanced-wallet.sol b/js/src/contracts/snippets/enhanced-wallet.sol index 374eb595f..1f89b1f6f 100644 --- a/js/src/contracts/snippets/enhanced-wallet.sol +++ b/js/src/contracts/snippets/enhanced-wallet.sol @@ -8,453 +8,454 @@ // use modifiers onlyowner (just own owned) or onlymanyowners(hash), whereby the same hash must be provided by // some number (specified in constructor) of the set of owners (specified in the constructor, modifiable) before the // interior is executed. -pragma solidity ^0.4.6; -contract multisig { - // EVENTS +pragma solidity ^0.4.9; - // this contract can accept a confirmation, in which case - // we record owner and operation (hash) alongside it. - event Confirmation(address owner, bytes32 operation); - event Revoke(address owner, bytes32 operation); +contract WalletEvents { + // EVENTS - // some others are in the case of an owner changing. - event OwnerChanged(address oldOwner, address newOwner); - event OwnerAdded(address newOwner); - event OwnerRemoved(address oldOwner); + // this contract only has six types of events: it can accept a confirmation, in which case + // we record owner and operation (hash) alongside it. + event Confirmation(address owner, bytes32 operation); + event Revoke(address owner, bytes32 operation); - // the last one is emitted if the required signatures change - event RequirementChanged(uint newRequirement); + // some others are in the case of an owner changing. + event OwnerChanged(address oldOwner, address newOwner); + event OwnerAdded(address newOwner); + event OwnerRemoved(address oldOwner); - // Funds has arrived into the wallet (record how much). - event Deposit(address _from, uint value); - // Single transaction going out of the wallet (record who signed for it, how much, and to whom it's going). - event SingleTransact(address owner, uint value, address to, bytes data); - // Multi-sig transaction going out of the wallet (record who signed for it last, the operation hash, how much, and to whom it's going). - event MultiTransact(address owner, bytes32 operation, uint value, address to, bytes data); - // Confirmation still needed for a transaction. - event ConfirmationNeeded(bytes32 operation, address initiator, uint value, address to, bytes data); + // the last one is emitted if the required signatures change + event RequirementChanged(uint newRequirement); + + // Funds has arrived into the wallet (record how much). + event Deposit(address _from, uint value); + // Single transaction going out of the wallet (record who signed for it, how much, and to whom it's going). + event SingleTransact(address owner, uint value, address to, bytes data, address created); + // Multi-sig transaction going out of the wallet (record who signed for it last, the operation hash, how much, and to whom it's going). + event MultiTransact(address owner, bytes32 operation, uint value, address to, bytes data, address created); + // Confirmation still needed for a transaction. + event ConfirmationNeeded(bytes32 operation, address initiator, uint value, address to, bytes data); } -contract multisigAbi is multisig { - function isOwner(address _addr) returns (bool); +contract WalletAbi { + // Revokes a prior confirmation of the given operation + function revoke(bytes32 _operation) external; - function hasConfirmed(bytes32 _operation, address _owner) constant returns (bool); + // Replaces an owner `_from` with another `_to`. + function changeOwner(address _from, address _to) external; - function confirm(bytes32 _h) returns(bool); + function addOwner(address _owner) external; - // (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today. - function setDailyLimit(uint _newLimit); + function removeOwner(address _owner) external; - function addOwner(address _owner); + function changeRequirement(uint _newRequired) external; - function removeOwner(address _owner); + function isOwner(address _addr) constant returns (bool); - function changeRequirement(uint _newRequired); + function hasConfirmed(bytes32 _operation, address _owner) external constant returns (bool); - // Revokes a prior confirmation of the given operation - function revoke(bytes32 _operation); + // (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today. + function setDailyLimit(uint _newLimit) external; - function changeOwner(address _from, address _to); - - function execute(address _to, uint _value, bytes _data) returns(bool); + function execute(address _to, uint _value, bytes _data) external returns (bytes32 o_hash); + function confirm(bytes32 _h) returns (bool o_success); } -contract WalletLibrary is multisig { - // TYPES +contract WalletLibrary is WalletEvents { + // TYPES - // struct for the status of a pending operation. - struct PendingState { - uint yetNeeded; - uint ownersDone; - uint index; + // struct for the status of a pending operation. + struct PendingState { + uint yetNeeded; + uint ownersDone; + uint index; + } + + // Transaction structure to remember details of transaction lest it need be saved for a later call. + struct Transaction { + address to; + uint value; + bytes data; + } + + // MODIFIERS + + // simple single-sig function modifier. + modifier onlyowner { + if (isOwner(msg.sender)) + _; + } + // multi-sig function modifier: the operation must have an intrinsic hash in order + // that later attempts can be realised as the same underlying operation and + // thus count as confirmations. + modifier onlymanyowners(bytes32 _operation) { + if (confirmAndCheck(_operation)) + _; + } + + // METHODS + + // gets called when no other function matches + function() payable { + // just being sent some cash? + if (msg.value > 0) + Deposit(msg.sender, msg.value); + } + + // constructor is given number of sigs required to do protected "onlymanyowners" transactions + // as well as the selection of addresses capable of confirming them. + function initMultiowned(address[] _owners, uint _required) { + m_numOwners = _owners.length + 1; + m_owners[1] = uint(msg.sender); + m_ownerIndex[uint(msg.sender)] = 1; + for (uint i = 0; i < _owners.length; ++i) + { + m_owners[2 + i] = uint(_owners[i]); + m_ownerIndex[uint(_owners[i])] = 2 + i; + } + m_required = _required; + } + + // Revokes a prior confirmation of the given operation + function revoke(bytes32 _operation) external { + uint ownerIndex = m_ownerIndex[uint(msg.sender)]; + // make sure they're an owner + if (ownerIndex == 0) return; + uint ownerIndexBit = 2**ownerIndex; + var pending = m_pending[_operation]; + if (pending.ownersDone & ownerIndexBit > 0) { + pending.yetNeeded++; + pending.ownersDone -= ownerIndexBit; + Revoke(msg.sender, _operation); + } + } + + // Replaces an owner `_from` with another `_to`. + function changeOwner(address _from, address _to) onlymanyowners(sha3(msg.data)) external { + if (isOwner(_to)) return; + uint ownerIndex = m_ownerIndex[uint(_from)]; + if (ownerIndex == 0) return; + + clearPending(); + m_owners[ownerIndex] = uint(_to); + m_ownerIndex[uint(_from)] = 0; + m_ownerIndex[uint(_to)] = ownerIndex; + OwnerChanged(_from, _to); + } + + function addOwner(address _owner) onlymanyowners(sha3(msg.data)) external { + if (isOwner(_owner)) return; + + clearPending(); + if (m_numOwners >= c_maxOwners) + reorganizeOwners(); + if (m_numOwners >= c_maxOwners) + return; + m_numOwners++; + m_owners[m_numOwners] = uint(_owner); + m_ownerIndex[uint(_owner)] = m_numOwners; + OwnerAdded(_owner); + } + + function removeOwner(address _owner) onlymanyowners(sha3(msg.data)) external { + uint ownerIndex = m_ownerIndex[uint(_owner)]; + if (ownerIndex == 0) return; + if (m_required > m_numOwners - 1) return; + + m_owners[ownerIndex] = 0; + m_ownerIndex[uint(_owner)] = 0; + clearPending(); + reorganizeOwners(); //make sure m_numOwner is equal to the number of owners and always points to the optimal free slot + OwnerRemoved(_owner); + } + + function changeRequirement(uint _newRequired) onlymanyowners(sha3(msg.data)) external { + if (_newRequired > m_numOwners) return; + m_required = _newRequired; + clearPending(); + RequirementChanged(_newRequired); + } + + // Gets an owner by 0-indexed position (using numOwners as the count) + function getOwner(uint ownerIndex) external constant returns (address) { + return address(m_owners[ownerIndex + 1]); + } + + function isOwner(address _addr) constant returns (bool) { + return m_ownerIndex[uint(_addr)] > 0; + } + + function hasConfirmed(bytes32 _operation, address _owner) external constant returns (bool) { + var pending = m_pending[_operation]; + uint ownerIndex = m_ownerIndex[uint(_owner)]; + + // make sure they're an owner + if (ownerIndex == 0) return false; + + // determine the bit to set for this owner. + uint ownerIndexBit = 2**ownerIndex; + return !(pending.ownersDone & ownerIndexBit == 0); + } + + // constructor - stores initial daily limit and records the present day's index. + function initDaylimit(uint _limit) { + m_dailyLimit = _limit; + m_lastDay = today(); + } + // (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today. + function setDailyLimit(uint _newLimit) onlymanyowners(sha3(msg.data)) external { + m_dailyLimit = _newLimit; + } + // resets the amount already spent today. needs many of the owners to confirm. + function resetSpentToday() onlymanyowners(sha3(msg.data)) external { + m_spentToday = 0; + } + + // constructor - just pass on the owner array to the multiowned and + // the limit to daylimit + function initWallet(address[] _owners, uint _required, uint _daylimit) { + initDaylimit(_daylimit); + initMultiowned(_owners, _required); + } + + // kills the contract sending everything to `_to`. + function kill(address _to) onlymanyowners(sha3(msg.data)) external { + suicide(_to); + } + + // Outside-visible transact entry point. Executes transaction immediately if below daily spend limit. + // If not, goes into multisig process. We provide a hash on return to allow the sender to provide + // shortcuts for the other confirmations (allowing them to avoid replicating the _to, _value + // and _data arguments). They still get the option of using them if they want, anyways. + function execute(address _to, uint _value, bytes _data) external onlyowner returns (bytes32 o_hash) { + // first, take the opportunity to check that we're under the daily limit. + if ((_data.length == 0 && underLimit(_value)) || m_required == 1) { + // yes - just execute the call. + address created; + if (_to == 0) { + created = create(_value, _data); + } else { + if (!_to.call.value(_value)(_data)) + throw; + } + SingleTransact(msg.sender, _value, _to, _data, created); + } else { + // determine our operation hash. + o_hash = sha3(msg.data, block.number); + // store if it's new + if (m_txs[o_hash].to == 0 && m_txs[o_hash].value == 0 && m_txs[o_hash].data.length == 0) { + m_txs[o_hash].to = _to; + m_txs[o_hash].value = _value; + m_txs[o_hash].data = _data; + } + if (!confirm(o_hash)) { + ConfirmationNeeded(o_hash, msg.sender, _value, _to, _data); + } + } + } + + function create(uint _value, bytes _code) internal returns (address o_addr) { + assembly { + o_addr := create(_value, add(_code, 0x20), mload(_code)) + jumpi(invalidJumpLabel, iszero(extcodesize(o_addr))) + } + } + + // confirm a transaction through just the hash. we use the previous transactions map, m_txs, in order + // to determine the body of the transaction from the hash provided. + function confirm(bytes32 _h) onlymanyowners(_h) returns (bool o_success) { + if (m_txs[_h].to != 0 || m_txs[_h].value != 0 || m_txs[_h].data.length != 0) { + address created; + if (m_txs[_h].to == 0) { + created = create(m_txs[_h].value, m_txs[_h].data); + } else { + if (!m_txs[_h].to.call.value(m_txs[_h].value)(m_txs[_h].data)) + throw; + } + + MultiTransact(msg.sender, _h, m_txs[_h].value, m_txs[_h].to, m_txs[_h].data, created); + delete m_txs[_h]; + return true; + } + } + + // INTERNAL METHODS + + function confirmAndCheck(bytes32 _operation) internal returns (bool) { + // determine what index the present sender is: + uint ownerIndex = m_ownerIndex[uint(msg.sender)]; + // make sure they're an owner + if (ownerIndex == 0) return; + + var pending = m_pending[_operation]; + // if we're not yet working on this operation, switch over and reset the confirmation status. + if (pending.yetNeeded == 0) { + // reset count of confirmations needed. + pending.yetNeeded = m_required; + // reset which owners have confirmed (none) - set our bitmap to 0. + pending.ownersDone = 0; + pending.index = m_pendingIndex.length++; + m_pendingIndex[pending.index] = _operation; + } + // determine the bit to set for this owner. + uint ownerIndexBit = 2**ownerIndex; + // make sure we (the message sender) haven't confirmed this operation previously. + if (pending.ownersDone & ownerIndexBit == 0) { + Confirmation(msg.sender, _operation); + // ok - check if count is enough to go ahead. + if (pending.yetNeeded <= 1) { + // enough confirmations: reset and run interior. + delete m_pendingIndex[m_pending[_operation].index]; + delete m_pending[_operation]; + return true; + } + else + { + // not enough: record that this owner in particular confirmed. + pending.yetNeeded--; + pending.ownersDone |= ownerIndexBit; + } + } + } + + function reorganizeOwners() private { + uint free = 1; + while (free < m_numOwners) + { + while (free < m_numOwners && m_owners[free] != 0) free++; + while (m_numOwners > 1 && m_owners[m_numOwners] == 0) m_numOwners--; + if (free < m_numOwners && m_owners[m_numOwners] != 0 && m_owners[free] == 0) + { + m_owners[free] = m_owners[m_numOwners]; + m_ownerIndex[m_owners[free]] = free; + m_owners[m_numOwners] = 0; + } + } + } + + // checks to see if there is at least `_value` left from the daily limit today. if there is, subtracts it and + // returns true. otherwise just returns false. + function underLimit(uint _value) internal onlyowner returns (bool) { + // reset the spend limit if we're on a different day to last time. + if (today() > m_lastDay) { + m_spentToday = 0; + m_lastDay = today(); + } + // check to see if there's enough left - if so, subtract and return true. + // overflow protection // dailyLimit check + if (m_spentToday + _value >= m_spentToday && m_spentToday + _value <= m_dailyLimit) { + m_spentToday += _value; + return true; + } + return false; + } + + // determines today's index. + function today() private constant returns (uint) { return now / 1 days; } + + function clearPending() internal { + uint length = m_pendingIndex.length; + + for (uint i = 0; i < length; ++i) { + delete m_txs[m_pendingIndex[i]]; + + if (m_pendingIndex[i] != 0) + delete m_pending[m_pendingIndex[i]]; } - // Transaction structure to remember details of transaction lest it need be saved for a later call. - struct Transaction { - address to; - uint value; - bytes data; - } + delete m_pendingIndex; + } - /****************************** - ***** MULTI OWNED SECTION **** - ******************************/ + // FIELDS + address constant _walletLibrary = 0xcafecafecafecafecafecafecafecafecafecafe; - // MODIFIERS + // the number of owners that must confirm the same operation before it is run. + uint public m_required; + // pointer used to find a free slot in m_owners + uint public m_numOwners; - // simple single-sig function modifier. - modifier onlyowner { - if (isOwner(msg.sender)) - _; - } - // multi-sig function modifier: the operation must have an intrinsic hash in order - // that later attempts can be realised as the same underlying operation and - // thus count as confirmations. - modifier onlymanyowners(bytes32 _operation) { - if (confirmAndCheck(_operation)) - _; - } + uint public m_dailyLimit; + uint public m_spentToday; + uint public m_lastDay; - // METHODS + // list of owners + uint[256] m_owners; - // constructor is given number of sigs required to do protected "onlymanyowners" transactions - // as well as the selection of addresses capable of confirming them. - function initMultiowned(address[] _owners, uint _required) { - m_numOwners = _owners.length + 1; - m_owners[1] = uint(msg.sender); - m_ownerIndex[uint(msg.sender)] = 1; - m_required = _required; + uint constant c_maxOwners = 250; + // index on the list of owners to allow reverse lookup + mapping(uint => uint) m_ownerIndex; + // the ongoing operations. + mapping(bytes32 => PendingState) m_pending; + bytes32[] m_pendingIndex; - for (uint i = 0; i < _owners.length; ++i) - { - m_owners[2 + i] = uint(_owners[i]); - m_ownerIndex[uint(_owners[i])] = 2 + i; - } - } - - // Revokes a prior confirmation of the given operation - function revoke(bytes32 _operation) { - uint ownerIndex = m_ownerIndex[uint(msg.sender)]; - // make sure they're an owner - if (ownerIndex == 0) return; - uint ownerIndexBit = 2**ownerIndex; - var pending = m_pending[_operation]; - if (pending.ownersDone & ownerIndexBit > 0) { - pending.yetNeeded++; - pending.ownersDone -= ownerIndexBit; - Revoke(msg.sender, _operation); - } - } - - // Replaces an owner `_from` with another `_to`. - function changeOwner(address _from, address _to) onlymanyowners(sha3(msg.data)) { - if (isOwner(_to)) return; - uint ownerIndex = m_ownerIndex[uint(_from)]; - if (ownerIndex == 0) return; - - clearPending(); - m_owners[ownerIndex] = uint(_to); - m_ownerIndex[uint(_from)] = 0; - m_ownerIndex[uint(_to)] = ownerIndex; - OwnerChanged(_from, _to); - } - - function addOwner(address _owner) onlymanyowners(sha3(msg.data)) { - if (isOwner(_owner)) return; - - clearPending(); - if (m_numOwners >= c_maxOwners) - reorganizeOwners(); - if (m_numOwners >= c_maxOwners) - return; - m_numOwners++; - m_owners[m_numOwners] = uint(_owner); - m_ownerIndex[uint(_owner)] = m_numOwners; - OwnerAdded(_owner); - } - - function removeOwner(address _owner) onlymanyowners(sha3(msg.data)) { - uint ownerIndex = m_ownerIndex[uint(_owner)]; - if (ownerIndex == 0) return; - if (m_required > m_numOwners - 1) return; - - m_owners[ownerIndex] = 0; - m_ownerIndex[uint(_owner)] = 0; - clearPending(); - reorganizeOwners(); //make sure m_numOwner is equal to the number of owners and always points to the optimal free slot - OwnerRemoved(_owner); - } - - function changeRequirement(uint _newRequired) onlymanyowners(sha3(msg.data)) { - if (_newRequired > m_numOwners) return; - m_required = _newRequired; - clearPending(); - RequirementChanged(_newRequired); - } - - function isOwner(address _addr) returns (bool) { - return m_ownerIndex[uint(_addr)] > 0; - } - - - function hasConfirmed(bytes32 _operation, address _owner) constant returns (bool) { - var pending = m_pending[_operation]; - uint ownerIndex = m_ownerIndex[uint(_owner)]; - - // make sure they're an owner - if (ownerIndex == 0) return false; - - // determine the bit to set for this owner. - uint ownerIndexBit = 2**ownerIndex; - return !(pending.ownersDone & ownerIndexBit == 0); - } - - // INTERNAL METHODS - - function confirmAndCheck(bytes32 _operation) internal returns (bool) { - // determine what index the present sender is: - uint ownerIndex = m_ownerIndex[uint(msg.sender)]; - // make sure they're an owner - if (ownerIndex == 0) return; - - var pending = m_pending[_operation]; - // if we're not yet working on this operation, switch over and reset the confirmation status. - if (pending.yetNeeded == 0) { - // reset count of confirmations needed. - pending.yetNeeded = m_required; - // reset which owners have confirmed (none) - set our bitmap to 0. - pending.ownersDone = 0; - pending.index = m_pendingIndex.length++; - m_pendingIndex[pending.index] = _operation; - } - // determine the bit to set for this owner. - uint ownerIndexBit = 2**ownerIndex; - // make sure we (the message sender) haven't confirmed this operation previously. - if (pending.ownersDone & ownerIndexBit == 0) { - Confirmation(msg.sender, _operation); - // ok - check if count is enough to go ahead. - if (pending.yetNeeded <= 1) { - // enough confirmations: reset and run interior. - delete m_pendingIndex[m_pending[_operation].index]; - delete m_pending[_operation]; - return true; - } - else - { - // not enough: record that this owner in particular confirmed. - pending.yetNeeded--; - pending.ownersDone |= ownerIndexBit; - } - } - } - - function reorganizeOwners() private { - uint free = 1; - while (free < m_numOwners) - { - while (free < m_numOwners && m_owners[free] != 0) free++; - while (m_numOwners > 1 && m_owners[m_numOwners] == 0) m_numOwners--; - if (free < m_numOwners && m_owners[m_numOwners] != 0 && m_owners[free] == 0) - { - m_owners[free] = m_owners[m_numOwners]; - m_ownerIndex[m_owners[free]] = free; - m_owners[m_numOwners] = 0; - } - } - } - - function clearPending() internal { - uint length = m_pendingIndex.length; - for (uint i = 0; i < length; ++i) - if (m_pendingIndex[i] != 0) - delete m_pending[m_pendingIndex[i]]; - delete m_pendingIndex; - } - - - /****************************** - ****** DAY LIMIT SECTION ***** - ******************************/ - - // MODIFIERS - - // simple modifier for daily limit. - modifier limitedDaily(uint _value) { - if (underLimit(_value)) - _; - } - - // METHODS - - // constructor - stores initial daily limit and records the present day's index. - function initDaylimit(uint _limit) { - m_dailyLimit = _limit; - m_lastDay = today(); - } - // (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today. - function setDailyLimit(uint _newLimit) onlymanyowners(sha3(msg.data)) { - m_dailyLimit = _newLimit; - } - // resets the amount already spent today. needs many of the owners to confirm. - function resetSpentToday() onlymanyowners(sha3(msg.data)) { - m_spentToday = 0; - } - - // INTERNAL METHODS - - // checks to see if there is at least `_value` left from the daily limit today. if there is, subtracts it and - // returns true. otherwise just returns false. - function underLimit(uint _value) internal onlyowner returns (bool) { - // reset the spend limit if we're on a different day to last time. - if (today() > m_lastDay) { - m_spentToday = 0; - m_lastDay = today(); - } - // check to see if there's enough left - if so, subtract and return true. - // overflow protection // dailyLimit check - if (m_spentToday + _value >= m_spentToday && m_spentToday + _value <= m_dailyLimit) { - m_spentToday += _value; - return true; - } - return false; - } - - // determines today's index. - function today() private constant returns (uint) { return now / 1 days; } - - - /****************************** - ********* WALLET SECTION ***** - ******************************/ - - // METHODS - - // constructor - just pass on the owner array to the multiowned and - // the limit to daylimit - function initWallet(address[] _owners, uint _required, uint _daylimit) { - initMultiowned(_owners, _required); - initDaylimit(_daylimit) ; - } - - // kills the contract sending everything to `_to`. - function kill(address _to) onlymanyowners(sha3(msg.data)) { - suicide(_to); - } - - // Outside-visible transact entry point. Executes transaction immediately if below daily spend limit. - // If not, goes into multisig process. We provide a hash on return to allow the sender to provide - // shortcuts for the other confirmations (allowing them to avoid replicating the _to, _value - // and _data arguments). They still get the option of using them if they want, anyways. - function execute(address _to, uint _value, bytes _data) onlyowner returns(bool _callValue) { - // first, take the opportunity to check that we're under the daily limit. - if (underLimit(_value)) { - SingleTransact(msg.sender, _value, _to, _data); - // yes - just execute the call. - _callValue =_to.call.value(_value)(_data); - } else { - // determine our operation hash. - bytes32 _r = sha3(msg.data, block.number); - if (!confirm(_r) && m_txs[_r].to == 0) { - m_txs[_r].to = _to; - m_txs[_r].value = _value; - m_txs[_r].data = _data; - ConfirmationNeeded(_r, msg.sender, _value, _to, _data); - } - } - } - - // confirm a transaction through just the hash. we use the previous transactions map, m_txs, in order - // to determine the body of the transaction from the hash provided. - function confirm(bytes32 _h) onlymanyowners(_h) returns (bool) { - if (m_txs[_h].to != 0) { - m_txs[_h].to.call.value(m_txs[_h].value)(m_txs[_h].data); - MultiTransact(msg.sender, _h, m_txs[_h].value, m_txs[_h].to, m_txs[_h].data); - delete m_txs[_h]; - return true; - } - } - - // INTERNAL METHODS - - function clearWalletPending() internal { - uint length = m_pendingIndex.length; - for (uint i = 0; i < length; ++i) - delete m_txs[m_pendingIndex[i]]; - clearPending(); - } - - // FIELDS - address constant _walletLibrary = 0xcafecafecafecafecafecafecafecafecafecafe; - - // the number of owners that must confirm the same operation before it is run. - uint m_required; - // pointer used to find a free slot in m_owners - uint m_numOwners; - - uint public m_dailyLimit; - uint public m_spentToday; - uint public m_lastDay; - - // list of owners - uint[256] m_owners; - uint constant c_maxOwners = 250; - - // index on the list of owners to allow reverse lookup - mapping(uint => uint) m_ownerIndex; - // the ongoing operations. - mapping(bytes32 => PendingState) m_pending; - bytes32[] m_pendingIndex; - - // pending transactions we have at present. - mapping (bytes32 => Transaction) m_txs; + // pending transactions we have at present. + mapping (bytes32 => Transaction) m_txs; } +contract Wallet is WalletEvents { -contract Wallet is multisig { + // WALLET CONSTRUCTOR + // calls the `initWallet` method of the Library in this context + function Wallet(address[] _owners, uint _required, uint _daylimit) { + // Signature of the Wallet Library's init function + bytes4 sig = bytes4(sha3("initWallet(address[],uint256,uint256)")); + address target = _walletLibrary; - // WALLET CONSTRUCTOR - // calls the `initWallet` method of the Library in this context - function Wallet(address[] _owners, uint _required, uint _daylimit) { - // Signature of the Wallet Library's init function - bytes4 sig = bytes4(sha3("initWallet(address[],uint256,uint256)")); - address target = _walletLibrary; + // Compute the size of the call data : arrays has 2 + // 32bytes for offset and length, plus 32bytes per element ; + // plus 2 32bytes for each uint + uint argarraysize = (2 + _owners.length); + uint argsize = (2 + argarraysize) * 32; - // Compute the size of the call data : arrays has 2 - // 32bytes for offset and length, plus 32bytes per element ; - // plus 2 32bytes for each uint - uint argarraysize = (2 + _owners.length); - uint argsize = (2 + argarraysize) * 32; - - assembly { - // Add the signature first to memory - mstore(0x0, sig) - // Add the call data, which is at the end of the - // code - codecopy(0x4, sub(codesize, argsize), argsize) - // Delegate call to the library - delegatecall(sub(gas, 10000), target, 0x0, add(argsize, 0x4), 0x0, 0x0) - } + assembly { + // Add the signature first to memory + mstore(0x0, sig) + // Add the call data, which is at the end of the + // code + codecopy(0x4, sub(codesize, argsize), argsize) + // Delegate call to the library + delegatecall(sub(gas, 10000), target, 0x0, add(argsize, 0x4), 0x0, 0x0) } + } - // METHODS + // METHODS - // gets called when no other function matches - function() payable { - // just being sent some cash? - if (msg.value > 0) - Deposit(msg.sender, msg.value); - else if (msg.data.length > 0) - _walletLibrary.delegatecall(msg.data); - } + // gets called when no other function matches + function() payable { + // just being sent some cash? + if (msg.value > 0) + Deposit(msg.sender, msg.value); + else if (msg.data.length > 0) + _walletLibrary.delegatecall(msg.data); + } - // Gets an owner by 0-indexed position (using numOwners as the count) - function getOwner(uint ownerIndex) constant returns (address) { - return address(m_owners[ownerIndex + 1]); - } + // Gets an owner by 0-indexed position (using numOwners as the count) + function getOwner(uint ownerIndex) constant returns (address) { + return address(m_owners[ownerIndex + 1]); + } - // As return statement unavailable in fallback, explicit the method here + // As return statement unavailable in fallback, explicit the method here - function hasConfirmed(bytes32 _operation, address _owner) constant returns (bool) { - return _walletLibrary.delegatecall(msg.data); - } + function hasConfirmed(bytes32 _operation, address _owner) external constant returns (bool) { + return _walletLibrary.delegatecall(msg.data); + } - function isOwner(address _addr) returns (bool) { - return _walletLibrary.delegatecall(msg.data); - } + function isOwner(address _addr) constant returns (bool) { + return _walletLibrary.delegatecall(msg.data); + } - // FIELDS - address constant _walletLibrary = 0xcafecafecafecafecafecafecafecafecafecafe; + // FIELDS + address constant _walletLibrary = 0xcafecafecafecafecafecafecafecafecafecafe; - // the number of owners that must confirm the same operation before it is run. - uint public m_required; - // pointer used to find a free slot in m_owners - uint public m_numOwners; + // the number of owners that must confirm the same operation before it is run. + uint public m_required; + // pointer used to find a free slot in m_owners + uint public m_numOwners; - uint public m_dailyLimit; - uint public m_spentToday; - uint public m_lastDay; + uint public m_dailyLimit; + uint public m_spentToday; + uint public m_lastDay; - // list of owners - uint[256] m_owners; + // list of owners + uint[256] m_owners; } diff --git a/js/src/contracts/snippets/wallet.sol b/js/src/contracts/snippets/wallet.sol index b369eea76..18de9df68 100644 --- a/js/src/contracts/snippets/wallet.sol +++ b/js/src/contracts/snippets/wallet.sol @@ -8,221 +8,222 @@ // use modifiers onlyowner (just own owned) or onlymanyowners(hash), whereby the same hash must be provided by // some number (specified in constructor) of the set of owners (specified in the constructor, modifiable) before the // interior is executed. -pragma solidity ^0.4.6; + +pragma solidity ^0.4.9; contract multiowned { - // TYPES + // TYPES - // struct for the status of a pending operation. - struct PendingState { - uint yetNeeded; - uint ownersDone; - uint index; + // struct for the status of a pending operation. + struct PendingState { + uint yetNeeded; + uint ownersDone; + uint index; + } + + // EVENTS + + // this contract only has six types of events: it can accept a confirmation, in which case + // we record owner and operation (hash) alongside it. + event Confirmation(address owner, bytes32 operation); + event Revoke(address owner, bytes32 operation); + // some others are in the case of an owner changing. + event OwnerChanged(address oldOwner, address newOwner); + event OwnerAdded(address newOwner); + event OwnerRemoved(address oldOwner); + // the last one is emitted if the required signatures change + event RequirementChanged(uint newRequirement); + + // MODIFIERS + + // simple single-sig function modifier. + modifier onlyowner { + if (isOwner(msg.sender)) + _; + } + // multi-sig function modifier: the operation must have an intrinsic hash in order + // that later attempts can be realised as the same underlying operation and + // thus count as confirmations. + modifier onlymanyowners(bytes32 _operation) { + if (confirmAndCheck(_operation)) + _; + } + + // METHODS + + // constructor is given number of sigs required to do protected "onlymanyowners" transactions + // as well as the selection of addresses capable of confirming them. + function multiowned(address[] _owners, uint _required) { + m_numOwners = _owners.length + 1; + m_owners[1] = uint(msg.sender); + m_ownerIndex[uint(msg.sender)] = 1; + for (uint i = 0; i < _owners.length; ++i) + { + m_owners[2 + i] = uint(_owners[i]); + m_ownerIndex[uint(_owners[i])] = 2 + i; } + m_required = _required; + } - // EVENTS - - // this contract only has six types of events: it can accept a confirmation, in which case - // we record owner and operation (hash) alongside it. - event Confirmation(address owner, bytes32 operation); - event Revoke(address owner, bytes32 operation); - // some others are in the case of an owner changing. - event OwnerChanged(address oldOwner, address newOwner); - event OwnerAdded(address newOwner); - event OwnerRemoved(address oldOwner); - // the last one is emitted if the required signatures change - event RequirementChanged(uint newRequirement); - - // MODIFIERS - - // simple single-sig function modifier. - modifier onlyowner { - if (isOwner(msg.sender)) - _; + // Revokes a prior confirmation of the given operation + function revoke(bytes32 _operation) external { + uint ownerIndex = m_ownerIndex[uint(msg.sender)]; + // make sure they're an owner + if (ownerIndex == 0) return; + uint ownerIndexBit = 2**ownerIndex; + var pending = m_pending[_operation]; + if (pending.ownersDone & ownerIndexBit > 0) { + pending.yetNeeded++; + pending.ownersDone -= ownerIndexBit; + Revoke(msg.sender, _operation); } - // multi-sig function modifier: the operation must have an intrinsic hash in order - // that later attempts can be realised as the same underlying operation and - // thus count as confirmations. - modifier onlymanyowners(bytes32 _operation) { - if (confirmAndCheck(_operation)) - _; + } + + // Replaces an owner `_from` with another `_to`. + function changeOwner(address _from, address _to) onlymanyowners(sha3(msg.data)) external { + if (isOwner(_to)) return; + uint ownerIndex = m_ownerIndex[uint(_from)]; + if (ownerIndex == 0) return; + + clearPending(); + m_owners[ownerIndex] = uint(_to); + m_ownerIndex[uint(_from)] = 0; + m_ownerIndex[uint(_to)] = ownerIndex; + OwnerChanged(_from, _to); + } + + function addOwner(address _owner) onlymanyowners(sha3(msg.data)) external { + if (isOwner(_owner)) return; + + clearPending(); + if (m_numOwners >= c_maxOwners) + reorganizeOwners(); + if (m_numOwners >= c_maxOwners) + return; + m_numOwners++; + m_owners[m_numOwners] = uint(_owner); + m_ownerIndex[uint(_owner)] = m_numOwners; + OwnerAdded(_owner); + } + + function removeOwner(address _owner) onlymanyowners(sha3(msg.data)) external { + uint ownerIndex = m_ownerIndex[uint(_owner)]; + if (ownerIndex == 0) return; + if (m_required > m_numOwners - 1) return; + + m_owners[ownerIndex] = 0; + m_ownerIndex[uint(_owner)] = 0; + clearPending(); + reorganizeOwners(); //make sure m_numOwner is equal to the number of owners and always points to the optimal free slot + OwnerRemoved(_owner); + } + + function changeRequirement(uint _newRequired) onlymanyowners(sha3(msg.data)) external { + if (_newRequired > m_numOwners) return; + m_required = _newRequired; + clearPending(); + RequirementChanged(_newRequired); + } + + // Gets an owner by 0-indexed position (using numOwners as the count) + function getOwner(uint ownerIndex) external constant returns (address) { + return address(m_owners[ownerIndex + 1]); + } + + function isOwner(address _addr) constant returns (bool) { + return m_ownerIndex[uint(_addr)] > 0; + } + + function hasConfirmed(bytes32 _operation, address _owner) constant returns (bool) { + var pending = m_pending[_operation]; + uint ownerIndex = m_ownerIndex[uint(_owner)]; + + // make sure they're an owner + if (ownerIndex == 0) return false; + + // determine the bit to set for this owner. + uint ownerIndexBit = 2**ownerIndex; + return !(pending.ownersDone & ownerIndexBit == 0); + } + + // INTERNAL METHODS + + function confirmAndCheck(bytes32 _operation) internal returns (bool) { + // determine what index the present sender is: + uint ownerIndex = m_ownerIndex[uint(msg.sender)]; + // make sure they're an owner + if (ownerIndex == 0) return; + + var pending = m_pending[_operation]; + // if we're not yet working on this operation, switch over and reset the confirmation status. + if (pending.yetNeeded == 0) { + // reset count of confirmations needed. + pending.yetNeeded = m_required; + // reset which owners have confirmed (none) - set our bitmap to 0. + pending.ownersDone = 0; + pending.index = m_pendingIndex.length++; + m_pendingIndex[pending.index] = _operation; } - - // METHODS - - // constructor is given number of sigs required to do protected "onlymanyowners" transactions - // as well as the selection of addresses capable of confirming them. - function multiowned(address[] _owners, uint _required) { - m_numOwners = _owners.length + 1; - m_owners[1] = uint(msg.sender); - m_ownerIndex[uint(msg.sender)] = 1; - for (uint i = 0; i < _owners.length; ++i) - { - m_owners[2 + i] = uint(_owners[i]); - m_ownerIndex[uint(_owners[i])] = 2 + i; - } - m_required = _required; + // determine the bit to set for this owner. + uint ownerIndexBit = 2**ownerIndex; + // make sure we (the message sender) haven't confirmed this operation previously. + if (pending.ownersDone & ownerIndexBit == 0) { + Confirmation(msg.sender, _operation); + // ok - check if count is enough to go ahead. + if (pending.yetNeeded <= 1) { + // enough confirmations: reset and run interior. + delete m_pendingIndex[m_pending[_operation].index]; + delete m_pending[_operation]; + return true; + } + else + { + // not enough: record that this owner in particular confirmed. + pending.yetNeeded--; + pending.ownersDone |= ownerIndexBit; + } } + } - // Revokes a prior confirmation of the given operation - function revoke(bytes32 _operation) external { - uint ownerIndex = m_ownerIndex[uint(msg.sender)]; - // make sure they're an owner - if (ownerIndex == 0) return; - uint ownerIndexBit = 2**ownerIndex; - var pending = m_pending[_operation]; - if (pending.ownersDone & ownerIndexBit > 0) { - pending.yetNeeded++; - pending.ownersDone -= ownerIndexBit; - Revoke(msg.sender, _operation); - } + function reorganizeOwners() private { + uint free = 1; + while (free < m_numOwners) + { + while (free < m_numOwners && m_owners[free] != 0) free++; + while (m_numOwners > 1 && m_owners[m_numOwners] == 0) m_numOwners--; + if (free < m_numOwners && m_owners[m_numOwners] != 0 && m_owners[free] == 0) + { + m_owners[free] = m_owners[m_numOwners]; + m_ownerIndex[m_owners[free]] = free; + m_owners[m_numOwners] = 0; + } } + } - // Replaces an owner `_from` with another `_to`. - function changeOwner(address _from, address _to) onlymanyowners(sha3(msg.data)) external { - if (isOwner(_to)) return; - uint ownerIndex = m_ownerIndex[uint(_from)]; - if (ownerIndex == 0) return; + function clearPending() internal { + uint length = m_pendingIndex.length; + for (uint i = 0; i < length; ++i) + if (m_pendingIndex[i] != 0) + delete m_pending[m_pendingIndex[i]]; + delete m_pendingIndex; + } - clearPending(); - m_owners[ownerIndex] = uint(_to); - m_ownerIndex[uint(_from)] = 0; - m_ownerIndex[uint(_to)] = ownerIndex; - OwnerChanged(_from, _to); - } + // FIELDS - function addOwner(address _owner) onlymanyowners(sha3(msg.data)) external { - if (isOwner(_owner)) return; + // the number of owners that must confirm the same operation before it is run. + uint public m_required; + // pointer used to find a free slot in m_owners + uint public m_numOwners; - clearPending(); - if (m_numOwners >= c_maxOwners) - reorganizeOwners(); - if (m_numOwners >= c_maxOwners) - return; - m_numOwners++; - m_owners[m_numOwners] = uint(_owner); - m_ownerIndex[uint(_owner)] = m_numOwners; - OwnerAdded(_owner); - } - - function removeOwner(address _owner) onlymanyowners(sha3(msg.data)) external { - uint ownerIndex = m_ownerIndex[uint(_owner)]; - if (ownerIndex == 0) return; - if (m_required > m_numOwners - 1) return; - - m_owners[ownerIndex] = 0; - m_ownerIndex[uint(_owner)] = 0; - clearPending(); - reorganizeOwners(); //make sure m_numOwner is equal to the number of owners and always points to the optimal free slot - OwnerRemoved(_owner); - } - - function changeRequirement(uint _newRequired) onlymanyowners(sha3(msg.data)) external { - if (_newRequired > m_numOwners) return; - m_required = _newRequired; - clearPending(); - RequirementChanged(_newRequired); - } - - // Gets an owner by 0-indexed position (using numOwners as the count) - function getOwner(uint ownerIndex) external constant returns (address) { - return address(m_owners[ownerIndex + 1]); - } - - function isOwner(address _addr) returns (bool) { - return m_ownerIndex[uint(_addr)] > 0; - } - - function hasConfirmed(bytes32 _operation, address _owner) constant returns (bool) { - var pending = m_pending[_operation]; - uint ownerIndex = m_ownerIndex[uint(_owner)]; - - // make sure they're an owner - if (ownerIndex == 0) return false; - - // determine the bit to set for this owner. - uint ownerIndexBit = 2**ownerIndex; - return !(pending.ownersDone & ownerIndexBit == 0); - } - - // INTERNAL METHODS - - function confirmAndCheck(bytes32 _operation) internal returns (bool) { - // determine what index the present sender is: - uint ownerIndex = m_ownerIndex[uint(msg.sender)]; - // make sure they're an owner - if (ownerIndex == 0) return; - - var pending = m_pending[_operation]; - // if we're not yet working on this operation, switch over and reset the confirmation status. - if (pending.yetNeeded == 0) { - // reset count of confirmations needed. - pending.yetNeeded = m_required; - // reset which owners have confirmed (none) - set our bitmap to 0. - pending.ownersDone = 0; - pending.index = m_pendingIndex.length++; - m_pendingIndex[pending.index] = _operation; - } - // determine the bit to set for this owner. - uint ownerIndexBit = 2**ownerIndex; - // make sure we (the message sender) haven't confirmed this operation previously. - if (pending.ownersDone & ownerIndexBit == 0) { - Confirmation(msg.sender, _operation); - // ok - check if count is enough to go ahead. - if (pending.yetNeeded <= 1) { - // enough confirmations: reset and run interior. - delete m_pendingIndex[m_pending[_operation].index]; - delete m_pending[_operation]; - return true; - } - else - { - // not enough: record that this owner in particular confirmed. - pending.yetNeeded--; - pending.ownersDone |= ownerIndexBit; - } - } - } - - function reorganizeOwners() private { - uint free = 1; - while (free < m_numOwners) - { - while (free < m_numOwners && m_owners[free] != 0) free++; - while (m_numOwners > 1 && m_owners[m_numOwners] == 0) m_numOwners--; - if (free < m_numOwners && m_owners[m_numOwners] != 0 && m_owners[free] == 0) - { - m_owners[free] = m_owners[m_numOwners]; - m_ownerIndex[m_owners[free]] = free; - m_owners[m_numOwners] = 0; - } - } - } - - function clearPending() internal { - uint length = m_pendingIndex.length; - for (uint i = 0; i < length; ++i) - if (m_pendingIndex[i] != 0) - delete m_pending[m_pendingIndex[i]]; - delete m_pendingIndex; - } - - // FIELDS - - // the number of owners that must confirm the same operation before it is run. - uint public m_required; - // pointer used to find a free slot in m_owners - uint public m_numOwners; - - // list of owners - uint[256] m_owners; - uint constant c_maxOwners = 250; - // index on the list of owners to allow reverse lookup - mapping(uint => uint) m_ownerIndex; - // the ongoing operations. - mapping(bytes32 => PendingState) m_pending; - bytes32[] m_pendingIndex; + // list of owners + uint[256] m_owners; + uint constant c_maxOwners = 250; + // index on the list of owners to allow reverse lookup + mapping(uint => uint) m_ownerIndex; + // the ongoing operations. + mapping(bytes32 => PendingState) m_pending; + bytes32[] m_pendingIndex; } // inheritable "property" contract that enables methods to be protected by placing a linear limit (specifiable) @@ -230,79 +231,70 @@ contract multiowned { // uses is specified in the modifier. contract daylimit is multiowned { - // MODIFIERS + // METHODS - // simple modifier for daily limit. - modifier limitedDaily(uint _value) { - if (underLimit(_value)) - _; + // constructor - stores initial daily limit and records the present day's index. + function daylimit(uint _limit) { + m_dailyLimit = _limit; + m_lastDay = today(); + } + // (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today. + function setDailyLimit(uint _newLimit) onlymanyowners(sha3(msg.data)) external { + m_dailyLimit = _newLimit; + } + // resets the amount already spent today. needs many of the owners to confirm. + function resetSpentToday() onlymanyowners(sha3(msg.data)) external { + m_spentToday = 0; + } + + // INTERNAL METHODS + + // checks to see if there is at least `_value` left from the daily limit today. if there is, subtracts it and + // returns true. otherwise just returns false. + function underLimit(uint _value) internal onlyowner returns (bool) { + // reset the spend limit if we're on a different day to last time. + if (today() > m_lastDay) { + m_spentToday = 0; + m_lastDay = today(); } - - // METHODS - - // constructor - stores initial daily limit and records the present day's index. - function daylimit(uint _limit) { - m_dailyLimit = _limit; - m_lastDay = today(); - } - // (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today. - function setDailyLimit(uint _newLimit) onlymanyowners(sha3(msg.data)) external { - m_dailyLimit = _newLimit; - } - // resets the amount already spent today. needs many of the owners to confirm. - function resetSpentToday() onlymanyowners(sha3(msg.data)) external { - m_spentToday = 0; + // check to see if there's enough left - if so, subtract and return true. + // overflow protection // dailyLimit check + if (m_spentToday + _value >= m_spentToday && m_spentToday + _value <= m_dailyLimit) { + m_spentToday += _value; + return true; } + return false; + } + // determines today's index. + function today() private constant returns (uint) { return now / 1 days; } - // INTERNAL METHODS + // FIELDS - // checks to see if there is at least `_value` left from the daily limit today. if there is, subtracts it and - // returns true. otherwise just returns false. - function underLimit(uint _value) internal onlyowner returns (bool) { - // reset the spend limit if we're on a different day to last time. - if (today() > m_lastDay) { - m_spentToday = 0; - m_lastDay = today(); - } - // check to see if there's enough left - if so, subtract and return true. - // overflow protection // dailyLimit check - if (m_spentToday + _value >= m_spentToday && m_spentToday + _value <= m_dailyLimit) { - m_spentToday += _value; - return true; - } - return false; - } - // determines today's index. - function today() private constant returns (uint) { return now / 1 days; } - - // FIELDS - - uint public m_dailyLimit; - uint public m_spentToday; - uint public m_lastDay; + uint public m_dailyLimit; + uint public m_spentToday; + uint public m_lastDay; } // interface contract for multisig proxy contracts; see below for docs. contract multisig { - // EVENTS + // EVENTS - // logged events: - // Funds has arrived into the wallet (record how much). - event Deposit(address _from, uint value); - // Single transaction going out of the wallet (record who signed for it, how much, and to whom it's going). - event SingleTransact(address owner, uint value, address to, bytes data); - // Multi-sig transaction going out of the wallet (record who signed for it last, the operation hash, how much, and to whom it's going). - event MultiTransact(address owner, bytes32 operation, uint value, address to, bytes data); - // Confirmation still needed for a transaction. - event ConfirmationNeeded(bytes32 operation, address initiator, uint value, address to, bytes data); + // logged events: + // Funds has arrived into the wallet (record how much). + event Deposit(address _from, uint value); + // Single transaction going out of the wallet (record who signed for it, how much, and to whom it's going). + event SingleTransact(address owner, uint value, address to, bytes data, address created); + // Multi-sig transaction going out of the wallet (record who signed for it last, the operation hash, how much, and to whom it's going). + event MultiTransact(address owner, bytes32 operation, uint value, address to, bytes data, address created); + // Confirmation still needed for a transaction. + event ConfirmationNeeded(bytes32 operation, address initiator, uint value, address to, bytes data); - // FUNCTIONS + // FUNCTIONS - // TODO: document - function changeOwner(address _from, address _to) external; - function execute(address _to, uint _value, bytes _data) external returns (bytes32); - function confirm(bytes32 _h) returns (bool); + // TODO: document + function execute(address _to, uint _value, bytes _data) external returns (bytes32 o_hash); + function confirm(bytes32 _h) external returns (bool o_success); } // usage: @@ -310,79 +302,102 @@ contract multisig { // Wallet(w).from(anotherOwner).confirm(h); contract Wallet is multisig, multiowned, daylimit { - // TYPES + // TYPES - // Transaction structure to remember details of transaction lest it need be saved for a later call. - struct Transaction { - address to; - uint value; - bytes data; + // Transaction structure to remember details of transaction lest it need be saved for a later call. + struct Transaction { + address to; + uint value; + bytes data; + } + + // METHODS + + // constructor - just pass on the owner array to the multiowned and + // the limit to daylimit + function Wallet(address[] _owners, uint _required, uint _daylimit) + multiowned(_owners, _required) daylimit(_daylimit) { + } + + // kills the contract sending everything to `_to`. + function kill(address _to) onlymanyowners(sha3(msg.data)) external { + suicide(_to); + } + + // gets called when no other function matches + function() payable { + // just being sent some cash? + if (msg.value > 0) + Deposit(msg.sender, msg.value); + } + + // Outside-visible transact entry point. Executes transaction immediately if below daily spend limit. + // If not, goes into multisig process. We provide a hash on return to allow the sender to provide + // shortcuts for the other confirmations (allowing them to avoid replicating the _to, _value + // and _data arguments). They still get the option of using them if they want, anyways. + function execute(address _to, uint _value, bytes _data) external onlyowner returns (bytes32 o_hash) { + // first, take the opportunity to check that we're under the daily limit. + if ((_data.length == 0 && underLimit(_value)) || m_required == 1) { + // yes - just execute the call. + address created; + if (_to == 0) { + created = create(_value, _data); + } else { + if (!_to.call.value(_value)(_data)) + throw; + } + SingleTransact(msg.sender, _value, _to, _data, created); + } else { + // determine our operation hash. + o_hash = sha3(msg.data, block.number); + // store if it's new + if (m_txs[o_hash].to == 0 && m_txs[o_hash].value == 0 && m_txs[o_hash].data.length == 0) { + m_txs[o_hash].to = _to; + m_txs[o_hash].value = _value; + m_txs[o_hash].data = _data; + } + if (!confirm(o_hash)) { + ConfirmationNeeded(o_hash, msg.sender, _value, _to, _data); + } } + } - // METHODS - - // constructor - just pass on the owner array to the multiowned and - // the limit to daylimit - function Wallet(address[] _owners, uint _required, uint _daylimit) - multiowned(_owners, _required) daylimit(_daylimit) { + function create(uint _value, bytes _code) internal returns (address o_addr) { + assembly { + o_addr := create(_value, add(_code, 0x20), mload(_code)) + jumpi(invalidJumpLabel, iszero(extcodesize(o_addr))) } + } - // kills the contract sending everything to `_to`. - function kill(address _to) onlymanyowners(sha3(msg.data)) external { - suicide(_to); + // confirm a transaction through just the hash. we use the previous transactions map, m_txs, in order + // to determine the body of the transaction from the hash provided. + function confirm(bytes32 _h) onlymanyowners(_h) returns (bool o_success) { + if (m_txs[_h].to != 0 || m_txs[_h].value != 0 || m_txs[_h].data.length != 0) { + address created; + if (m_txs[_h].to == 0) { + created = create(m_txs[_h].value, m_txs[_h].data); + } else { + if (!m_txs[_h].to.call.value(m_txs[_h].value)(m_txs[_h].data)) + throw; + } + + MultiTransact(msg.sender, _h, m_txs[_h].value, m_txs[_h].to, m_txs[_h].data, created); + delete m_txs[_h]; + return true; } + } - // gets called when no other function matches - function() payable { - // just being sent some cash? - if (msg.value > 0) - Deposit(msg.sender, msg.value); - } + // INTERNAL METHODS - // Outside-visible transact entry point. Executes transaction immediately if below daily spend limit. - // If not, goes into multisig process. We provide a hash on return to allow the sender to provide - // shortcuts for the other confirmations (allowing them to avoid replicating the _to, _value - // and _data arguments). They still get the option of using them if they want, anyways. - function execute(address _to, uint _value, bytes _data) external onlyowner returns (bytes32 _r) { - // first, take the opportunity to check that we're under the daily limit. - if (underLimit(_value)) { - SingleTransact(msg.sender, _value, _to, _data); - // yes - just execute the call. - _to.call.value(_value)(_data); - return 0; - } - // determine our operation hash. - _r = sha3(msg.data, block.number); - if (!confirm(_r) && m_txs[_r].to == 0) { - m_txs[_r].to = _to; - m_txs[_r].value = _value; - m_txs[_r].data = _data; - ConfirmationNeeded(_r, msg.sender, _value, _to, _data); - } - } + function clearPending() internal { + uint length = m_pendingIndex.length; + for (uint i = 0; i < length; ++i) + delete m_txs[m_pendingIndex[i]]; + super.clearPending(); + } - // confirm a transaction through just the hash. we use the previous transactions map, m_txs, in order - // to determine the body of the transaction from the hash provided. - function confirm(bytes32 _h) onlymanyowners(_h) returns (bool) { - if (m_txs[_h].to != 0) { - m_txs[_h].to.call.value(m_txs[_h].value)(m_txs[_h].data); - MultiTransact(msg.sender, _h, m_txs[_h].value, m_txs[_h].to, m_txs[_h].data); - delete m_txs[_h]; - return true; - } - } + // FIELDS - // INTERNAL METHODS - - function clearPending() internal { - uint length = m_pendingIndex.length; - for (uint i = 0; i < length; ++i) - delete m_txs[m_pendingIndex[i]]; - super.clearPending(); - } - - // FIELDS - - // pending transactions we have at present. - mapping (bytes32 => Transaction) m_txs; + // pending transactions we have at present. + mapping (bytes32 => Transaction) m_txs; } diff --git a/js/src/dapps/registry/Events/events.js b/js/src/dapps/registry/Events/events.js index ae5e45176..05b138bc5 100644 --- a/js/src/dapps/registry/Events/events.js +++ b/js/src/dapps/registry/Events/events.js @@ -53,7 +53,13 @@ const renderEvent = (classNames, verb) => (e) => { return ( -
+
{ verb } @@ -80,17 +86,23 @@ const renderDataChanged = (e) => { return ( -
+
updated - { 'key ' } + key  { new Buffer(e.parameters.plainKey.value).toString('utf8') } - { 'of ' } +  of  diff --git a/js/src/dapps/registry/actions.js b/js/src/dapps/registry/actions.js index d56c76f4b..00d851a37 100644 --- a/js/src/dapps/registry/actions.js +++ b/js/src/dapps/registry/actions.js @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import { registry as registryAbi } from '~/contracts/abi'; +import { registry as registryAbi, registry2 as registryAbi2 } from '~/contracts/abi'; import { api } from './parity.js'; import * as addresses from './addresses/actions.js'; @@ -27,15 +27,17 @@ import * as reverse from './Reverse/actions.js'; export { addresses, accounts, lookup, events, names, records, reverse }; -export const setIsTestnet = (isTestnet) => ({ type: 'set isTestnet', isTestnet }); +const REGISTRY_V1_HASHES = [ + '0x34f7c51bbb1b1902fbdabfdf04811100f5c9f998f26dd535d2f6f977492c748e', // ropsten + '0x64c3ee34851517a9faecd995c102b339f03e564ad6772dc43a26f993238b20ec' // homestead +]; + +export const setNetVersion = (netVersion) => ({ type: 'set netVersion', netVersion }); export const fetchIsTestnet = () => (dispatch) => api.net.version() .then((netVersion) => { - dispatch(setIsTestnet( - netVersion === '2' || // morden - netVersion === '3' // ropsten - )); + dispatch(setNetVersion(netVersion)); }) .catch((err) => { console.error('could not check if testnet'); @@ -47,13 +49,28 @@ export const fetchIsTestnet = () => (dispatch) => export const setContract = (contract) => ({ type: 'set contract', contract }); export const fetchContract = () => (dispatch) => - api.parity.registryAddress() + api.parity + .registryAddress() .then((address) => { - const contract = api.newContract(registryAbi, address); + return api.eth + .getCode(address) + .then((code) => { + const codeHash = api.util.sha3(code); + const isVersion1 = REGISTRY_V1_HASHES.includes(codeHash); - dispatch(setContract(contract)); - dispatch(fetchFee()); - dispatch(fetchOwner()); + console.log(`registry at ${address}, code ${codeHash}, version ${isVersion1 ? 1 : 2}`); + + const contract = api.newContract( + isVersion1 + ? registryAbi + : registryAbi2, + address + ); + + dispatch(setContract(contract)); + dispatch(fetchFee()); + dispatch(fetchOwner()); + }); }) .catch((err) => { console.error('could not fetch contract'); diff --git a/js/src/dapps/registry/reducers.js b/js/src/dapps/registry/reducers.js index cf5c82f02..6d0816273 100644 --- a/js/src/dapps/registry/reducers.js +++ b/js/src/dapps/registry/reducers.js @@ -22,8 +22,8 @@ import namesReducer from './Names/reducers.js'; import recordsReducer from './Records/reducers.js'; import reverseReducer from './Reverse/reducers.js'; -const isTestnetReducer = (state = null, action) => - action.type === 'set isTestnet' ? action.isTestnet : state; +const netVersionReducer = (state = null, action) => + action.type === 'set netVersion' ? action.netVersion : state; const contractReducer = (state = null, action) => action.type === 'set contract' ? action.contract : state; @@ -35,7 +35,7 @@ const ownerReducer = (state = null, action) => action.type === 'set owner' ? action.owner : state; const initialState = { - isTestnet: isTestnetReducer(undefined, { type: '' }), + netVersion: netVersionReducer(undefined, { type: '' }), accounts: accountsReducer(undefined, { type: '' }), contacts: contactsReducer(undefined, { type: '' }), contract: contractReducer(undefined, { type: '' }), @@ -49,7 +49,7 @@ const initialState = { }; export default (state = initialState, action) => ({ - isTestnet: isTestnetReducer(state.isTestnet, action), + netVersion: netVersionReducer(state.netVersion, action), accounts: accountsReducer(state.accounts, action), contacts: contactsReducer(state.contacts, action), contract: contractReducer(state.contract, action), diff --git a/js/src/dapps/registry/ui/address.js b/js/src/dapps/registry/ui/address.js index ab0500532..a01811fc4 100644 --- a/js/src/dapps/registry/ui/address.js +++ b/js/src/dapps/registry/ui/address.js @@ -28,7 +28,7 @@ class Address extends Component { static propTypes = { address: PropTypes.string.isRequired, account: nullableProptype(PropTypes.object.isRequired), - isTestnet: PropTypes.bool.isRequired, + netVersion: PropTypes.string.isRequired, key: PropTypes.string, shortenHash: PropTypes.bool }; @@ -56,7 +56,7 @@ class Address extends Component { } renderCaption () { - const { address, account, isTestnet, shortenHash } = this.props; + const { address, account, netVersion, shortenHash } = this.props; if (account) { const { name } = account; @@ -64,7 +64,7 @@ class Address extends Component { return ( { - const { isTestnet } = state; + const { netVersion } = state; const { address = '' } = props; const account = allAccounts[address] || null; return { account, - isTestnet + netVersion }; }; } diff --git a/js/src/dapps/registry/ui/hash.js b/js/src/dapps/registry/ui/hash.js index 88099fcf0..fe404f5b2 100644 --- a/js/src/dapps/registry/ui/hash.js +++ b/js/src/dapps/registry/ui/hash.js @@ -26,7 +26,7 @@ const leading0x = /^0x/; class Hash extends Component { static propTypes = { hash: PropTypes.string.isRequired, - isTestnet: PropTypes.bool.isRequired, + netVersion: PropTypes.string.isRequired, linked: PropTypes.bool } @@ -35,7 +35,7 @@ class Hash extends Component { } render () { - const { hash, isTestnet, linked } = this.props; + const { hash, netVersion, linked } = this.props; let shortened = hash.toLowerCase().replace(leading0x, ''); @@ -47,7 +47,7 @@ class Hash extends Component { return ( { shortened } @@ -61,7 +61,7 @@ class Hash extends Component { export default connect( (state) => ({ // mapStateToProps - isTestnet: state.isTestnet + netVersion: state.netVersion }), null // mapDispatchToProps )(Hash); diff --git a/js/src/dapps/registry/util/etherscan-url.js b/js/src/dapps/registry/util/etherscan-url.js index 88094a911..bb4e2fe98 100644 --- a/js/src/dapps/registry/util/etherscan-url.js +++ b/js/src/dapps/registry/util/etherscan-url.js @@ -14,13 +14,15 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +import { url as externalUrl } from '~/3rdparty/etherscan/links'; + const leading0x = /^0x/; -const etherscanUrl = (hash, isTestnet) => { +const etherscanUrl = (hash, isTestnet, netVersion) => { hash = hash.toLowerCase().replace(leading0x, ''); const type = hash.length === 40 ? 'address' : 'tx'; - return `https://${isTestnet ? 'testnet.' : ''}etherscan.io/${type}/0x${hash}`; + return `https://${externalUrl(isTestnet, netVersion)}/${type}/0x${hash}`; }; export default etherscanUrl; diff --git a/js/src/dapps/tokendeploy/services.js b/js/src/dapps/tokendeploy/services.js index 6853c8ac4..9ca4c4f56 100644 --- a/js/src/dapps/tokendeploy/services.js +++ b/js/src/dapps/tokendeploy/services.js @@ -16,6 +16,7 @@ import BigNumber from 'bignumber.js'; +import { url as etherscanUrl } from '~/3rdparty/etherscan/links'; import * as abis from '~/contracts/abi'; import { api } from './parity'; @@ -28,7 +29,7 @@ const subscriptions = {}; let defaultSubscriptionId; let nextSubscriptionId = 1000; -let isTest = false; +let netVersion = '0'; export function subscribeEvents (addresses, callback) { const subscriptionId = nextSubscriptionId++; @@ -117,15 +118,16 @@ export function attachInstances () { return Promise .all([ api.parity.registryAddress(), - api.parity.netChain() + api.parity.netChain(), + api.partiy.netVersion() ]) - .then(([registryAddress, netChain]) => { + .then(([registryAddress, netChain, _netVersion]) => { const registry = api.newContract(abis.registry, registryAddress).instance; - isTest = ['morden', 'ropsten', 'testnet'].includes(netChain); + netVersion = _netVersion; console.log(`contract was found at registry=${registryAddress}`); - console.log(`running on ${netChain}, isTest=${isTest}`); + console.log(`running on ${netChain}, network ${netVersion}`); return Promise .all([ @@ -287,5 +289,5 @@ export function loadTokenBalance (tokenAddress, address) { } export function txLink (txHash) { - return `https://${isTest ? 'testnet.' : ''}etherscan.io/tx/${txHash}`; + return `https://${etherscanUrl(false, netVersion)}/tx/${txHash}`; } diff --git a/js/src/dapps/tokenreg/Chip/chip.js b/js/src/dapps/tokenreg/Chip/chip.js index 1fa16c774..7c19a671c 100644 --- a/js/src/dapps/tokenreg/Chip/chip.js +++ b/js/src/dapps/tokenreg/Chip/chip.js @@ -18,7 +18,7 @@ import React, { Component, PropTypes } from 'react'; import { Chip } from 'material-ui'; -import IdentityIcon from '../IdentityIcon' ; +import IdentityIcon from '../IdentityIcon'; import styles from './chip.css'; diff --git a/js/src/i18n/_default/connection.js b/js/src/i18n/_default/connection.js index 10f330618..e51943178 100644 --- a/js/src/i18n/_default/connection.js +++ b/js/src/i18n/_default/connection.js @@ -18,7 +18,7 @@ export default { connectingAPI: `Connecting to the Parity Secure API.`, connectingNode: `Connecting to the Parity Node. If this informational message persists, please ensure that your Parity node is running and reachable on the network.`, invalidToken: `invalid signer token`, - noConnection: `Unable to make a connection to the Parity Secure API. To update your secure token or to generate a new one, run {newToken} and supply the token below`, + noConnection: `Unable to make a connection to the Parity Secure API. To update your secure token or to generate a new one, run {newToken} and paste the generated token into the space below.`, token: { hint: `a generated token from Parity`, label: `secure token` diff --git a/js/src/jsonrpc/interfaces/eth.js b/js/src/jsonrpc/interfaces/eth.js index 14003f4f2..6489a62ac 100644 --- a/js/src/jsonrpc/interfaces/eth.js +++ b/js/src/jsonrpc/interfaces/eth.js @@ -97,54 +97,6 @@ The following options are possible for the \`defaultBlock\` parameter: } }, - compileSerpent: { - desc: 'Returns compiled serpent code.', - params: [ - { - type: String, - desc: 'The source code.', - example: '/* some serpent */' - } - ], - returns: { - type: Data, - desc: 'The compiled source code.', - example: '0x603880600c6000396000f3006001600060e060020a600035048063c6888fa114601857005b6021600435602b565b8060005260206000f35b600081600702905091905056' - } - }, - - compileSolidity: { - desc: 'Returns compiled solidity code.', - params: [ - { - type: String, - desc: 'The source code.', - example: 'contract test { function multiply(uint a) returns(uint d) { return a * 7; } }' - } - ], - returns: { - type: Data, - desc: 'The compiled source code.', - example: '0x605880600c6000396000f3006000357c010000000000000000000000000000000000000000000000000000000090048063c6888fa114602e57005b603d6004803590602001506047565b8060005260206000f35b60006007820290506053565b91905056' - } - }, - - compileLLL: { - desc: 'Returns compiled LLL code.', - params: [ - { - type: String, - desc: 'The source code.', - example: '(returnlll (suicide (caller)))' - } - ], - returns: { - type: Data, - desc: 'The compiled source code.', - example: '0x603880600c6000396000f3006001600060e060020a600035048063c6888fa114601857005b6021600435602b565b8060005260206000f35b600081600702905091905056' - } - }, - estimateGas: { desc: 'Makes a call or transaction, which won\'t be added to the blockchain and returns the used gas, which can be used for estimating the used gas.', params: [ @@ -415,16 +367,6 @@ The following options are possible for the \`defaultBlock\` parameter: } }, - getCompilers: { - desc: 'Returns a list of available compilers in the client.', - params: [], - returns: { - type: Array, - desc: 'Array of available compilers.', - example: ['solidity', 'lll', 'serpent'] - } - }, - getFilterChanges: { desc: 'Polling method for a filter, which returns an array of logs which occurred since last poll.', params: [ diff --git a/js/src/jsonrpc/interfaces/parity.js b/js/src/jsonrpc/interfaces/parity.js index 2fbec7aaa..cf62213a5 100644 --- a/js/src/jsonrpc/interfaces/parity.js +++ b/js/src/jsonrpc/interfaces/parity.js @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import { Address, Data, Hash, Quantity, BlockNumber, TransactionRequest } from '../types'; +import { Address, Data, Hash, Quantity, BlockNumber, TransactionRequest, TransactionResponse } from '../types'; import { fromDecimal, withComment, Dummy } from '../helpers'; const SECTION_ACCOUNTS = 'Accounts (read-only) and Signatures'; @@ -27,86 +27,6 @@ const SECTION_VAULT = 'Account Vaults'; const SUBDOC_SET = 'set'; const SUBDOC_ACCOUNTS = 'accounts'; -const transactionDetails = { - hash: { - type: Hash, - desc: '32 Bytes - hash of the transaction.' - }, - nonce: { - type: Quantity, - desc: 'The number of transactions made by the sender prior to this one.' - }, - blockHash: { - type: Hash, - desc: '32 Bytes - hash of the block where this transaction was in. `null` when its pending.' - }, - blockNumber: { - type: BlockNumber, - desc: 'Block number where this transaction was in. `null` when its pending.' - }, - transactionIndex: { - type: Quantity, - desc: 'Integer of the transactions index position in the block. `null` when its pending.' - }, - from: { - type: Address, - desc: '20 Bytes - address of the sender.' - }, - to: { - type: Address, - desc: '20 Bytes - address of the receiver. `null` when its a contract creation transaction.' - }, - value: { - type: Quantity, - desc: 'Value transferred in Wei.' - }, - gasPrice: { - type: Quantity, - desc: 'Gas price provided by the sender in Wei.' - }, - gas: { - type: Quantity, - desc: 'Gas provided by the sender.' - }, - input: { - type: Data, - desc: 'The data send along with the transaction.' - }, - raw: { - type: Data, - desc: 'Raw transaction data.' - }, - publicKey: { - type: Data, - desc: 'Public key of the signer.' - }, - networkId: { - type: Quantity, - desc: 'The network id of the transaction, if any.' - }, - standardV: { - type: Quantity, - desc: 'The standardized V field of the signature (0 or 1).' - }, - v: { - type: Quantity, - desc: 'The V field of the signature.' - }, - r: { - type: Quantity, - desc: 'The R field of the signature.' - }, - s: { - type: Quantity, - desc: 'The S field of the signature.' - }, - condition: { - type: Object, - optional: true, - desc: 'Conditional submission, Block number in `block` or timestamp in `time` or `null`.' - } -}; - export default { accountsInfo: { section: SECTION_ACCOUNTS, @@ -393,6 +313,32 @@ export default { } }, + hardwareAccountsInfo: { + section: SECTION_ACCOUNTS, + desc: 'Provides metadata for attached hardware wallets', + params: [], + returns: { + type: Object, + desc: 'Maps account address to metadata.', + details: { + manufacturer: { + type: String, + desc: 'Manufacturer' + }, + name: { + type: String, + desc: 'Account name' + } + }, + example: { + '0x0024d0c7ab4c52f723f3aaf0872b9ea4406846a4': { + manufacturer: 'Ledger', + name: 'Nano S' + } + } + } + }, + listOpenedVaults: { desc: 'Returns a list of all opened vaults', params: [], @@ -608,7 +554,7 @@ export default { returns: { type: Array, desc: 'Transactions ordered by priority', - details: transactionDetails, + details: TransactionResponse.details, example: [ { blockHash: null, @@ -924,7 +870,7 @@ export default { returns: { type: Array, desc: 'Transaction list.', - details: transactionDetails, + details: TransactionResponse.details, example: [ { hash: '0x80de421cd2e7e46824a91c343ca42b2ff339409eef09e2d9d73882462f8fce31', diff --git a/js/src/jsonrpc/types.js b/js/src/jsonrpc/types.js index 4f5a085ce..8803fdd5c 100644 --- a/js/src/jsonrpc/types.js +++ b/js/src/jsonrpc/types.js @@ -109,3 +109,92 @@ export class TransactionRequest { } } } + +export class TransactionResponse { + static print = '`Object`'; + + static details = { + hash: { + type: Hash, + desc: '32 Bytes - hash of the transaction.' + }, + nonce: { + type: Quantity, + desc: 'The number of transactions made by the sender prior to this one.' + }, + blockHash: { + type: Hash, + desc: '32 Bytes - hash of the block where this transaction was in. `null` when its pending.' + }, + blockNumber: { + type: BlockNumber, + desc: 'Block number where this transaction was in. `null` when its pending.' + }, + transactionIndex: { + type: Quantity, + desc: 'Integer of the transactions index position in the block. `null` when its pending.' + }, + from: { + type: Address, + desc: '20 Bytes - address of the sender.' + }, + to: { + type: Address, + desc: '20 Bytes - address of the receiver. `null` when its a contract creation transaction.' + }, + value: { + type: Quantity, + desc: 'Value transferred in Wei.' + }, + gasPrice: { + type: Quantity, + desc: 'Gas price provided by the sender in Wei.' + }, + gas: { + type: Quantity, + desc: 'Gas provided by the sender.' + }, + input: { + type: Data, + desc: 'The data send along with the transaction.' + }, + creates: { + type: Address, + optional: true, + desc: 'Address of a created contract or `null`.' + }, + raw: { + type: Data, + desc: 'Raw transaction data.' + }, + publicKey: { + type: Data, + desc: 'Public key of the signer.' + }, + networkId: { + type: Quantity, + desc: 'The network id of the transaction, if any.' + }, + standardV: { + type: Quantity, + desc: 'The standardized V field of the signature (0 or 1).' + }, + v: { + type: Quantity, + desc: 'The V field of the signature.' + }, + r: { + type: Quantity, + desc: 'The R field of the signature.' + }, + s: { + type: Quantity, + desc: 'The S field of the signature.' + }, + condition: { + type: Object, + optional: true, + desc: 'Conditional submission, Block number in `block` or timestamp in `time` or `null`.' + } + } +} diff --git a/js/src/mobx/hardwareStore.js b/js/src/mobx/hardwareStore.js new file mode 100644 index 000000000..65213ad4e --- /dev/null +++ b/js/src/mobx/hardwareStore.js @@ -0,0 +1,159 @@ +// Copyright 2015-2017 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 . + +import { action, computed, observable, transaction } from 'mobx'; + +import Ledger from '~/3rdparty/ledger'; + +const HW_SCAN_INTERVAL = 5000; +let instance = null; + +export default class HardwareStore { + @observable isScanning = false; + @observable wallets = {}; + + constructor (api) { + this._api = api; + this._ledger = Ledger.create(api); + this._pollId = null; + + this._pollScan(); + } + + isConnected (address) { + return computed(() => !!this.wallets[address]).get(); + } + + @action setScanning = (isScanning) => { + this.isScanning = isScanning; + } + + @action setWallets = (wallets) => { + this.wallets = wallets; + } + + _pollScan = () => { + this._pollId = setTimeout(() => { + this.scan().then(this._pollScan); + }, HW_SCAN_INTERVAL); + } + + scanLedger () { + if (!this._ledger.isSupported) { + return Promise.resolve({}); + } + + return this._ledger + .scan() + .then((wallets) => { + console.log('HardwareStore::scanLedger', wallets); + + return wallets.reduce((hwInfo, wallet) => { + wallet.manufacturer = 'Ledger'; + wallet.name = 'Nano S'; + wallet.via = 'ledger'; + + hwInfo[wallet.address] = wallet; + + return hwInfo; + }, {}); + }) + .catch((error) => { + console.warn('HardwareStore::scanLedger', error); + + return {}; + }); + } + + scanParity () { + return this._api.parity + .hardwareAccountsInfo() + .then((hwInfo) => { + Object + .keys(hwInfo) + .forEach((address) => { + const info = hwInfo[address]; + + info.address = address; + info.via = 'parity'; + }); + + return hwInfo; + }) + .catch((error) => { + console.warn('HardwareStore::scanParity', error); + + return {}; + }); + } + + scan () { + this.setScanning(true); + + // NOTE: Depending on how the hardware is configured and how the local env setup + // is done, different results will be retrieved via Parity vs. the browser APIs + // (latter is Chrome-only, needs the browser app enabled on a Ledger, former is + // not intended as a network call, i.e. hw wallet is with the user) + return Promise + .all([ + this.scanParity(), + this.scanLedger() + ]) + .then(([hwAccounts, ledgerAccounts]) => { + transaction(() => { + this.setWallets(Object.assign({}, hwAccounts, ledgerAccounts)); + this.setScanning(false); + }); + }); + } + + createAccountInfo (entry) { + const { address, manufacturer, name } = entry; + + return Promise + .all([ + this._api.parity.setAccountName(address, name), + this._api.parity.setAccountMeta(address, { + description: `${manufacturer} ${name}`, + hardware: { + manufacturer + }, + tags: ['hardware'], + timestamp: Date.now() + }) + ]) + .catch((error) => { + console.warn('HardwareStore::createEntry', error); + throw error; + }); + } + + signLedger (transaction) { + return this._ledger.signTransaction(transaction); + } + + static get (api) { + if (!instance) { + instance = new HardwareStore(api); + } + + return instance; + } +} + +export { + HW_SCAN_INTERVAL +}; diff --git a/js/src/mobx/hardwareStore.spec.js b/js/src/mobx/hardwareStore.spec.js new file mode 100644 index 000000000..14feb5740 --- /dev/null +++ b/js/src/mobx/hardwareStore.spec.js @@ -0,0 +1,220 @@ +// Copyright 2015-2017 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 . + +import sinon from 'sinon'; + +import HardwareStore, { HW_SCAN_INTERVAL } from './hardwareStore'; + +const ADDRESS = '0x1234567890123456789012345678901234567890'; +const WALLET = { + address: ADDRESS, + name: 'testing' +}; + +let api; +let clock; +let ledger; +let store; + +function createApi () { + api = { + parity: { + hardwareAccountsInfo: sinon.stub().resolves({ ADDRESS: WALLET }), + setAccountMeta: sinon.stub().resolves(true), + setAccountName: sinon.stub().resolves(true) + } + }; + + return api; +} + +function createLedger () { + ledger = { + isSupported: true, + getAppConfiguration: sinon.stub().resolves(), + scan: sinon.stub().resolves(WALLET), + signTransaction: sinon.stub().resolves() + }; + + return ledger; +} + +function create () { + clock = sinon.useFakeTimers(); + store = new HardwareStore(createApi()); + store._ledger = createLedger(); + + return store; +} + +function teardown () { + clock.restore(); +} + +describe('mobx/HardwareStore', () => { + beforeEach(() => { + create(); + }); + + afterEach(() => { + teardown(); + }); + + describe('@computed', () => { + describe('isConnected', () => { + beforeEach(() => { + store.setWallets({ [ADDRESS]: WALLET }); + }); + + it('returns true for available', () => { + expect(store.isConnected(ADDRESS)).to.be.true; + }); + + it('returns false for non-available', () => { + expect(store.isConnected('nothing')).to.be.false; + }); + }); + }); + + describe('background polling', () => { + let pollId; + + beforeEach(() => { + pollId = store._pollId; + sinon.spy(store, 'scan'); + }); + + afterEach(() => { + store.scan.restore(); + }); + + it('starts the polling at creation', () => { + expect(pollId).not.to.be.null; + }); + + it('scans when timer elapsed', () => { + expect(store.scan).not.to.have.been.called; + clock.tick(HW_SCAN_INTERVAL + 1); + expect(store.scan).to.have.been.called; + }); + }); + + describe('@action', () => { + describe('setScanning', () => { + it('sets the flag', () => { + store.setScanning('testScanning'); + expect(store.isScanning).to.equal('testScanning'); + }); + }); + + describe('setWallets', () => { + it('sets the wallets', () => { + store.setWallets('testWallet'); + expect(store.wallets).to.equal('testWallet'); + }); + }); + }); + + describe('operations', () => { + describe('createAccountInfo', () => { + beforeEach(() => { + return store.createAccountInfo({ + address: 'testAddr', + manufacturer: 'testMfg', + name: 'testName' + }); + }); + + it('calls into parity_setAccountName', () => { + expect(api.parity.setAccountName).to.have.been.calledWith('testAddr', 'testName'); + }); + + it('calls into parity_setAccountMeta', () => { + expect(api.parity.setAccountMeta).to.have.been.calledWith('testAddr', sinon.match({ + description: 'testMfg testName', + hardware: { + manufacturer: 'testMfg' + } + })); + }); + }); + + describe('scanLedger', () => { + beforeEach(() => { + return store.scanLedger(); + }); + + it('calls scan on the Ledger APIs', () => { + expect(ledger.scan).to.have.been.called; + }); + }); + + describe('scanParity', () => { + beforeEach(() => { + return store.scanParity(); + }); + + it('calls parity_hardwareAccountsInfo', () => { + expect(api.parity.hardwareAccountsInfo).to.have.been.called; + }); + }); + + describe('scan', () => { + beforeEach(() => { + sinon.spy(store, 'setScanning'); + sinon.spy(store, 'setWallets'); + sinon.spy(store, 'scanLedger'); + sinon.spy(store, 'scanParity'); + + return store.scan(); + }); + + afterEach(() => { + store.setScanning.restore(); + store.setWallets.restore(); + store.scanLedger.restore(); + store.scanParity.restore(); + }); + + it('calls scanLedger', () => { + expect(store.scanLedger).to.have.been.called; + }); + + it('calls scanParity', () => { + expect(store.scanParity).to.have.been.called; + }); + + it('sets and resets the scanning state', () => { + expect(store.setScanning).to.have.been.calledWith(true); + expect(store.setScanning).to.have.been.calledWith(false); + }); + + it('sets the wallets', () => { + expect(store.setWallets).to.have.been.called; + }); + }); + + describe('signLedger', () => { + beforeEach(() => { + return store.signLedger('testTx'); + }); + + it('calls signTransaction on the ledger', () => { + expect(ledger.signTransaction).to.have.been.calledWith('testTx'); + }); + }); + }); +}); diff --git a/js/src/views/historyStore.js b/js/src/mobx/historyStore.js similarity index 100% rename from js/src/views/historyStore.js rename to js/src/mobx/historyStore.js diff --git a/js/src/views/historyStore.spec.js b/js/src/mobx/historyStore.spec.js similarity index 98% rename from js/src/views/historyStore.spec.js rename to js/src/mobx/historyStore.spec.js index 446aea68b..b791eb036 100644 --- a/js/src/views/historyStore.spec.js +++ b/js/src/mobx/historyStore.spec.js @@ -29,7 +29,7 @@ function create () { return store; } -describe('views/HistoryStore', () => { +describe('mobx/HistoryStore', () => { beforeEach(() => { create(); }); diff --git a/js/src/modals/CreateAccount/ChangeVault/changeVault.js b/js/src/modals/CreateAccount/ChangeVault/changeVault.js new file mode 100644 index 000000000..566fa402c --- /dev/null +++ b/js/src/modals/CreateAccount/ChangeVault/changeVault.js @@ -0,0 +1,51 @@ +// Copyright 2015-2017 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 . + +import { observer } from 'mobx-react'; +import React, { Component, PropTypes } from 'react'; + +import { VaultSelect } from '~/ui'; + +@observer +export default class ChangeVault extends Component { + static propTypes = { + store: PropTypes.object.isRequired, + vaultStore: PropTypes.object + } + + render () { + const { store, vaultStore } = this.props; + const { vaultName } = store; + + if (!vaultStore || vaultStore.vaultsOpened.length === 0) { + return null; + } + + return ( + + ); + } + + onSelect = (vaultName) => { + const { store } = this.props; + + store.setVaultName(vaultName); + } +} diff --git a/js/src/modals/CreateAccount/ChangeVault/changeVault.spec.js b/js/src/modals/CreateAccount/ChangeVault/changeVault.spec.js new file mode 100644 index 000000000..a2fcb834b --- /dev/null +++ b/js/src/modals/CreateAccount/ChangeVault/changeVault.spec.js @@ -0,0 +1,100 @@ +// Copyright 2015-2017 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 . + +import { shallow } from 'enzyme'; +import React from 'react'; +import sinon from 'sinon'; + +import ChangeVault from './'; + +let component; +let instance; +let store; +let vaultStore; + +function createStore () { + store = { + setVaultName: sinon.stub(), + vaultName: 'testing' + }; + + return store; +} + +function createVaultStore () { + vaultStore = { + vaultsOpened: ['testing'] + }; + + return vaultStore; +} + +function render () { + component = shallow( + + ); + instance = component.instance(); + + return component; +} + +describe('modals/CreateAccount/ChangeVault', () => { + beforeEach(() => { + render(); + }); + + it('renders defaults', () => { + expect(component).to.be.ok; + }); + + describe('components', () => { + describe('VaultSelect', () => { + let select; + + beforeEach(() => { + select = component.find('VaultSelect'); + }); + + it('renders', () => { + expect(select.get(0)).to.be.ok; + }); + + it('passes onSelect as instance method', () => { + expect(select.props().onSelect).to.equal(instance.onSelect); + }); + + it('passes the value', () => { + expect(select.props().value).to.equal('testing'); + }); + + it('passes the vaultStore', () => { + expect(select.props().vaultStore).to.equal(vaultStore); + }); + }); + }); + + describe('instance methods', () => { + describe('onSelect', () => { + it('calls into store setVaultName', () => { + instance.onSelect('newName'); + expect(store.setVaultName).to.have.been.calledWith('newName'); + }); + }); + }); +}); diff --git a/js/src/modals/CreateAccount/ChangeVault/index.js b/js/src/modals/CreateAccount/ChangeVault/index.js new file mode 100644 index 000000000..5eac8b21d --- /dev/null +++ b/js/src/modals/CreateAccount/ChangeVault/index.js @@ -0,0 +1,17 @@ +// Copyright 2015-2017 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 . + +export default from './changeVault'; diff --git a/js/src/modals/CreateAccount/NewAccount/newAccount.js b/js/src/modals/CreateAccount/NewAccount/newAccount.js index 3bf34b27e..e0a2f8ba2 100644 --- a/js/src/modals/CreateAccount/NewAccount/newAccount.js +++ b/js/src/modals/CreateAccount/NewAccount/newAccount.js @@ -24,13 +24,15 @@ import { Form, Input, IdentityIcon } from '~/ui'; import PasswordStrength from '~/ui/Form/PasswordStrength'; import { RefreshIcon } from '~/ui/Icons'; +import ChangeVault from '../ChangeVault'; import styles from '../createAccount.css'; @observer export default class CreateAccount extends Component { static propTypes = { newError: PropTypes.func.isRequired, - store: PropTypes.object.isRequired + store: PropTypes.object.isRequired, + vaultStore: PropTypes.object } state = { @@ -123,6 +125,10 @@ export default class CreateAccount extends Component { + { this.renderIdentitySelector() } { this.renderIdentities() } diff --git a/js/src/modals/CreateAccount/NewImport/newImport.js b/js/src/modals/CreateAccount/NewImport/newImport.js index 7f4a6bd1a..e3d888c3f 100644 --- a/js/src/modals/CreateAccount/NewImport/newImport.js +++ b/js/src/modals/CreateAccount/NewImport/newImport.js @@ -14,27 +14,25 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import { FloatingActionButton } from 'material-ui'; import { observer } from 'mobx-react'; import React, { Component, PropTypes } from 'react'; -import ReactDOM from 'react-dom'; import { FormattedMessage } from 'react-intl'; -import { Form, Input } from '~/ui'; -import { AttachFileIcon } from '~/ui/Icons'; +import { Form, FileSelect, Input } from '~/ui'; +import ChangeVault from '../ChangeVault'; import styles from '../createAccount.css'; -const STYLE_HIDDEN = { display: 'none' }; - @observer export default class NewImport extends Component { static propTypes = { - store: PropTypes.object.isRequired + store: PropTypes.object.isRequired, + vaultStore: PropTypes.object + } render () { - const { name, nameError, password, passwordHint, walletFile, walletFileError } = this.props.store; + const { name, nameError, password, passwordHint } = this.props.store; return (
@@ -93,58 +91,52 @@ export default class NewImport extends Component { /> -
- - } - label={ - - } - value={ walletFile } - /> -
- - - - -
-
+ + { this.renderFileSelector() } ); } - onFileChange = (event) => { - const { store } = this.props; + renderFileSelector () { + const { walletFile, walletFileError } = this.props.store; - if (event.target.files.length) { - const reader = new FileReader(); - - reader.onload = (event) => store.setWalletJson(event.target.result); - reader.readAsText(event.target.files[0]); - } - - store.setWalletFile(event.target.value); + return walletFile + ? ( + + } + label={ + + } + value={ walletFile } + /> + ) + : ( + + ); } - openFileDialog = () => { - ReactDOM.findDOMNode(this.refs.fileUpload).click(); + onFileSelect = (fileName, fileContent) => { + const { store } = this.props; + + store.setWalletFile(fileName); + store.setWalletJson(fileContent); } onEditName = (event, name) => { diff --git a/js/src/modals/CreateAccount/RawKey/rawKey.js b/js/src/modals/CreateAccount/RawKey/rawKey.js index ad96064bd..7f31cb066 100644 --- a/js/src/modals/CreateAccount/RawKey/rawKey.js +++ b/js/src/modals/CreateAccount/RawKey/rawKey.js @@ -21,6 +21,7 @@ import { FormattedMessage } from 'react-intl'; import { Form, Input } from '~/ui'; import PasswordStrength from '~/ui/Form/PasswordStrength'; +import ChangeVault from '../ChangeVault'; import styles from '../createAccount.css'; @observer @@ -30,7 +31,8 @@ export default class RawKey extends Component { } static propTypes = { - store: PropTypes.object.isRequired + store: PropTypes.object.isRequired, + vaultStore: PropTypes.object } render () { @@ -131,6 +133,10 @@ export default class RawKey extends Component { + ); } diff --git a/js/src/modals/CreateAccount/RecoveryPhrase/recoveryPhrase.js b/js/src/modals/CreateAccount/RecoveryPhrase/recoveryPhrase.js index 894daa767..1e49f821f 100644 --- a/js/src/modals/CreateAccount/RecoveryPhrase/recoveryPhrase.js +++ b/js/src/modals/CreateAccount/RecoveryPhrase/recoveryPhrase.js @@ -22,12 +22,14 @@ import { Checkbox } from 'material-ui'; import { Form, Input } from '~/ui'; import PasswordStrength from '~/ui/Form/PasswordStrength'; +import ChangeVault from '../ChangeVault'; import styles from '../createAccount.css'; @observer export default class RecoveryPhrase extends Component { static propTypes = { - store: PropTypes.object.isRequired + store: PropTypes.object.isRequired, + vaultStore: PropTypes.object } render () { @@ -127,6 +129,10 @@ export default class RecoveryPhrase extends Component { + div { - margin-right: 0.5em; -} - .checkbox { margin-top: 2em; } @@ -120,6 +109,7 @@ display: flex; .icon { + color: rgb(167, 151, 0) !important; flex: 0 0 56px; height: 56px !important; margin-right: 0.75em; @@ -131,6 +121,11 @@ } } +.fileImport { + height: 9em; + margin-top: 1em; +} + .summary { line-height: 1.618em; padding: 0 4em 1.5em 4em; diff --git a/js/src/modals/CreateAccount/createAccount.js b/js/src/modals/CreateAccount/createAccount.js index c2db3d060..d89c2afa8 100644 --- a/js/src/modals/CreateAccount/createAccount.js +++ b/js/src/modals/CreateAccount/createAccount.js @@ -20,11 +20,13 @@ import { FormattedMessage } from 'react-intl'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; +import ParityLogo from '~/../assets/images/parity-logo-black-no-text.svg'; import { createIdentityImg } from '~/api/util/identity'; import { newError } from '~/redux/actions'; import { Button, ModalBox, Portal } from '~/ui'; import { CancelIcon, CheckIcon, DoneIcon, NextIcon, PrevIcon, PrintIcon } from '~/ui/Icons'; -import ParityLogo from '~/../assets/images/parity-logo-black-no-text.svg'; + +import VaultStore from '~/views/Vaults/store'; import AccountDetails from './AccountDetails'; import AccountDetailsGeth from './AccountDetailsGeth'; @@ -82,13 +84,19 @@ class CreateAccount extends Component { } store = new Store(this.context.api, this.props.accounts); + vaultStore = VaultStore.get(this.context.api); + + componentWillMount () { + return this.vaultStore.loadVaults(); + } render () { - const { createType, stage } = this.store; + const { isBusy, createType, stage } = this.store; return ( ); } @@ -132,18 +141,27 @@ class CreateAccount extends Component { if (createType === 'fromPhrase') { return ( - + ); } if (createType === 'fromRaw') { return ( - + ); } return ( - + ); case STAGE_INFO: @@ -266,7 +284,7 @@ class CreateAccount extends Component { this.store.setBusy(true); return this.store - .createAccount() + .createAccount(this.vaultStore) .then(() => { this.store.setBusy(false); this.store.nextStage(); diff --git a/js/src/modals/CreateAccount/createAccount.test.js b/js/src/modals/CreateAccount/createAccount.test.js index d66729aec..d4ba5a0a4 100644 --- a/js/src/modals/CreateAccount/createAccount.test.js +++ b/js/src/modals/CreateAccount/createAccount.test.js @@ -42,7 +42,9 @@ function createApi () { newAccountFromWallet: sinon.stub().resolves(ADDRESS), phraseToAddress: () => Promise.resolve(`${++counter}`), setAccountMeta: sinon.stub().resolves(), - setAccountName: sinon.stub().resolves() + setAccountName: sinon.stub().resolves(), + listVaults: sinon.stub().resolves([]), + listOpenedVaults: sinon.stub().resolves([]) } }; } diff --git a/js/src/modals/CreateAccount/store.js b/js/src/modals/CreateAccount/store.js index 1875ebe76..c76102d5b 100644 --- a/js/src/modals/CreateAccount/store.js +++ b/js/src/modals/CreateAccount/store.js @@ -44,6 +44,7 @@ export default class Store { @observable rawKey = ''; @observable rawKeyError = ERRORS.nokey; @observable stage = STAGE_SELECT_TYPE; + @observable vaultName = ''; @observable walletFile = ''; @observable walletFileError = ERRORS.noFile; @observable walletJson = ''; @@ -93,8 +94,12 @@ export default class Store { this.phrase = ''; this.name = ''; this.nameError = null; + this.rawKey = ''; this.rawKeyError = null; + this.vaultName = ''; + this.walletFile = ''; this.walletFileError = null; + this.walletJson = ''; }); } @@ -131,6 +136,10 @@ export default class Store { this.gethImported = gethImported; } + @action setVaultName = (vaultName) => { + this.vaultName = vaultName; + } + @action setWindowsPhrase = (isWindowsPhrase = false) => { this.isWindowsPhrase = isWindowsPhrase; } @@ -217,7 +226,28 @@ export default class Store { this.stage--; } - createAccount = () => { + createAccount = (vaultStore) => { + this.setBusy(true); + + return this + ._createAccount() + .then(() => { + if (vaultStore && this.vaultName && this.vaultName.length) { + return vaultStore.moveAccount(this.vaultName, this.address); + } + + return true; + }) + .then(() => { + this.setBusy(false); + }) + .catch((error) => { + this.setBusy(false); + throw error; + }); + } + + _createAccount = () => { switch (this.createType) { case 'fromGeth': return this.createAccountFromGeth(); diff --git a/js/src/modals/CreateAccount/store.spec.js b/js/src/modals/CreateAccount/store.spec.js index 9c74365c9..833cb7ef5 100644 --- a/js/src/modals/CreateAccount/store.spec.js +++ b/js/src/modals/CreateAccount/store.spec.js @@ -22,8 +22,20 @@ import { ACCOUNTS, ADDRESS, GETH_ADDRESSES, createApi } from './createAccount.te let api; let store; +let vaultStore; + +function createVaultStore () { + vaultStore = { + moveAccount: sinon.stub().resolves(), + listVaults: sinon.stub().resolves() + }; + + return vaultStore; +} function createStore (loadGeth) { + createVaultStore(); + api = createApi(); store = new Store(api, ACCOUNTS, loadGeth); @@ -64,13 +76,28 @@ describe('modals/CreateAccount/Store', () => { describe('@action', () => { describe('clearErrors', () => { + beforeEach(() => { + store.setName('testing'); + store.setPassword('testing'); + store.setVaultName('testing'); + store.setRawKey('test'); + store.setWalletFile('test'); + store.setWalletJson('test'); + }); + it('clears all errors', () => { store.clearErrors(); + expect(store.name).to.equal(''); expect(store.nameError).to.be.null; + expect(store.password).to.equal(''); expect(store.passwordRepeatError).to.be.null; + expect(store.rawKey).to.equal(''); expect(store.rawKeyError).to.be.null; + expect(store.vaultName).to.equal(''); + expect(store.walletFile).to.equal(''); expect(store.walletFileError).to.be.null; + expect(store.walletJson).to.equal(''); }); }); @@ -187,6 +214,13 @@ describe('modals/CreateAccount/Store', () => { }); }); + describe('setVaultName', () => { + it('sets the vault name', () => { + store.setVaultName('testVault'); + expect(store.vaultName).to.equal('testVault'); + }); + }); + describe('setWalletFile', () => { it('sets the filepath', () => { store.setWalletFile('testing'); @@ -373,12 +407,22 @@ describe('modals/CreateAccount/Store', () => { let createAccountFromWalletSpy; let createAccountFromPhraseSpy; let createAccountFromRawSpy; + let busySpy; beforeEach(() => { createAccountFromGethSpy = sinon.spy(store, 'createAccountFromGeth'); createAccountFromWalletSpy = sinon.spy(store, 'createAccountFromWallet'); createAccountFromPhraseSpy = sinon.spy(store, 'createAccountFromPhrase'); createAccountFromRawSpy = sinon.spy(store, 'createAccountFromRaw'); + busySpy = sinon.spy(store, 'setBusy'); + }); + + afterEach(() => { + store.createAccountFromGeth.restore(); + store.createAccountFromWallet.restore(); + store.createAccountFromPhrase.restore(); + store.createAccountFromRaw.restore(); + store.setBusy.restore(); }); it('throws error on invalid createType', () => { @@ -388,38 +432,68 @@ describe('modals/CreateAccount/Store', () => { it('calls createAccountFromGeth on createType === fromGeth', () => { store.setCreateType('fromGeth'); - store.createAccount(); - expect(createAccountFromGethSpy).to.have.been.called; + + return store.createAccount().then(() => { + expect(createAccountFromGethSpy).to.have.been.called; + }); }); it('calls createAccountFromWallet on createType === fromJSON', () => { store.setCreateType('fromJSON'); - store.createAccount(); - expect(createAccountFromWalletSpy).to.have.been.called; + + return store.createAccount().then(() => { + expect(createAccountFromWalletSpy).to.have.been.called; + }); }); it('calls createAccountFromPhrase on createType === fromNew', () => { store.setCreateType('fromNew'); - store.createAccount(); - expect(createAccountFromPhraseSpy).to.have.been.called; + + return store.createAccount().then(() => { + expect(createAccountFromPhraseSpy).to.have.been.called; + }); }); it('calls createAccountFromPhrase on createType === fromPhrase', () => { store.setCreateType('fromPhrase'); - store.createAccount(); - expect(createAccountFromPhraseSpy).to.have.been.called; + + return store.createAccount().then(() => { + expect(createAccountFromPhraseSpy).to.have.been.called; + }); }); it('calls createAccountFromWallet on createType === fromPresale', () => { store.setCreateType('fromPresale'); - store.createAccount(); - expect(createAccountFromWalletSpy).to.have.been.called; + + return store.createAccount().then(() => { + expect(createAccountFromWalletSpy).to.have.been.called; + }); }); it('calls createAccountFromRaw on createType === fromRaw', () => { store.setCreateType('fromRaw'); - store.createAccount(); - expect(createAccountFromRawSpy).to.have.been.called; + + return store.createAccount().then(() => { + expect(createAccountFromRawSpy).to.have.been.called; + }); + }); + + it('moves account to vault when vaultName set', () => { + store.setCreateType('fromNew'); + store.setVaultName('testing'); + + return store.createAccount(vaultStore).then(() => { + expect(vaultStore.moveAccount).to.have.been.calledWith('testing', ADDRESS); + }); + }); + + it('sets and rests the busy flag', () => { + store.setCreateType('fromNew'); + + return store.createAccount().then(() => { + expect(busySpy).to.have.been.calledWith(true); + expect(busySpy).to.have.been.calledWith(false); + }); }); describe('createAccountFromGeth', () => { diff --git a/js/src/modals/CreateWallet/WalletDetails/walletDetails.js b/js/src/modals/CreateWallet/WalletDetails/walletDetails.js index 6b0ed7e1a..d3776e2cb 100644 --- a/js/src/modals/CreateWallet/WalletDetails/walletDetails.js +++ b/js/src/modals/CreateWallet/WalletDetails/walletDetails.js @@ -14,7 +14,6 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import { omitBy } from 'lodash'; import React, { Component, PropTypes } from 'react'; import { FormattedMessage } from 'react-intl'; @@ -106,9 +105,6 @@ export default class WalletDetails extends Component { renderMultisigDetails () { const { accounts, wallet, errors } = this.props; - // Wallets cannot create contracts - const _accounts = omitBy(accounts, (a) => a.wallet); - return (
{ - const walletLibraryAddress = (address || '').replace(/^0x/, '').toLowerCase(); - const code = walletLibraryAddress.length && !/^0+$/.test(walletLibraryAddress) - ? walletCode.replace(/(_)+WalletLibrary(_)+/g, walletLibraryAddress) - : fullWalletCode; + if (!address || /^(0x)?0*$/.test(address)) { + return null; + } + + // Check that it's actually the expected code + return this.api.eth + .getCode(address) + .then((code) => { + const strippedCode = code.replace(/^0x/, ''); + + // The actual deployed code is included in the wallet + // library code (which might have some more data) + if (walletLibraryCode.indexOf(strippedCode) >= 0) { + return address; + } + + return null; + }); + }) + .then((address) => { + let code = fullWalletCode; + + if (address) { + const walletLibraryAddress = address.replace(/^0x/, '').toLowerCase(); + + code = walletCode.replace(/(_)+WalletLibrary(_)+/g, walletLibraryAddress); + } else { + console.warn('wallet library has not been found in the registry'); + } const options = { data: code, from: account }; - return this.api - .newContract(walletAbi) - .deploy(options, [ owners, required, daylimit ], this.onDeploymentState); + const contract = this.api.newContract(walletAbi); + + this.wallet = this.getWalletWithMeta(this.wallet); + return deploy(contract, options, [ owners, required, daylimit ], this.wallet.metadata, this.onDeploymentState); }) .then((address) => { + if (!address || /^(0x)?0*$/.test(address)) { + return false; + } + this.deployed = true; this.wallet.address = address; return this.addWallet(this.wallet); @@ -233,26 +266,37 @@ export default class CreateWalletStore { } @action addWallet = (wallet) => { - const { address, name, description } = wallet; + const { address, name, metadata } = wallet; return Promise .all([ this.api.parity.setAccountName(address, name), - this.api.parity.setAccountMeta(address, { - abi: walletAbi, - wallet: true, - timestamp: Date.now(), - deleted: false, - description, - name, - tags: ['wallet'] - }) + this.api.parity.setAccountMeta(address, metadata) ]) .then(() => { this.step = 'INFO'; }); } + getWalletWithMeta = (wallet) => { + const { name, description } = wallet; + + const metadata = { + abi: walletAbi, + wallet: true, + timestamp: Date.now(), + deleted: false, + tags: [ 'wallet' ], + description, + name + }; + + return { + ...wallet, + metadata + }; + } + onDeploymentState = (error, data) => { if (error) { return console.error('createWallet::onDeploymentState', error); @@ -298,6 +342,15 @@ export default class CreateWalletStore { ); return; + case 'confirmationNeeded': + this.deployState = ( + + ); + return; + case 'completed': this.deployState = ( - , - defaultIcon: - } } - /> - - } onClose={ permissionStore.closeModal } open title={ diff --git a/js/src/modals/DeleteAccount/deleteAccount.js b/js/src/modals/DeleteAccount/deleteAccount.js index 8f47777c5..cd69d6bd9 100644 --- a/js/src/modals/DeleteAccount/deleteAccount.js +++ b/js/src/modals/DeleteAccount/deleteAccount.js @@ -37,25 +37,27 @@ class DeleteAccount extends Component { } state = { + isBusy: false, password: '' } render () { const { account } = this.props; - const { password } = this.state; + const { isBusy, password } = this.state; return ( } - visible >
{ + this.setState({ isBusy: true }); + if (result === true) { router.push('/accounts'); this.closeDeleteDialog(); @@ -128,6 +134,7 @@ class DeleteAccount extends Component { } }) .catch((error) => { + this.setState({ isBusy: false }); console.error('onDeleteConfirmed', error); newError(new Error(`Deletion failed: ${error.message}`)); }); diff --git a/js/src/modals/DeployContract/deployContract.js b/js/src/modals/DeployContract/deployContract.js index 4e5a9ff85..14930d312 100644 --- a/js/src/modals/DeployContract/deployContract.js +++ b/js/src/modals/DeployContract/deployContract.js @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import { pick, omitBy } from 'lodash'; +import { pick } from 'lodash'; import { observer } from 'mobx-react'; import React, { Component, PropTypes } from 'react'; import { FormattedMessage } from 'react-intl'; @@ -23,6 +23,7 @@ import { connect } from 'react-redux'; import { BusyStep, Button, CompletedStep, CopyToClipboard, GasPriceEditor, IdentityIcon, Portal, TxHash, Warning } from '~/ui'; import { CancelIcon, DoneIcon } from '~/ui/Icons'; import { ERRORS, validateAbi, validateCode, validateName } from '~/util/validation'; +import { deploy, deployEstimateGas } from '~/util/tx'; import DetailsStep from './DetailsStep'; import ParametersStep from './ParametersStep'; @@ -73,7 +74,7 @@ class DeployContract extends Component { static contextTypes = { api: PropTypes.object.isRequired, store: PropTypes.object.isRequired - } + }; static propTypes = { accounts: PropTypes.object.isRequired, @@ -150,15 +151,20 @@ class DeployContract extends Component { const title = realSteps ? null - : (deployError - ? - : + ) ); const waiting = realSteps @@ -195,9 +201,7 @@ class DeployContract extends Component { } return ( - + ); } @@ -419,9 +423,9 @@ class DeployContract extends Component { from: fromAddress }; - api - .newContract(abiParsed) - .deployEstimateGas(options, params) + const contract = api.newContract(abiParsed); + + deployEstimateGas(contract, options, params) .then(([gasEst, gas]) => { this.gasStore.setEstimated(gasEst.toFixed(0)); this.gasStore.setGas(gas.toFixed(0)); @@ -487,6 +491,17 @@ class DeployContract extends Component { const { api, store } = this.context; const { source } = this.props; const { abiParsed, code, description, name, params, fromAddress } = this.state; + + const metadata = { + abi: abiParsed, + contract: true, + deleted: false, + timestamp: Date.now(), + name, + description, + source + }; + const options = { data: code, from: fromAddress @@ -496,28 +511,25 @@ class DeployContract extends Component { const contract = api.newContract(abiParsed); - contract - .deploy(options, params, this.onDeploymentState) + deploy(contract, options, params, metadata, this.onDeploymentState) .then((address) => { - const blockNumber = contract._receipt + // No contract address given, might need some confirmations + // from the wallet owners... + if (!address || /^(0x)?0*$/.test(address)) { + return false; + } + + metadata.blockNumber = contract._receipt ? contract.receipt.blockNumber.toNumber() : null; return Promise.all([ api.parity.setAccountName(address, name), - api.parity.setAccountMeta(address, { - abi: abiParsed, - contract: true, - timestamp: Date.now(), - deleted: false, - blockNumber, - description, - source - }) + api.parity.setAccountMeta(address, metadata) ]) .then(() => { console.log(`contract deployed at ${address}`); - this.setState({ step: 'DEPLOYMENT', address }); + this.setState({ step: 'COMPLETED', address }); }); }) .catch((error) => { @@ -586,6 +598,17 @@ class DeployContract extends Component { }); return; + case 'confirmationNeeded': + this.setState({ + deployState: ( + + ) + }); + return; + case 'completed': this.setState({ deployState: ( @@ -611,17 +634,14 @@ class DeployContract extends Component { function mapStateToProps (initState, initProps) { const { accounts } = initProps; - // Skip Wallet accounts : they can't create Contracts - const _accounts = omitBy(accounts, (a) => a.wallet); - - const fromAddresses = Object.keys(_accounts); + const fromAddresses = Object.keys(accounts); return (state) => { const balances = pick(state.balances.balances, fromAddresses); const { gasLimit } = state.nodeStatus; return { - accounts: _accounts, + accounts, balances, gasLimit }; diff --git a/js/src/modals/EditMeta/editMeta.js b/js/src/modals/EditMeta/editMeta.js index 71e222ca6..5d0c91dbe 100644 --- a/js/src/modals/EditMeta/editMeta.js +++ b/js/src/modals/EditMeta/editMeta.js @@ -21,11 +21,10 @@ import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import { newError } from '~/redux/actions'; -import { Button, Form, Input, InputAddress, InputChip, Portal } from '~/ui'; +import { Button, Form, Input, InputChip, Portal, VaultSelect } from '~/ui'; import { CancelIcon, SaveIcon } from '~/ui/Icons'; import VaultStore from '~/views/Vaults/store'; -import VaultSelector from '../VaultSelector'; import Store from './store'; @observer @@ -48,11 +47,12 @@ class EditMeta extends Component { } render () { - const { description, name, nameError, tags } = this.store; + const { description, isBusy, name, nameError, tags } = this.store; return ( } > - { this.renderVaultSelector() } - { this.renderVault() } + { this.renderVaultSelector() } ); @@ -163,7 +162,7 @@ class EditMeta extends Component { ); } - renderVault () { + renderVaultSelector () { const { isAccount, vaultName } = this.store; if (!isAccount) { @@ -171,40 +170,9 @@ class EditMeta extends Component { } return ( - - } - label={ - - } - onClick={ this.toggleVaultSelector } - value={ vaultName } - /> - ); - } - - renderVaultSelector () { - const { isAccount, isVaultSelectorOpen, vaultName } = this.store; - - if (!isAccount || !isVaultSelectorOpen) { - return null; - } - - return ( - ); @@ -215,21 +183,12 @@ class EditMeta extends Component { } onSave = () => { - const { address, isAccount, meta, vaultName } = this.store; - if (this.store.hasError) { return; } return this.store - .save() - .then(() => { - if (isAccount && (meta.vault !== vaultName)) { - return this.vaultStore.moveAccount(vaultName, address); - } - - return true; - }) + .save(this.vaultStore) .then(this.onClose) .catch((error) => { this.props.newError(error); @@ -238,11 +197,6 @@ class EditMeta extends Component { setVaultName = (vaultName) => { this.store.setVaultName(vaultName); - this.toggleVaultSelector(); - } - - toggleVaultSelector = () => { - this.store.toggleVaultSelector(); } } diff --git a/js/src/modals/EditMeta/store.js b/js/src/modals/EditMeta/store.js index 46951f095..da3d88cd7 100644 --- a/js/src/modals/EditMeta/store.js +++ b/js/src/modals/EditMeta/store.js @@ -21,7 +21,7 @@ import { validateName } from '~/util/validation'; export default class Store { @observable address = null; @observable isAccount = false; - @observable isVaultSelectorOpen = false; + @observable isBusy = false; @observable description = null; @observable meta = null; @observable name = null; @@ -73,6 +73,10 @@ export default class Store { this.passwordHint = passwordHint; } + @action setBusy = (isBusy) => { + this.isBusy = isBusy; + } + @action setTags = (tags) => { this.tags = tags.slice(); } @@ -81,11 +85,9 @@ export default class Store { this.vaultName = vaultName; } - @action setVaultSelectorOpen = (isOpen) => { - this.isVaultSelectorOpen = isOpen; - } + save (vaultStore) { + this.setBusy(true); - save () { const meta = { description: this.description, tags: this.tags.peek() @@ -100,13 +102,20 @@ export default class Store { this._api.parity.setAccountName(this.address, this.name), this._api.parity.setAccountMeta(this.address, Object.assign({}, this.meta, meta)) ]) + .then(() => { + if (vaultStore && this.isAccount && (this.meta.vault !== this.vaultName)) { + return vaultStore.moveAccount(this.vaultName, this.address); + } + + return true; + }) + .then(() => { + this.setBusy(false); + }) .catch((error) => { console.error('onSave', error); + this.setBusy(false); throw error; }); } - - toggleVaultSelector () { - this.setVaultSelectorOpen(!this.isVaultSelectorOpen); - } } diff --git a/js/src/modals/EditMeta/store.spec.js b/js/src/modals/EditMeta/store.spec.js index 4ff775718..a38da055f 100644 --- a/js/src/modals/EditMeta/store.spec.js +++ b/js/src/modals/EditMeta/store.spec.js @@ -14,14 +14,24 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +import sinon from 'sinon'; + import Store from './store'; import { ACCOUNT, ADDRESS, createApi } from './editMeta.test.js'; let api; let store; +let vaultStore; + +function createVaultStore () { + return { + moveAccount: sinon.stub().resolves(true) + }; +} function createStore (account) { api = createApi(); + vaultStore = createVaultStore(); store = new Store(api, account); @@ -108,6 +118,13 @@ describe('modals/EditMeta/Store', () => { createStore(ADDRESS); }); + describe('setBusy', () => { + it('sets the isBusy flag', () => { + store.setBusy('testing'); + expect(store.isBusy).to.equal('testing'); + }); + }); + describe('setDescription', () => { it('sets the description', () => { store.setDescription('description'); @@ -149,26 +166,56 @@ describe('modals/EditMeta/Store', () => { expect(store.vaultName).to.equal('testing'); }); }); - - describe('setVaultSelectorOpen', () => { - it('sets the state', () => { - store.setVaultSelectorOpen('testing'); - expect(store.isVaultSelectorOpen).to.equal('testing'); - }); - }); }); describe('operations', () => { describe('save', () => { beforeEach(() => { createStore(ACCOUNT); + sinon.spy(store, 'setBusy'); + }); + + afterEach(() => { + store.setBusy.restore(); + }); + + it('sets the busy flag, clearing it when done', () => { + return store.save().then(() => { + expect(store.setBusy).to.have.been.calledWith(true); + expect(store.setBusy).to.have.been.calledWith(false); + }); }); it('calls parity.setAccountName with the set value', () => { store.setName('test name'); - store.save(); - expect(api.parity.setAccountName).to.be.calledWith(ACCOUNT.address, 'test name'); + return store.save().then(() => { + expect(api.parity.setAccountName).to.be.calledWith(ACCOUNT.address, 'test name'); + }); + }); + + it('calls parity.setAccountMeta with the adjusted values', () => { + store.setDescription('some new description'); + store.setPasswordHint('some new passwordhint'); + store.setTags(['taga']); + + return store.save().then(() => { + expect(api.parity.setAccountMeta).to.have.been.calledWith( + ACCOUNT.address, Object.assign({}, ACCOUNT.meta, { + description: 'some new description', + passwordHint: 'some new passwordhint', + tags: ['taga'] + }) + ); + }); + }); + + it('moves vault account when applicable', () => { + store.setVaultName('testing'); + + return store.save(vaultStore).then(() => { + expect(vaultStore.moveAccount).to.have.been.calledWith('testing', ACCOUNT.address); + }); }); it('calls parity.setAccountMeta with the adjusted values', () => { @@ -185,11 +232,4 @@ describe('modals/EditMeta/Store', () => { }); }); }); - - describe('toggleVaultSelector', () => { - it('inverts the selector state', () => { - store.toggleVaultSelector(); - expect(store.isVaultSelectorOpen).to.be.true; - }); - }); }); diff --git a/js/src/modals/ExecuteContract/executeContract.js b/js/src/modals/ExecuteContract/executeContract.js index 312f8bb27..20237acf9 100644 --- a/js/src/modals/ExecuteContract/executeContract.js +++ b/js/src/modals/ExecuteContract/executeContract.js @@ -84,7 +84,6 @@ class ExecuteContract extends Component { contract: PropTypes.object.isRequired, fromAddress: PropTypes.string, gasLimit: PropTypes.object.isRequired, - isTest: PropTypes.bool, onClose: PropTypes.func.isRequired, onFromAddressChange: PropTypes.func.isRequired } diff --git a/js/src/modals/Faucet/faucet.js b/js/src/modals/Faucet/faucet.js new file mode 100644 index 000000000..e4399e8ba --- /dev/null +++ b/js/src/modals/Faucet/faucet.js @@ -0,0 +1,162 @@ +// Copyright 2015-2017 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 . + +import { observer } from 'mobx-react'; +import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; + +import { txLink } from '~/3rdparty/etherscan/links'; +import { Button, ModalBox, Portal, ShortenedHash } from '~/ui'; +import { CloseIcon, DialIcon, DoneIcon, ErrorIcon, SendIcon } from '~/ui/Icons'; + +import Store from './store'; + +@observer +export default class Faucet extends Component { + static propTypes = { + address: PropTypes.string.isRequired, + netVersion: PropTypes.string.isRequired, + onClose: PropTypes.func.isRequired + } + + store = new Store(this.props.netVersion, this.props.address); + + render () { + const { error, isBusy, isCompleted } = this.store; + + let icon = ; + + if (isCompleted) { + icon = error + ? + : ; + } + + return ( + + } + > + + + ); + } + + renderActions = () => { + const { canTransact, isBusy, isCompleted } = this.store; + + return isCompleted || isBusy + ? ( +
diff --git a/js/src/modals/SaveContract/saveContract.js b/js/src/modals/SaveContract/saveContract.js index 242ba1593..7f45b6aa5 100644 --- a/js/src/modals/SaveContract/saveContract.js +++ b/js/src/modals/SaveContract/saveContract.js @@ -15,12 +15,11 @@ // along with Parity. If not, see . import React, { Component, PropTypes } from 'react'; - -import SaveIcon from 'material-ui/svg-icons/content/save'; -import ContentClear from 'material-ui/svg-icons/content/clear'; +import { FormattedMessage } from 'react-intl'; import { Button, Form, Input, Portal } from '~/ui'; import Editor from '~/ui/Editor'; +import { CancelIcon, SaveIcon } from '~/ui/Icons'; import { ERRORS, validateName } from '~/util/validation'; import styles from './saveContract.css'; @@ -46,13 +45,28 @@ export default class SaveContract extends Component { buttons={ this.renderDialogActions() } onClose={ this.onClose } open - title='save contract' + title={ + + } >
+ } + hint={ + + } value={ name } error={ nameError } onChange={ this.onChangeName } @@ -72,9 +86,14 @@ export default class SaveContract extends Component { renderDialogActions () { const cancelBtn = (
@@ -122,7 +126,7 @@ export default class AccountCard extends Component {
@@ -132,6 +136,17 @@ export default class AccountCard extends Component { ); } + handleAddressClick = (event) => { + const { disableAddressClick } = this.props; + + // Stop the event if address click is disallowed + if (disableAddressClick) { + return this.preventEvent(event); + } + + return this.onClick(event); + } + handleKeyDown = (event) => { const codeName = keycode(event); @@ -172,9 +187,14 @@ export default class AccountCard extends Component { } } - onClick = () => { + onClick = (event) => { const { account, onClick } = this.props; + // Stop the default event if text is selected + if (window.getSelection && window.getSelection().type === 'Range') { + return this.preventEvent(event); + } + onClick && onClick(account.address); } @@ -184,9 +204,9 @@ export default class AccountCard extends Component { onFocus && onFocus(account.index); } - preventEvent = (e) => { - e.preventDefault(); - e.stopPropagation(); + preventEvent = (event) => { + event.preventDefault(); + event.stopPropagation(); } setTagRef = (tagRef) => { diff --git a/js/src/ui/AccountCard/accountCard.spec.js b/js/src/ui/AccountCard/accountCard.spec.js index cecce7a89..6d7e921fd 100644 --- a/js/src/ui/AccountCard/accountCard.spec.js +++ b/js/src/ui/AccountCard/accountCard.spec.js @@ -74,9 +74,8 @@ describe('ui/AccountCard', () => { expect(balance.length).to.equal(1); }); - it('sets showOnlyEth & showZeroValues', () => { + it('sets showOnlyEth', () => { expect(balance.props().showOnlyEth).to.be.true; - expect(balance.props().showZeroValues).to.be.true; }); }); diff --git a/js/src/ui/Actionbar/Import/import.css b/js/src/ui/Actionbar/Import/import.css index da71e5f15..53a3e049a 100644 --- a/js/src/ui/Actionbar/Import/import.css +++ b/js/src/ui/Actionbar/Import/import.css @@ -15,30 +15,6 @@ /* along with Parity. If not, see . */ -.importZone { - width: 100%; - height: 200px; - border-width: 2px; - border-color: #666; - border-style: dashed; - border-radius: 10px; - - background-color: rgba(50, 50, 50, 0.2); - - display: flex; - align-items: center; - justify-content: center; - font-size: 1.2em; - - transition: all 0.5s ease; - - &:hover { - cursor: pointer; - border-radius: 0; - background-color: rgba(50, 50, 50, 0.5); - } -} - .desc { margin-top: 0; color: #ccc; diff --git a/js/src/ui/Actionbar/Import/import.js b/js/src/ui/Actionbar/Import/import.js index 91bdefe80..a447be4cb 100644 --- a/js/src/ui/Actionbar/Import/import.js +++ b/js/src/ui/Actionbar/Import/import.js @@ -15,12 +15,12 @@ // along with Parity. If not, see . import React, { Component, PropTypes } from 'react'; -import Dropzone from 'react-dropzone'; import { FormattedMessage } from 'react-intl'; import { nodeOrStringProptype } from '~/util/proptypes'; import Button from '../../Button'; +import FileSelect from '../../Form/FileSelect'; import { CancelIcon, DoneIcon, FileUploadIcon } from '../../Icons'; import Portal from '../../Portal'; @@ -65,7 +65,7 @@ export default class ActionbarImport extends Component { icon={ } label={ } @@ -87,19 +87,19 @@ export default class ActionbarImport extends Component { const steps = typeof renderValidation === 'function' ? [ , error ? ( ) : ( ) ] @@ -128,7 +128,7 @@ export default class ActionbarImport extends Component { key='cancel' label={ } @@ -147,7 +147,7 @@ export default class ActionbarImport extends Component { key='confirm' label={ } @@ -169,7 +169,7 @@ export default class ActionbarImport extends Component {

- -

- -
- -
+ ); } @@ -213,7 +196,7 @@ export default class ActionbarImport extends Component {

@@ -224,39 +207,30 @@ export default class ActionbarImport extends Component { ); } - onDrop = (files) => { + onFileSelect = (file, content) => { const { renderValidation } = this.props; - const file = files[0]; - const reader = new FileReader(); + if (typeof renderValidation !== 'function') { + this.props.onConfirm(content); + return this.onCloseModal(); + } - reader.onload = (e) => { - const content = e.target.result; + const validationBody = renderValidation(content); - if (typeof renderValidation !== 'function') { - this.props.onConfirm(content); - return this.onCloseModal(); - } - - const validationBody = renderValidation(content); - - if (validationBody && validationBody.error) { - return this.setState({ - step: 1, - error: true, - errorText: validationBody.error - }); - } - - this.setState({ + if (validationBody && validationBody.error) { + return this.setState({ step: 1, - validate: true, - validationBody, - content + error: true, + errorText: validationBody.error }); - }; + } - reader.readAsText(file); + this.setState({ + step: 1, + validate: true, + validationBody, + content + }); } onConfirm = () => { diff --git a/js/src/ui/Actionbar/Sort/sort.js b/js/src/ui/Actionbar/Sort/sort.js index 69085d35d..7fd775d12 100644 --- a/js/src/ui/Actionbar/Sort/sort.js +++ b/js/src/ui/Actionbar/Sort/sort.js @@ -15,6 +15,7 @@ // along with Parity. If not, see . import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; import { observer } from 'mobx-react'; import IconMenu from 'material-ui/IconMenu'; @@ -71,12 +72,38 @@ export default class ActionbarSort extends Component { > { showDefault - ? this.renderMenuItem('', 'Default') - : null + ? this.renderMenuItem('', ( + + )) + : null + } + { + this.renderMenuItem('tags', ( + + )) + } + { + this.renderMenuItem('name', ( + + )) + } + { + this.renderMenuItem('eth', ( + + )) } - { this.renderMenuItem('tags', 'Sort by tags') } - { this.renderMenuItem('name', 'Sort by name') } - { this.renderMenuItem('eth', 'Sort by ETH') } { this.renderSortByMetas() } @@ -88,8 +115,17 @@ export default class ActionbarSort extends Component { return metas .map((meta, index) => { - return this - .renderMenuItem(meta.key, `Sort by ${meta.label}`, index); + const label = ( + + ); + + return this.renderMenuItem(meta.key, label, index); }); } diff --git a/js/src/ui/Actionbar/actionbar.css b/js/src/ui/Actionbar/actionbar.css index ec740e635..0fcd4450f 100644 --- a/js/src/ui/Actionbar/actionbar.css +++ b/js/src/ui/Actionbar/actionbar.css @@ -14,6 +14,7 @@ /* You should have received a copy of the GNU General Public License /* along with Parity. If not, see . */ + .actionbar { padding: 0 24px !important; margin-bottom: 0; @@ -31,10 +32,13 @@ button { margin: 10px 0 10px 16px !important; - color: white !important; - } - svg { - fill: white !important; + &:not([disabled]) { + color: white !important; + + svg { + fill: white !important; + } + } } } diff --git a/js/src/ui/Balance/balance.css b/js/src/ui/Balance/balance.css index ed0f79e7b..1176cafea 100644 --- a/js/src/ui/Balance/balance.css +++ b/js/src/ui/Balance/balance.css @@ -18,16 +18,22 @@ .balances { display: flex; flex-wrap: wrap; - margin: 1em 0 0 0; + margin: 0.75em 0 0 0; vertical-align: top; + + &:not(.full) { + height: 2.5em; + overflow: hidden; + } } .balance, .empty { - margin: 0.75em 0.5em 0 0; + margin: 0.75em 0.5em 4px 0; } .empty { + min-height: 24px; line-height: 24px; opacity: 0.25; overflow: hidden; @@ -36,28 +42,35 @@ } .balance { - background: rgba(255, 255, 255, 0.07); + align-items: center; border-radius: 16px; + display: flex; max-height: 24px; max-width: 100%; - display: flex; - align-items: center; -} -.balance img { - height: 32px; - margin: -4px 1em 0 0; - width: 32px; -} + &.full { + background: rgba(255, 255, 255, 0.07); -.balanceValue { - margin: 0 0.5em 0 0; - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; -} + img { + margin-right: 1em; + } + } -.balanceTag { - font-size: 0.85em; - padding-right: 0.75rem; + img { + height: 32px; + margin-top: -4px; + width: 32px; + } + + .tag { + padding-right: 0.75rem; + font-size: 0.85em; + } + + .value { + margin: 0 0.5em 0 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } } diff --git a/js/src/ui/Balance/balance.js b/js/src/ui/Balance/balance.js index 04fed6942..d00e1f01c 100644 --- a/js/src/ui/Balance/balance.js +++ b/js/src/ui/Balance/balance.js @@ -41,7 +41,7 @@ export default class Balance extends Component { render () { const { api } = this.context; - const { balance, className, showZeroValues, showOnlyEth } = this.props; + const { balance, className, showOnlyEth } = this.props; if (!balance || !balance.tokens) { return null; @@ -49,12 +49,13 @@ export default class Balance extends Component { let body = balance.tokens .filter((balance) => { - const hasBalance = showZeroValues || new BigNumber(balance.value).gt(0); - const isValidToken = !showOnlyEth || (balance.token.tag || '').toLowerCase() === 'eth'; + const isEthToken = (balance.token.tag || '').toLowerCase() === 'eth'; + const hasBalance = new BigNumber(balance.value).gt(0); - return hasBalance && isValidToken; + return hasBalance || isEthToken; }) .map((balance, index) => { + const isFullToken = !showOnlyEth || (balance.token.tag || '').toLowerCase() === 'eth'; const token = balance.token; let value; @@ -77,16 +78,36 @@ export default class Balance extends Component { value = api.util.fromWei(balance.value).toFormat(3); } + const classNames = [styles.balance]; + let details = null; + + if (isFullToken) { + classNames.push(styles.full); + details = [ +
+ + { value } + +
, +
+ { token.tag } +
+ ]; + } + return (
-
- { value } -
-
{ token.tag }
+ { details }
); }); @@ -96,14 +117,24 @@ export default class Balance extends Component {
); } return ( -
+
{ body }
); diff --git a/js/src/ui/Balance/balance.spec.js b/js/src/ui/Balance/balance.spec.js index 8a886b39b..ffc25243a 100644 --- a/js/src/ui/Balance/balance.spec.js +++ b/js/src/ui/Balance/balance.spec.js @@ -79,28 +79,9 @@ describe('ui/Balance', () => { }); describe('render specifiers', () => { - it('renders only the single token with showOnlyEth', () => { - render({ showOnlyEth: true }); - expect(component.find('Connect(TokenImage)')).to.have.length(1); - }); - it('renders all the tokens with showZeroValues', () => { render({ showZeroValues: true }); - expect(component.find('Connect(TokenImage)')).to.have.length(3); - }); - - it('shows ETH with zero value with showOnlyEth & showZeroValues', () => { - render({ - showOnlyEth: true, - showZeroValues: true, - balance: { - tokens: [ - { value: '0', token: { tag: 'ETH' } }, - { value: '345', token: { tag: 'GAV', format: 1 } } - ] - } - }); - expect(component.find('Connect(TokenImage)')).to.have.length(1); + expect(component.find('Connect(TokenImage)')).to.have.length(2); }); }); }); diff --git a/js/src/ui/Certifications/certifications.css b/js/src/ui/Certifications/certifications.css index 0b1930c6a..4d1944b25 100644 --- a/js/src/ui/Certifications/certifications.css +++ b/js/src/ui/Certifications/certifications.css @@ -16,32 +16,43 @@ */ .certifications { - margin-top: 1em; + margin-top: 0.75em; +} + +.certification, +.certificationIcon { + border-radius: 1em; + display: inline-block; + line-height: 1em; + position: relative; + + .icon { + width: 2em; + height: 2em; + margin: 0; + padding: 0; + border-radius: 1em; + } +} + +.certificationIcon { + margin-left: 0.5em; } .certification { - display: inline-block; - position: relative; - margin-top: 1em; - margin-right: .5em; - padding: .3em .6em .2em 2.6em; - border-radius: 1em; - line-height: 1em; - text-transform: uppercase; background-color: rgba(255, 255, 255, 0.07); -} + margin-right: 0.5em; + margin-top: 1em; + padding: 0.3em 0.6em 0.2em 3em; + text-transform: uppercase; -.certification:last-child { - margin-right: 0; -} + &:last-child { + margin-right: 0; + } -.icon { - position: absolute; - top: -.25em; - left: 0; - width: 2em; - height: 2em; - margin: 0; - padding: 0; - border-radius: 1em; + .icon { + position: absolute; + top: -0.25em; + left: 0; + } } diff --git a/js/src/ui/Certifications/certifications.js b/js/src/ui/Certifications/certifications.js index 79eaf029e..0693cf2d7 100644 --- a/js/src/ui/Certifications/certifications.js +++ b/js/src/ui/Certifications/certifications.js @@ -28,7 +28,8 @@ class Certifications extends Component { address: PropTypes.string.isRequired, certifications: PropTypes.array.isRequired, className: PropTypes.string, - dappsUrl: PropTypes.string.isRequired + dappsUrl: PropTypes.string.isRequired, + showOnlyIcon: PropTypes.bool } render () { @@ -46,16 +47,47 @@ class Certifications extends Component { } renderCertification = (certification) => { - const { name, title, icon } = certification; - const { dappsUrl } = this.props; + const { name, icon } = certification; + const { dappsUrl, showOnlyIcon } = this.props; - const classNames = `${styles.certification} ${!icon ? styles.noIcon : ''}`; - const img = icon ? dappsUrl + hashToImageUrl(icon) : defaultIcon; + const classNames = [ + showOnlyIcon + ? styles.certificationIcon + : styles.certification, + !icon + ? styles.noIcon + : '' + ]; return ( -
- -
{ title || name }
+
+ + { this.renderCertificationName(certification) } +
+ ); + } + + renderCertificationName = (certification) => { + const { showOnlyIcon } = this.props; + const { name, title } = certification; + + if (showOnlyIcon) { + return null; + } + + return ( +
+ { title || name }
); } diff --git a/js/src/ui/CopyToClipboard/copyToClipboard.js b/js/src/ui/CopyToClipboard/copyToClipboard.js index 0afd967fd..016730fce 100644 --- a/js/src/ui/CopyToClipboard/copyToClipboard.js +++ b/js/src/ui/CopyToClipboard/copyToClipboard.js @@ -16,6 +16,7 @@ import { IconButton } from 'material-ui'; import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; import Clipboard from 'react-copy-to-clipboard'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; @@ -64,14 +65,33 @@ class CopyToClipboard extends Component { const { copied } = this.state; return ( - -
+ +
- +
@@ -82,9 +102,13 @@ class CopyToClipboard extends Component { const { data, onCopy, cooldown, showSnackbar } = this.props; const message = (
- copied - { data } - to clipboard + { data } + } } + />
); @@ -98,6 +122,11 @@ class CopyToClipboard extends Component { showSnackbar(message, cooldown); onCopy(); } + + onClick = (event) => { + event.stopPropagation(); + event.preventDefault(); + } } function mapDispatchToProps (dispatch) { diff --git a/js/src/ui/Form/AddressSelect/addressSelect.js b/js/src/ui/Form/AddressSelect/addressSelect.js index 07c133d7c..511ab930f 100644 --- a/js/src/ui/Form/AddressSelect/addressSelect.js +++ b/js/src/ui/Form/AddressSelect/addressSelect.js @@ -23,6 +23,7 @@ import { observer } from 'mobx-react'; import TextFieldUnderline from 'material-ui/TextField/TextFieldUnderline'; +import apiutil from '~/api/util'; import AccountCard from '~/ui/AccountCard'; import InputAddress from '~/ui/Form/InputAddress'; import Loading from '~/ui/Loading'; @@ -177,6 +178,7 @@ class AddressSelect extends Component { { + event.stopPropagation(); + } + handleInputAddresKeydown = (event) => { const code = keycode(event); @@ -588,6 +595,7 @@ class AddressSelect extends Component { } this.store.resetRegistryValues(); + this.store.handleChange(''); this.setState({ expanded: false, @@ -613,6 +621,10 @@ class AddressSelect extends Component { focusedItem: null, inputValue: value }); + + if (apiutil.isAddressValid(value)) { + this.handleClick(value); + } } } diff --git a/js/src/modals/DappPermissions/dappPermissions.css b/js/src/ui/Form/FileSelect/fileSelect.css similarity index 52% rename from js/src/modals/DappPermissions/dappPermissions.css rename to js/src/ui/Form/FileSelect/fileSelect.css index 44d901572..1ac5981c4 100644 --- a/js/src/modals/DappPermissions/dappPermissions.css +++ b/js/src/ui/Form/FileSelect/fileSelect.css @@ -15,11 +15,38 @@ /* along with Parity. If not, see . */ -.legend { - opacity: 0.75; +$backgroundNormal: rgba(0, 0, 0, 0.2); +$backgroundNormalHover: rgba(0, 0, 0, 0.5); +$backgroundError: rgba(255, 0, 0, 0.1); +$backgroundErrorHover: rgba(255, 0, 0, 0.2); - span { - line-height: 24px; - vertical-align: top; +.dropzone { + align-items: center; + background: $backgroundNormal; + border: 2px dashed #666; + border-radius: 0.5em; + display: flex; + justify-content: center; + font-size: 1.2em; + height: 12em; + transition: all 0.5s ease; + width: 100%; + + &:hover { + background: $backgroundNormalHover; + border-radius: 0; + cursor: pointer; + } + + &.error { + background: $backgroundError; + + &:hover { + background: $backgroundErrorHover; + } + } + + .label { + color: #aaa; } } diff --git a/js/src/ui/Form/FileSelect/fileSelect.js b/js/src/ui/Form/FileSelect/fileSelect.js new file mode 100644 index 000000000..18b2c8d8e --- /dev/null +++ b/js/src/ui/Form/FileSelect/fileSelect.js @@ -0,0 +1,76 @@ +// Copyright 2015-2017 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 . + +import React, { Component, PropTypes } from 'react'; +import Dropzone from 'react-dropzone'; +import { FormattedMessage } from 'react-intl'; + +import { nodeOrStringProptype } from '~/util/proptypes'; + +import styles from './fileSelect.css'; + +export default class FileSelect extends Component { + static propTypes = { + className: PropTypes.string, + error: nodeOrStringProptype(), + label: nodeOrStringProptype(), + onSelect: PropTypes.func.isRequired + } + + static defaultProps = { + label: ( + + ) + } + + render () { + const { className, error, label } = this.props; + + return ( + +
+ { error || label } +
+
+ ); + } + + onDrop = (files) => { + const { onSelect } = this.props; + const reader = new FileReader(); + const file = files[0]; + + reader.onload = (event) => { + onSelect(file.name, event.target.result); + }; + + reader.readAsText(file); + } +} diff --git a/js/src/ui/Form/FileSelect/fileSelect.spec.js b/js/src/ui/Form/FileSelect/fileSelect.spec.js new file mode 100644 index 000000000..f931fc655 --- /dev/null +++ b/js/src/ui/Form/FileSelect/fileSelect.spec.js @@ -0,0 +1,118 @@ +// Copyright 2015-2017 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 . + +import { shallow } from 'enzyme'; +import React from 'react'; +import sinon from 'sinon'; + +import FileSelect from './'; + +const FILE = { + content: 'some test content', + name: 'someName' +}; + +let component; +let globalFileReader; +let instance; +let onSelect; +let processedFile; + +function stubReader () { + globalFileReader = global.FileReader; + + global.FileReader = class { + readAsText (file) { + processedFile = file; + + this.onload({ + target: { + result: file.content + } + }); + } + }; +} + +function restoreReader () { + global.FileReader = globalFileReader; +} + +function render (props = {}) { + onSelect = sinon.stub(); + component = shallow( + + ); + instance = component.instance(); + + return component; +} + +describe('ui/Form/FileSelect', () => { + beforeEach(() => { + stubReader(); + render(); + }); + + afterEach(() => { + restoreReader(); + }); + + it('renders defaults', () => { + expect(component).to.be.ok; + }); + + describe('DropZone', () => { + let label; + let zone; + + beforeEach(() => { + label = component.find('FormattedMessage'); + zone = component.find('Dropzone'); + }); + + it('renders the label', () => { + expect(label.props().id).to.equal('ui.fileSelect.defaultLabel'); + }); + + it('attaches the onDrop event', () => { + expect(zone.props().onDrop).to.equal(instance.onDrop); + }); + + it('does not allow multiples', () => { + expect(zone.props().multiple).to.be.false; + }); + }); + + describe('event methods', () => { + describe('onDrop', () => { + beforeEach(() => { + instance.onDrop([ FILE ]); + }); + + it('reads the file as dropped', () => { + expect(processedFile).to.deep.equal(FILE); + }); + + it('calls prop onSelect with file & content', () => { + expect(onSelect).to.have.been.calledWith(FILE.name, FILE.content); + }); + }); + }); +}); diff --git a/js/src/ui/Form/FileSelect/index.js b/js/src/ui/Form/FileSelect/index.js new file mode 100644 index 000000000..ff9614ace --- /dev/null +++ b/js/src/ui/Form/FileSelect/index.js @@ -0,0 +1,17 @@ +// Copyright 2015-2017 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 . + +export default from './fileSelect'; diff --git a/js/src/ui/Form/Input/input.js b/js/src/ui/Form/Input/input.js index 9491bb2d3..44906ad93 100644 --- a/js/src/ui/Form/Input/input.js +++ b/js/src/ui/Form/Input/input.js @@ -46,6 +46,10 @@ const UNDERLINE_FOCUSED = { const NAME_ID = ' '; export default class Input extends Component { + static contextTypes = { + intl: React.PropTypes.object.isRequired + }; + static propTypes = { allowCopy: PropTypes.oneOfType([ PropTypes.string, @@ -79,7 +83,8 @@ export default class Input extends Component { style: PropTypes.object, value: PropTypes.oneOfType([ PropTypes.number, - PropTypes.string + PropTypes.string, + PropTypes.node ]) }; @@ -135,6 +140,13 @@ export default class Input extends Component { ? UNDERLINE_FOCUSED : readOnly && typeof focused !== 'boolean' ? { display: 'none' } : null; + const textValue = typeof value !== 'string' && (value && value.props) + ? this.context.intl.formatMessage( + value.props, + value.props.values || {} + ) + : value; + return (
{ this.renderCopyButton() } @@ -169,7 +181,7 @@ export default class Input extends Component { underlineStyle={ underlineStyle } underlineFocusStyle={ underlineFocusStyle } underlineShow={ !hideUnderline } - value={ value } + value={ textValue } > { children } diff --git a/js/src/ui/Form/InputAddressSelect/inputAddressSelect.js b/js/src/ui/Form/InputAddressSelect/inputAddressSelect.js index c6c3fe6a4..8199fece6 100644 --- a/js/src/ui/Form/InputAddressSelect/inputAddressSelect.js +++ b/js/src/ui/Form/InputAddressSelect/inputAddressSelect.js @@ -17,6 +17,8 @@ import React, { Component, PropTypes } from 'react'; import { connect } from 'react-redux'; +import { nodeOrStringProptype } from '~/util/proptypes'; + import AddressSelect from '../AddressSelect'; class InputAddressSelect extends Component { @@ -27,9 +29,9 @@ class InputAddressSelect extends Component { allowCopy: PropTypes.bool, className: PropTypes.string, - error: PropTypes.string, - hint: PropTypes.string, - label: PropTypes.string, + error: nodeOrStringProptype(), + hint: nodeOrStringProptype(), + label: nodeOrStringProptype(), onChange: PropTypes.func, readOnly: PropTypes.bool, value: PropTypes.string diff --git a/js/src/ui/Form/VaultSelect/index.js b/js/src/ui/Form/VaultSelect/index.js new file mode 100644 index 000000000..b8741956e --- /dev/null +++ b/js/src/ui/Form/VaultSelect/index.js @@ -0,0 +1,17 @@ +// Copyright 2015-2017 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 . + +export default from './vaultSelect'; diff --git a/js/src/ui/Form/VaultSelect/vaultSelect.js b/js/src/ui/Form/VaultSelect/vaultSelect.js new file mode 100644 index 000000000..c93e530e1 --- /dev/null +++ b/js/src/ui/Form/VaultSelect/vaultSelect.js @@ -0,0 +1,109 @@ +// Copyright 2015-2017 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 . + +import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; + +import VaultSelector from '~/modals/VaultSelector'; +import VaultStore from '~/views/Vaults/store'; + +import InputAddress from '../InputAddress'; + +export default class VaultSelect extends Component { + static contextTypes = { + api: PropTypes.object.isRequired + }; + + static propTypes = { + onSelect: PropTypes.func.isRequired, + value: PropTypes.string, + vaultStore: PropTypes.object + }; + + state = { + isOpen: false + }; + + vaultStore = this.props.vaultStore || VaultStore.get(this.context.api); + + componentWillMount () { + return this.vaultStore.loadVaults(); + } + + render () { + const { value } = this.props; + + return ( +
+ { this.renderSelector() } + + } + label={ + + } + onClick={ this.openSelector } + value={ (value || '').toUpperCase() } + /> +
+ ); + } + + renderSelector () { + const { value } = this.props; + const { isOpen } = this.state; + + if (!isOpen) { + return null; + } + + return ( + + ); + } + + openSelector = () => { + this.setState({ + isOpen: true + }); + } + + closeSelector = () => { + this.setState({ + isOpen: false + }); + } + + onSelect = (vaultName) => { + this.props.onSelect(vaultName); + this.closeSelector(); + } +} diff --git a/js/src/ui/Form/VaultSelect/vaultSelect.spec.js b/js/src/ui/Form/VaultSelect/vaultSelect.spec.js new file mode 100644 index 000000000..a0d5ed583 --- /dev/null +++ b/js/src/ui/Form/VaultSelect/vaultSelect.spec.js @@ -0,0 +1,90 @@ +// Copyright 2015-2017 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 . + +import { shallow } from 'enzyme'; +import React from 'react'; +import sinon from 'sinon'; + +import VaultSelect from './'; + +let component; +let instance; +let onSelect; +let vaultStore; + +function createVaultStore () { + vaultStore = { + loadVaults: sinon.stub().resolves(true) + }; + + return vaultStore; +} + +function render () { + onSelect = sinon.stub(); + + component = shallow( + + ); + instance = component.instance(); + + return component; +} + +describe('ui/Form/VaultSelect', () => { + beforeEach(() => { + render(); + }); + + it('renders defaults', () => { + expect(component).to.be.ok; + }); + + describe('components', () => { + describe('InputAddress', () => { + let input; + + beforeEach(() => { + input = component.find('Connect(InputAddress)'); + }); + + it('renders', () => { + expect(input.get(0)).to.be.ok; + }); + + it('passes value from props', () => { + expect(input.props().value).to.equal('INITIALVALUE'); + }); + + it('passes instance openSelector to onClick', () => { + expect(input.props().onClick).to.equal(instance.openSelector); + }); + }); + }); + + describe('instance methods', () => { + describe('onSelect', () => { + it('calls into props', () => { + instance.onSelect('testing'); + expect(onSelect).to.have.been.calledWith('testing'); + }); + }); + }); +}); diff --git a/js/src/ui/Form/index.js b/js/src/ui/Form/index.js index 6a003c472..8c8c7e1f2 100644 --- a/js/src/ui/Form/index.js +++ b/js/src/ui/Form/index.js @@ -16,6 +16,7 @@ export AddressSelect from './AddressSelect'; export DappUrlInput from './DappUrlInput'; +export FileSelect from './FileSelect'; export FormWrap from './FormWrap'; export Input from './Input'; export InputAddress from './InputAddress'; @@ -28,5 +29,6 @@ export Label from './Label'; export RadioButtons from './RadioButtons'; export Select from './Select'; export TypedInput from './TypedInput'; +export VaultSelect from './VaultSelect'; export default from './form'; diff --git a/js/src/ui/Icons/index.js b/js/src/ui/Icons/index.js index 0ea4f7e0f..aae962a88 100644 --- a/js/src/ui/Icons/index.js +++ b/js/src/ui/Icons/index.js @@ -29,22 +29,31 @@ export ContractIcon from 'material-ui/svg-icons/action/code'; export CopyIcon from 'material-ui/svg-icons/content/content-copy'; export DashboardIcon from 'material-ui/svg-icons/action/dashboard'; export DeleteIcon from 'material-ui/svg-icons/action/delete'; +export DevelopIcon from 'material-ui/svg-icons/action/description'; export DoneIcon from 'material-ui/svg-icons/action/done-all'; +export DialIcon from 'material-ui/svg-icons/communication/dialpad'; export EditIcon from 'material-ui/svg-icons/content/create'; export ErrorIcon from 'material-ui/svg-icons/alert/error'; export FileUploadIcon from 'material-ui/svg-icons/file/file-upload'; export FileIcon from 'material-ui/svg-icons/editor/insert-drive-file'; export FingerprintIcon from 'material-ui/svg-icons/action/fingerprint'; +export GasIcon from 'material-ui/svg-icons/maps/local-gas-station'; +export InfoIcon from 'material-ui/svg-icons/action/info-outline'; export KeyIcon from 'material-ui/svg-icons/communication/vpn-key'; export KeyboardIcon from 'material-ui/svg-icons/hardware/keyboard'; export LinkIcon from 'material-ui/svg-icons/content/link'; +export ListIcon from 'material-ui/svg-icons/action/view-list'; export LockedIcon from 'material-ui/svg-icons/action/lock'; export MembershipIcon from 'material-ui/svg-icons/action/card-membership'; export MoveIcon from 'material-ui/svg-icons/action/open-with'; export NextIcon from 'material-ui/svg-icons/navigation/arrow-forward'; +export PauseIcon from 'material-ui/svg-icons/av/pause'; +export PlayIcon from 'material-ui/svg-icons/av/play-arrow'; export PrevIcon from 'material-ui/svg-icons/navigation/arrow-back'; export PrintIcon from 'material-ui/svg-icons/action/print'; export RefreshIcon from 'material-ui/svg-icons/action/autorenew'; +export ReorderIcon from 'material-ui/svg-icons/action/reorder'; +export ReplayIcon from 'material-ui/svg-icons/av/replay'; export SaveIcon from 'material-ui/svg-icons/content/save'; export SendIcon from 'material-ui/svg-icons/content/send'; export SettingsIcon from 'material-ui/svg-icons/action/settings'; diff --git a/js/src/ui/IdentityIcon/identityIcon.css b/js/src/ui/IdentityIcon/identityIcon.css index a651568f5..b995b5aad 100644 --- a/js/src/ui/IdentityIcon/identityIcon.css +++ b/js/src/ui/IdentityIcon/identityIcon.css @@ -14,9 +14,15 @@ /* You should have received a copy of the GNU General Public License /* along with Parity. If not, see . */ + .icon { border-radius: 50%; margin: 0; + + &.disabled { + filter: grayscale(100%); + opacity: 0.33; + } } .center { diff --git a/js/src/ui/IdentityIcon/identityIcon.js b/js/src/ui/IdentityIcon/identityIcon.js index aea7e941b..3db1bc763 100644 --- a/js/src/ui/IdentityIcon/identityIcon.js +++ b/js/src/ui/IdentityIcon/identityIcon.js @@ -33,6 +33,7 @@ class IdentityIcon extends Component { button: PropTypes.bool, center: PropTypes.bool, className: PropTypes.string, + disabled: PropTypes.bool, images: PropTypes.object.isRequired, inline: PropTypes.bool, padded: PropTypes.bool, @@ -83,10 +84,11 @@ class IdentityIcon extends Component { } render () { - const { address, button, className, center, inline, padded, tiny } = this.props; + const { address, button, className, center, disabled, inline, padded, tiny } = this.props; const { iconsrc } = this.state; const classes = [ styles.icon, + disabled ? styles.disabled : '', tiny ? styles.tiny : '', button ? styles.button : '', center ? styles.center : styles.left, diff --git a/js/src/ui/MethodDecoding/methodDecoding.js b/js/src/ui/MethodDecoding/methodDecoding.js index 25f031c62..87edb7779 100644 --- a/js/src/ui/MethodDecoding/methodDecoding.js +++ b/js/src/ui/MethodDecoding/methodDecoding.js @@ -120,6 +120,18 @@ class MethodDecoding extends Component { { gas.toFormat(0) } gas ({ gasPrice.div(1000000).toFormat(0) }M/ETH) + { + transaction.gasUsed + ? ( + + used + + { transaction.gasUsed.toFormat(0) } gas + + + ) + : null + } for a total transaction value of { this.renderEtherValue(gasValue) } { this.renderMinBlock() } diff --git a/js/src/ui/MethodDecoding/methodDecodingStore.js b/js/src/ui/MethodDecoding/methodDecodingStore.js index da23f916c..05a8f8546 100644 --- a/js/src/ui/MethodDecoding/methodDecodingStore.js +++ b/js/src/ui/MethodDecoding/methodDecodingStore.js @@ -148,7 +148,19 @@ export default class MethodDecodingStore { // Contract deployment if (!signature || signature === CONTRACT_CREATE || transaction.creates) { - return this.decodeContractCreation(result, contractAddress || transaction.creates); + const address = contractAddress || transaction.creates; + + return this.isContractCreation(input, address) + .then((isContractCreation) => { + if (!isContractCreation) { + result.contract = false; + result.deploy = false; + + return result; + } + + return this.decodeContractCreation(result, address); + }); } return this @@ -204,7 +216,7 @@ export default class MethodDecodingStore { const { input } = data; const abi = this._contractsAbi[contractAddress]; - if (!input || !abi || !abi.constructors || abi.constructors.length === 0) { + if (!abi || !abi.constructors || abi.constructors.length === 0) { return Promise.resolve(result); } @@ -306,6 +318,30 @@ export default class MethodDecodingStore { return Promise.resolve(this._isContract[contractAddress]); } + /** + * Check if the input resulted in a contract creation + * by checking that the contract address code contains + * a part of the input, or vice-versa + */ + isContractCreation (input, contractAddress) { + return this.api.eth + .getCode(contractAddress) + .then((code) => { + if (/^(0x)?0*$/.test(code)) { + return false; + } + + const strippedCode = code.replace(/^0x/, ''); + const strippedInput = input.replace(/^0x/, ''); + + return strippedInput.indexOf(strippedInput) >= 0 || strippedCode.indexOf(strippedInput) >= 0; + }) + .catch((error) => { + console.error(error); + return false; + }); + } + getCode (contractAddress) { // If zero address, resolve to '0x' if (!contractAddress || /^(0x)?0*$/.test(contractAddress)) { diff --git a/js/src/ui/ModalBox/modalBox.js b/js/src/ui/ModalBox/modalBox.js index f0366cf06..d74dc1c6e 100644 --- a/js/src/ui/ModalBox/modalBox.js +++ b/js/src/ui/ModalBox/modalBox.js @@ -22,13 +22,13 @@ import styles from './modalBox.css'; export default class ModalBox extends Component { static propTypes = { - children: PropTypes.node.isRequired, + children: PropTypes.node, icon: PropTypes.node.isRequired, summary: nodeOrStringProptype() } render () { - const { children, icon } = this.props; + const { icon } = this.props; return (
@@ -37,14 +37,26 @@ export default class ModalBox extends Component {
{ this.renderSummary() } -
- { children } -
+ { this.renderBody() }
); } + renderBody () { + const { children } = this.props; + + if (!children) { + return null; + } + + return ( +
+ { children } +
+ ); + } + renderSummary () { const { summary } = this.props; diff --git a/js/src/ui/Portal/portal.js b/js/src/ui/Portal/portal.js index 213b2f7a9..f4c64bf46 100644 --- a/js/src/ui/Portal/portal.js +++ b/js/src/ui/Portal/portal.js @@ -14,7 +14,6 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import EventListener from 'react-event-listener'; import React, { Component, PropTypes } from 'react'; import ReactDOM from 'react-dom'; import ReactPortal from 'react-portal'; @@ -23,6 +22,7 @@ import keycode from 'keycode'; import { nodeOrStringProptype } from '~/util/proptypes'; import { CloseIcon } from '~/ui/Icons'; import ParityBackground from '~/ui/ParityBackground'; +import StackEventListener from '~/ui/StackEventListener'; import Title from '~/ui/Title'; import styles from './portal.css'; @@ -44,6 +44,7 @@ export default class Portal extends Component { hideClose: PropTypes.bool, isChildModal: PropTypes.bool, isSmallModal: PropTypes.bool, + onClick: PropTypes.func, onKeyDown: PropTypes.func, steps: PropTypes.array, title: nodeOrStringProptype() @@ -89,13 +90,10 @@ export default class Portal extends Component { className ].join(' ') } - onClick={ this.stopEvent } + onClick={ this.handleContainerClick } onKeyDown={ this.handleKeyDown } > - + { this.renderClose() } { + const { onClick } = this.props; + + if (!onClick) { + return this.stopEvent(event); + } + + return onClick(event); + } + handleClose = () => { const { hideClose, onClose } = this.props; @@ -175,7 +183,7 @@ export default class Portal extends Component { switch (codeName) { case 'esc': event.preventDefault(); - return this.handleClose(); + return this.props.onClose(); } } diff --git a/js/src/ui/SectionList/sectionList.css b/js/src/ui/SectionList/sectionList.css index c6b3765ff..14e8bffd1 100644 --- a/js/src/ui/SectionList/sectionList.css +++ b/js/src/ui/SectionList/sectionList.css @@ -17,7 +17,6 @@ $transition: all 0.25s; $widthNormal: 33.33%; -$widthShrunk: 29%; $widthExpanded: 42%; .section { @@ -39,18 +38,19 @@ $widthExpanded: 42%; display: flex; justify-content: center; - /* TODO: As per JS comments, the flex-base could be adjusted in the future to allow for */ + /* TODO: As per JS comments, the flex-base could be adjusted in the future to allow for /* case where <> 3 columns are required should the need arrise from a UI pov. */ .item { box-sizing: border-box; display: flex; flex: 0 1 $widthNormal; - max-width: $widthNormal; opacity: 0.85; padding: 0.25em; + /* https://www.binarymoon.co.uk/2014/02/fixing-css-transitions-in-google-chrome/ */ transform: translateZ(0); transition: $transition; + width: 0; &:hover { opacity: 1; @@ -58,22 +58,13 @@ $widthExpanded: 42%; } } - &:hover { - .item { - &.stretchOn { - flex: 0 1 $widthShrunk; - max-width: $widthShrunk; - - &:hover { - flex: 0 0 $widthExpanded; - max-width: $widthExpanded; - } - } - } + .item.stretchOn:hover { + flex: 0 0 $widthExpanded; + max-width: $widthExpanded; } } } -.section+.section { +.section + .section { margin-top: 1em; } diff --git a/js/src/ui/SelectionList/selectionList.css b/js/src/ui/SelectionList/selectionList.css index eee61dbb5..b6e6b05f9 100644 --- a/js/src/ui/SelectionList/selectionList.css +++ b/js/src/ui/SelectionList/selectionList.css @@ -16,6 +16,7 @@ */ .item { + cursor: pointer; display: flex; flex: 1; height: 100%; @@ -23,7 +24,8 @@ width: 100%; &:hover { - box-shadow: inset 0 0 0 2px rgb(255, 255, 255); + filter: none; + opacity: 1; } .content { @@ -31,27 +33,57 @@ width: 100%; &:hover { - background: rgba(255, 255, 255, 0.25); + background-color: rgba(255, 255, 255, 0.15); } } .overlay { position: absolute; - right: 0.5em; - top: 0.5em; + left: 0.75em; + top: 0.75em; + z-index: 1; + + .icon, + .iconDisabled { + border-radius: 0.25em; + color: #333 !important; + cursor: pointer; + margin-right: 0.25em; + } + + .icon { + background: white; + } + + .iconDisabled { + background: #666; + + &:hover { + background: white; + } + } } } .selected { - box-shadow: inset 0 0 0 2px rgb(255, 255, 255); filter: none; + + &::after { + background: rgb(0, 151, 167); + content: ''; + height: 4px; + left: 0; + position: absolute; + right: 0; + top: 0; + } + + &.default::after { + background: rgb(167, 151, 0); + } } .unselected { - filter: grayscale(100%); - opacity: 0.5; -} - -.iconDisabled { - opacity: 0.15; + filter: grayscale(50%); + opacity: 0.75; } diff --git a/js/src/ui/SelectionList/selectionList.js b/js/src/ui/SelectionList/selectionList.js index 2293cf9c3..b8ee1fee6 100644 --- a/js/src/ui/SelectionList/selectionList.js +++ b/js/src/ui/SelectionList/selectionList.js @@ -16,7 +16,7 @@ import React, { Component, PropTypes } from 'react'; -import { CheckIcon, StarIcon, StarOutlineIcon } from '~/ui/Icons'; +import { StarIcon } from '~/ui/Icons'; import SectionList from '~/ui/SectionList'; import { arrayOrObjectProptype } from '~/util/proptypes'; @@ -62,15 +62,25 @@ export default class SelectionList extends Component { let defaultIcon = null; if (onDefaultClick) { - defaultIcon = isSelected && item.default - ? <StarIcon /> - : <StarOutlineIcon className={ styles.iconDisabled } onClick={ makeDefault } />; + defaultIcon = ( + <div className={ styles.overlay }> + { + isSelected && item.default + ? <StarIcon className={ styles.icon } /> + : <StarIcon className={ styles.iconDisabled } onClick={ makeDefault } /> + } + </div> + ); } const classes = isSelected ? [styles.item, styles.selected] : [styles.item, styles.unselected]; + if (item.default) { + classes.push(styles.default); + } + return ( <div className={ classes.join(' ') }> <div @@ -79,14 +89,7 @@ export default class SelectionList extends Component { > { renderItem(item, index) } </div> - <div className={ styles.overlay }> - { defaultIcon } - { - isSelected - ? <CheckIcon onClick={ selectItem } /> - : <CheckIcon className={ styles.iconDisabled } onClick={ selectItem } /> - } - </div> + { defaultIcon } </div> ); } diff --git a/js/src/ui/StackEventListener/index.js b/js/src/ui/StackEventListener/index.js new file mode 100644 index 000000000..e046c51d5 --- /dev/null +++ b/js/src/ui/StackEventListener/index.js @@ -0,0 +1,17 @@ +// Copyright 2015-2017 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/>. + +export default from './stackEventListener'; diff --git a/js/src/ui/StackEventListener/stackEventListener.js b/js/src/ui/StackEventListener/stackEventListener.js new file mode 100644 index 000000000..586ddcad6 --- /dev/null +++ b/js/src/ui/StackEventListener/stackEventListener.js @@ -0,0 +1,56 @@ +// Copyright 2015-2017 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 ReactEventListener from 'react-event-listener'; +import React, { Component, PropTypes } from 'react'; + +let listenerId = 0; +let listenerIds = []; + +export default class StackEventListener extends Component { + static propTypes = { + onKeyUp: PropTypes.func.isRequired + }; + + componentWillMount () { + // Add to the list of listeners on mount + this.id = ++listenerId; + listenerIds.push(this.id); + } + + componentWillUnmount () { + // Remove from the listeners list on unmount + listenerIds = listenerIds.filter((id) => this.id !== id); + } + + render () { + return ( + <ReactEventListener + target='window' + onKeyUp={ this.handleKeyUp } + /> + ); + } + + handleKeyUp = (event) => { + // Only handle event if last of the listeners list + if (this.id !== listenerIds.slice(-1)[0]) { + return event; + } + + return this.props.onKeyUp(event); + } +} diff --git a/js/src/ui/TxHash/txHash.js b/js/src/ui/TxHash/txHash.js index 724004ae7..c5342ccd3 100644 --- a/js/src/ui/TxHash/txHash.js +++ b/js/src/ui/TxHash/txHash.js @@ -34,8 +34,8 @@ class TxHash extends Component { static propTypes = { hash: PropTypes.string.isRequired, - isTest: PropTypes.bool, maxConfirmations: PropTypes.number, + netVersion: PropTypes.string.isRequired, summary: PropTypes.bool } @@ -116,10 +116,10 @@ class TxHash extends Component { } render () { - const { hash, isTest, summary } = this.props; + const { hash, netVersion, summary } = this.props; const hashLink = ( - <a href={ txLink(hash, isTest) } target='_blank'> + <a href={ txLink(hash, false, netVersion) } target='_blank'> <ShortenedHash data={ hash } /> </a> ); @@ -255,9 +255,11 @@ class TxHash extends Component { } function mapStateToProps (state) { - const { isTest } = state.nodeStatus; + const { netVersion } = state.nodeStatus; - return { isTest }; + return { + netVersion + }; } export default connect( diff --git a/js/src/ui/TxHash/txHash.spec.js b/js/src/ui/TxHash/txHash.spec.js index 328a43836..8e44c7e57 100644 --- a/js/src/ui/TxHash/txHash.spec.js +++ b/js/src/ui/TxHash/txHash.spec.js @@ -69,7 +69,9 @@ function createRedux () { subscribe: sinon.stub(), getState: () => { return { - nodeStatus: { isTest: true } + nodeStatus: { + netVersion: '42' + } }; } }; diff --git a/js/src/ui/TxList/TxRow/txRow.js b/js/src/ui/TxList/TxRow/txRow.js index e42f64159..ef30fc8d1 100644 --- a/js/src/ui/TxList/TxRow/txRow.js +++ b/js/src/ui/TxList/TxRow/txRow.js @@ -35,7 +35,7 @@ class TxRow extends Component { static propTypes = { accountAddresses: PropTypes.array.isRequired, address: PropTypes.string.isRequired, - isTest: PropTypes.bool.isRequired, + netVersion: PropTypes.string.isRequired, tx: PropTypes.object.isRequired, block: PropTypes.object, @@ -48,7 +48,7 @@ class TxRow extends Component { }; render () { - const { tx, address, isTest, historic, className } = this.props; + const { address, className, historic, netVersion, tx } = this.props; return ( <tr className={ className || '' }> @@ -60,7 +60,7 @@ class TxRow extends Component { <div> <a className={ styles.link } - href={ txLink(tx.hash, isTest) } + href={ txLink(tx.hash, false, netVersion) } target='_blank' > { `${tx.hash.substr(2, 6)}...${tx.hash.slice(-6)}` } @@ -156,8 +156,13 @@ function mapStateToProps (initState) { const { accounts } = initState.personal; const accountAddresses = Object.keys(accounts); - return () => { - return { accountAddresses }; + return (state) => { + const { netVersion } = state.nodeStatus; + + return { + accountAddresses, + netVersion + }; }; } @@ -165,4 +170,3 @@ export default connect( mapStateToProps, null )(TxRow); - diff --git a/js/src/ui/TxList/TxRow/txRow.spec.js b/js/src/ui/TxList/TxRow/txRow.spec.js index dc9f4d3cc..da0c21c14 100644 --- a/js/src/ui/TxList/TxRow/txRow.spec.js +++ b/js/src/ui/TxList/TxRow/txRow.spec.js @@ -30,6 +30,9 @@ const STORE = { subscribe: sinon.stub(), getState: () => { return { + nodeStatus: { + netVersion: '42' + }, personal: { accounts: { '0x123': {} @@ -61,7 +64,7 @@ describe('ui/TxList/TxRow', () => { value: new BigNumber(1) }; - expect(render({ address: '0x123', block, isTest: true, tx })).to.be.ok; + expect(render({ address: '0x123', block, netVersion: '42', tx })).to.be.ok; }); it('renders an account link', () => { @@ -75,7 +78,7 @@ describe('ui/TxList/TxRow', () => { value: new BigNumber(1) }; - const element = render({ address: '0x123', block, isTest: true, tx }); + const element = render({ address: '0x123', block, netVersion: '42', tx }); expect(element.find('Link').prop('to')).to.equal('/accounts/0x123'); }); @@ -91,7 +94,7 @@ describe('ui/TxList/TxRow', () => { value: new BigNumber(1) }; - const element = render({ address: '0x123', block, isTest: true, tx }); + const element = render({ address: '0x123', block, netVersion: '42', tx }); expect(element.find('Link').prop('to')).to.equal('/addresses/0x456'); }); diff --git a/js/src/ui/TxList/store.js b/js/src/ui/TxList/store.js index 1a3c2a2ff..1e670e31d 100644 --- a/js/src/ui/TxList/store.js +++ b/js/src/ui/TxList/store.js @@ -51,6 +51,7 @@ export default class Store { return bnB.comparedTo(bnA); }); + this._pendingHashes = this.sortedHashes.filter((hash) => this.transactions[hash].blockNumber.eq(0)); }); } @@ -85,26 +86,53 @@ export default class Store { this._subscriptionId = 0; } - loadTransactions (_txhashes) { - const txhashes = _txhashes.filter((hash) => !this.transactions[hash] || this._pendingHashes.includes(hash)); + loadTransactions (_txhashes = []) { + const promises = _txhashes + .filter((txhash) => !this.transactions[txhash] || this._pendingHashes.includes(txhash)) + .map((txhash) => { + return Promise + .all([ + this._api.eth.getTransactionByHash(txhash), + this._api.eth.getTransactionReceipt(txhash) + ]) + .then(([ + transaction = {}, + transactionReceipt = {} + ]) => { + return { + ...transactionReceipt, + ...transaction + }; + }); + }); - if (!txhashes || !txhashes.length) { + if (!promises.length) { return; } Promise - .all(txhashes.map((txhash) => this._api.eth.getTransactionByHash(txhash))) + .all(promises) .then((_transactions) => { - const transactions = _transactions.filter((tx) => tx); + const blockNumbers = []; + const transactions = _transactions + .filter((tx) => tx && tx.hash) + .reduce((txs, tx) => { + txs[tx.hash] = tx; - this.addTransactions( - transactions.reduce((transactions, tx, index) => { - transactions[txhashes[index]] = tx; - return transactions; - }, {}) - ); + if (tx.blockNumber && tx.blockNumber.gt(0)) { + blockNumbers.push(tx.blockNumber.toNumber()); + } - this.loadBlocks(transactions.map((tx) => tx.blockNumber ? tx.blockNumber.toNumber() : 0)); + return txs; + }, {}); + + // No need to add transactions if there are none + if (Object.keys(transactions).length === 0) { + return false; + } + + this.addTransactions(transactions); + this.loadBlocks(blockNumbers); }) .catch((error) => { console.warn('loadTransactions', error); diff --git a/js/src/ui/TxList/txList.js b/js/src/ui/TxList/txList.js index 044c6581c..c2224903f 100644 --- a/js/src/ui/TxList/txList.js +++ b/js/src/ui/TxList/txList.js @@ -27,7 +27,7 @@ import styles from './txList.css'; class TxList extends Component { static contextTypes = { api: PropTypes.object.isRequired - } + }; static propTypes = { address: PropTypes.string.isRequired, @@ -35,8 +35,8 @@ class TxList extends Component { PropTypes.array, PropTypes.object ]).isRequired, - isTest: PropTypes.bool.isRequired - } + netVersion: PropTypes.string.isRequired + }; store = new Store(this.context.api); @@ -63,7 +63,7 @@ class TxList extends Component { } renderRows () { - const { address, isTest } = this.props; + const { address, netVersion } = this.props; return this.store.sortedHashes.map((txhash) => { const tx = this.store.transactions[txhash]; @@ -76,7 +76,7 @@ class TxList extends Component { tx={ tx } block={ block } address={ address } - isTest={ isTest } + netVersion={ netVersion } /> ); }); @@ -84,10 +84,10 @@ class TxList extends Component { } function mapStateToProps (state) { - const { isTest } = state.nodeStatus; + const { netVersion } = state.nodeStatus; return { - isTest + netVersion }; } diff --git a/js/src/ui/TxList/txList.spec.js b/js/src/ui/TxList/txList.spec.js index 48ed5aac5..58a5237ac 100644 --- a/js/src/ui/TxList/txList.spec.js +++ b/js/src/ui/TxList/txList.spec.js @@ -30,7 +30,7 @@ const STORE = { getState: () => { return { nodeStatus: { - isTest: true + netVersion: '42' } }; } diff --git a/js/src/ui/VaultTag/index.js b/js/src/ui/VaultTag/index.js new file mode 100644 index 000000000..af0419c99 --- /dev/null +++ b/js/src/ui/VaultTag/index.js @@ -0,0 +1,17 @@ +// Copyright 2015-2017 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/>. + +export default from './vaultTag'; diff --git a/js/src/ui/VaultTag/vaultTag.css b/js/src/ui/VaultTag/vaultTag.css new file mode 100644 index 000000000..acb139c6e --- /dev/null +++ b/js/src/ui/VaultTag/vaultTag.css @@ -0,0 +1,48 @@ +/* Copyright 2015-2017 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/>. +*/ + +/* TODO: These tag styles are shared with Balances & Certifications - should be made into +/* a component that can take a list of tags and render them in the correct format +*/ +.vault { + display: flex; + flex-wrap: wrap; + margin: 0.75em 0 0; + vertical-align: top; + + .vaultBody { + margin: 0.75em 0.5em 0 0; + background: rgba(255, 255, 255, 0.07); + border-radius: 16px; + max-height: 24px; + max-width: 100%; + display: flex; + align-items: center; + } + + img { + height: 32px !important; + margin: -4px 1em 0 0; + width: 32px !important; + } + + .text { + margin: 0 0.5em 0 0; + text-transform: uppercase; + white-space: nowrap; + } +} diff --git a/js/src/ui/VaultTag/vaultTag.js b/js/src/ui/VaultTag/vaultTag.js new file mode 100644 index 000000000..303aaca61 --- /dev/null +++ b/js/src/ui/VaultTag/vaultTag.js @@ -0,0 +1,45 @@ +// Copyright 2015-2017 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 React, { Component, PropTypes } from 'react'; + +import IdentityIcon from '~/ui/IdentityIcon'; + +import styles from './vaultTag.css'; + +export default class VaultTag extends Component { + static propTypes = { + vault: PropTypes.string.isRequired + }; + + render () { + const { vault } = this.props; + + return ( + <div className={ styles.vault }> + <div className={ styles.vaultBody }> + <IdentityIcon + address={ vault } + inline + /> + <div className={ styles.text }> + { vault } + </div> + </div> + </div> + ); + } +} diff --git a/js/src/ui/index.js b/js/src/ui/index.js index 992bb2b05..d076986be 100644 --- a/js/src/ui/index.js +++ b/js/src/ui/index.js @@ -30,7 +30,7 @@ export DappCard from './DappCard'; export DappIcon from './DappIcon'; export Errors from './Errors'; export Features, { FEATURES, FeaturesStore } from './Features'; -export Form, { AddressSelect, DappUrlInput, FormWrap, Input, InputAddress, InputAddressSelect, InputChip, InputDate, InputInline, InputTime, Label, RadioButtons, Select, TypedInput } from './Form'; +export Form, { AddressSelect, DappUrlInput, FileSelect, FormWrap, Input, InputAddress, InputAddressSelect, InputChip, InputDate, InputInline, InputTime, Label, RadioButtons, Select, TypedInput, VaultSelect } from './Form'; export GasPriceEditor from './GasPriceEditor'; export GasPriceSelector from './GasPriceSelector'; export Icons from './Icons'; @@ -56,4 +56,5 @@ export Tooltips, { Tooltip } from './Tooltips'; export TxHash from './TxHash'; export TxList from './TxList'; export VaultCard from './VaultCard'; +export VaultTag from './VaultTag'; export Warning from './Warning'; diff --git a/js/src/util/tx.js b/js/src/util/tx.js index 49ffee3da..8a48a3d26 100644 --- a/js/src/util/tx.js +++ b/js/src/util/tx.js @@ -61,17 +61,6 @@ export function estimateGas (_func, _options, _values = []) { const { func, options, values } = callArgs; return func._estimateGas(options, values); - }) - .then((gas) => { - return WalletsUtils - .isWallet(_func.contract.api, _options.from) - .then((isWallet) => { - if (isWallet) { - return gas.mul(1.5); - } - - return gas; - }); }); } @@ -84,6 +73,114 @@ export function postTransaction (_func, _options, _values = []) { }); } +export function deploy (contract, _options, values, metadata = {}, statecb = () => {}) { + const options = { ..._options }; + const { api } = contract; + const address = options.from; + + return WalletsUtils + .isWallet(api, address) + .then((isWallet) => { + if (!isWallet) { + return contract.deploy(options, values, statecb); + } + + statecb(null, { state: 'estimateGas' }); + + return deployEstimateGas(contract, options, values) + .then(([gasEst, gas]) => { + options.gas = gas.toFixed(0); + + statecb(null, { state: 'postTransaction', gas }); + + return WalletsUtils.getDeployArgs(contract, options, values); + }) + .then((callArgs) => { + const { func, options, values } = callArgs; + + return func._postTransaction(options, values) + .then((requestId) => { + statecb(null, { state: 'checkRequest', requestId }); + return contract._pollCheckRequest(requestId); + }) + .then((txhash) => { + statecb(null, { state: 'getTransactionReceipt', txhash }); + return contract._pollTransactionReceipt(txhash, options.gas); + }) + .then((receipt) => { + if (receipt.gasUsed.eq(options.gas)) { + throw new Error(`Contract not deployed, gasUsed == ${options.gas.toFixed(0)}`); + } + + const logs = WalletsUtils.parseLogs(api, receipt.logs || []); + + const confirmationLog = logs.find((log) => log.event === 'ConfirmationNeeded'); + const transactionLog = logs.find((log) => log.event === 'SingleTransact'); + + if (!confirmationLog && !transactionLog) { + throw new Error('Something went wrong in the Wallet Contract (no logs have been emitted)...'); + } + + // Confirmations are needed from the other owners + if (confirmationLog) { + const operationHash = api.util.bytesToHex(confirmationLog.params.operation.value); + + // Add the contract to pending contracts + WalletsUtils.addPendingContract(address, operationHash, metadata); + statecb(null, { state: 'confirmationNeeded' }); + return; + } + + // Set the contract address in the receip + receipt.contractAddress = transactionLog.params.created.value; + + const contractAddress = receipt.contractAddress; + + statecb(null, { state: 'hasReceipt', receipt }); + contract._receipt = receipt; + contract._address = contractAddress; + + statecb(null, { state: 'getCode' }); + + return api.eth.getCode(contractAddress) + .then((code) => { + if (code === '0x') { + throw new Error('Contract not deployed, getCode returned 0x'); + } + + statecb(null, { state: 'completed' }); + return contractAddress; + }); + }); + }); + }); +} + +export function deployEstimateGas (contract, _options, values) { + const options = { ..._options }; + const { api } = contract; + const address = options.from; + + return WalletsUtils + .isWallet(api, address) + .then((isWallet) => { + if (!isWallet) { + return contract.deployEstimateGas(options, values); + } + + return WalletsUtils + .getDeployArgs(contract, options, values) + .then((callArgs) => { + const { func, options, values } = callArgs; + + return func.estimateGas(options, values); + }) + .then((gasEst) => { + return [gasEst, gasEst.mul(1.05)]; + }); + }); +} + export function patchApi (api) { api.patch = { ...api.patch, diff --git a/js/src/util/wallets.js b/js/src/util/wallets.js index 3bc86f6ed..904f77900 100644 --- a/js/src/util/wallets.js +++ b/js/src/util/wallets.js @@ -16,59 +16,154 @@ import BigNumber from 'bignumber.js'; import { intersection, range, uniq } from 'lodash'; +import store from 'store'; +import Abi from '~/abi'; import Contract from '~/api/contract'; import { bytesToHex, toHex } from '~/api/util/format'; import { validateAddress } from '~/util/validation'; import WalletAbi from '~/contracts/abi/wallet.json'; +import OldWalletAbi from '~/contracts/abi/old-wallet.json'; + +const LS_PENDING_CONTRACTS_KEY = '_parity::wallets::pendingContracts'; const _cachedWalletLookup = {}; +let _cachedAccounts = {}; + +const walletAbi = new Abi(WalletAbi); +const oldWalletAbi = new Abi(OldWalletAbi); + +const walletEvents = walletAbi.events.reduce((events, event) => { + events[event.name] = event; + return events; +}, {}); + +const oldWalletEvents = oldWalletAbi.events.reduce((events, event) => { + events[event.name] = event; + return events; +}, {}); + +const WalletSignatures = { + OwnerChanged: toHex(walletEvents.OwnerChanged.signature), + OwnerAdded: toHex(walletEvents.OwnerAdded.signature), + OwnerRemoved: toHex(walletEvents.OwnerRemoved.signature), + RequirementChanged: toHex(walletEvents.RequirementChanged.signature), + Confirmation: toHex(walletEvents.Confirmation.signature), + Revoke: toHex(walletEvents.Revoke.signature), + Deposit: toHex(walletEvents.Deposit.signature), + SingleTransact: toHex(walletEvents.SingleTransact.signature), + MultiTransact: toHex(walletEvents.MultiTransact.signature), + ConfirmationNeeded: toHex(walletEvents.ConfirmationNeeded.signature), + + Old: { + SingleTransact: toHex(oldWalletEvents.SingleTransact.signature), + MultiTransact: toHex(oldWalletEvents.MultiTransact.signature) + } +}; export default class WalletsUtils { + static getWalletSignatures () { + return WalletSignatures; + } + + static getPendingContracts () { + return store.get(LS_PENDING_CONTRACTS_KEY) || {}; + } + + static setPendingContracts (contracts = {}) { + return store.set(LS_PENDING_CONTRACTS_KEY, contracts); + } + + static removePendingContract (operationHash) { + const nextContracts = WalletsUtils.getPendingContracts(); + + delete nextContracts[operationHash]; + WalletsUtils.setPendingContracts(nextContracts); + } + + static addPendingContract (address, operationHash, metadata) { + const nextContracts = { + ...WalletsUtils.getPendingContracts(), + [ operationHash ]: { + address, + metadata, + operationHash + } + }; + + WalletsUtils.setPendingContracts(nextContracts); + } + + static cacheAccounts (accounts) { + _cachedAccounts = accounts; + } + static getCallArgs (api, options, values = []) { const walletContract = new Contract(api, WalletAbi); + const walletAddress = options.from; - const promises = [ - api.parity.accountsInfo(), - WalletsUtils.fetchOwners(walletContract.at(options.from)) - ]; + return WalletsUtils + .fetchOwners(walletContract.at(walletAddress)) + .then((owners) => { + const addresses = Object.keys(_cachedAccounts); + const ownerAddress = intersection(addresses, owners).pop(); - return Promise - .all(promises) - .then(([ accounts, owners ]) => { - const addresses = Object.keys(accounts); - const owner = intersection(addresses, owners).pop(); - - if (!owner) { + if (!ownerAddress) { return false; } - return owner; - }) - .then((owner) => { - if (!owner) { - return false; - } - - const _options = Object.assign({}, options); - const { from, to, value = new BigNumber(0), data } = options; + const account = _cachedAccounts[ownerAddress]; + const _options = { ...options }; + const { to, value = new BigNumber(0), data } = _options; delete _options.data; const nextValues = [ to, value, data ]; const nextOptions = { ..._options, - from: owner, - to: from, + from: ownerAddress, + to: walletAddress, value: new BigNumber(0) }; const execFunc = walletContract.instance.execute; + const callArgs = { func: execFunc, options: nextOptions, values: nextValues }; - return { func: execFunc, options: nextOptions, values: nextValues }; + if (!account.wallet) { + return callArgs; + } + + const nextData = walletContract.getCallData(execFunc, nextOptions, nextValues); + + return WalletsUtils.getCallArgs(api, { ...nextOptions, data: nextData }, nextValues); }); } + static getDeployArgs (contract, options, values) { + const { api } = contract; + const func = contract.constructors[0]; + + options.data = contract.getCallData(func, options, values); + options.to = '0x'; + + return WalletsUtils + .getCallArgs(api, options, values) + .then((callArgs) => { + if (!callArgs) { + console.error('no call args', callArgs); + throw new Error('you do not own this wallet'); + } + + return callArgs; + }); + } + + static parseLogs (api, logs = []) { + const walletContract = new Contract(api, WalletAbi); + + return walletContract.parseEventLogs(logs); + } + /** * Check whether the given address could be * a Wallet. The result is cached in order not @@ -199,16 +294,18 @@ export default class WalletsUtils { } static fetchTransactions (walletContract) { - const walletInstance = walletContract.instance; - const signatures = { - single: toHex(walletInstance.SingleTransact.signature), - multi: toHex(walletInstance.MultiTransact.signature), - deposit: toHex(walletInstance.Deposit.signature) - }; + const { api } = walletContract; + const pendingContracts = WalletsUtils.getPendingContracts(); return walletContract .getAllLogs({ - topics: [ [ signatures.single, signatures.multi, signatures.deposit ] ] + topics: [ [ + WalletSignatures.SingleTransact, + WalletSignatures.MultiTransact, + WalletSignatures.Deposit, + WalletSignatures.Old.SingleTransact, + WalletSignatures.Old.MultiTransact + ] ] }) .then((logs) => { return logs.sort((logA, logB) => { @@ -226,11 +323,11 @@ export default class WalletsUtils { const signature = toHex(log.topics[0]); const value = log.params.value.value; - const from = signature === signatures.deposit + const from = signature === WalletSignatures.Deposit ? log.params['_from'].value : walletContract.address; - const to = signature === signatures.deposit + const to = signature === WalletSignatures.Deposit ? walletContract.address : log.params.to.value; @@ -240,8 +337,53 @@ export default class WalletsUtils { from, to, value }; + if (log.params.created && log.params.created.value && !/^(0x)?0*$/.test(log.params.created.value)) { + transaction.creates = log.params.created.value; + delete transaction.to; + } + if (log.params.operation) { - transaction.operation = bytesToHex(log.params.operation.value); + const operation = bytesToHex(log.params.operation.value); + + // Add the pending contract to the contracts + if (pendingContracts[operation]) { + const { metadata } = pendingContracts[operation]; + const contractName = metadata.name; + + metadata.blockNumber = log.blockNumber; + + // The contract creation might not be in the same log, + // but must be in the same transaction (eg. Contract creation + // from Wallet within a Wallet) + api.eth + .getTransactionReceipt(log.transactionHash) + .then((transactionReceipt) => { + const transactionLogs = WalletsUtils.parseLogs(api, transactionReceipt.logs); + const creationLog = transactionLogs.find((log) => { + return log.params.created && !/^(0x)?0*$/.test(log.params.created.value); + }); + + if (!creationLog) { + return false; + } + + const contractAddress = creationLog.params.created.value; + + return Promise + .all([ + api.parity.setAccountName(contractAddress, contractName), + api.parity.setAccountMeta(contractAddress, metadata) + ]) + .then(() => { + WalletsUtils.removePendingContract(operation); + }); + }) + .catch((error) => { + console.error('adding wallet contract', error); + }); + } + + transaction.operation = operation; } if (log.params.data) { diff --git a/js/src/views/Account/Header/header.css b/js/src/views/Account/Header/header.css index 62f072574..f894b7c49 100644 --- a/js/src/views/Account/Header/header.css +++ b/js/src/views/Account/Header/header.css @@ -66,6 +66,7 @@ .text { display: inline-block; opacity: 0.25; + text-transform: uppercase; } } diff --git a/js/src/views/Account/Header/header.js b/js/src/views/Account/Header/header.js index e92f545a1..d3c4a9c69 100644 --- a/js/src/views/Account/Header/header.js +++ b/js/src/views/Account/Header/header.js @@ -17,7 +17,7 @@ import React, { Component, PropTypes } from 'react'; import { FormattedMessage } from 'react-intl'; -import { Balance, Certifications, Container, CopyToClipboard, ContainerTitle, IdentityIcon, IdentityName, QrCode, Tags } from '~/ui'; +import { Balance, Certifications, Container, CopyToClipboard, ContainerTitle, IdentityIcon, IdentityName, QrCode, Tags, VaultTag } from '~/ui'; import styles from './header.css'; @@ -27,6 +27,7 @@ export default class Header extends Component { balance: PropTypes.object, children: PropTypes.node, className: PropTypes.string, + disabled: PropTypes.bool, hideName: PropTypes.bool, isContract: PropTypes.bool }; @@ -39,7 +40,7 @@ export default class Header extends Component { }; render () { - const { account, balance, children, className, hideName } = this.props; + const { account, balance, children, className, disabled, hideName } = this.props; if (!account) { return null; @@ -58,14 +59,16 @@ export default class Header extends Component { <IdentityIcon address={ address } className={ styles.identityIcon } + disabled={ disabled } /> <div className={ styles.info }> { this.renderName() } <div className={ [ hideName ? styles.bigaddress : '', styles.addressline ].join(' ') }> <CopyToClipboard data={ address } /> - <div className={ styles.address }>{ address }</div> + <div className={ styles.address }> + { address } + </div> </div> - { this.renderVault() } { this.renderUuid() } <div className={ styles.infoline }> { meta.description } @@ -77,6 +80,7 @@ export default class Header extends Component { balance={ balance } /> <Certifications address={ address } /> + { this.renderVault() } </div> </div> <div className={ styles.tags }> @@ -165,15 +169,7 @@ export default class Header extends Component { } return ( - <div className={ styles.vault }> - <IdentityIcon - address={ meta.vault } - inline - /> - <div className={ styles.text }> - { meta.vault } - </div> - </div> + <VaultTag vault={ meta.vault } /> ); } } diff --git a/js/src/views/Account/Transactions/store.js b/js/src/views/Account/Transactions/store.js index 2ad962904..10d5af025 100644 --- a/js/src/views/Account/Transactions/store.js +++ b/js/src/views/Account/Transactions/store.js @@ -21,8 +21,8 @@ import etherscan from '~/3rdparty/etherscan'; export default class Store { @observable address = null; @observable isLoading = false; - @observable isTest = undefined; @observable isTracing = false; + @observable netVersion = '0'; @observable txHashes = []; constructor (api) { @@ -44,8 +44,8 @@ export default class Store { this.isLoading = isLoading; } - @action setTest = (isTest) => { - this.isTest = isTest; + @action setNetVersion = (netVersion) => { + this.netVersion = netVersion; } @action setTracing = (isTracing) => { @@ -55,7 +55,7 @@ export default class Store { @action updateProps = (props) => { transaction(() => { this.setAddress(props.address); - this.setTest(props.isTest); + this.setNetVersion(props.netVersion); // TODO: When tracing is enabled again, adjust to actually set this.setTracing(false && props.traceMode); @@ -65,7 +65,7 @@ export default class Store { } getTransactions () { - if (this.isTest === undefined) { + if (this.netVersion === '0') { return Promise.resolve(); } @@ -87,7 +87,7 @@ export default class Store { } fetchEtherscanTransactions () { - return etherscan.account.transactions(this.address, 0, this.isTest); + return etherscan.account.transactions(this.address, 0, false, this.netVersion); } fetchTraceTransactions () { diff --git a/js/src/views/Account/Transactions/store.spec.js b/js/src/views/Account/Transactions/store.spec.js index 04b89e1fb..c99e91512 100644 --- a/js/src/views/Account/Transactions/store.spec.js +++ b/js/src/views/Account/Transactions/store.spec.js @@ -43,7 +43,7 @@ function mockQuery () { sort: 'desc' }, reply: [{ hash: '123' }] - }], true); + }], false, '42'); } describe('views/Account/Transactions/store', () => { @@ -94,10 +94,10 @@ describe('views/Account/Transactions/store', () => { }); }); - describe('setTest', () => { - it('sets the isTest flag', () => { - store.setTest(true); - expect(store.isTest).to.be.true; + describe('setNetVersion', () => { + it('sets the netVersion', () => { + store.setNetVersion('testing'); + expect(store.netVersion).to.equal('testing'); }); }); @@ -124,7 +124,7 @@ describe('views/Account/Transactions/store', () => { it('retrieves the hashes via etherscan', () => { sinon.spy(store, 'fetchEtherscanTransactions'); store.setAddress(ADDRESS); - store.setTest(true); + store.setNetVersion('42'); store.setTracing(false); return store.getTransactions().then(() => { @@ -137,7 +137,7 @@ describe('views/Account/Transactions/store', () => { it('retrieves the hashes via tracing', () => { sinon.spy(store, 'fetchTraceTransactions'); store.setAddress(ADDRESS); - store.setTest(true); + store.setNetVersion('42'); store.setTracing(true); return store.getTransactions().then(() => { @@ -151,7 +151,7 @@ describe('views/Account/Transactions/store', () => { describe('fetchEtherscanTransactions', () => { it('retrieves the transactions', () => { store.setAddress(ADDRESS); - store.setTest(true); + store.setNetVersion('42'); return store.fetchEtherscanTransactions().then((transactions) => { expect(transactions).to.deep.equal([{ @@ -169,7 +169,7 @@ describe('views/Account/Transactions/store', () => { describe('fetchTraceTransactions', () => { it('retrieves the transactions', () => { store.setAddress(ADDRESS); - store.setTest(true); + store.setNetVersion('42'); return store.fetchTraceTransactions().then((transactions) => { expect(transactions).to.deep.equal([ diff --git a/js/src/views/Account/Transactions/transactions.js b/js/src/views/Account/Transactions/transactions.js index 1e74726c1..f36529713 100644 --- a/js/src/views/Account/Transactions/transactions.js +++ b/js/src/views/Account/Transactions/transactions.js @@ -32,7 +32,7 @@ class Transactions extends Component { static propTypes = { address: PropTypes.string.isRequired, - isTest: PropTypes.bool, + netVersion: PropTypes.string.isRequired, traceMode: PropTypes.bool } @@ -48,7 +48,7 @@ class Transactions extends Component { return; } - const hasChanged = ['isTest', 'address'] + const hasChanged = ['address', 'netVersion'] .map(key => newProps[key] !== this.props[key]) .reduce((truth, keyTruth) => truth || keyTruth, false); @@ -112,10 +112,10 @@ class Transactions extends Component { } function mapStateToProps (state) { - const { isTest, traceMode } = state.nodeStatus; + const { netVersion, traceMode } = state.nodeStatus; return { - isTest, + netVersion, traceMode }; } diff --git a/js/src/views/Account/account.js b/js/src/views/Account/account.js index 4e457c94d..c2b600be8 100644 --- a/js/src/views/Account/account.js +++ b/js/src/views/Account/account.js @@ -21,11 +21,14 @@ import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import shapeshiftBtn from '~/../assets/images/shapeshift-btn.png'; -import { EditMeta, DeleteAccount, Shapeshift, Verification, Transfer, PasswordManager } from '~/modals'; +import HardwareStore from '~/mobx/hardwareStore'; +import { DeleteAccount, EditMeta, Faucet, PasswordManager, Shapeshift, Transfer, Verification } from '~/modals'; import { setVisibleAccounts } from '~/redux/providers/personalActions'; import { fetchCertifiers, fetchCertifications } from '~/redux/providers/certifications/actions'; import { Actionbar, Button, Page } from '~/ui'; -import { DeleteIcon, EditIcon, LockedIcon, SendIcon, VerifyIcon } from '~/ui/Icons'; +import { DeleteIcon, DialIcon, EditIcon, LockedIcon, SendIcon, VerifyIcon } from '~/ui/Icons'; + +import DeleteAddress from '../Address/Delete'; import Header from './Header'; import Store from './store'; @@ -34,6 +37,10 @@ import styles from './account.css'; @observer class Account extends Component { + static contextTypes = { + api: PropTypes.object.isRequired + }; + static propTypes = { fetchCertifiers: PropTypes.func.isRequired, fetchCertifications: PropTypes.func.isRequired, @@ -41,10 +48,13 @@ class Account extends Component { accounts: PropTypes.object, balances: PropTypes.object, + certifications: PropTypes.object, + netVersion: PropTypes.string.isRequired, params: PropTypes.object } store = new Store(); + hwstore = HardwareStore.get(this.context.api); componentDidMount () { this.props.fetchCertifiers(); @@ -83,19 +93,23 @@ class Account extends Component { return null; } + const isAvailable = !account.hardware || this.hwstore.isConnected(address); + return ( <div> { this.renderDeleteDialog(account) } { this.renderEditDialog(account) } + { this.renderFaucetDialog() } { this.renderFundDialog() } { this.renderPasswordDialog(account) } { this.renderTransferDialog(account, balance) } { this.renderVerificationDialog() } - { this.renderActionbar(balance) } + { this.renderActionbar(account, balance) } <Page padded> <Header account={ account } balance={ balance } + disabled={ !isAvailable } /> <Transactions accounts={ accounts } @@ -106,8 +120,35 @@ class Account extends Component { ); } - renderActionbar (balance) { + isKovan = (netVersion) => { + return netVersion === '42'; + } + + isMainnet = (netVersion) => { + return netVersion === '1'; + } + + isFaucettable = (netVersion, certifications, address) => { + return this.isKovan(netVersion) || ( + this.isMainnet(netVersion) && + this.isSmsCertified(certifications, address) + ); + } + + isSmsCertified = (_certifications, address) => { + const certifications = _certifications && _certifications[address] + ? _certifications[address].filter((cert) => cert.name.indexOf('smsverification') === 0) + : []; + + return certifications.length !== 0; + } + + renderActionbar (account, balance) { + const { certifications, netVersion } = this.props; + const { address } = this.props.params; const showTransferButton = !!(balance && balance.tokens); + const isVerifiable = this.isMainnet(netVersion); + const isFaucettable = this.isFaucettable(netVersion, certifications, address); const buttons = [ <Button @@ -138,17 +179,36 @@ class Account extends Component { } onClick={ this.store.toggleFundDialog } />, - <Button - icon={ <VerifyIcon /> } - key='sms-verification' - label={ - <FormattedMessage - id='account.button.verify' - defaultMessage='verify' + isVerifiable + ? ( + <Button + icon={ <VerifyIcon /> } + key='verification' + label={ + <FormattedMessage + id='account.button.verify' + defaultMessage='verify' + /> + } + onClick={ this.store.toggleVerificationDialog } /> - } - onClick={ this.store.toggleVerificationDialog } - />, + ) + : null, + isFaucettable + ? ( + <Button + icon={ <DialIcon /> } + key='faucet' + label={ + <FormattedMessage + id='account.button.faucet' + defaultMessage='Kovan ETH' + /> + } + onClick={ this.store.toggleFaucetDialog } + /> + ) + : null, <Button icon={ <EditIcon /> } key='editmeta' @@ -160,17 +220,19 @@ class Account extends Component { } onClick={ this.store.toggleEditDialog } />, - <Button - icon={ <LockedIcon /> } - key='passwordManager' - label={ - <FormattedMessage - id='account.button.password' - defaultMessage='password' - /> - } - onClick={ this.store.togglePasswordDialog } - />, + !account.hardware && ( + <Button + icon={ <LockedIcon /> } + key='passwordManager' + label={ + <FormattedMessage + id='account.button.password' + defaultMessage='password' + /> + } + onClick={ this.store.togglePasswordDialog } + /> + ), <Button icon={ <DeleteIcon /> } key='delete' @@ -202,6 +264,23 @@ class Account extends Component { return null; } + if (account.hardware) { + return ( + <DeleteAddress + account={ account } + confirmMessage={ + <FormattedMessage + id='account.hardware.confirmDelete' + defaultMessage='Are you sure you want to remove the following hardware address from your account list?' + /> + } + visible + route='/accounts' + onClose={ this.store.toggleDeleteDialog } + /> + ); + } + return ( <DeleteAccount account={ account } @@ -223,6 +302,24 @@ class Account extends Component { ); } + renderFaucetDialog () { + const { netVersion } = this.props; + + if (!this.store.isFaucetVisible) { + return null; + } + + const { address } = this.props.params; + + return ( + <Faucet + address={ address } + netVersion={ netVersion } + onClose={ this.store.toggleFaucetDialog } + /> + ); + } + renderFundDialog () { if (!this.store.isFundVisible) { return null; @@ -287,10 +384,14 @@ class Account extends Component { function mapStateToProps (state) { const { accounts } = state.personal; const { balances } = state.balances; + const certifications = state.certifications; + const { netVersion } = state.nodeStatus; return { accounts, - balances + balances, + certifications, + netVersion }; } diff --git a/js/src/views/Account/account.spec.js b/js/src/views/Account/account.spec.js index 9fb321cc1..6a96d6300 100644 --- a/js/src/views/Account/account.spec.js +++ b/js/src/views/Account/account.spec.js @@ -17,7 +17,7 @@ import { shallow } from 'enzyme'; import React from 'react'; -import { ADDRESS, createRedux } from './account.test.js'; +import { ACCOUNTS, ADDRESS, createRedux } from './account.test.js'; import Account from './'; @@ -28,10 +28,15 @@ let store; function render (props) { component = shallow( <Account + accounts={ ACCOUNTS } params={ { address: ADDRESS } } { ...props } />, - { context: { store: createRedux() } } + { + context: { + store: createRedux() + } + } ).find('Account').shallow(); instance = component.instance(); store = instance.store; @@ -75,57 +80,16 @@ describe('views/Account', () => { describe('sub-renderers', () => { describe('renderActionBar', () => { let bar; - let barShallow; beforeEach(() => { render(); bar = instance.renderActionbar({ tokens: {} }); - barShallow = shallow(bar); }); it('renders the bar', () => { expect(bar.type).to.match(/Actionbar/); }); - - // TODO: Finding by index is not optimal, however couldn't find a better method atm - // since we cannot find by key (prop not visible in shallow debug()) - describe('clicks', () => { - it('toggles transfer on click', () => { - barShallow.find('Button').at(0).simulate('click'); - expect(store.isTransferVisible).to.be.true; - }); - - it('toggles fund on click', () => { - barShallow.find('Button').at(1).simulate('click'); - expect(store.isFundVisible).to.be.true; - }); - - it('toggles fund on click', () => { - barShallow.find('Button').at(1).simulate('click'); - expect(store.isFundVisible).to.be.true; - }); - - it('toggles verify on click', () => { - barShallow.find('Button').at(2).simulate('click'); - expect(store.isVerificationVisible).to.be.true; - }); - - it('toggles edit on click', () => { - barShallow.find('Button').at(3).simulate('click'); - expect(store.isEditVisible).to.be.true; - }); - - it('toggles password on click', () => { - barShallow.find('Button').at(4).simulate('click'); - expect(store.isPasswordVisible).to.be.true; - }); - - it('toggles delete on click', () => { - barShallow.find('Button').at(5).simulate('click'); - expect(store.isDeleteVisible).to.be.true; - }); - }); }); describe('renderDeleteDialog', () => { @@ -133,14 +97,14 @@ describe('views/Account', () => { render(); expect(store.isDeleteVisible).to.be.false; - expect(instance.renderDeleteDialog()).to.be.null; + expect(instance.renderDeleteDialog(ACCOUNTS[ADDRESS])).to.be.null; }); it('renders the modal when visible', () => { render(); store.toggleDeleteDialog(); - expect(instance.renderDeleteDialog().type).to.match(/Connect/); + expect(instance.renderDeleteDialog(ACCOUNTS[ADDRESS]).type).to.match(/Connect/); }); }); @@ -149,14 +113,14 @@ describe('views/Account', () => { render(); expect(store.isEditVisible).to.be.false; - expect(instance.renderEditDialog()).to.be.null; + expect(instance.renderEditDialog(ACCOUNTS[ADDRESS])).to.be.null; }); it('renders the modal when visible', () => { render(); store.toggleEditDialog(); - expect(instance.renderEditDialog({ address: ADDRESS }).type).to.match(/Connect/); + expect(instance.renderEditDialog(ACCOUNTS[ADDRESS]).type).to.match(/Connect/); }); }); diff --git a/js/src/views/Account/account.test.js b/js/src/views/Account/account.test.js index 07095f210..c88e0e156 100644 --- a/js/src/views/Account/account.test.js +++ b/js/src/views/Account/account.test.js @@ -17,6 +17,11 @@ import sinon from 'sinon'; const ADDRESS = '0x0123456789012345678901234567890123456789'; +const ACCOUNTS = { + [ADDRESS]: { + address: ADDRESS + } +}; function createRedux () { return { @@ -31,7 +36,7 @@ function createRedux () { }, images: {}, nodeStatus: { - isTest: false, + netVersion: '1', traceMode: false }, personal: { @@ -47,6 +52,7 @@ function createRedux () { } export { + ACCOUNTS, ADDRESS, createRedux }; diff --git a/js/src/views/Account/store.js b/js/src/views/Account/store.js index 5b8fe58a0..45a9a7a8c 100644 --- a/js/src/views/Account/store.js +++ b/js/src/views/Account/store.js @@ -19,6 +19,7 @@ import { action, observable } from 'mobx'; export default class Store { @observable isDeleteVisible = false; @observable isEditVisible = false; + @observable isFaucetVisible = false; @observable isFundVisible = false; @observable isPasswordVisible = false; @observable isTransferVisible = false; @@ -32,6 +33,10 @@ export default class Store { this.isEditVisible = !this.isEditVisible; } + @action toggleFaucetDialog = () => { + this.isFaucetVisible = !this.isFaucetVisible; + } + @action toggleFundDialog = () => { this.isFundVisible = !this.isFundVisible; } diff --git a/js/src/views/Account/store.spec.js b/js/src/views/Account/store.spec.js index 408266254..9608b55a3 100644 --- a/js/src/views/Account/store.spec.js +++ b/js/src/views/Account/store.spec.js @@ -31,6 +31,7 @@ describe('views/Account/Store', () => { it('sets all modal visibility to false', () => { expect(store.isDeleteVisible).to.be.false; expect(store.isEditVisible).to.be.false; + expect(store.isFaucetVisible).to.be.false; expect(store.isFundVisible).to.be.false; expect(store.isPasswordVisible).to.be.false; expect(store.isTransferVisible).to.be.false; @@ -53,6 +54,13 @@ describe('views/Account/Store', () => { }); }); + describe('toggleFaucetDialog', () => { + it('toggles the visibility', () => { + store.toggleFaucetDialog(); + expect(store.isFaucetVisible).to.be.true; + }); + }); + describe('toggleFundDialog', () => { it('toggles the visibility', () => { store.toggleFundDialog(); diff --git a/js/src/views/Accounts/List/list.js b/js/src/views/Accounts/List/list.js index 89d51ac5b..edd90db7b 100644 --- a/js/src/views/Accounts/List/list.js +++ b/js/src/views/Accounts/List/list.js @@ -29,6 +29,7 @@ class List extends Component { accounts: PropTypes.object, balances: PropTypes.object, certifications: PropTypes.object.isRequired, + disabled: PropTypes.object, empty: PropTypes.bool, link: PropTypes.string, order: PropTypes.string, @@ -50,7 +51,7 @@ class List extends Component { } render () { - const { accounts, balances, empty } = this.props; + const { accounts, balances, disabled, empty } = this.props; if (empty) { return ( @@ -64,14 +65,16 @@ class List extends Component { const addresses = this .getAddresses() - .map((address, idx) => { + .map((address) => { const account = accounts[address] || {}; const balance = balances[address] || {}; + const isDisabled = disabled ? disabled[address] : false; const owners = account.owners || null; return { account, balance, + isDisabled, owners }; }); @@ -85,13 +88,14 @@ class List extends Component { } renderSummary = (item) => { - const { account, balance, owners } = item; + const { account, balance, isDisabled, owners } = item; const { handleAddSearchToken, link } = this.props; return ( <Summary account={ account } balance={ balance } + disabled={ isDisabled } handleAddSearchToken={ handleAddSearchToken } link={ link } owners={ owners } diff --git a/js/src/views/Accounts/Summary/summary.js b/js/src/views/Accounts/Summary/summary.js index 6850e1035..601f9d8c2 100644 --- a/js/src/views/Accounts/Summary/summary.js +++ b/js/src/views/Accounts/Summary/summary.js @@ -22,7 +22,7 @@ import { isEqual } from 'lodash'; import ReactTooltip from 'react-tooltip'; import { FormattedMessage } from 'react-intl'; -import { Balance, Container, ContainerTitle, IdentityIcon, IdentityName, Tags, Input } from '~/ui'; +import { Balance, Container, ContainerTitle, CopyToClipboard, IdentityIcon, IdentityName, Tags, VaultTag } from '~/ui'; import Certifications from '~/ui/Certifications'; import { arrayOrObjectProptype, nullableProptype } from '~/util/proptypes'; @@ -37,6 +37,7 @@ class Summary extends Component { account: PropTypes.object.isRequired, accountsInfo: PropTypes.object.isRequired, balance: PropTypes.object, + disabled: PropTypes.bool, link: PropTypes.string, name: PropTypes.string, noLink: PropTypes.bool, @@ -52,15 +53,21 @@ class Summary extends Component { shouldComponentUpdate (nextProps) { const prev = { - link: this.props.link, name: this.props.name, + link: this.props.link, + disabled: this.props.disabled, + name: this.props.name, noLink: this.props.noLink, - meta: this.props.account.meta, address: this.props.account.address + meta: this.props.account.meta, + address: this.props.account.address }; const next = { - link: nextProps.link, name: nextProps.name, + link: nextProps.link, + disabled: nextProps.disabled, + name: nextProps.name, noLink: nextProps.noLink, - meta: nextProps.account.meta, address: nextProps.account.address + meta: nextProps.account.meta, + address: nextProps.account.address }; if (!isEqual(next, prev)) { @@ -92,7 +99,7 @@ class Summary extends Component { } render () { - const { account, handleAddSearchToken, noLink } = this.props; + const { account, disabled, handleAddSearchToken, noLink } = this.props; const { tags } = account.meta; if (!account) { @@ -101,15 +108,6 @@ class Summary extends Component { const { address } = account; - const addressComponent = ( - <Input - readOnly - hideUnderline - value={ address } - allowCopy={ address } - /> - ); - return ( <Container className={ styles.account } @@ -118,7 +116,7 @@ class Summary extends Component { { this.renderBalance(false) } { this.renderDescription(account.meta) } { this.renderOwners() } - { this.renderCertifications() } + { this.renderVault(account.meta) } </div> } link={ this.getLink() } @@ -131,9 +129,15 @@ class Summary extends Component { <div className={ styles.heading }> <IdentityIcon address={ address } + disabled={ disabled } /> <ContainerTitle - byline={ addressComponent } + byline={ + <div className={ styles.addressline }> + <CopyToClipboard data={ address } /> + <div className={ styles.address }>{ address }</div> + </div> + } className={ noLink ? styles.main @@ -148,7 +152,10 @@ class Summary extends Component { } /> </div> - { this.renderBalance(true) } + <div className={ styles.summary }> + { this.renderBalance(true) } + </div> + { this.renderCertifications(true) } </Container> ); } @@ -215,7 +222,13 @@ class Summary extends Component { /> </div> <ReactTooltip id={ `owner_${owner.address}` }> - <strong>{ owner.name } </strong><small> (owner)</small> + <FormattedMessage + id='accounts.tooltips.owner' + defaultMessage='{name} (owner)' + values={ { + name: owner.name + } } + /> </ReactTooltip> </Link> ); @@ -255,7 +268,7 @@ class Summary extends Component { ); } - renderCertifications () { + renderCertifications (onlyIcon) { const { showCertifications, account } = this.props; if (!showCertifications) { @@ -265,10 +278,25 @@ class Summary extends Component { return ( <Certifications address={ account.address } - className={ styles.Certifications } + className={ + onlyIcon + ? styles.iconCertifications + : styles.fullCertifications + } + showOnlyIcon={ onlyIcon } /> ); } + + renderVault (meta) { + if (!meta || !meta.vault) { + return null; + } + + return ( + <VaultTag vault={ meta.vault } /> + ); + } } function mapStateToProps (state) { diff --git a/js/src/views/Accounts/accounts.css b/js/src/views/Accounts/accounts.css index 9a3d747ce..de7240cb9 100644 --- a/js/src/views/Accounts/accounts.css +++ b/js/src/views/Accounts/accounts.css @@ -23,13 +23,46 @@ .account { position: relative; + .addressline { + display: flex; + white-space: nowrap; + + .address { + display: inline-block; + flex: 1; + margin-left: 0.5em; + overflow: hidden; + text-overflow: ellipsis; + } + } + .blockDescription { color: rgba(255, 255, 255, 0.25); margin-top: 1.5em; } - .ethBalances { + .iconCertifications { + top: 72px; opacity: 1; + position: absolute; + left: 88px; + + img { + height: 1em !important; + width: 1em !important; + } + } + + .summary { + position: relative; + + .ethBalances { + opacity: 1; + } + } + + .overlay { + margin-top: -3.25em; } .owners { @@ -44,10 +77,6 @@ } } - .overlay { - margin-top: -3.25em; - } - &:not(:hover) { .tags { display: none; @@ -95,10 +124,12 @@ .heading { display: flex; flex-direction: row; + overflow: hidden; .main, .mainLink { flex: 1; + overflow: hidden; } .mainLink h3 { diff --git a/js/src/views/Accounts/accounts.js b/js/src/views/Accounts/accounts.js index 9dd6bb156..7067e8cb1 100644 --- a/js/src/views/Accounts/accounts.js +++ b/js/src/views/Accounts/accounts.js @@ -14,34 +14,42 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see <http://www.gnu.org/licenses/>. -import { uniq, isEqual, pickBy, omitBy } from 'lodash'; +import { observe } from 'mobx'; +import { observer } from 'mobx-react'; +import { uniq, isEqual, pickBy } from 'lodash'; import React, { Component, PropTypes } from 'react'; import { FormattedMessage } from 'react-intl'; import { connect } from 'react-redux'; import { Link } from 'react-router'; import { bindActionCreators } from 'redux'; -import List from './List'; +import HardwareStore from '~/mobx/hardwareStore'; import { CreateAccount, CreateWallet } from '~/modals'; import { Actionbar, ActionbarExport, ActionbarSearch, ActionbarSort, Button, Page, Tooltip } from '~/ui'; import { AddIcon, KeyIcon } from '~/ui/Icons'; import { setVisibleAccounts } from '~/redux/providers/personalActions'; +import List from './List'; import styles from './accounts.css'; +@observer class Accounts extends Component { static contextTypes = { api: PropTypes.object } static propTypes = { - setVisibleAccounts: PropTypes.func.isRequired, accounts: PropTypes.object.isRequired, + accountsInfo: PropTypes.object.isRequired, + balances: PropTypes.object, hasAccounts: PropTypes.bool.isRequired, - balances: PropTypes.object + setVisibleAccounts: PropTypes.func.isRequired } + hwstore = HardwareStore.get(this.context.api); + state = { + _observeCancel: null, addressBook: false, newDialog: false, newWalletDialog: false, @@ -58,6 +66,10 @@ class Accounts extends Component { }, 100); this.setVisibleAccounts(); + + this.setState({ + _observeCancel: observe(this.hwstore, 'wallets', this.onHardwareChange, true) + }); } componentWillReceiveProps (nextProps) { @@ -71,13 +83,13 @@ class Accounts extends Component { componentWillUnmount () { this.props.setVisibleAccounts([]); + this.state._observeCancel(); } setVisibleAccounts (props = this.props) { const { accounts, setVisibleAccounts } = props; - const addresses = Object.keys(accounts); - setVisibleAccounts(addresses); + setVisibleAccounts(Object.keys(accounts)); } render () { @@ -98,6 +110,7 @@ class Accounts extends Component { } /> + { this.renderHwWallets() } { this.renderWallets() } { this.renderAccounts() } </Page> @@ -121,8 +134,7 @@ class Accounts extends Component { renderAccounts () { const { accounts, balances } = this.props; - - const _accounts = omitBy(accounts, (a) => a.wallet); + const _accounts = pickBy(accounts, (account) => account.uuid); const _hasAccounts = Object.keys(_accounts).length > 0; if (!this.state.show) { @@ -145,27 +157,60 @@ class Accounts extends Component { renderWallets () { const { accounts, balances } = this.props; - - const wallets = pickBy(accounts, (a) => a.wallet); + const wallets = pickBy(accounts, (account) => account.wallet); const hasWallets = Object.keys(wallets).length > 0; + if (!hasWallets) { + return null; + } + if (!this.state.show) { return this.renderLoading(wallets); } const { searchValues, sortOrder } = this.state; - if (!wallets || Object.keys(wallets).length === 0) { - return null; - } - return ( <List link='wallet' search={ searchValues } accounts={ wallets } balances={ balances } - empty={ !hasWallets } + order={ sortOrder } + handleAddSearchToken={ this.onAddSearchToken } + /> + ); + } + + renderHwWallets () { + const { accounts, balances } = this.props; + const { wallets } = this.hwstore; + const hardware = pickBy(accounts, (account) => account.hardware); + const hasHardware = Object.keys(hardware).length > 0; + + if (!hasHardware) { + return null; + } + + if (!this.state.show) { + return this.renderLoading(hardware); + } + + const { searchValues, sortOrder } = this.state; + const disabled = Object + .keys(hardware) + .filter((address) => !wallets[address]) + .reduce((result, address) => { + result[address] = true; + return result; + }, {}); + + return ( + <List + search={ searchValues } + accounts={ hardware } + balances={ balances } + disabled={ disabled } order={ sortOrder } handleAddSearchToken={ this.onAddSearchToken } /> @@ -342,16 +387,29 @@ class Accounts extends Component { onNewAccountUpdate = () => { } + + onHardwareChange = () => { + const { accountsInfo } = this.props; + const { wallets } = this.hwstore; + + Object + .keys(wallets) + .filter((address) => !accountsInfo[address]) + .forEach((address) => this.hwstore.createAccountInfo(wallets[address])); + + this.setVisibleAccounts(); + } } function mapStateToProps (state) { - const { accounts, hasAccounts } = state.personal; + const { accounts, accountsInfo, hasAccounts } = state.personal; const { balances } = state.balances; return { - accounts: accounts, - hasAccounts: hasAccounts, - balances + accounts, + accountsInfo, + balances, + hasAccounts }; } diff --git a/js/src/views/Address/Delete/delete.js b/js/src/views/Address/Delete/delete.js index 36d0ea557..cf0380f2f 100644 --- a/js/src/views/Address/Delete/delete.js +++ b/js/src/views/Address/Delete/delete.js @@ -15,6 +15,7 @@ // along with Parity. If not, see <http://www.gnu.org/licenses/>. import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; @@ -34,13 +35,14 @@ class Delete extends Component { address: PropTypes.string, account: PropTypes.object, + confirmMessage: PropTypes.node, visible: PropTypes.bool, onClose: PropTypes.func, newError: PropTypes.func }; render () { - const { account, visible } = this.props; + const { account, confirmMessage, visible } = this.props; if (!visible) { return null; @@ -49,13 +51,25 @@ class Delete extends Component { return ( <ConfirmDialog className={ styles.delete } - title='confirm removal' + title={ + <FormattedMessage + id='address.delete.title' + defaultMessage='confirm removal' + /> + } visible onDeny={ this.closeDeleteDialog } onConfirm={ this.onDeleteConfirmed } > <div className={ styles.hero }> - Are you sure you want to remove the following address from your addressbook? + { + confirmMessage || ( + <FormattedMessage + id='address.delete.confirmInfo' + defaultMessage='Are you sure you want to remove the following address from your addressbook?' + /> + ) + } </div> <div className={ styles.info }> <IdentityIcon @@ -64,7 +78,10 @@ class Delete extends Component { /> <div className={ styles.nameinfo }> <div className={ styles.header }> - <IdentityName address={ account.address } unknown /> + <IdentityName + address={ account.address } + unknown + /> </div> <div className={ styles.address }> { account.address } @@ -100,15 +117,11 @@ class Delete extends Component { } } -function mapStateToProps (state) { - return {}; -} - function mapDispatchToProps (dispatch) { return bindActionCreators({ newError }, dispatch); } export default connect( - mapStateToProps, + null, mapDispatchToProps )(Delete); diff --git a/js/src/views/Address/address.js b/js/src/views/Address/address.js index 3a4f6d5f9..eb0a9bc4b 100644 --- a/js/src/views/Address/address.js +++ b/js/src/views/Address/address.js @@ -15,14 +15,13 @@ // along with Parity. If not, see <http://www.gnu.org/licenses/>. import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; -import ActionDelete from 'material-ui/svg-icons/action/delete'; -import ContentCreate from 'material-ui/svg-icons/content/create'; -import ContentAdd from 'material-ui/svg-icons/content/add'; import { EditMeta, AddAddress } from '~/modals'; import { Actionbar, Button, Page } from '~/ui'; +import { AddIcon, DeleteIcon, EditIcon } from '~/ui/Icons'; import Header from '../Account/Header'; import Transactions from '../Account/Transactions'; @@ -146,14 +145,24 @@ class Address extends Component { const buttons = [ <Button key='editmeta' - icon={ <ContentCreate /> } - label='edit' + icon={ <EditIcon /> } + label={ + <FormattedMessage + id='address.buttons.edit' + defaultMessage='edit' + /> + } onClick={ this.onEditClick } />, <Button key='delete' - icon={ <ActionDelete /> } - label='forget' + icon={ <DeleteIcon /> } + label={ + <FormattedMessage + id='address.buttons.forget' + defaultMessage='forget' + /> + } onClick={ this.showDeleteDialog } /> ]; @@ -161,16 +170,30 @@ class Address extends Component { const addToBook = ( <Button key='newAddress' - icon={ <ContentAdd /> } - label='save' + icon={ <AddIcon /> } + label={ + <FormattedMessage + id='address.buttons.save' + defaultMessage='save' + /> + } onClick={ this.onOpenAdd } /> ); return ( <Actionbar - title='Address Information' - buttons={ !contact ? [ addToBook ] : buttons } + title={ + <FormattedMessage + id='address.title' + defaultMessage='Address Information' + /> + } + buttons={ + !contact + ? [ addToBook ] + : buttons + } /> ); } diff --git a/js/src/views/Addresses/addresses.js b/js/src/views/Addresses/addresses.js index 4e40a7309..f61bcb3bb 100644 --- a/js/src/views/Addresses/addresses.js +++ b/js/src/views/Addresses/addresses.js @@ -15,6 +15,7 @@ // along with Parity. If not, see <http://www.gnu.org/licenses/>. import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import ContentAdd from 'material-ui/svg-icons/content/add'; @@ -143,7 +144,12 @@ class Addresses extends Component { <Button key='newAddress' icon={ <ContentAdd /> } - label='address' + label={ + <FormattedMessage + id='addresses.buttons.add' + defaultMessage='address' + /> + } onClick={ this.onOpenAdd } />, <ActionbarExport @@ -163,7 +169,12 @@ class Addresses extends Component { return ( <Actionbar className={ styles.toolbar } - title='Saved Addresses' + title={ + <FormattedMessage + id='addresses.title' + defaultMessage='Saved Addresses' + /> + } buttons={ buttons } /> ); @@ -187,7 +198,12 @@ class Addresses extends Component { renderValidation = (content) => { const error = { - error: 'The provided file is invalid...' + error: ( + <FormattedMessage + id='addresses.errors.invalidFile' + defaultMessage='The provided file is invalid...' + /> + ) }; try { @@ -214,7 +230,9 @@ class Addresses extends Component { { body } </div> ); - } catch (e) { return error; } + } catch (e) { + return error; + } } onImport = (content) => { diff --git a/js/src/views/Application/FrameError/frameError.js b/js/src/views/Application/FrameError/frameError.js index c1d022457..0cf1f4ef2 100644 --- a/js/src/views/Application/FrameError/frameError.js +++ b/js/src/views/Application/FrameError/frameError.js @@ -15,6 +15,7 @@ // along with Parity. If not, see <http://www.gnu.org/licenses/>. import React, { Component } from 'react'; +import { FormattedMessage } from 'react-intl'; import styles from './frameError.css'; @@ -22,7 +23,10 @@ export default class FrameError extends Component { render () { return ( <div className={ styles.error }> - ERROR: This application cannot and should not be loaded in an embedded iFrame + <FormattedMessage + id='application.frame.error' + defaultMessage='ERROR: This application cannot and should not be loaded in an embedded iFrame' + /> </div> ); } diff --git a/js/src/views/Application/TabBar/tabBar.js b/js/src/views/Application/TabBar/tabBar.js index d9aa07089..d136029e9 100644 --- a/js/src/views/Application/TabBar/tabBar.js +++ b/js/src/views/Application/TabBar/tabBar.js @@ -32,8 +32,6 @@ class TabBar extends Component { }; static propTypes = { - isTest: PropTypes.bool, - netChain: PropTypes.string, pending: PropTypes.array, views: PropTypes.array.isRequired }; diff --git a/js/src/views/Application/application.js b/js/src/views/Application/application.js index 377dcecbc..da8a1235b 100644 --- a/js/src/views/Application/application.js +++ b/js/src/views/Application/application.js @@ -46,8 +46,6 @@ class Application extends Component { static propTypes = { blockNumber: PropTypes.object, children: PropTypes.node, - isTest: PropTypes.bool, - netChain: PropTypes.string, pending: PropTypes.array } @@ -86,7 +84,7 @@ class Application extends Component { } renderApp () { - const { blockNumber, children, pending, netChain, isTest } = this.props; + const { blockNumber, children, pending } = this.props; return ( <Container @@ -94,11 +92,7 @@ class Application extends Component { onCloseFirstRun={ this.store.closeFirstrun } showFirstRun={ this.store.firstrunVisible } > - <TabBar - netChain={ netChain } - isTest={ isTest } - pending={ pending } - /> + <TabBar pending={ pending } /> <div className={ styles.content }> { children } </div> @@ -125,15 +119,13 @@ class Application extends Component { } function mapStateToProps (state) { - const { blockNumber, netChain, isTest } = state.nodeStatus; + const { blockNumber } = state.nodeStatus; const { hasAccounts } = state.personal; const { pending } = state.signer; return { blockNumber, hasAccounts, - isTest, - netChain, pending }; } diff --git a/js/src/views/Connection/connection.js b/js/src/views/Connection/connection.js index 710304a05..788385b89 100644 --- a/js/src/views/Connection/connection.js +++ b/js/src/views/Connection/connection.js @@ -88,7 +88,7 @@ class Connection extends Component { <div> <FormattedMessage id='connection.noConnection' - defaultMessage='Unable to make a connection to the Parity Secure API. To update your secure token or to generate a new one, run {newToken} and supply the token below' + defaultMessage='Unable to make a connection to the Parity Secure API. To update your secure token or to generate a new one, run {newToken} and paste the generated token into the space below.' values={ { newToken: <span className={ styles.console }>parity signer new-token</span> } } diff --git a/js/src/views/Contract/Events/Event/event.js b/js/src/views/Contract/Events/Event/event.js index de5cf6809..64e4cdd49 100644 --- a/js/src/views/Contract/Events/Event/event.js +++ b/js/src/views/Contract/Events/Event/event.js @@ -17,6 +17,7 @@ import BigNumber from 'bignumber.js'; import moment from 'moment'; import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; import { IdentityIcon, IdentityName, Input, InputAddress } from '~/ui'; import ShortenedHash from '~/ui/ShortenedHash'; @@ -31,7 +32,7 @@ export default class Event extends Component { static propTypes = { event: PropTypes.object.isRequired, - isTest: PropTypes.bool + netVersion: PropTypes.string.isRequired } state = { @@ -43,11 +44,11 @@ export default class Event extends Component { } render () { - const { event, isTest } = this.props; + const { event, netVersion } = this.props; const { block, transaction } = this.state; const classes = `${styles.event} ${styles[event.state]}`; - const url = txLink(event.transactionHash, isTest); + const url = txLink(event.transactionHash, false, netVersion); const keys = Object.keys(event.params).join(', '); const values = Object.keys(event.params).map((name, index) => { const param = event.params[name]; @@ -64,7 +65,12 @@ export default class Event extends Component { <td className={ styles.timestamp }> <div>{ event.state === 'pending' - ? 'pending' + ? ( + <FormattedMessage + id='contract.events.eventPending' + defaultMessage='pending' + /> + ) : this.formatBlockTimestamp(block) }</div> <div>{ this.formatNumber(transaction.blockNumber) }</div> @@ -96,7 +102,11 @@ export default class Event extends Component { center inline /> - { withName ? <IdentityName address={ address } /> : address } + { + withName + ? <IdentityName address={ address } /> + : address + } </span> ); } diff --git a/js/src/views/Contract/Events/events.js b/js/src/views/Contract/Events/events.js index 37fbd0592..a709c137a 100644 --- a/js/src/views/Contract/Events/events.js +++ b/js/src/views/Contract/Events/events.js @@ -15,6 +15,7 @@ // along with Parity. If not, see <http://www.gnu.org/licenses/>. import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; import { uniq } from 'lodash'; import { Container, Loading } from '~/ui'; @@ -22,15 +23,22 @@ import { Container, Loading } from '~/ui'; import Event from './Event'; import styles from '../contract.css'; +const TITLE = ( + <FormattedMessage + id='contract.events.title' + defaultMessage='events' + /> +); + export default class Events extends Component { static contextTypes = { api: PropTypes.object }; static propTypes = { - isTest: PropTypes.bool.isRequired, isLoading: PropTypes.bool, - events: PropTypes.array + events: PropTypes.array, + netVersion: PropTypes.string.isRequired }; static defaultProps = { @@ -39,11 +47,11 @@ export default class Events extends Component { }; render () { - const { events, isTest, isLoading } = this.props; + const { events, isLoading, netVersion } = this.props; if (isLoading) { return ( - <Container title='events'> + <Container title={ TITLE }> <div> <Loading size={ 2 } /> </div> @@ -53,8 +61,13 @@ export default class Events extends Component { if (!events || !events.length) { return ( - <Container title='events'> - <p>No events has been sent from this contract.</p> + <Container title={ TITLE }> + <p> + <FormattedMessage + id='contract.events.noEvents' + defaultMessage='No events has been sent from this contract.' + /> + </p> </Container> ); } @@ -67,13 +80,13 @@ export default class Events extends Component { <Event key={ event.key } event={ event } - isTest={ isTest } + netVersion={ netVersion } /> ); }); return ( - <Container title='events'> + <Container title={ TITLE }> <table className={ styles.events }> <thead> <tr> @@ -83,7 +96,9 @@ export default class Events extends Component { </th> </tr> </thead> - <tbody>{ list }</tbody> + <tbody> + { list } + </tbody> </table> </Container> ); diff --git a/js/src/views/Contract/Queries/inputQuery.js b/js/src/views/Contract/Queries/inputQuery.js index 1418aa6b7..8d4bddf02 100644 --- a/js/src/views/Contract/Queries/inputQuery.js +++ b/js/src/views/Contract/Queries/inputQuery.js @@ -14,7 +14,9 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see <http://www.gnu.org/licenses/>. +import { isEqual } from 'lodash'; import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; import LinearProgress from 'material-ui/LinearProgress'; import { Card, CardActions, CardTitle, CardText } from 'material-ui/Card'; import { connect } from 'react-redux'; @@ -23,6 +25,7 @@ import { bindActionCreators } from 'redux'; import { newError } from '~/redux/actions'; import { Button, TypedInput } from '~/ui'; import { arrayOrObjectProptype } from '~/util/proptypes'; +import { parseAbiType } from '~/util/abi'; import styles from './queries.css'; @@ -43,11 +46,35 @@ class InputQuery extends Component { }; state = { + inputs: [], isValid: true, results: [], values: {} }; + componentWillMount () { + this.parseInputs(); + } + + componentWillReceiveProps (nextProps) { + const prevInputTypes = this.props.inputs.map((input) => input.type); + const nextInputTypes = nextProps.inputs.map((input) => input.type); + + if (!isEqual(prevInputTypes, nextInputTypes)) { + this.parseInputs(nextProps); + } + } + + parseInputs (props = this.props) { + const inputs = props.inputs.map((input) => ({ ...input, parsed: parseAbiType(input.type) })); + const values = inputs.reduce((values, input, index) => { + values[index] = input.parsed.default; + return values; + }, {}); + + this.setState({ inputs, values }); + } + render () { const { name, className } = this.props; @@ -63,10 +90,9 @@ class InputQuery extends Component { } renderContent () { - const { inputs } = this.props; + const { inputs } = this.state; const { isValid } = this.state; - const inputsFields = inputs .map((input, index) => this.renderInput(input, index)); @@ -80,7 +106,12 @@ class InputQuery extends Component { </CardText> <CardActions> <Button - label='Query' + label={ + <FormattedMessage + id='contract.queries.buttons.query' + defaultMessage='Query' + /> + } disabled={ !isValid } onClick={ this.onClick } /> @@ -94,7 +125,9 @@ class InputQuery extends Component { const { accountsInfo, outputs } = this.props; if (isLoading) { - return (<LinearProgress mode='indeterminate' />); + return ( + <LinearProgress mode='indeterminate' /> + ); } if (!results || results.length < 1) { @@ -182,15 +215,15 @@ class InputQuery extends Component { } onClick = () => { - const { values } = this.state; - const { inputs, contract, name, outputs, signature } = this.props; + const { inputs, values } = this.state; + const { contract, name, outputs, signature } = this.props; this.setState({ isLoading: true, results: [] }); - const inputValues = inputs.map((input, index) => values[index] || ''); + const inputValues = inputs.map((input, index) => values[index]); contract .instance[signature] diff --git a/js/src/views/Contract/Queries/queries.js b/js/src/views/Contract/Queries/queries.js index dd84109fb..299d1ee84 100644 --- a/js/src/views/Contract/Queries/queries.js +++ b/js/src/views/Contract/Queries/queries.js @@ -15,6 +15,7 @@ // along with Parity. If not, see <http://www.gnu.org/licenses/>. import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; import { Card, CardTitle, CardText } from 'material-ui/Card'; import InputQuery from './inputQuery'; @@ -59,7 +60,14 @@ export default class Queries extends Component { } return ( - <Container title='queries'> + <Container + title={ + <FormattedMessage + id='contract.queries.title' + defaultMessage='queries' + /> + } + > <div className={ styles.methods }> { noInputQueries.length > 0 diff --git a/js/src/views/Contract/contract.js b/js/src/views/Contract/contract.js index aad2be8bd..801bd3f43 100644 --- a/js/src/views/Contract/contract.js +++ b/js/src/views/Contract/contract.js @@ -15,22 +15,16 @@ // along with Parity. If not, see <http://www.gnu.org/licenses/>. import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; -import { FormattedMessage } from 'react-intl'; import BigNumber from 'bignumber.js'; -import ActionDelete from 'material-ui/svg-icons/action/delete'; -import AvPlayArrow from 'material-ui/svg-icons/av/play-arrow'; -import ContentCreate from 'material-ui/svg-icons/content/create'; -import EyeIcon from 'material-ui/svg-icons/image/remove-red-eye'; -import ContentClear from 'material-ui/svg-icons/content/clear'; - +import { EditMeta, ExecuteContract } from '~/modals'; import { newError } from '~/redux/actions'; import { setVisibleAccounts } from '~/redux/providers/personalActions'; - -import { EditMeta, ExecuteContract } from '~/modals'; import { Actionbar, Button, Page, Portal } from '~/ui'; +import { CancelIcon, DeleteIcon, EditIcon, PlayIcon, VisibleIcon } from '~/ui/Icons'; import Editor from '~/ui/Editor'; import Header from '../Account/Header'; @@ -53,7 +47,7 @@ class Contract extends Component { accountsInfo: PropTypes.object, balances: PropTypes.object, contracts: PropTypes.object, - isTest: PropTypes.bool, + netVersion: PropTypes.string.isRequired, params: PropTypes.object }; @@ -121,7 +115,7 @@ class Contract extends Component { } render () { - const { accountsInfo, balances, contracts, params, isTest } = this.props; + const { accountsInfo, balances, contracts, netVersion, params } = this.props; const { allEvents, contract, queryValues, loadingEvents } = this.state; const account = contracts[params.address]; const balance = balances[params.address]; @@ -150,9 +144,9 @@ class Contract extends Component { values={ queryValues } /> <Events - isTest={ isTest } isLoading={ loadingEvents } events={ allEvents } + netVersion={ netVersion } /> { this.renderDetails(account) } </Page> @@ -191,8 +185,13 @@ class Contract extends Component { const cancelBtn = ( <Button - icon={ <ContentClear /> } - label='Close' + icon={ <CancelIcon /> } + label={ + <FormattedMessage + id='contract.buttons.close' + defaultMessage='Close' + /> + } onClick={ this.closeDetailsDialog } /> ); @@ -202,7 +201,12 @@ class Contract extends Component { buttons={ [ cancelBtn ] } onClose={ this.closeDetailsDialog } open - title={ 'contract details' } + title={ + <FormattedMessage + id='contract.details.title' + defaultMessage='contract details' + /> + } > <div className={ styles.details }> { this.renderSource(contract) } @@ -243,33 +247,58 @@ class Contract extends Component { const buttons = [ <Button key='execute' - icon={ <AvPlayArrow /> } - label='execute' + icon={ <PlayIcon /> } + label={ + <FormattedMessage + id='contract.buttons.execute' + defaultMessage='execute' + /> + } onClick={ this.showExecuteDialog } />, <Button key='editmeta' - icon={ <ContentCreate /> } - label='edit' + icon={ <EditIcon /> } + label={ + <FormattedMessage + id='contract.buttons.edit' + defaultMessage='edit' + /> + } onClick={ this.showEditDialog } />, <Button key='delete' - icon={ <ActionDelete /> } - label='forget' + icon={ <DeleteIcon /> } + label={ + <FormattedMessage + id='contract.buttons.forget' + defaultMessage='forget' + /> + } onClick={ this.showDeleteDialog } />, <Button key='viewDetails' - icon={ <EyeIcon /> } - label='details' + icon={ <VisibleIcon /> } + label={ + <FormattedMessage + id='contract.buttons.details' + defaultMessage='details' + /> + } onClick={ this.showDetailsDialog } /> ]; return ( <Actionbar - title='Contract Information' + title={ + <FormattedMessage + id='contract.title' + defaultMessage='Contract Information' + /> + } buttons={ !account ? [] : buttons } /> ); @@ -489,14 +518,14 @@ class Contract extends Component { function mapStateToProps (state) { const { accounts, accountsInfo, contracts } = state.personal; const { balances } = state.balances; - const { isTest } = state.nodeStatus; + const { netVersion } = state.nodeStatus; return { - isTest, accounts, accountsInfo, + balances, contracts, - balances + netVersion }; } diff --git a/js/src/views/Contracts/contracts.js b/js/src/views/Contracts/contracts.js index fba8fd7fc..938fc12b5 100644 --- a/js/src/views/Contracts/contracts.js +++ b/js/src/views/Contracts/contracts.js @@ -15,19 +15,40 @@ // along with Parity. If not, see <http://www.gnu.org/licenses/>. import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; import { Link } from 'react-router'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; -import ContentAdd from 'material-ui/svg-icons/content/add'; -import FileIcon from 'material-ui/svg-icons/action/description'; import { uniq, isEqual } from 'lodash'; -import { Actionbar, ActionbarSearch, ActionbarSort, Button, Page } from '~/ui'; import { AddContract, DeployContract } from '~/modals'; import { setVisibleAccounts } from '~/redux/providers/personalActions'; +import { Actionbar, ActionbarSearch, ActionbarSort, Button, Page } from '~/ui'; +import { AddIcon, DevelopIcon } from '~/ui/Icons'; import List from '../Accounts/List'; +const META_SORT = [ + { + key: 'timestamp', + label: ( + <FormattedMessage + id='contracts.sortOrder.date' + defaultMessage='date' + /> + ) + }, + { + key: 'blockNumber:-1', + label: ( + <FormattedMessage + id='contracts.sortOrder.minedBlock' + defaultMessage='mined block' + /> + ) + } +]; + class Contracts extends Component { static contextTypes = { api: PropTypes.object.isRequired @@ -109,10 +130,7 @@ class Contracts extends Component { key='sortAccounts' id='sortContracts' order={ this.state.sortOrder } - metas={ [ - { key: 'timestamp', label: 'date' }, - { key: 'blockNumber:-1', label: 'mined block' } - ] } + metas={ META_SORT } showDefault={ false } onChange={ onChange } /> @@ -137,14 +155,24 @@ class Contracts extends Component { const buttons = [ <Button key='addContract' - icon={ <ContentAdd /> } - label='watch' + icon={ <AddIcon /> } + label={ + <FormattedMessage + id='contracts.buttons.watch' + defaultMessage='watch' + /> + } onClick={ this.onAddContract } />, <Button key='deployContract' - icon={ <ContentAdd /> } - label='deploy' + icon={ <AddIcon /> } + label={ + <FormattedMessage + id='contracts.buttons.deploy' + defaultMessage='deploy' + /> + } onClick={ this.onDeployContract } />, <Link @@ -152,8 +180,13 @@ class Contracts extends Component { key='writeContract' > <Button - icon={ <FileIcon /> } - label='develop' + icon={ <DevelopIcon /> } + label={ + <FormattedMessage + id='contracts.buttons.develop' + defaultMessage='develop' + /> + } /> </Link>, @@ -163,7 +196,12 @@ class Contracts extends Component { return ( <Actionbar - title='Contracts' + title={ + <FormattedMessage + id='contracts.title' + defaultMessage='Contracts' + /> + } buttons={ buttons } /> ); diff --git a/js/src/views/Home/home.js b/js/src/views/Home/home.js index e1ebd37a5..8db45baf4 100644 --- a/js/src/views/Home/home.js +++ b/js/src/views/Home/home.js @@ -18,11 +18,11 @@ import { observer } from 'mobx-react'; import React, { Component, PropTypes } from 'react'; import { FormattedMessage } from 'react-intl'; +import HistoryStore from '~/mobx/historyStore'; import { Page } from '~/ui'; import DappsStore from '../Dapps/dappsStore'; import ExtensionStore from '../Application/Extension/store'; -import HistoryStore from '../historyStore'; import WebStore from '../Web/store'; import Accounts from './Accounts'; diff --git a/js/src/views/ParityBar/accountStore.js b/js/src/views/ParityBar/accountStore.js index f13881df0..0a35a27ba 100644 --- a/js/src/views/ParityBar/accountStore.js +++ b/js/src/views/ParityBar/accountStore.js @@ -81,7 +81,8 @@ export default class AccountStore { Object .keys(accounts) .filter((address) => { - const isAccount = accounts[address].uuid; + const account = accounts[address]; + const isAccount = account.uuid || (account.meta && account.meta.hardware); const isWhitelisted = !whitelist || whitelist.includes(address); return isAccount && isWhitelisted; diff --git a/js/src/views/ParityBar/parityBar.css b/js/src/views/ParityBar/parityBar.css index 265bf7894..11ebce073 100644 --- a/js/src/views/ParityBar/parityBar.css +++ b/js/src/views/ParityBar/parityBar.css @@ -42,6 +42,7 @@ $modalZ: 10001; .container { display: flex; flex-direction: column; + width: 100%; } .overlay { @@ -106,6 +107,7 @@ $modalZ: 10001; min-height: 30vh; max-height: 80vh; max-width: calc(100vw - 2em); + width: 960px; .content { flex: 1; diff --git a/js/src/views/Signer/components/Account/AccountLink/accountLink.js b/js/src/views/Signer/components/Account/AccountLink/accountLink.js index 81f25f4e1..afa8e98e5 100644 --- a/js/src/views/Signer/components/Account/AccountLink/accountLink.js +++ b/js/src/views/Signer/components/Account/AccountLink/accountLink.js @@ -26,8 +26,7 @@ class AccountLink extends Component { address: PropTypes.string.isRequired, className: PropTypes.string, children: PropTypes.node, - externalLink: PropTypes.string.isRequired, - isTest: PropTypes.bool.isRequired + externalLink: PropTypes.string.isRequired } state = { @@ -35,15 +34,15 @@ class AccountLink extends Component { }; componentWillMount () { - const { address, externalLink, isTest } = this.props; + const { address, externalLink } = this.props; - this.updateLink(address, externalLink, isTest); + this.updateLink(address, externalLink); } componentWillReceiveProps (nextProps) { - const { address, externalLink, isTest } = nextProps; + const { address, externalLink } = nextProps; - this.updateLink(address, externalLink, isTest); + this.updateLink(address, externalLink); } render () { @@ -71,7 +70,7 @@ class AccountLink extends Component { ); } - updateLink (address, externalLink, isTest) { + updateLink (address, externalLink) { const { accountAddresses } = this.props; const isAccount = accountAddresses.includes(address); diff --git a/js/src/views/Signer/components/Account/account.js b/js/src/views/Signer/components/Account/account.js index 1a7197d1a..5a675abf7 100644 --- a/js/src/views/Signer/components/Account/account.js +++ b/js/src/views/Signer/components/Account/account.js @@ -23,10 +23,11 @@ import styles from './account.css'; export default class Account extends Component { static propTypes = { - className: PropTypes.string, address: PropTypes.string.isRequired, + className: PropTypes.string, + disabled: PropTypes.bool, externalLink: PropTypes.string.isRequired, - isTest: PropTypes.bool.isRequired, + netVersion: PropTypes.string.isRequired, balance: PropTypes.object // eth BigNumber, not required since it mght take time to fetch }; @@ -52,17 +53,18 @@ export default class Account extends Component { } render () { - const { address, externalLink, isTest, className } = this.props; + const { address, className, disabled, externalLink, netVersion } = this.props; return ( <div className={ `${styles.acc} ${className}` }> <AccountLink address={ address } externalLink={ externalLink } - isTest={ isTest } + netVersion={ netVersion } > <IdentityIcon center + disabled={ disabled } address={ address } /> </AccountLink> @@ -81,7 +83,7 @@ export default class Account extends Component { } renderName () { - const { address, externalLink, isTest } = this.props; + const { address, externalLink, netVersion } = this.props; const name = <IdentityName address={ address } empty />; if (!name) { @@ -89,7 +91,7 @@ export default class Account extends Component { <AccountLink address={ address } externalLink={ externalLink } - isTest={ isTest } + netVersion={ netVersion } > [{ this.shortAddress(address) }] </AccountLink> @@ -100,7 +102,7 @@ export default class Account extends Component { <AccountLink address={ address } externalLink={ externalLink } - isTest={ isTest } + netVersion={ netVersion } > <span> <span className={ styles.name }>{ name }</span> diff --git a/js/src/views/Signer/components/RequestOrigin/requestOrigin.js b/js/src/views/Signer/components/RequestOrigin/requestOrigin.js index fb9ef4cd6..6582e9780 100644 --- a/js/src/views/Signer/components/RequestOrigin/requestOrigin.js +++ b/js/src/views/Signer/components/RequestOrigin/requestOrigin.js @@ -15,6 +15,7 @@ // along with Parity. If not, see <http://www.gnu.org/licenses/>. import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; import IdentityIcon from '~/ui/IdentityIcon'; @@ -45,16 +46,36 @@ export default class RequestOrigin extends Component { renderOrigin (origin) { if (origin.type === 'unknown') { return ( - <span className={ styles.unknown }>via unknown interface</span> + <span className={ styles.unknown }> + <FormattedMessage + id='signer.requestOrigin.unknownInterface' + defaultMessage='via unknown interface' + /> + </span> ); } if (origin.type === 'dapp') { return ( <span> - by a dapp at <span className={ styles.url }> - { origin.details || 'unknown URL' } - </span> + <FormattedMessage + id='signer.requestOrigin.dapp' + defaultMessage='by a dapp at {url}' + values={ { + url: ( + <span className={ styles.url }> + { + origin.details || ( + <FormattedMessage + id='signer.requestOrigin.unknownUrl' + defaultMessage='unknown URL' + /> + ) + } + </span> + ) + } } + /> </span> ); } @@ -62,9 +83,24 @@ export default class RequestOrigin extends Component { if (origin.type === 'rpc') { return ( <span> - via RPC <span className={ styles.url }> - ({ origin.details || 'unidentified' }) - </span> + <FormattedMessage + id='signer.requestOrigin.rpc' + defaultMessage='via RPC {rpc}' + values={ { + url: ( + <span className={ styles.url }> + ({ + origin.details || ( + <FormattedMessage + id='signer.requestOrigin.unknownRpc' + defaultMessage='unidentified' + /> + ) + }) + </span> + ) + } } + /> </span> ); } @@ -72,7 +108,10 @@ export default class RequestOrigin extends Component { if (origin.type === 'ipc') { return ( <span> - via IPC session + <FormattedMessage + id='signer.requestOrigin.ipc' + defaultMessage='via IPC session' + /> <span className={ styles.hash } title={ origin.details } @@ -94,13 +133,21 @@ export default class RequestOrigin extends Component { renderSigner (session) { if (session.substr(2) === this.context.api.transport.sessionHash) { return ( - <span title={ session }>via current tab</span> + <span title={ session }> + <FormattedMessage + id='signer.requestOrigin.signerCurrent' + defaultMessage='via current tab' + /> + </span> ); } return ( <span> - via UI session + <FormattedMessage + id='signer.requestOrigin.signerUI' + defaultMessage='via UI session' + /> <span className={ styles.hash } title={ `UI Session id: ${session}` } diff --git a/js/src/views/Signer/components/RequestOrigin/requestOrigin.spec.js b/js/src/views/Signer/components/RequestOrigin/requestOrigin.spec.js index f1de13db3..0faac6bd3 100644 --- a/js/src/views/Signer/components/RequestOrigin/requestOrigin.spec.js +++ b/js/src/views/Signer/components/RequestOrigin/requestOrigin.spec.js @@ -34,39 +34,39 @@ describe('views/Signer/components/RequestOrigin', () => { expect(shallow( <RequestOrigin origin={ { type: 'unknown', details: '' } } />, context - ).text()).to.equal('Requested via unknown interface'); + ).find('FormattedMessage').props().id).to.equal('signer.requestOrigin.unknownInterface'); }); it('renders dapps', () => { expect(shallow( <RequestOrigin origin={ { type: 'dapp', details: 'http://parity.io' } } />, context - ).text()).to.equal('Requested by a dapp at http://parity.io'); + ).find('FormattedMessage').props().id).to.equal('signer.requestOrigin.dapp'); }); it('renders rpc', () => { expect(shallow( <RequestOrigin origin={ { type: 'rpc', details: '' } } />, context - ).text()).to.equal('Requested via RPC (unidentified)'); + ).find('FormattedMessage').props().id).to.equal('signer.requestOrigin.rpc'); }); it('renders ipc', () => { expect(shallow( <RequestOrigin origin={ { type: 'ipc', details: '0x1234' } } />, context - ).text()).to.equal('Requested via IPC session<Connect(IdentityIcon) />'); + ).find('FormattedMessage').props().id).to.equal('signer.requestOrigin.ipc'); }); it('renders signer', () => { expect(shallow( <RequestOrigin origin={ { type: 'signer', details: '0x12345' } } />, context - ).text()).to.equal('Requested via UI session<Connect(IdentityIcon) />'); + ).find('FormattedMessage').props().id).to.equal('signer.requestOrigin.signerUI'); expect(shallow( <RequestOrigin origin={ { type: 'signer', details: '0x1234' } } />, context - ).text()).to.equal('Requested via current tab'); + ).find('FormattedMessage').props().id).to.equal('signer.requestOrigin.signerCurrent'); }); }); diff --git a/js/src/views/Signer/components/RequestPending/requestPending.js b/js/src/views/Signer/components/RequestPending/requestPending.js index 2d745d26f..671df6cbb 100644 --- a/js/src/views/Signer/components/RequestPending/requestPending.js +++ b/js/src/views/Signer/components/RequestPending/requestPending.js @@ -27,7 +27,7 @@ export default class RequestPending extends Component { gasLimit: PropTypes.object.isRequired, id: PropTypes.object.isRequired, isSending: PropTypes.bool.isRequired, - isTest: PropTypes.bool.isRequired, + netVersion: PropTypes.string.isRequired, onConfirm: PropTypes.func.isRequired, onReject: PropTypes.func.isRequired, origin: PropTypes.object.isRequired, @@ -36,7 +36,7 @@ export default class RequestPending extends Component { PropTypes.shape({ sign: PropTypes.object.isRequired }), PropTypes.shape({ signTransaction: PropTypes.object.isRequired }) ]).isRequired, - store: PropTypes.object.isRequired + signerstore: PropTypes.object.isRequired }; static defaultProps = { @@ -44,15 +44,8 @@ export default class RequestPending extends Component { isSending: false }; - onConfirm = data => { - const { onConfirm, payload } = this.props; - - data.payload = payload; - onConfirm(data); - }; - render () { - const { className, date, focus, gasLimit, id, isSending, isTest, onReject, payload, store, origin } = this.props; + const { className, date, focus, gasLimit, id, isSending, netVersion, onReject, payload, signerstore, origin } = this.props; if (payload.sign) { const { sign } = payload; @@ -66,11 +59,11 @@ export default class RequestPending extends Component { id={ id } isFinished={ false } isSending={ isSending } - isTest={ isTest } + netVersion={ netVersion } onConfirm={ this.onConfirm } onReject={ onReject } origin={ origin } - store={ store } + signerstore={ signerstore } /> ); } @@ -86,11 +79,11 @@ export default class RequestPending extends Component { gasLimit={ gasLimit } id={ id } isSending={ isSending } - isTest={ isTest } + netVersion={ netVersion } onConfirm={ this.onConfirm } onReject={ onReject } origin={ origin } - store={ store } + signerstore={ signerstore } transaction={ transaction } /> ); @@ -99,4 +92,11 @@ export default class RequestPending extends Component { console.error('RequestPending: Unknown payload', payload); return null; } + + onConfirm = (data) => { + const { onConfirm, payload } = this.props; + + data.payload = payload; + onConfirm(data); + }; } diff --git a/js/src/views/Signer/components/RequestPending/requestPending.spec.js b/js/src/views/Signer/components/RequestPending/requestPending.spec.js new file mode 100644 index 000000000..686c23b67 --- /dev/null +++ b/js/src/views/Signer/components/RequestPending/requestPending.spec.js @@ -0,0 +1,112 @@ +// Copyright 2015-2017 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 { shallow } from 'enzyme'; +import React from 'react'; +import sinon from 'sinon'; + +import RequestPending from './'; + +const ADDRESS = '0x1234567890123456789012345678901234567890'; +const TRANSACTION = { + from: ADDRESS, + gas: new BigNumber(21000), + gasPrice: new BigNumber(20000000), + value: new BigNumber(1) +}; +const PAYLOAD_SENDTX = { + sendTransaction: TRANSACTION +}; +const PAYLOAD_SIGN = { + sign: { + address: ADDRESS, + data: 'testing' + } +}; +const PAYLOAD_SIGNTX = { + signTransaction: TRANSACTION +}; + +let component; +let onConfirm; +let onReject; + +function render (payload) { + onConfirm = sinon.stub(); + onReject = sinon.stub(); + + component = shallow( + <RequestPending + date={ new Date() } + gasLimit={ new BigNumber(100000) } + id={ new BigNumber(123) } + isSending={ false } + netVersion='42' + onConfirm={ onConfirm } + onReject={ onReject } + origin={ {} } + payload={ payload } + store={ {} } + /> + ); + + return component; +} + +describe('views/Signer/RequestPending', () => { + describe('sendTransaction', () => { + beforeEach(() => { + render(PAYLOAD_SENDTX); + }); + + it('renders defaults', () => { + expect(component).to.be.ok; + }); + + it('renders TransactionPending component', () => { + expect(component.find('Connect(TransactionPending)')).to.have.length(1); + }); + }); + + describe('sign', () => { + beforeEach(() => { + render(PAYLOAD_SIGN); + }); + + it('renders defaults', () => { + expect(component).to.be.ok; + }); + + it('renders SignRequest component', () => { + expect(component.find('SignRequest')).to.have.length(1); + }); + }); + + describe('signTransaction', () => { + beforeEach(() => { + render(PAYLOAD_SIGNTX); + }); + + it('renders defaults', () => { + expect(component).to.be.ok; + }); + + it('renders TransactionPending component', () => { + expect(component.find('Connect(TransactionPending)')).to.have.length(1); + }); + }); +}); diff --git a/js/src/views/Signer/components/SignRequest/signRequest.js b/js/src/views/Signer/components/SignRequest/signRequest.js index c5b9cc976..373262d41 100644 --- a/js/src/views/Signer/components/SignRequest/signRequest.js +++ b/js/src/views/Signer/components/SignRequest/signRequest.js @@ -15,6 +15,7 @@ // along with Parity. If not, see <http://www.gnu.org/licenses/>. import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; import { observer } from 'mobx-react'; import Account from '../Account'; @@ -45,8 +46,8 @@ export default class SignRequest extends Component { data: PropTypes.string.isRequired, id: PropTypes.object.isRequired, isFinished: PropTypes.bool.isRequired, - isTest: PropTypes.bool.isRequired, - store: PropTypes.object.isRequired, + netVersion: PropTypes.string.isRequired, + signerstore: PropTypes.object.isRequired, className: PropTypes.string, focus: PropTypes.bool, @@ -66,9 +67,9 @@ export default class SignRequest extends Component { }; componentWillMount () { - const { address, store } = this.props; + const { address, signerstore } = this.props; - store.fetchBalance(address); + signerstore.fetchBalance(address); } render () { @@ -91,15 +92,22 @@ export default class SignRequest extends Component { } renderBinaryDetails (data) { - return (<div className={ styles.signData }> - <p>(Unknown binary data)</p> - </div>); + return ( + <div className={ styles.signData }> + <p> + <FormattedMessage + id='signer.signRequest.unknownBinary' + defaultMessage='(Unknown binary data)' + /> + </p> + </div> + ); } renderDetails () { const { api } = this.context; - const { address, isTest, store, data, origin } = this.props; - const { balances, externalLink } = store; + const { address, data, netVersion, origin, signerstore } = this.props; + const { balances, externalLink } = signerstore; const balance = balances[address]; @@ -114,18 +122,30 @@ export default class SignRequest extends Component { address={ address } balance={ balance } externalLink={ externalLink } - isTest={ isTest } + netVersion={ netVersion } /> <RequestOrigin origin={ origin } /> </div> <div className={ styles.info } title={ api.util.sha3(data) }> - <p>A request to sign data using your account:</p> + <p> + <FormattedMessage + id='signer.signRequest.request' + defaultMessage='A request to sign data using your account:' + /> + </p> { isAscii(data) ? this.renderAsciiDetails(api.util.hexToAscii(data)) : this.renderBinaryDetails(data) } - <p><strong>WARNING: This consequences of doing this may be grave. Confirm the request only if you are sure.</strong></p> + <p> + <strong> + <FormattedMessage + id='signer.signRequest.warning' + defaultMessage='WARNING: This consequences of doing this may be grave. Confirm the request only if you are sure.' + /> + </strong> + </p> </div> </div> ); @@ -138,14 +158,24 @@ export default class SignRequest extends Component { if (status === 'confirmed') { return ( <div className={ styles.actions }> - <span className={ styles.isConfirmed }>Confirmed</span> + <span className={ styles.isConfirmed }> + <FormattedMessage + id='signer.signRequest.state.confirmed' + defaultMessage='Confirmed' + /> + </span> </div> ); } return ( <div className={ styles.actions }> - <span className={ styles.isRejected }>Rejected</span> + <span className={ styles.isRejected }> + <FormattedMessage + id='signer.signRequest.state.rejected' + defaultMessage='Rejected' + /> + </span> </div> ); } diff --git a/js/src/views/Signer/components/SignRequest/signRequest.spec.js b/js/src/views/Signer/components/SignRequest/signRequest.spec.js index 67ca529ec..cf7d7e9f6 100644 --- a/js/src/views/Signer/components/SignRequest/signRequest.spec.js +++ b/js/src/views/Signer/components/SignRequest/signRequest.spec.js @@ -28,7 +28,7 @@ const store = { describe('views/Signer/components/SignRequest', () => { it('renders', () => { expect(shallow( - <SignRequest store={ store } />, + <SignRequest signerstore={ store } />, )).to.be.ok; }); }); diff --git a/js/src/views/Signer/components/TransactionMainDetails/transactionMainDetails.js b/js/src/views/Signer/components/TransactionMainDetails/transactionMainDetails.js index 119af2382..ebbddf895 100644 --- a/js/src/views/Signer/components/TransactionMainDetails/transactionMainDetails.js +++ b/js/src/views/Signer/components/TransactionMainDetails/transactionMainDetails.js @@ -14,11 +14,12 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see <http://www.gnu.org/licenses/>. -import MapsLocalGasStation from 'material-ui/svg-icons/maps/local-gas-station'; import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; import ReactTooltip from 'react-tooltip'; import { Button, MethodDecoding } from '~/ui'; +import { GasIcon } from '~/ui/Icons'; import * as tUtil from '../util/transaction'; import Account from '../Account'; @@ -29,12 +30,13 @@ import styles from './transactionMainDetails.css'; export default class TransactionMainDetails extends Component { static propTypes = { children: PropTypes.node, + disabled: PropTypes.bool, externalLink: PropTypes.string.isRequired, from: PropTypes.string.isRequired, fromBalance: PropTypes.object, gasStore: PropTypes.object, id: PropTypes.object.isRequired, - isTest: PropTypes.bool.isRequired, + netVersion: PropTypes.string.isRequired, origin: PropTypes.any, totalValue: PropTypes.object.isRequired, transaction: PropTypes.object.isRequired, @@ -61,7 +63,7 @@ export default class TransactionMainDetails extends Component { } render () { - const { children, externalLink, from, fromBalance, gasStore, isTest, transaction, origin } = this.props; + const { children, disabled, externalLink, from, fromBalance, gasStore, netVersion, transaction, origin } = this.props; return ( <div className={ styles.transaction }> @@ -70,8 +72,9 @@ export default class TransactionMainDetails extends Component { <Account address={ from } balance={ fromBalance } + disabled={ disabled } externalLink={ externalLink } - isTest={ isTest } + netVersion={ netVersion } /> </div> <RequestOrigin origin={ origin } /> @@ -103,8 +106,13 @@ export default class TransactionMainDetails extends Component { return ( <div className={ styles.editButtonRow }> <Button - icon={ <MapsLocalGasStation /> } - label='Edit conditions/gas/gasPrice' + icon={ <GasIcon /> } + label={ + <FormattedMessage + id='signer.mainDetails.editTx' + defaultMessage='Edit conditions/gas/gasPrice' + /> + } onClick={ this.toggleGasEditor } /> </div> @@ -128,8 +136,23 @@ export default class TransactionMainDetails extends Component { { totalValueDisplay } <small>ETH</small> </div> <ReactTooltip id={ labelId }> - The value of the transaction including the mining fee is <strong>{ totalValueDisplayWei }</strong> <small>WEI</small>. <br /> - (This includes a mining fee of <strong>{ feeEth }</strong> <small>ETH</small>) + <FormattedMessage + id='signer.mainDetails.tooltips.total1' + defaultMessage='The value of the transaction including the mining fee is {total} {type}.' + values={ { + total: <strong>{ totalValueDisplayWei }</strong>, + type: <small>WEI</small> + } } + /> + <br /> + <FormattedMessage + id='signer.mainDetails.tooltips.total2' + defaultMessage='(This includes a mining fee of {fee} {token})' + values={ { + fee: <strong>{ feeEth }</strong>, + token: <small>ETH</small> + } } + /> </ReactTooltip> </div> ); @@ -151,7 +174,11 @@ export default class TransactionMainDetails extends Component { <small>ETH</small> </div> <ReactTooltip id={ labelId }> - The value of the transaction.<br /> + <FormattedMessage + id='signer.mainDetails.tooltips.value1' + defaultMessage='The value of the transaction.' + /> + <br /> <strong>{ valueDisplayWei }</strong> <small>WEI</small> </ReactTooltip> </div> diff --git a/js/src/views/Signer/components/TransactionPending/transactionPending.js b/js/src/views/Signer/components/TransactionPending/transactionPending.js index 59f4b5b42..9b0b91ef6 100644 --- a/js/src/views/Signer/components/TransactionPending/transactionPending.js +++ b/js/src/views/Signer/components/TransactionPending/transactionPending.js @@ -14,9 +14,12 @@ // 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, { Component, PropTypes } from 'react'; import { observer } from 'mobx-react'; +import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; +import { connect } from 'react-redux'; +import HardwareStore from '~/mobx/hardwareStore'; import { Button, GasPriceEditor } from '~/ui'; import TransactionMainDetails from '../TransactionMainDetails'; @@ -27,24 +30,25 @@ import styles from './transactionPending.css'; import * as tUtil from '../util/transaction'; @observer -export default class TransactionPending extends Component { +class TransactionPending extends Component { static contextTypes = { api: PropTypes.object.isRequired }; static propTypes = { + accounts: PropTypes.object.isRequired, className: PropTypes.string, date: PropTypes.instanceOf(Date).isRequired, focus: PropTypes.bool, gasLimit: PropTypes.object, id: PropTypes.object.isRequired, isSending: PropTypes.bool.isRequired, - isTest: PropTypes.bool.isRequired, + netVersion: PropTypes.string.isRequired, nonce: PropTypes.number, onConfirm: PropTypes.func.isRequired, onReject: PropTypes.func.isRequired, origin: PropTypes.any, - store: PropTypes.object.isRequired, + signerstore: PropTypes.object.isRequired, transaction: PropTypes.shape({ condition: PropTypes.object, data: PropTypes.string, @@ -71,8 +75,10 @@ export default class TransactionPending extends Component { gasPrice: this.props.transaction.gasPrice.toFixed() }); + hwstore = HardwareStore.get(this.context.api); + componentWillMount () { - const { store, transaction } = this.props; + const { signerstore, transaction } = this.props; const { from, gas, gasPrice, to, value } = transaction; const fee = tUtil.getFee(gas, gasPrice); // BigNumber object @@ -82,7 +88,7 @@ export default class TransactionPending extends Component { this.setState({ gasPriceEthmDisplay, totalValue, gasToDisplay }); this.gasStore.setEthValue(value); - store.fetchBalances([from, to]); + signerstore.fetchBalances([from, to]); } render () { @@ -92,30 +98,34 @@ export default class TransactionPending extends Component { } renderTransaction () { - const { className, focus, id, isSending, isTest, store, transaction, origin } = this.props; + const { accounts, className, focus, id, isSending, netVersion, origin, signerstore, transaction } = this.props; const { totalValue } = this.state; - const { balances, externalLink } = store; + const { balances, externalLink } = signerstore; const { from, value } = transaction; - const fromBalance = balances[from]; + const account = accounts[from] || {}; + const disabled = account.hardware && !this.hwstore.isConnected(from); return ( <div className={ `${styles.container} ${className}` }> <TransactionMainDetails className={ styles.transactionDetails } + disabled={ disabled } externalLink={ externalLink } from={ from } fromBalance={ fromBalance } gasStore={ this.gasStore } id={ id } - isTest={ isTest } + netVersion={ netVersion } origin={ origin } totalValue={ totalValue } transaction={ transaction } value={ value } /> <TransactionPendingForm + account={ account } address={ from } + disabled={ disabled } focus={ focus } isSending={ isSending } onConfirm={ this.onConfirm } @@ -132,7 +142,12 @@ export default class TransactionPending extends Component { <div className={ `${styles.container} ${className}` }> <GasPriceEditor store={ this.gasStore }> <Button - label='view transaction' + label={ + <FormattedMessage + id='signer.txPending.buttons.viewToggle' + defaultMessage='view transaction' + /> + } onClick={ this.toggleGasEditor } /> </GasPriceEditor> @@ -168,3 +183,16 @@ export default class TransactionPending extends Component { this.gasStore.setEditing(false); } } + +function mapStateToProps (state) { + const { accounts } = state.personal; + + return { + accounts + }; +} + +export default connect( + mapStateToProps, + null +)(TransactionPending); diff --git a/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/transactionPendingFormConfirm.js b/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/transactionPendingFormConfirm.js index 428ff660e..13bf27876 100644 --- a/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/transactionPendingFormConfirm.js +++ b/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/transactionPendingFormConfirm.js @@ -18,17 +18,18 @@ import keycode from 'keycode'; import RaisedButton from 'material-ui/RaisedButton'; import React, { Component, PropTypes } from 'react'; import ReactDOM from 'react-dom'; -import { connect } from 'react-redux'; +import { FormattedMessage } from 'react-intl'; import ReactTooltip from 'react-tooltip'; import { Form, Input, IdentityIcon } from '~/ui'; import styles from './transactionPendingFormConfirm.css'; -class TransactionPendingFormConfirm extends Component { +export default class TransactionPendingFormConfirm extends Component { static propTypes = { account: PropTypes.object.isRequired, address: PropTypes.string.isRequired, + disabled: PropTypes.bool, isSending: PropTypes.bool.isRequired, onConfirm: PropTypes.func.isRequired, focus: PropTypes.bool @@ -92,44 +93,16 @@ class TransactionPendingFormConfirm extends Component { } render () { - const { account, address, isSending } = this.props; - const { password, wallet, walletError } = this.state; - const isExternal = !account.uuid; - - const passwordHintText = this.getPasswordHint(); - const passwordHint = passwordHintText - ? (<div><span>(hint) </span>{ passwordHintText }</div>) - : null; - - const isWalletOk = !isExternal || (walletError === null && wallet !== null); - const keyInput = isExternal - ? this.renderKeyInput() - : null; + const { account, address, disabled, isSending } = this.props; + const { wallet, walletError } = this.state; + const isWalletOk = account.hardware || account.uuid || (walletError === null && wallet !== null); return ( <div className={ styles.confirmForm }> <Form> - { keyInput } - <Input - hint={ - isExternal - ? 'decrypt the key' - : 'unlock the account' - } - label={ - isExternal - ? 'Key Password' - : 'Account Password' - } - onChange={ this.onModifyPassword } - onKeyDown={ this.onKeyDown } - ref='input' - type='password' - value={ password } - /> - <div className={ styles.passwordHint }> - { passwordHint } - </div> + { this.renderKeyInput() } + { this.renderPassword() } + { this.renderHint() } <div data-effect='solid' data-for={ `transactionConfirmForm${this.id}` } @@ -138,7 +111,7 @@ class TransactionPendingFormConfirm extends Component { > <RaisedButton className={ styles.confirmButton } - disabled={ isSending || !isWalletOk } + disabled={ disabled || isSending || !isWalletOk } fullWidth icon={ <IdentityIcon @@ -149,8 +122,18 @@ class TransactionPendingFormConfirm extends Component { } label={ isSending - ? 'Confirming...' - : 'Confirm Request' + ? ( + <FormattedMessage + id='signer.txPendingConfirm.buttons.confirmBusy' + defaultMessage='Confirming...' + /> + ) + : ( + <FormattedMessage + id='signer.txPendingConfirm.buttons.confirmRequest' + defaultMessage='Confirm Request' + /> + ) } onTouchTap={ this.onConfirm } primary @@ -162,14 +145,123 @@ class TransactionPendingFormConfirm extends Component { ); } + renderPassword () { + const { account } = this.props; + const { password } = this.state; + + if (account && account.hardware) { + return null; + } + + return ( + <Input + hint={ + account.uuid + ? ( + <FormattedMessage + id='signer.txPendingConfirm.password.unlock.hint' + defaultMessage='unlock the account' + /> + ) + : ( + <FormattedMessage + id='signer.txPendingConfirm.password.decrypt.hint' + defaultMessage='decrypt the key' + /> + ) + } + label={ + account.uuid + ? ( + <FormattedMessage + id='signer.txPendingConfirm.password.unlock.label' + defaultMessage='Account Password' + /> + ) + : ( + <FormattedMessage + id='signer.txPendingConfirm.password.decrypt.label' + defaultMessage='Key Password' + /> + ) + } + onChange={ this.onModifyPassword } + onKeyDown={ this.onKeyDown } + ref='input' + type='password' + value={ password } + /> + ); + } + + renderHint () { + const { account, disabled, isSending } = this.props; + + if (account.hardware) { + if (isSending) { + return ( + <div className={ styles.passwordHint }> + <FormattedMessage + id='signer.sending.hardware.confirm' + defaultMessage='Please confirm the transaction on your attached hardware device' + /> + </div> + ); + } else if (disabled) { + return ( + <div className={ styles.passwordHint }> + <FormattedMessage + id='signer.sending.hardware.connect' + defaultMessage='Please attach your hardware device before confirming the transaction' + /> + </div> + ); + } + } + + const passwordHint = this.getPasswordHint(); + + if (!passwordHint) { + return null; + } + + return ( + <div className={ styles.passwordHint }> + <FormattedMessage + id='signer.txPendingConfirm.passwordHint' + defaultMessage='(hint) {passwordHint}' + values={ { + passwordHint + } } + /> + </div> + ); + } + renderKeyInput () { + const { account } = this.props; const { walletError } = this.state; + if (account.uuid || account.wallet || account.hardware) { + return null; + } + return ( <Input className={ styles.fileInput } error={ walletError } - label='Select Local Key' + hint={ + <FormattedMessage + id='signer.txPendingConfirm.selectKey.hint' + defaultMessage='The keyfile to use for this account' + /> + } + label={ + <FormattedMessage + id='signer.txPendingConfirm.selectKey.label' + defaultMessage='Select Local Key' + /> + } onChange={ this.onKeySelect } type='file' /> @@ -177,13 +269,18 @@ class TransactionPendingFormConfirm extends Component { } renderTooltip () { - if (this.state.password.length) { + const { account } = this.props; + + if (this.state.password.length || account.hardware) { return; } return ( <ReactTooltip id={ `transactionConfirmForm${this.id}` }> - Please provide a password for this account + <FormattedMessage + id='signer.txPendingConfirm.tooltips.password' + defaultMessage='Please provide a password for this account' + /> </ReactTooltip> ); } @@ -216,7 +313,12 @@ class TransactionPendingFormConfirm extends Component { } catch (error) { this.setState({ wallet: null, - walletError: 'Given wallet file is invalid.' + walletError: ( + <FormattedMessage + id='signer.txPendingConfirm.errors.invalidWallet' + defaultMessage='Given wallet file is invalid.' + /> + ) }); } }; @@ -236,7 +338,8 @@ class TransactionPendingFormConfirm extends Component { const { password, wallet } = this.state; this.props.onConfirm({ - password, wallet + password, + wallet }); } @@ -250,20 +353,3 @@ class TransactionPendingFormConfirm extends Component { this.onConfirm(); } } - -function mapStateToProps (_, initProps) { - const { address } = initProps; - - return (state) => { - const { accounts } = state.personal; - let gotAddress = Object.keys(accounts).find(a => a.toLowerCase() === address.toLowerCase()); - const account = gotAddress ? accounts[gotAddress] : {}; - - return { account }; - }; -} - -export default connect( - mapStateToProps, - null -)(TransactionPendingFormConfirm); diff --git a/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/transactionPendingFormConfirm.spec.js b/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/transactionPendingFormConfirm.spec.js new file mode 100644 index 000000000..79456c8fd --- /dev/null +++ b/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/transactionPendingFormConfirm.spec.js @@ -0,0 +1,134 @@ +// Copyright 2015-2017 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 TransactionPendingFormConfirm from './'; + +const ADDR_NORMAL = '0x0123456789012345678901234567890123456789'; +const ADDR_WALLET = '0x1234567890123456789012345678901234567890'; +const ADDR_HARDWARE = '0x2345678901234567890123456789012345678901'; +const ADDR_SIGN = '0x3456789012345678901234567890123456789012'; +const ACCOUNTS = { + [ADDR_NORMAL]: { + address: ADDR_NORMAL, + uuid: ADDR_NORMAL + }, + [ADDR_WALLET]: { + address: ADDR_WALLET, + wallet: true + }, + [ADDR_HARDWARE]: { + address: ADDR_HARDWARE, + hardware: true + } +}; + +let component; +let instance; +let onConfirm; + +function render (address) { + onConfirm = sinon.stub(); + + component = shallow( + <TransactionPendingFormConfirm + account={ ACCOUNTS[address] || {} } + address={ address } + onConfirm={ onConfirm } + isSending={ false } + /> + ); + instance = component.instance(); + + return component; +} + +describe('views/Signer/TransactionPendingFormConfirm', () => { + describe('normal accounts', () => { + beforeEach(() => { + render(ADDR_NORMAL); + }); + + it('renders defaults', () => { + expect(component).to.be.ok; + }); + + it('does not render the key input', () => { + expect(instance.renderKeyInput()).to.be.null; + }); + + it('renders the password', () => { + expect(instance.renderPassword()).not.to.be.null; + }); + }); + + describe('hardware accounts', () => { + beforeEach(() => { + render(ADDR_HARDWARE); + }); + + it('renders defaults', () => { + expect(component).to.be.ok; + }); + + it('does not render the key input', () => { + expect(instance.renderKeyInput()).to.be.null; + }); + + it('does not render the password', () => { + expect(instance.renderPassword()).to.be.null; + }); + }); + + describe('wallet accounts', () => { + beforeEach(() => { + render(ADDR_WALLET); + }); + + it('renders defaults', () => { + expect(component).to.be.ok; + }); + + it('does not render the key input', () => { + expect(instance.renderKeyInput()).to.be.null; + }); + + it('renders the password', () => { + expect(instance.renderPassword()).not.to.be.null; + }); + }); + + describe('signing accounts', () => { + beforeEach(() => { + render(ADDR_SIGN); + }); + + it('renders defaults', () => { + expect(component).to.be.ok; + }); + + it('renders the key input', () => { + expect(instance.renderKeyInput()).not.to.be.null; + }); + + it('renders the password', () => { + expect(instance.renderPassword()).not.to.be.null; + }); + }); +}); diff --git a/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormReject/transactionPendingFormReject.js b/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormReject/transactionPendingFormReject.js index 01767c24a..3f5ba943f 100644 --- a/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormReject/transactionPendingFormReject.js +++ b/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormReject/transactionPendingFormReject.js @@ -15,6 +15,7 @@ // along with Parity. If not, see <http://www.gnu.org/licenses/>. import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; import RaisedButton from 'material-ui/RaisedButton'; @@ -32,14 +33,28 @@ export default class TransactionPendingFormReject extends Component { return ( <div> <div className={ styles.rejectText }> - Are you sure you want to reject request? <br /> - <strong>This cannot be undone</strong> + <FormattedMessage + id='signer.txPendingReject.info' + defaultMessage='Are you sure you want to reject request?' + /> + <br /> + <strong> + <FormattedMessage + id='signer.txPendingReject.undone' + defaultMessage='This cannot be undone' + /> + </strong> </div> <RaisedButton onTouchTap={ onReject } className={ styles.rejectButton } fullWidth - label={ 'Reject Request' } + label={ + <FormattedMessage + id='signer.txPendingReject.buttons.reject' + defaultMessage='Reject Request' + /> + } /> </div> ); diff --git a/js/src/views/Signer/components/TransactionPendingForm/transactionPendingForm.js b/js/src/views/Signer/components/TransactionPendingForm/transactionPendingForm.js index 9b114759e..193eab9f4 100644 --- a/js/src/views/Signer/components/TransactionPendingForm/transactionPendingForm.js +++ b/js/src/views/Signer/components/TransactionPendingForm/transactionPendingForm.js @@ -15,8 +15,9 @@ // along with Parity. If not, see <http://www.gnu.org/licenses/>. import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; -import BackIcon from 'material-ui/svg-icons/navigation/arrow-back'; +import { PrevIcon } from '~/ui/Icons'; import TransactionPendingFormConfirm from './TransactionPendingFormConfirm'; import TransactionPendingFormReject from './TransactionPendingFormReject'; @@ -24,7 +25,9 @@ import styles from './transactionPendingForm.css'; export default class TransactionPendingForm extends Component { static propTypes = { + account: PropTypes.object.isRequired, address: PropTypes.string.isRequired, + disabled: PropTypes.bool, isSending: PropTypes.bool.isRequired, onConfirm: PropTypes.func.isRequired, onReject: PropTypes.func.isRequired, @@ -52,7 +55,7 @@ export default class TransactionPendingForm extends Component { } renderForm () { - const { address, focus, isSending, onConfirm, onReject } = this.props; + const { account, address, disabled, focus, isSending, onConfirm, onReject } = this.props; if (this.state.isRejectOpen) { return ( @@ -63,6 +66,8 @@ export default class TransactionPendingForm extends Component { return ( <TransactionPendingFormConfirm address={ address } + account={ account } + disabled={ disabled } focus={ focus } isSending={ isSending } onConfirm={ onConfirm } @@ -75,9 +80,24 @@ export default class TransactionPendingForm extends Component { let html; if (!isRejectOpen) { - html = <span>reject request</span>; + html = ( + <span> + <FormattedMessage + id='signer.txPendingForm.reject' + defaultMessage='reject request' + /> + </span> + ); } else { - html = <span><BackIcon />{ "I've changed my mind" }</span>; + html = ( + <span> + <PrevIcon /> + <FormattedMessage + id='signer.txPendingForm.changedMind' + defaultMessage="I've changed my mind" + /> + </span> + ); } return ( diff --git a/js/src/views/Signer/components/TxHashLink/txHashLink.js b/js/src/views/Signer/components/TxHashLink/txHashLink.js index 629f30835..42cc336e7 100644 --- a/js/src/views/Signer/components/TxHashLink/txHashLink.js +++ b/js/src/views/Signer/components/TxHashLink/txHashLink.js @@ -22,17 +22,17 @@ export default class TxHashLink extends Component { static propTypes = { children: PropTypes.node, className: PropTypes.string, - isTest: PropTypes.bool.isRequired, + netVersion: PropTypes.string.isRequired, txHash: PropTypes.string.isRequired } render () { - const { children, className, isTest, txHash } = this.props; + const { children, className, netVersion, txHash } = this.props; return ( <a className={ className } - href={ txLink(txHash, isTest) } + href={ txLink(txHash, false, netVersion) } target='_blank' > { children || txHash } diff --git a/js/src/views/Signer/containers/Embedded/embedded.js b/js/src/views/Signer/containers/Embedded/embedded.js index 76c12644b..6d63590c7 100644 --- a/js/src/views/Signer/containers/Embedded/embedded.js +++ b/js/src/views/Signer/containers/Embedded/embedded.js @@ -39,7 +39,7 @@ class Embedded extends Component { }).isRequired, externalLink: PropTypes.string, gasLimit: PropTypes.object.isRequired, - isTest: PropTypes.bool.isRequired, + netVersion: PropTypes.string.isRequired, signer: PropTypes.shape({ finished: PropTypes.array.isRequired, pending: PropTypes.array.isRequired @@ -80,7 +80,7 @@ class Embedded extends Component { } renderPending = (data, index) => { - const { actions, gasLimit, isTest } = this.props; + const { actions, gasLimit, netVersion } = this.props; const { date, id, isSending, payload, origin } = data; return ( @@ -91,13 +91,13 @@ class Embedded extends Component { gasLimit={ gasLimit } id={ id } isSending={ isSending } - isTest={ isTest } + netVersion={ netVersion } key={ id } onConfirm={ actions.startConfirmRequest } onReject={ actions.startRejectRequest } origin={ origin } payload={ payload } - store={ this.store } + signerstore={ this.store } /> ); } @@ -108,13 +108,13 @@ class Embedded extends Component { } function mapStateToProps (state) { - const { gasLimit, isTest } = state.nodeStatus; + const { gasLimit, netVersion } = state.nodeStatus; const { actions, signer } = state; return { actions, gasLimit, - isTest, + netVersion, signer }; } diff --git a/js/src/views/Signer/containers/RequestsPage/requestsPage.js b/js/src/views/Signer/containers/RequestsPage/requestsPage.js index 7ec4abf2a..d90ed7693 100644 --- a/js/src/views/Signer/containers/RequestsPage/requestsPage.js +++ b/js/src/views/Signer/containers/RequestsPage/requestsPage.js @@ -40,7 +40,7 @@ class RequestsPage extends Component { startRejectRequest: PropTypes.func.isRequired }).isRequired, gasLimit: PropTypes.object.isRequired, - isTest: PropTypes.bool.isRequired, + netVersion: PropTypes.string.isRequired, signer: PropTypes.shape({ pending: PropTypes.array.isRequired, finished: PropTypes.array.isRequired @@ -106,7 +106,7 @@ class RequestsPage extends Component { } renderPending = (data, index) => { - const { actions, gasLimit, isTest } = this.props; + const { actions, gasLimit, netVersion } = this.props; const { date, id, isSending, payload, origin } = data; return ( @@ -117,26 +117,26 @@ class RequestsPage extends Component { gasLimit={ gasLimit } id={ id } isSending={ isSending } - isTest={ isTest } + netVersion={ netVersion } key={ id } onConfirm={ actions.startConfirmRequest } onReject={ actions.startRejectRequest } origin={ origin } payload={ payload } - store={ this.store } + signerstore={ this.store } /> ); } } function mapStateToProps (state) { - const { gasLimit, isTest } = state.nodeStatus; + const { gasLimit, netVersion } = state.nodeStatus; const { actions, signer } = state; return { actions, gasLimit, - isTest, + netVersion, signer }; } diff --git a/js/src/views/Signer/signer.js b/js/src/views/Signer/signer.js index c4df9bf3a..093a980ed 100644 --- a/js/src/views/Signer/signer.js +++ b/js/src/views/Signer/signer.js @@ -15,6 +15,7 @@ // along with Parity. If not, see <http://www.gnu.org/licenses/>. import React, { Component } from 'react'; +import { FormattedMessage } from 'react-intl'; import { Actionbar } from '~/ui'; import RequestsPage from './containers/RequestsPage'; @@ -23,7 +24,14 @@ export default class Signer extends Component { render () { return ( <div> - <Actionbar title='Trusted Signer' /> + <Actionbar + title={ + <FormattedMessage + id='signer.title' + defaultMessage='Trusted Signer' + /> + } + /> <RequestsPage /> </div> ); diff --git a/js/src/views/Signer/store.js b/js/src/views/Signer/store.js index ad50bad87..76e3522f8 100644 --- a/js/src/views/Signer/store.js +++ b/js/src/views/Signer/store.js @@ -41,8 +41,9 @@ export default class SignerStore { this.balances = Object.assign({}, this.balances, balances); } - @action setLocalHashes = (localHashes) => { - if (!isEqual(localHashes, this.localHashes)) { + @action setLocalHashes = (localHashes = []) => { + // Use slice to make sure they are both Arrays (MobX uses Objects for Observable Arrays) + if (!isEqual(localHashes.slice(), this.localHashes.slice())) { this.localHashes = localHashes; } } diff --git a/js/src/views/Status/components/Calls/Calls.js b/js/src/views/Status/components/Calls/Calls.js index 710401754..3a1023bc6 100644 --- a/js/src/views/Status/components/Calls/Calls.js +++ b/js/src/views/Status/components/Calls/Calls.js @@ -15,6 +15,8 @@ // along with Parity. If not, see <http://www.gnu.org/licenses/>. import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; + import Call from '../Call'; import CallsToolbar from '../CallsToolbar'; import styles from './Calls.css'; @@ -33,7 +35,12 @@ export default class Calls extends Component { { ...this._test('container') } > { this.renderClear() } - <h2 className={ styles.header }>History</h2> + <h2 className={ styles.header }> + <FormattedMessage + id='status.calls.title' + defaultMessage='History' + /> + </h2> <div className={ `${styles.history} row` } ref={ this.setCallsHistory }> { this.renderNoCallsMsg() } { this.renderCalls() } @@ -56,7 +63,12 @@ export default class Calls extends Component { return ( <a { ...this._test('remove') } - title='Clear RPC calls history' + title={ + <FormattedMessage + id='status.calls.clearHistory' + defaultMessage='Clear RPC calls history' + /> + } onClick={ this.clearHistory } className={ styles.removeIcon } > @@ -73,7 +85,10 @@ export default class Calls extends Component { return ( <div { ...this._test('empty-wrapper') }> <h3 className={ styles.historyInfo } { ...this._test('empty') }> - Fire up some calls and the results will be here. + <FormattedMessage + id='status.calls.rpcResults' + defaultMessage='Fire up some calls and the results will be here.' + /> </h3> </div> ); diff --git a/js/src/views/Status/components/CallsToolbar/CallsToolbar.js b/js/src/views/Status/components/CallsToolbar/CallsToolbar.js index bf5a20691..618a5de26 100644 --- a/js/src/views/Status/components/CallsToolbar/CallsToolbar.js +++ b/js/src/views/Status/components/CallsToolbar/CallsToolbar.js @@ -16,6 +16,7 @@ import React, { Component, PropTypes } from 'react'; import CopyToClipboard from 'react-copy-to-clipboard'; +import { FormattedMessage } from 'react-intl'; import { sortBy, find, extend } from 'lodash'; import IconButton from 'material-ui/IconButton'; @@ -58,7 +59,12 @@ export default class CallsToolbar extends Component { <IconButton className={ styles.callAction } onTouchTap={ this.setCall } - tooltip='Set' + tooltip={ + <FormattedMessage + id='status.callsToolbar.tooltip.set' + defaultMessage='Set' + /> + } tooltipPosition='top-left' { ...this._test('button-setCall') } > @@ -67,7 +73,12 @@ export default class CallsToolbar extends Component { <IconButton className={ styles.callAction } onTouchTap={ this.makeCall } - tooltip='Fire again' + tooltip={ + <FormattedMessage + id='status.callsToolbar.tooltip.fireAgain' + defaultMessage='Fire again' + /> + } tooltipPosition='top-left' { ...this._test('button-makeCall') } > @@ -79,7 +90,12 @@ export default class CallsToolbar extends Component { > <IconButton className={ styles.callAction } - tooltip='Copy to clipboard' + tooltip={ + <FormattedMessage + id='status.callsToolbar.tooltip.copy' + defaultMessage='Copy to clipboard' + /> + } tooltipPosition='top-left' { ...this._test('copyCallToClipboard') } > @@ -112,7 +128,12 @@ export default class CallsToolbar extends Component { } copyToClipboard = () => { - this.props.actions.copyToClipboard('method copied to clipboard'); + this.props.actions.copyToClipboard( + <FormattedMessage + id='status.callsToolbar.copied' + defaultMessage='method copied to clipboard' + /> + ); } hasScrollbar (el) { diff --git a/js/src/views/Status/components/Debug/debug.js b/js/src/views/Status/components/Debug/debug.js index 2b892b584..ed423572f 100644 --- a/js/src/views/Status/components/Debug/debug.js +++ b/js/src/views/Status/components/Debug/debug.js @@ -15,12 +15,10 @@ // along with Parity. If not, see <http://www.gnu.org/licenses/>. import React, { Component, PropTypes } from 'react'; -import AvPause from 'material-ui/svg-icons/av/pause'; -import AvPlay from 'material-ui/svg-icons/av/play-arrow'; -import AvReplay from 'material-ui/svg-icons/av/replay'; -import ReorderIcon from 'material-ui/svg-icons/action/reorder'; +import { FormattedMessage } from 'react-intl'; import { Container } from '~/ui'; +import { PauseIcon, PlayIcon, ReorderIcon, ReplayIcon } from '~/ui/Icons'; import styles from './debug.css'; @@ -42,7 +40,14 @@ export default class Debug extends Component { const { devLogsLevels } = nodeStatus; return ( - <Container title='Node Logs'> + <Container + title={ + <FormattedMessage + id='status.debug.title' + defaultMessage='Node Logs' + /> + } + > { this.renderActions() } <h2 className={ styles.subheader }> { devLogsLevels || '-' } @@ -62,7 +67,10 @@ export default class Debug extends Component { return ( <div className={ styles.stopped }> - Refresh and display of logs from Parity is currently stopped via the UI, start it to see the latest updates. + <FormattedMessage + id='status.debug.stopped' + defaultMessage='Refresh and display of logs from Parity is currently stopped via the UI, start it to see the latest updates.' + /> </div> ); } @@ -112,14 +120,24 @@ export default class Debug extends Component { renderActions () { const { devLogsEnabled } = this.props.nodeStatus; const toggleButton = devLogsEnabled - ? <AvPause /> - : <AvPlay />; + ? <PauseIcon /> + : <PlayIcon />; return ( <div className={ styles.actions }> <a onClick={ this.toggle }>{ toggleButton }</a> - <a onClick={ this.clear }><AvReplay /></a> - <a onClick={ this.reverse } title='Reverse Order'><ReorderIcon /></a> + <a onClick={ this.clear }><ReplayIcon /></a> + <a + onClick={ this.reverse } + title={ + <FormattedMessage + id='status.debug.reverse' + defaultMessage='Reverse Order' + /> + } + > + <ReorderIcon /> + </a> </div> ); } diff --git a/js/src/views/Status/components/EditableValue/EditableValue.js b/js/src/views/Status/components/EditableValue/EditableValue.js index 8be14c76a..98ad223ca 100644 --- a/js/src/views/Status/components/EditableValue/EditableValue.js +++ b/js/src/views/Status/components/EditableValue/EditableValue.js @@ -15,6 +15,7 @@ // along with Parity. If not, see <http://www.gnu.org/licenses/>. import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; import AutoComplete from 'material-ui/AutoComplete'; import styles from './EditableValue.css'; @@ -133,10 +134,18 @@ export default class EditableValue extends Component { return ( <a - key={ 'reset' } + key='reset' className={ `${styles.icon} ${styles.firstIcon}` } onClick={ this.onResetToDefault } - title={ `Reset to ${this.props.defaultValue}` } + title={ + <FormattedMessage + id='status.editableValue.reset' + defaultMessage='Reset to {defaultVaule}' + values={ { + defaultVaule: this.props.defaultValue + } } + /> + } { ...this._testInherit('reset') } > <i className='icon-anchor' /> @@ -148,7 +157,7 @@ export default class EditableValue extends Component { if (this.state.inEditMode) { return [ <a - key={ 'submit' } + key='submit' className={ styles.iconSuccess } onClick={ this.onSubmit } { ...this._testInherit('submit') } @@ -156,7 +165,7 @@ export default class EditableValue extends Component { <i className='icon-check' /> </a>, <a - key={ 'cancel' } + key='cancel' className={ styles.icon } onClick={ this.onCancel } { ...this._testInherit('cancel') } @@ -168,10 +177,15 @@ export default class EditableValue extends Component { return ( <a - key={ 'edit' } + key='edit' className={ styles.icon } onClick={ this.onOpenEdit } - title='Edit' + title={ + <FormattedMessage + id='status.editableValue.edit' + defaultMessage='Edit' + /> + } { ...this._testInherit('edit') } > <i className='icon-pencil' /> diff --git a/js/src/views/Status/components/MiningSettings/miningSettings.js b/js/src/views/Status/components/MiningSettings/miningSettings.js index a538e030b..6e51b891a 100644 --- a/js/src/views/Status/components/MiningSettings/miningSettings.js +++ b/js/src/views/Status/components/MiningSettings/miningSettings.js @@ -15,6 +15,7 @@ // along with Parity. If not, see <http://www.gnu.org/licenses/>. import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; import formatNumber from 'format-number'; import { ContainerTitle, Input } from '~/ui'; @@ -47,10 +48,27 @@ export default class MiningSettings extends Component { return ( <div { ...this._testInherit() }> - <ContainerTitle title='mining settings' /> + <ContainerTitle + title={ + <FormattedMessage + id='status.miningSettings.title' + defaultMessage='mining settings' + /> + } + /> <Input - label='author' - hint='the mining author' + label={ + <FormattedMessage + id='status.miningSettings.input.author.label' + defaultMessage='author' + /> + } + hint={ + <FormattedMessage + id='status.miningSettings.input.author.hint' + defaultMessage='the mining author' + /> + } value={ coinbase } onSubmit={ this.onAuthorChange } allowCopy @@ -59,8 +77,18 @@ export default class MiningSettings extends Component { /> <Input - label='extradata' - hint='extra data for mined blocks' + label={ + <FormattedMessage + id='status.miningSettings.input.extradata.label' + defaultMessage='extradata' + /> + } + hint={ + <FormattedMessage + id='status.miningSettings.input.extradata.hint' + defaultMessage='extra data for mined blocks' + /> + } value={ extradata } onSubmit={ this.onExtraDataChange } defaultValue={ defaultExtradata } @@ -70,8 +98,18 @@ export default class MiningSettings extends Component { /> <Input - label='minimal gas price' - hint='the minimum gas price for mining' + label={ + <FormattedMessage + id='status.miningSettings.input.gasPrice.label' + defaultMessage='minimal gas price' + /> + } + hint={ + <FormattedMessage + id='status.miningSettings.input.gasPrice.hint' + defaultMessage='the minimum gas price for mining' + /> + } value={ toNiceNumber(minGasPrice) } onSubmit={ this.onMinGasPriceChange } allowCopy={ minGasPrice.toString() } @@ -80,8 +118,18 @@ export default class MiningSettings extends Component { /> <Input - label='gas floor target' - hint='the gas floor target for mining' + label={ + <FormattedMessage + id='status.miningSettings.input.gasFloor.label' + defaultMessage='gas floor target' + /> + } + hint={ + <FormattedMessage + id='status.miningSettings.input.gasFloor.hint' + defaultMessage='the gas floor target for mining' + /> + } value={ toNiceNumber(gasFloorTarget) } onSubmit={ this.onGasFloorTargetChange } allowCopy={ gasFloorTarget.toString() } diff --git a/js/src/views/Status/components/RpcCalls/RpcCalls.js b/js/src/views/Status/components/RpcCalls/RpcCalls.js index 69fabf956..022c28975 100644 --- a/js/src/views/Status/components/RpcCalls/RpcCalls.js +++ b/js/src/views/Status/components/RpcCalls/RpcCalls.js @@ -15,6 +15,7 @@ // along with Parity. If not, see <http://www.gnu.org/licenses/>. import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; import _ from 'lodash'; import Toggle from 'material-ui/Toggle/Toggle'; @@ -38,11 +39,11 @@ export default class RpcCalls extends Component { const { paramsValues, params } = nextProps.rpc.selectedMethod; if (paramsValues) { - params.map((p, idx) => { + params.map((p, index) => { // todo [adgo] 01.05.2016 - make sure this works // not sure idx is the same for paramsValues and params this.setState({ - [this.paramKey(p)]: paramsValues[idx] + [this.paramKey(p)]: paramsValues[index] }); }); @@ -59,7 +60,12 @@ export default class RpcCalls extends Component { <div className='dapp-container'> <div className='row'> <div className='col col-6'> - <h1><span>RPC</span> Requests</h1> + <h1> + <FormattedMessage + id='status.rpcCalls.requests' + defaultMessage='RPC Requests' + /> + </h1> </div> <div className='col col-6'> <RpcNav /> @@ -92,11 +98,19 @@ export default class RpcCalls extends Component { <Toggle className={ styles.jsonToggle } onToggle={ this.onJsonToggle } - label='JSON' + label={ + <FormattedMessage + id='status.rpcCalls.json' + defaultMessage='JSON' + /> + } /> <h2 className={ styles.header }> <label htmlFor='selectedMethod'> - Call Method + <FormattedMessage + id='status.rpcCalls.callMethod' + defaultMessage='Call Method' + /> </label> </h2> <AnimateChildren absolute> @@ -117,9 +131,19 @@ export default class RpcCalls extends Component { return ( <div className='row'> { this.renderMethodList() } - <h3>Parameters</h3> + <h3> + <FormattedMessage + id='status.rpcCalls.parameters' + defaultMessage='Parameters' + /> + </h3> { this.renderInputs() } - <h3>Returns</h3> + <h3> + <FormattedMessage + id='status.rpcCalls.returns' + defaultMessage='Returns' + /> + </h3> <Markdown val={ formatRpcMd(returns) } /> { this.renderFormButton() } </div> @@ -177,7 +201,10 @@ export default class RpcCalls extends Component { if (!params || !params.length) { return ( - <span>none</span> + <FormattedMessage + id='status.rpcCalls.none' + defaultMessage='none' + /> ); } @@ -284,7 +311,10 @@ export default class RpcCalls extends Component { disabled={ this.state.jsonEditorError } onClick={ this.onRpcFire } > - Fire! + <FormattedMessage + id='status.rpcCalls.fireButton' + defaultMessage='Fire' + /> </button> ); } diff --git a/js/src/views/Status/components/RpcDocs/RpcDocs.js b/js/src/views/Status/components/RpcDocs/RpcDocs.js index a56d182a4..922346763 100644 --- a/js/src/views/Status/components/RpcDocs/RpcDocs.js +++ b/js/src/views/Status/components/RpcDocs/RpcDocs.js @@ -16,6 +16,7 @@ import React, { Component } from 'react'; import ReactDOM from 'react-dom'; +import { FormattedMessage } from 'react-intl'; import { sortBy } from 'lodash'; import List from 'material-ui/List/List'; import ListItem from 'material-ui/List/ListItem'; @@ -38,7 +39,12 @@ class RpcDocs extends Component { <div className='dapp-container'> <div className='row'> <div className='col col-6'> - <h1><span>RPC</span> Docs</h1> + <h1> + <FormattedMessage + id='status.rpcDocs.title' + defaultMessage='RPC Docs' + /> + </h1> </div> <div className='col col-6'> <RpcNav /> @@ -50,7 +56,12 @@ class RpcDocs extends Component { <div className='row'> <div className='col col-12'> <AutoComplete - floatingLabelText='Method name' + floatingLabelText={ + <FormattedMessage + id='status.rpcDocs.methodName' + defaultMessage='Method name' + /> + } className={ styles.autocomplete } dataSource={ rpcMethods.map(m => m.name) } onNewRequest={ this.handleMethodChange } @@ -78,9 +89,38 @@ class RpcDocs extends Component { > <h3 className={ styles.headline }>{ m.name }</h3> <Markdown val={ m.desc } /> - <p><strong>Params</strong>{ !m.params.length ? ' - none' : '' }</p> - { m.params.map((p, idx) => <Markdown key={ `${m.name}-${idx}` } val={ formatRpcMd(p) } />) } - <p className={ styles.returnsTitle }><strong>Returns</strong> - </p> + <p> + <FormattedMessage + id='status.rpcDocs.params' + defaultMessage='Params {params}' + vaules={ { + params: !m.params.length + ? ( + <FormattedMessage + id='status.rpcDocs.paramsNone' + defaultMessage=' - none' + /> + ) + : '' + } } + /> + </p> + { + m.params.map((p, idx) => { + return ( + <Markdown + key={ `${m.name}-${idx}` } + val={ formatRpcMd(p) } + /> + ); + }) + } + <p className={ styles.returnsTitle }> + <FormattedMessage + id='status.rpcDocs.returns' + defaultMessage='Returns - ' + /> + </p> <Markdown className={ styles.returnsDesc } val={ formatRpcMd(m.returns) } /> { idx !== rpcMethods.length - 1 ? <hr /> : '' } </ListItem> diff --git a/js/src/views/Status/components/Status/status.js b/js/src/views/Status/components/Status/status.js index f7d06833a..efb0c80f7 100644 --- a/js/src/views/Status/components/Status/status.js +++ b/js/src/views/Status/components/Status/status.js @@ -17,6 +17,7 @@ import bytes from 'bytes'; import moment from 'moment'; import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; import { Container, ContainerTitle, Input } from '~/ui'; @@ -47,7 +48,14 @@ export default class Status extends Component { <div className={ styles.row }> <div className={ styles.col3 }> <div className={ `${styles.col12} ${styles.padBottom}` }> - <ContainerTitle title='best block' /> + <ContainerTitle + title={ + <FormattedMessage + id='status.status.title.bestBlock' + defaultMessage='best block' + /> + } + /> <div { ...this._test('best-block') } className={ styles.blockInfo }> #{ nodeStatus.blockNumber.toFormat() } </div> @@ -56,15 +64,35 @@ export default class Status extends Component { </div> </div> <div className={ `${styles.col12} ${styles.padBottom}` }> - <ContainerTitle title='peers' /> + <ContainerTitle + title={ + <FormattedMessage + id='status.status.title.peers' + defaultMessage='peers' + /> + } + /> <div { ...this._test('peers') } className={ styles.blockInfo }> { peers } </div> </div> <div className={ `${styles.col12} ${styles.padBottom}` }> - <ContainerTitle title='hash rate' /> + <ContainerTitle + title={ + <FormattedMessage + id='status.status.title.hashRate' + defaultMessage='hash rate' + /> + } + /> <div { ...this._test('hashrate') } className={ styles.blockInfo }> - { `${hashrate} H/s` } + <FormattedMessage + id='status.status.hashrate' + defaultMessage='{hashrate} H/s' + values={ { + hashrate + } } + /> </div> </div> </div> @@ -89,7 +117,12 @@ export default class Status extends Component { return ( <span> - { nodeStatus.nodeName || 'Node' } + { nodeStatus.nodeName || ( + <FormattedMessage + id='status.status.title.node' + defaultMessage='Node' + />) + } </span> ); } @@ -107,11 +140,23 @@ export default class Status extends Component { return ( <div { ...this._test('settings') }> - <ContainerTitle title='network settings' /> + <ContainerTitle + title={ + <FormattedMessage + id='status.status.title.network' + defaultMessage='network settings' + /> + } + /> <Input allowCopy readOnly - label='chain' + label={ + <FormattedMessage + id='status.status.input.chain' + defaultMessage='chain' + /> + } value={ nodeStatus.netChain } { ...this._test('chain') } /> @@ -120,7 +165,12 @@ export default class Status extends Component { <Input allowCopy readOnly - label='peers' + label={ + <FormattedMessage + id='status.status.input.peers' + defaultMessage='peers' + /> + } value={ peers } { ...this._test('peers') } /> @@ -129,7 +179,12 @@ export default class Status extends Component { <Input allowCopy readOnly - label='network port' + label={ + <FormattedMessage + id='status.status.input.port' + defaultMessage='network port' + /> + } value={ netPort.toString() } { ...this._test('network-port') } /> @@ -139,11 +194,26 @@ export default class Status extends Component { <Input allowCopy readOnly - label='rpc enabled' + label={ + <FormattedMessage + id='status.status.input.rpcEnabled' + defaultMessage='rpc enabled' + /> + } value={ rpcSettings.enabled - ? 'yes' - : 'no' + ? ( + <FormattedMessage + id='status.status.input.yes' + defaultMessage='yes' + /> + ) + : ( + <FormattedMessage + id='status.status.input.no' + defaultMessage='no' + /> + ) } { ...this._test('rpc-enabled') } /> @@ -152,7 +222,12 @@ export default class Status extends Component { <Input allowCopy readOnly - label='rpc interface' + label={ + <FormattedMessage + id='status.status.input.rpcInterface' + defaultMessage='rpc interface' + /> + } value={ rpcSettings.interface } { ...this._test('rpc-interface') } /> @@ -161,7 +236,12 @@ export default class Status extends Component { <Input allowCopy readOnly - label='rpc port' + label={ + <FormattedMessage + id='status.status.input.rpcPort' + defaultMessage='rpc port' + /> + } value={ rpcPort.toString() } { ...this._test('rpc-port') } /> @@ -173,7 +253,12 @@ export default class Status extends Component { <Input allowCopy readOnly - label='enode' + label={ + <FormattedMessage + id='status.status.input.enode' + defaultMessage='enode' + /> + } value={ nodeStatus.enode } { ...this._test('node-enode') } /> diff --git a/js/src/views/Status/status.js b/js/src/views/Status/status.js index ff17bd575..68621fe9b 100644 --- a/js/src/views/Status/status.js +++ b/js/src/views/Status/status.js @@ -15,6 +15,7 @@ // along with Parity. If not, see <http://www.gnu.org/licenses/>. import React, { Component } from 'react'; +import { FormattedMessage } from 'react-intl'; import { Page } from '~/ui'; @@ -23,7 +24,14 @@ import StatusPage from './containers/StatusPage'; export default class Status extends Component { render () { return ( - <Page title='status'> + <Page + title={ + <FormattedMessage + id='status.title' + defaultMessage='Status' + /> + } + > <StatusPage /> </Page> ); diff --git a/js/src/views/Vaults/store.js b/js/src/views/Vaults/store.js index 75a52954d..2d4f4c2df 100644 --- a/js/src/views/Vaults/store.js +++ b/js/src/views/Vaults/store.js @@ -37,6 +37,7 @@ export default class Store { @observable selectedAccounts = {}; @observable vault = null; @observable vaults = []; + @observable vaultsOpened = []; @observable vaultNames = []; @observable vaultName = ''; @observable vaultNameError = ERRORS.noName; @@ -143,6 +144,7 @@ export default class Store { isOpen: openedVaults.includes(name) }; }); + this.vaultsOpened = this.vaults.filter((vault) => vault.isOpen); }); } diff --git a/js/src/views/Vaults/store.spec.js b/js/src/views/Vaults/store.spec.js index 9f971d383..863b853da 100644 --- a/js/src/views/Vaults/store.spec.js +++ b/js/src/views/Vaults/store.spec.js @@ -180,6 +180,12 @@ describe('modals/Vaults/Store', () => { { name: 'some', meta: 'metaSome', isOpen: false } ]); }); + + it('sets the opened vaults', () => { + expect(store.vaultsOpened.peek()).to.deep.equal([ + { name: 'TEST', meta: 'metaTest', isOpen: true } + ]); + }); }); describe('setVaultDescription', () => { @@ -553,6 +559,36 @@ describe('modals/Vaults/Store', () => { }); }); + describe('editVaultMeta', () => { + beforeEach(() => { + sinon.spy(store, 'setBusyMeta'); + + store.setVaultDescription('testDescription'); + store.setVaultName('testCreateName'); + store.setVaultPasswordHint('testCreateHint'); + store.setVaultTags('testTags'); + + return store.editVaultMeta(); + }); + + afterEach(() => { + store.setBusyMeta.restore(); + }); + + it('sets and resets the busy flag', () => { + expect(store.setBusyMeta).to.have.been.calledWith(true); + expect(store.isBusyMeta).to.be.false; + }); + + it('calls into parity_setVaultMeta', () => { + expect(api.parity.setVaultMeta).to.have.been.calledWith('testCreateName', { + description: 'testDescription', + passwordHint: 'testCreateHint', + tags: 'testTags' + }); + }); + }); + describe('editVaultPassword', () => { beforeEach(() => { sinon.spy(store, 'setBusyMeta'); diff --git a/js/src/views/Wallet/Confirmations/confirmations.js b/js/src/views/Wallet/Confirmations/confirmations.js index fd4d65a12..bea1ffd95 100644 --- a/js/src/views/Wallet/Confirmations/confirmations.js +++ b/js/src/views/Wallet/Confirmations/confirmations.js @@ -14,8 +14,9 @@ // 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, { Component, PropTypes } from 'react'; import { LinearProgress, MenuItem, IconMenu } from 'material-ui'; +import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; import ReactTooltip from 'react-tooltip'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; @@ -36,7 +37,7 @@ class WalletConfirmations extends Component { static propTypes = { accounts: PropTypes.object.isRequired, address: PropTypes.string.isRequired, - isTest: PropTypes.bool.isRequired, + netVersion: PropTypes.string.isRequired, owners: PropTypes.array.isRequired, require: PropTypes.object.isRequired, confirmOperation: PropTypes.func.isRequired, @@ -70,7 +71,12 @@ class WalletConfirmations extends Component { if (realConfirmations.length === 0) { return ( <div> - <p>No transactions needs confirmation right now.</p> + <p> + <FormattedMessage + id='wallet.confirmations.none' + defaultMessage='No transactions needs confirmation right now.' + /> + </p> </div> ); } @@ -109,7 +115,7 @@ class WalletConfirmation extends Component { accounts: PropTypes.object.isRequired, confirmation: PropTypes.object.isRequired, address: PropTypes.string.isRequired, - isTest: PropTypes.bool.isRequired, + netVersion: PropTypes.string.isRequired, owners: PropTypes.array.isRequired, require: PropTypes.object.isRequired, confirmOperation: PropTypes.func.isRequired, @@ -217,7 +223,12 @@ class WalletConfirmation extends Component { const confirmButton = ( <Button onClick={ this.handleOpenConfirm } - label='Confirm As...' + label={ + <FormattedMessage + id='wallet.confirmations.buttons.confirmAs' + defaultMessage='Confirm As...' + /> + } disabled={ pending || possibleConfirm.length === 0 } /> ); @@ -225,13 +236,21 @@ class WalletConfirmation extends Component { const revokeButton = ( <Button onClick={ this.handleOpenRevoke } - label='Revoke As...' + label={ + <FormattedMessage + id='wallet.confirmations.buttons.revokeAs' + defaultMessage='Revoke As...' + /> + } disabled={ pending || possibleRevoke.length === 0 } /> ); return ( - <tr key={ `actions_${operation}` } className={ className }> + <tr + className={ className } + key={ `actions_${operation}` } + > <td /> <td colSpan={ 3 }> <div className={ styles.actions }> @@ -263,11 +282,15 @@ class WalletConfirmation extends Component { const account = this.props.accounts[address]; return ( - <MenuItem value={ address } key={ address }> + <MenuItem + key={ address } + value={ address } + > <div className={ styles.accountItem }> <IdentityIcon - inline center address={ address } + center + inline /> <span>{ account.name.toUpperCase() || account.address }</span> </div> @@ -283,7 +306,10 @@ class WalletConfirmation extends Component { return ( <tr key={ `prog_${operation}` }> - <td colSpan={ 5 } style={ { padding: 0, paddingTop: '1em' } }> + <td + colSpan={ 5 } + style={ { padding: 0, paddingTop: '1em' } } + > <div data-tip data-for={ `tooltip_${operation}` } @@ -312,7 +338,14 @@ class WalletConfirmation extends Component { </div> <ReactTooltip id={ `tooltip_${operation}` }> - Confirmed by { confirmedBy.length }/{ require.toNumber() } owners + <FormattedMessage + id='wallet.confirmations.tooltip.confirmed' + defaultMessage='Confirmed by {number}/{required} owners' + values={ { + required: require.toNumber(), + number: confirmedBy.length + } } + /> </ReactTooltip> </td> </tr> @@ -320,33 +353,33 @@ class WalletConfirmation extends Component { } renderTransactionRow (confirmation, className) { - const { address, isTest } = this.props; + const { address, netVersion } = this.props; const { operation, transactionHash, blockNumber, value, to, data } = confirmation; if (value && to && data) { return ( <TxRow + address={ address } className={ className } + historic={ false } + netVersion={ netVersion } key={ operation } tx={ { hash: transactionHash, - blockNumber: blockNumber, + blockNumber, from: address, - to: to, - value: value, + to, + value, input: bytesToHex(data) } } - address={ address } - isTest={ isTest } - historic={ false } /> ); } return ( <tr - key={ operation } className={ className } + key={ operation } > <td colSpan={ 5 }> <code>{ operation }</code> diff --git a/js/src/views/Wallet/Details/details.js b/js/src/views/Wallet/Details/details.js index 0fe7b9257..37e3e9395 100644 --- a/js/src/views/Wallet/Details/details.js +++ b/js/src/views/Wallet/Details/details.js @@ -15,6 +15,7 @@ // along with Parity. If not, see <http://www.gnu.org/licenses/>. import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; import { Container, InputAddress } from '~/ui'; @@ -40,7 +41,14 @@ export default class WalletDetails extends Component { return ( <div className={ [ styles.details, className ].join(' ') }> - <Container title='Details'> + <Container + title={ + <FormattedMessage + id='wallet.details.title' + defaultMessage='Details' + /> + } + > { this.renderDetails() } { this.renderOwners() } </Container> @@ -62,10 +70,10 @@ export default class WalletDetails extends Component { return ( <InputAddress - key={ `${idx}_${address}` } - value={ address } disabled + key={ `${idx}_${address}` } text + value={ address } /> ); }); @@ -87,9 +95,24 @@ export default class WalletDetails extends Component { return ( <div> <p> - <span>This wallet requires at least</span> - <span className={ styles.detail }>{ require.toFormat() } owners</span> - <span>to validate any action (transactions, modifications).</span> + <FormattedMessage + id='wallet.details.requiredOwners' + defaultMessage='This wallet requires at least {owners} to validate any action (transactions, modifications).' + values={ { + owners: ( + <span className={ styles.detail }> + <FormattedMessage + id='wallet.details.requiredOwnersNumber' + defaultMessage='{number} {numberValue, plural, one {owner} other {owners}}' + values={ { + number: require.toFormat(), + numberValue: require.toNumber() + } } + /> + </span> + ) + } } + /> </p> </div> ); diff --git a/js/src/views/Wallet/Transactions/transactions.js b/js/src/views/Wallet/Transactions/transactions.js index 35d8c03f1..aa1fae672 100644 --- a/js/src/views/Wallet/Transactions/transactions.js +++ b/js/src/views/Wallet/Transactions/transactions.js @@ -15,6 +15,7 @@ // along with Parity. If not, see <http://www.gnu.org/licenses/>. import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; import { bytesToHex } from '~/api/util/format'; import { Container } from '~/ui'; @@ -25,7 +26,7 @@ import txListStyles from '~/ui/TxList/txList.css'; export default class WalletTransactions extends Component { static propTypes = { address: PropTypes.string.isRequired, - isTest: PropTypes.bool.isRequired, + netVersion: PropTypes.string.isRequired, transactions: PropTypes.array }; @@ -36,14 +37,21 @@ export default class WalletTransactions extends Component { render () { return ( <div> - <Container title='Transactions'> + <Container + title={ + <FormattedMessage + id='wallet.transactions.title' + defaultMessage='Transactions' + /> + } + > { this.renderTransactions() } </Container> </div> ); } renderTransactions () { - const { address, isTest, transactions } = this.props; + const { address, netVersion, transactions } = this.props; if (!transactions) { return null; @@ -52,24 +60,29 @@ export default class WalletTransactions extends Component { if (transactions.length === 0) { return ( <div> - <p>No transactions has been sent.</p> + <p> + <FormattedMessage + id='wallet.transactions.none' + defaultMessage='No transactions has been sent.' + /> + </p> </div> ); } const txRows = transactions.slice(0, 15).map((transaction, index) => { - const { transactionHash, blockNumber, from, to, value, data } = transaction; + const { transactionHash, data } = transaction; return ( <TxRow + address={ address } + netVersion={ netVersion } key={ `${transactionHash}_${index}` } tx={ { hash: transactionHash, input: data && bytesToHex(data) || '', - blockNumber, from, to, value + ...transaction } } - address={ address } - isTest={ isTest } /> ); }); diff --git a/js/src/views/Wallet/wallet.js b/js/src/views/Wallet/wallet.js index 55e15dbc9..84d4dbe5b 100644 --- a/js/src/views/Wallet/wallet.js +++ b/js/src/views/Wallet/wallet.js @@ -15,18 +15,15 @@ // along with Parity. If not, see <http://www.gnu.org/licenses/>. import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import moment from 'moment'; -import ContentCreate from 'material-ui/svg-icons/content/create'; -import ActionDelete from 'material-ui/svg-icons/action/delete'; -import ContentSend from 'material-ui/svg-icons/content/send'; -import SettingsIcon from 'material-ui/svg-icons/action/settings'; - -import { nullableProptype } from '~/util/proptypes'; import { EditMeta, Transfer, WalletSettings } from '~/modals'; import { Actionbar, Button, Page, Loading } from '~/ui'; +import { DeleteIcon, EditIcon, SendIcon, SettingsIcon } from '~/ui/Icons'; +import { nullableProptype } from '~/util/proptypes'; import Delete from '../Address/Delete'; import Header from '../Account/Header'; @@ -40,20 +37,23 @@ import styles from './wallet.css'; class WalletContainer extends Component { static propTypes = { - isTest: PropTypes.any + netVersion: PropTypes.string.isRequired }; render () { - const { isTest, ...others } = this.props; + const { netVersion, ...others } = this.props; - if (isTest !== false && isTest !== true) { + if (netVersion === '0') { return ( <Loading size={ 4 } /> ); } return ( - <Wallet isTest={ isTest } { ...others } /> + <Wallet + netVersion={ netVersion } + { ...others } + /> ); } } @@ -66,7 +66,7 @@ class Wallet extends Component { static propTypes = { address: PropTypes.string.isRequired, balance: nullableProptype(PropTypes.object.isRequired), - isTest: PropTypes.bool.isRequired, + netVersion: PropTypes.string.isRequired, owned: PropTypes.bool.isRequired, setVisibleAccounts: PropTypes.func.isRequired, wallet: PropTypes.object.isRequired, @@ -166,18 +166,22 @@ class Wallet extends Component { <div> <br /> <p> - <span className={ styles.detail }>{ spent }<span className={ styles.eth } /></span> - <span>has been spent today, out of</span> - <span className={ styles.detail }>{ limit }<span className={ styles.eth } /></span> - <span>set as the daily limit, which has been reset on</span> - <span className={ styles.detail }>{ date.format('LL') }</span> + <FormattedMessage + id='wallet.details.spent' + defaultMessage='{spent} has been spent today, out of {limit} set as the daily limit, which has been reset on {date}' + values={ { + date: <span className={ styles.detail }>{ date.format('LL') }</span>, + limit: <span className={ styles.detail }>{ limit }<span className={ styles.eth } /></span>, + spent: <span className={ styles.detail }>{ spent }<span className={ styles.eth } /></span> + } } + /> </p> </div> ); } renderDetails () { - const { address, isTest, wallet } = this.props; + const { address, netVersion, wallet } = this.props; const { owners, require, confirmations, transactions } = wallet; if (!owners || !require) { @@ -190,19 +194,19 @@ class Wallet extends Component { return [ <WalletConfirmations + address={ address } + confirmations={ confirmations } + netVersion={ netVersion } key='confirmations' owners={ owners } require={ require } - confirmations={ confirmations } - isTest={ isTest } - address={ address } />, <WalletTransactions + address={ address } + netVersion={ netVersion } key='transactions' transactions={ transactions } - address={ address } - isTest={ isTest } /> ]; } @@ -216,10 +220,15 @@ class Wallet extends Component { if (owned) { buttons.push( <Button - key='transferFunds' - icon={ <ContentSend /> } - label='transfer' disabled={ !showTransferButton } + icon={ <SendIcon /> } + key='transferFunds' + label={ + <FormattedMessage + id='wallet.buttons.transfer' + defaultMessage='transfer' + /> + } onClick={ this.onTransferClick } /> ); @@ -227,18 +236,28 @@ class Wallet extends Component { buttons.push( <Button + icon={ <DeleteIcon /> } key='delete' - icon={ <ActionDelete /> } - label='delete' + label={ + <FormattedMessage + id='wallet.buttons.forget' + defaultMessage='forget' + /> + } onClick={ this.showDeleteDialog } /> ); buttons.push( <Button + icon={ <EditIcon /> } key='editmeta' - icon={ <ContentCreate /> } - label='edit' + label={ + <FormattedMessage + id='wallet.buttons.edit' + defaultMessage='edit' + /> + } onClick={ this.onEditClick } /> ); @@ -246,9 +265,14 @@ class Wallet extends Component { if (owned) { buttons.push( <Button - key='settings' icon={ <SettingsIcon /> } - label='settings' + key='settings' + label={ + <FormattedMessage + id='wallet.buttons.settings' + defaultMessage='settings' + /> + } onClick={ this.onSettingsClick } /> ); @@ -256,8 +280,13 @@ class Wallet extends Component { return ( <Actionbar - title='Wallet Management' buttons={ buttons } + title={ + <FormattedMessage + id='wallet.title' + defaultMessage='Wallet Management' + /> + } /> ); } @@ -360,7 +389,7 @@ function mapStateToProps (_, initProps) { const { address } = initProps.params; return (state) => { - const { isTest } = state.nodeStatus; + const { netVersion } = state.nodeStatus; const { accountsInfo = {}, accounts = {} } = state.personal; const { balances } = state.balances; const walletAccount = accounts[address] || accountsInfo[address] || null; @@ -376,7 +405,7 @@ function mapStateToProps (_, initProps) { return { address, balance, - isTest, + netVersion, owned, wallet, walletAccount diff --git a/js/src/views/Web/store.js b/js/src/views/Web/store.js index e62effb89..4eed3eaf3 100644 --- a/js/src/views/Web/store.js +++ b/js/src/views/Web/store.js @@ -20,7 +20,7 @@ import { parse as parseUrl } from 'url'; import { encodePath, encodeUrl } from '~/util/dapplink'; -const DEFAULT_URL = 'https://mkr.market'; +const DEFAULT_URL = 'https://oasisdex.com'; const LS_LAST_ADDRESS = '_parity::webLastAddress'; const hasProtocol = /^https?:\/\//; diff --git a/js/src/views/Web/web.spec.js b/js/src/views/Web/web.spec.js index 94a125afa..950806d0b 100644 --- a/js/src/views/Web/web.spec.js +++ b/js/src/views/Web/web.spec.js @@ -17,10 +17,9 @@ import { shallow } from 'enzyme'; import React from 'react'; +import { DEFAULT_URL } from './store'; import Web from './'; -const TEST_URL = 'https://mkr.market'; - let api; let component; @@ -30,7 +29,7 @@ function createApi () { return api; } -function render (url = TEST_URL) { +function render (url = DEFAULT_URL) { component = shallow( <Web params={ { url } } />, { diff --git a/js/src/views/WriteContract/writeContract.js b/js/src/views/WriteContract/writeContract.js index 8fdb55c6f..c4b08e3dd 100644 --- a/js/src/views/WriteContract/writeContract.js +++ b/js/src/views/WriteContract/writeContract.js @@ -15,6 +15,7 @@ // along with Parity. If not, see <http://www.gnu.org/licenses/>. import React, { PropTypes, Component } from 'react'; +import { FormattedMessage } from 'react-intl'; import { observer } from 'mobx-react'; import { MenuItem, Toggle } from 'material-ui'; import { connect } from 'react-redux'; @@ -22,13 +23,8 @@ import CircularProgress from 'material-ui/CircularProgress'; import moment from 'moment'; import { throttle } from 'lodash'; -import ContentClear from 'material-ui/svg-icons/content/clear'; -import SaveIcon from 'material-ui/svg-icons/content/save'; -import ListIcon from 'material-ui/svg-icons/action/view-list'; -import SettingsIcon from 'material-ui/svg-icons/action/settings'; -import SendIcon from 'material-ui/svg-icons/content/send'; - import { Actionbar, ActionbarExport, ActionbarImport, Button, Page, Select, Input } from '~/ui'; +import { CancelIcon, ListIcon, SaveIcon, SendIcon, SettingsIcon } from '~/ui/Icons'; import Editor from '~/ui/Editor'; import { DeployContract, SaveContract, LoadContract } from '~/modals'; @@ -97,7 +93,6 @@ class WriteContract extends Component { { this.renderDeployModal() } { this.renderSaveModal() } { this.renderLoadModal() } - { this.renderActionBar() } <Page className={ styles.page }> <div @@ -133,7 +128,12 @@ class WriteContract extends Component { className={ styles.parameters } style={ { flex: `${100 - size}%` } } > - <h2>Parameters</h2> + <h2> + <FormattedMessage + id='writeContract.title.parameters' + defaultMessage='Parameters' + /> + </h2> { this.renderParameters() } </div> </div> @@ -146,7 +146,12 @@ class WriteContract extends Component { const { selectedContract } = this.store; if (!selectedContract || !selectedContract.name) { - return 'New Solidity Contract'; + return ( + <FormattedMessage + id='writeContract.title.new' + defaultMessage='New Solidity Contract' + /> + ); } return ( @@ -154,9 +159,23 @@ class WriteContract extends Component { { selectedContract.name } <span className={ styles.timestamp } - title={ `saved @ ${(new Date(selectedContract.timestamp)).toISOString()}` } + title={ + <FormattedMessage + id='writeContract.title.saved' + defaultMessage='saved @ {timestamp}' + vaules={ { + timestamp: (new Date(selectedContract.timestamp)).toISOString() + } } + /> + } > - (saved { moment(selectedContract.timestamp).fromNow() }) + <FormattedMessage + id='writeContract.details.saved' + defaultMessage='(saved {timestamp})' + values={ { + timestamp: moment(selectedContract.timestamp).fromNow() + } } + /> </span> </span> ); @@ -176,20 +195,35 @@ class WriteContract extends Component { const buttons = [ <Button - icon={ <ContentClear /> } - label='New' + icon={ <CancelIcon /> } + label={ + <FormattedMessage + id='writeContract.buttons.new' + defaultMessage='New' + /> + } key='newContract' onClick={ this.store.handleNewContract } />, <Button icon={ <ListIcon /> } - label='Load' + label={ + <FormattedMessage + id='writeContract.buttons.load' + defaultMessage='Load' + /> + } key='loadContract' onClick={ this.store.handleOpenLoadModal } />, <Button icon={ <SaveIcon /> } - label='Save' + label={ + <FormattedMessage + id='writeContract.buttons.save' + defaultMessage='Save' + /> + } key='saveContract' onClick={ this.store.handleSaveContract } />, @@ -200,7 +234,12 @@ class WriteContract extends Component { />, <ActionbarImport key='importSourcecode' - title='Import Solidity code' + title={ + <FormattedMessage + id='writeContract.buttons.import' + defaultMessage='Import Solidity' + /> + } onConfirm={ this.store.handleImport } renderValidation={ this.renderImportValidation } /> @@ -208,7 +247,12 @@ class WriteContract extends Component { return ( <Actionbar - title='Write a Contract' + title={ + <FormattedMessage + id='writeContract.title.main' + defaultMessage='Write a Contract' + /> + } buttons={ buttons } /> ); @@ -231,8 +275,15 @@ class WriteContract extends Component { return ( <div className={ styles.panel }> <div className={ styles.centeredMessage }> - <p>Unfortuantely, an error occurred...</p> - <div className={ styles.error }>{ workerError.toString() }</div> + <p> + <FormattedMessage + id='writeContract.error.params' + defaultMessage='An error occurred with the following description' + /> + </p> + <div className={ styles.error }> + { workerError.toString() } + </div> </div> </div> ); @@ -245,7 +296,12 @@ class WriteContract extends Component { size={ 80 } thickness={ 5 } /> - <p>Loading...</p> + <p> + <FormattedMessage + id='writeContract.title.loading' + defaultMessage='Loading...' + /> + </p> </div> ); } @@ -260,7 +316,15 @@ class WriteContract extends Component { size={ 80 } thickness={ 5 } /> - <p>Loading Solidity { longVersion }</p> + <p> + <FormattedMessage + id='writeContract.title.solidity' + defaultMessage='Loading Solidity {version}' + values={ { + version: longVersion + } } + /> + </p> </div> </div> ); @@ -271,7 +335,12 @@ class WriteContract extends Component { <div> <Button icon={ <SettingsIcon /> } - label='Compile' + label={ + <FormattedMessage + id='writeContract.buttons.compile' + defaultMessage='Compile' + /> + } onClick={ this.store.handleCompile } primary={ false } disabled={ compiling } @@ -281,7 +350,12 @@ class WriteContract extends Component { ? ( <Button icon={ <SendIcon /> } - label='Deploy' + label={ + <FormattedMessage + id='writeContract.buttons.deploy' + defaultMessage='Deploy' + /> + } onClick={ this.store.handleOpenDeployModal } primary={ false } /> @@ -292,7 +366,12 @@ class WriteContract extends Component { <div className={ styles.toggles }> <div> <Toggle - label='Optimize' + label={ + <FormattedMessage + id='writeContract.buttons.optimise' + defaultMessage='Optimise' + /> + } labelPosition='right' onToggle={ this.store.handleOptimizeToggle } toggled={ this.store.optimize } @@ -300,7 +379,12 @@ class WriteContract extends Component { </div> <div> <Toggle - label='Auto-Compile' + label={ + <FormattedMessage + id='writeContract.buttons.autoCompile' + defaultMessage='Auto-Compile' + /> + } labelPosition='right' onToggle={ this.store.handleAutocompileToggle } toggled={ this.store.autocompile } @@ -324,7 +408,11 @@ class WriteContract extends Component { > { build.release - ? (<span className={ styles.big }>{ build.version }</span>) + ? ( + <span className={ styles.big }> + { build.version } + </span> + ) : build.longVersion } </MenuItem> @@ -333,7 +421,12 @@ class WriteContract extends Component { return ( <div> <Select - label='Select a Solidity version' + label={ + <FormattedMessage + id='writeContract.title.selectSolidity' + defaultMessage='Select a Solidity version' + /> + } value={ selectedBuild } onChange={ this.store.handleSelectBuild } > @@ -406,7 +499,12 @@ class WriteContract extends Component { size={ 80 } thickness={ 5 } /> - <p>Compiling...</p> + <p> + <FormattedMessage + id='writeContract.compiling.busy' + defaultMessage='Compiling...' + /> + </p> </div> ); } @@ -414,7 +512,12 @@ class WriteContract extends Component { if (!compiled) { return ( <div className={ styles.centeredMessage }> - <p>Please compile the source code.</p> + <p> + <FormattedMessage + id='writeContract.compiling.action' + defaultMessage='Please compile the source code.' + /> + </p> </div> ); } @@ -428,7 +531,12 @@ class WriteContract extends Component { if (contractKeys.length === 0) { return ( <div className={ styles.centeredMessage }> - <p>No contract has been found.</p> + <p> + <FormattedMessage + id='writeContract.error.noContract' + defaultMessage='No contract has been found.' + /> + </p> </div> ); } @@ -446,15 +554,24 @@ class WriteContract extends Component { return ( <div className={ styles.compilation }> <Select - label='Select a contract' + label={ + <FormattedMessage + id='writeContract.title.contract' + defaultMessage='Select a contract' + /> + } value={ contractIndex } onChange={ this.store.handleSelectContract } > { contractsList } </Select> { this.renderContract(contract) } - - <h4 className={ styles.messagesHeader }>Compiler messages</h4> + <h4 className={ styles.messagesHeader }> + <FormattedMessage + id='writeContract.title.messages' + defaultMessage='Compiler messages' + /> + </h4> { this.renderErrors() } </div> ); @@ -468,7 +585,12 @@ class WriteContract extends Component { ? ( <Input allowCopy - label='Metadata' + label={ + <FormattedMessage + id='writeContract.input.metadata' + defaultMessage='Metadata' + /> + } readOnly value={ contract.metadata } /> @@ -479,14 +601,24 @@ class WriteContract extends Component { <div> <Input allowCopy - label='ABI Interface' + label={ + <FormattedMessage + id='writeContract.input.abi' + defaultMessage='ABI Interface' + /> + } readOnly value={ abi } /> <Input allowCopy - label='Bytecode' + label={ + <FormattedMessage + id='writeContract.input.code' + defaultMessage='Bytecode' + /> + } readOnly value={ `0x${bytecode}` } /> @@ -516,7 +648,12 @@ class WriteContract extends Component { return ( <Input allowCopy - label='Swarm Metadata Hash' + label={ + <FormattedMessage + id='writeContract.input.swarm' + defaultMessage='Swarm Metadata Hash' + /> + } readOnly value={ `${hash}` } /> diff --git a/js/src/views/WriteContract/writeContractStore.js b/js/src/views/WriteContract/writeContractStore.js index bcea1c403..8774f6ce0 100644 --- a/js/src/views/WriteContract/writeContractStore.js +++ b/js/src/views/WriteContract/writeContractStore.js @@ -14,35 +14,62 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see <http://www.gnu.org/licenses/>. -import { action, observable, transaction } from 'mobx'; -import store from 'store'; import { debounce } from 'lodash'; +import { action, observable, transaction } from 'mobx'; +import React from 'react'; +import { FormattedMessage } from 'react-intl'; +import store from 'store'; import { sha3 } from '~/api/util/sha3'; import SolidityUtils from '~/util/solidity'; +const SOLIDITY_LIST_URL = 'https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/bin/list.json'; const WRITE_CONTRACT_STORE_KEY = '_parity::writeContractStore'; const SNIPPETS = { snippet0: { name: 'Token.sol', - description: 'Standard ERP20 Token Contract', - id: 'snippet0', sourcecode: require('raw-loader!../../contracts/snippets/token.sol') + description: ( + <FormattedMessage + id='writeContract.type.standardErc20' + defaultMessage='Standard ERC20 Token Contract' + /> + ), + id: 'snippet0', + sourcecode: require('raw-loader!../../contracts/snippets/token.sol') }, snippet1: { name: 'StandardToken.sol', - description: 'Implementation of ERP20 Token Contract', - id: 'snippet1', sourcecode: require('raw-loader!../../contracts/snippets/standard-token.sol') + description: ( + <FormattedMessage + id='writeContract.type.implementErc20' + defaultMessage='Implementation of ERC20 Token Contract' + /> + ), + id: 'snippet1', + sourcecode: require('raw-loader!../../contracts/snippets/standard-token.sol') }, snippet2: { name: 'HumanStandardToken.sol', - description: 'Implementation of the Human Token Contract', - id: 'snippet2', sourcecode: require('raw-loader!../../contracts/snippets/human-standard-token.sol') + description: ( + <FormattedMessage + id='writeContract.type.humanErc20' + defaultMessage='Implementation of the Human Token Contract' + /> + ), + id: 'snippet2', + sourcecode: require('raw-loader!../../contracts/snippets/human-standard-token.sol') }, snippet3: { name: 'Wallet.sol', - description: 'Implementation of a multisig Wallet', - id: 'snippet3', sourcecode: require('raw-loader!../../contracts/snippets/wallet.sol') + description: ( + <FormattedMessage + id='writeContract.type.multisig' + defaultMessage='Implementation of a multisig Wallet' + /> + ), + id: 'snippet3', + sourcecode: require('raw-loader!../../contracts/snippets/wallet.sol') } }; @@ -118,8 +145,8 @@ export default class WriteContractStore { } fetchSolidityVersions () { - return fetch('https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/bin/list.json') - .then((r) => r.json()) + return fetch(SOLIDITY_LIST_URL) + .then((response) => response.json()) .then((data) => { const { builds, releases, latestRelease } = data; let latestIndex = -1; diff --git a/js/src/views/index.js b/js/src/views/index.js index 0239d3d90..7460868ae 100644 --- a/js/src/views/index.js +++ b/js/src/views/index.js @@ -23,7 +23,6 @@ export Contract from './Contract'; export Contracts from './Contracts'; export Dapp from './Dapp'; export Dapps from './Dapps'; -export HistoryStore from './historyStore'; export Home from './Home'; export ParityBar from './ParityBar'; export Settings, { SettingsBackground, SettingsParity, SettingsProxy, SettingsViews } from './Settings'; diff --git a/js/test/mocha.config.js b/js/test/mocha.config.js index 0927daf3d..92fa79bbd 100644 --- a/js/test/mocha.config.js +++ b/js/test/mocha.config.js @@ -43,6 +43,7 @@ global.WebSocket = WebSocket; global.document = jsdom.jsdom('<!doctype html><html><body></body></html>'); global.window = document.defaultView; global.navigator = global.window.navigator; +global.location = global.window.location; // attach mocked localStorage onto the window as exposed by jsdom global.window.localStorage = global.localStorage; diff --git a/js/webpack/app.js b/js/webpack/app.js index 1e65a181b..14f2876db 100644 --- a/js/webpack/app.js +++ b/js/webpack/app.js @@ -62,8 +62,7 @@ module.exports = { { test: /\.js$/, exclude: /(node_modules)/, - // use: [ 'happypack/loader?id=js' ] - use: isProd ? 'babel-loader' : 'babel-loader?cacheDirectory=true' + use: [ 'happypack/loader?id=babel-js' ] }, { test: /\.js$/, @@ -96,17 +95,16 @@ module.exports = { test: /\.css$/, include: [ /src/ ], // exclude: [ /src\/dapps/ ], - loader: (isProd && !isEmbed) ? ExtractTextPlugin.extract([ - // 'style-loader', - 'css-loader?modules&sourceMap&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]', - 'postcss-loader' - ]) : undefined, - // use: [ 'happypack/loader?id=css' ] - use: (isProd && !isEmbed) ? undefined : [ - 'style-loader', - 'css-loader?modules&sourceMap&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]', - 'postcss-loader' - ] + loader: (isProd && !isEmbed) + ? ExtractTextPlugin.extract([ + // 'style-loader', + 'css-loader?modules&sourceMap&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]', + 'postcss-loader' + ]) + : undefined, + use: (isProd && !isEmbed) + ? undefined + : [ 'happypack/loader?id=css' ] }, { diff --git a/js/webpack/dev.server.js b/js/webpack/dev.server.js index 2410d3fa8..75ea7703a 100644 --- a/js/webpack/dev.server.js +++ b/js/webpack/dev.server.js @@ -48,7 +48,7 @@ let progressBar = { update: () => {} }; webpackConfig.plugins.push(new webpack.HotModuleReplacementPlugin()); webpackConfig.plugins.push(new webpack.NamedModulesPlugin()); - webpackConfig.plugins.push(new webpack.NoErrorsPlugin()); + webpackConfig.plugins.push(new webpack.NoEmitOnErrorsPlugin()); webpackConfig.plugins.push(new webpack.ProgressPlugin( (percentage) => progressBar.update(percentage) diff --git a/js/webpack/shared.js b/js/webpack/shared.js index 80e79a9d2..3f5fcd66f 100644 --- a/js/webpack/shared.js +++ b/js/webpack/shared.js @@ -17,7 +17,7 @@ const webpack = require('webpack'); const path = require('path'); const fs = require('fs'); -// const HappyPack = require('happypack'); +const HappyPack = require('happypack'); const postcssImport = require('postcss-import'); const postcssNested = require('postcss-nested'); @@ -85,32 +85,21 @@ function getPlugins (_isProd = isProd) { format: '[:msg] [:bar] ' + ':percent' + ' (:elapsed seconds)' }), - // NB: HappyPack is not yet working with Webpack 2... (as of Nov. 26) + new HappyPack({ + id: 'css', + threads: 4, + loaders: [ + 'style-loader', + 'css-loader?modules&sourceMap&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]', + 'postcss-loader' + ] + }), - // new HappyPack({ - // id: 'css', - // threads: 4, - // loaders: [ - // 'style-loader', - // 'css-loader?modules&sourceMap&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]', - // 'postcss-loader' - // ] - // }), - - // new HappyPack({ - // id: 'js', - // threads: 4, - // loaders: _isProd ? ['babel'] : [ - // 'react-hot-loader', - // 'babel-loader?cacheDirectory=true' - // ] - // }), - - // new HappyPack({ - // id: 'babel', - // threads: 4, - // loaders: ['babel-loader'] - // }), + new HappyPack({ + id: 'babel-js', + threads: 4, + loaders: [ isProd ? 'babel-loader' : 'babel-loader?cacheDirectory=true' ] + }), new webpack.DefinePlugin({ 'process.env': { diff --git a/js/webpack/vendor.js b/js/webpack/vendor.js index b67451305..5081a894f 100644 --- a/js/webpack/vendor.js +++ b/js/webpack/vendor.js @@ -44,7 +44,8 @@ let modules = [ 'recharts', 'redux', 'redux-thunk', - 'scryptsy' + 'scryptsy', + 'zxcvbn' ]; module.exports = { diff --git a/json/src/spec/authority_round.rs b/json/src/spec/authority_round.rs index 321463d24..35f6298ae 100644 --- a/json/src/spec/authority_round.rs +++ b/json/src/spec/authority_round.rs @@ -17,6 +17,7 @@ //! Authority params deserialization. use uint::Uint; +use hash::Address; use super::ValidatorSet; /// Authority params deserialization. @@ -33,6 +34,8 @@ pub struct AuthorityRoundParams { /// Block reward. #[serde(rename="blockReward")] pub block_reward: Option<Uint>, + /// Address of the registrar contract. + pub registrar: Option<Address>, /// Starting step. Determined automatically if not specified. /// To be used for testing only. #[serde(rename="startStep")] diff --git a/json/src/spec/engine.rs b/json/src/spec/engine.rs index 896e48010..ef54c4ab1 100644 --- a/json/src/spec/engine.rs +++ b/json/src/spec/engine.rs @@ -16,7 +16,7 @@ //! Engine deserialization. -use super::{Ethash, BasicAuthority, AuthorityRound, Tendermint}; +use super::{Ethash, InstantSeal, BasicAuthority, AuthorityRound, Tendermint}; /// Engine deserialization. #[derive(Debug, PartialEq, Deserialize)] @@ -26,7 +26,7 @@ pub enum Engine { Null, /// Instantly sealing engine. #[serde(rename="instantSeal")] - InstantSeal, + InstantSeal(InstantSeal), /// Ethash engine. Ethash(Ethash), /// BasicAuthority engine. @@ -55,12 +55,10 @@ mod tests { assert_eq!(Engine::Null, deserialized); let s = r#"{ - "instantSeal": null + "instantSeal": { "params": {} } }"#; - let deserialized: Engine = serde_json::from_str(s).unwrap(); - assert_eq!(Engine::InstantSeal, deserialized); - + let _deserialized: Engine = serde_json::from_str(s).unwrap(); let s = r#"{ "Ethash": { diff --git a/json/src/spec/instant_seal.rs b/json/src/spec/instant_seal.rs new file mode 100644 index 000000000..168b51c68 --- /dev/null +++ b/json/src/spec/instant_seal.rs @@ -0,0 +1,50 @@ +// Copyright 2015-2017 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/>. + +//! Instant params deserialization. + +use hash::Address; + +/// Instant params deserialization. +#[derive(Debug, PartialEq, Deserialize)] +pub struct InstantSealParams { + /// Address of the registrar contract. + pub registrar: Option<Address>, +} + +/// Instant engine deserialization. +#[derive(Debug, PartialEq, Deserialize)] +pub struct InstantSeal { + /// Instant Seal params. + pub params: InstantSealParams, +} + +#[cfg(test)] +mod tests { + use serde_json; + use spec::instant_seal::InstantSeal; + + #[test] + fn instant_seal_deserialization() { + let s = r#"{ + "params": { + "registrar": "0xc6d9d2cd449a754c494264e1809c50e34d64562b" + } + }"#; + + let _deserialized: InstantSeal = serde_json::from_str(s).unwrap(); + } +} diff --git a/json/src/spec/mod.rs b/json/src/spec/mod.rs index bc7e751af..63c26a010 100644 --- a/json/src/spec/mod.rs +++ b/json/src/spec/mod.rs @@ -26,6 +26,7 @@ pub mod engine; pub mod state; pub mod ethash; pub mod validator_set; +pub mod instant_seal; pub mod basic_authority; pub mod authority_round; pub mod tendermint; @@ -40,6 +41,7 @@ pub use self::engine::Engine; pub use self::state::State; pub use self::ethash::{Ethash, EthashParams}; pub use self::validator_set::ValidatorSet; +pub use self::instant_seal::{InstantSeal, InstantSealParams}; pub use self::basic_authority::{BasicAuthority, BasicAuthorityParams}; pub use self::authority_round::{AuthorityRound, AuthorityRoundParams}; pub use self::tendermint::{Tendermint, TendermintParams}; diff --git a/json/src/spec/tendermint.rs b/json/src/spec/tendermint.rs index 4eaefe886..16c2d7255 100644 --- a/json/src/spec/tendermint.rs +++ b/json/src/spec/tendermint.rs @@ -17,6 +17,7 @@ //! Tendermint params deserialization. use uint::Uint; +use hash::Address; use super::ValidatorSet; /// Tendermint params deserialization. @@ -42,6 +43,8 @@ pub struct TendermintParams { /// Block reward. #[serde(rename="blockReward")] pub block_reward: Option<Uint>, + /// Address of the registrar contract. + pub registrar: Option<Address>, } /// Tendermint engine deserialization. diff --git a/logger/Cargo.toml b/logger/Cargo.toml index 091f9fefa..e0004cd48 100644 --- a/logger/Cargo.toml +++ b/logger/Cargo.toml @@ -1,7 +1,7 @@ [package] description = "Ethcore client." name = "ethcore-logger" -version = "1.6.0" +version = "1.7.0" license = "GPL-3.0" authors = ["Parity Technologies <admin@parity.io>"] diff --git a/mac/Parity.pkgproj b/mac/Parity.pkgproj index caecc7c0d..cc7810dba 100755 --- a/mac/Parity.pkgproj +++ b/mac/Parity.pkgproj @@ -462,7 +462,7 @@ <key>OVERWRITE_PERMISSIONS</key> <false/> <key>VERSION</key> - <string>1.6.0</string> + <string>1.7.0</string> </dict> <key>UUID</key> <string>2DCD5B81-7BAF-4DA1-9251-6274B089FD36</string> diff --git a/nsis/installer.nsi b/nsis/installer.nsi index f40765944..cc4a0243b 100644 --- a/nsis/installer.nsi +++ b/nsis/installer.nsi @@ -9,7 +9,7 @@ !define COMPANYNAME "Ethcore" !define DESCRIPTION "Fast, light, robust Ethereum implementation" !define VERSIONMAJOR 1 -!define VERSIONMINOR 6 +!define VERSIONMINOR 7 !define VERSIONBUILD 0 !define ARGS "--warp" !define FIRST_START_ARGS "ui --warp --mode=passive" diff --git a/parity/cli/config.full.toml b/parity/cli/config.full.toml index 60670750b..980457887 100644 --- a/parity/cli/config.full.toml +++ b/parity/cli/config.full.toml @@ -27,7 +27,6 @@ interface = "127.0.0.1" path = "$HOME/.parity/signer" [network] -disable = false port = 30303 min_peers = 25 max_peers = 50 diff --git a/parity/cli/config.toml b/parity/cli/config.toml index 9b356a811..227578b13 100644 --- a/parity/cli/config.toml +++ b/parity/cli/config.toml @@ -12,7 +12,6 @@ password = ["passwdfile path"] disable = true [network] -disable = false warp = false discovery = true nat = "any" diff --git a/parity/cli/mod.rs b/parity/cli/mod.rs index 0c057ff30..cb256f0b7 100644 --- a/parity/cli/mod.rs +++ b/parity/cli/mod.rs @@ -119,8 +119,8 @@ usage! { flag_ui_no_validation: bool = false, or |_| None, // -- Networking Options - flag_warp: bool = false, - or |c: &Config| otry!(c.network).warp.clone(), + flag_no_warp: bool = false, + or |c: &Config| otry!(c.network).warp.clone().map(|w| !w), flag_port: u16 = 30303u16, or |c: &Config| otry!(c.network).port.clone(), flag_min_peers: u16 = 25u16, @@ -181,6 +181,8 @@ usage! { or |c: &Config| otry!(c.dapps).interface.clone(), flag_dapps_hosts: String = "none", or |c: &Config| otry!(c.dapps).hosts.as_ref().map(|vec| vec.join(",")), + flag_dapps_cors: Option<String> = None, + or |c: &Config| otry!(c.dapps).cors.clone().map(Some), flag_dapps_path: String = "$BASE/dapps", or |c: &Config| otry!(c.dapps).path.clone(), flag_dapps_user: Option<String> = None, @@ -330,6 +332,7 @@ usage! { // Values with optional default value. flag_base_path: Option<String>, display dir::default_data_path(), or |c: &Config| otry!(c.parity).base_path.clone().map(Some), flag_db_path: Option<String>, display dir::CHAINS_PATH, or |c: &Config| otry!(c.parity).db_path.clone().map(Some), + flag_warp: Option<bool>, display true, or |c: &Config| Some(otry!(c.network).warp.clone()), } } @@ -388,7 +391,6 @@ struct Ui { #[derive(Default, Debug, PartialEq, RustcDecodable)] struct Network { - disable: Option<bool>, warp: Option<bool>, port: Option<u16>, min_peers: Option<u16>, @@ -428,6 +430,7 @@ struct Dapps { port: Option<u16>, interface: Option<String>, hosts: Option<Vec<String>>, + cors: Option<String>, path: Option<String>, user: Option<String>, pass: Option<String>, @@ -639,7 +642,7 @@ mod tests { flag_ui_no_validation: false, // -- Networking Options - flag_warp: true, + flag_no_warp: false, flag_port: 30303u16, flag_min_peers: 25u16, flag_max_peers: 50u16, @@ -674,6 +677,7 @@ mod tests { flag_dapps_port: 8080u16, flag_dapps_interface: "local".into(), flag_dapps_hosts: "none".into(), + flag_dapps_cors: None, flag_dapps_path: "$HOME/.parity/dapps".into(), flag_dapps_user: Some("test_user".into()), flag_dapps_pass: Some("test_pass".into()), @@ -780,6 +784,7 @@ mod tests { flag_etherbase: None, flag_extradata: None, flag_cache: None, + flag_warp: Some(true), // -- Miscellaneous Options flag_version: false, @@ -838,7 +843,6 @@ mod tests { path: None, }), network: Some(Network { - disable: Some(false), warp: Some(false), port: None, min_peers: Some(10), @@ -873,6 +877,7 @@ mod tests { path: None, interface: None, hosts: None, + cors: None, user: Some("username".into()), pass: Some("password".into()) }), diff --git a/parity/cli/usage.txt b/parity/cli/usage.txt index dee6dd788..da7a72db9 100644 --- a/parity/cli/usage.txt +++ b/parity/cli/usage.txt @@ -59,7 +59,7 @@ Operating Options: --chain CHAIN Specify the blockchain type. CHAIN may be either a JSON chain specification file or olympic, frontier, homestead, mainnet, morden, ropsten, classic, expanse, - testnet or dev (default: {flag_chain}). + testnet, kovan or dev (default: {flag_chain}). -d --base-path PATH Specify the base data storage path. (default: {flag_base_path}). --db-path PATH Specify the database directory path @@ -97,7 +97,7 @@ UI Options: development. (default: {flag_ui_no_validation}) Networking Options: - --warp Enable syncing from the snapshot over the network. (default: {flag_warp}) + --no-warp Disable syncing from the snapshot over the network. (default: {flag_no_warp}) --port PORT Override the port on which the node should listen (default: {flag_port}). --min-peers NUM Try to maintain at least NUM peers (default: {flag_min_peers}). @@ -164,6 +164,8 @@ API and Console Options: is additional security against some attack vectors. Special options: "all", "none", (default: {flag_dapps_hosts}). + --dapps-cors URL Specify CORS headers for Dapps server APIs. + (default: {flag_dapps_cors:?}) --dapps-user USERNAME Specify username for Dapps server. It will be used in HTTP Basic Authentication Scheme. If --dapps-pass is not specified you will be @@ -372,8 +374,7 @@ Legacy Options: to be the same as Geth's. Overrides the --ipc-path and --ipcpath options. Alters RPCs to reflect Geth bugs. Includes the personal_ RPC by default. - --testnet Geth-compatible testnet mode. Equivalent to --chain - testnet --keys-path $HOME/parity/testnet-keys. + --testnet Testnet mode. Equivalent to --chain testnet. Overrides the --keys-path option. --import-geth-keys Attempt to import keys from Geth client. --datadir PATH Equivalent to --base-path PATH. @@ -386,6 +387,7 @@ Legacy Options: -w --webapp Does nothing; dapps server is on by default now. --dapps-off Equivalent to --no-dapps. --rpc Does nothing; JSON-RPC is on by default now. + --warp Does nothing; Warp sync is on by default. (default: {flag_warp}) --rpcaddr IP Equivalent to --jsonrpc-interface IP. --rpcport PORT Equivalent to --jsonrpc-port PORT. --rpcapi APIS Equivalent to --jsonrpc-apis APIS. diff --git a/parity/configuration.rs b/parity/configuration.rs index 8a5c64920..c0756a771 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -15,12 +15,13 @@ // along with Parity. If not, see <http://www.gnu.org/licenses/>. use std::time::Duration; -use std::io::Read; +use std::io::{Read, Write, stderr}; use std::net::SocketAddr; use std::path::{Path, PathBuf}; use std::cmp::max; use cli::{Args, ArgsError}; use util::{Hashable, H256, U256, Uint, Bytes, version_data, Address}; +use util::journaldb::Algorithm; use util::log::Colour; use ethsync::{NetworkConfiguration, is_valid_node_url, AllowIP}; use ethcore::ethstore::ethkey::Secret; @@ -33,7 +34,7 @@ use ethcore_rpc::NetworkSettings; use cache::CacheConfig; use helpers::{to_duration, to_mode, to_block_id, to_u256, to_pending_set, to_price, replace_home, replace_home_for_db, geth_ipc_path, parity_ipc_path, to_bootnodes, to_addresses, to_address, to_gas_limit, to_queue_strategy}; -use params::{SpecType, ResealPolicy, AccountsConfig, GasPricerConfig, MinerExtras}; +use params::{SpecType, ResealPolicy, AccountsConfig, GasPricerConfig, MinerExtras, Pruning, Switch}; use ethcore_logger::Config as LogConfig; use dir::{self, Directories, default_hypervisor_path, default_local_path, default_data_path}; use dapps::Configuration as DappsConfiguration; @@ -115,7 +116,14 @@ impl Configuration { let fat_db = self.args.flag_fat_db.parse()?; let compaction = self.args.flag_db_compaction.parse()?; let wal = !self.args.flag_fast_and_loose; - let warp_sync = self.args.flag_warp; + match self.args.flag_warp { + // Logging is not initialized yet, so we print directly to stderr + Some(true) if fat_db == Switch::On => writeln!(&mut stderr(), "Warning: Warp Sync is disabled because Fat DB is turned on").expect("Error writing to stderr"), + Some(true) if tracing == Switch::On => writeln!(&mut stderr(), "Warning: Warp Sync is disabled because tracing is turned on").expect("Error writing to stderr"), + Some(true) if pruning == Pruning::Specific(Algorithm::Archive) => writeln!(&mut stderr(), "Warning: Warp Sync is disabled because pruning mode is set to archive").expect("Error writing to stderr"), + _ => {}, + }; + let warp_sync = !self.args.flag_no_warp && fat_db != Switch::On && tracing != Switch::On && pruning != Pruning::Specific(Algorithm::Archive); let geth_compatibility = self.args.flag_geth; let ui_address = self.ui_port().map(|port| (self.ui_interface(), port)); let dapps_conf = self.dapps_config(); @@ -430,7 +438,7 @@ impl Configuration { fn chain(&self) -> String { if self.args.flag_testnet { - "ropsten".to_owned() + "testnet".to_owned() } else { self.args.flag_chain.clone() } @@ -538,6 +546,7 @@ impl Configuration { interface: self.dapps_interface(), port: self.args.flag_dapps_port, hosts: self.dapps_hosts(), + cors: self.dapps_cors(), user: self.args.flag_dapps_user.clone(), pass: self.args.flag_dapps_pass.clone(), dapps_path: PathBuf::from(self.directories().dapps), @@ -714,6 +723,10 @@ impl Configuration { Self::cors(self.args.flag_ipfs_api_cors.as_ref()) } + fn dapps_cors(&self) -> Option<Vec<String>> { + Self::cors(self.args.flag_dapps_cors.as_ref()) + } + fn hosts(hosts: &str) -> Option<Vec<String>> { match hosts { "none" => return Some(Vec::new()), @@ -820,8 +833,8 @@ impl Configuration { let secretstore_path = replace_home(&data_path, &self.args.flag_secretstore_path); let ui_path = replace_home(&data_path, &self.args.flag_ui_path); - if self.args.flag_geth && !cfg!(windows) { - let geth_root = if self.args.flag_testnet { path::ethereum::test() } else { path::ethereum::default() }; + if self.args.flag_geth && !cfg!(windows) { + let geth_root = if self.chain() == "testnet".to_owned() { path::ethereum::test() } else { path::ethereum::default() }; ::std::fs::create_dir_all(geth_root.as_path()).unwrap_or_else( |e| warn!("Failed to create '{}' for geth mode: {}", &geth_root.to_str().unwrap(), e)); } @@ -1144,7 +1157,7 @@ mod tests { ipc_conf: Default::default(), net_conf: default_network_config(), network_id: None, - warp_sync: false, + warp_sync: true, acc_conf: Default::default(), gas_pricer: Default::default(), miner_extras: Default::default(), @@ -1223,7 +1236,7 @@ mod tests { // then assert_eq!(conf.network_settings(), NetworkSettings { name: "testname".to_owned(), - chain: "ropsten".to_owned(), + chain: "testnet".to_owned(), network_port: 30303, rpc_enabled: true, rpc_interface: "local".to_owned(), diff --git a/parity/dapps.rs b/parity/dapps.rs index 572d32b48..b9094c16d 100644 --- a/parity/dapps.rs +++ b/parity/dapps.rs @@ -33,6 +33,7 @@ pub struct Configuration { pub interface: String, pub port: u16, pub hosts: Option<Vec<String>>, + pub cors: Option<Vec<String>>, pub user: Option<String>, pub pass: Option<String>, pub dapps_path: PathBuf, @@ -48,6 +49,7 @@ impl Default for Configuration { interface: "127.0.0.1".into(), port: 8080, hosts: Some(Vec::new()), + cors: None, user: None, pass: None, dapps_path: replace_home(&data_dir, "$BASE/dapps").into(), @@ -93,6 +95,7 @@ pub fn new(configuration: Configuration, deps: Dependencies) -> Result<Option<We configuration.extra_dapps, &addr, configuration.hosts, + configuration.cors, auth, configuration.all_apis, )?)) @@ -114,6 +117,7 @@ mod server { _extra_dapps: Vec<PathBuf>, _url: &SocketAddr, _allowed_hosts: Option<Vec<String>>, + _cors: Option<Vec<String>>, _auth: Option<(String, String)>, _all_apis: bool, ) -> Result<WebappServer, String> { @@ -147,6 +151,7 @@ mod server { extra_dapps: Vec<PathBuf>, url: &SocketAddr, allowed_hosts: Option<Vec<String>>, + cors: Option<Vec<String>>, auth: Option<(String, String)>, all_apis: bool, ) -> Result<WebappServer, String> { @@ -167,7 +172,8 @@ mod server { .web_proxy_tokens(Arc::new(move |token| signer.is_valid_web_proxy_access_token(&token))) .extra_dapps(&extra_dapps) .signer_address(deps.signer.address()) - .allowed_hosts(allowed_hosts); + .allowed_hosts(allowed_hosts) + .extra_cors_headers(cors); let api_set = if all_apis { warn!("{}", Colour::Red.bold().paint("*** INSECURE *** Running Dapps with all APIs exposed.")); diff --git a/parity/migration.rs b/parity/migration.rs index c2d5c0797..445724325 100644 --- a/parity/migration.rs +++ b/parity/migration.rs @@ -146,7 +146,7 @@ pub fn default_migration_settings(compaction_profile: &CompactionProfile) -> Mig fn consolidated_database_migrations(compaction_profile: &CompactionProfile) -> Result<MigrationManager, Error> { let mut manager = MigrationManager::new(default_migration_settings(compaction_profile)); manager.add_migration(migrations::ToV10::new()).map_err(|_| Error::MigrationImpossible)?; - manager.add_migration(migrations::ToV11::default()).map_err(|_| Error::MigrationImpossible)?; + manager.add_migration(migrations::TO_V11).map_err(|_| Error::MigrationImpossible)?; Ok(manager) } @@ -201,6 +201,10 @@ fn migrate_database(version: u32, db_path: PathBuf, mut migrations: MigrationMan // migrate old database to the new one let temp_path = migrations.execute(&db_path, version)?; + // completely in-place migration leads to the paths being equal. + // in that case, no need to shuffle directories. + if temp_path == db_path { return Ok(()) } + // create backup fs::rename(&db_path, &backup_path)?; @@ -212,9 +216,7 @@ fn migrate_database(version: u32, db_path: PathBuf, mut migrations: MigrationMan } // remove backup - fs::remove_dir_all(&backup_path)?; - - Ok(()) + fs::remove_dir_all(&backup_path).map_err(Into::into) } fn exists(path: &Path) -> bool { diff --git a/parity/params.rs b/parity/params.rs index 4e3256bf1..85019b3e7 100644 --- a/parity/params.rs +++ b/parity/params.rs @@ -26,9 +26,10 @@ use user_defaults::UserDefaults; #[derive(Debug, PartialEq)] pub enum SpecType { - Mainnet, + Foundation, Morden, Ropsten, + Kovan, Olympic, Classic, Expanse, @@ -38,7 +39,7 @@ pub enum SpecType { impl Default for SpecType { fn default() -> Self { - SpecType::Mainnet + SpecType::Foundation } } @@ -47,10 +48,11 @@ impl str::FromStr for SpecType { fn from_str(s: &str) -> Result<Self, Self::Err> { let spec = match s { - "frontier" | "homestead" | "mainnet" => SpecType::Mainnet, + "foundation" | "frontier" | "homestead" | "mainnet" => SpecType::Foundation, "frontier-dogmatic" | "homestead-dogmatic" | "classic" => SpecType::Classic, "morden" | "classic-testnet" => SpecType::Morden, - "ropsten" | "testnet" => SpecType::Ropsten, + "ropsten" => SpecType::Ropsten, + "kovan" | "testnet" => SpecType::Kovan, "olympic" => SpecType::Olympic, "expanse" => SpecType::Expanse, "dev" => SpecType::Dev, @@ -63,12 +65,13 @@ impl str::FromStr for SpecType { impl fmt::Display for SpecType { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str(match *self { - SpecType::Mainnet => "homestead", + SpecType::Foundation => "foundation", SpecType::Morden => "morden", SpecType::Ropsten => "ropsten", SpecType::Olympic => "olympic", SpecType::Classic => "classic", SpecType::Expanse => "expanse", + SpecType::Kovan => "kovan", SpecType::Dev => "dev", SpecType::Custom(ref custom) => custom, }) @@ -78,12 +81,13 @@ impl fmt::Display for SpecType { impl SpecType { pub fn spec(&self) -> Result<Spec, String> { match *self { - SpecType::Mainnet => Ok(ethereum::new_frontier()), + SpecType::Foundation => Ok(ethereum::new_foundation()), SpecType::Morden => Ok(ethereum::new_morden()), SpecType::Ropsten => Ok(ethereum::new_ropsten()), SpecType::Olympic => Ok(ethereum::new_olympic()), SpecType::Classic => Ok(ethereum::new_classic()), SpecType::Expanse => Ok(ethereum::new_expanse()), + SpecType::Kovan => Ok(ethereum::new_kovan()), SpecType::Dev => Ok(Spec::new_instant()), SpecType::Custom(ref filename) => { let file = fs::File::open(filename).map_err(|_| "Could not load specification file.")?; @@ -317,10 +321,12 @@ mod tests { #[test] fn test_spec_type_parsing() { - assert_eq!(SpecType::Mainnet, "frontier".parse().unwrap()); - assert_eq!(SpecType::Mainnet, "homestead".parse().unwrap()); - assert_eq!(SpecType::Mainnet, "mainnet".parse().unwrap()); - assert_eq!(SpecType::Ropsten, "testnet".parse().unwrap()); + assert_eq!(SpecType::Foundation, "frontier".parse().unwrap()); + assert_eq!(SpecType::Foundation, "homestead".parse().unwrap()); + assert_eq!(SpecType::Foundation, "mainnet".parse().unwrap()); + assert_eq!(SpecType::Foundation, "foundation".parse().unwrap()); + assert_eq!(SpecType::Kovan, "testnet".parse().unwrap()); + assert_eq!(SpecType::Kovan, "kovan".parse().unwrap()); assert_eq!(SpecType::Morden, "morden".parse().unwrap()); assert_eq!(SpecType::Ropsten, "ropsten".parse().unwrap()); assert_eq!(SpecType::Olympic, "olympic".parse().unwrap()); @@ -330,17 +336,18 @@ mod tests { #[test] fn test_spec_type_default() { - assert_eq!(SpecType::Mainnet, SpecType::default()); + assert_eq!(SpecType::Foundation, SpecType::default()); } #[test] fn test_spec_type_display() { - assert_eq!(format!("{}", SpecType::Mainnet), "homestead"); + assert_eq!(format!("{}", SpecType::Foundation), "foundation"); assert_eq!(format!("{}", SpecType::Ropsten), "ropsten"); assert_eq!(format!("{}", SpecType::Morden), "morden"); assert_eq!(format!("{}", SpecType::Olympic), "olympic"); assert_eq!(format!("{}", SpecType::Classic), "classic"); assert_eq!(format!("{}", SpecType::Expanse), "expanse"); + assert_eq!(format!("{}", SpecType::Kovan), "kovan"); assert_eq!(format!("{}", SpecType::Dev), "dev"); assert_eq!(format!("{}", SpecType::Custom("foo/bar".into())), "foo/bar"); } diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index 91058b990..13ce8962f 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -1,7 +1,7 @@ [package] description = "Ethcore jsonrpc" name = "ethcore-rpc" -version = "1.6.0" +version = "1.7.0" license = "GPL-3.0" authors = ["Parity Technologies <admin@parity.io>"] diff --git a/rpc/rpctest/Cargo.toml b/rpc/rpctest/Cargo.toml index 5951ef380..1d37ee264 100644 --- a/rpc/rpctest/Cargo.toml +++ b/rpc/rpctest/Cargo.toml @@ -1,7 +1,7 @@ [package] description = "Rpc test client." name = "rpctest" -version = "1.6.0" +version = "1.7.0" license = "GPL-3.0" authors = ["Parity Technologies <admin@parity.io>"] diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs index 47143ac75..6df8f5278 100644 --- a/rpc/src/v1/impls/eth.rs +++ b/rpc/src/v1/impls/eth.rs @@ -176,10 +176,15 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> EthClient<C, SN, S, M, EM> where None => { return Ok(None); } }; + let size = client.block(BlockId::Hash(uncle.hash())) + .map(|block| block.into_inner().len()) + .map(U256::from) + .map(Into::into); + let block = RichBlock { block: Block { hash: Some(uncle.hash().into()), - size: None, + size: size, parent_hash: uncle.parent_hash().clone().into(), uncles_hash: uncle.uncles_hash().clone().into(), author: uncle.author().clone().into(), diff --git a/rpc/src/v1/types/block.rs b/rpc/src/v1/types/block.rs index 7c06b9de2..e63137520 100644 --- a/rpc/src/v1/types/block.rs +++ b/rpc/src/v1/types/block.rs @@ -185,4 +185,42 @@ mod tests { assert_eq!(serialized_block, r#"{"hash":"0x0000000000000000000000000000000000000000000000000000000000000000","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","sha3Uncles":"0x0000000000000000000000000000000000000000000000000000000000000000","author":"0x0000000000000000000000000000000000000000","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","number":"0x0","gasUsed":"0x0","gasLimit":"0x0","extraData":"0x","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","timestamp":"0x0","difficulty":"0x0","totalDifficulty":"0x0","sealFields":["0x","0x"],"uncles":[],"transactions":[],"size":"0x45"}"#); assert_eq!(serialized_rich_block, r#"{"author":"0x0000000000000000000000000000000000000000","difficulty":"0x0","extraData":"0x","gasLimit":"0x0","gasUsed":"0x0","hash":"0x0000000000000000000000000000000000000000000000000000000000000000","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","miner":"0x0000000000000000000000000000000000000000","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","number":"0x0","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","sealFields":["0x","0x"],"sha3Uncles":"0x0000000000000000000000000000000000000000000000000000000000000000","size":"0x45","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","timestamp":"0x0","totalDifficulty":"0x0","transactions":[],"transactionsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","uncles":[]}"#); } + + #[test] + fn none_size_null() { + let block = Block { + hash: Some(H256::default()), + parent_hash: H256::default(), + uncles_hash: H256::default(), + author: H160::default(), + miner: H160::default(), + state_root: H256::default(), + transactions_root: H256::default(), + receipts_root: H256::default(), + number: Some(U256::default()), + gas_used: U256::default(), + gas_limit: U256::default(), + extra_data: Bytes::default(), + logs_bloom: H2048::default(), + timestamp: U256::default(), + difficulty: U256::default(), + total_difficulty: Some(U256::default()), + seal_fields: vec![Bytes::default(), Bytes::default()], + uncles: vec![], + transactions: BlockTransactions::Hashes(vec![].into()), + size: None, + }; + let serialized_block = serde_json::to_string(&block).unwrap(); + let rich_block = RichBlock { + block: block, + extra_info: map![ + "mixHash".into() => format!("0x{:?}", H256::default()), + "nonce".into() => format!("0x{:?}", H64::default()) + ], + }; + let serialized_rich_block = serde_json::to_string(&rich_block).unwrap(); + + assert_eq!(serialized_block, r#"{"hash":"0x0000000000000000000000000000000000000000000000000000000000000000","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","sha3Uncles":"0x0000000000000000000000000000000000000000000000000000000000000000","author":"0x0000000000000000000000000000000000000000","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","number":"0x0","gasUsed":"0x0","gasLimit":"0x0","extraData":"0x","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","timestamp":"0x0","difficulty":"0x0","totalDifficulty":"0x0","sealFields":["0x","0x"],"uncles":[],"transactions":[],"size":null}"#); + assert_eq!(serialized_rich_block, r#"{"author":"0x0000000000000000000000000000000000000000","difficulty":"0x0","extraData":"0x","gasLimit":"0x0","gasUsed":"0x0","hash":"0x0000000000000000000000000000000000000000000000000000000000000000","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","miner":"0x0000000000000000000000000000000000000000","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","number":"0x0","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","sealFields":["0x","0x"],"sha3Uncles":"0x0000000000000000000000000000000000000000000000000000000000000000","size":null,"stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","timestamp":"0x0","totalDifficulty":"0x0","transactions":[],"transactionsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","uncles":[]}"#); + } } diff --git a/scripts/contractABI.js b/scripts/contractABI.js index 198055c12..4db0fe3a1 100644 --- a/scripts/contractABI.js +++ b/scripts/contractABI.js @@ -41,11 +41,11 @@ ${convertContract(name, json, prefs)} function convertContract(name, json, prefs) { return `${prefs._pub ? "pub " : ""}struct ${name} { contract: ethabi::Contract, - address: util::Address, + pub address: util::Address, ${prefs._explicit_do_call ? "" : `do_call: Box<Fn(util::Address, Vec<u8>) -> Result<Vec<u8>, String> + Send${prefs._sync ? " + Sync " : ""}+ 'static>,`} } impl ${name} { - pub fn new${prefs._explicit_do_call ? "" : "<F>"}(address: util::Address${prefs._explicit_do_call ? "" : `", do_call: F"`}) -> Self + pub fn new${prefs._explicit_do_call ? "" : "<F>"}(address: util::Address${prefs._explicit_do_call ? "" : `, do_call: F`}) -> Self ${prefs._explicit_do_call ? "" : `where F: Fn(util::Address, Vec<u8>) -> Result<Vec<u8>, String> + Send ${prefs._sync ? "+ Sync " : ""}+ 'static`} { ${name} { contract: ethabi::Contract::new(ethabi::Interface::load(b"${JSON.stringify(json.filter(a => a.type == 'function')).replaceAll('"', '\\"')}").expect("JSON is autogenerated; qed")), @@ -233,19 +233,23 @@ function convertFunction(json, _prefs) { let prefs = {"_pub": true, "_": {"_client": {"string": true}, "_platform": {"string": true}}, "_sync": true}; // default contract json ABI let jsonabi = [{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"","type":"address[]"}],"payable":false,"type":"function"}]; +// default name +let name = 'Contract'; // parse command line options for (let i = 1; i < process.argv.length; ++i) { let arg = process.argv[i]; - if (arg.indexOf("--jsonabi") == 0) { + if (arg.indexOf("--jsonabi=") == 0) { jsonabi = arg.slice(10); if (fs.existsSync(jsonabi)) { jsonabi = JSON.parse(fs.readFileSync(jsonabi).toString()); } } else if (arg.indexOf("--explicit-do-call") == 0) { prefs._explicit_do_call = true; + } else if (arg.indexOf("--name=") == 0) { + name = arg.slice(7); } } -let out = makeContractFile("Contract", jsonabi, prefs); +let out = makeContractFile(name, jsonabi, prefs); console.log(`${out}`); diff --git a/scripts/cov.sh b/scripts/cov.sh index 036cf9c80..7f21e9804 100755 --- a/scripts/cov.sh +++ b/scripts/cov.sh @@ -50,6 +50,6 @@ do $KCOV --exclude-pattern $EXCLUDE $KCOV_FLAGS $KCOV_TARGET $FILE done -$KCOV --coveralls-id=${TRAVIS_JOB_ID} --exclude-pattern $EXCLUDE $KCOV_FLAGS $KCOV_TARGET target/debug/parity-* +$KCOV --coveralls-id=${CI_BUILD_ID} --exclude-pattern $EXCLUDE $KCOV_FLAGS $KCOV_TARGET target/debug/parity-* exit 0 diff --git a/scripts/docker-build.sh b/scripts/docker-build.sh index bc6185d15..b9724cd2f 100644 --- a/scripts/docker-build.sh +++ b/scripts/docker-build.sh @@ -1,3 +1,4 @@ #!/bin/bash -docker build --no-cache=true --tag ethcore/parity:$1 . +cd docker/hub +docker build --build-arg BUILD_TAG=$1 --no-cache=true --tag ethcore/parity:$1 . docker push ethcore/parity:$1 diff --git a/signer/Cargo.toml b/signer/Cargo.toml index 5a24c52bb..ba33bad68 100644 --- a/signer/Cargo.toml +++ b/signer/Cargo.toml @@ -3,7 +3,7 @@ description = "Ethcore Trusted Signer" homepage = "http://parity.io" license = "GPL-3.0" name = "ethcore-signer" -version = "1.6.0" +version = "1.7.0" authors = ["Parity Technologies <admin@parity.io>"] build = "build.rs" diff --git a/stratum/Cargo.toml b/stratum/Cargo.toml index 65b2c0e9c..75a45eacc 100644 --- a/stratum/Cargo.toml +++ b/stratum/Cargo.toml @@ -1,7 +1,7 @@ [package] description = "Ethcore stratum lib" name = "ethcore-stratum" -version = "1.6.0" +version = "1.7.0" license = "GPL-3.0" authors = ["Parity Technologies <admin@parity.io>"] build = "build.rs" diff --git a/sync/Cargo.toml b/sync/Cargo.toml index 5fa635760..c3eabbb02 100644 --- a/sync/Cargo.toml +++ b/sync/Cargo.toml @@ -1,7 +1,7 @@ [package] description = "Ethcore blockchain sync" name = "ethsync" -version = "1.6.0" +version = "1.7.0" license = "GPL-3.0" authors = ["Parity Technologies <admin@parity.io>"] build = "build.rs" diff --git a/sync/src/tests/consensus.rs b/sync/src/tests/consensus.rs index 228096f28..2c45bbd28 100644 --- a/sync/src/tests/consensus.rs +++ b/sync/src/tests/consensus.rs @@ -83,17 +83,19 @@ fn authority_round() { net.peer(0).chain.miner().import_own_transaction(&*net.peer(0).chain, new_tx(s0.secret(), 1.into())).unwrap(); net.peer(1).chain.miner().import_own_transaction(&*net.peer(1).chain, new_tx(s1.secret(), 1.into())).unwrap(); - // Move to next proposer step + // Move to next proposer step. net.peer(0).chain.engine().step(); net.peer(1).chain.engine().step(); net.sync(); assert_eq!(net.peer(0).chain.chain_info().best_block_number, 2); assert_eq!(net.peer(1).chain.chain_info().best_block_number, 2); - // Fork the network + // Fork the network with equal height. net.peer(0).chain.miner().import_own_transaction(&*net.peer(0).chain, new_tx(s0.secret(), 2.into())).unwrap(); net.peer(1).chain.miner().import_own_transaction(&*net.peer(1).chain, new_tx(s1.secret(), 2.into())).unwrap(); + // Let both nodes build one block. net.peer(0).chain.engine().step(); + let early_hash = net.peer(0).chain.chain_info().best_block_hash; net.peer(1).chain.engine().step(); net.peer(0).chain.engine().step(); net.peer(1).chain.engine().step(); @@ -102,13 +104,39 @@ fn authority_round() { assert_eq!(ci0.best_block_number, 3); assert_eq!(ci1.best_block_number, 3); assert!(ci0.best_block_hash != ci1.best_block_hash); - // Reorg to the correct one. + // Reorg to the chain with earlier view. net.sync(); let ci0 = net.peer(0).chain.chain_info(); let ci1 = net.peer(1).chain.chain_info(); assert_eq!(ci0.best_block_number, 3); assert_eq!(ci1.best_block_number, 3); assert_eq!(ci0.best_block_hash, ci1.best_block_hash); + assert_eq!(ci1.best_block_hash, early_hash); + + // Selfish miner + net.peer(0).chain.miner().import_own_transaction(&*net.peer(0).chain, new_tx(s0.secret(), 3.into())).unwrap(); + net.peer(1).chain.miner().import_own_transaction(&*net.peer(1).chain, new_tx(s1.secret(), 3.into())).unwrap(); + // Node 0 is an earlier primary. + net.peer(0).chain.engine().step(); + assert_eq!(net.peer(0).chain.chain_info().best_block_number, 4); + net.peer(0).chain.engine().step(); + net.peer(0).chain.engine().step(); + net.peer(0).chain.engine().step(); + assert_eq!(net.peer(0).chain.chain_info().best_block_number, 4); + // Node 1 makes 2 blocks, but is a later primary on the first one. + net.peer(1).chain.engine().step(); + net.peer(1).chain.engine().step(); + net.peer(1).chain.miner().import_own_transaction(&*net.peer(1).chain, new_tx(s1.secret(), 4.into())).unwrap(); + net.peer(1).chain.engine().step(); + net.peer(1).chain.engine().step(); + assert_eq!(net.peer(1).chain.chain_info().best_block_number, 5); + // Reorg to the longest chain one not ealier view one. + net.sync(); + let ci0 = net.peer(0).chain.chain_info(); + let ci1 = net.peer(1).chain.chain_info(); + assert_eq!(ci0.best_block_number, 5); + assert_eq!(ci1.best_block_number, 5); + assert_eq!(ci0.best_block_hash, ci1.best_block_hash); } #[test] diff --git a/updater/Cargo.toml b/updater/Cargo.toml index 8d0014e14..c0738cc59 100644 --- a/updater/Cargo.toml +++ b/updater/Cargo.toml @@ -1,7 +1,7 @@ [package] description = "Parity Updater Service." name = "parity-updater" -version = "1.6.0" +version = "1.7.0" license = "GPL-3.0" authors = ["Parity Technologies <admin@parity.io>"] build = "build.rs" diff --git a/updater/src/updater.rs b/updater/src/updater.rs index 31c3106bd..605ede5c1 100644 --- a/updater/src/updater.rs +++ b/updater/src/updater.rs @@ -245,7 +245,7 @@ impl Updater { if let Some(ops_addr) = self.client.upgrade().and_then(|c| c.registry_address("operations".into())) { trace!(target: "updater", "Found operations at {}", ops_addr); let client = self.client.clone(); - *self.operations.lock() = Some(Operations::new(ops_addr, move |a, d| client.upgrade().ok_or("No client!".into()).and_then(|c| c.call_contract(a, d)))); + *self.operations.lock() = Some(Operations::new(ops_addr, move |a, d| client.upgrade().ok_or("No client!".into()).and_then(|c| c.call_contract(BlockId::Latest, a, d)))); } else { // No Operations contract - bail. return; @@ -330,7 +330,7 @@ impl fetch::urlhint::ContractClient for Updater { fn call(&self, address: Address, data: Bytes) -> Result<Bytes, String> { self.client.upgrade().ok_or_else(|| "Client not available".to_owned())? - .call_contract(address, data) + .call_contract(BlockId::Latest, address, data) } } diff --git a/util/Cargo.toml b/util/Cargo.toml index 7a195212a..d32d2519a 100644 --- a/util/Cargo.toml +++ b/util/Cargo.toml @@ -3,7 +3,7 @@ description = "Ethcore utility library" homepage = "http://parity.io" license = "GPL-3.0" name = "ethcore-util" -version = "1.6.0" +version = "1.7.0" authors = ["Parity Technologies <admin@parity.io>"] build = "build.rs" diff --git a/util/io/Cargo.toml b/util/io/Cargo.toml index d433f66f2..d81557f8e 100644 --- a/util/io/Cargo.toml +++ b/util/io/Cargo.toml @@ -3,7 +3,7 @@ description = "Ethcore IO library" homepage = "http://parity.io" license = "GPL-3.0" name = "ethcore-io" -version = "1.6.0" +version = "1.7.0" authors = ["Parity Technologies <admin@parity.io>"] [dependencies] diff --git a/util/network/Cargo.toml b/util/network/Cargo.toml index 4b256bf1d..7fced467a 100644 --- a/util/network/Cargo.toml +++ b/util/network/Cargo.toml @@ -3,7 +3,7 @@ description = "Ethcore network library" homepage = "http://parity.io" license = "GPL-3.0" name = "ethcore-network" -version = "1.6.0" +version = "1.7.0" authors = ["Parity Technologies <admin@parity.io>"] [dependencies] diff --git a/util/src/kvdb.rs b/util/src/kvdb.rs index 1714ce22f..043b3d983 100644 --- a/util/src/kvdb.rs +++ b/util/src/kvdb.rs @@ -410,6 +410,29 @@ struct DBAndColumns { cfs: Vec<Column>, } +// get column family configuration from database config. +fn col_config(col: u32, config: &DatabaseConfig) -> Options { + // default cache size for columns not specified. + const DEFAULT_CACHE: usize = 2; + + let mut opts = Options::new(); + opts.set_compaction_style(DBCompactionStyle::DBUniversalCompaction); + opts.set_target_file_size_base(config.compaction.initial_file_size); + opts.set_target_file_size_multiplier(config.compaction.file_size_multiplier); + + let col_opt = config.columns.map(|_| col); + + { + let cache_size = config.cache_sizes.get(&col_opt).cloned().unwrap_or(DEFAULT_CACHE); + let mut block_opts = BlockBasedOptions::new(); + // all goes to read cache. + block_opts.set_cache(Cache::new(cache_size * 1024 * 1024)); + opts.set_block_based_table_factory(&block_opts); + } + + opts +} + /// Key-Value database. pub struct Database { db: RwLock<Option<DBAndColumns>>, @@ -434,9 +457,6 @@ impl Database { /// Open database file. Creates if it does not exist. pub fn open(config: &DatabaseConfig, path: &str) -> Result<Database, String> { - // default cache size for columns not specified. - const DEFAULT_CACHE: usize = 2; - let mut opts = Options::new(); if let Some(rate_limit) = config.compaction.write_rate_limit { opts.set_parsed_options(&format!("rate_limiter_bytes_per_sec={}", rate_limit))?; @@ -460,22 +480,7 @@ impl Database { let cfnames: Vec<&str> = cfnames.iter().map(|n| n as &str).collect(); for col in 0 .. config.columns.unwrap_or(0) { - let mut opts = Options::new(); - opts.set_compaction_style(DBCompactionStyle::DBUniversalCompaction); - opts.set_target_file_size_base(config.compaction.initial_file_size); - opts.set_target_file_size_multiplier(config.compaction.file_size_multiplier); - - let col_opt = config.columns.map(|_| col); - - { - let cache_size = config.cache_sizes.get(&col_opt).cloned().unwrap_or(DEFAULT_CACHE); - let mut block_opts = BlockBasedOptions::new(); - // all goes to read cache. - block_opts.set_cache(Cache::new(cache_size * 1024 * 1024)); - opts.set_block_based_table_factory(&block_opts); - } - - cf_options.push(opts); + cf_options.push(col_config(col, &config)); } let mut write_opts = WriteOptions::new(); @@ -768,6 +773,42 @@ impl Database { *self.flushing.write() = mem::replace(&mut *db.flushing.write(), Vec::new()); Ok(()) } + + /// The number of non-default column families. + pub fn num_columns(&self) -> u32 { + self.db.read().as_ref() + .and_then(|db| if db.cfs.is_empty() { None } else { Some(db.cfs.len()) } ) + .map(|n| n as u32) + .unwrap_or(0) + } + + /// Drop a column family. + pub fn drop_column(&self) -> Result<(), String> { + match *self.db.write() { + Some(DBAndColumns { ref mut db, ref mut cfs }) => { + if let Some(col) = cfs.pop() { + let name = format!("col{}", cfs.len()); + drop(col); + db.drop_cf(&name)?; + } + Ok(()) + }, + None => Ok(()), + } + } + + /// Add a column family. + pub fn add_column(&self) -> Result<(), String> { + match *self.db.write() { + Some(DBAndColumns { ref mut db, ref mut cfs }) => { + let col = cfs.len() as u32; + let name = format!("col{}", col); + cfs.push(db.create_cf(&name, &col_config(col, &self.config))?); + Ok(()) + }, + None => Ok(()), + } + } } // duplicate declaration of methods here to avoid trait import in certain existing cases @@ -886,4 +927,54 @@ mod tests { let expected_output = Some(PathBuf::from("/sys/block/sda/queue/rotational")); assert_eq!(rotational_from_df_output(example_df), expected_output); } + + #[test] + fn add_columns() { + let config = DatabaseConfig::default(); + let config_5 = DatabaseConfig::with_columns(Some(5)); + + let path = RandomTempPath::create_dir(); + + // open empty, add 5. + { + let db = Database::open(&config, path.as_path().to_str().unwrap()).unwrap(); + assert_eq!(db.num_columns(), 0); + + for i in 0..5 { + db.add_column().unwrap(); + assert_eq!(db.num_columns(), i + 1); + } + } + + // reopen as 5. + { + let db = Database::open(&config_5, path.as_path().to_str().unwrap()).unwrap(); + assert_eq!(db.num_columns(), 5); + } + } + + #[test] + fn drop_columns() { + let config = DatabaseConfig::default(); + let config_5 = DatabaseConfig::with_columns(Some(5)); + + let path = RandomTempPath::create_dir(); + + // open 5, remove all. + { + let db = Database::open(&config_5, path.as_path().to_str().unwrap()).unwrap(); + assert_eq!(db.num_columns(), 5); + + for i in (0..5).rev() { + db.drop_column().unwrap(); + assert_eq!(db.num_columns(), i); + } + } + + // reopen as 0. + { + let db = Database::open(&config, path.as_path().to_str().unwrap()).unwrap(); + assert_eq!(db.num_columns(), 0); + } + } } diff --git a/util/src/migration/mod.rs b/util/src/migration/mod.rs index 50464444f..6ec465517 100644 --- a/util/src/migration/mod.rs +++ b/util/src/migration/mod.rs @@ -127,6 +127,9 @@ pub trait Migration: 'static { fn pre_columns(&self) -> Option<u32> { self.columns() } /// Number of columns in database after the migration. fn columns(&self) -> Option<u32>; + /// Whether this migration alters any existing columns. + /// if not, then column families will simply be added and `migrate` will never be called. + fn alters_existing(&self) -> bool { true } /// Version of the database after the migration. fn version(&self) -> u32; /// Migrate a source to a destination. @@ -149,6 +152,8 @@ impl<T: SimpleMigration> Migration for T { fn version(&self) -> u32 { SimpleMigration::version(self) } + fn alters_existing(&self) -> bool { true } + fn migrate(&mut self, source: Arc<Database>, config: &Config, dest: &mut Database, col: Option<u32>) -> Result<(), Error> { let mut batch = Batch::new(config, col); @@ -162,6 +167,26 @@ impl<T: SimpleMigration> Migration for T { } } +/// An even simpler migration which just changes the number of columns. +pub struct ChangeColumns { + /// The amount of columns before this migration. + pub pre_columns: Option<u32>, + /// The amount of columns after this migration. + pub post_columns: Option<u32>, + /// The version after this migration. + pub version: u32, +} + +impl Migration for ChangeColumns { + fn pre_columns(&self) -> Option<u32> { self.pre_columns } + fn columns(&self) -> Option<u32> { self.post_columns } + fn version(&self) -> u32 { self.version } + fn alters_existing(&self) -> bool { false } + fn migrate(&mut self, _: Arc<Database>, _: &Config, _: &mut Database, _: Option<u32>) -> Result<(), Error> { + Ok(()) + } +} + /// Get the path where all databases reside. fn database_path(path: &Path) -> PathBuf { let mut temp_path = path.to_owned(); @@ -244,7 +269,7 @@ impl Manager { let db_root = database_path(old_path); let mut temp_idx = TempIndex::One; - let mut temp_path = temp_idx.path(&db_root); + let mut temp_path = old_path.to_path_buf(); // start with the old db. let old_path_str = old_path.to_str().ok_or(Error::MigrationImpossible)?; @@ -256,28 +281,42 @@ impl Manager { let current_columns = db_config.columns; db_config.columns = migration.columns(); - // open the target temporary database. - temp_path = temp_idx.path(&db_root); - let temp_path_str = temp_path.to_str().ok_or(Error::MigrationImpossible)?; - let mut new_db = Database::open(&db_config, temp_path_str).map_err(Error::Custom)?; + // slow migrations: alter existing data. + if migration.alters_existing() { + temp_path = temp_idx.path(&db_root); - // perform the migration from cur_db to new_db. - match current_columns { - // migrate only default column - None => migration.migrate(cur_db.clone(), &config, &mut new_db, None)?, - Some(v) => { - // Migrate all columns in previous DB - for col in 0..v { - migration.migrate(cur_db.clone(), &config, &mut new_db, Some(col))? + // open the target temporary database. + let temp_path_str = temp_path.to_str().ok_or(Error::MigrationImpossible)?; + let mut new_db = Database::open(&db_config, temp_path_str).map_err(Error::Custom)?; + + match current_columns { + // migrate only default column + None => migration.migrate(cur_db.clone(), &config, &mut new_db, None)?, + Some(v) => { + // Migrate all columns in previous DB + for col in 0..v { + migration.migrate(cur_db.clone(), &config, &mut new_db, Some(col))? + } } } - } - // next iteration, we will migrate from this db into the other temp. - cur_db = Arc::new(new_db); - temp_idx.swap(); + // next iteration, we will migrate from this db into the other temp. + cur_db = Arc::new(new_db); + temp_idx.swap(); - // remove the other temporary migration database. - let _ = fs::remove_dir_all(temp_idx.path(&db_root)); + // remove the other temporary migration database. + let _ = fs::remove_dir_all(temp_idx.path(&db_root)); + } else { + // migrations which simply add or remove column families. + // we can do this in-place. + let goal_columns = migration.columns().unwrap_or(0); + while cur_db.num_columns() < goal_columns { + cur_db.add_column().map_err(Error::Custom)?; + } + + while cur_db.num_columns() > goal_columns { + cur_db.drop_column().map_err(Error::Custom)?; + } + } } Ok(temp_path) } diff --git a/util/src/migration/tests.rs b/util/src/migration/tests.rs index 852b96a8d..31226ec49 100644 --- a/util/src/migration/tests.rs +++ b/util/src/migration/tests.rs @@ -226,3 +226,26 @@ fn pre_columns() { // short of the one before it. manager.execute(&db_path, 0).unwrap(); } + +#[test] +fn change_columns() { + use kvdb::DatabaseConfig; + + let mut manager = Manager::new(Config::default()); + manager.add_migration(::migration::ChangeColumns { + pre_columns: None, + post_columns: Some(4), + version: 1, + }).unwrap(); + + let dir = RandomTempPath::create_dir(); + let db_path = db_path(dir.as_path()); + + let new_path = manager.execute(&db_path, 0).unwrap(); + + assert_eq!(db_path, new_path, "Changing columns is an in-place migration."); + + let config = DatabaseConfig::with_columns(Some(4)); + let db = Database::open(&config, new_path.to_str().unwrap()).unwrap(); + assert_eq!(db.num_columns(), 4); +} diff --git a/windows/ptray/ptray.ico b/windows/ptray/ptray.ico index 61e68b90b..337475b70 100644 Binary files a/windows/ptray/ptray.ico and b/windows/ptray/ptray.ico differ