diff --git a/dapps/src/handlers/async.rs b/dapps/src/handlers/async.rs new file mode 100644 index 0000000000000000000000000000000000000000..d68c55cce169772bf0eb9afe5b687f39db7ff088 --- /dev/null +++ b/dapps/src/handlers/async.rs @@ -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 . + +//! Async Content Handler +//! Temporary solution until we switch to future-based server. +//! Wraps a future and converts it to hyper::server::Handler; + +use std::{mem, time}; +use std::sync::mpsc; +use futures::Future; +use hyper::{server, Decoder, Encoder, Next, Control}; +use hyper::net::HttpStream; + +use handlers::ContentHandler; +use parity_reactor::Remote; + +const TIMEOUT_SECS: u64 = 15; + +enum State { + Initial(F, M, Remote, Control), + Waiting(mpsc::Receiver>, M), + Done(ContentHandler), + Invalid, +} + +pub struct AsyncHandler { + state: State, +} + +impl AsyncHandler { + pub fn new(future: F, map: M, remote: Remote, control: Control) -> Self { + AsyncHandler { + state: State::Initial(future, map, remote, control), + } + } +} + +impl server::Handler for AsyncHandler, M> where + F: Future + Send + 'static, + M: FnOnce(Result, ()>) -> ContentHandler, + T: Send + 'static, + E: Send + 'static, +{ + fn on_request(&mut self, _request: server::Request) -> Next { + if let State::Initial(future, map, remote, control) = mem::replace(&mut self.state, State::Invalid) { + let (tx, rx) = mpsc::sync_channel(1); + let control2 = control.clone(); + let tx2 = tx.clone(); + remote.spawn_with_timeout(move || future.then(move |result| { + // Send a result (ignore errors if the connection was dropped) + let _ = tx.send(Ok(result)); + // Resume handler + let _ = control.ready(Next::read()); + + Ok(()) + }), time::Duration::from_secs(TIMEOUT_SECS), move || { + // Notify about error + let _ = tx2.send(Err(())); + // Resume handler + let _ = control2.ready(Next::read()); + }); + + self.state = State::Waiting(rx, map); + } + + Next::wait() + } + + fn on_request_readable(&mut self, _decoder: &mut Decoder) -> Next { + if let State::Waiting(rx, map) = mem::replace(&mut self.state, State::Invalid) { + match rx.try_recv() { + Ok(result) => { + self.state = State::Done(map(result)); + }, + Err(err) => { + warn!("Resuming handler in incorrect state: {:?}", err); + } + } + } + + Next::write() + } + + fn on_response(&mut self, res: &mut server::Response) -> Next { + if let State::Done(ref mut handler) = self.state { + handler.on_response(res) + } else { + Next::end() + } + } + + fn on_response_writable(&mut self, encoder: &mut Encoder) -> Next { + if let State::Done(ref mut handler) = self.state { + handler.on_response_writable(encoder) + } else { + Next::end() + } + } +} diff --git a/dapps/src/url.rs b/dapps/src/url.rs new file mode 100644 index 0000000000000000000000000000000000000000..23dbfb4fcc3043380def50f963a81a49277d98bf --- /dev/null +++ b/dapps/src/url.rs @@ -0,0 +1,150 @@ +// 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/HTTPS URL type. Based on URL type from Iron library. + +use url_lib::{self}; +pub use url_lib::Host; + +/// HTTP/HTTPS URL type for Iron. +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct Url { + /// Raw url of url + pub raw: url_lib::Url, + + /// The host field of the URL, probably a domain. + pub host: Host, + + /// The connection port. + pub port: u16, + + /// The URL path, the resource to be accessed. + /// + /// A *non-empty* vector encoding the parts of the URL path. + /// Empty entries of `""` correspond to trailing slashes. + pub path: Vec, + + /// The URL query. + pub query: Option, + + /// The URL username field, from the userinfo section of the URL. + /// + /// `None` if the `@` character was not part of the input OR + /// if a blank username was provided. + /// Otherwise, a non-empty string. + pub username: Option, + + /// The URL password field, from the userinfo section of the URL. + /// + /// `None` if the `@` character was not part of the input OR + /// if a blank password was provided. + /// Otherwise, a non-empty string. + pub password: Option, +} + +impl Url { + /// Create a URL from a string. + /// + /// The input must be a valid URL with a special scheme for this to succeed. + /// + /// HTTP and HTTPS are special schemes. + /// + /// See: http://url.spec.whatwg.org/#special-scheme + pub fn parse(input: &str) -> Result { + // Parse the string using rust-url, then convert. + match url_lib::Url::parse(input) { + Ok(raw_url) => Url::from_generic_url(raw_url), + Err(e) => Err(format!("{}", e)) + } + } + + /// Create a `Url` from a `rust-url` `Url`. + pub fn from_generic_url(raw_url: url_lib::Url) -> Result { + // Map empty usernames to None. + let username = match raw_url.username() { + "" => None, + username => Some(username.to_owned()) + }; + + // Map empty passwords to None. + let password = match raw_url.password() { + Some(password) if !password.is_empty() => Some(password.to_owned()), + _ => None, + }; + + let port = raw_url.port_or_known_default().ok_or_else(|| format!("Unknown port for scheme: `{}`", raw_url.scheme()))?; + let host = raw_url.host().ok_or_else(|| "Valid host, because only data:, mailto: protocols does not have host.".to_owned())?.to_owned(); + let path = raw_url.path_segments().ok_or_else(|| "Valid path segments. In HTTP we won't get cannot-be-a-base URLs".to_owned())? + .map(|part| part.to_owned()).collect(); + let query = raw_url.query().map(|x| x.to_owned()); + + Ok(Url { + port: port, + host: host, + path: path, + query: query, + raw: raw_url, + username: username, + password: password, + }) + } +} + +#[cfg(test)] +mod test { + use super::Url; + + #[test] + fn test_default_port() { + assert_eq!(Url::parse("http://example.com/wow").unwrap().port, 80u16); + assert_eq!(Url::parse("https://example.com/wow").unwrap().port, 443u16); + } + + #[test] + fn test_explicit_port() { + assert_eq!(Url::parse("http://localhost:3097").unwrap().port, 3097u16); + } + + #[test] + fn test_empty_username() { + assert!(Url::parse("http://@example.com").unwrap().username.is_none()); + assert!(Url::parse("http://:password@example.com").unwrap().username.is_none()); + } + + #[test] + fn test_not_empty_username() { + let user = Url::parse("http://john:pass@example.com").unwrap().username; + assert_eq!(user.unwrap(), "john"); + + let user = Url::parse("http://john:@example.com").unwrap().username; + assert_eq!(user.unwrap(), "john"); + } + + #[test] + fn test_empty_password() { + assert!(Url::parse("http://michael@example.com").unwrap().password.is_none()); + assert!(Url::parse("http://:@example.com").unwrap().password.is_none()); + } + + #[test] + fn test_not_empty_password() { + let pass = Url::parse("http://michael:pass@example.com").unwrap().password; + assert_eq!(pass.unwrap(), "pass"); + + let pass = Url::parse("http://:pass@example.com").unwrap().password; + assert_eq!(pass.unwrap(), "pass"); + } +} diff --git a/ethcore/build.rs b/ethcore/build.rs new file mode 100644 index 0000000000000000000000000000000000000000..8fe38e757f8ddca6c9c910fae5f8f219b10d1b66 --- /dev/null +++ b/ethcore/build.rs @@ -0,0 +1,23 @@ +// 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 . + +extern crate ethcore_ipc_codegen; + +fn main() { + ethcore_ipc_codegen::derive_ipc_cond("src/client/traits.rs", cfg!(feature="ipc")).unwrap(); + ethcore_ipc_codegen::derive_ipc_cond("src/snapshot/snapshot_service_trait.rs", cfg!(feature="ipc")).unwrap(); + ethcore_ipc_codegen::derive_ipc_cond("src/client/chain_notify.rs", cfg!(feature="ipc")).unwrap(); +} diff --git a/ethcore/light/build.rs b/ethcore/light/build.rs new file mode 100644 index 0000000000000000000000000000000000000000..b9c79f77888ea5e0e226bdcb91d37c44b5906629 --- /dev/null +++ b/ethcore/light/build.rs @@ -0,0 +1,27 @@ +// 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 . + +#[cfg(feature = "ipc")] +extern crate ethcore_ipc_codegen; + +#[cfg(feature = "ipc")] +fn main() { + ethcore_ipc_codegen::derive_binary("src/types/mod.rs.in").unwrap(); + ethcore_ipc_codegen::derive_ipc_cond("src/provider.rs", true).unwrap(); +} + +#[cfg(not(feature = "ipc"))] +fn main() { } \ No newline at end of file diff --git a/ethcore/light/src/types/mod.rs.in b/ethcore/light/src/types/mod.rs.in new file mode 100644 index 0000000000000000000000000000000000000000..eba551b533c396d027c868f893165caba2d8315e --- /dev/null +++ b/ethcore/light/src/types/mod.rs.in @@ -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 . + +pub mod request; diff --git a/ethcore/light/src/types/request/builder.rs b/ethcore/light/src/types/request/builder.rs new file mode 100644 index 0000000000000000000000000000000000000000..74503f54fd942d86ebc7ef181c8d84bd392a5f02 --- /dev/null +++ b/ethcore/light/src/types/request/builder.rs @@ -0,0 +1,258 @@ +// 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 . + +//! Request chain builder utility. +//! Push requests with `push`. Back-references and data required to verify responses must be +//! supplied as well. + +use std::collections::HashMap; +use std::ops::{Deref, DerefMut}; +use request::{ + IncompleteRequest, OutputKind, Output, NoSuchOutput, ResponseError, ResponseLike, +}; + +/// Build chained requests. Push them onto the series with `push`, +/// and produce a `Requests` object with `build`. Outputs are checked for consistency. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RequestBuilder { + output_kinds: HashMap<(usize, usize), OutputKind>, + requests: Vec, +} + +impl Default for RequestBuilder { + fn default() -> Self { + RequestBuilder { + output_kinds: HashMap::new(), + requests: Vec::new(), + } + } +} + +impl RequestBuilder { + /// Attempt to push a request onto the request chain. Fails if the request + /// references a non-existent output of a prior request. + pub fn push(&mut self, request: T) -> Result<(), NoSuchOutput> { + request.check_outputs(|req, idx, kind| { + match self.output_kinds.get(&(req, idx)) { + Some(k) if k == &kind => Ok(()), + _ => Err(NoSuchOutput), + } + })?; + let req_idx = self.requests.len(); + request.note_outputs(|idx, kind| { self.output_kinds.insert((req_idx, idx), kind); }); + self.requests.push(request); + Ok(()) + } + + /// Get a reference to the output kinds map. + pub fn output_kinds(&self) -> &HashMap<(usize, usize), OutputKind> { + &self.output_kinds + } + + /// Convert this into a "requests" object. + pub fn build(self) -> Requests { + Requests { + outputs: HashMap::new(), + requests: self.requests, + answered: 0, + } + } +} + +/// Requests pending responses. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Requests { + outputs: HashMap<(usize, usize), Output>, + requests: Vec, + answered: usize, +} + +impl Requests { + /// Get access to the underlying slice of requests. + // TODO: unimplemented -> Vec, // do we _have to_ allocate? + pub fn requests(&self) -> &[T] { &self.requests } + + /// Get the number of answered requests. + pub fn num_answered(&self) -> usize { self.answered } + + /// Whether the batch is complete. + pub fn is_complete(&self) -> bool { + self.answered == self.requests.len() + } + + /// Map requests from one type into another. + pub fn map_requests(self, f: F) -> Requests + where F: FnMut(T) -> U, U: IncompleteRequest + { + Requests { + outputs: self.outputs, + requests: self.requests.into_iter().map(f).collect(), + answered: self.answered, + } + } +} + +impl Requests { + /// Get the next request as a filled request. Returns `None` when all requests answered. + pub fn next_complete(&self) -> Option { + if self.is_complete() { + None + } else { + Some(self.requests[self.answered].clone() + .complete() + .expect("All outputs checked as invariant of `Requests` object; qed")) + } + } + + /// Sweep through all unanswered requests, filling them as necessary. + pub fn fill_unanswered(&mut self) { + let outputs = &mut self.outputs; + + for req in self.requests.iter_mut().skip(self.answered) { + req.fill(|req_idx, out_idx| outputs.get(&(req_idx, out_idx)).cloned().ok_or(NoSuchOutput)) + } + } + + /// Supply a response, asserting its correctness. + /// Fill outputs based upon it. + pub fn supply_response_unchecked(&mut self, response: &R) { + if self.is_complete() { return } + + let outputs = &mut self.outputs; + let idx = self.answered; + response.fill_outputs(|out_idx, output| { + // we don't need to check output kinds here because all back-references + // are validated in the builder. + // TODO: optimization for only storing outputs we "care about"? + outputs.insert((idx, out_idx), output); + }); + + self.answered += 1; + + // fill as much of the next request as we can. + if let Some(ref mut req) = self.requests.get_mut(self.answered) { + req.fill(|req_idx, out_idx| outputs.get(&(req_idx, out_idx)).cloned().ok_or(NoSuchOutput)) + } + } +} + +impl Requests { + /// Supply a response for the next request. + /// Fails on: wrong request kind, all requests answered already. + pub fn supply_response(&mut self, env: &T::Environment, response: &T::Response) + -> Result> + { + let idx = self.answered; + + // check validity. + if idx == self.requests.len() { return Err(ResponseError::Unexpected) } + let completed = self.next_complete() + .expect("only fails when all requests have been answered; this just checked against; qed"); + + let extracted = self.requests[idx] + .check_response(&completed, env, response).map_err(ResponseError::Validity)?; + + self.supply_response_unchecked(response); + Ok(extracted) + } +} + +impl Requests { + /// For each request, produce a response. + /// The responses vector produced goes up to the point where the responder + /// first returns `None`, an invalid response, or until all requests have been responded to. + pub fn respond_to_all(mut self, responder: F) -> Vec + where F: Fn(super::CompleteRequest) -> Option + { + let mut responses = Vec::new(); + + while let Some(response) = self.next_complete().and_then(&responder) { + match self.supply_response(&(), &response) { + Ok(()) => responses.push(response), + Err(e) => { + debug!(target: "pip", "produced bad response to request: {:?}", e); + return responses; + } + } + } + + responses + } +} + +impl Deref for Requests { + type Target = [T]; + + fn deref(&self) -> &[T] { + &self.requests[..] + } +} + +impl DerefMut for Requests { + fn deref_mut(&mut self) -> &mut [T] { + &mut self.requests[..] + } +} + +#[cfg(test)] +mod tests { + use request::*; + use super::RequestBuilder; + use bigint::hash::H256; + + #[test] + fn all_scalar() { + let mut builder = RequestBuilder::default(); + builder.push(Request::HeaderProof(IncompleteHeaderProofRequest { + num: 100.into(), + })).unwrap(); + builder.push(Request::Receipts(IncompleteReceiptsRequest { + hash: H256::default().into(), + })).unwrap(); + } + + #[test] + #[should_panic] + fn missing_backref() { + let mut builder = RequestBuilder::default(); + builder.push(Request::HeaderProof(IncompleteHeaderProofRequest { + num: Field::BackReference(100, 3), + })).unwrap(); + } + + #[test] + #[should_panic] + fn wrong_kind() { + let mut builder = RequestBuilder::default(); + assert!(builder.push(Request::HeaderProof(IncompleteHeaderProofRequest { + num: 100.into(), + })).is_ok()); + builder.push(Request::HeaderProof(IncompleteHeaderProofRequest { + num: Field::BackReference(0, 0), + })).unwrap(); + } + + #[test] + fn good_backreference() { + let mut builder = RequestBuilder::default(); + builder.push(Request::HeaderProof(IncompleteHeaderProofRequest { + num: 100.into(), // header proof puts hash at output 0. + })).unwrap(); + builder.push(Request::Receipts(IncompleteReceiptsRequest { + hash: Field::BackReference(0, 0), + })).unwrap(); + } +} diff --git a/ethcore/res/ethereum/metropolis_test.json b/ethcore/res/ethereum/metropolis_test.json new file mode 100644 index 0000000000000000000000000000000000000000..a452e5a80a121b35e01d85fb3c40443868bcdf7f --- /dev/null +++ b/ethcore/res/ethereum/metropolis_test.json @@ -0,0 +1,53 @@ +{ + "name": "Metropolis (Test)", + "engine": { + "Ethash": { + "params": { + "minimumDifficulty": "0x020000", + "difficultyBoundDivisor": "0x0800", + "durationLimit": "0x0d", + + "homesteadTransition": "0x0", + "eip150Transition": "0x0", + "eip160Transition": "0x0", + "eip161abcTransition": "0x0", + "eip161dTransition": "0x0", + "maxCodeSize": 24576 + } + } + }, + "params": { + "gasLimitBoundDivisor": "0x0400", + "blockReward": "0x4563918244F40000", + "registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b", + "accountStartNonce": "0x00", + "maximumExtraDataSize": "0x20", + "minGasLimit": "0x1388", + "networkID" : "0x1", + "eip98Transition": "0x0", + "eip86Transition": "0x0", + "eip140Transition": "0x0", + "eip210Transition": "0x0", + "eip155Transition": "0x0" + }, + "genesis": { + "seal": { + "ethereum": { + "nonce": "0x0000000000000042", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, + "difficulty": "0x400000000", + "author": "0x0000000000000000000000000000000000000000", + "timestamp": "0x00", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "extraData": "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa", + "gasLimit": "0x1388" + }, + "accounts": { + "0000000000000000000000000000000000000001": { "balance": "1", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } }, + "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 } } } } + } +} diff --git a/ethcore/res/ethereum/tobalaba.json b/ethcore/res/ethereum/tobalaba.json new file mode 100644 index 0000000000000000000000000000000000000000..f580a0a19e926c4b659dc69e2308818becbfc360 --- /dev/null +++ b/ethcore/res/ethereum/tobalaba.json @@ -0,0 +1,38 @@ +{ + "name": "Tobalaba", + "engine": { + "authorityRound": { + "params": { + "stepDuration": "3", + "validators": { + "safeContract": "0x1000000000000000000000000000000000000005" + } + } + } + }, + "params": { + "maximumExtraDataSize": "0x20", + "minGasLimit": "0x1388", + "networkID": "0x62121", + "gasLimitBoundDivisor": "0x400" + }, + "genesis": { + "seal": { + "authorityRound": { + "step": "0x0", + "signature": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + } + }, + "difficulty": "0x20000", + "gasLimit": "0x800000" + }, + "accounts": { + "0x1000000000000000000000000000000000000005": { + "balance": "1", + "constructor": "6060604052602060405190810160405280734ba15b56452521c0826a35a6f2022e1210fc519b73ffffffffffffffffffffffffffffffffffffffff16815250600190600161004e929190610100565b50602060405190810160405280734ba15b56452521c0826a35a6f2022e1210fc519b73ffffffffffffffffffffffffffffffffffffffff168152506002906001610099929190610100565b5034156100a557600080fd5b5b734ba15b56452521c0826a35a6f2022e1210fc519b6000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505b6101cd565b828054828255906000526020600020908101928215610179579160200282015b828111156101785782518260006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555091602001919060010190610120565b5b509050610186919061018a565b5090565b6101ca91905b808211156101c657600081816101000a81549073ffffffffffffffffffffffffffffffffffffffff021916905550600101610190565b5090565b90565b610a07806101dc6000396000f30060606040523615610081576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806318def8ef1461009957806340a141ff146100e65780634d238c8e1461011f57806375286211146101585780638914d3041461016d5780638da5cb5b14610196578063b7ab4db5146101eb575b341561008c57600080fd5b6100975b600080fd5b565b005b34156100a457600080fd5b6100d0600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610256565b6040518082815260200191505060405180910390f35b34156100f157600080fd5b61011d600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190505061026e565b005b341561012a57600080fd5b610156600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919050506105da565b005b341561016357600080fd5b61016b61079f565b005b341561017857600080fd5b6101806107ed565b6040518082815260200191505060405180910390f35b34156101a157600080fd5b6101a96107fb565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34156101f657600080fd5b6101fe610820565b6040518080602001828103825283818151815260200191508051906020019060200280838360005b838110156102425780820151818401525b602081019050610226565b505050509050019250505060405180910390f35b60036020528060005260406000206000915090505481565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415156102cb57600080fd5b6000600360008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541415151561031a57600080fd5b6001806103256107ed565b0381548110151561033257fe5b906000526020600020900160005b9054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050806002600360008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020548154811015156103b057fe5b906000526020600020900160005b6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550600260016002805490500381548110151561041157fe5b906000526020600020900160005b6101000a81549073ffffffffffffffffffffffffffffffffffffffff021916905560016002805490500360028161045691906108b5565b50600360008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054600360008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055506000600360008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055506001430340600019167f55252fa6eee4741b4e24a74a70e9c11fd2c2281df8d6ea13126ff845f7825c896002604051808060200182810382528381815481526020019150805480156105c657602002820191906000526020600020905b8160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001906001019080831161057c575b50509250505060405180910390a25b5b5050565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561063557600080fd5b6002805480600101828161064991906108e1565b916000526020600020900160005b83909190916101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050600160028054905003600360008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055506001430340600019167f55252fa6eee4741b4e24a74a70e9c11fd2c2281df8d6ea13126ff845f7825c8960026040518080602001828103825283818154815260200191508054801561078c57602002820191906000526020600020905b8160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019060010190808311610742575b50509250505060405180910390a25b5b50565b73fffffffffffffffffffffffffffffffffffffffe3373ffffffffffffffffffffffffffffffffffffffff161415156107d757600080fd5b600260019080546107e992919061090d565b505b565b600060028054905090505b90565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b61082861095f565b60018054806020026020016040519081016040528092919081815260200182805480156108aa57602002820191906000526020600020905b8160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019060010190808311610860575b505050505090505b90565b8154818355818115116108dc578183600052602060002091820191016108db9190610973565b5b505050565b815481835581811511610908578183600052602060002091820191016109079190610973565b5b505050565b82805482825590600052602060002090810192821561094e5760005260206000209182015b8281111561094d578254825591600101919060010190610932565b5b50905061095b9190610998565b5090565b602060405190810160405280600081525090565b61099591905b80821115610991576000816000905550600101610979565b5090565b90565b6109d891905b808211156109d457600081816101000a81549073ffffffffffffffffffffffffffffffffffffffff02191690555060010161099e565b5090565b905600a165627a7a72305820f010290c3ae950b0bd158019fc7b606faa22bd57d199727172b91971312cfca20029" + }, + "0x4ba15b56452521c0826a35a6f2022e1210fc519b": { + "balance": "0x7E37BE2022B2B09472D89C0000" + } + } +} \ No newline at end of file diff --git a/ethcore/src/json_tests/homestead_chain.rs b/ethcore/src/json_tests/homestead_chain.rs new file mode 100644 index 0000000000000000000000000000000000000000..5f0fe5769ee9385eecd919356737005b04fe39fe --- /dev/null +++ b/ethcore/src/json_tests/homestead_chain.rs @@ -0,0 +1,39 @@ +// 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::chain::json_chain_test; +use tests::helpers::*; + +fn do_json_test(json_data: &[u8]) -> Vec { + json_chain_test(json_data, ChainEra::Homestead) +} + +declare_test!{BlockchainTests_Homestead_bcBlockGasLimitTest, "BlockchainTests/Homestead/bcBlockGasLimitTest"} +declare_test!{BlockchainTests_Homestead_bcForkStressTest, "BlockchainTests/Homestead/bcForkStressTest"} +declare_test!{BlockchainTests_Homestead_bcGasPricerTest, "BlockchainTests/Homestead/bcGasPricerTest"} +declare_test!{BlockchainTests_Homestead_bcInvalidHeaderTest, "BlockchainTests/Homestead/bcInvalidHeaderTest"} +declare_test!{BlockchainTests_Homestead_bcInvalidRLPTest, "BlockchainTests/Homestead/bcInvalidRLPTest"} +declare_test!{BlockchainTests_Homestead_bcMultiChainTest, "BlockchainTests/Homestead/bcMultiChainTest"} +declare_test!{BlockchainTests_Homestead_bcRPC_API_Test, "BlockchainTests/Homestead/bcRPC_API_Test"} +declare_test!{BlockchainTests_Homestead_bcStateTest, "BlockchainTests/Homestead/bcStateTest"} +declare_test!{BlockchainTests_Homestead_bcTotalDifficultyTest, "BlockchainTests/Homestead/bcTotalDifficultyTest"} +declare_test!{BlockchainTests_Homestead_bcUncleHeaderValiditiy, "BlockchainTests/Homestead/bcUncleHeaderValiditiy"} +declare_test!{BlockchainTests_Homestead_bcUncleTest, "BlockchainTests/Homestead/bcUncleTest"} +declare_test!{BlockchainTests_Homestead_bcValidBlockTest, "BlockchainTests/Homestead/bcValidBlockTest"} +declare_test!{BlockchainTests_Homestead_bcWalletTest, "BlockchainTests/Homestead/bcWalletTest"} +declare_test!{BlockchainTests_Homestead_bcShanghaiLove, "BlockchainTests/Homestead/bcShanghaiLove"} +declare_test!{BlockchainTests_Homestead_bcSuicideIssue, "BlockchainTests/Homestead/bcSuicideIssue"} +declare_test!{BlockchainTests_Homestead_bcExploitTest, "BlockchainTests/Homestead/bcExploitTest"} diff --git a/ethcore/src/snapshot/snapshot_service_trait.rs b/ethcore/src/snapshot/snapshot_service_trait.rs new file mode 100644 index 0000000000000000000000000000000000000000..9ef7706b442ae17b7108c5f53d094f3368e25a01 --- /dev/null +++ b/ethcore/src/snapshot/snapshot_service_trait.rs @@ -0,0 +1,58 @@ +// 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::{ManifestData, RestorationStatus}; +use bigint::hash::H256; +use util::Bytes; +use ipc::IpcConfig; + +/// The interface for a snapshot network service. +/// This handles: +/// - restoration of snapshots to temporary databases. +/// - responding to queries for snapshot manifests and chunks +#[ipc(client_ident="RemoteSnapshotService")] +pub trait SnapshotService : Sync + Send { + /// Query the most recent manifest data. + fn manifest(&self) -> Option; + + /// Get the supported range of snapshot version numbers. + /// `None` indicates warp sync isn't supported by the consensus engine. + fn supported_versions(&self) -> Option<(u64, u64)>; + + /// Get raw chunk for a given hash. + fn chunk(&self, hash: H256) -> Option; + + /// Ask the snapshot service for the restoration status. + fn status(&self) -> RestorationStatus; + + /// Begin snapshot restoration. + /// If restoration in-progress, this will reset it. + /// From this point on, any previous snapshot may become unavailable. + fn begin_restore(&self, manifest: ManifestData); + + /// Abort an in-progress restoration if there is one. + fn abort_restore(&self); + + /// Feed a raw state chunk to the service to be processed asynchronously. + /// no-op if not currently restoring. + fn restore_state_chunk(&self, hash: H256, chunk: Bytes); + + /// Feed a raw block chunk to the service to be processed asynchronously. + /// no-op if currently restoring. + fn restore_block_chunk(&self, hash: H256, chunk: Bytes); +} + +impl IpcConfig for SnapshotService { } diff --git a/ethcore/src/tests/rpc.rs b/ethcore/src/tests/rpc.rs new file mode 100644 index 0000000000000000000000000000000000000000..6444daa56b5d156b262a2021b4a58a73930f6c75 --- /dev/null +++ b/ethcore/src/tests/rpc.rs @@ -0,0 +1,78 @@ +// 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 . + +//! Client RPC tests + +use nanoipc; +use std::sync::Arc; +use std::sync::atomic::{Ordering, AtomicBool}; +use client::{Client, BlockChainClient, ClientConfig, BlockId}; +use client::remote::RemoteClient; +use tests::helpers::*; +use devtools::*; +use miner::Miner; +use crossbeam; +use io::IoChannel; +use util::kvdb::DatabaseConfig; + +pub fn run_test_worker(scope: &crossbeam::Scope, stop: Arc, socket_path: &str) { + let socket_path = socket_path.to_owned(); + scope.spawn(move || { + let temp = RandomTempPath::create_dir(); + let spec = get_test_spec(); + let db_config = DatabaseConfig::with_columns(::db::NUM_COLUMNS); + + let client = Client::new( + ClientConfig::default(), + &spec, + temp.as_path(), + Arc::new(Miner::with_spec(&spec)), + IoChannel::disconnected(), + &db_config + ).unwrap(); + let mut worker = nanoipc::Worker::new(&(client as Arc)); + worker.add_reqrep(&socket_path).unwrap(); + while !stop.load(Ordering::Relaxed) { + worker.poll(); + } + }); +} + +#[test] +fn can_handshake() { + crossbeam::scope(|scope| { + let stop_guard = StopGuard::new(); + let socket_path = "ipc:///tmp/parity-client-rpc-10.ipc"; + run_test_worker(scope, stop_guard.share(), socket_path); + let remote_client = nanoipc::generic_client::>(socket_path).unwrap(); + + assert!(remote_client.handshake().is_ok()); + }) +} + +#[test] +fn can_query_block() { + crossbeam::scope(|scope| { + let stop_guard = StopGuard::new(); + let socket_path = "ipc:///tmp/parity-client-rpc-20.ipc"; + run_test_worker(scope, stop_guard.share(), socket_path); + let remote_client = nanoipc::generic_client::>(socket_path).unwrap(); + + let non_existant_block = remote_client.block_header(BlockId::Number(999)); + + assert!(non_existant_block.is_none()); + }) +} diff --git a/ethcore/wasm/src/call_args.rs b/ethcore/wasm/src/call_args.rs new file mode 100644 index 0000000000000000000000000000000000000000..7fb50bff3b28f8af927550046532ea7d9f608096 --- /dev/null +++ b/ethcore/wasm/src/call_args.rs @@ -0,0 +1,63 @@ +// 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 . + +//! Wasm evm call arguments helper + +use bigint::prelude::U256; +use bigint::hash::H160; + +/// Input part of the wasm call descriptor +pub struct CallArgs { + /// Receiver of the transaction + pub address: [u8; 20], + + /// Sender of the transaction + pub sender: [u8; 20], + + /// Original transaction initiator + pub origin: [u8; 20], + + /// Transfer value + pub value: [u8; 32], + + /// call/create params + pub data: Vec, +} + +impl CallArgs { + /// New contract call payload with known parameters + pub fn new(address: H160, sender: H160, origin: H160, value: U256, data: Vec) -> Self { + let mut descriptor = CallArgs { + address: [0u8; 20], + sender: [0u8; 20], + origin: [0u8; 20], + value: [0u8; 32], + data: data, + }; + + descriptor.address.copy_from_slice(&*address); + descriptor.sender.copy_from_slice(&*sender); + descriptor.origin.copy_from_slice(&*origin); + value.to_big_endian(&mut descriptor.value); + + descriptor + } + + /// Total call payload length in linear memory + pub fn len(&self) -> u32 { + self.data.len() as u32 + 92 + } +} diff --git a/ipc-common-types/Cargo.toml b/ipc-common-types/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..a9efe143986d26197e9494bf0824ad52c3ffebf2 --- /dev/null +++ b/ipc-common-types/Cargo.toml @@ -0,0 +1,16 @@ +[package] +description = "Types that implement IPC and are common to multiple modules." +name = "ipc-common-types" +version = "1.8.0" +license = "GPL-3.0" +authors = ["Parity Technologies "] +build = "build.rs" + +[build-dependencies] +ethcore-ipc-codegen = { path = "../ipc/codegen" } + +[dependencies] +semver = "0.6" +ethcore-ipc = { path = "../ipc/rpc" } +ethcore-util = { path = "../util" } +ethcore-bigint = { path = "../util/bigint" } diff --git a/ipc-common-types/build.rs b/ipc-common-types/build.rs new file mode 100644 index 0000000000000000000000000000000000000000..3a51d6a215876b207501304611fc7e5ee68cb944 --- /dev/null +++ b/ipc-common-types/build.rs @@ -0,0 +1,21 @@ +// 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 . + +extern crate ethcore_ipc_codegen; + +fn main() { + ethcore_ipc_codegen::derive_binary("src/types/mod.rs.in").unwrap(); +} diff --git a/ipc-common-types/src/lib.rs b/ipc-common-types/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..c1d18a8d76197e331cf7aef4e4b65024441de8fb --- /dev/null +++ b/ipc-common-types/src/lib.rs @@ -0,0 +1,26 @@ +// 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 . + +//! Updater for Parity executables + +extern crate semver; +extern crate ethcore_util as util; +extern crate ethcore_bigint as bigint; +extern crate ethcore_ipc as ipc; + +mod types; + +pub use types::*; diff --git a/ipc-common-types/src/types/mod.rs b/ipc-common-types/src/types/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..68dfe0b252839d0b6027446bc17c3ead1ea46d7d --- /dev/null +++ b/ipc-common-types/src/types/mod.rs @@ -0,0 +1,21 @@ +// 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 . + +//! Types used in the public api + +#![allow(dead_code, unused_assignments, unused_variables)] // codegen issues +include!(concat!(env!("OUT_DIR"), "/mod.rs.in")); + diff --git a/ipc-common-types/src/types/mod.rs.in b/ipc-common-types/src/types/mod.rs.in new file mode 100644 index 0000000000000000000000000000000000000000..f137d1d607bdba37c1c5b262bd866eb0d6249fd4 --- /dev/null +++ b/ipc-common-types/src/types/mod.rs.in @@ -0,0 +1,21 @@ +// 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 . + +pub mod release_track; +pub mod version_info; + +pub use release_track::{ReleaseTrack}; +pub use version_info::{VersionInfo}; \ No newline at end of file diff --git a/ipc-common-types/src/types/release_track.rs b/ipc-common-types/src/types/release_track.rs new file mode 100644 index 0000000000000000000000000000000000000000..418f590c88ce34d7a15286320c19753f4ec14b3d --- /dev/null +++ b/ipc-common-types/src/types/release_track.rs @@ -0,0 +1,83 @@ +// 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 . + +//! Types used in the public API + +use std::fmt; + +/// A release's track. +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[binary] +pub enum ReleaseTrack { + /// Stable track. + Stable, + /// Beta track. + Beta, + /// Nightly track. + Nightly, + /// Testing track. + Testing, + /// No known track, also "current executable's track" when it's not yet known. + Unknown, +} + +impl fmt::Display for ReleaseTrack { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!(f, "{}", match *self { + ReleaseTrack::Stable => "stable", + ReleaseTrack::Beta => "beta", + ReleaseTrack::Nightly => "nightly", + ReleaseTrack::Testing => "testing", + ReleaseTrack::Unknown => "unknown", + }) + } +} + +impl<'a> From<&'a str> for ReleaseTrack { + fn from(s: &'a str) -> Self { + match s { + "stable" => ReleaseTrack::Stable, + "beta" => ReleaseTrack::Beta, + "nightly" => ReleaseTrack::Nightly, + "testing" => ReleaseTrack::Testing, + _ => ReleaseTrack::Unknown, + } + } +} + +impl From for ReleaseTrack { + fn from(i: u8) -> Self { + match i { + 1 => ReleaseTrack::Stable, + 2 => ReleaseTrack::Beta, + 3 => ReleaseTrack::Nightly, + 4 => ReleaseTrack::Testing, + _ => ReleaseTrack::Unknown, + } + } +} + +impl Into for ReleaseTrack { + fn into(self) -> u8 { + match self { + ReleaseTrack::Stable => 1, + ReleaseTrack::Beta => 2, + ReleaseTrack::Nightly => 3, + ReleaseTrack::Testing => 4, + ReleaseTrack::Unknown => 0, + } + } +} diff --git a/ipc-common-types/src/types/version_info.rs b/ipc-common-types/src/types/version_info.rs new file mode 100644 index 0000000000000000000000000000000000000000..ef386824d7c77ae62e1bcb9608dbb1d5409cd8eb --- /dev/null +++ b/ipc-common-types/src/types/version_info.rs @@ -0,0 +1,70 @@ +// 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 . + +//! Types used in the public API + +use std::fmt; +use std::str::FromStr; +use semver::{Version}; +use bigint::hash::H160; +use util::misc::raw_package_info; +use release_track::ReleaseTrack; + +/// Version information of a particular release. +#[derive(Debug, Clone, PartialEq)] +#[binary] +pub struct VersionInfo { + /// The track on which it was released. + pub track: ReleaseTrack, + /// The version. + pub version: Version, + /// The (SHA1?) 160-bit hash of this build's code base. + pub hash: H160, +} + +impl fmt::Display for VersionInfo { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!(f, "{}.{}.{}-{}-{}", self.version.major, self.version.minor, self.version.patch, self.track, self.hash) + } +} + +impl VersionInfo { + /// Get information for this (currently running) binary. + pub fn this() -> Self { + let raw = raw_package_info(); + VersionInfo { + track: raw.0.into(), + version: { let mut v = Version::parse(raw.1).expect("Environment variables are known to be valid; qed"); v.build = vec![]; v.pre = vec![]; v }, + hash: H160::from_str(raw.2).unwrap_or_else(|_| H160::zero()), + } + } + + /// Compose the information from the provided raw fields. + pub fn from_raw(semver: u32, track: u8, hash: H160) -> Self { + let t = track.into(); + VersionInfo { + version: Version { + major: (semver >> 16) as u64, + minor: ((semver >> 8) & 0xff) as u64, + patch: (semver & 0xff) as u64, + build: vec![], + pre: vec![], + }, + track: t, + hash: hash, + } + } +} diff --git a/ipc/codegen/Cargo.toml b/ipc/codegen/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..69580e08d76f7faf55f66bc4706cc55eae324923 --- /dev/null +++ b/ipc/codegen/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "ethcore-ipc-codegen" +version = "1.8.0" +authors = ["Parity Technologies "] +license = "GPL-3.0" +description = "Macros to auto-generate implementations for ipc call" +build = "build.rs" +keywords = ["ipc", "codegen"] + +[features] +default = ["with-syntex"] +nightly = ["quasi_macros"] +nightly-testing = ["clippy"] +with-syntex = ["quasi/with-syntex", "quasi_codegen", "quasi_codegen/with-syntex", "syntex", "syntex_syntax"] + +[build-dependencies] +quasi_codegen = { version = "0.32", optional = true } +syntex = { version = "0.58", optional = true } + +[dependencies] +aster = { version = "0.41", default-features = false } +clippy = { version = "^0.*", optional = true } +quasi = { version = "0.32", default-features = false } +quasi_macros = { version = "0.32", optional = true } +syntex = { version = "0.58", optional = true } +syntex_syntax = { version = "0.58", optional = true } diff --git a/ipc/codegen/build.rs b/ipc/codegen/build.rs new file mode 100644 index 0000000000000000000000000000000000000000..442abf7dfba6404c05fbf2923dbe67346457b1b1 --- /dev/null +++ b/ipc/codegen/build.rs @@ -0,0 +1,43 @@ +// 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 . + + +#[cfg(feature = "with-syntex")] +mod inner { + extern crate syntex; + extern crate quasi_codegen; + + use std::env; + use std::path::Path; + + pub fn main() { + let out_dir = env::var_os("OUT_DIR").unwrap(); + + let src = Path::new("src/lib.rs.in"); + let dst = Path::new(&out_dir).join("lib.rs"); + + quasi_codegen::expand(&src, &dst).unwrap(); + } +} + +#[cfg(not(feature = "with-syntex"))] +mod inner { + pub fn main() {} +} + +fn main() { + inner::main(); +} diff --git a/ipc/codegen/src/codegen.rs b/ipc/codegen/src/codegen.rs new file mode 100644 index 0000000000000000000000000000000000000000..2f4752d9716407f99e954183210960ba8803e542 --- /dev/null +++ b/ipc/codegen/src/codegen.rs @@ -0,0 +1,898 @@ +// 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 aster; + +use syntax::ast::{ + MetaItem, + Item, + ImplItemKind, + MethodSig, + Arg, + PatKind, + FunctionRetTy, + Ty, + TraitRef, + Ident, + Generics, + TraitItemKind, +}; + +use syntax::ast; +use syntax::codemap::Span; +use syntax::ext::base::{Annotatable, ExtCtxt}; +use syntax::ptr::P; + +pub struct Error; + +const RESERVED_MESSAGE_IDS: u16 = 16; + +pub fn expand_ipc_implementation( + cx: &mut ExtCtxt, + span: Span, + meta_item: &MetaItem, + annotatable: &Annotatable, + push: &mut FnMut(Annotatable) +) { + let item = match *annotatable { + Annotatable::Item(ref item) => item, + _ => { + cx.span_err(meta_item.span, "`#[ipc]` may only be applied to implementations and traits"); + return; + }, + }; + + let builder = aster::AstBuilder::new().span(span); + + let interface_map = match implement_interface(cx, &builder, &item, push, meta_item) { + Ok(interface_map) => interface_map, + Err(Error) => { return; }, + }; + + push_client(cx, &builder, &interface_map, push); + + push(Annotatable::Item(interface_map.item)); +} + +macro_rules! literal { + ($builder:ident, $($arg:tt)*) => { + $builder.expr().lit().str::<&str>(&format!($($arg)*)) + } +} + +fn field_name(builder: &aster::AstBuilder, arg: &Arg) -> ast::Ident { + match arg.pat.node { + PatKind::Ident(_, ref ident, _) => builder.id(ident.node), + _ => { panic!("unexpected param in interface: {:?}", arg.pat.node) } + } +} + +pub fn replace_slice_u8(builder: &aster::AstBuilder, ty: &P) -> P { + if ::syntax::print::pprust::ty_to_string(&strip_ptr(ty)) == "[u8]" { + return builder.ty().id("Vec") + } + ty.clone() +} + +struct NamedSignature<'a> { + sig: &'a MethodSig, + ident: &'a Ident, +} + +fn push_invoke_signature_aster( + builder: &aster::AstBuilder, + named_signature: &NamedSignature, + push: &mut FnMut(Annotatable), +) -> Dispatch { + let inputs = &named_signature.sig.decl.inputs; + let (input_type_name, input_arg_names, input_arg_tys) = if inputs.len() > 0 { + let first_field_name = field_name(builder, &inputs[0]).name.as_str(); + if &*first_field_name == "self" && inputs.len() == 1 { (None, vec![], vec![]) } + else { + let skip = if &*first_field_name == "self" { 2 } else { 1 }; + let name_str = format!("{}_input", named_signature.ident.name.as_str()); + + let mut arg_names = Vec::new(); + let mut arg_tys = Vec::new(); + + let arg_name = format!("{}", field_name(builder, &inputs[skip-1]).name); + let arg_ty = &inputs[skip-1].ty; + + let mut tree = builder.item() + .attr().word("binary") + .attr().word("allow(non_camel_case_types)") + .struct_(name_str.as_str()) + .field(arg_name.as_str()) + .ty().build(replace_slice_u8(builder, &strip_ptr(arg_ty))); + + arg_names.push(arg_name); + arg_tys.push(arg_ty.clone()); + for arg in inputs.iter().skip(skip) { + let arg_name = format!("{}", field_name(builder, &arg)); + let arg_ty = &arg.ty; + + tree = tree.field(arg_name.as_str()).ty().build(replace_slice_u8(builder, &strip_ptr(arg_ty))); + arg_names.push(arg_name); + arg_tys.push(arg_ty.clone()); + } + + push(Annotatable::Item(tree.build())); + (Some(name_str.to_owned()), arg_names, arg_tys) + } + } + else { + (None, vec![], vec![]) + }; + + let return_type_ty = match named_signature.sig.decl.output { + FunctionRetTy::Ty(ref ty) => { + let name_str = format!("{}_output", named_signature.ident.name.as_str()); + let tree = builder.item() + .attr().word("binary") + .attr().word("allow(non_camel_case_types)") + .struct_(name_str.as_str()) + .field(format!("payload")).ty().build(ty.clone()); + push(Annotatable::Item(tree.build())); + Some(ty.clone()) + } + _ => None + }; + + Dispatch { + function_name: format!("{}", named_signature.ident.name.as_str()), + input_type_name: input_type_name, + input_arg_names: input_arg_names, + input_arg_tys: input_arg_tys, + return_type_ty: return_type_ty, + } +} + +struct Dispatch { + function_name: String, + input_type_name: Option, + input_arg_names: Vec, + input_arg_tys: Vec>, + return_type_ty: Option>, +} + +// This is the expanded version of this: +// +// let invoke_serialize_stmt = quote_stmt!(cx, { +// ::bincode::serde::serialize(& $output_type_id { payload: self. $function_name ($hand_param_a, $hand_param_b) }, ::bincode::SizeLimit::Infinite).unwrap() +// }); +// +// But the above does not allow comma-separated expressions for arbitrary number +// of parameters ...$hand_param_a, $hand_param_b, ... $hand_param_n +fn implement_dispatch_arm_invoke_stmt( + cx: &ExtCtxt, + builder: &aster::AstBuilder, + dispatch: &Dispatch, +) -> ast::Stmt +{ + use ::syntax::tokenstream::TokenTree::Token; + let function_name = builder.id(dispatch.function_name.as_str()); + + let input_args_exprs = dispatch.input_arg_names.iter().enumerate().map(|(arg_index, arg_name)| { + let arg_ident = builder.id(arg_name); + let expr = quote_expr!(cx, input. $arg_ident); + if has_ptr(&dispatch.input_arg_tys[arg_index]) { quote_expr!(cx, & $expr) } + else { expr } + }).collect::>>(); + + let ext_cx = &*cx; + ::quasi::parse_stmt_panic(&mut ::syntax::parse::new_parser_from_tts( + ext_cx.parse_sess(), + { + let _sp = ext_cx.call_site(); + let mut tt = ::std::vec::Vec::new(); + + tt.push(Token(_sp, ::syntax::parse::token::OpenDelim(::syntax::parse::token::Brace))); + + if dispatch.return_type_ty.is_some() { + tt.push(Token(_sp, ::syntax::parse::token::ModSep)); + tt.push(Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of("ipc")))); + tt.push(Token(_sp, ::syntax::parse::token::ModSep)); + tt.push(Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of("binary")))); + tt.push(Token(_sp, ::syntax::parse::token::ModSep)); + tt.push(Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of("serialize")))); + tt.push(Token(_sp, ::syntax::parse::token::OpenDelim(::syntax::parse::token::Paren))); + tt.push(Token(_sp, ::syntax::parse::token::BinOp(::syntax::parse::token::And))); + } + + tt.push(Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of("self")))); + tt.push(Token(_sp, ::syntax::parse::token::Dot)); + tt.extend(::quasi::ToTokens::to_tokens(&function_name, ext_cx).into_iter()); + tt.push(Token(_sp, ::syntax::parse::token::OpenDelim(::syntax::parse::token::Paren))); + + for arg_expr in input_args_exprs { + tt.extend(::quasi::ToTokens::to_tokens(&arg_expr, ext_cx).into_iter()); + tt.push(Token(_sp, ::syntax::parse::token::Comma)); + } + + tt.push(Token(_sp, ::syntax::parse::token::CloseDelim(::syntax::parse::token::Paren))); + + if dispatch.return_type_ty.is_some() { + tt.push(Token(_sp, ::syntax::parse::token::CloseDelim(::syntax::parse::token::Paren))); + tt.push(Token(_sp, ::syntax::parse::token::Dot)); + tt.push(Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of("unwrap")))); + tt.push(Token(_sp, ::syntax::parse::token::OpenDelim(::syntax::parse::token::Paren))); + tt.push(Token(_sp, ::syntax::parse::token::CloseDelim(::syntax::parse::token::Paren))); + } + else { + tt.push(Token(_sp, ::syntax::parse::token::Semi)); + tt.push(Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of("Vec")))); + tt.push(Token(_sp, ::syntax::parse::token::ModSep)); + tt.push(Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of("new")))); + tt.push(Token(_sp, ::syntax::parse::token::OpenDelim(::syntax::parse::token::Paren))); + tt.push(Token(_sp, ::syntax::parse::token::CloseDelim(::syntax::parse::token::Paren))); + + } + tt.push(Token(_sp, ::syntax::parse::token::CloseDelim(::syntax::parse::token::Brace))); + + tt + } + )).unwrap() +} + +fn implement_dispatch_arm_invoke( + cx: &ExtCtxt, + builder: &aster::AstBuilder, + dispatch: &Dispatch, + buffer: bool, +) -> P +{ + let deserialize_expr = if buffer { + quote_expr!(cx, + ::ipc::binary::deserialize(buf) + .unwrap_or_else(|e| { panic!("ipc error while deserializing payload, aborting \n payload: {:?}, \n error: {:?}", buf, e); } ) + ) + } else { + quote_expr!(cx, + ::ipc::binary::deserialize_from(r) + .unwrap_or_else(|e| { panic!("ipc error while deserializing payload, aborting \n error: {:?}", e); } ) + ) + }; + + let invoke_serialize_stmt = implement_dispatch_arm_invoke_stmt(cx, builder, dispatch); + dispatch.input_type_name.as_ref().map(|val| { + let input_type_id = builder.id(val.clone().as_str()); + quote_expr!(cx, { + let input: $input_type_id = $deserialize_expr; + $invoke_serialize_stmt + }) + }).unwrap_or(quote_expr!(cx, { $invoke_serialize_stmt })) +} + +/// generates dispatch match for method id +fn implement_dispatch_arm( + cx: &ExtCtxt, + builder: &aster::AstBuilder, + index: u32, + dispatch: &Dispatch, + buffer: bool, +) -> ast::Arm +{ + let index_ident = builder.id(format!("{}", index + (RESERVED_MESSAGE_IDS as u32)).as_str()); + let invoke_expr = implement_dispatch_arm_invoke(cx, builder, dispatch, buffer); + let trace = literal!(builder, "Dispatching: {}", &dispatch.function_name); + quote_arm!(cx, $index_ident => { + trace!(target: "ipc", $trace); + $invoke_expr + }) +} + +fn implement_dispatch_arms( + cx: &ExtCtxt, + builder: &aster::AstBuilder, + dispatches: &[Dispatch], + buffer: bool, +) -> Vec +{ + let mut index = -1; + dispatches.iter() + .map(|dispatch| { index = index + 1; implement_dispatch_arm(cx, builder, index as u32, dispatch, buffer) }).collect() +} + +pub fn strip_ptr(ty: &P) -> P { + if let ast::TyKind::Rptr(_, ref ptr_mut) = ty.node { + ptr_mut.ty.clone() + } + else { ty.clone() } +} + +pub fn has_ptr(ty: &P) -> bool { + if let ast::TyKind::Rptr(_, ref _ptr_mut) = ty.node { + true + } + else { false } +} + +/// returns an expression with the body for single operation that is being sent to server +/// operation itself serializes input, writes to socket and waits for socket to respond +/// (the latter only if original method signature returns anyting) +/// +/// assuming expanded class contains method +/// fn commit(&self, f: u32) -> u32 +/// +/// the expanded implementation will generate method for the client like that +/// #[binary] +/// struct Request<'a> { +/// f: &'a u32, +/// } +/// let payload = Request{f: &f,}; +/// let mut socket_ref = self.socket.borrow_mut(); +/// let mut socket = socket_ref.deref_mut(); +/// let serialized_payload = ::bincode::serde::serialize(&payload, ::bincode::SizeLimit::Infinite).unwrap(); +/// ::ipc::invoke(0, &Some(serialized_payload), &mut socket); +/// while !socket.ready().load(::std::sync::atomic::Ordering::Relaxed) { } +/// ::bincode::serde::deserialize_from::<_, u32>(&mut socket, ::bincode::SizeLimit::Infinite).unwrap() +fn implement_client_method_body( + cx: &ExtCtxt, + builder: &aster::AstBuilder, + index: u16, + interface_map: &InterfaceMap, +) -> P +{ + use ::syntax::tokenstream::TokenTree::Token; + + let dispatch = &interface_map.dispatches[index as usize]; + let index_ident = builder.id(format!("{}", index + RESERVED_MESSAGE_IDS).as_str()); + + let request = if dispatch.input_arg_names.len() > 0 { + + let arg_name = dispatch.input_arg_names[0].as_str(); + let static_ty = strip_ptr(&dispatch.input_arg_tys[0]); + let arg_ty = builder + .ty().ref_() + .lifetime("'a") + .ty() + .build(static_ty.clone()); + + let mut tree = builder.item() + .attr().word("binary") + .struct_("Request") + .generics() + .lifetime_name("'a") + .build() + .field(arg_name).ty() + .build(arg_ty); + + for arg_idx in 1..dispatch.input_arg_names.len() { + let arg_name = dispatch.input_arg_names[arg_idx].as_str(); + let static_ty = strip_ptr(&dispatch.input_arg_tys[arg_idx]); + + let arg_ty = builder + .ty().ref_() + .lifetime("'a") + .ty() + .build(static_ty); + tree = tree.field(arg_name).ty().build(arg_ty); + + } + let mut request_serialization_statements = Vec::new(); + + let struct_tree = tree.build(); + let struct_stmt = quote_stmt!(cx, $struct_tree); + request_serialization_statements.push(struct_stmt); + + // actually this is just expanded version of this: + // request_serialization_statements.push(quote_stmt!(cx, let payload = Request { p1: &p1, p2: &p2, ... pn: &pn, })); + // again, cannot dynamically create expression with arbitrary number of comma-separated members + request_serialization_statements.push({ + let ext_cx = &*cx; + ::quasi::parse_stmt_panic(&mut ::syntax::parse::new_parser_from_tts( + ext_cx.parse_sess(), + { + let _sp = ext_cx.call_site(); + let mut tt = ::std::vec::Vec::new(); + tt.push(Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of("let")))); + tt.push(Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of("payload")))); + tt.push(Token(_sp, ::syntax::parse::token::Eq)); + tt.push(Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of("Request")))); + tt.push(Token(_sp, ::syntax::parse::token::OpenDelim(::syntax::parse::token::Brace))); + + for arg in dispatch.input_arg_names.iter() { + tt.push(Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of(arg.as_str())))); + tt.push(Token(_sp, ::syntax::parse::token::Colon)); + tt.push(Token(_sp, ::syntax::parse::token::BinOp(::syntax::parse::token::And))); + + tt.push(Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of(arg.as_str())))); + tt.push(Token(_sp, ::syntax::parse::token::Comma)); + } + + tt.push(Token(_sp, ::syntax::parse::token::CloseDelim(::syntax::parse::token::Brace))); + tt + })) + }); + + request_serialization_statements.push( + quote_stmt!(cx, let mut socket = self.socket.write().unwrap(); )); + + request_serialization_statements.push( + quote_stmt!(cx, let serialized_payload = ::ipc::binary::serialize(&payload).unwrap())); + + request_serialization_statements.push( + quote_stmt!(cx, ::ipc::invoke($index_ident, &Some(serialized_payload), &mut *socket))); + + + request_serialization_statements + } + else { + let mut request_serialization_statements = Vec::new(); + request_serialization_statements.push( + quote_stmt!(cx, let mut socket = self.socket.write().unwrap(); )); + request_serialization_statements.push( + quote_stmt!(cx, ::ipc::invoke($index_ident, &None, &mut *socket))); + request_serialization_statements + }; + + let trace = literal!(builder, "Invoking: {}", &dispatch.function_name); + if let Some(ref return_ty) = dispatch.return_type_ty { + let return_expr = quote_expr!(cx, + ::ipc::binary::deserialize_from::<$return_ty, _>(&mut *socket).unwrap() + ); + quote_expr!(cx, { + trace!(target: "ipc", $trace); + $request; + $return_expr + }) + } + else { + quote_expr!(cx, { + trace!(target: "ipc", $trace); + $request + }) + } +} + +/// Generates signature and body (see `implement_client_method_body`) +/// for the client (signature is identical to the original method) +fn implement_client_method( + cx: &ExtCtxt, + builder: &aster::AstBuilder, + index: u16, + interface_map: &InterfaceMap, +) + -> ast::ImplItem +{ + use ::syntax::tokenstream::TokenTree::Token; + + let dispatch = &interface_map.dispatches[index as usize]; + let method_name = builder.id(dispatch.function_name.as_str()); + let body = implement_client_method_body(cx, builder, index, interface_map); + + let ext_cx = &*cx; + // expanded version of this + // pub fn $method_name(&self, p1: p1_ty, p2: p2_ty ... pn: pn_ty, ) [-> return_ty] { $body } + // looks like it's tricky to build function declaration with aster if body already generated + let signature = ::syntax::parse::parser::Parser::parse_impl_item( + &mut ::syntax::parse::new_parser_from_tts( + ext_cx.parse_sess(), + { + let _sp = ext_cx.call_site(); + let mut tt = ::std::vec::Vec::new(); + tt.push(Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of("fn")))); + tt.extend(::quasi::ToTokens::to_tokens(&method_name, ext_cx).into_iter()); + tt.push(Token(_sp, ::syntax::parse::token::OpenDelim(::syntax::parse::token::Paren))); + tt.push(Token(_sp, ::syntax::parse::token::BinOp(::syntax::parse::token::And))); + tt.push(Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of("self")))); + tt.push(Token(_sp, ::syntax::parse::token::Comma)); + + for arg_idx in 0..dispatch.input_arg_names.len() { + let arg_name = dispatch.input_arg_names[arg_idx].as_str(); + let arg_ty = dispatch.input_arg_tys[arg_idx].clone(); + + tt.push(Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of(arg_name)))); + tt.push(Token(_sp, ::syntax::parse::token::Colon)); + tt.extend(::quasi::ToTokens::to_tokens(&arg_ty, ext_cx).into_iter()); + tt.push(Token(_sp, ::syntax::parse::token::Comma)); + } + tt.push(Token(_sp, ::syntax::parse::token::CloseDelim(::syntax::parse::token::Paren))); + + if let Some(ref return_ty) = dispatch.return_type_ty { + tt.push(Token(_sp, ::syntax::parse::token::RArrow)); + tt.extend(::quasi::ToTokens::to_tokens(return_ty, ext_cx).into_iter()); + } + + tt.push(Token(_sp, ::syntax::parse::token::OpenDelim(::syntax::parse::token::Brace))); + tt.extend(::quasi::ToTokens::to_tokens(&body, ext_cx).into_iter()); + tt.push(Token(_sp, ::syntax::parse::token::CloseDelim(::syntax::parse::token::Brace))); + + tt + })); + + signature.unwrap() +} + +fn client_generics(builder: &aster::AstBuilder, interface_map: &InterfaceMap) -> Generics { + let ty_param = aster::ty_param::TyParamBuilder::new( + builder.id("S")).trait_bound( + builder.path().global().ids(&["ipc", "IpcSocket"]).build() + ).build().build(); + + builder.from_generics(interface_map.generics.clone()) + .with_ty_param(ty_param) + .build() +} + +fn client_qualified_ident(cx: &ExtCtxt, builder: &aster::AstBuilder, interface_map: &InterfaceMap) -> P { + let generics = client_generics(builder, interface_map); + aster::ty::TyBuilder::new().path().segment(interface_map.ident_map.client_ident(cx, builder)) + .with_generics(generics).build() + .build() +} + +fn client_phantom_ident(builder: &aster::AstBuilder, interface_map: &InterfaceMap) -> P { + let generics = client_generics(builder, interface_map); + aster::ty::TyBuilder::new().phantom_data() + .tuple().with_tys(generics.ty_params.iter().map(|x| aster::ty::TyBuilder::new().id(x.ident))) + .build() +} + +/// generates client type for specified server type +/// for say `Service` it generates `ServiceClient` +fn push_client_struct(cx: &ExtCtxt, builder: &aster::AstBuilder, interface_map: &InterfaceMap, push: &mut FnMut(Annotatable)) { + let generics = client_generics(builder, interface_map); + let client_short_ident = interface_map.ident_map.client_ident(cx, builder); + let phantom = client_phantom_ident(builder, interface_map); + + let client_struct_item = quote_item!(cx, + pub struct $client_short_ident $generics { + socket: ::std::sync::RwLock, + phantom: $phantom, + }); + + push(Annotatable::Item(client_struct_item.expect(&format!("could not generate client struct for {:?}", client_short_ident.name)))); +} + +/// pushes generated code for the client class (type declaration and method invocation implementations) +fn push_client( + cx: &ExtCtxt, + builder: &aster::AstBuilder, + interface_map: &InterfaceMap, + push: &mut FnMut(Annotatable), +) { + push_client_struct(cx, builder, interface_map, push); + push_client_implementation(cx, builder, interface_map, push); + push_with_socket_client_implementation(cx, builder, interface_map, push); +} + +fn push_with_socket_client_implementation( + cx: &ExtCtxt, + builder: &aster::AstBuilder, + interface_map: &InterfaceMap, + push: &mut FnMut(Annotatable)) +{ + let generics = client_generics(builder, interface_map); + let client_ident = client_qualified_ident(cx, builder, interface_map); + let where_clause = &generics.where_clause; + let client_short_ident = interface_map.ident_map.client_ident(cx, builder); + + let implement = quote_item!(cx, + impl $generics ::ipc::WithSocket for $client_ident $where_clause { + fn init(socket: S) -> $client_ident { + $client_short_ident { + socket: ::std::sync::RwLock::new(socket), + phantom: ::std::marker::PhantomData, + } + } + }).unwrap(); + push(Annotatable::Item(implement)); +} + +/// pushes full client side code for the original class exposed via ipc +fn push_client_implementation( + cx: &ExtCtxt, + builder: &aster::AstBuilder, + interface_map: &InterfaceMap, + push: &mut FnMut(Annotatable), +) { + let mut index = -1i32; + let items = interface_map.dispatches.iter() + .map(|_| { index = index + 1; P(implement_client_method(cx, builder, index as u16, interface_map)) }) + .collect::>>(); + + let generics = client_generics(builder, interface_map); + let client_ident = client_qualified_ident(cx, builder, interface_map); + let where_clause = &generics.where_clause; + let endpoint = interface_map.endpoint; + + let handshake_item = quote_impl_item!(cx, + pub fn handshake(&self) -> Result<(), ::ipc::Error> { + let payload = ::ipc::Handshake { + protocol_version: $endpoint::protocol_version(), + api_version: $endpoint::api_version(), + }; + + ::ipc::invoke( + 0, + &Some(::ipc::binary::serialize(&::ipc::BinHandshake::from(payload)).unwrap()), + &mut *self.socket.write().unwrap()); + + let mut result = vec![0u8; 1]; + if try!(self.socket.write().unwrap().read(&mut result).map_err(|_| ::ipc::Error::HandshakeFailed)) == 1 { + match result[0] { + 1 => Ok(()), + _ => Err(::ipc::Error::RemoteServiceUnsupported), + } + } + else { Err(::ipc::Error::HandshakeFailed) } + }).unwrap(); + + let socket_item = quote_impl_item!(cx, + #[cfg(test)] + pub fn socket(&self) -> &::std::sync::RwLock { + &self.socket + }).unwrap(); + + let generic_items = vec![P(handshake_item), P(socket_item)]; + + if interface_map.impl_trait.is_some() { + let trait_ty = builder.id( + ::syntax::print::pprust::path_to_string( + &interface_map.impl_trait.as_ref().unwrap().path)); + + let implement_trait = + quote_item!(cx, + impl $generics $trait_ty for $client_ident $where_clause { + $items + } + ).unwrap(); + push(Annotatable::Item(implement_trait)); + + let implement = + quote_item!(cx, + impl $generics $client_ident $where_clause { + $generic_items + } + ).unwrap(); + push(Annotatable::Item(implement)); + } + else { + let pub_items = items.iter().map(|item| { + let pub_item = item.clone(); + pub_item.map(|mut val| { val.vis = ast::Visibility::Public; val }) + }).collect::>>(); + + let implement = quote_item!(cx, + impl $generics $client_ident $where_clause { + $pub_items + $generic_items + }).unwrap(); + push(Annotatable::Item(implement)); + } + +} + +/// implements dispatching of system handshake invocation (method_num 0) +fn implement_handshake_arm( + cx: &ExtCtxt, +) -> (ast::Arm, ast::Arm) +{ + let handshake_deserialize = quote_stmt!(&cx, + let handshake_payload = ::ipc::binary::deserialize_from::<::ipc::BinHandshake, _>(r).unwrap(); + ); + + let handshake_deserialize_buf = quote_stmt!(&cx, + let handshake_payload = ::ipc::binary::deserialize::<::ipc::BinHandshake>(buf).unwrap(); + ); + + let handshake_serialize = quote_expr!(&cx, + ::ipc::binary::serialize::(&Self::handshake(&handshake_payload.to_semver())).unwrap() + ); + + ( + quote_arm!(&cx, 0 => { + $handshake_deserialize + $handshake_serialize + }), + quote_arm!(&cx, 0 => { + $handshake_deserialize_buf + $handshake_serialize + }), + ) +} + +fn get_str_from_lit(cx: &ExtCtxt, name: &str, lit: &ast::Lit) -> Result { + match lit.node { + ast::LitKind::Str(ref s, _) => Ok(format!("{}", s)), + _ => { + cx.span_err( + lit.span, + &format!("ipc client_ident annotation `{}` must be a string, not `{}`", + name, + ::syntax::print::pprust::lit_to_string(lit))); + + return Err(()); + } + } +} + +fn client_ident_renamed(cx: &ExtCtxt, meta_item: &MetaItem) -> Option { + if let ast::MetaItemKind::List(ref list) = meta_item.node { + for nested in list { + match nested.node { + ast::NestedMetaItemKind::MetaItem(ref meta_item) => { + let is_client_ident = &*meta_item.name.as_str() == "client_ident"; + match meta_item.node { + ast::MetaItemKind::NameValue(ref lit) if is_client_ident => { + if let Ok(s) = get_str_from_lit(cx, "client_ident", lit) { + return Some(s); + } + } + _ => { + cx.span_err( + meta_item.span, + &format!("unknown client_ident container attribute `{}`", + ::syntax::print::pprust::meta_item_to_string(&meta_item))); + } + } + }, + _ => {}, + } + } + } + + None +} + +struct InterfaceMap { + pub original_item: Item, + pub item: P, + pub dispatches: Vec, + pub generics: Generics, + pub impl_trait: Option, + pub ident_map: IdentMap, + pub endpoint: Ident, +} + +struct IdentMap { + original_path: ast::Path, + meta_item: MetaItem, +} + +impl IdentMap { + fn ident(&self, builder: &aster::AstBuilder) -> Ident { + builder.id(format!("{}", ::syntax::print::pprust::path_to_string(&self.original_path))) + } + + fn client_ident(&self, cx: &ExtCtxt, builder: &aster::AstBuilder) -> Ident { + if let Some(new_name) = client_ident_renamed(cx, &self.meta_item) { + builder.id(new_name) + } + else { + builder.id(format!("{}Client", self.original_path.segments[0].identifier)) + } + } +} + +fn ty_ident_map(original_ty: &P, meta_item: &MetaItem) -> IdentMap { + let original_path = match original_ty.node { + ::syntax::ast::TyKind::Path(_, ref path) => path.clone(), + _ => { panic!("incompatible implementation"); } + }; + let ident_map = IdentMap { original_path: original_path, meta_item: meta_item.clone() }; + ident_map +} + +/// implements `IpcInterface` for the given class `C` +fn implement_interface( + cx: &ExtCtxt, + builder: &aster::AstBuilder, + item: &Item, + push: &mut FnMut(Annotatable), + meta_item: &MetaItem, +) -> Result { + let (generics, impl_trait, original_ty, dispatch_table) = match item.node { + ast::ItemKind::Impl(_, _, ref generics, ref impl_trait, ref ty, ref impl_items) => { + let mut method_signatures = Vec::new(); + for impl_item in impl_items { + if let ImplItemKind::Method(ref signature, _) = impl_item.node { + method_signatures.push(NamedSignature { ident: &impl_item.ident, sig: signature }); + } + } + + let dispatch_table = method_signatures.iter().map(|named_signature| + push_invoke_signature_aster(builder, named_signature, push)) + .collect::>(); + + (generics, impl_trait.clone(), ty.clone(), dispatch_table) + }, + ast::ItemKind::Trait(_, ref generics, _, ref trait_items) => { + let mut method_signatures = Vec::new(); + for trait_item in trait_items { + if let TraitItemKind::Method(ref signature, _) = trait_item.node { + method_signatures.push(NamedSignature { ident: &trait_item.ident, sig: signature }); + } + } + + let dispatch_table = method_signatures.iter().map(|named_signature| + push_invoke_signature_aster(builder, named_signature, push)) + .collect::>(); + + ( + generics, + Some(ast::TraitRef { + path: builder.path().ids(&[item.ident.name]).build(), + ref_id: item.id, + }), + builder.ty().id(item.ident), + dispatch_table + ) + }, + _ => { + cx.span_err( + item.span, + "`#[ipc]` may only be applied to implementations and traits"); + return Err(Error); + }, + }; + let impl_generics = builder.from_generics(generics.clone()).build(); + let where_clause = &impl_generics.where_clause; + + let dispatch_arms = implement_dispatch_arms(cx, builder, &dispatch_table, false); + let dispatch_arms_buffered = implement_dispatch_arms(cx, builder, &dispatch_table, true); + + let (handshake_arm, handshake_arm_buf) = implement_handshake_arm(cx); + + let ty = ty_ident_map(&original_ty, meta_item).ident(builder); + let (interface_endpoint, host_generics) = match impl_trait { + Some(ref trait_) => (builder.id(::syntax::print::pprust::path_to_string(&trait_.path)), None), + None => (ty, Some(&impl_generics)), + }; + + let ipc_item = quote_item!(cx, + impl $host_generics ::ipc::IpcInterface for $interface_endpoint $where_clause { + fn dispatch(&self, r: &mut R) -> Vec + where R: ::std::io::Read + { + let mut method_num = vec![0u8;2]; + match r.read(&mut method_num) { + Ok(size) if size == 0 => { panic!("method id not supplied" ); } + Err(e) => { panic!("ipc read error: {:?}, aborting", e); } + _ => { } + } + + // method_num is a 16-bit little-endian unsigned number + match method_num[1] as u16 + (method_num[0] as u16)*256 { + // handshake + $handshake_arm + // user methods + $dispatch_arms + _ => vec![] + } + } + + fn dispatch_buf(&self, method_num: u16, buf: &[u8]) -> Vec + { + match method_num { + $handshake_arm_buf + $dispatch_arms_buffered + _ => vec![] + } + } + } + ).unwrap(); + + Ok(InterfaceMap { + ident_map: ty_ident_map(&original_ty, meta_item), + original_item: item.clone(), + item: ipc_item, + dispatches: dispatch_table, + generics: generics.clone(), + impl_trait: impl_trait.clone(), + endpoint: interface_endpoint, + }) +} diff --git a/ipc/codegen/src/lib.rs b/ipc/codegen/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..6e7fc441b324f217ce883b4d184e463f25061c74 --- /dev/null +++ b/ipc/codegen/src/lib.rs @@ -0,0 +1,237 @@ +// 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 . + +//! Codegen for IPC RPC + +#![cfg_attr(feature = "nightly-testing", plugin(clippy))] +#![cfg_attr(feature = "nightly-testing", feature(plugin))] +#![cfg_attr(feature = "nightly-testing", allow(used_underscore_binding))] +#![cfg_attr(not(feature = "with-syntex"), feature(rustc_private, plugin))] +#![cfg_attr(not(feature = "with-syntex"), plugin(quasi_macros))] + +extern crate aster; +extern crate quasi; + +#[cfg(feature = "with-syntex")] +extern crate syntex; + +#[cfg(feature = "with-syntex")] +extern crate syntex_syntax as syntax; + +#[cfg(not(feature = "with-syntex"))] +#[macro_use] +extern crate syntax; + +#[cfg(not(feature = "with-syntex"))] +extern crate rustc_plugin; + +#[cfg(not(feature = "with-syntex"))] +use syntax::feature_gate::AttributeType; + +#[cfg(feature = "with-syntex")] +use syntax::{ast, fold}; + + +#[cfg(feature = "with-syntex")] +include!(concat!(env!("OUT_DIR"), "/lib.rs")); + +#[cfg(not(feature = "with-syntex"))] +include!("lib.rs.in"); + +#[cfg(feature = "with-syntex")] +pub fn expand(src: &std::path::Path, dst: &std::path::Path) { + let mut registry = syntex::Registry::new(); + register(&mut registry); + registry.expand("", src, dst).unwrap(); +} + +#[cfg(feature = "with-syntex")] +struct StripAttributeFolder<'a> { + attr_title: &'a str, +} + +#[cfg(feature = "with-syntex")] +impl<'a> fold::Folder for StripAttributeFolder<'a> { + fn fold_attribute(&mut self, attr: ast::Attribute) -> Option { + let is_self = &*attr.value.name.as_str() == self.attr_title; + + match attr.value.node { + ast::MetaItemKind::List(_) if is_self => { return None; } + ast::MetaItemKind::Word if is_self => { return None; } + _ => {} + } + + Some(attr) + } + + fn fold_mac(&mut self, mac: ast::Mac) -> ast::Mac { + fold::noop_fold_mac(mac, self) + } +} + +#[cfg(feature = "with-syntex")] +pub fn register_cleaner_ipc(reg: &mut syntex::Registry) { + #[cfg(feature = "with-syntex")] + fn strip_attributes(krate: ast::Crate) -> ast::Crate { + let mut folder = StripAttributeFolder { attr_title: "ipc" }; + fold::Folder::fold_crate(&mut folder, krate) + } + + reg.add_post_expansion_pass(strip_attributes); +} + +#[cfg(feature = "with-syntex")] +pub fn register_cleaner_binary(reg: &mut syntex::Registry) { + #[cfg(feature = "with-syntex")] + fn strip_attributes(krate: ast::Crate) -> ast::Crate { + let mut folder = StripAttributeFolder { attr_title: "binary" }; + fold::Folder::fold_crate(&mut folder, krate) + } + + reg.add_post_expansion_pass(strip_attributes); +} + +#[cfg(feature = "with-syntex")] +pub fn register(reg: &mut syntex::Registry) { + reg.add_attr("feature(custom_derive)"); + reg.add_attr("feature(custom_attribute)"); + + reg.add_decorator("ipc", codegen::expand_ipc_implementation); + reg.add_decorator("binary", serialization::expand_serialization_implementation); + + register_cleaner_ipc(reg); + register_cleaner_binary(reg); +} + +#[cfg(not(feature = "with-syntex"))] +pub fn register(reg: &mut rustc_plugin::Registry) { + reg.register_syntax_extension( + syntax::parse::token::intern("ipc"), + syntax::ext::base::MultiDecorator( + Box::new(codegen::expand_ipc_implementation))); + reg.register_syntax_extension( + syntax::parse::token::intern("binary"), + syntax::ext::base::MultiDecorator( + Box::new(serialization::expand_serialization_implementation))); + + reg.register_attribute("ipc".to_owned(), AttributeType::Normal); + reg.register_attribute("binary".to_owned(), AttributeType::Normal); +} + +#[derive(Debug)] +pub enum Error { InvalidFileName, ExpandFailure, Io(std::io::Error) } + +impl std::convert::From for Error { + fn from(err: std::io::Error) -> Self { + Error::Io(err) + } +} + +pub fn derive_ipc_cond(src_path: &str, has_feature: bool) -> Result<(), Error> { + if has_feature { derive_ipc(src_path) } + else { cleanup_ipc(src_path) } +} + +pub fn cleanup_ipc(src_path: &str) -> Result<(), Error> { + cleanup(src_path, AttributeKind::Ipc) +} + +pub fn cleanup_binary(src_path: &str) -> Result<(), Error> { + cleanup(src_path, AttributeKind::Binary) +} + +enum AttributeKind { + Ipc, + Binary, +} + +fn cleanup(src_path: &str, attr: AttributeKind) -> Result<(), Error> { + use std::env; + use std::path::{Path, PathBuf}; + + let out_dir = env::var_os("OUT_DIR").unwrap(); + let file_name = PathBuf::from(src_path).file_name().ok_or(Error::InvalidFileName).map(|val| val.to_str().unwrap().to_owned())?; + let mut registry = syntex::Registry::new(); + + match attr { + AttributeKind::Ipc => { register_cleaner_ipc(&mut registry); } + AttributeKind::Binary => { register_cleaner_binary(&mut registry); } + } + + if let Err(_) = registry.expand("", &Path::new(src_path), &Path::new(&out_dir).join(&file_name)) + { + // will be reported by compiler + return Err(Error::ExpandFailure) + } + Ok(()) +} + +pub fn derive_ipc(src_path: &str) -> Result<(), Error> { + use std::env; + use std::path::{Path, PathBuf}; + + let out_dir = env::var_os("OUT_DIR").unwrap(); + let file_name = PathBuf::from(src_path).file_name().ok_or(Error::InvalidFileName).map(|val| val.to_str().unwrap().to_owned())?; + + let final_path = Path::new(&out_dir).join(&file_name); + + let mut intermediate_file_name = file_name.clone(); + intermediate_file_name.push_str(".rpc.in"); + let intermediate_path = Path::new(&out_dir).join(&intermediate_file_name); + + { + let mut registry = syntex::Registry::new(); + register(&mut registry); + if let Err(_) = registry.expand("", &Path::new(src_path), &intermediate_path) { + // will be reported by compiler + return Err(Error::ExpandFailure) + } + } + + { + let mut registry = syntex::Registry::new(); + register(&mut registry); + if let Err(_) = registry.expand("", &intermediate_path, &final_path) { + // will be reported by compiler + return Err(Error::ExpandFailure) + } + } + + Ok(()) +} + +pub fn derive_binary(src_path: &str) -> Result<(), Error> { + use std::env; + use std::path::{Path, PathBuf}; + + let out_dir = env::var_os("OUT_DIR").unwrap(); + let file_name = PathBuf::from(src_path).file_name().ok_or(Error::InvalidFileName).map(|val| val.to_str().unwrap().to_owned())?; + let final_path = Path::new(&out_dir).join(&file_name); + + let mut registry = syntex::Registry::new(); + register(&mut registry); + if let Err(_) = registry.expand("", &Path::new(src_path), &final_path) { + // will be reported by compiler + return Err(Error::ExpandFailure) + } + + Ok(()) +} + +pub fn derive_binary_cond(src_path: &str, has_feature: bool) -> Result<(), Error> { + if has_feature { derive_binary(src_path) } + else { cleanup_binary(src_path) } +} diff --git a/ipc/codegen/src/lib.rs.in b/ipc/codegen/src/lib.rs.in new file mode 100644 index 0000000000000000000000000000000000000000..c8aa8ebf27416c489965c6490cc98a7081ab5070 --- /dev/null +++ b/ipc/codegen/src/lib.rs.in @@ -0,0 +1,18 @@ +// 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 . + +mod codegen; +mod serialization; diff --git a/ipc/codegen/src/serialization.rs b/ipc/codegen/src/serialization.rs new file mode 100644 index 0000000000000000000000000000000000000000..fd908725c9b54e98b46d94d81ff5616f55a19df2 --- /dev/null +++ b/ipc/codegen/src/serialization.rs @@ -0,0 +1,810 @@ +// 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 aster; + +use syntax::ast::{ + MetaItem, + Item, + Ident, +}; + +use syntax::ast; +use syntax::codemap::Span; +use syntax::ext::base::{Annotatable, ExtCtxt}; +use syntax::ptr::P; + +pub struct Error; + +use super::codegen; + +pub fn expand_serialization_implementation( + cx: &mut ExtCtxt, + span: Span, + meta_item: &MetaItem, + annotatable: &Annotatable, + push: &mut FnMut(Annotatable) +) { + let item = match *annotatable { + Annotatable::Item(ref item) => item, + _ => { + cx.span_err(meta_item.span, "`#[derive(Binary)]` may only be applied to structs and enums"); + return; + }, + }; + + let builder = aster::AstBuilder::new().span(span); + + let impl_item = match serialize_item(cx, &builder, &item) { + Ok(item) => item, + Err(Error) => { + // An error occurred, but it should have been reported already. + return; + }, + }; + + push(Annotatable::Item(impl_item)) +} + +fn serialize_item( + cx: &ExtCtxt, + builder: &aster::AstBuilder, + item: &Item, +) -> Result, Error> { + let generics = match item.node { + ast::ItemKind::Struct(_, ref generics) => generics, + ast::ItemKind::Enum(_, ref generics) => generics, + _ => { + cx.span_err( + item.span, + "`#[derive(Binary)]` may only be applied to structs and enums"); + return Err(Error); + }, + }; + + let ty = builder.ty().path() + .segment(item.ident).with_generics(generics.clone()).build() + .build(); + + let where_clause = &generics.where_clause; + + let binary_expressions = try!(binary_expr(cx, + &builder, + &item, + &generics, + ty.clone())); + + let (size_expr, read_expr, write_expr) = + (binary_expressions.size, binary_expressions.read, binary_expressions.write); + + match quote_item!(cx, + impl $generics ::ipc::BinaryConvertable for $ty $where_clause { + fn size(&self) -> usize { + $size_expr + } + + fn to_bytes(&self, buffer: &mut [u8], length_stack: &mut ::std::collections::VecDeque) -> Result<(), ::ipc::BinaryConvertError> { + $write_expr + } + + fn from_bytes(buffer: &[u8], length_stack: &mut ::std::collections::VecDeque) -> Result { + $read_expr + } + + fn len_params() -> usize { + 1 + } + }) + { + Some(item) => Ok(item), + None => { + cx.span_err( + item.span, + "syntax error expanding serialization implementation"); + Err(Error) + } + } +} + +#[allow(unreachable_code)] +fn binary_expr( + cx: &ExtCtxt, + builder: &aster::AstBuilder, + item: &Item, + impl_generics: &ast::Generics, + ty: P, +) -> Result { + match item.node { + ast::ItemKind::Struct(ref variant_data, _) => { + binary_expr_item_struct( + cx, + builder, + impl_generics, + ty, + item.span, + variant_data, + ) + }, + ast::ItemKind::Enum(ref enum_def, _) => { + binary_expr_enum( + cx, + builder, + item.ident, + impl_generics, + ty, + item.span, + enum_def, + ) + }, + _ => { + cx.span_bug(item.span, + "expected ItemStruct or ItemEnum in #[derive(Binary)]"); + Err(Error) as Result + }, + } +} + +struct BinaryExpressions { + pub size: P, + pub write: P, + pub read: P, +} + +fn replace_qualified(s: &str) -> String { + if let Some(pos) = s.find("<") { + let mut source = s.to_owned(); + source.insert(pos, ':'); + source.insert(pos, ':'); + source + } + else { s.to_owned() } +} + +fn binary_expr_struct( + cx: &ExtCtxt, + builder: &aster::AstBuilder, + ty: P, + fields: &[ast::StructField], + value_ident: Option, + instance_ident: Option, +) -> Result { + + let size_exprs: Vec> = fields.iter().enumerate().map(|(index, field)| { + let raw_ident = ::syntax::print::pprust::ty_to_string(&codegen::strip_ptr(&field.ty)); + let index_ident = builder.id(format!("__field{}", index)); + let field_id = match field.ident { + Some(ident) => builder.id(ident), + None => builder.id(format!("{}", index)), + }; + + match raw_ident.as_ref() { + "u8" => { + quote_expr!(cx, 1) + }, + "[u8]" => { + value_ident.and_then(|x| { + Some(quote_expr!(cx, $x. $field_id .len())) + }) + .unwrap_or_else(|| { + quote_expr!(cx, $index_ident .len()) + } + ) + } + _ => { + let field_type_ident = builder.id( + &::syntax::print::pprust::ty_to_string(&codegen::strip_ptr(&field.ty))); + + let field_type_ident_qualified = builder.id( + replace_qualified(&::syntax::print::pprust::ty_to_string(&codegen::strip_ptr(&field.ty)))); + + value_ident.and_then(|x| + { + Some(quote_expr!(cx, + match $field_type_ident_qualified::len_params() { + 0 => ::std::mem::size_of::<$field_type_ident>(), + _ => $x. $field_id .size(), + })) + }) + .unwrap_or_else(|| { + quote_expr!(cx, match $field_type_ident_qualified::len_params() { + 0 => ::std::mem::size_of::<$field_type_ident>(), + _ => $index_ident .size(), + }) + }) + } + } + }).collect(); + + let first_size_expr = size_exprs[0].clone(); + let mut total_size_expr = quote_expr!(cx, 0usize + $first_size_expr); + for index in 1..size_exprs.len() { + let next_expr = size_exprs[index].clone(); + total_size_expr = quote_expr!(cx, $total_size_expr + $next_expr); + } + + let mut write_stmts = Vec::::new(); + write_stmts.push(quote_stmt!(cx, let mut offset = 0usize;).expect("stmt1")); + + let mut map_stmts = Vec::::new(); + let field_amount = builder.id(&format!("{}",fields.len())); + map_stmts.push(quote_stmt!(cx, let mut map = vec![0usize; $field_amount];).expect("stmt2")); + map_stmts.push(quote_stmt!(cx, let mut total = 0usize;).expect("stmt3")); + + let mut post_write_stmts = Vec::::new(); + + for (index, field) in fields.iter().enumerate() { + let field_type_ident = builder.id( + &::syntax::print::pprust::ty_to_string(&codegen::strip_ptr(&field.ty))); + + let field_type_ident_qualified = builder.id( + replace_qualified(&::syntax::print::pprust::ty_to_string(&codegen::strip_ptr(&field.ty)))); + + let field_id = match field.ident { + Some(ident) => builder.id(ident), + None => builder.id(format!("{}", index)), + }; + let member_expr = match value_ident { + Some(x) => { + quote_expr!(cx, $x . $field_id) + }, + None => { + let index_ident = builder.id(format!("__field{}", index)); + quote_expr!(cx, $index_ident) + }, + }; + + let raw_ident = ::syntax::print::pprust::ty_to_string(&codegen::strip_ptr(&field.ty)); + let range_ident = builder.id(format!("r{}", index)); + + let error_message = "Error serializing member: ".to_owned() + &::syntax::print::pprust::expr_to_string(&member_expr); + let _error_message_literal = builder.expr().lit().str::<&str>(&error_message); + + match raw_ident.as_ref() { + "u8" => { + write_stmts.push(quote_stmt!(cx, let next_line = offset + 1;).expect("stmt4")); + write_stmts.push(quote_stmt!(cx, buffer[offset] = $member_expr; ).expect("stm5")); + }, + "[u8]" => { + write_stmts.push(quote_stmt!(cx, let size = $member_expr .len();).unwrap()); + write_stmts.push(quote_stmt!(cx, let next_line = offset + size;).unwrap()); + write_stmts.push(quote_stmt!(cx, length_stack.push_back(size);).unwrap()); + write_stmts.push(quote_stmt!(cx, let $range_ident = offset..next_line; ).unwrap()); + post_write_stmts.push(quote_stmt!(cx, buffer[$range_ident].clone_from_slice($member_expr); ).unwrap()); + } + _ => { + write_stmts.push(quote_stmt!(cx, let next_line = offset + match $field_type_ident_qualified::len_params() { + 0 => ::std::mem::size_of::<$field_type_ident>(), + _ => { let size = $member_expr .size(); length_stack.push_back(size); size }, + }).unwrap()); + write_stmts.push(quote_stmt!(cx, let $range_ident = offset..next_line; ).unwrap()); + post_write_stmts.push(quote_stmt!(cx, + if $range_ident.end - $range_ident.start > 0 { + if let Err(e) = $member_expr .to_bytes(&mut buffer[$range_ident], length_stack) { + return Err(e) + }; + } + ).unwrap()); + } + } + + write_stmts.push(quote_stmt!(cx, offset = next_line; ).unwrap()); + + let field_index = builder.id(&format!("{}", index)); + map_stmts.push(quote_stmt!(cx, map[$field_index] = total;).unwrap()); + + match raw_ident.as_ref() { + "u8" => { + map_stmts.push(quote_stmt!(cx, total += 1;).unwrap()); + }, + "[u8]" => { + map_stmts.push(quote_stmt!(cx, let size = length_stack.pop_front().unwrap();).unwrap()); + map_stmts.push(quote_stmt!(cx, total += size;).unwrap()); + }, + _ => { + map_stmts.push(quote_stmt!(cx, let size = match $field_type_ident_qualified::len_params() { + 0 => ::std::mem::size_of::<$field_type_ident>(), + _ => length_stack.pop_front().unwrap(), + }).unwrap()); + map_stmts.push(quote_stmt!(cx, total += size;).unwrap()); + } + } + }; + + let read_expr = match fields.iter().any(|f| codegen::has_ptr(&f.ty)) { + true => { + // cannot create structs with pointers + quote_expr!(cx, Err(::ipc::binary::BinaryConvertError::not_supported())) + }, + false => { + if value_ident.is_some() { + let instance_create = named_fields_sequence(cx, &ty, fields); + quote_expr!(cx, { $map_stmts; $instance_create; Ok(result) }) + } + else { + let map_variant = P(fields_sequence(cx, &ty, fields, &instance_ident.unwrap_or(builder.id("Self")))); + quote_expr!(cx, { $map_stmts; Ok($map_variant) }) + } + }, + }; + + Ok(BinaryExpressions { + size: total_size_expr, + write: quote_expr!(cx, { $write_stmts; $post_write_stmts; Ok(()) } ), + read: read_expr, + }) +} + +#[allow(unreachable_code)] +fn binary_expr_item_struct( + cx: &ExtCtxt, + builder: &aster::AstBuilder, + _impl_generics: &ast::Generics, + ty: P, + span: Span, + variant_data: &ast::VariantData, +) -> Result { + match *variant_data { + ast::VariantData::Tuple(ref fields, _) => { + binary_expr_struct( + cx, + &builder, + ty, + fields, + Some(builder.id("self")), + None, + ) + }, + ast::VariantData::Struct(ref fields, _) => { + binary_expr_struct( + cx, + &builder, + ty, + fields, + Some(builder.id("self")), + None, + ) + }, + _ => { + cx.span_bug(span, + &format!("#[derive(Binary)] Unsupported struct content, expected tuple/struct, found: {:?}", + variant_data)); + Err(Error) as Result + }, + } +} + +fn binary_expr_enum( + cx: &ExtCtxt, + builder: &aster::AstBuilder, + type_ident: Ident, + impl_generics: &ast::Generics, + ty: P, + span: Span, + enum_def: &ast::EnumDef, +) -> Result { + let arms: Vec<_> = try!(enum_def.variants.iter() + .enumerate() + .map(|(variant_index, variant)| { + binary_expr_variant( + cx, + builder, + type_ident, + impl_generics, + ty.clone(), + span, + variant, + variant_index, + ) + }) + .collect()); + + let (size_arms, write_arms, mut read_arms) = ( + arms.iter().map(|x| x.size.clone()).collect::>(), + arms.iter().map(|x| x.write.clone()).collect::>(), + arms.iter().map(|x| x.read.clone()).collect::>()); + + read_arms.push(quote_arm!(cx, _ => { Err(::ipc::BinaryConvertError::variant(buffer[0])) } )); + + Ok(BinaryExpressions { + size: quote_expr!(cx, 1usize + match *self { $size_arms }), + write: quote_expr!(cx, match *self { $write_arms }; ), + read: quote_expr!(cx, match buffer[0] { $read_arms }), + }) +} + +struct BinaryArm { + size: ast::Arm, + write: ast::Arm, + read: ast::Arm, +} + +fn fields_sequence( + ext_cx: &ExtCtxt, + _ty: &P, + fields: &[ast::StructField], + variant_ident: &ast::Ident, +) -> ast::Expr { + use syntax::parse::token; + use syntax::tokenstream::TokenTree::Token; + + let named_members = fields.iter().any(|f| f.ident.is_some()); + + ::quasi::parse_expr_panic(&mut ::syntax::parse::new_parser_from_tts( + ext_cx.parse_sess(), + { + let _sp = ext_cx.call_site(); + let mut tt = ::std::vec::Vec::new(); + tt.push(Token(_sp, token::Ident(variant_ident.clone()))); + if named_members { + tt.push(Token(_sp, token::OpenDelim(token::Brace))); + } + else { + tt.push(Token(_sp, token::OpenDelim(token::Paren))); + } + + for (idx, field) in fields.iter().enumerate() { + if field.ident.is_some() { + tt.push(Token(_sp, token::Ident(field.ident.clone().unwrap()))); + tt.push(Token(_sp, token::Colon)); + } + + // special case for u8, it just takes byte form sequence + if ::syntax::print::pprust::ty_to_string(&field.ty) == "u8" { + tt.push(Token(_sp, token::Ident(ext_cx.ident_of("buffer")))); + + tt.push(Token(_sp, token::OpenDelim(token::Bracket))); + tt.push(Token(_sp, token::Ident(ext_cx.ident_of("map")))); + tt.push(Token(_sp, token::OpenDelim(token::Bracket))); + tt.push(Token(_sp, token::Ident(ext_cx.ident_of(&format!("{}", idx))))); + tt.push(Token(_sp, token::CloseDelim(token::Bracket))); + tt.push(Token(_sp, token::CloseDelim(token::Bracket))); + + tt.push(Token(_sp, token::Comma)); + continue; + } + + // special case for [u8], it just takes a byte sequence + if ::syntax::print::pprust::ty_to_string(&field.ty) == "[u8]" { + tt.push(Token(_sp, token::Ident(ext_cx.ident_of("buffer")))); + + tt.push(Token(_sp, token::OpenDelim(token::Bracket))); + tt.push(Token(_sp, token::Ident(ext_cx.ident_of("map")))); + tt.push(Token(_sp, token::OpenDelim(token::Bracket))); + tt.push(Token(_sp, token::Ident(ext_cx.ident_of(&format!("{}", idx))))); + tt.push(Token(_sp, token::CloseDelim(token::Bracket))); + tt.push(Token(_sp, token::DotDot)); + + if idx+1 != fields.len() { + tt.push(Token(_sp, token::Ident(ext_cx.ident_of("map")))); + tt.push(Token(_sp, token::OpenDelim(token::Bracket))); + tt.push(Token(_sp, token::Ident(ext_cx.ident_of(&format!("{}", idx+1))))); + tt.push(Token(_sp, token::CloseDelim(token::Bracket))); + } + + tt.push(Token(_sp, token::CloseDelim(token::Bracket))); + + tt.push(Token(_sp, token::Comma)); + continue; + } + + tt.push(Token(_sp, token::Ident(ext_cx.ident_of("try!")))); + tt.push(Token(_sp, token::OpenDelim(token::Paren))); + tt.push( + Token( + _sp, + token::Ident(ext_cx.ident_of(&replace_qualified(&::syntax::print::pprust::ty_to_string(&field.ty)))) + )); + tt.push(Token(_sp, token::ModSep)); + tt.push(Token(_sp, token::Ident(ext_cx.ident_of("from_bytes")))); + tt.push(Token(_sp, token::OpenDelim(token::Paren))); + + tt.push(Token(_sp, token::BinOp(token::And))); + + tt.push(Token(_sp, token::Ident(ext_cx.ident_of("buffer")))); + + tt.push(Token(_sp, token::OpenDelim(token::Bracket))); + tt.push(Token(_sp, token::Ident(ext_cx.ident_of("map")))); + tt.push(Token(_sp, token::OpenDelim(token::Bracket))); + tt.push(Token(_sp, token::Ident(ext_cx.ident_of(&format!("{}", idx))))); + tt.push(Token(_sp, token::CloseDelim(token::Bracket))); + tt.push(Token(_sp, token::DotDot)); + + if idx+1 != fields.len() { + tt.push(Token(_sp, token::Ident(ext_cx.ident_of("map")))); + tt.push(Token(_sp, token::OpenDelim(token::Bracket))); + tt.push(Token(_sp, token::Ident(ext_cx.ident_of(&format!("{}", idx+1))))); + tt.push(Token(_sp, token::CloseDelim(token::Bracket))); + } + + tt.push(Token(_sp, token::CloseDelim(token::Bracket))); + + tt.push(Token(_sp, token::Comma)); + tt.push(Token(_sp, token::Ident(ext_cx.ident_of("length_stack")))); + tt.push(Token(_sp, token::CloseDelim(token::Paren))); + + // name member if it has resulted in the error + tt.push(Token(_sp, token::Dot)); + tt.push(Token(_sp, token::Ident(ext_cx.ident_of("map_err")))); + + tt.push(Token(_sp, token::OpenDelim(token::Paren))); + tt.push(Token(_sp, token::BinOp(token::Or))); + tt.push(Token(_sp, token::Ident(ext_cx.ident_of("e")))); + tt.push(Token(_sp, token::BinOp(token::Or))); + tt.push(Token(_sp, token::Ident(ext_cx.ident_of("e")))); + tt.push(Token(_sp, token::Dot)); + tt.push(Token(_sp, token::Ident(ext_cx.ident_of("named")))); + tt.push(Token(_sp, token::OpenDelim(token::Paren))); + tt.push(Token(_sp, token::Literal(token::Lit::Str_( + field.ident.unwrap_or(ext_cx.ident_of(&format!("f{}", idx))).name), + None)) + ); + tt.push(Token(_sp, token::CloseDelim(token::Paren))); + tt.push(Token(_sp, token::CloseDelim(token::Paren))); + + tt.push(Token(_sp, token::CloseDelim(token::Paren))); + + tt.push(Token(_sp, token::Comma)); + } + if named_members { + tt.push(Token(_sp, token::CloseDelim(token::Brace))); + } + else { + tt.push(Token(_sp, token::CloseDelim(token::Paren))); + } + tt + }) + ).unwrap() +} + +fn named_fields_sequence( + ext_cx: &ExtCtxt, + ty: &P, + fields: &[ast::StructField], +) -> ast::Stmt { + use syntax::parse::token; + use syntax::tokenstream::TokenTree::Token; + + ::quasi::parse_stmt_panic(&mut ::syntax::parse::new_parser_from_tts( + ext_cx.parse_sess(), + { + let _sp = ext_cx.call_site(); + let mut tt = ::std::vec::Vec::new(); + tt.push(Token(_sp, token::Ident(ext_cx.ident_of("let")))); + tt.push(Token(_sp, token::Ident(ext_cx.ident_of("result")))); + tt.push(Token(_sp, token::Eq)); + + tt.push(Token( + _sp, + token::Ident( + ext_cx.ident_of(&::syntax::print::pprust::ty_to_string(ty)) + ))); + + tt.push(Token(_sp, token::OpenDelim(token::Brace))); + + for (idx, field) in fields.iter().enumerate() { + tt.push(Token(_sp, match field.ident { + Some(ident) => token::Ident(ident), + None => token::Ident(ext_cx.ident_of(&format!("{}", idx))), + })); + tt.push(Token(_sp, token::Colon)); + + // special case for u8, it just takes byte form sequence + if ::syntax::print::pprust::ty_to_string(&field.ty) == "u8" { + tt.push(Token(_sp, token::Ident(ext_cx.ident_of("buffer")))); + + tt.push(Token(_sp, token::OpenDelim(token::Bracket))); + tt.push(Token(_sp, token::Ident(ext_cx.ident_of("map")))); + tt.push(Token(_sp, token::OpenDelim(token::Bracket))); + tt.push(Token(_sp, token::Ident(ext_cx.ident_of(&format!("{}", idx))))); + tt.push(Token(_sp, token::CloseDelim(token::Bracket))); + tt.push(Token(_sp, token::CloseDelim(token::Bracket))); + + tt.push(Token(_sp, token::Comma)); + continue; + } + + // special case for [u8], it just takes a byte sequence + if ::syntax::print::pprust::ty_to_string(&field.ty) == "[u8]" { + tt.push(Token(_sp, token::Ident(ext_cx.ident_of("buffer")))); + + tt.push(Token(_sp, token::OpenDelim(token::Bracket))); + tt.push(Token(_sp, token::Ident(ext_cx.ident_of("map")))); + tt.push(Token(_sp, token::OpenDelim(token::Bracket))); + tt.push(Token(_sp, token::Ident(ext_cx.ident_of(&format!("{}", idx))))); + tt.push(Token(_sp, token::CloseDelim(token::Bracket))); + tt.push(Token(_sp, token::DotDot)); + + if idx+1 != fields.len() { + tt.push(Token(_sp, token::Ident(ext_cx.ident_of("map")))); + tt.push(Token(_sp, token::OpenDelim(token::Bracket))); + tt.push(Token(_sp, token::Ident(ext_cx.ident_of(&format!("{}", idx+1))))); + tt.push(Token(_sp, token::CloseDelim(token::Bracket))); + } + + tt.push(Token(_sp, token::CloseDelim(token::Bracket))); + + tt.push(Token(_sp, token::Comma)); + continue; + } + + tt.push(Token(_sp, token::Ident(ext_cx.ident_of("try!")))); + tt.push(Token(_sp, token::OpenDelim(token::Paren))); + tt.push(Token( + _sp, + token::Ident( + ext_cx.ident_of(&replace_qualified(&::syntax::print::pprust::ty_to_string(&field.ty))) + ))); + tt.push(Token(_sp, token::ModSep)); + tt.push(Token(_sp, token::Ident(ext_cx.ident_of("from_bytes")))); + tt.push(Token(_sp, token::OpenDelim(token::Paren))); + + tt.push(Token(_sp, token::BinOp(token::And))); + tt.push(Token(_sp, token::Ident(ext_cx.ident_of("buffer")))); + + tt.push(Token(_sp, token::OpenDelim(token::Bracket))); + tt.push(Token(_sp, token::Ident(ext_cx.ident_of("map")))); + tt.push(Token(_sp, token::OpenDelim(token::Bracket))); + tt.push(Token(_sp, token::Ident(ext_cx.ident_of(&format!("{}", idx))))); + tt.push(Token(_sp, token::CloseDelim(token::Bracket))); + tt.push(Token(_sp, token::DotDot)); + if idx + 1 != fields.len() { + tt.push(Token(_sp, token::Ident(ext_cx.ident_of("map")))); + tt.push(Token(_sp, token::OpenDelim(token::Bracket))); + tt.push(Token(_sp, token::Ident(ext_cx.ident_of(&format!("{}", idx+1))))); + tt.push(Token(_sp, token::CloseDelim(token::Bracket))); + } + + tt.push(Token(_sp, token::CloseDelim(token::Bracket))); + + tt.push(Token(_sp, token::Comma)); + tt.push(Token(_sp, token::Ident(ext_cx.ident_of("length_stack")))); + tt.push(Token(_sp, token::CloseDelim(token::Paren))); + + // name member if it has resulted in the error + tt.push(Token(_sp, token::Dot)); + tt.push(Token(_sp, token::Ident(ext_cx.ident_of("map_err")))); + tt.push(Token(_sp, token::OpenDelim(token::Paren))); + tt.push(Token(_sp, token::BinOp(token::Or))); + tt.push(Token(_sp, token::Ident(ext_cx.ident_of("e")))); + tt.push(Token(_sp, token::BinOp(token::Or))); + tt.push(Token(_sp, token::Ident(ext_cx.ident_of("e")))); + tt.push(Token(_sp, token::Dot)); + tt.push(Token(_sp, token::Ident(ext_cx.ident_of("named")))); + tt.push(Token(_sp, token::OpenDelim(token::Paren))); + tt.push(Token(_sp, token::Literal(token::Lit::Str_( + field.ident.unwrap_or(ext_cx.ident_of(&format!("f{}", idx))).name), + None)) + ); + tt.push(Token(_sp, token::CloseDelim(token::Paren))); + tt.push(Token(_sp, token::CloseDelim(token::Paren))); + + tt.push(Token(_sp, token::CloseDelim(token::Paren))); + tt.push(Token(_sp, token::Comma)); + } + + tt.push(Token(_sp, token::CloseDelim(token::Brace))); + tt + }) + ).unwrap() +} + +fn binary_expr_variant( + cx: &ExtCtxt, + builder: &aster::AstBuilder, + type_ident: Ident, + _generics: &ast::Generics, + ty: P, + _span: Span, + variant: &ast::Variant, + variant_index: usize, +) -> Result { + let variant_ident = variant.node.name; + let variant_index_ident = builder.id(format!("{}", variant_index)); + + match variant.node.data { + ast::VariantData::Unit(_) => { + let pat = builder.pat().path() + .id(type_ident).id(variant_ident) + .build(); + + let variant_val = builder.id(format!("{}::{}", type_ident, variant_ident)); + + Ok(BinaryArm { + size: quote_arm!(cx, $pat => { 0usize } ), + write: quote_arm!(cx, $pat => { buffer[0] = $variant_index_ident; Ok(()) } ), + read: quote_arm!(cx, $variant_index_ident => { Ok($variant_val) } ), + }) + }, + ast::VariantData::Tuple(ref fields, _) => { + let field_names: Vec = (0 .. fields.len()) + .map(|i| builder.id(format!("__field{}", i))) + .collect(); + + let pat = builder.pat().enum_() + .id(type_ident).id(variant_ident).build() + .with_pats( + field_names.iter() + .map(|field| builder.pat().ref_id(field)) + ) + .build(); + + let binary_expr = try!(binary_expr_struct( + cx, + &builder, + ty, + fields, + None, + Some(builder.id(format!("{}::{}", type_ident, variant_ident))), + )); + + let (size_expr, write_expr, read_expr) = (binary_expr.size, vec![binary_expr.write], binary_expr.read); + Ok(BinaryArm { + size: quote_arm!(cx, $pat => { $size_expr } ), + write: quote_arm!(cx, + $pat => { + buffer[0] = $variant_index_ident; + let buffer = &mut buffer[1..]; + $write_expr + }), + read: quote_arm!(cx, + $variant_index_ident => { + let buffer = &buffer[1..]; + $read_expr + } + ), + }) + }, + ast::VariantData::Struct(ref fields, _) => { + let field_names: Vec<_> = (0 .. fields.len()) + .map(|i| builder.id(format!("__field{}", i))) + .collect(); + + let pat = builder.pat().struct_() + .id(type_ident).id(variant_ident).build() + .with_pats( + field_names.iter() + .zip(fields.iter()) + .map(|(id, field)|(field.ident.unwrap(), builder.pat().ref_id(id)))) + .build(); + + let binary_expr = try!(binary_expr_struct( + cx, + &builder, + ty, + fields, + None, + Some(builder.id(format!("{}::{}", type_ident, variant_ident))), + )); + + let (size_expr, write_expr, read_expr) = (binary_expr.size, vec![binary_expr.write], binary_expr.read); + + Ok(BinaryArm { + size: quote_arm!(cx, $pat => { $size_expr } ), + write: quote_arm!(cx, + $pat => { + buffer[0] = $variant_index_ident; + let buffer = &mut buffer[1..]; + $write_expr + }), + read: quote_arm!(cx, + $variant_index_ident => { + let buffer = &buffer[1..]; + $read_expr + } + ), + }) + }, + } +} diff --git a/ipc/hypervisor/Cargo.toml b/ipc/hypervisor/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..1db60c904e67a612ace957ec983eb36bfbfdcf4f --- /dev/null +++ b/ipc/hypervisor/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "ethcore-ipc-hypervisor" +version = "1.2.0" +authors = ["Parity Technologies "] +license = "GPL-3.0" +build = "build.rs" + +[features] + +[dependencies] +ethcore-ipc = { path = "../rpc" } +nanomsg = { git = "https://github.com/paritytech/nanomsg.rs.git", branch = "parity-1.7" } +ethcore-ipc-nano = { path = "../nano" } +semver = "0.6" +log = "0.3" +time = "0.1" + +[build-dependencies] +ethcore-ipc-codegen = { path = "../codegen" } diff --git a/ipc/hypervisor/build.rs b/ipc/hypervisor/build.rs new file mode 100644 index 0000000000000000000000000000000000000000..097aba0464ba18220fb5d00c85682199f43c0c5a --- /dev/null +++ b/ipc/hypervisor/build.rs @@ -0,0 +1,21 @@ +// 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 . + +extern crate ethcore_ipc_codegen; + +fn main() { + ethcore_ipc_codegen::derive_ipc("src/service.rs.in").unwrap(); +} diff --git a/ipc/hypervisor/src/lib.rs b/ipc/hypervisor/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..b522122b5b87d1c379e9e8b57555564098569ac4 --- /dev/null +++ b/ipc/hypervisor/src/lib.rs @@ -0,0 +1,273 @@ +// 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 . + +//! Parity interprocess hypervisor module + +#![cfg_attr(feature="dev", allow(used_underscore_binding))] + +extern crate ethcore_ipc as ipc; +extern crate ethcore_ipc_nano as nanoipc; +extern crate semver; +#[macro_use] extern crate log; +extern crate time; + +pub mod service; + +/// Default value for hypervisor ipc listener +pub const HYPERVISOR_IPC_URL: &'static str = "parity-internal-hyper-status.ipc"; + +use std::sync::{Arc,RwLock}; +use service::{HypervisorService, IpcModuleId}; +use std::process::{Command,Child}; +use std::collections::HashMap; + +pub use service::{HypervisorServiceClient, ControlService, CLIENT_MODULE_ID, SYNC_MODULE_ID}; + +pub type BinaryId = &'static str; + +pub struct Hypervisor { + ipc_addr: String, + service: Arc, + ipc_worker: RwLock>, + processes: RwLock>, + modules: HashMap, + pub io_path: String, +} + +/// Boot arguments for binary +pub struct BootArgs { + cli: Option>, + stdin: Option>, +} + +impl BootArgs { + /// New empty boot arguments + pub fn new() -> BootArgs { + BootArgs { + cli: None, + stdin: None, + } + } + + /// Set command-line arguments for boot + pub fn cli(mut self, cli: Vec) -> BootArgs { + self.cli = Some(cli); + self + } + + /// Set std-in stream for boot + pub fn stdin(mut self, stdin: Vec) -> BootArgs { + self.stdin = Some(stdin); + self + } +} + +impl Hypervisor { + /// initializes the Hypervisor service with the open ipc socket for incoming clients + pub fn new() -> Hypervisor { + Hypervisor::with_url(HYPERVISOR_IPC_URL) + } + + pub fn module(mut self, module_id: IpcModuleId, args: BootArgs) -> Hypervisor { + self.modules.insert(module_id, args); + self.service.add_module(module_id); + self + } + + pub fn local_module(self, module_id: IpcModuleId) -> Hypervisor { + self.service.add_module(module_id); + self + } + + pub fn io_path(mut self, directory: &str) -> Hypervisor { + self.io_path = directory.to_owned(); + self + } + + /// Starts with the specified address for the ipc listener and + /// the specified list of modules in form of created service + pub fn with_url(addr: &str) -> Hypervisor { + let service = HypervisorService::new(); + let worker = nanoipc::Worker::new(&service); + Hypervisor{ + ipc_addr: addr.to_owned(), + service: service, + ipc_worker: RwLock::new(worker), + processes: RwLock::new(HashMap::new()), + modules: HashMap::new(), + io_path: "/tmp".to_owned(), + } + } + + /// Since one binary can host multiple modules + /// we match binaries + fn match_module(&self, module_id: &IpcModuleId) -> Option<&BootArgs> { + self.modules.get(module_id) + } + + /// Creates IPC listener and starts all binaries + pub fn start(&self) { + let mut worker = self.ipc_worker.write().unwrap(); + worker.add_reqrep(&self.ipc_addr).unwrap_or_else(|e| panic!("Hypervisor ipc worker can not start - critical! ({:?})", e)); + + for module_id in self.service.module_ids() { + self.start_module(module_id); + } + } + + /// Start binary for the specified module + /// Does nothing when it is already started on module is inside the + /// main binary + fn start_module(&self, module_id: IpcModuleId) { + use std::io::Write; + + self.match_module(&module_id).map(|boot_args| { + let mut processes = self.processes.write().unwrap(); + { + if processes.get(&module_id).is_some() { + // already started for another module + return; + } + } + + let mut command = Command::new(&std::env::current_exe().unwrap()); + command.stderr(std::process::Stdio::inherit()); + + if let Some(ref cli_args) = boot_args.cli { + for arg in cli_args { command.arg(arg); } + } + + command.stdin(std::process::Stdio::piped()); + + trace!(target: "hypervisor", "Spawn executable: {:?}", command); + + let mut child = command.spawn().unwrap_or_else( + |e| panic!("Hypervisor cannot execute command ({:?}): {}", command, e)); + + if let Some(ref std_in) = boot_args.stdin { + trace!(target: "hypervisor", "Pushing std-in payload..."); + child.stdin.as_mut() + .expect("std-in should be piped above") + .write(std_in) + .unwrap_or_else(|e| panic!(format!("Error trying to pipe stdin for {:?}: {:?}", &command, e))); + drop(child.stdin.take()); + } + + processes.insert(module_id, child); + }); + } + + /// Reports if all modules are checked in + pub fn modules_ready(&self) -> bool { + self.service.unchecked_count() == 0 + } + + pub fn modules_shutdown(&self) -> bool { + self.service.running_count() == 0 + } + + /// Waits for every required module to check in + pub fn wait_for_startup(&self) { + let mut worker = self.ipc_worker.write().unwrap(); + while !self.modules_ready() { + worker.poll() + } + } + + /// Waits for every required module to check in + pub fn wait_for_shutdown(&self) -> bool { + use time::{PreciseTime, Duration}; + + let mut worker = self.ipc_worker.write().unwrap(); + let start = PreciseTime::now(); + while !self.modules_shutdown() { + worker.poll(); + if start.to(PreciseTime::now()) > Duration::seconds(30) { + warn!("Some modules failed to shutdown gracefully, they will be terminated."); + break; + } + } + self.modules_shutdown() + } + + /// Shutdown the ipc and all managed child processes + pub fn shutdown(&self) { + let mut childs = self.processes.write().unwrap(); + for (ref module, _) in childs.iter() { + trace!(target: "hypervisor", "Stopping process module: {}", module); + self.service.send_shutdown(**module); + } + trace!(target: "hypervisor", "Waiting for shutdown..."); + if self.wait_for_shutdown() { + trace!(target: "hypervisor", "All modules reported shutdown"); + return; + } + + for (ref module, ref mut process) in childs.iter_mut() { + if self.service.is_running(**module) { + process.kill().unwrap(); + trace!("Terminated {}", module); + } + } + } +} + +impl Drop for Hypervisor { + fn drop(&mut self) { + self.shutdown(); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::sync::atomic::{AtomicBool,Ordering}; + use std::sync::Arc; + use nanoipc; + + #[test] + fn can_init() { + let url = "ipc:///tmp/test-parity-hypervisor-10.ipc"; + let test_module_id = 8080u64; + + let hypervisor = Hypervisor::with_url(url).local_module(test_module_id); + assert_eq!(false, hypervisor.modules_ready()); + } + + #[test] + fn can_wait_for_startup() { + let url = "ipc:///tmp/test-parity-hypervisor-20.ipc"; + let test_module_id = 8080u64; + + let hypervisor_ready = Arc::new(AtomicBool::new(false)); + let hypervisor_ready_local = hypervisor_ready.clone(); + + ::std::thread::spawn(move || { + while !hypervisor_ready.load(Ordering::Relaxed) { } + + let client = nanoipc::fast_client::>(url).unwrap(); + client.handshake().unwrap(); + client.module_ready(test_module_id, url.to_owned()); + }); + + let hypervisor = Hypervisor::with_url(url).local_module(test_module_id); + hypervisor.start(); + hypervisor_ready_local.store(true, Ordering::Relaxed); + hypervisor.wait_for_startup(); + + assert_eq!(true, hypervisor.modules_ready()); + } +} diff --git a/ipc/hypervisor/src/service.rs b/ipc/hypervisor/src/service.rs new file mode 100644 index 0000000000000000000000000000000000000000..59040251e6452018c4a99454e842c54cb26a969f --- /dev/null +++ b/ipc/hypervisor/src/service.rs @@ -0,0 +1,20 @@ +// 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 . + +//! Parity interprocess hypervisor IPC service +#![allow(dead_code, unused_assignments, unused_variables)] // codegen issues + +include!(concat!(env!("OUT_DIR"), "/service.rs.in")); diff --git a/ipc/hypervisor/src/service.rs.in b/ipc/hypervisor/src/service.rs.in new file mode 100644 index 0000000000000000000000000000000000000000..6d6f382680cd6632060d3a0d6f4783b3099141bb --- /dev/null +++ b/ipc/hypervisor/src/service.rs.in @@ -0,0 +1,125 @@ +// 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 std::sync::{RwLock,Arc}; +use ipc::IpcConfig; +use std::collections::HashMap; +use nanoipc; + +pub type IpcModuleId = u64; + +/// Blockhain database module id +pub const CLIENT_MODULE_ID: IpcModuleId = 2000; + +/// Sync module id +pub const SYNC_MODULE_ID: IpcModuleId = 2100; + +/// IPC service that handles module management +pub struct HypervisorService { + modules: RwLock>, +} + +#[derive(Default)] +pub struct ModuleState { + started: bool, + control_url: String, + shutdown: bool, +} + +#[ipc] +pub trait ControlService { + fn shutdown(&self) -> bool; +} + +#[ipc] +impl HypervisorService { + // return type for making method synchronous + fn module_ready(&self, module_id: u64, control_url: String) -> bool { + let mut modules = self.modules.write().unwrap(); + modules.get_mut(&module_id).map(|mut module| { + module.started = true; + module.control_url = control_url; + }); + trace!(target: "hypervisor", "Module ready: {}", module_id); + true + } + + // return type for making method synchronous + fn module_shutdown(&self, module_id: u64) -> bool { + let mut modules = self.modules.write().unwrap(); + modules.get_mut(&module_id).map(|mut module| { + module.shutdown = true; + }); + trace!(target: "hypervisor", "Module shutdown: {}", module_id); + true + } +} + +impl HypervisorService { + /// New service with the default list of modules + pub fn new() -> Arc { + HypervisorService::with_modules(vec![]) + } + + /// New service with list of modules that will report for being ready + pub fn with_modules(module_ids: Vec) -> Arc { + let mut modules = HashMap::new(); + for module_id in module_ids { + modules.insert(module_id, ModuleState::default()); + } + Arc::new(HypervisorService { + modules: RwLock::new(modules), + }) + } + + /// Add the module to the check-list + pub fn add_module(&self, module_id: IpcModuleId) { + self.modules.write().unwrap().insert(module_id, ModuleState::default()); + } + + /// Number of modules still being waited for check-in + pub fn unchecked_count(&self) -> usize { + self.modules.read().unwrap().iter().filter(|&(_, module)| !module.started).count() + } + + /// List of all modules within this service + pub fn module_ids(&self) -> Vec { + self.modules.read().unwrap().iter().map(|(module_id, _)| module_id).cloned().collect() + } + + /// Number of modules started and running + pub fn running_count(&self) -> usize { + self.modules.read().unwrap().iter().filter(|&(_, module)| module.started && !module.shutdown).count() + } + + pub fn is_running(&self, id: IpcModuleId) -> bool { + self.modules.read().unwrap().get(&id).map(|module| module.started && !module.shutdown).unwrap_or(false) + } + + pub fn send_shutdown(&self, module_id: IpcModuleId) { + let modules = self.modules.read().unwrap(); + modules.get(&module_id).map(|module| { + trace!(target: "hypervisor", "Sending shutdown to {}({})", module_id, &module.control_url); + let client = nanoipc::fast_client::>(&module.control_url).unwrap(); + client.shutdown(); + trace!(target: "hypervisor", "Sent shutdown to {}", module_id); + }); + } +} + +impl ::ipc::IpcConfig for HypervisorService {} + +impl ::ipc::IpcConfig for ControlService {} diff --git a/ipc/nano/Cargo.toml b/ipc/nano/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..a714e1d008da60ab0f91167e1a3558dc1074ac27 --- /dev/null +++ b/ipc/nano/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "ethcore-ipc-nano" +version = "1.8.0" +authors = ["Parity Technologies "] +license = "GPL-3.0" + +[features] + +[dependencies] +ethcore-ipc = { path = "../rpc" } +nanomsg = { git = "https://github.com/paritytech/nanomsg.rs.git", branch = "parity-1.7" } +log = "0.3" +lazy_static = "0.2" diff --git a/ipc/nano/src/lib.rs b/ipc/nano/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..9be3d2b1d26a3fd660eb0613d4747f0ea5c198a9 --- /dev/null +++ b/ipc/nano/src/lib.rs @@ -0,0 +1,355 @@ +// 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 . + +//! IPC over nanomsg transport + +extern crate ethcore_ipc as ipc; +extern crate nanomsg; +#[macro_use] extern crate log; +#[macro_use] extern crate lazy_static; + +pub use ipc::{WithSocket, IpcInterface, IpcConfig}; +pub use nanomsg::Socket as NanoSocket; + +use std::sync::*; +use nanomsg::{Socket, Protocol, Error, Endpoint, PollRequest, PollFd, PollInOut}; +use std::ops::Deref; + +const POLL_TIMEOUT: isize = 200; +const DEFAULT_CONNECTION_TIMEOUT: isize = 30000; +const DEBUG_CONNECTION_TIMEOUT: isize = 5000; + +/// Generic worker to handle service (binded) sockets +pub struct Worker where S: IpcInterface { + service: Arc, + sockets: Vec<(Socket, Endpoint)>, + polls: Vec, + buf: Vec, +} + +/// struct for guarding `_endpoint` (so that it wont drop) +/// derefs to client `S` +pub struct GuardedSocket where S: WithSocket { + client: Arc, + _endpoint: Endpoint, +} + +impl GuardedSocket where S: WithSocket { + pub fn service(&self) -> Arc { + self.client.clone() + } +} + +impl Deref for GuardedSocket where S: WithSocket { + type Target = Arc; + + fn deref(&self) -> &Arc { + &self.client + } +} + +/// Spawns client <`S`> over specified address +/// creates socket and connects endpoint to it +/// for duplex (paired) connections with the service +pub fn init_duplex_client(socket_addr: &str) -> Result, SocketError> where S: WithSocket { + let mut socket = Socket::new(Protocol::Pair).map_err(|e| { + warn!(target: "ipc", "Failed to create ipc socket: {:?}", e); + SocketError::DuplexLink + })?; + + socket.set_receive_timeout(DEFAULT_CONNECTION_TIMEOUT).unwrap(); + + let endpoint = socket.connect(socket_addr).map_err(|e| { + warn!(target: "ipc", "Failed to bind socket to address '{}': {:?}", socket_addr, e); + SocketError::DuplexLink + })?; + + Ok(GuardedSocket { + client: Arc::new(S::init(socket)), + _endpoint: endpoint, + }) +} + +/// Spawns client <`S`> over specified address +/// creates socket and connects endpoint to it +/// for request-reply connections to the service +pub fn client(socket_addr: &str, receive_timeout: Option) -> Result, SocketError> where S: WithSocket { + let mut socket = Socket::new(Protocol::Req).map_err(|e| { + warn!(target: "ipc", "Failed to create ipc socket: {:?}", e); + SocketError::RequestLink + })?; + + if let Some(timeout) = receive_timeout { + socket.set_receive_timeout(timeout).unwrap(); + } + + let endpoint = socket.connect(socket_addr).map_err(|e| { + warn!(target: "ipc", "Failed to bind socket to address '{}': {:?}", socket_addr, e); + SocketError::RequestLink + })?; + + trace!(target: "ipc", "Created client for {}", socket_addr); + Ok(GuardedSocket { + client: Arc::new(S::init(socket)), + _endpoint: endpoint, + }) +} + +lazy_static! { + /// Set PARITY_IPC_DEBUG=1 for fail-fast connectivity problems diagnostic + pub static ref DEBUG_FLAG: bool = { + use std::env; + + if let Ok(debug) = env::var("PARITY_IPC_DEBUG") { + debug == "1" || debug.to_uppercase() == "TRUE" + } + else { false } + }; +} + +/// Client with no default timeout on operations +pub fn generic_client(socket_addr: &str) -> Result, SocketError> where S: WithSocket { + if *DEBUG_FLAG { + client(socket_addr, Some(DEBUG_CONNECTION_TIMEOUT)) + } else { + client(socket_addr, None) + } +} + +/// Client over interface that is supposed to give quick almost non-blocking responses +pub fn fast_client(socket_addr: &str) -> Result, SocketError> where S: WithSocket { + if *DEBUG_FLAG { + client(socket_addr, Some(DEBUG_CONNECTION_TIMEOUT)) + } else { + client(socket_addr, Some(DEFAULT_CONNECTION_TIMEOUT)) + } +} + +/// Error occurred while establising socket or endpoint +#[derive(Debug)] +pub enum SocketError { + /// Error establising duplex (paired) socket and/or endpoint + DuplexLink, + /// Error establising duplex (paired) socket and/or endpoint + RequestLink, +} + +impl Worker where S: IpcInterface { + /// New worker over specified `service` + pub fn new(service: &Arc) -> Worker { + Worker:: { + service: service.clone(), + sockets: Vec::new(), + polls: Vec::new(), + buf: Vec::new(), + } + } + + /// Polls all sockets, reads and dispatches method invocations + pub fn poll(&mut self) { + use std::io::Write; + + let mut request = PollRequest::new(&mut self.polls[..]); + let _result_guard = Socket::poll(&mut request, POLL_TIMEOUT); + + for (fd_index, fd) in request.get_fds().iter().enumerate() { + if fd.can_read() { + let (ref mut socket, _) = self.sockets[fd_index]; + unsafe { self.buf.set_len(0); } + match socket.nb_read_to_end(&mut self.buf) { + Ok(method_sign_len) => { + if method_sign_len >= 2 { + + // method_num + let method_num = self.buf[0] as u16 * 256 + self.buf[1] as u16; + // payload + let payload = &self.buf[2..]; + + // dispatching for ipc interface + let result = self.service.dispatch_buf(method_num, payload); + + if let Err(e) = socket.write(&result) { + warn!(target: "ipc", "Failed to write response: {:?}", e); + } + } + else { + warn!(target: "ipc", "Failed to read method signature from socket: unexpected message length({})", method_sign_len); + } + }, + Err(Error::TryAgain) => { + }, + Err(x) => { + warn!(target: "ipc", "Error polling connections {:?}", x); + panic!(); + } + } + } + } + } + + /// Stores nanomsg poll request for reuse + fn rebuild_poll_request(&mut self) { + self.polls = self.sockets.iter() + .map(|&(ref socket, _)| socket.new_pollfd(PollInOut::In)) + .collect::>(); + } + + /// Add exclusive socket for paired client + /// Only one connection over this address is allowed + pub fn add_duplex(&mut self, addr: &str) -> Result<(), SocketError> { + let mut socket = Socket::new(Protocol::Pair).map_err(|e| { + warn!(target: "ipc", "Failed to create ipc socket: {:?}", e); + SocketError::DuplexLink + })?; + + let endpoint = socket.bind(addr).map_err(|e| { + warn!(target: "ipc", "Failed to bind socket to address '{}': {:?}", addr, e); + SocketError::DuplexLink + })?; + + self.sockets.push((socket, endpoint)); + + self.rebuild_poll_request(); + + trace!(target: "ipc", "Started duplex worker at {}", addr); + + Ok(()) + } + + /// Add generic socket for request-reply style communications + /// with multiple clients + pub fn add_reqrep(&mut self, addr: &str) -> Result<(), SocketError> { + let mut socket = Socket::new(Protocol::Rep).map_err(|e| { + warn!(target: "ipc", "Failed to create ipc socket: {:?}", e); + SocketError::DuplexLink + })?; + + + let endpoint = socket.bind(addr).map_err(|e| { + warn!(target: "ipc", "Failed to bind socket to address '{}': {:?}", addr, e); + SocketError::DuplexLink + })?; + + self.sockets.push((socket, endpoint)); + + self.rebuild_poll_request(); + + trace!(target: "ipc", "Started request-reply worker at {}", addr); + Ok(()) + } +} + +#[cfg(test)] +mod service_tests { + + use super::Worker; + use ipc::*; + use std::io::{Read, Write}; + use std::sync::{Arc, RwLock}; + use nanomsg::{Socket, Protocol, Endpoint}; + + struct TestInvoke { + method_num: u16, + params: Vec, + } + + struct DummyService { + methods_stack: RwLock>, + } + + impl DummyService { + fn new() -> DummyService { + DummyService { methods_stack: RwLock::new(Vec::new()) } + } + } + + impl IpcInterface for DummyService { + fn dispatch(&self, _r: &mut R) -> Vec where R: Read { + vec![] + } + fn dispatch_buf(&self, method_num: u16, buf: &[u8]) -> Vec { + self.methods_stack.write().unwrap().push( + TestInvoke { + method_num: method_num, + params: buf.to_vec(), + }); + vec![] + } + } + + impl IpcConfig for DummyService {} + + fn dummy_write(addr: &str, buf: &[u8]) -> (Socket, Endpoint) { + let mut socket = Socket::new(Protocol::Pair).unwrap(); + let endpoint = socket.connect(addr).unwrap(); + socket.write(buf).unwrap(); + (socket, endpoint) + } + + #[test] + fn can_create_worker() { + let worker = Worker::::new(&Arc::new(DummyService::new())); + assert_eq!(0, worker.sockets.len()); + } + + #[test] + fn can_add_duplex_socket_to_worker() { + let mut worker = Worker::::new(&Arc::new(DummyService::new())); + worker.add_duplex("ipc:///tmp/parity-test10.ipc").unwrap(); + assert_eq!(1, worker.sockets.len()); + } + + #[test] + fn worker_can_poll_empty() { + let service = Arc::new(DummyService::new()); + let mut worker = Worker::::new(&service); + worker.add_duplex("ipc:///tmp/parity-test20.ipc").unwrap(); + worker.poll(); + assert_eq!(0, service.methods_stack.read().unwrap().len()); + } + + #[test] + fn worker_can_poll() { + let url = "ipc:///tmp/parity-test30.ipc"; + + let mut worker = Worker::::new(&Arc::new(DummyService::new())); + worker.add_duplex(url).unwrap(); + + let (_socket, _endpoint) = dummy_write(url, &vec![0, 0, 7, 7, 6, 6]); + worker.poll(); + + assert_eq!(1, worker.service.methods_stack.read().unwrap().len()); + assert_eq!(0, worker.service.methods_stack.read().unwrap()[0].method_num); + assert_eq!([7, 7, 6, 6], worker.service.methods_stack.read().unwrap()[0].params[..]); + } + + #[test] + fn worker_can_poll_long() { + let url = "ipc:///tmp/parity-test40.ipc"; + + let mut worker = Worker::::new(&Arc::new(DummyService::new())); + worker.add_duplex(url).unwrap(); + + let message = [0u8; 1024*1024]; + + let (_socket, _endpoint) = dummy_write(url, &message); + worker.poll(); + + assert_eq!(1, worker.service.methods_stack.read().unwrap().len()); + assert_eq!(0, worker.service.methods_stack.read().unwrap()[0].method_num); + assert_eq!(vec![0u8; 1024*1024-2], worker.service.methods_stack.read().unwrap()[0].params); + } +} diff --git a/ipc/rpc/Cargo.toml b/ipc/rpc/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..12d73a118bf47dfeac43a1038a57fa41a07f77b0 --- /dev/null +++ b/ipc/rpc/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "ethcore-ipc" +version = "1.8.0" +authors = ["Parity Technologies "] +license = "GPL-3.0" + +[features] + +[dependencies] +ethcore-devtools = { path = "../../devtools" } +nanomsg = { git = "https://github.com/paritytech/nanomsg.rs.git", branch = "parity-1.7" } +ethcore-bigint = { path = "../../util/bigint"} +ethcore-util = { path = "../../util" } +semver = "0.6" diff --git a/ipc/rpc/src/binary.rs b/ipc/rpc/src/binary.rs new file mode 100644 index 0000000000000000000000000000000000000000..6466acdb1394e261eeafa890cd982ea75572fb43 --- /dev/null +++ b/ipc/rpc/src/binary.rs @@ -0,0 +1,1196 @@ +// 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 . + +//! Binary representation of types + +use bigint::prelude::{U256, U512}; +use bigint::hash::{H256, H512, H2048}; +use util::{Address}; +use std::mem; +use std::collections::{VecDeque, BTreeMap}; +use std::ops::Range; +use super::Handshake; + +#[derive(Debug)] +pub enum BinaryConvertErrorKind { + SizeMismatch { + expected: usize, + found: usize, + }, + TargetPayloadEmpty, + UnexpectedVariant(u8), + MissingLengthValue, + InconsistentBoundaries, + NotSupported, +} + +#[derive(Debug)] +pub struct BinaryConvertError { + member_tree: Vec<&'static str>, + kind: BinaryConvertErrorKind, +} + +impl BinaryConvertError { + pub fn size(expected: usize, found: usize) -> BinaryConvertError { + BinaryConvertError { + member_tree: Vec::new(), + kind: BinaryConvertErrorKind::SizeMismatch { + expected: expected, + found: found, + } + } + } + + pub fn empty() -> BinaryConvertError { + BinaryConvertError { member_tree: Vec::new(), kind: BinaryConvertErrorKind::TargetPayloadEmpty } + } + + pub fn variant(val: u8) -> BinaryConvertError { + BinaryConvertError { member_tree: Vec::new(), kind: BinaryConvertErrorKind::UnexpectedVariant(val) } + } + + pub fn length() -> BinaryConvertError { + BinaryConvertError { member_tree: Vec::new(), kind: BinaryConvertErrorKind::MissingLengthValue } + } + + pub fn boundaries() -> BinaryConvertError { + BinaryConvertError { member_tree: Vec::new(), kind: BinaryConvertErrorKind::InconsistentBoundaries } + } + + pub fn not_supported() -> BinaryConvertError { + BinaryConvertError { member_tree: Vec::new(), kind: BinaryConvertErrorKind::NotSupported } + } + + pub fn named(mut self, name: &'static str) -> BinaryConvertError { + self.member_tree.push(name); + self + } +} + +#[derive(Debug)] +pub enum BinaryError { + Serialization(BinaryConvertError), + Io(::std::io::Error), +} + +impl From<::std::io::Error> for BinaryError { + fn from(err: ::std::io::Error) -> Self { BinaryError::Io(err) } +} + +impl From for BinaryError { + fn from(err: BinaryConvertError) -> Self { BinaryError::Serialization(err) } +} + +pub trait BinaryConvertable : Sized { + fn size(&self) -> usize { + mem::size_of::() + } + + fn to_bytes(&self, buffer: &mut [u8], length_stack: &mut VecDeque) -> Result<(), BinaryConvertError>; + + fn from_bytes(buffer: &[u8], length_stack: &mut VecDeque) -> Result; + + fn from_empty_bytes() -> Result { + Err(BinaryConvertError::size(mem::size_of::(), 0)) + } + + fn len_params() -> usize { + 0 + } +} + +impl BinaryConvertable for Option where T: BinaryConvertable { + fn size(&self) -> usize { + match * self { None => 0, Some(ref val) => val.size() } + } + + fn to_bytes(&self, buffer: &mut [u8], length_stack: &mut VecDeque) -> Result<(), BinaryConvertError> { + match *self { None => Err(BinaryConvertError::empty()), Some(ref val) => val.to_bytes(buffer, length_stack) } + } + + fn from_bytes(buffer: &[u8], length_stack: &mut VecDeque) -> Result { + if buffer.len() == 0 { return Self::from_empty_bytes(); } + Ok(Some(T::from_bytes(buffer, length_stack)?)) + } + + fn from_empty_bytes() -> Result { + Ok(None) + } + + fn len_params() -> usize { + 1 + } +} + +impl BinaryConvertable for Result<(), E> { + fn size(&self) -> usize { + match *self { + Ok(_) => 0, + Err(ref e) => e.size(), + } + } + + fn to_bytes(&self, buffer: &mut [u8], length_stack: &mut VecDeque) -> Result<(), BinaryConvertError> { + match *self { + Ok(_) => Err(BinaryConvertError::empty()), + Err(ref e) => Ok(e.to_bytes(buffer, length_stack)?), + } + } + + fn from_bytes(buffer: &[u8], length_stack: &mut VecDeque) -> Result { + Ok(Err(E::from_bytes(&buffer, length_stack)?)) + } + + fn from_empty_bytes() -> Result { + Ok(Ok(())) + } + + fn len_params() -> usize { + 1 + } +} + + +impl BinaryConvertable for Result { + fn size(&self) -> usize { + match *self { + Ok(ref r) => r.size(), + Err(_) => 0, + } + } + + fn to_bytes(&self, buffer: &mut [u8], length_stack: &mut VecDeque) -> Result<(), BinaryConvertError> { + match *self { + Ok(ref r) => Ok(r.to_bytes(buffer, length_stack)?), + Err(_) => Err(BinaryConvertError::empty()), + } + } + + fn from_bytes(buffer: &[u8], length_stack: &mut VecDeque) -> Result { + Ok(Ok(R::from_bytes(&buffer, length_stack)?)) + } + + fn from_empty_bytes() -> Result { + Ok(Err(())) + } + + fn len_params() -> usize { + 1 + } +} + +impl BinaryConvertable for Result { + fn size(&self) -> usize { + 1usize + match *self { + Ok(ref r) => r.size(), + Err(ref e) => e.size(), + } + } + + fn to_bytes(&self, buffer: &mut [u8], length_stack: &mut VecDeque) -> Result<(), BinaryConvertError> { + match *self { + Ok(ref r) => { + buffer[0] = 0; + if r.size() > 0 { + Ok(r.to_bytes(&mut buffer[1..], length_stack)?) + } + else { Ok(()) } + }, + Err(ref e) => { + buffer[0] = 1; + if e.size() > 0 { + Ok(e.to_bytes(&mut buffer[1..], length_stack)?) + } + else { Ok(()) } + }, + } + } + + fn from_bytes(buffer: &[u8], length_stack: &mut VecDeque) -> Result { + match buffer[0] { + 0 => { + match buffer.len() { + 1 => Ok(Ok(R::from_empty_bytes()?)), + _ => Ok(Ok(R::from_bytes(&buffer[1..], length_stack)?)), + } + } + 1 => Ok(Err(E::from_bytes(&buffer[1..], length_stack)?)), + _ => Err(BinaryConvertError::variant(buffer[0])) + } + } + + fn len_params() -> usize { + 1 + } +} + +impl BinaryConvertable for BTreeMap where K : BinaryConvertable + Ord, V: BinaryConvertable { + fn size(&self) -> usize { + 0usize + match K::len_params() { + 0 => mem::size_of::() * self.len(), + _ => self.iter().fold(0usize, |acc, (k, _)| acc + k.size()) + } + match V::len_params() { + 0 => mem::size_of::() * self.len(), + _ => self.iter().fold(0usize, |acc, (_, v)| acc + v.size()) + } + } + + fn to_bytes(&self, buffer: &mut [u8], length_stack: &mut VecDeque) -> Result<(), BinaryConvertError> { + let mut offset = 0usize; + for (key, val) in self.iter() { + let key_size = match K::len_params() { + 0 => mem::size_of::(), + _ => { let size = key.size(); length_stack.push_back(size); size } + }; + let val_size = match K::len_params() { + 0 => mem::size_of::(), + _ => { let size = val.size(); length_stack.push_back(size); size } + }; + + if key_size > 0 { + let item_end = offset + key_size; + key.to_bytes(&mut buffer[offset..item_end], length_stack)?; + offset = item_end; + } + + if val_size > 0 { + let item_end = offset + key_size; + val.to_bytes(&mut buffer[offset..item_end], length_stack)?; + offset = item_end; + } + } + Ok(()) + } + + fn from_bytes(buffer: &[u8], length_stack: &mut VecDeque) -> Result { + let mut index = 0; + let mut result = Self::new(); + + if buffer.len() == 0 { return Ok(result); } + + loop { + let key_size = match K::len_params() { + 0 => mem::size_of::(), + _ => length_stack.pop_front().ok_or(BinaryConvertError::length())?, + }; + let key = if key_size == 0 { + K::from_empty_bytes()? + } else { + if index + key_size > buffer.len() { + return Err(BinaryConvertError::boundaries()) + } + K::from_bytes(&buffer[index..index+key_size], length_stack)? + }; + index = index + key_size; + + let val_size = match V::len_params() { + 0 => mem::size_of::(), + _ => length_stack.pop_front().ok_or(BinaryConvertError::length())?, + }; + let val = if val_size == 0 { + V::from_empty_bytes()? + } else { + if index + val_size > buffer.len() { + return Err(BinaryConvertError::boundaries()) + } + V::from_bytes(&buffer[index..index+val_size], length_stack)? + }; + result.insert(key, val); + index = index + val_size; + + if index == buffer.len() { break; } + if index > buffer.len() { + return Err(BinaryConvertError::boundaries()) + } + } + + Ok(result) + } + + fn from_empty_bytes() -> Result { + Ok(Self::new()) + } + + fn len_params() -> usize { + 1 + } +} + +impl BinaryConvertable for VecDeque where T: BinaryConvertable { + fn size(&self) -> usize { + match T::len_params() { + 0 => mem::size_of::() * self.len(), + _ => self.iter().fold(0usize, |acc, t| acc + t.size()), + } + } + + fn to_bytes(&self, buffer: &mut [u8], length_stack: &mut VecDeque) -> Result<(), BinaryConvertError> { + let mut offset = 0usize; + for item in self.iter() { + let next_size = match T::len_params() { + 0 => mem::size_of::(), + _ => { let size = item.size(); length_stack.push_back(size); size }, + }; + if next_size > 0 { + let item_end = offset + next_size; + item.to_bytes(&mut buffer[offset..item_end], length_stack)?; + offset = item_end; + } + } + Ok(()) + } + + fn from_bytes(buffer: &[u8], length_stack: &mut VecDeque) -> Result { + let mut index = 0; + let mut result = Self::with_capacity( + match T::len_params() { + 0 => buffer.len() / mem::size_of::(), + _ => 128, + }); + + if buffer.len() == 0 { return Ok(result); } + + loop { + let next_size = match T::len_params() { + 0 => mem::size_of::(), + _ => length_stack.pop_front().ok_or(BinaryConvertError::length())?, + }; + let item = if next_size == 0 { + T::from_empty_bytes()? + } + else { + if index + next_size > buffer.len() { + return Err(BinaryConvertError::boundaries()) + } + T::from_bytes(&buffer[index..index+next_size], length_stack)? + }; + result.push_back(item); + + index = index + next_size; + if index == buffer.len() { break; } + if index > buffer.len() { + return Err(BinaryConvertError::boundaries()) + } + } + + Ok(result) + } + + fn from_empty_bytes() -> Result { + Ok(Self::new()) + } + + fn len_params() -> usize { + 1 + } +} + +impl BinaryConvertable for Vec where T: BinaryConvertable { + fn size(&self) -> usize { + match T::len_params() { + 0 => mem::size_of::() * self.len(), + _ => self.iter().fold(0usize, |acc, t| acc + t.size()), + } + } + + fn to_bytes(&self, buffer: &mut [u8], length_stack: &mut VecDeque) -> Result<(), BinaryConvertError> { + let mut offset = 0usize; + for item in self.iter() { + let next_size = match T::len_params() { + 0 => mem::size_of::(), + _ => { let size = item.size(); length_stack.push_back(size); size }, + }; + if next_size > 0 { + let item_end = offset + next_size; + item.to_bytes(&mut buffer[offset..item_end], length_stack)?; + offset = item_end; + } + } + Ok(()) + } + + fn from_bytes(buffer: &[u8], length_stack: &mut VecDeque) -> Result { + let mut index = 0; + let mut result = Self::with_capacity( + match T::len_params() { + 0 => buffer.len() / mem::size_of::(), + _ => 128, + }); + + if buffer.len() == 0 { return Ok(result); } + + loop { + let next_size = match T::len_params() { + 0 => mem::size_of::(), + _ => length_stack.pop_front().ok_or(BinaryConvertError::length())?, + }; + let item = if next_size == 0 { + T::from_empty_bytes()? + } + else { + if index + next_size > buffer.len() { + return Err(BinaryConvertError::boundaries()) + } + T::from_bytes(&buffer[index..index+next_size], length_stack)? + }; + result.push(item); + + index = index + next_size; + if index == buffer.len() { break; } + if index > buffer.len() { + return Err(BinaryConvertError::boundaries()) + } + } + + Ok(result) + } + + fn from_empty_bytes() -> Result { + Ok(Self::new()) + } + + fn len_params() -> usize { + 1 + } +} + +impl BinaryConvertable for String { + fn size(&self) -> usize { + self.as_bytes().len() + } + + fn from_empty_bytes() -> Result { + Ok(String::new()) + } + + fn to_bytes(&self, buffer: &mut [u8], _length_stack: &mut VecDeque) -> Result<(), BinaryConvertError> { + buffer[..].clone_from_slice(self.as_bytes()); + Ok(()) + } + + fn from_bytes(buffer: &[u8], _length_stack: &mut VecDeque) -> Result { + Ok(::std::str::from_utf8(buffer).unwrap().to_owned()) + } + + fn len_params() -> usize { + 1 + } +} + +impl BinaryConvertable for Range where T: BinaryConvertable { + fn size(&self) -> usize { + mem::size_of::() * 2 + } + + fn from_empty_bytes() -> Result { + Err(BinaryConvertError::empty()) + } + + fn to_bytes(&self, buffer: &mut[u8], length_stack: &mut VecDeque) -> Result<(), BinaryConvertError> { + self.start.to_bytes(&mut buffer[..mem::size_of::()], length_stack)?; + self.end.to_bytes(&mut buffer[mem::size_of::() + 1..], length_stack)?; + Ok(()) + } + + fn from_bytes(buffer: &[u8], length_stack: &mut VecDeque) -> Result { + Ok(T::from_bytes(&buffer[..mem::size_of::()], length_stack)?..T::from_bytes(&buffer[mem::size_of::()+1..], length_stack)?) + } + + fn len_params() -> usize { + assert_eq!(0, T::len_params()); + 0 + } +} + +impl BinaryConvertable for ::std::cell::RefCell where T: BinaryConvertable { + fn size(&self) -> usize { + self.borrow().size() + } + + fn from_empty_bytes() -> Result { + Ok(::std::cell::RefCell::new(T::from_empty_bytes()?)) + } + + fn from_bytes(buffer: &[u8], length_stack: &mut VecDeque) -> Result { + Ok(::std::cell::RefCell::new(T::from_bytes(buffer, length_stack)?)) + } + + fn to_bytes(&self, buffer: &mut [u8], length_stack: &mut VecDeque) -> Result<(), BinaryConvertError> { + self.borrow().to_bytes(buffer, length_stack)?; + Ok(()) + } + + fn len_params() -> usize { + T::len_params() + } +} + +impl BinaryConvertable for ::std::cell::Cell where T: BinaryConvertable + Copy { + fn size(&self) -> usize { + self.get().size() + } + + fn from_empty_bytes() -> Result { + Ok(::std::cell::Cell::new(T::from_empty_bytes()?)) + } + + fn from_bytes(buffer: &[u8], length_stack: &mut VecDeque) -> Result { + Ok(::std::cell::Cell::new(T::from_bytes(buffer, length_stack)?)) + } + + fn to_bytes(&self, buffer: &mut [u8], length_stack: &mut VecDeque) -> Result<(), BinaryConvertError> { + self.get().to_bytes(buffer, length_stack)?; + Ok(()) + } + + fn len_params() -> usize { + T::len_params() + } +} + +impl BinaryConvertable for Vec { + fn size(&self) -> usize { + self.len() + } + + fn from_empty_bytes() -> Result { + Ok(Vec::new()) + } + + fn to_bytes(&self, buffer: &mut [u8], _length_stack: &mut VecDeque) -> Result<(), BinaryConvertError> { + buffer[..].clone_from_slice(&self[..]); + Ok(()) + } + + fn from_bytes(buffer: &[u8], _length_stack: &mut VecDeque) -> Result { + let mut res = Self::with_capacity(buffer.len()); + unsafe { res.set_len(buffer.len()) } + res[..].clone_from_slice(&buffer[..]); + Ok(res) + } + + fn len_params() -> usize { + 1 + } +} + +pub fn deserialize_from(r: &mut R) -> Result + where R: ::std::io::Read, + T: BinaryConvertable +{ + let mut fake_stack = VecDeque::new(); + + match T::len_params() { + 0 => { + let fixed_size = mem::size_of::(); + let mut payload_buffer = Vec::with_capacity(fixed_size); + unsafe { payload_buffer.set_len(fixed_size); } + let bytes_read = r.read(&mut payload_buffer)?; + if bytes_read != mem::size_of::() { + return Err(BinaryError::Serialization(BinaryConvertError::size(fixed_size, bytes_read))) + } + Ok(T::from_bytes(&payload_buffer[..], &mut fake_stack)?) + }, + _ => { + let mut payload = Vec::new(); + r.read_to_end(&mut payload)?; + + let stack_len = u64::from_bytes(&payload[0..8], &mut fake_stack)? as usize; + let mut length_stack = VecDeque::::with_capacity(stack_len); + + if stack_len > 0 { + for idx in 0..stack_len { + let stack_item = u64::from_bytes(&payload[8 + idx*8..8 + (idx+1)*8], &mut fake_stack)?; + length_stack.push_back(stack_item as usize); + } + } + + let size = u64::from_bytes(&payload[8+stack_len*8..16+stack_len*8], &mut fake_stack)? as usize; + match size { + 0 => { + Ok(T::from_empty_bytes()?) + }, + _ => { + Ok(T::from_bytes(&payload[16+stack_len*8..], &mut length_stack)?) + } + } + }, + } +} + +pub fn deserialize(buffer: &[u8]) -> Result { + use std::io::Cursor; + let mut buff = Cursor::new(buffer); + deserialize_from::(&mut buff) +} + +pub fn serialize_into(t: &T, w: &mut W) -> Result<(), BinaryError> + where W: ::std::io::Write, + T: BinaryConvertable +{ + let mut fake_stack = VecDeque::new(); + + match T::len_params() { + 0 => { + let fixed_size = mem::size_of::(); + let mut buffer = Vec::with_capacity(fixed_size); + unsafe { buffer.set_len(fixed_size); } + t.to_bytes(&mut buffer[..], &mut fake_stack)?; + w.write(&buffer[..])?; + Ok(()) + }, + _ => { + let mut length_stack = VecDeque::::new(); + let mut size_buffer = [0u8; 8]; + + let size = t.size(); + if size == 0 { + w.write(&size_buffer)?; + w.write(&size_buffer)?; + return Ok(()); + } + + let mut buffer = Vec::with_capacity(size); + unsafe { buffer.set_len(size); } + t.to_bytes(&mut buffer[..], &mut length_stack)?; + + let stack_len = length_stack.len(); + (stack_len as u64).to_bytes(&mut size_buffer[..], &mut fake_stack)?; + w.write(&size_buffer[..])?; + if stack_len > 0 { + let mut header_buffer = Vec::with_capacity(stack_len * 8); + unsafe { header_buffer.set_len(stack_len * 8); }; + (stack_len as u64).to_bytes(&mut header_buffer[0..8], &mut fake_stack)?; + let mut idx = 0; + loop { + match length_stack.pop_front() { + Some(val) => (val as u64).to_bytes(&mut header_buffer[idx * 8..(idx+1) * 8], &mut fake_stack)?, + None => { break; } + } + idx = idx + 1; + } + w.write(&header_buffer[..])?; + } + + (size as u64).to_bytes(&mut size_buffer[..], &mut fake_stack)?; + w.write(&size_buffer[..])?; + + w.write(&buffer[..])?; + + Ok(()) + }, + } +} + +pub fn serialize(t: &T) -> Result, BinaryError> { + use std::io::Cursor; + let mut buff = Cursor::new(Vec::new()); + serialize_into(t, &mut buff)?; + let into_inner = buff.into_inner(); + Ok(into_inner) +} + +#[macro_export] +macro_rules! binary_fixed_size { + ($target_ty: ty) => { + impl BinaryConvertable for $target_ty where $target_ty: Copy { + fn from_bytes(bytes: &[u8], _length_stack: &mut ::std::collections::VecDeque) -> Result { + let size = ::std::mem::size_of::<$target_ty>(); + match bytes.len().cmp(&size) { + ::std::cmp::Ordering::Equal => (), + _ => return Err(BinaryConvertError::size(size, bytes.len())), + }; + let res: Self = unsafe { + let mut temp = ::std::mem::zeroed(); + let temp_ptr = &mut temp as *mut _ as *mut u8; + ::std::ptr::copy_nonoverlapping(bytes.as_ptr(), temp_ptr, size); + + temp + }; + + Ok(res) + } + + fn to_bytes(&self, buffer: &mut [u8], _length_stack: &mut ::std::collections::VecDeque) -> Result<(), BinaryConvertError> { + let sz = ::std::mem::size_of::<$target_ty>(); + let ip: *const $target_ty = self; + let ptr: *const u8 = ip as *const _; + unsafe { + ::std::ptr::copy(ptr, buffer.as_mut_ptr(), sz); + } + Ok(()) + } + } + } +} + +/// Fixed-sized version of Handshake struct +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct BinHandshake { + api_version: BinVersion, + protocol_version: BinVersion, +} + +/// Shorten version of semver Version without `pre` and `build` information +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct BinVersion { + pub major: u64, + pub minor: u64, + pub patch: u64, +} + +impl From for BinHandshake { + fn from(other: Handshake) -> Self { + BinHandshake { + api_version: BinVersion::from(other.api_version), + protocol_version: BinVersion::from(other.protocol_version), + } + } +} + +impl BinHandshake { + pub fn to_semver(self) -> Handshake { + Handshake { + api_version: self.api_version.to_semver(), + protocol_version: self.protocol_version.to_semver(), + } + } +} + +impl BinVersion { + pub fn to_semver(self) -> ::semver::Version { + ::semver::Version { + major: self.major, + minor: self.minor, + patch: self.patch, + pre: vec![], + build: vec![], + } + } +} + +impl From<::semver::Version> for BinVersion { + fn from(other: ::semver::Version) -> Self { + BinVersion { + major: other.major, + minor: other.minor, + patch: other.patch, + } + } +} + +binary_fixed_size!(u16); +binary_fixed_size!(u64); +binary_fixed_size!(u32); +binary_fixed_size!(usize); +binary_fixed_size!(i32); +binary_fixed_size!(bool); +binary_fixed_size!(U256); +binary_fixed_size!(U512); +binary_fixed_size!(H256); +binary_fixed_size!(H512); +binary_fixed_size!(H2048); +binary_fixed_size!(Address); +binary_fixed_size!(BinHandshake); +binary_fixed_size!(BinVersion); + +impl BinaryConvertable for ::semver::Version { + fn from_bytes(bytes: &[u8], length_stack: &mut ::std::collections::VecDeque) -> Result { + BinVersion::from_bytes(bytes, length_stack).map(BinVersion::to_semver) + } + + fn to_bytes(&self, buffer: &mut [u8], length_stack: &mut ::std::collections::VecDeque) -> Result<(), BinaryConvertError> { + BinVersion::from(self.clone()).to_bytes(buffer, length_stack) + } +} + +#[test] +fn vec_serialize() { + let mut v = Vec::new(); + v.push(5u64); + v.push(10u64); + let mut length_stack = VecDeque::new(); + let mut data = Vec::with_capacity(v.size()); + unsafe { data.set_len(v.size()); } + let result = v.to_bytes(&mut data[..], &mut length_stack); + + assert!(result.is_ok()); + assert_eq!(5, data[0]); + assert_eq!(0, data[1]); + assert_eq!(10, data[8]); + assert_eq!(0, data[12]); +} + +#[test] +fn calculates_size() { + let mut v = Vec::new(); + v.push(5u64); + v.push(10u64); + + assert_eq!(16, v.size()); +} + +#[test] +fn vec_deserialize() { + let data = [ + 10u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, + 5u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, + ]; + + let mut length_stack = VecDeque::new(); + let vec = Vec::::from_bytes(&data[..], &mut length_stack).unwrap(); + + assert_eq!(vec![10u64, 5u64], vec); +} + +#[test] +fn vec_deserialize_chained() { + let mut v = Vec::new(); + v.push(Some(5u64)); + v.push(Some(10u64)); + v.push(None); + v.push(Some(12u64)); + + let mut length_stack = VecDeque::new(); + let mut data = Vec::with_capacity(v.size()); + unsafe { data.set_len(v.size()); } + let result = v.to_bytes(&mut data[..], &mut length_stack); + + assert!(result.is_ok()); + assert_eq!(4, length_stack.len()); +} + +#[test] +fn vec_serialize_deserialize() { + let mut v = Vec::new(); + v.push(Some(5u64)); + v.push(None); + v.push(Some(10u64)); + v.push(None); + v.push(Some(12u64)); + + + let mut data = Vec::with_capacity(v.size()); + unsafe { data.set_len(v.size()); } + let mut length_stack = VecDeque::new(); + + v.to_bytes(&mut data[..], &mut length_stack).unwrap(); + let de_v = Vec::>::from_bytes(&data[..], &mut length_stack).unwrap(); + + assert_eq!(v, de_v); +} + +#[test] +fn serialize_into_ok() { + use std::io::Cursor; + let mut buff = Cursor::new(vec![0; 128]); + + let mut v = Vec::new(); + v.push(Some(5u64)); + v.push(None); + v.push(Some(10u64)); + v.push(None); + v.push(Some(12u64)); + + serialize_into(&v, &mut buff).unwrap(); + assert_eq!(5, buff.get_ref()[0]); + assert_eq!(8, buff.get_ref()[8]); + assert_eq!(0, buff.get_ref()[16]); + assert_eq!(8, buff.get_ref()[24]); +} + +#[test] +fn deserialize_from_ok() { + use std::io::Cursor; + let mut buff = Cursor::new(vec![ + 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, + 16u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, + 10u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, + 5u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, + ]); + + let vec = deserialize_from::, _>(&mut buff).unwrap(); + + assert_eq!(vec![10u64, 5u64], vec); +} + +#[test] +fn serialize_into_deserialize_from() { + use std::io::{Cursor, SeekFrom, Seek}; + + let mut buff = Cursor::new(Vec::new()); + let mut v = Vec::new(); + v.push(Some(5u64)); + v.push(None); + v.push(Some(10u64)); + v.push(None); + v.push(Some(12u64)); + + serialize_into(&v, &mut buff).unwrap(); + buff.seek(SeekFrom::Start(0)).unwrap(); + let de_v = deserialize_from::>, _>(&mut buff).unwrap(); + assert_eq!(v, de_v); +} + +#[test] +fn serialize_vec_str() { + // empty + let source = Vec::::new(); + let serialized = serialize(&source).unwrap(); + let deserialized = deserialize::>(&serialized).unwrap(); + + assert_eq!(source, deserialized); + + // with few values + let mut source = Vec::::new(); + source.push("val1".to_owned()); + source.push("val2".to_owned()); + let serialized = serialize(&source).unwrap(); + let deserialized = deserialize::>(&serialized).unwrap(); + + assert_eq!(source, deserialized); +} + +#[test] +fn serialize_opt_str() { + // none + let source: Option = None; + let serialized = serialize(&source).unwrap(); + let deserialized = deserialize::>(&serialized).unwrap(); + + assert_eq!(source, deserialized); + + // value + let source: Option = Some("i have value".to_owned()); + let serialized = serialize(&source).unwrap(); + let deserialized = deserialize::>(&serialized).unwrap(); + + assert_eq!(source, deserialized); +} + +#[test] +fn serialize_opt_vec() { + use std::io::Cursor; + + let mut buff = Cursor::new(Vec::new()); + let optional_vec: Option> = None; + serialize_into(&optional_vec, &mut buff).unwrap(); + + assert_eq!(&vec![0u8; 16], buff.get_ref()); +} + +#[test] +fn serialize_opt_vec_payload() { + let optional_vec: Option> = None; + let payload = serialize(&optional_vec).unwrap(); + + assert_eq!(vec![0u8;16], payload); +} + +#[test] +fn deserialize_opt_vec() { + use std::io::Cursor; + let mut buff = Cursor::new(vec![0u8; 16]); + + let vec = deserialize_from::>, _>(&mut buff).unwrap(); + + assert!(vec.is_none()); +} + +#[test] +fn deserialize_simple_err() { + use std::io::Cursor; + let mut buff = Cursor::new(vec![0u8; 16]); + + let result = deserialize_from::, _>(&mut buff).unwrap(); + + assert!(result.is_ok()); +} + +#[test] +fn serialize_opt_vec_in_out() { + use std::io::{Cursor, SeekFrom, Seek}; + + let mut buff = Cursor::new(Vec::new()); + let optional_vec: Option> = None; + serialize_into(&optional_vec, &mut buff).unwrap(); + + buff.seek(SeekFrom::Start(0)).unwrap(); + let vec = deserialize_from::>, _>(&mut buff).unwrap(); + + assert!(vec.is_none()); +} + +#[test] +fn serialize_err_opt_vec_in_out() { + use std::io::{Cursor, SeekFrom, Seek}; + + let mut buff = Cursor::new(Vec::new()); + let optional_vec: Result>, u32> = Ok(None); + serialize_into(&optional_vec, &mut buff).unwrap(); + + buff.seek(SeekFrom::Start(0)).unwrap(); + let vec = deserialize_from::>, u32>, _>(&mut buff).unwrap(); + + assert!(vec.is_ok()); +} + +#[test] +fn serialize_btree() { + use std::io::{Cursor, SeekFrom, Seek}; + + let mut buff = Cursor::new(Vec::new()); + let mut btree = BTreeMap::new(); + btree.insert(1u64, 5u64); + serialize_into(&btree, &mut buff).unwrap(); + + buff.seek(SeekFrom::Start(0)).unwrap(); + let res = deserialize_from::, _>(&mut buff).unwrap(); + + assert_eq!(res[&1u64], 5u64); +} + +#[test] +fn serialize_refcell() { + use std::cell::RefCell; + + let source = RefCell::new(vec![5u32, 12u32, 19u32]); + let serialized = serialize(&source).unwrap(); + let deserialized = deserialize::>>(&serialized).unwrap(); + + assert_eq!(source, deserialized); +} + +#[test] +fn serialize_cell() { + use std::cell::Cell; + use std::str::FromStr; + + let source = Cell::new(U256::from_str("01231231231239999").unwrap()); + let serialized = serialize(&source).unwrap(); + let deserialized = deserialize::>(&serialized).unwrap(); + + assert_eq!(source, deserialized); +} + +#[test] +fn serialize_handshake() { + use std::io::{Cursor, SeekFrom, Seek}; + + let mut buff = Cursor::new(Vec::new()); + + let handshake = Handshake { + api_version: ::semver::Version::parse("1.2.0").unwrap(), + protocol_version: ::semver::Version::parse("1.2.0").unwrap(), + }; + + serialize_into(&BinHandshake::from(handshake.clone()), &mut buff).unwrap(); + + buff.seek(SeekFrom::Start(0)).unwrap(); + let res = deserialize_from::(&mut buff).unwrap().to_semver(); + + assert_eq!(res, handshake); +} + +#[test] +fn serialize_invalid_size() { + // value + let deserialized = deserialize::(&[]); + match deserialized { + Err(BinaryError::Serialization( + BinaryConvertError { + kind: BinaryConvertErrorKind::SizeMismatch { expected: 8, found: 0 }, + member_tree: _ + })) => {}, + other => panic!("Not a size mismatched error but: {:?}", other), + } +} + +#[test] +fn serialize_boundaries() { + // value + let deserialized = deserialize::>( + &[ + // payload header + 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, + 2u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, + // + 0u8, 0u8, 0u8, 5u8, + 0u8, 0u8, 0u8, 4u8, + 1u8, 1u8, /* not 4 bytes */ + ] + ); + match deserialized { + Err(BinaryError::Serialization( + BinaryConvertError { + kind: BinaryConvertErrorKind::InconsistentBoundaries, + member_tree: _ + })) => {}, + other => panic!("Not an inconsistent boundaries error but: {:?}", other), + } +} + +#[test] +fn serialize_empty_try() { + // value + let mut stack = VecDeque::new(); + let mut data = vec![0u8; 16]; + let sample: Option> = None; + let serialized = sample.to_bytes(&mut data, &mut stack); + match serialized { + Err(BinaryConvertError { + kind: BinaryConvertErrorKind::TargetPayloadEmpty, + member_tree: _ + }) => {}, + other => panic!("Not an error about empty payload to be produced but: {:?}", other), + } +} + +#[test] +fn serialize_not_enough_lengths() { + // value + let deserialized = deserialize::>>( + &[ + // payload header + 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, + 2u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, + // does not matter because no length param for the first option + 0u8, + ] + ); + match deserialized { + Err(BinaryError::Serialization( + BinaryConvertError { + kind: BinaryConvertErrorKind::MissingLengthValue, + member_tree: _ + })) => {}, + other => panic!("Not an missing length param error but: {:?}", other), + } +} + +#[test] +fn vec_of_vecs() { + let sample = vec![vec![5u8, 10u8], vec![], vec![9u8, 11u8]]; + let serialized = serialize(&sample).unwrap(); + let deserialized = deserialize::>>(&serialized).unwrap(); + assert_eq!(sample, deserialized); + + // empty + let sample: Vec> = vec![]; + let serialized = serialize(&sample).unwrap(); + let deserialized = deserialize::>>(&serialized).unwrap(); + assert_eq!(sample, deserialized); +} diff --git a/ipc/rpc/src/interface.rs b/ipc/rpc/src/interface.rs new file mode 100644 index 0000000000000000000000000000000000000000..c3e82fef4a63cbc029695d8fe3a7c7a7ad59654a --- /dev/null +++ b/ipc/rpc/src/interface.rs @@ -0,0 +1,106 @@ +// 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 . + +//! IPC RPC interface + +use std::io::{Read, Write}; +use std::marker::Sync; +use semver::Version; + +#[derive(Debug, PartialEq, Eq, Clone)] +/// Handshake for client and server to negotiate api/protocol version +pub struct Handshake { + pub protocol_version: Version, + pub api_version: Version, +} + +/// Allows to configure custom version and custom handshake response for +/// ipc host +pub trait IpcConfig { + /// Current service api version + /// Should be increased if any of the methods changes signature + fn api_version() -> Version { + Version::parse("1.0.0").unwrap() + } + + /// Current ipc protocol version + /// Should be increased only if signature of system methods changes + fn protocol_version() -> Version { + Version::parse("1.0.0").unwrap() + } + + /// Default handshake requires exact versions match + fn handshake(handshake: &Handshake) -> bool { + handshake.protocol_version == Self::protocol_version() && + handshake.api_version == Self::api_version() + } +} + +/// Error in dispatching or invoking methods via IPC +#[derive(Debug)] +pub enum Error { + UnkownSystemCall, + ClientUnsupported, + RemoteServiceUnsupported, + HandshakeFailed, +} + +/// Allows implementor to be attached to generic worker and dispatch rpc requests +/// over IPC +pub trait IpcInterface : IpcConfig { + /// reads the message from io, dispatches the call and returns serialized result + fn dispatch(&self, r: &mut R) -> Vec where R: Read; + + /// deserializes the payload from buffer, dispatches invoke and returns serialized result + /// (for non-blocking io) + fn dispatch_buf(&self, method_num: u16, buf: &[u8]) -> Vec; +} + +/// serializes method invocation (method_num and parameters) to the stream specified by `w` +pub fn invoke(method_num: u16, params: &Option>, w: &mut W) where W: Write { + // creating buffer to contain all message + let buf_len = match *params { None => 2, Some(ref val) => val.len() + 2 }; + let mut buf = vec![0u8; buf_len]; + + // writing method_num as u16 + buf[1] = (method_num & 255) as u8; + buf[0] = (method_num >> 8) as u8; + + // serializing parameters only if provided with any + if params.is_some() { + buf[2..buf_len].clone_from_slice(params.as_ref().unwrap()); + } + + if w.write(&buf).unwrap() != buf_len + { + // if write was inconsistent + panic!("failed to write to socket"); + } +} + +/// IpcSocket, read/write generalization +pub trait IpcSocket: Read + Write + Sync + Send { +} + +/// Basically something that needs only socket to be spawned +pub trait WithSocket { + fn init(socket: S) -> Self; +} + + +impl IpcSocket for ::devtools::TestSocket {} + +impl IpcSocket for ::nanomsg::Socket {} diff --git a/ipc/rpc/src/lib.rs b/ipc/rpc/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..c6aff23b5d07b7da9746852697f0cb381cd925aa --- /dev/null +++ b/ipc/rpc/src/lib.rs @@ -0,0 +1,28 @@ +// 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 . + +//! IPC RPC interface + +extern crate ethcore_devtools as devtools; +extern crate semver; +extern crate nanomsg; +extern crate ethcore_util as util; +extern crate ethcore_bigint as bigint; + +pub mod interface; +pub mod binary; +pub use interface::{IpcInterface, IpcSocket, invoke, IpcConfig, Handshake, Error, WithSocket}; +pub use binary::{BinaryConvertable, BinaryConvertError, BinVersion, BinHandshake}; diff --git a/ipc/tests/Cargo.toml b/ipc/tests/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..89a4f98c5b5cb17f3294d10669710196e6fa5b0d --- /dev/null +++ b/ipc/tests/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "ethcore-ipc-tests" +version = "0.1.0" +authors = ["Parity Technologies "] +build = "build.rs" + +[lib] +path = "run.rs" + +[dependencies] +ethcore-ipc = { path = "../rpc" } +ethcore-devtools = { path = "../../devtools" } +semver = "0.6" +nanomsg = { git = "https://github.com/paritytech/nanomsg.rs.git", branch = "parity-1.7" } +ethcore-ipc-nano = { path = "../nano" } +ethcore-util = { path = "../../util" } +log = "0.3" + +[build-dependencies] +ethcore-ipc-codegen = { path = "../codegen" } diff --git a/ipc/tests/binary.rs b/ipc/tests/binary.rs new file mode 100644 index 0000000000000000000000000000000000000000..6eb1da8522546cd68a9e23a54c3622013784b5c3 --- /dev/null +++ b/ipc/tests/binary.rs @@ -0,0 +1,19 @@ +// 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 . + +#![allow(dead_code, unused_assignments, unused_variables)] // codegen issues + +include!(concat!(env!("OUT_DIR"), "/binary.rs.in")); diff --git a/ipc/tests/binary.rs.in b/ipc/tests/binary.rs.in new file mode 100644 index 0000000000000000000000000000000000000000..dfc020c454b8d69ef3ce0e412d2311b3c1cff3cf --- /dev/null +++ b/ipc/tests/binary.rs.in @@ -0,0 +1,169 @@ +// 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 util::Bytes; + +#[binary] +pub enum Root { + Top, + Middle(u32, u64), +} + +#[derive(PartialEq, Debug)] +#[binary] +pub struct DoubleRoot { + pub x1: u32, + pub x2: u64, + pub x3: u32, +} + +#[derive(PartialEq, Debug)] +#[binary] +pub struct ReferenceStruct<'a> { + pub ref_data: &'a u64, +} + +#[derive(PartialEq, Debug)] +#[binary] +pub enum EnumWithStruct { + Left, + Right { how_much: u64 }, +} + +#[binary] +pub struct TwoVec { + v1: Vec, + v2: Vec, +} + +#[binary] +struct ChunkSet { + items: Vec, +} + +#[test] +fn opt_two_vec() { + let example: Option = None; + + let serialized = ::ipc::binary::serialize(&example).unwrap(); + assert_eq!(serialized, vec![0u8; 16]); +} + +#[test] +fn enum_with_struct() { + let example = EnumWithStruct::Right { how_much: 15 }; + let serialized = ::ipc::binary::serialize(&example).unwrap(); + let deserialized = ::ipc::binary::deserialize::(&serialized).unwrap(); + assert_eq!(example, deserialized); +} + +#[test] +fn chunks() { + let sample: Vec = vec! [ + 15, 0, 0, 0, 0, 0, 0, 0, 81, 6, 0, 0, 0, 0, 0, 0, + 110, 0, 0, 0, 0, 0, 0, 0, 112, 0, 0, 0, 0, 0, 0, 0, + 112, 0, 0, 0, 0, 0, 0, 0, 112, 0, 0, 0, 0, 0, 0, 0, + 173, 0, 0, 0, 0, 0, 0, 0, 103, 0, 0, 0, 0, 0, 0, 0, + 112, 0, 0, 0, 0, 0, 0, 0, 112, 0, 0, 0, 0, 0, 0, 0, + 107, 0, 0, 0, 0, 0, 0, 0, 112, 0, 0, 0, 0, 0, 0, 0, + 114, 0, 0, 0, 0, 0, 0, 0, 112, 0, 0, 0, 0, 0, 0, 0, + 114, 0, 0, 0, 0, 0, 0, 0, 112, 0, 0, 0, 0, 0, 0, 0, + 81, 6, 0, 0, 0, 0, 0, 0, + + 248, 108, 58, 133, 4, 168, 23, 200, 0, 130, 82, 8, 148, 182, 54, + 177, 224, 198, 163, 123, 250, 122, 157, 158, 75, 138, 30, 7, 83, 91, 141, 56, 46, 136, 2, + 197, 12, 243, 220, 69, 192, 0, 128, 28, 160, 32, 232, 123, 221, 90, 116, 93, 94, 136, 78, + 235, 200, 167, 154, 3, 175, 74, 53, 133, 32, 239, 199, 169, 46, 95, 111, 114, 204, 19, 138, + 15, 167, 160, 103, 72, 60, 241, 165, 248, 130, 213, 45, 166, 249, 102, 83, 87, 98, 153, + 68, 58, 13, 83, 193, 182, 217, 182, 9, 241, 217, 28, 162, 152, 114, 136, 248, 110, 130, 6, + 96, 133, 4, 168, 23, 200, 0, 131, 1, 95, 144, 148, 100, 131, 212, 219, 190, 174, 5, 47, + 105, 201, 11, 11, 210, 108, 207, 242, 164, 74, 218, 19, 135, 6, 10, 36, 24, 30, 64, 0, + 128, 27, 160, 206, 145, 163, 186, 24, 122, 126, 115, 80, 203, 152, 219, 160, 243, 1, 139, + 109, 199, 115, 50, 159, 197, 95, 184, 174, 53, 150, 3, 200, 82, 138, 22, 160, 119, 226, + 202, 208, 136, 165, 174, 240, 216, 222, 27, 214, 12, 213, 250, 68, 214, 144, 120, 53, 158, + 46, 124, 105, 87, 220, 213, 192, 28, 81, 118, 6, 248, 110, 130, 6, 95, 133, 4, 168, 23, + 200, 0, 131, 1, 95, 144, 148, 100, 131, 212, 219, 190, 174, 5, 47, 105, 201, 11, 11, 210, + 108, 207, 242, 164, 74, 218, 19, 135, 6, 10, 36, 24, 30, 64, 0, 128, 27, 160, 19, 172, 66, + 208, 28, 189, 213, 239, 125, 170, 127, 147, 190, 97, 171, 194, 229, 241, 178, 176, 111, + 3, 201, 217, 9, 179, 23, 159, 159, 64, 55, 225, 160, 7, 123, 227, 76, 149, 80, 48, 130, + 122, 23, 165, 175, 24, 89, 228, 128, 25, 106, 160, 195, 82, 204, 206, 150, 83, 70, 127, + 34, 221, 169, 80, 43, 248, 110, 130, 6, 102, 133, 4, 168, 23, 200, 0, 131, 1, 95, 144, 148, + 100, 131, 212, 219, 190, 174, 5, 47, 105, 201, 11, 11, 210, 108, 207, 242, 164, 74, 218, + 19, 135, 6, 10, 36, 24, 30, 64, 0, 128, 27, 160, 29, 6, 237, 5, 67, 42, 51, 65, 172, 133, + 9, 222, 160, 39, 202, 88, 230, 123, 232, 135, 234, 5, 244, 215, 99, 129, 242, 59, 63, 36, + 26, 253, 160, 104, 178, 37, 227, 142, 255, 186, 41, 91, 108, 206, 13, 108, 24, 73, 229, + 96, 224, 142, 230, 93, 214, 27, 60, 119, 149, 119, 56, 62, 5, 204, 179, 248, 171, 129, 136, + 133, 4, 168, 23, 200, 0, 131, 2, 77, 248, 148, 137, 32, 90, 58, 59, 42, 105, 222, 109, 191, + 127, 1, 237, 19, 178, 16, 139, 44, 67, 231, 128, 184, 68, 169, 5, 156, 187, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 105, 4, 134, 47, 167, 212, 79, 177, 44, 91, 97, 85, 184, 182, 222, 59, 231, 242, + 110, 141, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 2, 28, 160, 98, 5, 133, 180, 19, 27, 159, 186, 76, 27, 159, 143, 186, 97, 124, 253, 123, 40, + 87, 35, 184, 115, 99, 176, 68, 85, 191, 210, 218, 132, 220, 52, 160, 48, 160, 79, 26, 47, 127, 253, + 55, 252, 196, 196, 129, 87, 131, 132, 84, 74, 166, 33, 85, 134, 25, 34, 244, 14, 1, 16, 1, 205, 34, + 30, 3, 248, 101, 3, 133, 13, 43, 184, 47, 225, 131, 1, 95, 144, 148, 202, 110, 19, 32, 65, 111, 158, + 204, 235, 91, 252, 213, 35, 215, 54, 91, 165, 67, 183, 171, 1, 128, 28, 160, 182, 194, 27, 223, 136, + 182, 189, 146, 57, 61, 173, 62, 58, 241, 42, 80, 125, 174, 84, 191, 82, 124, 228, 62, 216, 233, 116, + 117, 227, 207, 138, 56, 160, 31, 210, 242, 212, 31, 156, 129, 155, 166, 64, 102, 140, 134, 35, 176, + 137, 201, 206, 213, 199, 238, 132, 185, 145, 220, 217, 151, 80, 243, 93, 71, 211, 248, 110, 130, + 6, 101, 133, 4, 168, 23, 200, 0, 131, 1, 95, 144, 148, 100, 131, 212, 219, 190, 174, 5, 47, 105, + 201, 11, 11, 210, 108, 207, 242, 164, 74, 218, 19, 135, 6, 10, 36, 24, 30, 64, 0, 128, 27, 160, + 86, 37, 61, 45, 13, 251, 9, 19, 188, 242, 233, 83, 77, 137, 28, 185, 141, 105, 217, 54, 182, 156, + 119, 223, 213, 112, 240, 20, 119, 167, 4, 7, 160, 95, 53, 122, 159, 6, 209, 70, 155, 122, 153, 165, + 192, 249, 223, 219, 83, 159, 40, 242, 39, 44, 132, 182, 208, 232, 77, 64, 178, 241, 233, 230, 253, + 248, 110, 130, 6, 99, 133, 4, 168, 23, 200, 0, 131, 1, 95, 144, 148, 100, 131, 212, 219, 190, 174, + 5, 47, 105, 201, 11, 11, 210, 108, 207, 242, 164, 74, 218, 19, 135, 6, 10, 36, 24, 30, 64, 0, 128, + 27, 160, 114, 33, 104, 64, 195, 12, 156, 235, 56, 59, 210, 102, 183, 210, 216, 137, 223, 207, 134, + 63, 65, 36, 204, 121, 38, 175, 214, 106, 184, 197, 26, 173, 160, 39, 94, 238, 34, 106, 190, 22, + 225, 95, 211, 192, 249, 95, 231, 1, 111, 8, 204, 133, 35, 84, 242, 134, 75, 61, 50, 26, 150, 46, + 209, 129, 155, 248, 105, 106, 133, 5, 103, 130, 1, 225, 131, 2, 130, 119, 148, 191, 78, 215, 178, + 127, 29, 102, 101, 70, 227, 13, 116, 213, 13, 23, 61, 32, 188, 167, 84, 128, 132, 60, 207, 214, + 11, 28, 160, 3, 83, 228, 182, 32, 30, 183, 26, 157, 247, 32, 142, 60, 192, 100, 175, 106, 216, + 144, 16, 100, 165, 95, 91, 135, 138, 14, 41, 82, 251, 207, 159, 160, 74, 160, 161, 187, 63, 216, + 18, 23, 64, 172, 216, 238, 192, 134, 191, 204, 206, 236, 197, 134, 116, 130, 15, 85, 113, 173, + 130, 39, 50, 160, 49, 222, 248, 110, 130, 6, 98, 133, 4, 168, 23, 200, 0, 131, 1, 95, 144, 148, + 100, 131, 212, 219, 190, 174, 5, 47, 105, 201, 11, 11, 210, 108, 207, 242, 164, 74, 218, 19, 135, + 6, 10, 36, 24, 30, 64, 0, 128, 28, 160, 138, 12, 20, 188, 112, 66, 91, 30, 216, 44, 24, 124, 242, + 200, 111, 179, 32, 26, 37, 221, 239, 110, 1, 84, 48, 89, 86, 61, 169, 129, 90, 21, 160, 44, 172, + 112, 11, 130, 45, 247, 188, 207, 91, 247, 195, 58, 188, 110, 127, 59, 227, 41, 151, 244, 41, 120, + 68, 185, 238, 41, 236, 195, 141, 38, 16, 248, 112, 131, 5, 238, 44, 133, 4, 168, 23, 200, 0, 131, + 1, 95, 144, 148, 5, 113, 9, 145, 204, 89, 103, 32, 126, 46, 182, 84, 47, 76, 28, 159, 77, 184, + 154, 59, 136, 1, 100, 240, 147, 154, 69, 101, 18, 128, 27, 160, 130, 0, 124, 82, 177, 112, 241, + 14, 47, 186, 67, 117, 176, 187, 147, 94, 4, 177, 218, 198, 55, 59, 245, 9, 142, 95, 88, 220, 63, + 98, 175, 49, 160, 17, 204, 228, 24, 242, 38, 166, 219, 17, 56, 103, 244, 33, 125, 223, 45, 43, + 252, 215, 163, 40, 1, 187, 152, 34, 229, 82, 180, 213, 148, 129, 32, 248, 110, 130, 6, 100, 133, + 4, 168, 23, 200, 0, 131, 1, 95, 144, 148, 100, 131, 212, 219, 190, 174, 5, 47, 105, 201, 11, 11, + 210, 108, 207, 242, 164, 74, 218, 19, 135, 6, 10, 36, 24, 30, 64, 0, 128, 27, 160, 149, 149, 154, + 6, 198, 127, 14, 11, 164, 0, 244, 4, 74, 83, 9, 108, 164, 66, 186, 26, 109, 69, 98, 41, 149, 33, + 238, 137, 23, 175, 124, 226, 160, 39, 249, 210, 237, 52, 83, 110, 229, 138, 84, 199, 64, 19, 209, + 156, 195, 9, 50, 184, 64, 78, 67, 158, 167, 121, 220, 80, 137, 104, 240, 50, 60, 248, 112, 131, 5, + 238, 45, 133, 4, 168, 23, 200, 0, 131, 1, 95, 144, 148, 247, 0, 201, 156, 113, 12, 158, 3, 208, 61, + 221, 91, 236, 235, 235, 195, 40, 46, 100, 73, 136, 13, 230, 68, 67, 114, 161, 197, 100, 128, 27, + 160, 123, 206, 0, 221, 2, 87, 197, 156, 109, 157, 133, 31, 26, 145, 223, 150, 235, 160, 54, 144, + 210, 146, 31, 173, 221, 128, 233, 148, 73, 82, 191, 220, 160, 57, 62, 114, 94, 77, 8, 116, 150, + 51, 112, 241, 70, 149, 157, 209, 193, 213, 109, 248, 102, 177, 27, 132, 226, 77, 141, 128, 122, + 185, 238, 188, 114, 248, 110, 130, 6, 97, 133, 4, 168, 23, 200, 0, 131, 1, 95, 144, 148, 100, + 131, 212, 219, 190, 174, 5, 47, 105, 201, 11, 11, 210, 108, 207, 242, 164, 74, 218, 19, 135, 6, + 10, 36, 24, 30, 64, 0, 128, 28, 160, 53, 222, 41, 101, 73, 44, 103, 26, 39, 165, 120, 194, 128, + 67, 109, 151, 96, 42, 193, 47, 255, 23, 27, 247, 8, 125, 200, 53, 129, 69, 103, 64, 160, 49, 12, + 64, 143, 74, 149, 161, 245, 68, 83, 89, 101, 212, 254, 81, 16, 170, 176, 33, 87, 6, 112, 34, 153, + 6, 192, 98, 126, 188, 17, 199, 155]; + + let chunks = ::ipc::binary::deserialize::(&sample).unwrap(); + let total_length = chunks.items.iter().fold(0usize, |total, item| total + item.len()); + + assert_eq!(1617, total_length); +} diff --git a/ipc/tests/build.rs b/ipc/tests/build.rs new file mode 100644 index 0000000000000000000000000000000000000000..7f68b16a892bb30e110906863186831add5e00de --- /dev/null +++ b/ipc/tests/build.rs @@ -0,0 +1,24 @@ +// 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 . + +extern crate ethcore_ipc_codegen as codegen; + +pub fn main() { + codegen::derive_ipc("nested.rs.in").unwrap(); + codegen::derive_ipc("service.rs.in").unwrap(); + codegen::derive_ipc("with_attrs.rs.in").unwrap(); + codegen::derive_binary("binary.rs.in").unwrap(); +} diff --git a/ipc/tests/examples.rs b/ipc/tests/examples.rs new file mode 100644 index 0000000000000000000000000000000000000000..8cd2d2ea5c6bceb0029253db654b6d57e1c9b4c3 --- /dev/null +++ b/ipc/tests/examples.rs @@ -0,0 +1,173 @@ +// 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 . + +#[cfg(test)] +mod tests { + + use super::super::service::*; + use super::super::binary::*; + use super::super::nested::{DBClient, DBWriter}; + use ipc::*; + use devtools::*; + use semver::Version; + use std::sync::Arc; + + #[test] + fn call_service() { + // method_num = 0, f = 10 (method Service::commit) + let mut socket = TestSocket::new_ready(vec![ + 0, 16, + 0, 0, 0, 0, 0, 0, 0, 0, + 4, 0, 0, 0, 0, 0, 0, 0, + 10, 0, 0, 0]); + + let service = Arc::new(Service::new()); + assert_eq!(0, *service.commits.read().unwrap()); + + service.dispatch(&mut socket); + + assert_eq!(10, *service.commits.read().unwrap()); + } + + #[test] + fn call_service_client() { + let mut socket = TestSocket::new(); + socket.read_buffer = vec![10, 0, 0, 0]; + let service_client = ServiceClient::init(socket); + + let result = service_client.commit(5); + + assert_eq!( + vec![0, 16, + 0, 0, 0, 0, 0, 0, 0, 0, + 4, 0, 0, 0, 0, 0, 0, 0, + 5, 0, 0, 0], + service_client.socket().write().unwrap().write_buffer.clone()); + assert_eq!(10, result); + } + + #[test] + fn call_service_client_optional() { + let mut socket = TestSocket::new(); + socket.read_buffer = vec![10, 0, 0, 0]; + let service_client = ServiceClient::init(socket); + + let result = service_client.rollback(Some(5), 10); + + assert_eq!(vec![ + 0, 17, + 1, 0, 0, 0, 0, 0, 0, 0, + 4, 0, 0, 0, 0, 0, 0, 0, + 8, 0, 0, 0, 0, 0, 0, 0, + 5, 0, 0, 0, 10, 0, 0, 0], service_client.socket().write().unwrap().write_buffer.clone()); + assert_eq!(10, result); + } + + #[test] + fn query_default_version() { + let ver = Service::protocol_version(); + assert_eq!(ver, Version::parse("1.0.0").unwrap()); + let ver = Service::api_version(); + assert_eq!(ver, Version::parse("1.0.0").unwrap()); + } + + #[test] + fn call_service_client_handshake() { + let mut socket = TestSocket::new(); + socket.read_buffer = vec![1]; + let service_client = ServiceClient::init(socket); + + let result = service_client.handshake(); + + assert!(result.is_ok()); + } + + #[test] + fn can_use_custom_params() { + let mut socket = TestSocket::new(); + socket.read_buffer = vec![1]; + let service_client = ServiceClient::init(socket); + + let result = service_client.push_custom(CustomData { a: 3, b: 11}); + + assert_eq!(vec![ + // message num.. + 0, 18, + // variable size length-s + 1, 0, 0, 0, 0, 0, 0, 0, + 16, 0, 0, 0, 0, 0, 0, 0, + // total length + 16, 0, 0, 0, 0, 0, 0, 0, + // items + 3, 0, 0, 0, 0, 0, 0, 0, + 11, 0, 0, 0, 0, 0, 0, 0], + service_client.socket().write().unwrap().write_buffer.clone()); + assert_eq!(true, result); + } + + #[test] + fn can_invoke_generic_service() { + let mut socket = TestSocket::new(); + socket.read_buffer = vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + let db_client = DBClient::::init(socket); + let result = db_client.write(vec![1u8; 1]); + assert_eq!(vec![0, 16, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1], + db_client.socket().write().unwrap().write_buffer.clone()); + assert!(result.is_ok()); + } + + #[test] + fn can_handshake_generic_service() { + let mut socket = TestSocket::new(); + socket.read_buffer = vec![1]; + let db_client = DBClient::::init(socket); + + let result = db_client.handshake(); + + assert!(result.is_ok()); + } + + #[test] + fn can_serialize_dummy_structs() { + let mut socket = TestSocket::new(); + + let struct_ = DoubleRoot { x1: 0, x2: 100, x3: 100000}; + let res = ::ipc::binary::serialize_into(&struct_, &mut socket); + + assert!(res.is_ok()); + + let mut read_socket = TestSocket::new_ready(socket.write_buffer.clone()); + let new_struct: DoubleRoot = ::ipc::binary::deserialize_from(&mut read_socket).unwrap(); + + assert_eq!(struct_, new_struct); + } + + #[test] + fn can_call_void_method() { + let mut socket = TestSocket::new(); + socket.read_buffer = vec![1]; + let service_client = ServiceClient::init(socket); + + service_client.void(99); + + assert_eq!(vec![ + 0, 19, + 0, 0, 0, 0, 0, 0, 0, 0, + 8, 0, 0, 0, 0, 0, 0, 0, + 99, 0, 0, 0, 0, 0, 0, 0], + service_client.socket().write().unwrap().write_buffer.clone()); + } +} diff --git a/ipc/tests/nested.rs b/ipc/tests/nested.rs new file mode 100644 index 0000000000000000000000000000000000000000..ab3f9fc1b9cd8595fc2ba9abdc7ee9a1528792de --- /dev/null +++ b/ipc/tests/nested.rs @@ -0,0 +1,18 @@ +// 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 . + +#![allow(dead_code, unused_assignments, unused_variables)] // codegen issues +include!(concat!(env!("OUT_DIR"), "/nested.rs.in")); diff --git a/ipc/tests/nested.rs.in b/ipc/tests/nested.rs.in new file mode 100644 index 0000000000000000000000000000000000000000..e68bec0d764f84dea6dea385fee3f13c695ce5e1 --- /dev/null +++ b/ipc/tests/nested.rs.in @@ -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 . + +use std::sync::RwLock; +use ipc::IpcConfig; + +pub struct DB { + pub writes: RwLock, + pub reads: RwLock, + pub holdings: L, +} + +pub trait DBWriter { + fn write(&self, data: Vec) -> Result<(), DBError>; + fn write_slice(&self, data: &[u8]) -> Result<(), DBError>; +} + +impl IpcConfig for DBWriter {} + +#[binary] +pub enum DBError { Write, Read } + +#[ipc] +impl DBWriter for DB { + fn write(&self, data: Vec) -> Result<(), DBError> { + let mut writes = self.writes.write().unwrap(); + *writes = *writes + data.len() as u64; + Ok(()) + } + + fn write_slice(&self, data: &[u8]) -> Result<(), DBError> { + let mut writes = self.writes.write().unwrap(); + *writes = *writes + data.len() as u64; + Ok(()) + } +} + +#[ipc] +trait DBNotify { + fn notify(&self, a: u64, b: u64) -> bool; +} + +impl IpcConfig for DBNotify { } diff --git a/ipc/tests/over_nano.rs b/ipc/tests/over_nano.rs new file mode 100644 index 0000000000000000000000000000000000000000..6f45dd24552a5c7117533bef4d0f6bcbb27e7bea --- /dev/null +++ b/ipc/tests/over_nano.rs @@ -0,0 +1,77 @@ +// 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 . + +#[cfg(test)] +mod tests { + + use super::super::service::*; + use super::super::with_attrs::PrettyNamedClient; + use nanoipc; + use std::sync::Arc; + use std::io::Write; + use std::sync::atomic::{Ordering, AtomicBool}; + + fn dummy_write(addr: &str, buf: &[u8]) -> (::nanomsg::Socket, ::nanomsg::Endpoint) { + let mut socket = ::nanomsg::Socket::new(::nanomsg::Protocol::Pair).unwrap(); + let endpoint = socket.connect(addr).unwrap(); + socket.write(buf).unwrap(); + (socket, endpoint) + } + + + fn init_worker(addr: &str) -> nanoipc::Worker { + let mut worker = nanoipc::Worker::::new(&Arc::new(Service::new())); + worker.add_duplex(addr).unwrap(); + worker + } + + #[test] + fn can_create_client() { + let client = nanoipc::init_duplex_client::>("ipc:///tmp/parity-nano-test10.ipc"); + assert!(client.is_ok()); + } + + #[test] + fn can_create_renamed_client() { + let client = nanoipc::init_duplex_client::>("ipc:///tmp/parity-nano-test10.ipc"); + assert!(client.is_ok()); + } + + #[test] + fn can_call_handshake() { + let url = "ipc:///tmp/parity-test-nano-20.ipc"; + let worker_should_exit = Arc::new(AtomicBool::new(false)); + let worker_is_ready = Arc::new(AtomicBool::new(false)); + let c_worker_should_exit = worker_should_exit.clone(); + let c_worker_is_ready = worker_is_ready.clone(); + + ::std::thread::spawn(move || { + let mut worker = init_worker(url); + while !c_worker_should_exit.load(Ordering::Relaxed) { + worker.poll(); + c_worker_is_ready.store(true, Ordering::Relaxed); + } + }); + + while !worker_is_ready.load(Ordering::Relaxed) { } + let client = nanoipc::init_duplex_client::>(url).unwrap(); + + let hs = client.handshake(); + + worker_should_exit.store(true, Ordering::Relaxed); + assert!(hs.is_ok()); + } +} diff --git a/ipc/tests/run.rs b/ipc/tests/run.rs new file mode 100644 index 0000000000000000000000000000000000000000..607e20636734843459f8d997254ca3de4e6f5fdf --- /dev/null +++ b/ipc/tests/run.rs @@ -0,0 +1,32 @@ +// 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 . + +#![allow(dead_code)] + +extern crate ethcore_ipc as ipc; +extern crate ethcore_devtools as devtools; +extern crate semver; +extern crate nanomsg; +extern crate ethcore_ipc_nano as nanoipc; +extern crate ethcore_util as util; +#[macro_use] extern crate log; + +pub mod service; +mod examples; +mod over_nano; +mod nested; +mod binary; +mod with_attrs; diff --git a/ipc/tests/service.rs b/ipc/tests/service.rs new file mode 100644 index 0000000000000000000000000000000000000000..56b9915db660ed1d75b219e5d19e000d8c23684a --- /dev/null +++ b/ipc/tests/service.rs @@ -0,0 +1,18 @@ +// 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 . + +#![allow(dead_code, unused_assignments, unused_variables)] // codegen issues +include!(concat!(env!("OUT_DIR"), "/service.rs.in")); diff --git a/ipc/tests/service.rs.in b/ipc/tests/service.rs.in new file mode 100644 index 0000000000000000000000000000000000000000..68d40efc402e8f4696c19fc0cc85e0cc6e441973 --- /dev/null +++ b/ipc/tests/service.rs.in @@ -0,0 +1,69 @@ +// 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 std::sync::RwLock; +use ipc::IpcConfig; + +pub struct Service { + pub commits: RwLock, + pub rollbacks: RwLock, +} + +#[binary] +pub struct CustomData { + pub a: u64, + pub b: u64, +} + +#[ipc] +impl Service { + fn commit(&self, f: u32) -> u32 { + let mut lock = self.commits.write().unwrap(); + *lock = *lock + f as usize; + f + } + + pub fn rollback(&self, a: Option, b: u32) -> i32 { + let a_0 = a.unwrap_or_else(|| 0); + let mut lock = self.rollbacks.write().unwrap(); + *lock = *lock + a_0 as usize - b as usize; + (a_0 - b) as i32 + } + + pub fn push_custom(&self, data: CustomData) -> bool { + let mut clock = self.commits.write().unwrap(); + let mut rlock = self.commits.write().unwrap(); + + *clock = data.a as usize; + *rlock = data.b as usize; + + true + } + + pub fn void(&self, a: u64) { + } +} + +impl Service { + pub fn new() -> Service { + Service { + commits: RwLock::new(0usize), + rollbacks: RwLock::new(0usize), + } + } +} + +impl ::ipc::IpcConfig for Service {} diff --git a/ipc/tests/with_attrs.rs b/ipc/tests/with_attrs.rs new file mode 100644 index 0000000000000000000000000000000000000000..89f3575f74fb655fb84e6813461720a2ea1bb5b0 --- /dev/null +++ b/ipc/tests/with_attrs.rs @@ -0,0 +1,18 @@ +// 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 . + +#![allow(dead_code, unused_assignments, unused_variables)] // codegen issues +include!(concat!(env!("OUT_DIR"), "/with_attrs.rs.in")); diff --git a/ipc/tests/with_attrs.rs.in b/ipc/tests/with_attrs.rs.in new file mode 100644 index 0000000000000000000000000000000000000000..f18dedf9aa7ed5364f452673322cd17d53f40dc1 --- /dev/null +++ b/ipc/tests/with_attrs.rs.in @@ -0,0 +1,28 @@ +// 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 ipc::IpcConfig; + +pub struct BadlyNamedService; + +#[ipc(client_ident="PrettyNamedClient")] +impl BadlyNamedService { + fn is_zero(&self, x: u64) -> bool { + x == 0 + } +} + +impl IpcConfig for BadlyNamedService {} diff --git a/js/assets/fonts/Roboto/LICENSE.txt b/js/assets/fonts/Roboto/LICENSE.txt new file mode 100755 index 0000000000000000000000000000000000000000..75b52484ea471f882c29e02693b4f02dba175b5e --- /dev/null +++ b/js/assets/fonts/Roboto/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/js/assets/fonts/Roboto/font.css b/js/assets/fonts/Roboto/font.css new file mode 100644 index 0000000000000000000000000000000000000000..75226e62185177b5dd8fa2102aeb36da94b69269 --- /dev/null +++ b/js/assets/fonts/Roboto/font.css @@ -0,0 +1,56 @@ +/* cyrillic-ext */ +@font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 300; + src: local('Roboto Light'), local('Roboto-Light'), url(./v15/0eC6fl06luXEYWpBSJvXCIX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2'); + unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F; +} +/* cyrillic */ +@font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 300; + src: local('Roboto Light'), local('Roboto-Light'), url(./v15/Fl4y0QdOxyyTHEGMXX8kcYX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2'); + unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} +/* greek-ext */ +@font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 300; + src: local('Roboto Light'), local('Roboto-Light'), url(./v15/-L14Jk06m6pUHB-5mXQQnYX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2'); + unicode-range: U+1F00-1FFF; +} +/* greek */ +@font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 300; + src: local('Roboto Light'), local('Roboto-Light'), url(./v15/I3S1wsgSg9YCurV6PUkTOYX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2'); + unicode-range: U+0370-03FF; +} +/* vietnamese */ +@font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 300; + src: local('Roboto Light'), local('Roboto-Light'), url(./v15/NYDWBdD4gIq26G5XYbHsFIX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2'); + unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB; +} +/* latin-ext */ +@font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 300; + src: local('Roboto Light'), local('Roboto-Light'), url(./v15/Pru33qjShpZSmG3z6VYwnYX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2'); + unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 300; + src: local('Roboto Light'), local('Roboto-Light'), url(./v15/Hgo13k-tfSpn0qi1SFdUfZBw1xU1rKptJj_0jans920.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000; +} diff --git a/js/assets/fonts/Roboto/ttf/Roboto-Black.ttf b/js/assets/fonts/Roboto/ttf/Roboto-Black.ttf new file mode 100755 index 0000000000000000000000000000000000000000..fbde625d403cc1fe3be06e15ae90c448c013eee5 Binary files /dev/null and b/js/assets/fonts/Roboto/ttf/Roboto-Black.ttf differ diff --git a/js/assets/fonts/Roboto/ttf/Roboto-BlackItalic.ttf b/js/assets/fonts/Roboto/ttf/Roboto-BlackItalic.ttf new file mode 100755 index 0000000000000000000000000000000000000000..60f7782a2e4aba9bbc96d7634799eaa05512c927 Binary files /dev/null and b/js/assets/fonts/Roboto/ttf/Roboto-BlackItalic.ttf differ diff --git a/js/assets/fonts/Roboto/ttf/Roboto-Bold.ttf b/js/assets/fonts/Roboto/ttf/Roboto-Bold.ttf new file mode 100755 index 0000000000000000000000000000000000000000..a355c27cde02b13da43c30ae060c5fb164b36b76 Binary files /dev/null and b/js/assets/fonts/Roboto/ttf/Roboto-Bold.ttf differ diff --git a/js/assets/fonts/Roboto/ttf/Roboto-BoldItalic.ttf b/js/assets/fonts/Roboto/ttf/Roboto-BoldItalic.ttf new file mode 100755 index 0000000000000000000000000000000000000000..3c9a7a37361b6ae0571b33f09b6b55367e188cfe Binary files /dev/null and b/js/assets/fonts/Roboto/ttf/Roboto-BoldItalic.ttf differ diff --git a/js/assets/fonts/Roboto/ttf/Roboto-Italic.ttf b/js/assets/fonts/Roboto/ttf/Roboto-Italic.ttf new file mode 100755 index 0000000000000000000000000000000000000000..ff6046d5bfa7cd4498ad4a549d2d9028f6c73372 Binary files /dev/null and b/js/assets/fonts/Roboto/ttf/Roboto-Italic.ttf differ diff --git a/js/assets/fonts/Roboto/ttf/Roboto-Light.ttf b/js/assets/fonts/Roboto/ttf/Roboto-Light.ttf new file mode 100755 index 0000000000000000000000000000000000000000..94c6bcc67e09602f6d90ac10f449b5c05c2f7021 Binary files /dev/null and b/js/assets/fonts/Roboto/ttf/Roboto-Light.ttf differ diff --git a/js/assets/fonts/Roboto/ttf/Roboto-LightItalic.ttf b/js/assets/fonts/Roboto/ttf/Roboto-LightItalic.ttf new file mode 100755 index 0000000000000000000000000000000000000000..04cc002302024c4e032d32319f0d40a32b54aada Binary files /dev/null and b/js/assets/fonts/Roboto/ttf/Roboto-LightItalic.ttf differ diff --git a/js/assets/fonts/Roboto/ttf/Roboto-Medium.ttf b/js/assets/fonts/Roboto/ttf/Roboto-Medium.ttf new file mode 100755 index 0000000000000000000000000000000000000000..39c63d7461796094c0b8889ee8fe2706d344a99a Binary files /dev/null and b/js/assets/fonts/Roboto/ttf/Roboto-Medium.ttf differ diff --git a/js/assets/fonts/Roboto/ttf/Roboto-MediumItalic.ttf b/js/assets/fonts/Roboto/ttf/Roboto-MediumItalic.ttf new file mode 100755 index 0000000000000000000000000000000000000000..dc743f0a66cf3741e90f712aba22a91197b7e59c Binary files /dev/null and b/js/assets/fonts/Roboto/ttf/Roboto-MediumItalic.ttf differ diff --git a/js/assets/fonts/Roboto/ttf/Roboto-Regular.ttf b/js/assets/fonts/Roboto/ttf/Roboto-Regular.ttf new file mode 100755 index 0000000000000000000000000000000000000000..8c082c8de090865264d37594e396c4d6c0099fe4 Binary files /dev/null and b/js/assets/fonts/Roboto/ttf/Roboto-Regular.ttf differ diff --git a/js/assets/fonts/Roboto/ttf/Roboto-Thin.ttf b/js/assets/fonts/Roboto/ttf/Roboto-Thin.ttf new file mode 100755 index 0000000000000000000000000000000000000000..d69555029c3e184189c6cf9961c9cb21205bda96 Binary files /dev/null and b/js/assets/fonts/Roboto/ttf/Roboto-Thin.ttf differ diff --git a/js/assets/fonts/Roboto/ttf/Roboto-ThinItalic.ttf b/js/assets/fonts/Roboto/ttf/Roboto-ThinItalic.ttf new file mode 100755 index 0000000000000000000000000000000000000000..07172ff666ad2d590e29324165b6c7b8e2be72ee Binary files /dev/null and b/js/assets/fonts/Roboto/ttf/Roboto-ThinItalic.ttf differ diff --git a/js/assets/fonts/Roboto/v15/-L14Jk06m6pUHB-5mXQQnYX0hVgzZQUfRDuZrPvH3D8.woff2 b/js/assets/fonts/Roboto/v15/-L14Jk06m6pUHB-5mXQQnYX0hVgzZQUfRDuZrPvH3D8.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..58fd4bcd24711c74b7c0ecf045cef580471f02f1 Binary files /dev/null and b/js/assets/fonts/Roboto/v15/-L14Jk06m6pUHB-5mXQQnYX0hVgzZQUfRDuZrPvH3D8.woff2 differ diff --git a/js/assets/fonts/Roboto/v15/0eC6fl06luXEYWpBSJvXCIX0hVgzZQUfRDuZrPvH3D8.woff2 b/js/assets/fonts/Roboto/v15/0eC6fl06luXEYWpBSJvXCIX0hVgzZQUfRDuZrPvH3D8.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..eacda321c795555dcaf46c05373bff8ddd7b11a8 Binary files /dev/null and b/js/assets/fonts/Roboto/v15/0eC6fl06luXEYWpBSJvXCIX0hVgzZQUfRDuZrPvH3D8.woff2 differ diff --git a/js/assets/fonts/Roboto/v15/Fl4y0QdOxyyTHEGMXX8kcYX0hVgzZQUfRDuZrPvH3D8.woff2 b/js/assets/fonts/Roboto/v15/Fl4y0QdOxyyTHEGMXX8kcYX0hVgzZQUfRDuZrPvH3D8.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..cc16b36c5848dbd0175f6fa5b4d57966f9b61a39 Binary files /dev/null and b/js/assets/fonts/Roboto/v15/Fl4y0QdOxyyTHEGMXX8kcYX0hVgzZQUfRDuZrPvH3D8.woff2 differ diff --git a/js/assets/fonts/Roboto/v15/Hgo13k-tfSpn0qi1SFdUfZBw1xU1rKptJj_0jans920.woff2 b/js/assets/fonts/Roboto/v15/Hgo13k-tfSpn0qi1SFdUfZBw1xU1rKptJj_0jans920.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..4411cbc8754cf7ec78501ba711efabe15b936675 Binary files /dev/null and b/js/assets/fonts/Roboto/v15/Hgo13k-tfSpn0qi1SFdUfZBw1xU1rKptJj_0jans920.woff2 differ diff --git a/js/assets/fonts/Roboto/v15/I3S1wsgSg9YCurV6PUkTOYX0hVgzZQUfRDuZrPvH3D8.woff2 b/js/assets/fonts/Roboto/v15/I3S1wsgSg9YCurV6PUkTOYX0hVgzZQUfRDuZrPvH3D8.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..0028bd81e9db90411095790d72c9568f1778bd66 Binary files /dev/null and b/js/assets/fonts/Roboto/v15/I3S1wsgSg9YCurV6PUkTOYX0hVgzZQUfRDuZrPvH3D8.woff2 differ diff --git a/js/assets/fonts/Roboto/v15/NYDWBdD4gIq26G5XYbHsFIX0hVgzZQUfRDuZrPvH3D8.woff2 b/js/assets/fonts/Roboto/v15/NYDWBdD4gIq26G5XYbHsFIX0hVgzZQUfRDuZrPvH3D8.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..a5cefc9c67e2f5e20dccc3408988f16cbd6336ea Binary files /dev/null and b/js/assets/fonts/Roboto/v15/NYDWBdD4gIq26G5XYbHsFIX0hVgzZQUfRDuZrPvH3D8.woff2 differ diff --git a/js/assets/fonts/Roboto/v15/Pru33qjShpZSmG3z6VYwnYX0hVgzZQUfRDuZrPvH3D8.woff2 b/js/assets/fonts/Roboto/v15/Pru33qjShpZSmG3z6VYwnYX0hVgzZQUfRDuZrPvH3D8.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..feec9f1e6a187f63cbb826958bbc01c050b5b9c8 Binary files /dev/null and b/js/assets/fonts/Roboto/v15/Pru33qjShpZSmG3z6VYwnYX0hVgzZQUfRDuZrPvH3D8.woff2 differ diff --git a/js/assets/fonts/RobotoMono/LICENSE.txt b/js/assets/fonts/RobotoMono/LICENSE.txt new file mode 100755 index 0000000000000000000000000000000000000000..75b52484ea471f882c29e02693b4f02dba175b5e --- /dev/null +++ b/js/assets/fonts/RobotoMono/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/js/assets/fonts/RobotoMono/font.css b/js/assets/fonts/RobotoMono/font.css new file mode 100644 index 0000000000000000000000000000000000000000..6a86af729e4fb38974843b3c1309d9dd3eabda49 --- /dev/null +++ b/js/assets/fonts/RobotoMono/font.css @@ -0,0 +1,56 @@ +/* cyrillic-ext */ +@font-face { + font-family: 'Roboto Mono'; + font-style: normal; + font-weight: 300; + src: local('Roboto Mono Light'), local('RobotoMono-Light'), url(./v4/N4duVc9C58uwPiY8_59Fz0ExlR2MysFCBK8OirNw2kM.woff2) format('woff2'); + unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F; +} +/* cyrillic */ +@font-face { + font-family: 'Roboto Mono'; + font-style: normal; + font-weight: 300; + src: local('Roboto Mono Light'), local('RobotoMono-Light'), url(./v4/N4duVc9C58uwPiY8_59Fz2dsm03krrxlabhmVQFB99s.woff2) format('woff2'); + unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} +/* greek-ext */ +@font-face { + font-family: 'Roboto Mono'; + font-style: normal; + font-weight: 300; + src: local('Roboto Mono Light'), local('RobotoMono-Light'), url(./v4/N4duVc9C58uwPiY8_59FzyJ0caWjaSBdV-xZbEgst_k.woff2) format('woff2'); + unicode-range: U+1F00-1FFF; +} +/* greek */ +@font-face { + font-family: 'Roboto Mono'; + font-style: normal; + font-weight: 300; + src: local('Roboto Mono Light'), local('RobotoMono-Light'), url(./v4/N4duVc9C58uwPiY8_59Fz2MSHb9EAJwuSzGfuRChQzQ.woff2) format('woff2'); + unicode-range: U+0370-03FF; +} +/* vietnamese */ +@font-face { + font-family: 'Roboto Mono'; + font-style: normal; + font-weight: 300; + src: local('Roboto Mono Light'), local('RobotoMono-Light'), url(./v4/N4duVc9C58uwPiY8_59Fz-pRBTtN4E2_qSPBnw6AgMc.woff2) format('woff2'); + unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB; +} +/* latin-ext */ +@font-face { + font-family: 'Roboto Mono'; + font-style: normal; + font-weight: 300; + src: local('Roboto Mono Light'), local('RobotoMono-Light'), url(./v4/N4duVc9C58uwPiY8_59Fz9Dnm4qiMZlH5rhYv_7LI2Y.woff2) format('woff2'); + unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Roboto Mono'; + font-style: normal; + font-weight: 300; + src: local('Roboto Mono Light'), local('RobotoMono-Light'), url(./v4/N4duVc9C58uwPiY8_59Fz9TIkQYohD4BpHvJ3NvbHoA.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000; +} diff --git a/js/assets/fonts/RobotoMono/ttf/RobotoMono-Bold.ttf b/js/assets/fonts/RobotoMono/ttf/RobotoMono-Bold.ttf new file mode 100755 index 0000000000000000000000000000000000000000..07ef607d50c2e0b48d251910fc75c7eb83a41b34 Binary files /dev/null and b/js/assets/fonts/RobotoMono/ttf/RobotoMono-Bold.ttf differ diff --git a/js/assets/fonts/RobotoMono/ttf/RobotoMono-BoldItalic.ttf b/js/assets/fonts/RobotoMono/ttf/RobotoMono-BoldItalic.ttf new file mode 100755 index 0000000000000000000000000000000000000000..1cca0bf456a3b6b42b3c92e81b4f4ebfa7d41f95 Binary files /dev/null and b/js/assets/fonts/RobotoMono/ttf/RobotoMono-BoldItalic.ttf differ diff --git a/js/assets/fonts/RobotoMono/ttf/RobotoMono-Italic.ttf b/js/assets/fonts/RobotoMono/ttf/RobotoMono-Italic.ttf new file mode 100755 index 0000000000000000000000000000000000000000..ef92c372cf47ec6ac92644bdc52ef131867037b3 Binary files /dev/null and b/js/assets/fonts/RobotoMono/ttf/RobotoMono-Italic.ttf differ diff --git a/js/assets/fonts/RobotoMono/ttf/RobotoMono-Light.ttf b/js/assets/fonts/RobotoMono/ttf/RobotoMono-Light.ttf new file mode 100755 index 0000000000000000000000000000000000000000..63229b2805b102f626c06e29752fe28e1ce9471f Binary files /dev/null and b/js/assets/fonts/RobotoMono/ttf/RobotoMono-Light.ttf differ diff --git a/js/assets/fonts/RobotoMono/ttf/RobotoMono-LightItalic.ttf b/js/assets/fonts/RobotoMono/ttf/RobotoMono-LightItalic.ttf new file mode 100755 index 0000000000000000000000000000000000000000..f25bed56adba942784c953710324ef0bba7577fa Binary files /dev/null and b/js/assets/fonts/RobotoMono/ttf/RobotoMono-LightItalic.ttf differ diff --git a/js/assets/fonts/RobotoMono/ttf/RobotoMono-Medium.ttf b/js/assets/fonts/RobotoMono/ttf/RobotoMono-Medium.ttf new file mode 100755 index 0000000000000000000000000000000000000000..88ff0c15a56bd3275b7938d7e7bedc56f71ccb8f Binary files /dev/null and b/js/assets/fonts/RobotoMono/ttf/RobotoMono-Medium.ttf differ diff --git a/js/assets/fonts/RobotoMono/ttf/RobotoMono-MediumItalic.ttf b/js/assets/fonts/RobotoMono/ttf/RobotoMono-MediumItalic.ttf new file mode 100755 index 0000000000000000000000000000000000000000..307efad8fcaa533cdc695b8f1aae696ba662431e Binary files /dev/null and b/js/assets/fonts/RobotoMono/ttf/RobotoMono-MediumItalic.ttf differ diff --git a/js/assets/fonts/RobotoMono/ttf/RobotoMono-Regular.ttf b/js/assets/fonts/RobotoMono/ttf/RobotoMono-Regular.ttf new file mode 100755 index 0000000000000000000000000000000000000000..b158a334eb372a9ab2ecd4f2566e60d561e71a9f Binary files /dev/null and b/js/assets/fonts/RobotoMono/ttf/RobotoMono-Regular.ttf differ diff --git a/js/assets/fonts/RobotoMono/ttf/RobotoMono-Thin.ttf b/js/assets/fonts/RobotoMono/ttf/RobotoMono-Thin.ttf new file mode 100755 index 0000000000000000000000000000000000000000..309484d3239a307904ad64f95d87c38fb6d24b7b Binary files /dev/null and b/js/assets/fonts/RobotoMono/ttf/RobotoMono-Thin.ttf differ diff --git a/js/assets/fonts/RobotoMono/ttf/RobotoMono-ThinItalic.ttf b/js/assets/fonts/RobotoMono/ttf/RobotoMono-ThinItalic.ttf new file mode 100755 index 0000000000000000000000000000000000000000..e1bb9121ec6568d6b8772d121eafeaa5da0db928 Binary files /dev/null and b/js/assets/fonts/RobotoMono/ttf/RobotoMono-ThinItalic.ttf differ diff --git a/js/assets/fonts/RobotoMono/v4/N4duVc9C58uwPiY8_59Fz-pRBTtN4E2_qSPBnw6AgMc.woff2 b/js/assets/fonts/RobotoMono/v4/N4duVc9C58uwPiY8_59Fz-pRBTtN4E2_qSPBnw6AgMc.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..f1fdb93524e7a84934cc4f6d4b731006059705f1 Binary files /dev/null and b/js/assets/fonts/RobotoMono/v4/N4duVc9C58uwPiY8_59Fz-pRBTtN4E2_qSPBnw6AgMc.woff2 differ diff --git a/js/assets/fonts/RobotoMono/v4/N4duVc9C58uwPiY8_59Fz0ExlR2MysFCBK8OirNw2kM.woff2 b/js/assets/fonts/RobotoMono/v4/N4duVc9C58uwPiY8_59Fz0ExlR2MysFCBK8OirNw2kM.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..501a4d77720aec360723de2ad0292c49eb10caf9 Binary files /dev/null and b/js/assets/fonts/RobotoMono/v4/N4duVc9C58uwPiY8_59Fz0ExlR2MysFCBK8OirNw2kM.woff2 differ diff --git a/js/assets/fonts/RobotoMono/v4/N4duVc9C58uwPiY8_59Fz2MSHb9EAJwuSzGfuRChQzQ.woff2 b/js/assets/fonts/RobotoMono/v4/N4duVc9C58uwPiY8_59Fz2MSHb9EAJwuSzGfuRChQzQ.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..54484b1e3edcbac1fc80f13cfa4a03cb29c84bf1 Binary files /dev/null and b/js/assets/fonts/RobotoMono/v4/N4duVc9C58uwPiY8_59Fz2MSHb9EAJwuSzGfuRChQzQ.woff2 differ diff --git a/js/assets/fonts/RobotoMono/v4/N4duVc9C58uwPiY8_59Fz2dsm03krrxlabhmVQFB99s.woff2 b/js/assets/fonts/RobotoMono/v4/N4duVc9C58uwPiY8_59Fz2dsm03krrxlabhmVQFB99s.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..c3f35ca2911c1db7e8afb85f28e23f7db0ae986c Binary files /dev/null and b/js/assets/fonts/RobotoMono/v4/N4duVc9C58uwPiY8_59Fz2dsm03krrxlabhmVQFB99s.woff2 differ diff --git a/js/assets/fonts/RobotoMono/v4/N4duVc9C58uwPiY8_59Fz9Dnm4qiMZlH5rhYv_7LI2Y.woff2 b/js/assets/fonts/RobotoMono/v4/N4duVc9C58uwPiY8_59Fz9Dnm4qiMZlH5rhYv_7LI2Y.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..72a43ab3c0332b0539f230c402090fdbab079956 Binary files /dev/null and b/js/assets/fonts/RobotoMono/v4/N4duVc9C58uwPiY8_59Fz9Dnm4qiMZlH5rhYv_7LI2Y.woff2 differ diff --git a/js/assets/fonts/RobotoMono/v4/N4duVc9C58uwPiY8_59Fz9TIkQYohD4BpHvJ3NvbHoA.woff2 b/js/assets/fonts/RobotoMono/v4/N4duVc9C58uwPiY8_59Fz9TIkQYohD4BpHvJ3NvbHoA.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..62b3a15f698a5e966f5f6b4a5b6d5081e340c654 Binary files /dev/null and b/js/assets/fonts/RobotoMono/v4/N4duVc9C58uwPiY8_59Fz9TIkQYohD4BpHvJ3NvbHoA.woff2 differ diff --git a/js/assets/fonts/RobotoMono/v4/N4duVc9C58uwPiY8_59FzyJ0caWjaSBdV-xZbEgst_k.woff2 b/js/assets/fonts/RobotoMono/v4/N4duVc9C58uwPiY8_59FzyJ0caWjaSBdV-xZbEgst_k.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..2d4fda538a22e1046718d4d158e7512eae086f69 Binary files /dev/null and b/js/assets/fonts/RobotoMono/v4/N4duVc9C58uwPiY8_59FzyJ0caWjaSBdV-xZbEgst_k.woff2 differ diff --git a/js/assets/images/certifications/unknown.svg b/js/assets/images/certifications/unknown.svg new file mode 100644 index 0000000000000000000000000000000000000000..1554bcc258119e20eba68341d5b81e26867aff93 --- /dev/null +++ b/js/assets/images/certifications/unknown.svg @@ -0,0 +1,4 @@ + + + + diff --git a/js/assets/images/contracts/ethereum-black-64x64.png b/js/assets/images/contracts/ethereum-black-64x64.png new file mode 100644 index 0000000000000000000000000000000000000000..8e80e4ff134759b9cfbbc6f535791b93aac64a49 Binary files /dev/null and b/js/assets/images/contracts/ethereum-black-64x64.png differ diff --git a/js/assets/images/contracts/ethereum-black.png b/js/assets/images/contracts/ethereum-black.png new file mode 100644 index 0000000000000000000000000000000000000000..d7bdc4c904012b2224306216f1d36c78eb29122a Binary files /dev/null and b/js/assets/images/contracts/ethereum-black.png differ diff --git a/js/assets/images/contracts/ethereum-white.png b/js/assets/images/contracts/ethereum-white.png new file mode 100644 index 0000000000000000000000000000000000000000..0691d09aeaffaa78db1a267f3527a79072d71b73 Binary files /dev/null and b/js/assets/images/contracts/ethereum-white.png differ diff --git a/js/assets/images/contracts/unknown-64x64.png b/js/assets/images/contracts/unknown-64x64.png new file mode 100644 index 0000000000000000000000000000000000000000..c2b39a80a372151cb98f216568917953a2b76727 Binary files /dev/null and b/js/assets/images/contracts/unknown-64x64.png differ diff --git a/js/assets/images/contracts/unknown.png b/js/assets/images/contracts/unknown.png new file mode 100644 index 0000000000000000000000000000000000000000..204ffa486372bfed2b04aca09f3f5eaa63115845 Binary files /dev/null and b/js/assets/images/contracts/unknown.png differ diff --git a/js/assets/images/dapps/blocks-350.jpg b/js/assets/images/dapps/blocks-350.jpg new file mode 100644 index 0000000000000000000000000000000000000000..524f9267c50777c162f3f2081933716329926907 Binary files /dev/null and b/js/assets/images/dapps/blocks-350.jpg differ diff --git a/js/assets/images/dapps/close.svg b/js/assets/images/dapps/close.svg new file mode 100644 index 0000000000000000000000000000000000000000..d46acd872988a41bca4206aafb783e42d3d41d55 --- /dev/null +++ b/js/assets/images/dapps/close.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/js/assets/images/dapps/plus.svg b/js/assets/images/dapps/plus.svg new file mode 100644 index 0000000000000000000000000000000000000000..0fce86130ad3291c4930981069081102ffdd6657 --- /dev/null +++ b/js/assets/images/dapps/plus.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/js/assets/images/dapps/signature.png b/js/assets/images/dapps/signature.png new file mode 100644 index 0000000000000000000000000000000000000000..36b341a036642764e48a0a958077b2440f370cbe Binary files /dev/null and b/js/assets/images/dapps/signature.png differ diff --git a/js/assets/images/parity-logo-black-no-text.ico b/js/assets/images/parity-logo-black-no-text.ico new file mode 100644 index 0000000000000000000000000000000000000000..8edfa3e6a9d902ee4b9f9ff54659a61a1b52a55c Binary files /dev/null and b/js/assets/images/parity-logo-black-no-text.ico differ diff --git a/js/assets/images/parity-logo-black-no-text.png b/js/assets/images/parity-logo-black-no-text.png new file mode 100644 index 0000000000000000000000000000000000000000..b2aea789e82472d574be6e74a9f3bb9dc92004df Binary files /dev/null and b/js/assets/images/parity-logo-black-no-text.png differ diff --git a/js/assets/images/parity-logo-black-no-text.svg b/js/assets/images/parity-logo-black-no-text.svg new file mode 100644 index 0000000000000000000000000000000000000000..a4f104838702e3a6c431cf3113f866101ddecafc --- /dev/null +++ b/js/assets/images/parity-logo-black-no-text.svg @@ -0,0 +1,65 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/js/assets/images/parity-logo-black.svg b/js/assets/images/parity-logo-black.svg new file mode 100644 index 0000000000000000000000000000000000000000..f2b2fa72633bd2de0d810e99bdc2365ebcdf4882 --- /dev/null +++ b/js/assets/images/parity-logo-black.svg @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/js/assets/images/parity-logo-white-no-text.svg b/js/assets/images/parity-logo-white-no-text.svg new file mode 100644 index 0000000000000000000000000000000000000000..213ab1db441d60a7bb04e4a84fef845c97020eec --- /dev/null +++ b/js/assets/images/parity-logo-white-no-text.svg @@ -0,0 +1,66 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/js/assets/images/parity-logo-white.svg b/js/assets/images/parity-logo-white.svg new file mode 100644 index 0000000000000000000000000000000000000000..7360ef6a6d7c36ded131a370c71fd146226a5a84 --- /dev/null +++ b/js/assets/images/parity-logo-white.svg @@ -0,0 +1,100 @@ + + + +image/svg+xml diff --git a/js/assets/images/paritybar.png b/js/assets/images/paritybar.png new file mode 100644 index 0000000000000000000000000000000000000000..f0b0e1457409c386690e52ab1091393de19aedc4 Binary files /dev/null and b/js/assets/images/paritybar.png differ diff --git a/js/assets/images/shapeshift-btn.png b/js/assets/images/shapeshift-btn.png new file mode 100644 index 0000000000000000000000000000000000000000..1650ecf375660f706f59ae7bf2f4455dedeb8aae Binary files /dev/null and b/js/assets/images/shapeshift-btn.png differ diff --git a/js/assets/images/shapeshift-logo.png b/js/assets/images/shapeshift-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..586a01ef13a7ce480d46b88fe6dc457d3083c966 Binary files /dev/null and b/js/assets/images/shapeshift-logo.png differ diff --git a/js/npm/etherscan/README.md b/js/npm/etherscan/README.md new file mode 100644 index 0000000000000000000000000000000000000000..730cb7e1d0e94744eeca3e8d33750e0a74f3d5e3 --- /dev/null +++ b/js/npm/etherscan/README.md @@ -0,0 +1,34 @@ +# @parity/etherscan + +A thin, lightweight promise wrapper for the api.etherscan.io/apis service, exposing a common endpoint for use in JavaScript applications. + +[https://github.com/paritytech/parity/tree/master/js/src/3rdparty/etherscan](https://github.com/paritytech/parity/tree/master/js/src/3rdparty/etherscan) + +## usage + +installation - + +``` +npm install --save @parity/etherscan +``` + +Usage - + +``` +const etherscan = require('@parity/etherscan'); + +// api calls goes here +``` + +## api + +account (exposed on etherscan.account) - + +- `balance(address)` +- `balances(addresses)` (array or addresses) +- `transactions(address, page)` (page offset starts at 0, returns 25) + +stats (exposed on etherscan.stats) - + +- `price()` +- `supply()` diff --git a/js/npm/etherscan/package.json b/js/npm/etherscan/package.json new file mode 100644 index 0000000000000000000000000000000000000000..1aed97171dd9d4816a0378e641887ef176c7ffc3 --- /dev/null +++ b/js/npm/etherscan/package.json @@ -0,0 +1,33 @@ +{ + "name": "@parity/etherscan", + "description": "The Parity Promise-based library for interfacing with Etherscan over HTTP", + "version": "0.0.0", + "main": "library.js", + "author": "Parity Team ", + "maintainers": [ + "Jaco Greeff" + ], + "contributors": [], + "license": "GPL-3.0", + "repository": { + "type": "git", + "url": "git+https://github.com/paritytech/parity.git" + }, + "keywords": [ + "Ethereum", + "ABI", + "API", + "RPC", + "Parity", + "Promise" + ], + "scripts": { + }, + "devDependencies": { + "chai": "3.5.0", + "mocha": "3.2.0" + }, + "dependencies": { + "node-fetch": "~1.6.3" + } +} diff --git a/js/npm/jsonrpc/README.md b/js/npm/jsonrpc/README.md new file mode 100644 index 0000000000000000000000000000000000000000..e9678864b4d9f9ad13bb7eee8a0174e4f5c13919 --- /dev/null +++ b/js/npm/jsonrpc/README.md @@ -0,0 +1,5 @@ +# @parity/jsonrpc + +JSON and JS interface defintions for RPC calls. + +[https://github.com/paritytech/parity/tree/master/js/src/jsonrpc](https://github.com/paritytech/parity/tree/master/js/src/jsonrpc) diff --git a/js/npm/jsonrpc/package.json b/js/npm/jsonrpc/package.json new file mode 100644 index 0000000000000000000000000000000000000000..90ebe1577b7e3b2b4352b463e36096936298ae16 --- /dev/null +++ b/js/npm/jsonrpc/package.json @@ -0,0 +1,29 @@ +{ + "name": "@parity/jsonrpc", + "description": "JSON and JS interface defintions for RPC", + "version": "0.0.0", + "main": "library.js", + "author": "Parity Team ", + "maintainers": [ + "Jaco Greeff" + ], + "contributors": [], + "license": "GPL-3.0", + "repository": { + "type": "git", + "url": "git+https://github.com/paritytech/parity.git" + }, + "keywords": [ + "Ethereum", + "ABI", + "API", + "RPC", + "Parity" + ], + "scripts": { + }, + "devDependencies": { + }, + "dependencies": { + } +} diff --git a/js/npm/parity/README.md b/js/npm/parity/README.md new file mode 100644 index 0000000000000000000000000000000000000000..2c5396e2d6b9b54ef981f56917b5e15a1fc3cf69 --- /dev/null +++ b/js/npm/parity/README.md @@ -0,0 +1,83 @@ +# @parity/parity.js + +Parity.js is a thin, fast, Promise-based wrapper around the Ethereum APIs. + +[https://github.com/paritytech/parity/tree/master/js/src/api](https://github.com/paritytech/parity/tree/master/js/src/api) + +## installation + +Install the package with `npm install --save @parity/parity.js` + +## usage + +### initialisation + +```javascript +// import the actual Api class +import { Api } from '@parity/parity.js'; + +// do the setup +const transport = new Api.Transport.Http('http://localhost:8545'); +const api = new Api(transport); +``` + +### making calls + +perform a call + +```javascript +api.eth + .coinbase() + .then((coinbase) => { + console.log(`The coinbase is ${coinbase}`); + }); +``` + +multiple promises + +```javascript +Promise + .all([ + api.eth.coinbase(), + api.net.listening() + ]) + .then(([coinbase, listening]) => { + // do stuff here + }); +``` + +chaining promises + +```javascript +api.eth + .newFilter({...}) + .then((filterId) => api.eth.getFilterChanges(filterId)) + .then((changes) => { + console.log(changes); + }); +``` + +### contracts + +attach contract + +```javascript +const abi = [{ name: 'callMe', inputs: [{ type: 'bool', ...}, { type: 'string', ...}]}, ...abi...]; +const address = '0x123456...9abc'; +const contract = new api.newContract(abi, address); +``` + +find & call a function + +```javascript +contract.instance + .callMe + .call({ gas: 21000 }, [true, 'someString']) // or estimateGas or postTransaction + .then((result) => { + console.log(`the result was ${result}`); + }); +``` + +## apis + +APIs implement the calls as exposed in the [Ethcore JSON Ethereum RPC](https://github.com/paritytech/ethereum-rpc-json/) definitions. Mapping follows the naming conventions of the originals, i.e. `eth_call` becomes `eth.call`, `personal_accounts` becomes `personal.accounts`, etc. diff --git a/js/npm/parity/package.json b/js/npm/parity/package.json new file mode 100644 index 0000000000000000000000000000000000000000..c43da096d3e6ad36f8a99e585cffa51a83789c91 --- /dev/null +++ b/js/npm/parity/package.json @@ -0,0 +1,33 @@ +{ + "name": "@parity/parity.js", + "description": "The Parity Promise-based API & ABI library for interfacing with Ethereum over RPC", + "version": "0.0.0", + "main": "library.js", + "author": "Parity Team ", + "maintainers": [ + "Jaco Greeff" + ], + "contributors": [], + "license": "GPL-3.0", + "repository": { + "type": "git", + "url": "git+https://github.com/paritytech/parity.git" + }, + "keywords": [ + "Ethereum", + "ABI", + "API", + "RPC", + "Parity", + "Promise" + ], + "scripts": { + }, + "devDependencies": { + }, + "dependencies": { + "bignumber.js": "~2.3.0", + "js-sha3": "~0.5.2", + "node-fetch": "~1.6.3" + } +} diff --git a/js/npm/parity/test/smoke.spec.js b/js/npm/parity/test/smoke.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..fb1ae0d4d52a29538f50a64238d78e159aac5ffe --- /dev/null +++ b/js/npm/parity/test/smoke.spec.js @@ -0,0 +1,26 @@ +// 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 . + +const parity = require('../'); + +describe('load the Parity library', function () { + it('should no throw any error', () => { + expect(parity).to.be.ok; + + expect(parity.Api).to.be.ok; + expect(parity.Abi).to.be.ok; + }); +}); diff --git a/js/npm/shapeshift/README.md b/js/npm/shapeshift/README.md new file mode 100644 index 0000000000000000000000000000000000000000..0f9a190abc13f2c6f7431f8de22eaa6c21590b18 --- /dev/null +++ b/js/npm/shapeshift/README.md @@ -0,0 +1,34 @@ +# @parity/shapeshift + +A thin ES6 promise wrapper around the shapeshift.io APIs as documented at https://shapeshift.io/api + +[https://github.com/paritytech/parity/tree/master/js/src/3rdparty/shapeshift](https://github.com/paritytech/parity/tree/master/js/src/3rdparty/shapeshift) + +## usage + +installation - + +``` +npm install --save @parity/shapeshift +``` + +Usage - + +``` +const APIKEY = 'private affiliate key or undefined'; +const shapeshift = require('@parity/shapeshift')(APIKEY); + +// api calls goes here +``` + +## api + +queries - + +- `getCoins()` [https://shapeshift.io/api#api-104](https://shapeshift.io/api#api-104) +- `getMarketInfo(pair)` [https://shapeshift.io/api#api-103](https://shapeshift.io/api#api-103) +- `getStatus(depositAddress)` [https://shapeshift.io/api#api-5](https://shapeshift.io/api#api-5) + +transactions - + +- `shift(toAddress, returnAddress, pair)` [https://shapeshift.io/api#api-7](https://shapeshift.io/api#api-7) diff --git a/js/npm/shapeshift/package.json b/js/npm/shapeshift/package.json new file mode 100644 index 0000000000000000000000000000000000000000..1aa2d38f315ed4ccc46fba82422e232bb6bf0d8a --- /dev/null +++ b/js/npm/shapeshift/package.json @@ -0,0 +1,31 @@ +{ + "name": "@parity/shapeshift", + "description": "The Parity Promise-based library for interfacing with ShapeShift over HTTP", + "version": "0.0.0", + "main": "library.js", + "author": "Parity Team ", + "maintainers": [ + "Jaco Greeff" + ], + "contributors": [], + "license": "GPL-3.0", + "repository": { + "type": "git", + "url": "git+https://github.com/paritytech/parity.git" + }, + "keywords": [ + "Ethereum", + "ABI", + "API", + "RPC", + "Parity", + "Promise" + ], + "scripts": { + }, + "devDependencies": { + }, + "dependencies": { + "node-fetch": "~1.6.3" + } +} diff --git a/js/npm/test/mocha.config.js b/js/npm/test/mocha.config.js new file mode 100644 index 0000000000000000000000000000000000000000..fc21631b0a811e8c150e1168cea160b00380a61b --- /dev/null +++ b/js/npm/test/mocha.config.js @@ -0,0 +1,29 @@ +// 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 . + +const chai = require('chai'); +// const chaiAsPromised from 'chai-as-promised'; +// const chaiEnzyme from 'chai-enzyme'; +// const sinonChai from 'sinon-chai'; + +// chai.use(chaiAsPromised); +// chai.use(chaiEnzyme()); +// chai.use(sinonChai); + +// expose expect to global so we won't have to manually import & define it in every test +global.expect = chai.expect; + +module.exports = {}; diff --git a/js/npm/test/mocha.opts b/js/npm/test/mocha.opts new file mode 100644 index 0000000000000000000000000000000000000000..0ed8269b4ff3676c83c4819eb7046804ef55804a --- /dev/null +++ b/js/npm/test/mocha.opts @@ -0,0 +1 @@ +-r ./test/mocha.config diff --git a/js/scripts/build-rpc-json.js b/js/scripts/build-rpc-json.js new file mode 100644 index 0000000000000000000000000000000000000000..740f96aa104365293fb59ab2f88bff87afe05b6a --- /dev/null +++ b/js/scripts/build-rpc-json.js @@ -0,0 +1,70 @@ +// 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 fs from 'fs'; +import path from 'path'; +import yargs from 'yargs'; + +import interfaces from '../src/jsonrpc'; + +const argv = yargs.default('output', 'release').argv; + +const INDEX_JSON = path.join(__dirname, `../${argv.output}/index.json`); +const methods = []; + +function formatDescription (obj) { + const optional = obj.optional ? '(optional) ' : ''; + const defaults = obj.default ? `(default: ${obj.default}) ` : ''; + + return `${obj.type.name} - ${optional}${defaults}${obj.desc}`; +} + +function formatType (obj) { + if (obj.type === Object && obj.details) { + const formatted = {}; + + Object.keys(obj.details).sort().forEach((key) => { + formatted[key] = formatType(obj.details[key]); + }); + + return { + desc: formatDescription(obj), + details: formatted + }; + } else if (obj.type && obj.type.name) { + return formatDescription(obj); + } + + return obj; +} + +Object.keys(interfaces).sort().forEach((group) => { + Object.keys(interfaces[group]).sort().forEach((name) => { + const method = interfaces[group][name]; + const deprecated = method.deprecated ? ' (Deprecated and not supported, to be removed in a future version)' : ''; + + methods.push({ + name: `${group}_${name}`, + desc: `${method.desc}${deprecated}`, + params: method.params.map(formatType), + returns: formatType(method.returns), + inputFormatters: method.params.map((param) => param.format || null), + outputFormatter: method.returns.format || null + }); + }); +}); + +fs.writeFileSync(INDEX_JSON, JSON.stringify({ methods: methods }, null, 2), 'utf8'); diff --git a/js/scripts/build-rpc-markdown.js b/js/scripts/build-rpc-markdown.js new file mode 100644 index 0000000000000000000000000000000000000000..7a78e1ce96c3c52ae764a6b8899e8b700407811b --- /dev/null +++ b/js/scripts/build-rpc-markdown.js @@ -0,0 +1,328 @@ +// 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 fs from 'fs'; +import path from 'path'; +import { isPlainObject } from 'lodash'; + +import { info, warn, error } from './helpers/log'; +import { Dummy } from '../src/jsonrpc/helpers'; +import interfaces from '../src/jsonrpc'; +import rustMethods from './helpers/parsed-rpc-traits'; + +const ROOT_DIR = path.join(__dirname, '../docs'); + +if (!fs.existsSync(ROOT_DIR)) { + fs.mkdirSync(ROOT_DIR); +} + +Object.keys(rustMethods).forEach((group) => { + Object.keys(rustMethods[group]).forEach((method) => { + if (interfaces[group] == null || interfaces[group][method] == null) { + error(`${group}_${method} is defined in Rust traits, but not in js/src/jsonrpc/interfaces`); + } + }); +}); + +function printType (type, obj) { + if (!type) { + throw new Error(`Invalid type in ${JSON.stringify(obj)}`); + } + + return type.print || `\`${type.name}\``; +} + +function formatDescription (obj, prefix = '', indent = '') { + const optional = obj.optional ? '(optional) ' : ''; + const defaults = obj.default ? `(default: \`${obj.default}\`) ` : ''; + + return `${indent}${prefix}${printType(obj.type, obj)} - ${optional}${defaults}${obj.desc}`; +} + +function formatType (obj) { + if (obj == null || obj.type == null) { + return obj; + } + + const details = obj.details || obj.type.details; + + if (details) { + const sub = Object.keys(details).map((key) => { + return formatDescription(details[key], `\`${key}\`: `, ' - '); + }).join('\n'); + + return `${formatDescription(obj)}\n${sub}`; + } else if (obj.type && obj.type.name) { + return formatDescription(obj); + } + + return obj; +} + +const rpcReqTemplate = { + method: 'web3_clientVersion', + params: [], + id: 1, + jsonrpc: '2.0' +}; + +const { isDummy } = Dummy; +const { isArray } = Array; + +// Checks if a field definition has an example, +// or describes an object with fields that recursively have examples of their own, +// or is optional. +function hasExample ({ optional, example, details } = {}) { + if (optional || example !== undefined) { + return true; + } + + if (details !== undefined) { + const values = Object.keys(details).map((key) => details[key]); + + return values.every(hasExample); + } + + return false; +} + +// Remove all optional (trailing) params without examples from an array +function removeOptionalWithoutExamples (arr) { + return arr.filter(({ optional, example, details }) => { + return !optional || example !== undefined || details !== undefined; + }); +} + +// Grabs JSON compatible +function getExample (obj) { + if (isArray(obj)) { + return removeOptionalWithoutExamples(obj).map(getExample); + } + + const { example, details } = obj; + + if (example === undefined && details !== undefined) { + const nested = {}; + + Object.keys(details).forEach((key) => { + nested[key] = getExample(details[key]); + }); + + return nested; + } + + return example; +} + +function stringifyExample (example, dent = '') { + const indent = `${dent} `; + + if (isDummy(example)) { + return example.toString(); + } + + if (isArray(example)) { + const last = example.length - 1; + + // If all elements are dummies, print out a single line. + // Also covers empty arrays. + if (example.every(isDummy)) { + const dummies = example.map(d => d.toString()); + + return `[${dummies.join(', ')}]`; + } + + // For arrays containing just one object or string, don't unwind the array to multiline + if (last === 0 && (isPlainObject(example[0]) || typeof example[0] === 'string')) { + return `[${stringifyExample(example[0], dent)}]`; + } + + const elements = example.map((value, index) => { + const comma = index !== last ? ',' : ''; + const comment = value != null && value._comment ? ` // ${value._comment}` : ''; + + return `${stringifyExample(value, indent)}${comma}${comment}`; + }); + + return `[\n${indent}${elements.join(`\n${indent}`)}\n${dent}]`; + } + + if (isPlainObject(example)) { + const keys = Object.keys(example); + const last = keys.length - 1; + + // print out an empty object + if (last === -1) { + return '{}'; + } + + const elements = keys.map((key, index) => { + const value = example[key]; + const comma = index !== last ? ',' : ''; + const comment = value && value._comment ? ` // ${example[key]._comment}` : ''; + + return `${JSON.stringify(key)}: ${stringifyExample(value, indent)}${comma}${comment}`; + }); + + return `{\n${indent}${elements.join(`\n${indent}`)}\n${dent}}`; + } + + return JSON.stringify(example); +} + +function buildExample (name, method) { + // deprecated, don't care + if (method.deprecated) { + return ''; + } + + const logPostfix = method.subdoc ? ` (${method.subdoc})` : ''; + + const hasReqExample = method.params.every(hasExample); + const hasResExample = hasExample(method.returns); + + if (!hasReqExample && !hasResExample) { + error(`${name} has no examples${logPostfix}`); + + return ''; + } + + const examples = []; + + if (hasReqExample) { + const params = getExample(method.params); + const req = Dummy.stringifyJSON(Object.assign({}, rpcReqTemplate, { method: name, params })); + + examples.push(`Request\n\`\`\`bash\ncurl --data '${req}' -H "Content-Type: application/json" -X POST localhost:8545\n\`\`\``); + } else { + warn(`${name} has a response example but not a request example${logPostfix}`); + } + + if (hasResExample) { + const res = stringifyExample({ + id: 1, + jsonrpc: '2.0', + result: getExample(method.returns) + }); + + examples.push(`Response\n\`\`\`js\n${res}\n\`\`\``); + } else { + if (typeof method.returns === 'string') { + info(`${name} has a request example and only text description for response${logPostfix}`); + } else { + warn(`${name} has a request example but not a response example${logPostfix}`); + } + } + + return `\n\n#### Example\n\n${examples.join('\n\n')}`; +} + +function buildParameters (params) { + if (params.length === 0) { + return ''; + } + + let md = `0. ${params.map(formatType).join('\n0. ')}`; + + if (params.length > 0 && params.every(hasExample) && !isDummy(params[0].example)) { + const example = getExample(params); + + md = `${md}\n\n\`\`\`js\nparams: ${stringifyExample(example)}\n\`\`\``; + } + + return md; +} + +Object.keys(interfaces).sort().forEach((group) => { + const spec = interfaces[group]; + + for (const key in spec) { + const method = spec[key]; + + if (!method || !method.subdoc) { + continue; + } + + const subgroup = `${group}_${method.subdoc}`; + + interfaces[subgroup] = interfaces[subgroup] || {}; + + interfaces[subgroup][key] = method; + delete spec[key]; + } +}); + +Object.keys(interfaces).sort().forEach((group) => { + let preamble = `# The \`${group}\` Module`; + let markdown = `## JSON-RPC methods\n`; + + const spec = interfaces[group]; + + if (spec._preamble) { + preamble = `${preamble}\n\n${spec._preamble}`; + } + + const content = []; + const tocMain = []; + const tocSections = {}; + + // Comparator that will sort by sections first, names second + function methodComparator (a, b) { + const sectionA = spec[a].section || ''; + const sectionB = spec[b].section || ''; + + return sectionA.localeCompare(sectionB) || a.localeCompare(b); + } + + Object.keys(spec).sort(methodComparator).forEach((iname) => { + const method = spec[iname]; + const groupName = group.replace(/_.*$/, ''); + const name = `${groupName}_${iname}`; + + if (method.nodoc || method.deprecated) { + info(`Skipping ${name}: ${method.nodoc || 'Deprecated'}`); + + return; + } + + if (rustMethods[groupName] == null || rustMethods[groupName][iname] == null) { + error(`${name} is defined in js/src/jsonrpc/interfaces, but not in Rust traits`); + } + + const desc = method.desc; + const params = buildParameters(method.params); + const returns = `- ${formatType(method.returns)}`; + const example = buildExample(name, method); + + const { section } = method; + const toc = section ? tocSections[section] = tocSections[section] || [] : tocMain; + + toc.push(`- [${name}](#${name.toLowerCase()})`); + content.push(`### ${name}\n\n${desc}\n\n#### Parameters\n\n${params || 'None'}\n\n#### Returns\n\n${returns || 'None'}${example}`); + }); + + markdown = `${markdown}\n${tocMain.join('\n')}`; + + Object.keys(tocSections).sort().forEach((section) => { + markdown = `${markdown}\n\n#### ${section}\n${tocSections[section].join('\n')}`; + }); + + markdown = `${markdown}\n\n## JSON-RPC API Reference\n\n${content.join('\n\n***\n\n')}\n\n`; + + const mdFile = path.join(ROOT_DIR, `${group}.md`); + + fs.writeFileSync(mdFile, `${preamble}\n\n${markdown}`, 'utf8'); +}); diff --git a/js/scripts/helpers/log.js b/js/scripts/helpers/log.js new file mode 100644 index 0000000000000000000000000000000000000000..dc495564995c1e8198c06ea4937d846cb2e0a2ad --- /dev/null +++ b/js/scripts/helpers/log.js @@ -0,0 +1,32 @@ +// 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 chalk from 'chalk'; + +// INFO Logging helper +export function info (log) { + console.log(chalk.blue(`INFO:\t${log}`)); +} + +// WARN Logging helper +export function warn (log) { + console.warn(chalk.yellow(`WARN:\t${log}`)); +} + +// ERROR Logging helper +export function error (log) { + console.error(chalk.red(`ERROR:\t${log}`)); +} diff --git a/js/scripts/helpers/parsed-rpc-traits.js b/js/scripts/helpers/parsed-rpc-traits.js new file mode 100644 index 0000000000000000000000000000000000000000..9f2d4ea8a47a07183f8979643d5ad5fda516651d --- /dev/null +++ b/js/scripts/helpers/parsed-rpc-traits.js @@ -0,0 +1,81 @@ +// 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 fs from 'fs'; +import path from 'path'; + +// ```js +// rustMethods['eth']['call'] === true +// ``` +const rustMethods = {}; + +export default rustMethods; + +// Get a list of JSON-RPC from Rust trait source code +function parseMethodsFromRust (source) { + // Matching the custom `rpc` attribute with it's doc comment + const attributePattern = /((?:\s*\/\/\/.*$)*)\s*#\[rpc\(([^)]+)\)]/gm; + const commentPattern = /\s*\/\/\/\s*/g; + const separatorPattern = /\s*,\s*/g; + const assignPattern = /([\S]+)\s*=\s*"([^"]*)"/; + const ignorePattern = /@(ignore|deprecated|unimplemented|alias)\b/i; + + const methods = []; + + source.toString().replace(attributePattern, (match, comment, props) => { + comment = comment.replace(commentPattern, '\n').trim(); + + // Skip deprecated methods + if (ignorePattern.test(comment)) { + return match; + } + + props.split(separatorPattern).forEach((prop) => { + const [, key, value] = prop.split(assignPattern) || []; + + if (key === 'name' && value != null) { + methods.push(value); + } + }); + + return match; + }); + + return methods; +} + +// Get a list of all JSON-RPC methods from all defined traits +function getMethodsFromRustTraits () { + const traitsDir = path.join(__dirname, '../../../rpc/src/v1/traits'); + + return fs.readdirSync(traitsDir) + .filter((name) => name !== 'mod.rs' && /\.rs$/.test(name)) + .map((name) => fs.readFileSync(path.join(traitsDir, name))) + .map(parseMethodsFromRust) + .reduce((a, b) => a.concat(b)); +} + +getMethodsFromRustTraits().sort().forEach((method) => { + const [group, name] = method.split('_'); + + // Skip methods with malformed names + if (group == null || name == null) { + return; + } + + rustMethods[group] = rustMethods[group] || {}; + rustMethods[group][name] = true; +}); diff --git a/js/src/3rdparty/email-verification/index.js b/js/src/3rdparty/email-verification/index.js new file mode 100644 index 0000000000000000000000000000000000000000..9e79547ec66bb22d9c6f7e8c4eeba0da358945e9 --- /dev/null +++ b/js/src/3rdparty/email-verification/index.js @@ -0,0 +1,68 @@ +// 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 { stringify } from 'querystring'; + +export const isServerRunning = (isTestnet = false) => { + const port = isTestnet ? 28443 : 18443; + + return fetch(`https://email-verification.parity.io:${port}/health`, { + mode: 'cors', + cache: 'no-store' + }) + .then((res) => { + return res.ok; + }) + .catch(() => { + return false; + }); +}; + +export const hasReceivedCode = (email, address, isTestnet = false) => { + const port = isTestnet ? 28443 : 18443; + const query = stringify({ email, address }); + + return fetch(`https://email-verification.parity.io:${port}/?${query}`, { + mode: 'cors', + cache: 'no-store' + }) + .then((res) => { + return res.ok; + }) + .catch(() => { + return false; // todo: check for 404 + }); +}; + +export const postToServer = (query, isTestnet = false) => { + const port = isTestnet ? 28443 : 18443; + + query = stringify(query); + + return fetch(`https://email-verification.parity.io:${port}/?${query}`, { + method: 'POST', + mode: 'cors', + cache: 'no-store' + }) + .then((res) => { + return res.json().then((data) => { + if (res.ok) { + return data.message; + } + throw new Error(data.message || 'unknown error'); + }); + }); +}; diff --git a/js/src/3rdparty/email-verification/terms-of-service.js b/js/src/3rdparty/email-verification/terms-of-service.js new file mode 100644 index 0000000000000000000000000000000000000000..8826be0329090acaca3dd1b63caaed221eb1e6f2 --- /dev/null +++ b/js/src/3rdparty/email-verification/terms-of-service.js @@ -0,0 +1,27 @@ +// 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 from 'react'; + +export default ( +
    +
  • This privacy notice relates to your use of the Parity email verification service. We take your privacy seriously and deal in an honest, direct and transparent way when it comes to your data.
  • +
  • We collect your email address when you use this service. This is temporarily kept in memory, and then encrypted and stored in our EU servers. We only retain the cryptographic hash of the email address to prevent duplicated accounts. The cryptographic hash of your email address is also stored on the blockchain which is public by design. You consent to this use.
  • +
  • You pay a fee for the cost of this service using the account you want to verify.
  • +
  • Your email address is transmitted to a third party EU email verification service mailjet for the sole purpose of the email verification. You consent to this use. Mailjet's privacy policy is here: https://www.mailjet.com/privacy-policy.
  • +
  • Parity Technology Limited is registered in England and Wales under company number 09760015 and complies with the Data Protection Act 1998 (UK). You may contact us via email at admin@parity.io. Our general privacy policy can be found here: https://parity.io/legal.html.
  • +
+); diff --git a/js/src/3rdparty/etherscan/account.js b/js/src/3rdparty/etherscan/account.js new file mode 100644 index 0000000000000000000000000000000000000000..7a6844759117bca1289bfbf5df4ee2b78411fb16 --- /dev/null +++ b/js/src/3rdparty/etherscan/account.js @@ -0,0 +1,75 @@ +// 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 BigNumber from 'bignumber.js'; + +const PAGE_SIZE = 25; + +import util from '../../api/util'; +import { call } from './call'; + +function _call (method, params, test, netVersion) { + return call('account', method, params, test, netVersion); +} + +function balance (address, test, netVersion) { + return _call('balance', { + address: address, + tag: 'latest' + }, test, netVersion).then((balance) => { + // same format as balancemulti below + return { + account: address, + balance: balance + }; + }); +} + +function balances (addresses, test, netVersion) { + return _call('balancemulti', { + address: addresses.join(','), + tag: 'latest' + }, test, netVersion); +} + +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, netVersion).then((transactions) => { + return transactions.map((tx) => { + return { + blockNumber: new BigNumber(tx.blockNumber || 0), + from: util.toChecksumAddress(tx.from), + hash: tx.hash, + timeStamp: tx.timeStamp, + to: util.toChecksumAddress(tx.to), + value: tx.value + }; + }); + }); +} + +const account = { + balance, + balances, + transactions +}; + +export { account }; diff --git a/js/src/3rdparty/etherscan/account.spec.js b/js/src/3rdparty/etherscan/account.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..54eff9f9cd5ca0cda0ece385a820fa1dd8d4bf49 --- /dev/null +++ b/js/src/3rdparty/etherscan/account.spec.js @@ -0,0 +1,71 @@ +// 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 . + +const etherscan = require('./'); + +const TESTADDR = '0xbf885e2b55c6bcc84556a3c5f07d3040833c8d00'; + +describe.skip('etherscan/account', function () { + this.timeout(60 * 1000); + + const checkBalance = function (balance, addr) { + expect(balance).to.be.ok; + expect(balance.account).to.equal(addr); + expect(balance.balance).to.be.ok; + }; + + it('retrieves an account balance', () => { + return etherscan.account + .balance(TESTADDR) + .then((balance) => { + checkBalance(balance, TESTADDR); + }); + }); + + it('retrieves multi account balances', () => { + const addresses = ['0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae', TESTADDR]; + + return etherscan.account + .balances(addresses) + .then((balances) => { + expect(balances).to.be.ok; + expect(balances.length).to.equal(2); + balances.forEach((balance, idx) => { + checkBalance(balance, addresses[idx]); + }); + }); + }); + + describe('transactions', () => { + it('retrieves a list of transactions (default)', () => { + return etherscan.account + .transactions(TESTADDR) + .then((transactions) => { + expect(transactions).to.be.ok; + expect(transactions.length).to.equal(25); + }); + }); + + it('retrieves a list of transactions (page 1)', () => { + return etherscan.account + .transactions(TESTADDR, 1) + .then((transactions) => { + expect(transactions).to.be.ok; + expect(transactions.length).to.equal(25); + }); + }); + }); +}); diff --git a/js/src/3rdparty/etherscan/call.js b/js/src/3rdparty/etherscan/call.js new file mode 100644 index 0000000000000000000000000000000000000000..069627d78567fbbe1c001d1db777997a4a9e81be --- /dev/null +++ b/js/src/3rdparty/etherscan/call.js @@ -0,0 +1,47 @@ +// 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 { stringify } from 'qs'; +import { apiLink } from './links'; + +const options = { + method: 'GET', + headers: { + 'Accept': 'application/json' + } +}; + +export function call (module, action, _params, test, netVersion) { + const query = stringify(Object.assign({ + module, action + }, _params || {})); + + return fetch(apiLink(query, test, netVersion), options) + .then((response) => { + if (!response.ok) { + throw { code: response.status, message: response.statusText }; // eslint-disable-line + } + + return response.json(); + }) + .then((result) => { + if (result.message === 'NOTOK') { + throw { code: -1, message: result.result }; // eslint-disable-line + } + + return result.result; + }); +} diff --git a/js/src/3rdparty/etherscan/helpers.spec.js b/js/src/3rdparty/etherscan/helpers.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..fa29c3d97715003893163b11bae6b13db043a2cb --- /dev/null +++ b/js/src/3rdparty/etherscan/helpers.spec.js @@ -0,0 +1,38 @@ +// 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 nock from 'nock'; +import { stringify } from 'qs'; + +import { url } from './links'; + +function mockget (requests, test, netVersion) { + let scope = nock(url(test, netVersion)); + + requests.forEach((request) => { + scope = scope + .get(`/api?${stringify(request.query)}`) + .reply(request.code || 200, () => { + return { result: request.reply }; + }); + }); + + return scope; +} + +export { + mockget +}; diff --git a/js/src/3rdparty/etherscan/index.js b/js/src/3rdparty/etherscan/index.js new file mode 100644 index 0000000000000000000000000000000000000000..594e833a8f8de02c0046d79db8a1b1b66d3bfdc1 --- /dev/null +++ b/js/src/3rdparty/etherscan/index.js @@ -0,0 +1,28 @@ +// 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 { account } from './account'; +import { stats } from './stats'; +import { txLink, addressLink } from './links'; + +const etherscan = { + account: account, + stats: stats, + txLink: txLink, + addressLink: addressLink +}; + +export default etherscan; diff --git a/js/src/3rdparty/etherscan/links.js b/js/src/3rdparty/etherscan/links.js new file mode 100644 index 0000000000000000000000000000000000000000..3b08d60e835455fdd5dbea702c41deae02a324bd --- /dev/null +++ b/js/src/3rdparty/etherscan/links.js @@ -0,0 +1,55 @@ +// 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 . + +// NOTE: Keep 'isTestnet' for backwards library compatibility +const getUrlPrefix = (isTestnet = false, netVersion = '0', defaultPrefix = '') => { + if (isTestnet) { + return 'ropsten.'; + } + + switch (netVersion) { + case '1': + return defaultPrefix; + + case '3': + return 'ropsten.'; + + case '4': + return 'rinkeby.'; + + case '42': + return 'kovan.'; + + default: + return 'testnet.'; + } +}; + +export const url = (isTestnet = false, netVersion = '0', defaultPrefix = '') => { + return `https://${getUrlPrefix(isTestnet, netVersion, defaultPrefix)}etherscan.io`; +}; + +export const txLink = (hash, isTestnet = false, netVersion = '0') => { + return `${url(isTestnet, netVersion)}/tx/${hash}`; +}; + +export const addressLink = (address, isTestnet = false, netVersion = '0') => { + return `${url(isTestnet, netVersion)}/address/${address}`; +}; + +export const apiLink = (query, isTestnet = false, netVersion = '0') => { + return `${url(isTestnet, netVersion, 'api.')}/api?${query}`; +}; diff --git a/js/src/3rdparty/etherscan/links.spec.js b/js/src/3rdparty/etherscan/links.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..c906940cbd9ca7fe2caedcf630a111a2795616a6 --- /dev/null +++ b/js/src/3rdparty/etherscan/links.spec.js @@ -0,0 +1,57 @@ +// 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 . + +const { url, txLink, addressLink, apiLink } = require('./links'); + +describe('etherscan/links', function () { + it('builds link with a prefix', () => { + expect(url(false, '1', 'api.')).to.be.equal('https://api.etherscan.io'); + }); + + it('builds link to main network', () => { + expect(url(false, '1')).to.be.equal('https://etherscan.io'); + }); + + it('builds link to ropsten', () => { + expect(url(false, '3')).to.be.equal('https://ropsten.etherscan.io'); + expect(url(true)).to.be.equal('https://ropsten.etherscan.io'); + }); + + it('builds link to kovan', () => { + expect(url(false, '42')).to.be.equal('https://kovan.etherscan.io'); + }); + + it('builds link to rinkeby', () => { + expect(url(false, '4')).to.be.equal('https://rinkeby.etherscan.io'); + }); + + it('builds link to the testnet selector for unknown networks', () => { + expect(url(false, '10042')).to.be.equal('https://testnet.etherscan.io'); + expect(url(false, '51224')).to.be.equal('https://testnet.etherscan.io'); + }); + + it('builds transaction link', () => { + expect(txLink('aTxHash', false, '1')).to.be.equal('https://etherscan.io/tx/aTxHash'); + }); + + it('builds address link', () => { + expect(addressLink('anAddress', false, '1')).to.be.equal('https://etherscan.io/address/anAddress'); + }); + + it('builds api link', () => { + expect(apiLink('answer=42', false, '1')).to.be.equal('https://api.etherscan.io/api?answer=42'); + }); +}); diff --git a/js/src/3rdparty/etherscan/stats.js b/js/src/3rdparty/etherscan/stats.js new file mode 100644 index 0000000000000000000000000000000000000000..282376c5ee1449aecd2621fd4f4c5f7120d8f073 --- /dev/null +++ b/js/src/3rdparty/etherscan/stats.js @@ -0,0 +1,36 @@ +// 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 { call } from './call'; + +function _call (action, test) { + return call('stats', action, null, test); +} + +function price (test = false) { + return _call('ethprice', test); +} + +function supply (test = false) { + return _call('ethsupply', test); +} + +const stats = { + price: price, + supply: supply +}; + +export { stats }; diff --git a/js/src/3rdparty/etherscan/stats.spec.js b/js/src/3rdparty/etherscan/stats.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..d837f17f768198cddad9a48c2fbde66da108295a --- /dev/null +++ b/js/src/3rdparty/etherscan/stats.spec.js @@ -0,0 +1,37 @@ +// 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 . + +const etherscan = require('./'); + +describe.skip('etherscan/stats', function () { + this.timeout(60 * 1000); + + it('retrieves the latest price', () => { + return etherscan.stats + .price() + .then((price) => { + expect(price).to.be.ok; + }); + }); + + it('retrieves the ether total', () => { + return etherscan.stats + .supply() + .then((supply) => { + expect(supply).to.be.ok; + }); + }); +}); diff --git a/js/src/3rdparty/ledger/index.js b/js/src/3rdparty/ledger/index.js new file mode 100644 index 0000000000000000000000000000000000000000..a5b876dcac816e26d88312735ad58d1871c23480 --- /dev/null +++ b/js/src/3rdparty/ledger/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 './ledger'; diff --git a/js/src/3rdparty/ledger/ledger.js b/js/src/3rdparty/ledger/ledger.js new file mode 100644 index 0000000000000000000000000000000000000000..13a67199857c82ab10fd303665c4962478f7c9ed --- /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 0000000000000000000000000000000000000000..406a4bfcd07e4f738e8d68e128ad0980bbabbe27 --- /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/3rdparty/ledger/vendor/README.md b/js/src/3rdparty/ledger/vendor/README.md new file mode 100644 index 0000000000000000000000000000000000000000..a44c3521bdf91b539c8841425d02f463cc0972f2 --- /dev/null +++ b/js/src/3rdparty/ledger/vendor/README.md @@ -0,0 +1,11 @@ +# Description + +Vendor files (c) 2016 [Ledger](https://github.com/LedgerHQ) for [Ledger Nano-S](https://www.ledgerwallet.com/) integration + +# Origin + +Files originally created via [https://github.com/kvhnuke/etherwallet/pull/248/files](https://github.com/kvhnuke/etherwallet/pull/248/files) + +# License + +Files in this directory is licensed under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) by their original author diff --git a/js/src/3rdparty/ledger/vendor/ledger-eth.js b/js/src/3rdparty/ledger/vendor/ledger-eth.js new file mode 100644 index 0000000000000000000000000000000000000000..80ff0d0a6a62f2c4866fd7d71eb4658c111d636a --- /dev/null +++ b/js/src/3rdparty/ledger/vendor/ledger-eth.js @@ -0,0 +1,166 @@ +/******************************************************************************** +* Ledger Communication toolkit +* (c) 2016 Ledger +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ + +/* eslint-disable */ + +'use strict'; + +var LedgerEth = function(comm) { + this.comm = comm; +} + +LedgerEth.splitPath = function(path) { + var result = []; + var components = path.split('/'); + components.forEach(function (element, index) { + var number = parseInt(element, 10); + if (isNaN(number)) { + return; + } + if ((element.length > 1) && (element[element.length - 1] == "'")) { + number += 0x80000000; + } + result.push(number); + }); + return result; +} + +// callback is function(response, error) +LedgerEth.prototype.getAddress = function(path, callback, boolDisplay, boolChaincode) { + var splitPath = LedgerEth.splitPath(path); + var buffer = new Buffer(5 + 1 + splitPath.length * 4); + buffer[0] = 0xe0; + buffer[1] = 0x02; + buffer[2] = (boolDisplay ? 0x01 : 0x00); + buffer[3] = (boolChaincode ? 0x01 : 0x00); + buffer[4] = 1 + splitPath.length * 4; + buffer[5] = splitPath.length; + splitPath.forEach(function (element, index) { + buffer.writeUInt32BE(element, 6 + 4 * index); + }); + var self = this; + var localCallback = function(response, error) { + if (typeof error != "undefined") { + callback(undefined, error); + } + else { + var result = {}; + response = new Buffer(response, 'hex'); + var sw = response.readUInt16BE(response.length - 2); + if (sw != 0x9000) { + callback(undefined, "Invalid status " + sw.toString(16)); + return; + } + var publicKeyLength = response[0]; + var addressLength = response[1 + publicKeyLength]; + result['publicKey'] = response.slice(1, 1 + publicKeyLength).toString('hex'); + result['address'] = "0x" + response.slice(1 + publicKeyLength + 1, 1 + publicKeyLength + 1 + addressLength).toString('ascii'); + if (boolChaincode) { + result['chainCode'] = response.slice(1 + publicKeyLength + 1 + addressLength, 1 + publicKeyLength + 1 + addressLength + 32).toString('hex'); + } + callback(result); + } + }; + this.comm.exchange(buffer.toString('hex'), localCallback); +} + +// callback is function(response, error) +LedgerEth.prototype.signTransaction = function(path, rawTxHex, callback) { + var splitPath = LedgerEth.splitPath(path); + var offset = 0; + var rawTx = new Buffer(rawTxHex, 'hex'); + var apdus = []; + while (offset != rawTx.length) { + var maxChunkSize = (offset == 0 ? (150 - 1 - splitPath.length * 4) : 150) + var chunkSize = (offset + maxChunkSize > rawTx.length ? rawTx.length - offset : maxChunkSize); + var buffer = new Buffer(offset == 0 ? 5 + 1 + splitPath.length * 4 + chunkSize : 5 + chunkSize); + buffer[0] = 0xe0; + buffer[1] = 0x04; + buffer[2] = (offset == 0 ? 0x00 : 0x80); + buffer[3] = 0x00; + buffer[4] = (offset == 0 ? 1 + splitPath.length * 4 + chunkSize : chunkSize); + if (offset == 0) { + buffer[5] = splitPath.length; + splitPath.forEach(function (element, index) { + buffer.writeUInt32BE(element, 6 + 4 * index); + }); + rawTx.copy(buffer, 6 + 4 * splitPath.length, offset, offset + chunkSize); + } + else { + rawTx.copy(buffer, 5, offset, offset + chunkSize); + } + apdus.push(buffer.toString('hex')); + offset += chunkSize; + } + var self = this; + var localCallback = function(response, error) { + if (typeof error != "undefined") { + callback(undefined, error); + } + else { + response = new Buffer(response, 'hex'); + var sw = response.readUInt16BE(response.length - 2); + if (sw != 0x9000) { + callback(undefined, "Invalid status " + sw.toString(16)); + return; + } + if (apdus.length == 0) { + var result = {}; + result['v'] = response.slice(0, 1).toString('hex'); + result['r'] = response.slice(1, 1 + 32).toString('hex'); + result['s'] = response.slice(1 + 32, 1 + 32 + 32).toString('hex'); + callback(result); + } + else { + self.comm.exchange(apdus.shift(), localCallback); + } + } + }; + self.comm.exchange(apdus.shift(), localCallback); +} + +// callback is function(response, error) +LedgerEth.prototype.getAppConfiguration = function(callback) { + var buffer = new Buffer(5); + buffer[0] = 0xe0; + buffer[1] = 0x06; + buffer[2] = 0x00; + buffer[3] = 0x00; + buffer[4] = 0x00; + var localCallback = function(response, error) { + if (typeof error != "undefined") { + callback(undefined, error); + } + else { + response = new Buffer(response, 'hex'); + var result = {}; + var sw = response.readUInt16BE(response.length - 2); + if (sw != 0x9000) { + callback(undefined, "Invalid status " + sw.toString(16)); + return; + } + result['arbitraryDataEnabled'] = (response[0] & 0x01); + result['version'] = "" + response[1] + '.' + response[2] + '.' + response[3]; + callback(result); + } + }; + this.comm.exchange(buffer.toString('hex'), localCallback); +} + +module.exports = LedgerEth; + +/* eslint-enable */ diff --git a/js/src/3rdparty/ledger/vendor/ledger3.js b/js/src/3rdparty/ledger/vendor/ledger3.js new file mode 100644 index 0000000000000000000000000000000000000000..9601279ad6b10cc39aac904e1ad9308a967b794b --- /dev/null +++ b/js/src/3rdparty/ledger/vendor/ledger3.js @@ -0,0 +1,72 @@ +/******************************************************************************** +* Ledger Communication toolkit +* (c) 2016 Ledger +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ + +/* eslint-disable */ + +'use strict'; + +var Ledger3 = function(scrambleKey, timeoutSeconds) { + this.scrambleKey = new Buffer(scrambleKey, 'ascii'); + this.timeoutSeconds = timeoutSeconds; +} + +Ledger3.wrapApdu = function(apdu, key) { + var result = new Buffer(apdu.length); + for (var i=0; i. + +const nock = require('nock'); + +const APIKEY = '0x123454321'; + +function mockget (shapeshift, requests) { + let scope = nock(shapeshift.getRpc().ENDPOINT); + + requests.forEach((request) => { + scope = scope + .get(`/${request.path}`) + .reply(request.code || 200, () => { + return request.reply; + }); + }); + + return scope; +} + +function mockpost (shapeshift, requests) { + let scope = nock(shapeshift.getRpc().ENDPOINT); + + requests.forEach((request) => { + scope = scope + .post(`/${request.path}`) + .reply(request.code || 200, (uri, body) => { + scope.body = scope.body || {}; + scope.body[request.path] = body; + + return request.reply; + }); + }); + + return scope; +} + +module.exports = { + APIKEY, + mockget, + mockpost +}; diff --git a/js/src/3rdparty/shapeshift/index.js b/js/src/3rdparty/shapeshift/index.js new file mode 100644 index 0000000000000000000000000000000000000000..aac05d3ebbb040b9df59f64c9480f1d3698e0845 --- /dev/null +++ b/js/src/3rdparty/shapeshift/index.js @@ -0,0 +1,22 @@ +// 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 initRpc from './rpc'; +import initShapeshift from './shapeshift'; + +export default function (apikey) { + return initShapeshift(initRpc(apikey)); +} diff --git a/js/src/3rdparty/shapeshift/rpc.js b/js/src/3rdparty/shapeshift/rpc.js new file mode 100644 index 0000000000000000000000000000000000000000..711a41c51be3ea981400c08d889900ab6d52d429 --- /dev/null +++ b/js/src/3rdparty/shapeshift/rpc.js @@ -0,0 +1,67 @@ +// 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 . + +const ENDPOINT = 'https://cors.shapeshift.io'; + +function call (method, options) { + return fetch(`${ENDPOINT}/${method}`, options) + .then((response) => { + if (!response.ok) { + throw new Error(response.statusText); + } + + return response.json(); + }) + .then((result) => { + if (result.error) { + throw new Error(result.error); + } + + return result; + }); +} + +export default function (apiKey) { + function get (method) { + return call(method, { + method: 'GET', + headers: { + 'Accept': 'application/json' + } + }); + } + + function post (method, data) { + const params = Object.assign({}, { apiKey }, data); + const body = JSON.stringify(params); + + return call(method, { + method: 'POST', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'Content-Length': body.length + }, + body + }); + } + + return { + ENDPOINT, + get, + post + }; +} diff --git a/js/src/3rdparty/shapeshift/rpc.spec.js b/js/src/3rdparty/shapeshift/rpc.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..d445fbb050833e32dc683c72c7ce95f15fefb6bd --- /dev/null +++ b/js/src/3rdparty/shapeshift/rpc.spec.js @@ -0,0 +1,91 @@ +// 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 . + +const helpers = require('./helpers.spec.js'); + +const ShapeShift = require('./'); +const initShapeshift = (ShapeShift.default || ShapeShift); + +const mockget = helpers.mockget; +const mockpost = helpers.mockpost; + +describe('shapeshift/rpc', () => { + let rpc; + let shapeshift; + + beforeEach(() => { + shapeshift = initShapeshift(helpers.APIKEY); + rpc = shapeshift.getRpc(); + }); + + describe('GET', () => { + const REPLY = { test: 'this is some result' }; + + let scope; + let result; + + beforeEach(() => { + scope = mockget(shapeshift, [{ path: 'test', reply: REPLY }]); + + return rpc + .get('test') + .then((_result) => { + result = _result; + }); + }); + + it('does GET', () => { + expect(scope.isDone()).to.be.true; + }); + + it('retrieves the info', () => { + expect(result).to.deep.equal(REPLY); + }); + }); + + describe('POST', () => { + const REPLY = { test: 'this is some result' }; + + let scope; + let result; + + beforeEach(() => { + scope = mockpost(shapeshift, [{ path: 'test', reply: REPLY }]); + + return rpc + .post('test', { input: 'stuff' }) + .then((_result) => { + result = _result; + }); + }); + + it('does POST', () => { + expect(scope.isDone()).to.be.true; + }); + + it('retrieves the info', () => { + expect(result).to.deep.equal(REPLY); + }); + + it('passes the input object', () => { + expect(scope.body.test.input).to.equal('stuff'); + }); + + it('passes the apikey specified', () => { + expect(scope.body.test.apiKey).to.equal(helpers.APIKEY); + }); + }); +}); diff --git a/js/src/3rdparty/shapeshift/shapeshift.js b/js/src/3rdparty/shapeshift/shapeshift.js new file mode 100644 index 0000000000000000000000000000000000000000..5ecf141daa8d7c76b171966452fc1985462a2e7e --- /dev/null +++ b/js/src/3rdparty/shapeshift/shapeshift.js @@ -0,0 +1,135 @@ +// 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 function (rpc) { + let _subscriptions = []; + let _pollStatusIntervalId = null; + let _subscriptionPromises = null; + + function getCoins () { + return rpc.get('getcoins'); + } + + function getMarketInfo (pair) { + return rpc.get(`marketinfo/${pair}`); + } + + function getRpc () { + return rpc; + } + + function getStatus (depositAddress) { + return rpc.get(`txStat/${depositAddress}`); + } + + function shift (toAddress, returnAddress, pair) { + return rpc.post('shift', { + pair, + returnAddress, + withdrawal: toAddress + }); + } + + function subscribe (depositAddress, callback) { + if (!depositAddress || !callback) { + return; + } + + const index = _subscriptions.length; + + _subscriptions.push({ + callback, + depositAddress, + index + }); + + if (_pollStatusIntervalId === null) { + _pollStatusIntervalId = setInterval(_pollStatus, 2000); + } + } + + function unsubscribe (depositAddress) { + _subscriptions = _subscriptions.filter((sub) => sub.depositAddress !== depositAddress); + + if (_subscriptions.length === 0) { + clearInterval(_pollStatusIntervalId); + _pollStatusIntervalId = null; + } + + return true; + } + + function _getSubscriptionStatus (subscription) { + if (!subscription) { + return Promise.resolve(); + } + + return getStatus(subscription.depositAddress) + .then((result) => { + switch (result.status) { + case 'no_deposits': + case 'received': + subscription.callback(null, result); + return true; + + case 'complete': + subscription.callback(null, result); + unsubscribe(subscription.depositAddress); + return true; + + case 'failed': + subscription.callback({ + message: status.error, + fatal: true + }); + unsubscribe(subscription.depositAddress); + return true; + } + }) + .catch(() => { + return true; + }); + } + + function _pollStatus () { + _subscriptionPromises = Promise.all(_subscriptions.map(_getSubscriptionStatus)); + } + + function _getSubscriptions () { + return _subscriptions; + } + + function _getSubscriptionPromises () { + return _subscriptionPromises; + } + + function _isPolling () { + return _pollStatusIntervalId !== null; + } + + return { + _getSubscriptions, + _getSubscriptionPromises, + _isPolling, + getCoins, + getMarketInfo, + getRpc, + getStatus, + shift, + subscribe, + unsubscribe + }; +} diff --git a/js/src/3rdparty/shapeshift/shapeshift.spec.js b/js/src/3rdparty/shapeshift/shapeshift.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..0fa6639d8e4f824cae2db5ee12c7fe33bb61e98e --- /dev/null +++ b/js/src/3rdparty/shapeshift/shapeshift.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 . + +const sinon = require('sinon'); + +const ShapeShift = require('./'); +const initShapeshift = (ShapeShift.default || ShapeShift); + +const helpers = require('./helpers.spec.js'); + +const mockget = helpers.mockget; +const mockpost = helpers.mockpost; + +describe('shapeshift/calls', () => { + let clock; + let shapeshift; + + beforeEach(() => { + clock = sinon.useFakeTimers(); + shapeshift = initShapeshift(helpers.APIKEY); + }); + + afterEach(() => { + clock.restore(); + }); + + describe('getCoins', () => { + const REPLY = { + BTC: { + name: 'Bitcoin', + symbol: 'BTC', + image: 'https://shapeshift.io/images/coins/bitcoin.png', + status: 'available' + }, + ETH: { + name: 'Ether', + symbol: 'ETH', + image: 'https://shapeshift.io/images/coins/ether.png', + status: 'available' + } + }; + + let scope; + + beforeEach(() => { + scope = mockget(shapeshift, [{ path: 'getcoins', reply: REPLY }]); + + return shapeshift.getCoins(); + }); + + it('makes the call', () => { + expect(scope.isDone()).to.be.ok; + }); + }); + + describe('getMarketInfo', () => { + const REPLY = { + pair: 'btc_ltc', + rate: 128.17959917, + minerFee: 0.003, + limit: 0, + minimum: 0.00004632 + }; + + let scope; + + beforeEach(() => { + scope = mockget(shapeshift, [{ path: 'marketinfo/btc_ltc', reply: REPLY }]); + + return shapeshift.getMarketInfo('btc_ltc'); + }); + + it('makes the call', () => { + expect(scope.isDone()).to.be.ok; + }); + }); + + describe('getStatus', () => { + const REPLY = { + status: '0x123', + address: '0x123' + }; + + let scope; + + beforeEach(() => { + scope = mockget(shapeshift, [{ path: 'txStat/0x123', reply: REPLY }]); + + return shapeshift.getStatus('0x123'); + }); + + it('makes the call', () => { + expect(scope.isDone()).to.be.ok; + }); + }); + + describe('shift', () => { + const REPLY = { + deposit: '1BTC', + depositType: 'btc', + withdrawal: '0x456', + withdrawalType: 'eth' + }; + + let scope; + + beforeEach(() => { + scope = mockpost(shapeshift, [{ path: 'shift', reply: REPLY }]); + + return shapeshift.shift('0x456', '1BTC', 'btc_eth'); + }); + + it('makes the call', () => { + expect(scope.isDone()).to.be.ok; + }); + + describe('body', () => { + it('has withdrawal set', () => { + expect(scope.body.shift.withdrawal).to.equal('0x456'); + }); + + it('has returnAddress set', () => { + expect(scope.body.shift.returnAddress).to.equal('1BTC'); + }); + + it('has pair set', () => { + expect(scope.body.shift.pair).to.equal('btc_eth'); + }); + }); + }); + + describe('subscriptions', () => { + const ADDRESS = '0123456789abcdef'; + const REPLY = { + status: 'complete', + address: ADDRESS + }; + + let callback; + + beforeEach(() => { + mockget(shapeshift, [{ path: `txStat/${ADDRESS}`, reply: REPLY }]); + callback = sinon.stub(); + shapeshift.subscribe(ADDRESS, callback); + }); + + describe('subscribe', () => { + it('adds the depositAddress to the list', () => { + const subscriptions = shapeshift._getSubscriptions(); + + expect(subscriptions.length).to.equal(1); + expect(subscriptions[0].depositAddress).to.equal(ADDRESS); + }); + + it('starts the polling timer', () => { + expect(shapeshift._isPolling()).to.be.true; + }); + + it('calls the callback once the timer has elapsed', () => { + clock.tick(2222); + + return shapeshift._getSubscriptionPromises().then(() => { + expect(callback).to.have.been.calledWith(null, REPLY); + }); + }); + + it('auto-unsubscribes on completed', () => { + clock.tick(2222); + + return shapeshift._getSubscriptionPromises().then(() => { + expect(shapeshift._getSubscriptions().length).to.equal(0); + }); + }); + }); + + describe('unsubscribe', () => { + it('unbsubscribes when requested', () => { + expect(shapeshift._getSubscriptions().length).to.equal(1); + shapeshift.unsubscribe(ADDRESS); + expect(shapeshift._getSubscriptions().length).to.equal(0); + }); + + it('clears the polling on no subscriptions', () => { + shapeshift.unsubscribe(ADDRESS); + expect(shapeshift._isPolling()).to.be.false; + }); + + it('handles unsubscribe of auto-unsubscribe', () => { + clock.tick(2222); + + return shapeshift._getSubscriptionPromises().then(() => { + expect(shapeshift.unsubscribe(ADDRESS)).to.be.true; + }); + }); + + it('handles unsubscribe when multiples listed', () => { + const ADDRESS2 = 'abcdef0123456789'; + + shapeshift.subscribe(ADDRESS2, sinon.stub()); + expect(shapeshift._getSubscriptions().length).to.equal(2); + expect(shapeshift._getSubscriptions()[0].depositAddress).to.equal(ADDRESS); + shapeshift.unsubscribe(ADDRESS); + expect(shapeshift._getSubscriptions()[0].depositAddress).to.equal(ADDRESS2); + }); + }); + }); +}); diff --git a/js/src/3rdparty/sms-verification/index.js b/js/src/3rdparty/sms-verification/index.js new file mode 100644 index 0000000000000000000000000000000000000000..79cbf13d2fe7499401ffdd0baa20d253172cb6ed --- /dev/null +++ b/js/src/3rdparty/sms-verification/index.js @@ -0,0 +1,68 @@ +// 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 { stringify } from 'querystring'; + +export const isServerRunning = (isTestnet = false) => { + const port = isTestnet ? 8443 : 443; + + return fetch(`https://sms-verification.parity.io:${port}/health`, { + mode: 'cors', + cache: 'no-store' + }) + .then((res) => { + return res.ok; + }) + .catch(() => { + return false; + }); +}; + +export const hasReceivedCode = (number, address, isTestnet = false) => { + const port = isTestnet ? 8443 : 443; + const query = stringify({ number, address }); + + return fetch(`https://sms-verification.parity.io:${port}/?${query}`, { + mode: 'cors', + cache: 'no-store' + }) + .then((res) => { + return res.ok; + }) + .catch(() => { + return false; // todo: check for 404 + }); +}; + +export const postToServer = (query, isTestnet = false) => { + const port = isTestnet ? 8443 : 443; + + query = stringify(query); + + return fetch(`https://sms-verification.parity.io:${port}/?${query}`, { + method: 'POST', + mode: 'cors', + cache: 'no-store' + }) + .then((res) => { + return res.json().then((data) => { + if (res.ok) { + return data.message; + } + throw new Error(data.message || 'unknown error'); + }); + }); +}; diff --git a/js/src/3rdparty/sms-verification/terms-of-service.js b/js/src/3rdparty/sms-verification/terms-of-service.js new file mode 100644 index 0000000000000000000000000000000000000000..9451a382b7b1c3cbd3c860ca00e2df3ebd981547 --- /dev/null +++ b/js/src/3rdparty/sms-verification/terms-of-service.js @@ -0,0 +1,27 @@ +// 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 from 'react'; + +export default ( +
    +
  • This privacy notice relates to your use of the Parity SMS verification service. We take your privacy seriously and deal in an honest, direct and transparent way when it comes to your data.
  • +
  • We collect your phone number when you use this service. This is temporarily kept in memory, and then encrypted and stored in our EU servers. We only retain the cryptographic hash of the number to prevent duplicated accounts. You consent to this use.
  • +
  • You pay a fee for the cost of this service using the account you want to verify.
  • +
  • Your phone number is transmitted to a third party US SMS verification service Twilio for the sole purpose of the SMS verification. You consent to this use. Twilio’s privacy policy is here: https://www.twilio.com/legal/privacy/developer.
  • +
  • Parity Technology Limited is registered in England and Wales under company number 09760015 and complies with the Data Protection Act 1998 (UK). You may contact us via email at admin@parity.io. Our general privacy policy can be found here: https://parity.io/legal.html.
  • +
+); diff --git a/js/src/abi/README.md b/js/src/abi/README.md new file mode 100644 index 0000000000000000000000000000000000000000..20bc094ded6d8e4d90e77847174add1914262e65 --- /dev/null +++ b/js/src/abi/README.md @@ -0,0 +1,32 @@ +# ethabi-js + +A very early, very POC-type port of [https://github.com/paritytech/ethabi](https://github.com/paritytech/ethabi) to JavaScript + +[![Build Status](https://travis-ci.org/jacogr/ethabi-js.svg?branch=master)](https://travis-ci.org/jacogr/ethabi-js) +[![Coverage Status](https://coveralls.io/repos/github/jacogr/ethabi-js/badge.svg?branch=master)](https://coveralls.io/github/jacogr/ethabi-js?branch=master) +[![Dependency Status](https://david-dm.org/jacogr/ethabi-js.svg)](https://david-dm.org/jacogr/ethabi-js) +[![devDependency Status](https://david-dm.org/jacogr/ethabi-js/dev-status.svg)](https://david-dm.org/jacogr/ethabi-js#info=devDependencies) + +## contributing + +Clone the repo and install dependencies via `npm install`. Tests can be executed via + +- `npm run testOnce` (100% covered unit tests) + +## installation + +Install the package with `npm install --save ethabi-js` from the [npm registry ethabi-js](https://www.npmjs.com/package/ethabi-js) + + +## implementation +### approach + +- this version tries to stay as close to the original Rust version in intent, function names & purpose +- it is a basic port of the Rust version, relying on effectively the same test-suite (expanded where deemed appropriate) +- it is meant as a library to be used in other projects, i.e. [ethapi-js](https://www.npmjs.com/package/ethapi-js) + +### differences to original Rust version + +- internally the library operates on string binary representations as opposed to Vector bytes, lengths are therefore 64 bytes as opposed to 32 bytes +- function names are adapted from the Rust standard snake_case to the JavaScript standard camelCase +- due to the initial library focus, the cli component (as implemented by the original) is not supported nor mplemented diff --git a/js/src/abi/abi.js b/js/src/abi/abi.js new file mode 100644 index 0000000000000000000000000000000000000000..892f531fa0feabd36068e48d31bc4ce8a8733d12 --- /dev/null +++ b/js/src/abi/abi.js @@ -0,0 +1,20 @@ +// 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 Interface from './spec/interface'; + +export default class Abi extends Interface { +} diff --git a/js/src/abi/decoder/bytesTaken.js b/js/src/abi/decoder/bytesTaken.js new file mode 100644 index 0000000000000000000000000000000000000000..dbf766f4bf136674168b38719bba0588293671c4 --- /dev/null +++ b/js/src/abi/decoder/bytesTaken.js @@ -0,0 +1,30 @@ +// 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 class BytesTaken { + constructor (bytes, newOffset) { + this._bytes = bytes; + this._newOffset = newOffset; + } + + get bytes () { + return this._bytes; + } + + get newOffset () { + return this._newOffset; + } +} diff --git a/js/src/abi/decoder/bytesTaken.spec.js b/js/src/abi/decoder/bytesTaken.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..9329657e48a62e23a2a20dfc6e14665a6773e394 --- /dev/null +++ b/js/src/abi/decoder/bytesTaken.spec.js @@ -0,0 +1,29 @@ +// 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 BytesTaken from './bytesTaken'; + +describe('abi/decoder/BytesTaken', () => { + describe('constructor', () => { + it('sets the bytes of the object', () => { + expect((new BytesTaken(1, 2)).bytes).to.equal(1); + }); + + it('sets the newOffset of the object', () => { + expect((new BytesTaken(3, 4)).newOffset).to.equal(4); + }); + }); +}); diff --git a/js/src/abi/decoder/decodeResult.js b/js/src/abi/decoder/decodeResult.js new file mode 100644 index 0000000000000000000000000000000000000000..5e41da4b60aa8595f8a1e451e4f3f02f54e5361f --- /dev/null +++ b/js/src/abi/decoder/decodeResult.js @@ -0,0 +1,30 @@ +// 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 class DecodeResult { + constructor (token, newOffset) { + this._token = token; + this._newOffset = newOffset; + } + + get token () { + return this._token; + } + + get newOffset () { + return this._newOffset; + } +} diff --git a/js/src/abi/decoder/decodeResult.spec.js b/js/src/abi/decoder/decodeResult.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..0c4c81c5c4ffb46ec8fa6ce79f7b6e030ab9c466 --- /dev/null +++ b/js/src/abi/decoder/decodeResult.spec.js @@ -0,0 +1,29 @@ +// 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 DecodeResult from './decodeResult'; + +describe('abi/decoder/DecodeResult', () => { + describe('constructor', () => { + it('sets the token of the object', () => { + expect((new DecodeResult('token', 2)).token).to.equal('token'); + }); + + it('sets the newOffset of the object', () => { + expect((new DecodeResult('baz', 4)).newOffset).to.equal(4); + }); + }); +}); diff --git a/js/src/abi/decoder/decoder.js b/js/src/abi/decoder/decoder.js new file mode 100644 index 0000000000000000000000000000000000000000..6922c91e800924e0664ccc2fcb88484e3f9b1962 --- /dev/null +++ b/js/src/abi/decoder/decoder.js @@ -0,0 +1,156 @@ +// 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 utf8 from 'utf8'; + +import Token from '../token/token'; +import BytesTaken from './bytesTaken'; +import DecodeResult from './decodeResult'; +import ParamType from '../spec/paramType/paramType'; +import { sliceData } from '../util/slice'; +import { asAddress, asBool, asI32, asU32 } from '../util/sliceAs'; +import { isArray, isInstanceOf } from '../util/types'; + +const NULL = '0000000000000000000000000000000000000000000000000000000000000000'; + +export default class Decoder { + static decode (params, data) { + if (!isArray(params)) { + throw new Error('Parameters should be array of ParamType'); + } + + const slices = sliceData(data); + let offset = 0; + + return params.map((param) => { + const result = Decoder.decodeParam(param, slices, offset); + + offset = result.newOffset; + return result.token; + }); + } + + static peek (slices, position) { + if (!slices || !slices[position]) { + return NULL; + } + + return slices[position]; + } + + static takeBytes (slices, position, length) { + const slicesLength = Math.floor((length + 31) / 32); + let bytesStr = ''; + + for (let idx = 0; idx < slicesLength; idx++) { + bytesStr = `${bytesStr}${Decoder.peek(slices, position + idx)}`; + } + + const bytes = (bytesStr.substr(0, length * 2).match(/.{1,2}/g) || []).map((code) => parseInt(code, 16)); + + return new BytesTaken(bytes, position + slicesLength); + } + + static decodeParam (param, slices, offset) { + if (!isInstanceOf(param, ParamType)) { + throw new Error('param should be instanceof ParamType'); + } + + const tokens = []; + let taken; + let lengthOffset; + let length; + let newOffset; + + switch (param.type) { + case 'address': + return new DecodeResult(new Token(param.type, asAddress(Decoder.peek(slices, offset))), offset + 1); + + case 'bool': + return new DecodeResult(new Token(param.type, asBool(Decoder.peek(slices, offset))), offset + 1); + + case 'int': + return new DecodeResult(new Token(param.type, asI32(Decoder.peek(slices, offset))), offset + 1); + + case 'uint': + return new DecodeResult(new Token(param.type, asU32(Decoder.peek(slices, offset))), offset + 1); + + case 'fixedBytes': + taken = Decoder.takeBytes(slices, offset, param.length); + + return new DecodeResult(new Token(param.type, taken.bytes), taken.newOffset); + + case 'bytes': + lengthOffset = asU32(Decoder.peek(slices, offset)).div(32).toNumber(); + length = asU32(Decoder.peek(slices, lengthOffset)).toNumber(); + taken = Decoder.takeBytes(slices, lengthOffset + 1, length); + + return new DecodeResult(new Token(param.type, taken.bytes), offset + 1); + + case 'string': + if (param.indexed) { + taken = Decoder.takeBytes(slices, offset, 32); + + return new DecodeResult(new Token('fixedBytes', taken.bytes), offset + 1); + } + + lengthOffset = asU32(Decoder.peek(slices, offset)).div(32).toNumber(); + length = asU32(Decoder.peek(slices, lengthOffset)).toNumber(); + taken = Decoder.takeBytes(slices, lengthOffset + 1, length); + + const str = taken.bytes.map((code) => String.fromCharCode(code)).join(''); + + let decoded; + + try { + decoded = utf8.decode(str); + } catch (error) { + decoded = str; + } + + return new DecodeResult(new Token(param.type, decoded), offset + 1); + + case 'array': + lengthOffset = asU32(Decoder.peek(slices, offset)).div(32).toNumber(); + length = asU32(Decoder.peek(slices, lengthOffset)).toNumber(); + newOffset = lengthOffset + 1; + + for (let idx = 0; idx < length; idx++) { + const result = Decoder.decodeParam(param.subtype, slices, newOffset); + + newOffset = result.newOffset; + tokens.push(result.token); + } + + return new DecodeResult(new Token(param.type, tokens), offset + 1); + + case 'fixedArray': + newOffset = offset; + + for (let idx = 0; idx < param.length; idx++) { + const result = Decoder.decodeParam(param.subtype, slices, newOffset); + + newOffset = result.newOffset; + tokens.push(result.token); + } + + return new DecodeResult(new Token(param.type, tokens), newOffset); + + default: + throw new Error(`Invalid param type ${param.type} in decodeParam`); + } + } +} diff --git a/js/src/abi/decoder/decoder.spec.js b/js/src/abi/decoder/decoder.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..5065be3a7ee603ca487e53aa5d72542e44b944fa --- /dev/null +++ b/js/src/abi/decoder/decoder.spec.js @@ -0,0 +1,319 @@ +// 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 BigNumber from 'bignumber.js'; + +import Decoder from './decoder'; +import ParamType from '../spec/paramType'; +import Token from '../token'; +import { padU32 } from '../util/pad'; + +describe('abi/decoder/Decoder', () => { + const stringToBytes = function (str) { + return str.match(/.{1,2}/g).map((code) => parseInt(code, 16)); + }; + + const address1 = '0000000000000000000000001111111111111111111111111111111111111111'; + const address2 = '0000000000000000000000002222222222222222222222222222222222222222'; + const address3 = '0000000000000000000000003333333333333333333333333333333333333333'; + const address4 = '0000000000000000000000004444444444444444444444444444444444444444'; + const bool1 = '0000000000000000000000000000000000000000000000000000000000000001'; + const bytes1 = '1234000000000000000000000000000000000000000000000000000000000000'; + const bytes2 = '1000000000000000000000000000000000000000000000000000000000000000'; + const bytes3 = '10000000000000000000000000000000000000000000000000000000000002'; + const bytes4 = '0010000000000000000000000000000000000000000000000000000000000002'; + const int1 = '0111111111111111111111111111111111111111111111111111111111111111'; + const intn = 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff85'; + const string1 = '6761766f66796f726b0000000000000000000000000000000000000000000000'; + const string2 = '4665726ee16e64657a0000000000000000000000000000000000000000000000'; + const tokenAddress1 = new Token('address', `0x${address1.slice(-40)}`); + const tokenAddress2 = new Token('address', `0x${address2.slice(-40)}`); + const tokenAddress3 = new Token('address', `0x${address3.slice(-40)}`); + const tokenAddress4 = new Token('address', `0x${address4.slice(-40)}`); + const tokenBool1 = new Token('bool', true); + const tokenFixedBytes1 = new Token('fixedBytes', [0x12, 0x34]); + const tokenBytes1 = new Token('bytes', [0x12, 0x34]); + const tokenBytes2 = new Token('bytes', stringToBytes(bytes2).concat(stringToBytes(bytes2))); + const tokenBytes3 = new Token('bytes', stringToBytes(bytes3)); + const tokenBytes4 = new Token('bytes', stringToBytes(bytes4)); + const tokenInt1 = new Token('int', new BigNumber(int1, 16)); + const tokenIntn = new Token('int', new BigNumber(-123)); + const tokenUint1 = new Token('uint', new BigNumber(int1, 16)); + const tokenUintn = new Token('uint', new BigNumber(intn, 16)); + const tokenString1 = new Token('string', 'gavofyork'); + const tokenString2 = new Token('string', 'Fernández'); + const slices = [ address1, address2, address3, address4 ]; + + describe('peek', () => { + it('returns the slice at the correct position', () => { + expect(Decoder.peek(slices, 1)).to.equal(slices[1]); + }); + + it('returns empty on invalid slices', () => { + expect(Decoder.peek(null, 4)).to.equal('0000000000000000000000000000000000000000000000000000000000000000'); + }); + }); + + describe('takeBytes', () => { + it('returns a single slice', () => { + expect(Decoder.takeBytes(slices, 0, 32).bytes).to.deep.equal(stringToBytes(slices[0])); + }); + + it('returns a single partial slice', () => { + expect(Decoder.takeBytes(slices, 0, 20).bytes).to.deep.equal(stringToBytes(slices[0].substr(0, 40))); + }); + + it('returns multiple slices', () => { + expect(Decoder.takeBytes(slices, 0, 64).bytes).to.deep.equal(stringToBytes(`${slices[0]}${slices[1]}`)); + }); + + it('returns a single offset slice', () => { + expect(Decoder.takeBytes(slices, 1, 32).bytes).to.deep.equal(stringToBytes(slices[1])); + }); + + it('returns multiple offset slices', () => { + expect(Decoder.takeBytes(slices, 1, 64).bytes).to.deep.equal(stringToBytes(`${slices[1]}${slices[2]}`)); + }); + + it('returns the requires length from slices', () => { + expect( + Decoder.takeBytes(slices, 1, 75).bytes + ).to.deep.equal(stringToBytes(`${slices[1]}${slices[2]}${slices[3]}`.substr(0, 150))); + }); + }); + + describe('decodeParam', () => { + it('throws an error on non ParamType param', () => { + expect(() => Decoder.decodeParam({})).to.throw(/ParamType/); + }); + + it('throws an error on invalid param type', () => { + const pt = new ParamType('address'); + + pt._type = 'noMatch'; + + expect(() => Decoder.decodeParam(pt)).to.throw(/noMatch/); + }); + + it('decodes an address', () => { + expect( + Decoder.decodeParam(new ParamType('address'), [address1], 0).token + ).to.deep.equal(tokenAddress1); + }); + + it('decodes a bool', () => { + expect( + Decoder.decodeParam(new ParamType('bool'), [bool1], 0).token + ).to.deep.equal(tokenBool1); + }); + + it('decodes an int', () => { + expect( + Decoder.decodeParam(new ParamType('int'), [int1], 0).token + ).to.deep.equal(tokenInt1); + }); + + it('decodes a negative int', () => { + expect( + Decoder.decodeParam(new ParamType('int'), [intn], 0).token + ).to.deep.equal(tokenIntn); + }); + + it('decodes an uint', () => { + expect( + Decoder.decodeParam(new ParamType('uint'), [int1], 0).token + ).to.deep.equal(tokenUint1); + }); + + it('decodes an uint (negative as int)', () => { + expect( + Decoder.decodeParam(new ParamType('uint'), [intn], 0).token + ).to.deep.equal(tokenUintn); + }); + + it('decodes fixedBytes', () => { + expect( + Decoder.decodeParam(new ParamType('fixedBytes', null, 2), [bytes1], 0).token + ).to.deep.equal(tokenFixedBytes1); + }); + + it('decodes bytes', () => { + expect( + Decoder.decodeParam(new ParamType('bytes'), [padU32(0x20), padU32(2), bytes1], 0).token + ).to.deep.equal(tokenBytes1); + }); + + it('decodes string', () => { + expect( + Decoder.decodeParam(new ParamType('string'), [padU32(0x20), padU32(9), string1], 0).token + ).to.deep.equal(tokenString1); + }); + + it('decodes utf8-invalid string', () => { + expect( + Decoder.decodeParam(new ParamType('string'), [padU32(0x20), padU32(9), string2], 0).token + ).to.deep.equal(tokenString2); + }); + + it('decodes string (indexed)', () => { + expect( + Decoder.decodeParam(new ParamType('string', null, 0, true), [bytes1], 0) + ).to.deep.equal(Decoder.decodeParam(new ParamType('fixedBytes', null, 32, true), [bytes1], 0)); + }); + }); + + describe('decode', () => { + it('throws an error on invalid params', () => { + expect(() => Decoder.decode(null, '123')).to.throw(/array/); + }); + + describe('address', () => { + it('decodes an address', () => { + expect( + Decoder.decode( + [new ParamType('address')], + `${address1}` + ) + ).to.deep.equal([tokenAddress1]); + }); + + it('decodes 2 addresses', () => { + expect( + Decoder.decode( + [new ParamType('address'), new ParamType('address')], + `${address1}${address2}` + ) + ).to.deep.equal([tokenAddress1, tokenAddress2]); + }); + + it('decodes a fixedArray of addresses', () => { + expect( + Decoder.decode( + [new ParamType('fixedArray', new ParamType('address'), 2)], + `${address1}${address2}` + ) + ).to.deep.equal([new Token('fixedArray', [tokenAddress1, tokenAddress2])]); + }); + + it('decodes a dynamic array of addresses', () => { + expect( + Decoder.decode( + [new ParamType('array', new ParamType('address'))], + `${padU32(0x20)}${padU32(2)}${address1}${address2}` + ) + ).to.deep.equal([new Token('array', [tokenAddress1, tokenAddress2])]); + }); + + it('decodes a dynamic array of fixed arrays', () => { + expect( + Decoder.decode( + [new ParamType('array', new ParamType('fixedArray', new ParamType('address'), 2))], + `${padU32(0x20)}${padU32(2)}${address1}${address2}${address3}${address4}` + ) + ).to.deep.equal([ + new Token('array', [ + new Token('fixedArray', [tokenAddress1, tokenAddress2]), + new Token('fixedArray', [tokenAddress3, tokenAddress4]) + ]) + ]); + }); + }); + + describe('int', () => { + it('decodes an int', () => { + expect( + Decoder.decode( + [new ParamType('int')], + `${int1}` + ) + ).to.deep.equal([tokenInt1]); + }); + }); + + describe('uint', () => { + it('decodes an uint', () => { + expect( + Decoder.decode( + [new ParamType('uint')], + `${int1}` + ) + ).to.deep.equal([tokenUint1]); + }); + }); + + describe('fixedBytes', () => { + it('decodes fixedBytes', () => { + expect( + Decoder.decode( + [new ParamType('fixedBytes', null, 2)], + `${bytes1}` + ) + ).to.deep.equal([tokenFixedBytes1]); + }); + }); + + describe('bytes', () => { + it('decodes bytes', () => { + expect( + Decoder.decode( + [new ParamType('bytes')], + `${padU32(0x20)}${padU32(2)}${bytes1}` + ) + ).to.deep.equal([tokenBytes1]); + }); + + it('decodes bytes sequence', () => { + expect( + Decoder.decode( + [new ParamType('bytes')], + `${padU32(0x20)}${padU32(0x40)}${bytes2}${bytes2}` + ) + ).to.deep.equal([tokenBytes2]); + }); + + it('decodes bytes seuence (2)', () => { + expect( + Decoder.decode( + [new ParamType('bytes'), new ParamType('bytes')], + `${padU32(0x40)}${padU32(0x80)}${padU32(0x1f)}${bytes3}00${padU32(0x20)}${bytes4}` + ) + ).to.deep.equal([tokenBytes3, tokenBytes4]); + }); + }); + + describe('bool', () => { + it('decodes a single bool', () => { + expect( + Decoder.decode( + [new ParamType('bool')], + bool1 + ) + ).to.deep.equal([tokenBool1]); + }); + }); + + describe('string', () => { + it('decodes a string', () => { + expect( + Decoder.decode( + [new ParamType('string')], + `${padU32(0x20)}${padU32(9)}${string1}` + ) + ).to.deep.equal([tokenString1]); + }); + }); + }); +}); diff --git a/js/src/abi/decoder/index.js b/js/src/abi/decoder/index.js new file mode 100644 index 0000000000000000000000000000000000000000..2ead31504180855d0ee4e5e1192bf9135c2cbc7c --- /dev/null +++ b/js/src/abi/decoder/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 './decoder'; diff --git a/js/src/abi/encoder/encoder.js b/js/src/abi/encoder/encoder.js new file mode 100644 index 0000000000000000000000000000000000000000..fa1aa04538a4164c2199591653aa1f668ede0cf6 --- /dev/null +++ b/js/src/abi/encoder/encoder.js @@ -0,0 +1,75 @@ +// 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 { padAddress, padBool, padBytes, padFixedBytes, padU32, padString } from '../util/pad'; +import Mediate from './mediate'; +import Token from '../token/token'; +import { isArray, isInstanceOf } from '../util/types'; + +export default class Encoder { + static encode (tokens) { + if (!isArray(tokens)) { + throw new Error('tokens should be array of Token'); + } + + const mediates = tokens.map((token, index) => Encoder.encodeToken(token, index)); + const inits = mediates + .map((mediate, idx) => mediate.init(Mediate.offsetFor(mediates, idx))) + .join(''); + const closings = mediates + .map((mediate, idx) => mediate.closing(Mediate.offsetFor(mediates, idx))) + .join(''); + + return `${inits}${closings}`; + } + + static encodeToken (token, index = 0) { + if (!isInstanceOf(token, Token)) { + throw new Error('token should be instanceof Token'); + } + + try { + switch (token.type) { + case 'address': + return new Mediate('raw', padAddress(token.value)); + + case 'int': + case 'uint': + return new Mediate('raw', padU32(token.value)); + + case 'bool': + return new Mediate('raw', padBool(token.value)); + + case 'fixedBytes': + return new Mediate('raw', padFixedBytes(token.value)); + + case 'bytes': + return new Mediate('prefixed', padBytes(token.value)); + + case 'string': + return new Mediate('prefixed', padString(token.value)); + + case 'fixedArray': + case 'array': + return new Mediate(token.type, token.value.map((token) => Encoder.encodeToken(token))); + } + } catch (e) { + throw new Error(`Cannot encode token #${index} [${token.type}: ${token.value}]. ${e.message}`); + } + + throw new Error(`Invalid token type ${token.type} in encodeToken`); + } +} diff --git a/js/src/abi/encoder/encoder.spec.js b/js/src/abi/encoder/encoder.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..9a4a24361ffc226cde3c7cbaf2b79150bea0bba3 --- /dev/null +++ b/js/src/abi/encoder/encoder.spec.js @@ -0,0 +1,291 @@ +// 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 Encoder from './encoder'; +import Token from '../token'; +import { padAddress, padFixedBytes, padU32 } from '../util/pad'; + +describe('abi/encoder/Encoder', () => { + describe('encodeToken', () => { + it('requires token as Token', () => { + expect(() => Encoder.encodeToken()).to.throw(/Token/); + }); + + it('encodes address tokens in Mediate(raw)', () => { + const mediate = Encoder.encodeToken(new Token('address', '123')); + + expect(mediate.type).to.equal('raw'); + expect(mediate.value).to.be.ok; + }); + + it('encodes bool tokens in Mediate(raw)', () => { + const mediatet = Encoder.encodeToken(new Token('bool', true)); + const mediatef = Encoder.encodeToken(new Token('bool', false)); + + expect(mediatet.type).to.equal('raw'); + expect(mediatet.value).to.be.ok; + + expect(mediatef.type).to.equal('raw'); + expect(mediatef.value).to.be.ok; + }); + + it('encodes int tokens in Mediate(raw)', () => { + const mediate = Encoder.encodeToken(new Token('int', '123')); + + expect(mediate.type).to.equal('raw'); + expect(mediate.value).to.be.ok; + }); + + it('encodes uint tokens in Mediate(raw)', () => { + const mediate = Encoder.encodeToken(new Token('uint', '123')); + + expect(mediate.type).to.equal('raw'); + expect(mediate.value).to.be.ok; + }); + + it('encodes fixedBytes tokens in Mediate(raw)', () => { + const mediate = Encoder.encodeToken(new Token('fixedBytes', '123')); + + expect(mediate.type).to.equal('raw'); + expect(mediate.value).to.be.ok; + }); + + it('encodes bytes tokens in Mediate(prefixed)', () => { + const mediate = Encoder.encodeToken(new Token('bytes', '123')); + + expect(mediate.type).to.equal('prefixed'); + expect(mediate.value).to.be.ok; + }); + + it('encodes string tokens in Mediate(prefixed)', () => { + const mediate = Encoder.encodeToken(new Token('string', '123')); + + expect(mediate.type).to.equal('prefixed'); + expect(mediate.value).to.be.ok; + }); + + it('encodes fixedArray tokens in Mediate(fixedArray)', () => { + const mediate = Encoder.encodeToken(new Token('fixedArray', [new Token('uint', '123')])); + + expect(mediate.type).to.equal('fixedArray'); + expect(mediate.value).to.be.ok; + }); + + it('encodes array tokens in Mediate(array)', () => { + const mediate = Encoder.encodeToken(new Token('array', [new Token('uint', '123')])); + + expect(mediate.type).to.equal('array'); + expect(mediate.value).to.be.ok; + }); + + it('throws an Error on invalid tokens', () => { + const token = new Token('address'); + + token._type = 'noMatch'; + + expect(() => Encoder.encodeToken(token)).to.throw(/noMatch/); + }); + }); + + describe('encode', () => { + it('requires tokens array', () => { + expect(() => Encoder.encode()).to.throw(/array/); + }); + + describe('addresses', () => { + const address1 = '1111111111111111111111111111111111111111'; + const address2 = '2222222222222222222222222222222222222222'; + const address3 = '3333333333333333333333333333333333333333'; + const address4 = '4444444444444444444444444444444444444444'; + const encAddress1 = padAddress(address1); + const encAddress2 = padAddress(address2); + const encAddress3 = padAddress(address3); + const encAddress4 = padAddress(address4); + const tokenAddress1 = new Token('address', address1); + const tokenAddress2 = new Token('address', address2); + const tokenAddress3 = new Token('address', address3); + const tokenAddress4 = new Token('address', address4); + + it('encodes an address', () => { + const token = tokenAddress1; + + expect(Encoder.encode([token])).to.equal(encAddress1); + }); + + it('encodes an array of addresses', () => { + const expected = `${padU32(0x20)}${padU32(2)}${encAddress1}${encAddress2}`; + const token = new Token('array', [tokenAddress1, tokenAddress2]); + + expect(Encoder.encode([token])).to.equal(expected); + }); + + it('encodes an fixedArray of addresses', () => { + const expected = `${encAddress1}${encAddress2}`; + const token = new Token('fixedArray', [tokenAddress1, tokenAddress2]); + + expect(Encoder.encode([token])).to.equal(expected); + }); + + it('encodes two addresses', () => { + const expected = `${encAddress1}${encAddress2}`; + const tokens = [tokenAddress1, tokenAddress2]; + + expect(Encoder.encode(tokens)).to.equal(expected); + }); + + it('encodes fixed array of dynamic array addresses', () => { + const tokens1 = new Token('array', [tokenAddress1, tokenAddress2]); + const tokens2 = new Token('array', [tokenAddress3, tokenAddress4]); + const fixed = new Token('fixedArray', [tokens1, tokens2]); + const expected = `${padU32(0x40)}${padU32(0xa0)}${padU32(2)}${encAddress1}${encAddress2}${padU32(2)}${encAddress3}${encAddress4}`; + + expect(Encoder.encode([fixed])).to.equal(expected); + }); + + it('encodes dynamic array of fixed array addresses', () => { + const tokens1 = new Token('fixedArray', [tokenAddress1, tokenAddress2]); + const tokens2 = new Token('fixedArray', [tokenAddress3, tokenAddress4]); + const dynamic = new Token('array', [tokens1, tokens2]); + const expected = `${padU32(0x20)}${padU32(2)}${encAddress1}${encAddress2}${encAddress3}${encAddress4}`; + + expect(Encoder.encode([dynamic])).to.equal(expected); + }); + + it('encodes dynamic array of dynamic array addresses', () => { + const tokens1 = new Token('array', [tokenAddress1]); + const tokens2 = new Token('array', [tokenAddress2]); + const dynamic = new Token('array', [tokens1, tokens2]); + const expected = `${padU32(0x20)}${padU32(2)}${padU32(0x80)}${padU32(0xc0)}${padU32(1)}${encAddress1}${padU32(1)}${encAddress2}`; + + expect(Encoder.encode([dynamic])).to.equal(expected); + }); + + it('encodes dynamic array of dynamic array addresses (2)', () => { + const tokens1 = new Token('array', [tokenAddress1, tokenAddress2]); + const tokens2 = new Token('array', [tokenAddress3, tokenAddress4]); + const dynamic = new Token('array', [tokens1, tokens2]); + const expected = `${padU32(0x20)}${padU32(2)}${padU32(0x80)}${padU32(0xe0)}${padU32(2)}${encAddress1}${encAddress2}${padU32(2)}${encAddress3}${encAddress4}`; + + expect(Encoder.encode([dynamic])).to.equal(expected); + }); + + it('encodes fixed array of fixed array addresses', () => { + const tokens1 = new Token('fixedArray', [tokenAddress1, tokenAddress2]); + const tokens2 = new Token('fixedArray', [tokenAddress3, tokenAddress4]); + const dynamic = new Token('fixedArray', [tokens1, tokens2]); + const expected = `${encAddress1}${encAddress2}${encAddress3}${encAddress4}`; + + expect(Encoder.encode([dynamic])).to.equal(expected); + }); + }); + + describe('bytes', () => { + const bytes1 = '0x1234'; + const bytes2 = '0x10000000000000000000000000000000000000000000000000000000000002'; + const bytes3 = '0x1000000000000000000000000000000000000000000000000000000000000000'; + + it('encodes fixed bytes', () => { + const token = new Token('fixedBytes', bytes1); + + expect(Encoder.encode([token])).to.equal(padFixedBytes(bytes1)); + }); + + it('encodes bytes', () => { + const token = new Token('bytes', bytes1); + + expect(Encoder.encode([token])).to.equal(`${padU32(0x20)}${padU32(2)}${padFixedBytes(bytes1)}`); + }); + + it('encodes bytes (short of boundary)', () => { + const token = new Token('bytes', bytes2); + + expect(Encoder.encode([token])).to.equal(`${padU32(0x20)}${padU32(0x1f)}${padFixedBytes(bytes2)}`); + }); + + it('encodes bytes (two blocks)', () => { + const input = `${bytes3}${bytes3.slice(-64)}`; + const token = new Token('bytes', input); + + expect(Encoder.encode([token])).to.equal(`${padU32(0x20)}${padU32(0x40)}${padFixedBytes(input)}`); + }); + + it('encodes two consecutive bytes', () => { + const in1 = '0x10000000000000000000000000000000000000000000000000000000000002'; + const in2 = '0x0010000000000000000000000000000000000000000000000000000000000002'; + const tokens = [new Token('bytes', in1), new Token('bytes', in2)]; + + expect(Encoder.encode(tokens)).to.equal(`${padU32(0x40)}${padU32(0x80)}${padU32(0x1f)}${padFixedBytes(in1)}${padU32(0x20)}${padFixedBytes(in2)}`); + }); + }); + + describe('string', () => { + it('encodes a string', () => { + const string = 'gavofyork'; + const stringEnc = padFixedBytes('0x6761766f66796f726b'); + const token = new Token('string', string); + + expect(Encoder.encode([token])).to.equal(`${padU32(0x20)}${padU32(string.length.toString(16))}${stringEnc}`); + }); + }); + + describe('uint', () => { + it('encodes a uint', () => { + const token = new Token('uint', 4); + + expect(Encoder.encode([token])).to.equal(padU32(4)); + }); + }); + + describe('int', () => { + it('encodes a int', () => { + const token = new Token('int', 4); + + expect(Encoder.encode([token])).to.equal(padU32(4)); + }); + }); + + describe('bool', () => { + it('encodes a bool (true)', () => { + const token = new Token('bool', true); + + expect(Encoder.encode([token])).to.equal(padU32(1)); + }); + + it('encodes a bool (false)', () => { + const token = new Token('bool', false); + + expect(Encoder.encode([token])).to.equal(padU32(0)); + }); + }); + + describe('comprehensive test', () => { + it('encodes a complex sequence', () => { + const bytes = '0x131a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b131a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b'; + const tokens = [new Token('int', 5), new Token('bytes', bytes), new Token('int', 3), new Token('bytes', bytes)]; + + expect(Encoder.encode(tokens)).to.equal(`${padU32(5)}${padU32(0x80)}${padU32(3)}${padU32(0xe0)}${padU32(0x40)}${bytes.substr(2)}${padU32(0x40)}${bytes.substr(2)}`); + }); + + it('encodes a complex sequence (nested)', () => { + const array = [new Token('int', 5), new Token('int', 6), new Token('int', 7)]; + const tokens = [new Token('int', 1), new Token('string', 'gavofyork'), new Token('int', 2), new Token('int', 3), new Token('int', 4), new Token('array', array)]; + const stringEnc = padFixedBytes('0x6761766f66796f726b'); + + expect(Encoder.encode(tokens)).to.equal(`${padU32(1)}${padU32(0xc0)}${padU32(2)}${padU32(3)}${padU32(4)}${padU32(0x100)}${padU32(9)}${stringEnc}${padU32(3)}${padU32(5)}${padU32(6)}${padU32(7)}`); + }); + }); + }); +}); diff --git a/js/src/abi/encoder/index.js b/js/src/abi/encoder/index.js new file mode 100644 index 0000000000000000000000000000000000000000..5ac3c0a6b2150912c3f423c718e1923581004884 --- /dev/null +++ b/js/src/abi/encoder/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 './encoder'; diff --git a/js/src/abi/encoder/mediate.js b/js/src/abi/encoder/mediate.js new file mode 100644 index 0000000000000000000000000000000000000000..90cc28881d793a36216143b5972d665aede6b724 --- /dev/null +++ b/js/src/abi/encoder/mediate.js @@ -0,0 +1,142 @@ +// 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 . + +const TYPES = ['raw', 'prefixed', 'fixedArray', 'array']; + +import { padU32 } from '../util/pad'; + +export default class Mediate { + constructor (type, value) { + Mediate.validateType(type); + + this._type = type; + this._value = value; + } + + initLength () { + switch (this._type) { + case 'raw': + return this._value.length / 2; + + case 'array': + case 'prefixed': + return 32; + + case 'fixedArray': + return this._value + .reduce((total, mediate) => { + return total + mediate.initLength(); + }, 0); + } + } + + closingLength () { + switch (this._type) { + case 'raw': + return 0; + + case 'prefixed': + return this._value.length / 2; + + case 'array': + return this._value + .reduce((total, mediate) => { + return total + mediate.initLength(); + }, 32); + + case 'fixedArray': + return this._value + .reduce((total, mediate) => { + return total + mediate.initLength() + mediate.closingLength(); + }, 0); + } + } + + init (suffixOffset) { + switch (this._type) { + case 'raw': + return this._value; + + case 'fixedArray': + return this._value + .map((mediate, idx) => mediate.init(Mediate.offsetFor(this._value, idx)).toString(16)) + .join(''); + + case 'prefixed': + case 'array': + return padU32(suffixOffset); + } + } + + closing (offset) { + switch (this._type) { + case 'raw': + return ''; + + case 'prefixed': + return this._value; + + case 'fixedArray': + return this._value + .map((mediate, idx) => mediate.closing(Mediate.offsetFor(this._value, idx)).toString(16)) + .join(''); + + case 'array': + const prefix = padU32(this._value.length); + const inits = this._value + .map((mediate, idx) => mediate.init(offset + Mediate.offsetFor(this._value, idx) + 32).toString(16)) + .join(''); + const closings = this._value + .map((mediate, idx) => mediate.closing(offset + Mediate.offsetFor(this._value, idx)).toString(16)) + .join(''); + + return `${prefix}${inits}${closings}`; + } + } + + get type () { + return this._type; + } + + get value () { + return this._value; + } + + static offsetFor (mediates, position) { + if (position < 0 || position >= mediates.length) { + throw new Error(`Invalid position ${position} specified for Mediate.offsetFor`); + } + + const initLength = mediates + .reduce((total, mediate) => { + return total + mediate.initLength(); + }, 0); + + return mediates + .slice(0, position) + .reduce((total, mediate) => { + return total + mediate.closingLength(); + }, initLength); + } + + static validateType (type) { + if (TYPES.filter((_type) => type === _type).length) { + return true; + } + + throw new Error(`Invalid type ${type} received for Mediate.validateType`); + } +} diff --git a/js/src/abi/encoder/mediate.spec.js b/js/src/abi/encoder/mediate.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..ab89909b1ed96fdfba04fa0f4f22f3f79e99a25b --- /dev/null +++ b/js/src/abi/encoder/mediate.spec.js @@ -0,0 +1,105 @@ +// 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 Mediate from './mediate'; + +describe('abi/encoder/Mediate', () => { + const LONG15 = '1234567890abcdef000000000000000000000000000000000000000000000000'; + const DOUBLE15 = `${LONG15}${LONG15}`; + const ARRAY = [new Mediate('raw', DOUBLE15), new Mediate('raw', LONG15)]; + + describe('validateType', () => { + it('validates raw', () => { + expect(Mediate.validateType('raw')).to.be.true; + }); + + it('validates prefixed', () => { + expect(Mediate.validateType('prefixed')).to.be.true; + }); + + it('validates fixedArray', () => { + expect(Mediate.validateType('fixedArray')).to.be.true; + }); + + it('validates array', () => { + expect(Mediate.validateType('array')).to.be.true; + }); + + it('throws an error on invalid types', () => { + expect(() => Mediate.validateType('noMatch')).to.throw(/noMatch/); + }); + }); + + describe('offsetFor', () => { + it('thows an error when offset < 0', () => { + expect(() => Mediate.offsetFor([1], -1)).to.throw(/Invalid position/); + }); + + it('throws an error when offset >= length', () => { + expect(() => Mediate.offsetFor([1], 1)).to.throw(/Invalid position/); + }); + }); + + describe('constructor', () => { + it('throws an error on invalid types', () => { + expect(() => new Mediate('noMatch', '1')).to.throw(/noMatch/); + }); + + it('sets the type of the object', () => { + expect((new Mediate('raw', '1')).type).to.equal('raw'); + }); + + it('sets the value of the object', () => { + expect((new Mediate('raw', '1')).value).to.equal('1'); + }); + }); + + describe('initLength', () => { + it('returns correct variable byte length for raw', () => { + expect(new Mediate('raw', DOUBLE15).initLength()).to.equal(64); + }); + + it('returns correct fixed byte length for array', () => { + expect(new Mediate('array', [1, 2, 3, 4]).initLength()).to.equal(32); + }); + + it('returns correct fixed byte length for prefixed', () => { + expect(new Mediate('prefixed', 0).initLength()).to.equal(32); + }); + + it('returns correct variable byte length for fixedArray', () => { + expect(new Mediate('fixedArray', ARRAY).initLength()).to.equal(96); + }); + }); + + describe('closingLength', () => { + it('returns 0 byte length for raw', () => { + expect(new Mediate('raw', DOUBLE15).closingLength()).to.equal(0); + }); + + it('returns prefix + size for prefixed', () => { + expect(new Mediate('prefixed', DOUBLE15).closingLength()).to.equal(64); + }); + + it('returns prefix + size for array', () => { + expect(new Mediate('array', ARRAY).closingLength()).to.equal(128); + }); + + it('returns total length for fixedArray', () => { + expect(new Mediate('fixedArray', ARRAY).closingLength()).to.equal(96); + }); + }); +}); diff --git a/js/src/abi/index.js b/js/src/abi/index.js new file mode 100644 index 0000000000000000000000000000000000000000..703ed1c7884ecfbaf917f28799d1b75fb980df1d --- /dev/null +++ b/js/src/abi/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 './abi'; diff --git a/js/src/abi/spec/constructor.js b/js/src/abi/spec/constructor.js new file mode 100644 index 0000000000000000000000000000000000000000..e9b23b593df2dfc38d3878ba7d5d49b6abefbadc --- /dev/null +++ b/js/src/abi/spec/constructor.js @@ -0,0 +1,36 @@ +// 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 Encoder from '../encoder/encoder'; +import Param from './param'; + +export default class Constructor { + constructor (abi) { + this._inputs = Param.toParams(abi.inputs || []); + } + + get inputs () { + return this._inputs; + } + + inputParamTypes () { + return this._inputs.map((input) => input.kind); + } + + encodeCall (tokens) { + return Encoder.encode(tokens); + } +} diff --git a/js/src/abi/spec/constructor.spec.js b/js/src/abi/spec/constructor.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..7c7414bdc8a6422f1df35377e9cff25fcd90a2d2 --- /dev/null +++ b/js/src/abi/spec/constructor.spec.js @@ -0,0 +1,52 @@ +// 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 Constructor from './constructor'; +import Param from './param'; +import Token from '../token'; + +describe('abi/spec/Constructor', () => { + const inputsArr = [{ name: 'boolin', type: 'bool' }, { name: 'stringin', type: 'string' }]; + const bool = new Param('boolin', 'bool'); + const string = new Param('stringin', 'string'); + + const inputs = [bool, string]; + const cr = new Constructor({ inputs: inputsArr }); + + describe('constructor', () => { + it('stores the inputs as received', () => { + expect(cr.inputs).to.deep.equal(inputs); + }); + + it('matches empty inputs with []', () => { + expect(new Constructor({}).inputs).to.deep.equal([]); + }); + }); + + describe('inputParamTypes', () => { + it('retrieves the input types as received', () => { + expect(cr.inputParamTypes()).to.deep.equal([bool.kind, string.kind]); + }); + }); + + describe('encodeCall', () => { + it('encodes correctly', () => { + const result = cr.encodeCall([new Token('bool', true), new Token('string', 'jacogr')]); + + expect(result).to.equal('0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000066a61636f67720000000000000000000000000000000000000000000000000000'); + }); + }); +}); diff --git a/js/src/abi/spec/event/decodedLog.js b/js/src/abi/spec/event/decodedLog.js new file mode 100644 index 0000000000000000000000000000000000000000..e398c5a1bf552cee88cf7d6e33aa103a428da1ec --- /dev/null +++ b/js/src/abi/spec/event/decodedLog.js @@ -0,0 +1,30 @@ +// 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 class DecodedLog { + constructor (params, address) { + this._params = params; + this._address = address; + } + + get address () { + return this._address; + } + + get params () { + return this._params; + } +} diff --git a/js/src/abi/spec/event/decodedLog.spec.js b/js/src/abi/spec/event/decodedLog.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..f448af011c0afbb42a8b5adde8f3e52ed5c0298c --- /dev/null +++ b/js/src/abi/spec/event/decodedLog.spec.js @@ -0,0 +1,28 @@ +// 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 DecodedLog from './decodedLog'; + +const log = new DecodedLog('someParams', 'someAddress'); + +describe('abi/spec/event/DecodedLog', () => { + describe('constructor', () => { + it('sets internal state', () => { + expect(log.params).to.equal('someParams'); + expect(log.address).to.equal('someAddress'); + }); + }); +}); diff --git a/js/src/abi/spec/event/decodedLogParam.js b/js/src/abi/spec/event/decodedLogParam.js new file mode 100644 index 0000000000000000000000000000000000000000..422fbaac84fa03facdfcfc82bc4a4070f9c2a7f3 --- /dev/null +++ b/js/src/abi/spec/event/decodedLogParam.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 . + +import ParamType from '../paramType/paramType'; +import Token from '../../token/token'; +import { isInstanceOf } from '../../util/types'; + +export default class DecodedLogParam { + constructor (name, kind, token) { + if (!isInstanceOf(kind, ParamType)) { + throw new Error('kind not instanceof ParamType'); + } else if (!isInstanceOf(token, Token)) { + throw new Error('token not instanceof Token'); + } + + this._name = name; + this._kind = kind; + this._token = token; + } + + get name () { + return this._name; + } + + get kind () { + return this._kind; + } + + get token () { + return this._token; + } +} diff --git a/js/src/abi/spec/event/decodedLogParam.spec.js b/js/src/abi/spec/event/decodedLogParam.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..6a7f5204495dfbdaec05d0f9d4f6c14c05379562 --- /dev/null +++ b/js/src/abi/spec/event/decodedLogParam.spec.js @@ -0,0 +1,42 @@ +// 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 DecodedLogParam from './decodedLogParam'; +import ParamType from '../paramType'; +import Token from '../../token'; + +describe('abi/spec/event/DecodedLogParam', () => { + describe('constructor', () => { + const pt = new ParamType('bool'); + const tk = new Token('bool'); + + it('disallows kind not instanceof ParamType', () => { + expect(() => new DecodedLogParam('test', 'param')).to.throw(/ParamType/); + }); + + it('disallows token not instanceof Token', () => { + expect(() => new DecodedLogParam('test', pt, 'token')).to.throw(/Token/); + }); + + it('stores all parameters received', () => { + const log = new DecodedLogParam('test', pt, tk); + + expect(log.name).to.equal('test'); + expect(log.kind).to.equal(pt); + expect(log.token).to.equal(tk); + }); + }); +}); diff --git a/js/src/abi/spec/event/event.js b/js/src/abi/spec/event/event.js new file mode 100644 index 0000000000000000000000000000000000000000..604f58cb18b163b6bbe1da2ccda9f7196177a300 --- /dev/null +++ b/js/src/abi/spec/event/event.js @@ -0,0 +1,114 @@ +// 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 Decoder from '../../decoder/decoder'; +import DecodedLog from './decodedLog'; +import DecodedLogParam from './decodedLogParam'; +import EventParam from './eventParam'; +import { asAddress } from '../../util/sliceAs'; +import { eventSignature } from '../../util/signature'; + +export default class Event { + constructor (abi) { + this._inputs = EventParam.toEventParams(abi.inputs || []); + this._anonymous = !!abi.anonymous; + + const { id, name, signature } = eventSignature(abi.name, this.inputParamTypes()); + + this._id = id; + this._name = name; + this._signature = signature; + } + + get name () { + return this._name; + } + + get id () { + return this._id; + } + + get inputs () { + return this._inputs; + } + + get anonymous () { + return this._anonymous; + } + + get signature () { + return this._signature; + } + + inputParamTypes () { + return this._inputs.map((input) => input.kind); + } + + inputParamNames () { + return this._inputs.map((input) => input.name); + } + + indexedParams (indexed) { + return this._inputs.filter((input) => input.indexed === indexed); + } + + decodeLog (topics, data) { + const topicParams = this.indexedParams(true); + const dataParams = this.indexedParams(false); + + let address; + let toSkip; + + if (!this.anonymous) { + address = asAddress(topics[0]); + toSkip = 1; + } else { + toSkip = 0; + } + + const topicTypes = topicParams.map((param) => param.kind); + const flatTopics = topics + .filter((topic, idx) => idx >= toSkip) + .map((topic) => { + return (topic.substr(0, 2) === '0x') + ? topic.substr(2) + : topic; + }).join(''); + const topicTokens = Decoder.decode(topicTypes, flatTopics); + + if (topicTokens.length !== (topics.length - toSkip)) { + throw new Error('Invalid topic data'); + } + + const dataTypes = dataParams.map((param) => param.kind); + const dataTokens = Decoder.decode(dataTypes, data); + + const namedTokens = {}; + + topicParams.forEach((param, idx) => { + namedTokens[param.name || idx] = topicTokens[idx]; + }); + dataParams.forEach((param, idx) => { + namedTokens[param.name || idx] = dataTokens[idx]; + }); + + const inputParamTypes = this.inputParamTypes(); + const decodedParams = this.inputParamNames() + .map((name, idx) => new DecodedLogParam(name, inputParamTypes[idx], namedTokens[name || idx])); + + return new DecodedLog(decodedParams, address); + } +} diff --git a/js/src/abi/spec/event/event.spec.js b/js/src/abi/spec/event/event.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..bf47980fc6441631ffc07a32ab885ac918abbde0 --- /dev/null +++ b/js/src/abi/spec/event/event.spec.js @@ -0,0 +1,111 @@ +// 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 BigNumber from 'bignumber.js'; + +import Event from './event'; +import EventParam from './eventParam'; +import DecodedLogParam from './decodedLogParam'; +import ParamType from '../paramType'; +import Token from '../../token'; + +describe('abi/spec/event/Event', () => { + const inputArr = [{ name: 'a', type: 'bool' }, { name: 'b', type: 'uint', indexed: true }]; + const inputs = [new EventParam('a', 'bool', false), new EventParam('b', 'uint', true)]; + const event = new Event({ name: 'test', inputs: inputArr, anonymous: true }); + + describe('constructor', () => { + it('stores the parameters as received', () => { + expect(event.name).to.equal('test'); + expect(event.inputs).to.deep.equal(inputs); + expect(event.anonymous).to.be.true; + }); + + it('matches empty inputs with []', () => { + expect(new Event({ name: 'test' }).inputs).to.deep.equal([]); + }); + + it('sets the event signature', () => { + expect(new Event({ name: 'baz' }).signature) + .to.equal('a7916fac4f538170f7cd12c148552e2cba9fcd72329a2dd5b07a6fa906488ddf'); + }); + }); + + describe('inputParamTypes', () => { + it('returns all the types', () => { + expect(event.inputParamTypes()).to.deep.equal([new ParamType('bool'), new ParamType('uint', null, 256, true)]); + }); + }); + + describe('inputParamNames', () => { + it('returns all the names', () => { + expect(event.inputParamNames()).to.deep.equal(['a', 'b']); + }); + }); + + describe('indexedParams', () => { + it('returns all indexed parameters (indexed)', () => { + expect(event.indexedParams(true)).to.deep.equal([inputs[1]]); + }); + + it('returns all indexed parameters (non-indexed)', () => { + expect(event.indexedParams(false)).to.deep.equal([inputs[0]]); + }); + }); + + describe('decodeLog', () => { + it('decodes an event', () => { + const event = new Event({ + name: 'foo', + inputs: [ + { name: 'a', type: 'int' }, + { name: 'b', type: 'int', indexed: true }, + { name: 'c', type: 'address' }, + { name: 'd', type: 'address', indexed: true } + ] + }); + const decoded = event.decodeLog([ + '0000000000000000000000004444444444444444444444444444444444444444', + '0000000000000000000000000000000000000000000000000000000000000002', + '0000000000000000000000001111111111111111111111111111111111111111' ], + '00000000000000000000000000000000000000000000000000000000000000030000000000000000000000002222222222222222222222222222222222222222'); + + expect(decoded.address).to.equal('0x4444444444444444444444444444444444444444'); + expect(decoded.params).to.deep.equal([ + new DecodedLogParam('a', new ParamType('int', null, 256), new Token('int', new BigNumber(3))), + new DecodedLogParam('b', new ParamType('int', null, 256, true), new Token('int', new BigNumber(2))), + new DecodedLogParam('c', new ParamType('address'), new Token('address', '0x2222222222222222222222222222222222222222')), + new DecodedLogParam('d', new ParamType('address', null, 0, true), new Token('address', '0x1111111111111111111111111111111111111111')) + ]); + }); + + it('decodes an anonymous event', () => { + const event = new Event({ name: 'foo', inputs: [{ name: 'a', type: 'int' }], anonymous: true }); + const decoded = event.decodeLog([], '0000000000000000000000000000000000000000000000000000000000000003'); + + expect(decoded.address).to.not.be.ok; + expect(decoded.params).to.deep.equal([ + new DecodedLogParam('a', new ParamType('int', null, 256), new Token('int', new BigNumber(3))) + ]); + }); + + it('throws on invalid topics', () => { + const event = new Event({ name: 'foo', inputs: [{ name: 'a', type: 'int' }], anonymous: true }); + + expect(() => event.decodeLog(['0000000000000000000000004444444444444444444444444444444444444444'], '0000000000000000000000000000000000000000000000000000000000000003')).to.throw(/Invalid/); + }); + }); +}); diff --git a/js/src/abi/spec/event/eventParam.js b/js/src/abi/spec/event/eventParam.js new file mode 100644 index 0000000000000000000000000000000000000000..b49b778141183c8be180ae0a141f2975a3492a4c --- /dev/null +++ b/js/src/abi/spec/event/eventParam.js @@ -0,0 +1,41 @@ +// 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 { toParamType } from '../paramType/format'; + +export default class EventParam { + constructor (name, type, indexed = false) { + this._name = name; + this._indexed = indexed; + this._kind = toParamType(type, indexed); + } + + get name () { + return this._name; + } + + get kind () { + return this._kind; + } + + get indexed () { + return this._indexed; + } + + static toEventParams (params) { + return params.map((param) => new EventParam(param.name, param.type, param.indexed)); + } +} diff --git a/js/src/abi/spec/event/eventParam.spec.js b/js/src/abi/spec/event/eventParam.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..e35ebcebcce2f87d890670544c176a8fab3f75c7 --- /dev/null +++ b/js/src/abi/spec/event/eventParam.spec.js @@ -0,0 +1,44 @@ +// 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 EventParam from './eventParam'; + +describe('abi/spec/event/EventParam', () => { + describe('constructor', () => { + it('sets the properties', () => { + const param = new EventParam('foo', 'uint', true); + + expect(param.name).to.equal('foo'); + expect(param.kind.type).to.equal('uint'); + expect(param.indexed).to.be.true; + }); + + it('uses defaults for indexed', () => { + expect(new EventParam('foo', 'uint').indexed).to.be.false; + }); + }); + + describe('toEventParams', () => { + it('maps an array of params', () => { + const params = EventParam.toEventParams([{ name: 'foo', type: 'uint' }]); + + expect(params.length).to.equal(1); + expect(params[0].indexed).to.be.false; + expect(params[0].name).to.equal('foo'); + expect(params[0].kind.type).to.equal('uint'); + }); + }); +}); diff --git a/js/src/abi/spec/event/index.js b/js/src/abi/spec/event/index.js new file mode 100644 index 0000000000000000000000000000000000000000..25441954b4837428bc895fc61c48f5f81cd24e58 --- /dev/null +++ b/js/src/abi/spec/event/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 './event'; diff --git a/js/src/abi/spec/function.js b/js/src/abi/spec/function.js new file mode 100644 index 0000000000000000000000000000000000000000..6010f4e598f898c980a389f810057c2c893e21af --- /dev/null +++ b/js/src/abi/spec/function.js @@ -0,0 +1,88 @@ +// 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 Decoder from '../decoder/decoder'; +import Encoder from '../encoder/encoder'; +import Param from './param'; +import { methodSignature } from '../util/signature'; + +export default class Func { + constructor (abi) { + this._abi = abi; + this._constant = !!abi.constant; + this._payable = abi.payable; + this._inputs = Param.toParams(abi.inputs || []); + this._outputs = Param.toParams(abi.outputs || []); + + const { id, name, signature } = methodSignature(abi.name, this.inputParamTypes()); + + this._id = id; + this._name = name; + this._signature = signature; + } + + get abi () { + return this._abi; + } + + get constant () { + return this._constant; + } + + get name () { + return this._name; + } + + get id () { + return this._id; + } + + get payable () { + return this._payable; + } + + get inputs () { + return this._inputs; + } + + get outputs () { + return this._outputs; + } + + get signature () { + return this._signature; + } + + inputParamTypes () { + return this._inputs.map((input) => input.kind); + } + + outputParamTypes () { + return this._outputs.map((output) => output.kind); + } + + encodeCall (tokens) { + return `${this._signature}${Encoder.encode(tokens)}`; + } + + decodeInput (data) { + return Decoder.decode(this.inputParamTypes(), data); + } + + decodeOutput (data) { + return Decoder.decode(this.outputParamTypes(), data); + } +} diff --git a/js/src/abi/spec/function.spec.js b/js/src/abi/spec/function.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..436ca73650b1cf1014bbfb6d54f9a04001a8aa24 --- /dev/null +++ b/js/src/abi/spec/function.spec.js @@ -0,0 +1,101 @@ +// 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 Func from './function'; +import Param from './param'; +import Token from '../token'; + +describe('abi/spec/Function', () => { + const inputsArr = [{ name: 'boolin', type: 'bool' }, { name: 'stringin', type: 'string' }]; + const outputsArr = [{ name: 'output', type: 'uint' }]; + + const uint = new Param('output', 'uint'); + const bool = new Param('boolin', 'bool'); + const string = new Param('stringin', 'string'); + const inputs = [bool, string]; + const outputs = [uint]; + + const func = new Func({ + name: 'test', + inputs: inputsArr, + outputs: outputsArr + }); + + describe('constructor', () => { + it('returns signature correctly if name already contains it', () => { + const func = new Func({ + name: 'test(bool,string)', + inputs: inputsArr, + outputs: outputsArr + }); + + expect(func.name).to.equal('test'); + expect(func.id).to.equal('test(bool,string)'); + expect(func.signature).to.equal('02356205'); + }); + + it('stores the parameters as received', () => { + expect(func.name).to.equal('test'); + expect(func.constant).to.be.false; + expect(func.inputs).to.deep.equal(inputs); + expect(func.outputs).to.deep.equal(outputs); + }); + + it('matches empty inputs with []', () => { + expect(new Func({ name: 'test', outputs: outputsArr }).inputs).to.deep.equal([]); + }); + + it('matches empty outputs with []', () => { + expect(new Func({ name: 'test', inputs: inputsArr }).outputs).to.deep.equal([]); + }); + + it('sets the method signature', () => { + expect(new Func({ name: 'baz' }).signature).to.equal('a7916fac'); + }); + + it('allows constant functions', () => { + expect(new Func({ name: 'baz', constant: true }).constant).to.be.true; + }); + }); + + describe('inputParamTypes', () => { + it('retrieves the input types as received', () => { + expect(func.inputParamTypes()).to.deep.equal([bool.kind, string.kind]); + }); + }); + + describe('outputParamTypes', () => { + it('retrieves the output types as received', () => { + expect(func.outputParamTypes()).to.deep.equal([uint.kind]); + }); + }); + + describe('encodeCall', () => { + it('encodes the call correctly', () => { + const result = func.encodeCall([new Token('bool', true), new Token('string', 'jacogr')]); + + expect(result).to.equal('023562050000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000066a61636f67720000000000000000000000000000000000000000000000000000'); + }); + }); + + describe('decodeOutput', () => { + it('decodes the result correctly', () => { + const result = func.decodeOutput('1111111111111111111111111111111111111111111111111111111111111111'); + + expect(result[0].value.toString(16)).to.equal('1111111111111111111111111111111111111111111111111111111111111111'); + }); + }); +}); diff --git a/js/src/abi/spec/index.js b/js/src/abi/spec/index.js new file mode 100644 index 0000000000000000000000000000000000000000..87852d4880307162ae8dd143843878be5c7077b8 --- /dev/null +++ b/js/src/abi/spec/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 './interface'; diff --git a/js/src/abi/spec/interface.js b/js/src/abi/spec/interface.js new file mode 100644 index 0000000000000000000000000000000000000000..b24569525dbf4b8def78debea25fab04a56eaea1 --- /dev/null +++ b/js/src/abi/spec/interface.js @@ -0,0 +1,77 @@ +// 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 Constructor from './constructor'; +import Event from './event/event'; +import Func from './function'; +import Token from '../token'; + +export default class Interface { + constructor (abi) { + this._interface = Interface.parseABI(abi); + } + + get interface () { + return this._interface; + } + + get constructors () { + return this._interface.filter((item) => item instanceof Constructor); + } + + get events () { + return this._interface.filter((item) => item instanceof Event); + } + + get functions () { + return this._interface.filter((item) => item instanceof Func); + } + + encodeTokens (paramTypes, values) { + return Interface.encodeTokens(paramTypes, values); + } + + static encodeTokens (paramTypes, values) { + const createToken = function (paramType, value) { + if (paramType.subtype) { + return new Token(paramType.type, value.map((entry) => createToken(paramType.subtype, entry))); + } + + return new Token(paramType.type, value); + }; + + return paramTypes.map((paramType, idx) => createToken(paramType, values[idx])); + } + + static parseABI (abi) { + return abi.map((item) => { + switch (item.type) { + case 'constructor': + return new Constructor(item); + + case 'event': + return new Event(item); + + case 'function': + case 'fallback': + return new Func(item); + + default: + throw new Error(`Unknown ABI type ${item.type}`); + } + }); + } +} diff --git a/js/src/abi/spec/interface.spec.js b/js/src/abi/spec/interface.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..ebaa323ed526644517e06eb55e696466f82f0019 --- /dev/null +++ b/js/src/abi/spec/interface.spec.js @@ -0,0 +1,126 @@ +// 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 Interface from './interface'; +import ParamType from './paramType'; +import Token from '../token'; + +describe('abi/spec/Interface', () => { + const construct = { + type: 'constructor', + inputs: [] + }; + const event = { + type: 'event', + name: 'Event2', + anonymous: false, + inputs: [{ name: 'a', type: 'uint256', indexed: true }, { name: 'b', type: 'bytes32', indexed: false }] + }; + const func = { + type: 'function', + name: 'foo', + inputs: [{ name: 'a', type: 'uint256' }], + outputs: [] + }; + + describe('parseABI', () => { + it('throws on invalid types', () => { + expect(() => Interface.parseABI([{ type: 'noMatch' }])).to.throw(/noMatch/); + }); + + it('creates constructors', () => { + expect(Interface.parseABI([ construct ])).to.deep.equal([{ _inputs: [] }]); + }); + + it('creates events', () => { + expect(Interface.parseABI([ event ])[0].name).to.equal('Event2'); + }); + + it('creates functions', () => { + expect(Interface.parseABI([ func ])[0].name).to.equal('foo'); + }); + + it('parse complex interfaces', () => { + expect(Interface.parseABI([ construct, event, func ]).length).to.equal(3); + }); + }); + + describe('constructor', () => { + const int = new Interface([ construct, event, func ]); + + it('contains the full interface', () => { + expect(int.interface.length).to.equal(3); + }); + + it('contains the constructors', () => { + expect(int.constructors.length).to.equal(1); + }); + + it('contains the events', () => { + expect(int.events.length).to.equal(1); + }); + + it('contains the functions', () => { + expect(int.functions.length).to.equal(1); + }); + }); + + describe('encodeTokens', () => { + const int = new Interface([ construct, event, func ]); + + it('encodes simple types', () => { + expect( + int.encodeTokens( + [new ParamType('bool'), new ParamType('string'), new ParamType('int'), new ParamType('uint')], + [true, 'gavofyork', -123, 123] + ) + ).to.deep.equal([ + new Token('bool', true), new Token('string', 'gavofyork'), new Token('int', -123), new Token('uint', 123) + ]); + }); + + it('encodes array', () => { + expect( + int.encodeTokens( + [new ParamType('array', new ParamType('bool'))], + [[true, false, true]] + ) + ).to.deep.equal([ + new Token('array', [ + new Token('bool', true), new Token('bool', false), new Token('bool', true) + ]) + ]); + }); + + it('encodes simple with array of array', () => { + expect( + int.encodeTokens( + [ + new ParamType('bool'), + new ParamType('fixedArray', new ParamType('array', new ParamType('uint')), 2) + ], + [true, [[0, 1], [2, 3]]] + ) + ).to.deep.equal([ + new Token('bool', true), + new Token('fixedArray', [ + new Token('array', [new Token('uint', 0), new Token('uint', 1)]), + new Token('array', [new Token('uint', 2), new Token('uint', 3)]) + ]) + ]); + }); + }); +}); diff --git a/js/src/abi/spec/param.js b/js/src/abi/spec/param.js new file mode 100644 index 0000000000000000000000000000000000000000..e02b99100389273b68f79835855bd2540ec2efa1 --- /dev/null +++ b/js/src/abi/spec/param.js @@ -0,0 +1,42 @@ +// 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 { toParamType } from './paramType/format'; + +export default class Param { + constructor (name, type) { + this._name = name; + this._kind = toParamType(type); + } + + get name () { + return this._name; + } + + get kind () { + return this._kind; + } + + static toParams (params) { + return params.map((param) => { + if (param instanceof Param) { + return param; + } + + return new Param(param.name, param.type); + }); + } +} diff --git a/js/src/abi/spec/param.spec.js b/js/src/abi/spec/param.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..0ecdb0319b19d08a766b55c9bdbf33a3f00b99fc --- /dev/null +++ b/js/src/abi/spec/param.spec.js @@ -0,0 +1,47 @@ +// 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 Param from './param'; + +describe('abi/spec/Param', () => { + describe('constructor', () => { + const param = new Param('foo', 'uint'); + + it('sets the properties', () => { + expect(param.name).to.equal('foo'); + expect(param.kind.type).to.equal('uint'); + }); + }); + + describe('toParams', () => { + it('maps an array of params', () => { + const params = Param.toParams([{ name: 'foo', type: 'uint' }]); + + expect(params.length).to.equal(1); + expect(params[0].name).to.equal('foo'); + expect(params[0].kind.type).to.equal('uint'); + }); + + it('converts only if needed', () => { + const _params = Param.toParams([{ name: 'foo', type: 'uint' }]); + const params = Param.toParams(_params); + + expect(params.length).to.equal(1); + expect(params[0].name).to.equal('foo'); + expect(params[0].kind.type).to.equal('uint'); + }); + }); +}); diff --git a/js/src/abi/spec/paramType/format.js b/js/src/abi/spec/paramType/format.js new file mode 100644 index 0000000000000000000000000000000000000000..5aafd5e3070cdc47eaa27ba9ef4ea2e62419aad4 --- /dev/null +++ b/js/src/abi/spec/paramType/format.js @@ -0,0 +1,80 @@ +// 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 ParamType from './paramType'; + +export function toParamType (type, indexed) { + if (type[type.length - 1] === ']') { + const last = type.lastIndexOf('['); + const length = type.substr(last + 1, type.length - last - 2); + const subtype = toParamType(type.substr(0, last)); + + if (length.length === 0) { + return new ParamType('array', subtype, 0, indexed); + } + + return new ParamType('fixedArray', subtype, parseInt(length, 10), indexed); + } + + switch (type) { + case 'address': + case 'bool': + case 'bytes': + case 'string': + return new ParamType(type, null, 0, indexed); + + case 'int': + case 'uint': + return new ParamType(type, null, 256, indexed); + + default: + if (type.indexOf('uint') === 0) { + return new ParamType('uint', null, parseInt(type.substr(4), 10), indexed); + } else if (type.indexOf('int') === 0) { + return new ParamType('int', null, parseInt(type.substr(3), 10), indexed); + } else if (type.indexOf('bytes') === 0) { + return new ParamType('fixedBytes', null, parseInt(type.substr(5), 10), indexed); + } + + throw new Error(`Cannot convert ${type} to valid ParamType`); + } +} + +export function fromParamType (paramType) { + switch (paramType.type) { + case 'address': + case 'bool': + case 'bytes': + case 'string': + return paramType.type; + + case 'int': + case 'uint': + return `${paramType.type}${paramType.length}`; + + case 'fixedBytes': + return `bytes${paramType.length}`; + + case 'fixedArray': + return `${fromParamType(paramType.subtype)}[${paramType.length}]`; + + case 'array': + return `${fromParamType(paramType.subtype)}[]`; + + default: + throw new Error(`Cannot convert from ParamType ${paramType.type}`); + } +} diff --git a/js/src/abi/spec/paramType/format.spec.js b/js/src/abi/spec/paramType/format.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..cb000473380546c50f4289119f95e30d840085a8 --- /dev/null +++ b/js/src/abi/spec/paramType/format.spec.js @@ -0,0 +1,228 @@ +// 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 ParamType from './paramType'; +import { fromParamType, toParamType } from './format'; + +describe('abi/spec/paramType/format', () => { + describe('fromParamType', () => { + it('errors on invalid types', () => { + expect(() => fromParamType({ type: 'noMatch' })).to.throw(/noMatch/); + }); + + describe('simple types', () => { + it('converts address to address', () => { + const pt = new ParamType('address'); + + expect(fromParamType(pt)).to.equal('address'); + }); + + it('converts bool to bool', () => { + const pt = new ParamType('bool'); + + expect(fromParamType(pt)).to.equal('bool'); + }); + + it('converts bytes to bytes', () => { + const pt = new ParamType('bytes'); + + expect(fromParamType(pt)).to.equal('bytes'); + }); + + it('converts string to string', () => { + const pt = new ParamType('string'); + + expect(fromParamType(pt)).to.equal('string'); + }); + }); + + describe('length types', () => { + it('converts int32 to int32', () => { + const pt = new ParamType('int', null, 32); + + expect(fromParamType(pt)).to.equal('int32'); + }); + + it('converts uint64 to int64', () => { + const pt = new ParamType('uint', null, 64); + + expect(fromParamType(pt)).to.equal('uint64'); + }); + + it('converts fixedBytes8 to bytes8', () => { + const pt = new ParamType('fixedBytes', null, 8); + + expect(fromParamType(pt)).to.equal('bytes8'); + }); + }); + + describe('arrays', () => { + it('converts string[2] to string[2]', () => { + const pt = new ParamType('fixedArray', new ParamType('string'), 2); + + expect(fromParamType(pt)).to.equal('string[2]'); + }); + + it('converts bool[] to bool[]', () => { + const pt = new ParamType('array', new ParamType('bool')); + + expect(fromParamType(pt)).to.equal('bool[]'); + }); + + it('converts bool[][2] to bool[][2]', () => { + const pt = new ParamType('fixedArray', new ParamType('array', new ParamType('bool')), 2); + + expect(fromParamType(pt)).to.equal('bool[][2]'); + }); + + it('converts bool[2][] to bool[2][]', () => { + const pt = new ParamType('array', new ParamType('fixedArray', new ParamType('bool'), 2)); + + expect(fromParamType(pt)).to.equal('bool[2][]'); + }); + }); + }); + + describe('toParamType', () => { + it('errors on invalid types', () => { + expect(() => toParamType('noMatch')).to.throw(/noMatch/); + }); + + describe('simple mapping', () => { + it('converts address to address', () => { + const pt = toParamType('address'); + + expect(pt.type).to.equal('address'); + }); + + it('converts bool to bool', () => { + const pt = toParamType('bool'); + + expect(pt.type).to.equal('bool'); + }); + + it('converts bytes to bytes', () => { + const pt = toParamType('bytes'); + + expect(pt.type).to.equal('bytes'); + }); + + it('converts string to string', () => { + const pt = toParamType('string'); + + expect(pt.type).to.equal('string'); + }); + }); + + describe('number', () => { + it('converts int to int256', () => { + const pt = toParamType('int'); + + expect(pt.type).to.equal('int'); + expect(pt.length).to.equal(256); + }); + + it('converts uint to uint256', () => { + const pt = toParamType('uint'); + + expect(pt.type).to.equal('uint'); + expect(pt.length).to.equal(256); + }); + }); + + describe('sized types', () => { + it('converts int32 to int32', () => { + const pt = toParamType('int32'); + + expect(pt.type).to.equal('int'); + expect(pt.length).to.equal(32); + }); + + it('converts uint16 to uint16', () => { + const pt = toParamType('uint32'); + + expect(pt.type).to.equal('uint'); + expect(pt.length).to.equal(32); + }); + + it('converts bytes8 to fixedBytes8', () => { + const pt = toParamType('bytes8'); + + expect(pt.type).to.equal('fixedBytes'); + expect(pt.length).to.equal(8); + }); + }); + + describe('arrays', () => { + describe('fixed arrays', () => { + it('creates fixed array', () => { + const pt = toParamType('bytes[8]'); + + expect(pt.type).to.equal('fixedArray'); + expect(pt.subtype.type).to.equal('bytes'); + expect(pt.length).to.equal(8); + }); + + it('creates fixed arrays of fixed arrays', () => { + const pt = toParamType('bytes[45][3]'); + + expect(pt.type).to.equal('fixedArray'); + expect(pt.length).to.equal(3); + expect(pt.subtype.type).to.equal('fixedArray'); + expect(pt.subtype.length).to.equal(45); + expect(pt.subtype.subtype.type).to.equal('bytes'); + }); + }); + + describe('dynamic arrays', () => { + it('creates a dynamic array', () => { + const pt = toParamType('bytes[]'); + + expect(pt.type).to.equal('array'); + expect(pt.subtype.type).to.equal('bytes'); + }); + + it('creates a dynamic array of dynamic arrays', () => { + const pt = toParamType('bool[][]'); + + expect(pt.type).to.equal('array'); + expect(pt.subtype.type).to.equal('array'); + expect(pt.subtype.subtype.type).to.equal('bool'); + }); + }); + + describe('mixed arrays', () => { + it('creates a fixed dynamic array', () => { + const pt = toParamType('bool[][3]'); + + expect(pt.type).to.equal('fixedArray'); + expect(pt.length).to.equal(3); + expect(pt.subtype.type).to.equal('array'); + expect(pt.subtype.subtype.type).to.equal('bool'); + }); + + it('creates a dynamic fixed array', () => { + const pt = toParamType('bool[3][]'); + + expect(pt.type).to.equal('array'); + expect(pt.subtype.type).to.equal('fixedArray'); + expect(pt.subtype.length).to.equal(3); + expect(pt.subtype.subtype.type).to.equal('bool'); + }); + }); + }); + }); +}); diff --git a/js/src/abi/spec/paramType/index.js b/js/src/abi/spec/paramType/index.js new file mode 100644 index 0000000000000000000000000000000000000000..123e198e8ea99cb0fe915794b3199dd135d2b2df --- /dev/null +++ b/js/src/abi/spec/paramType/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 './paramType'; diff --git a/js/src/abi/spec/paramType/paramType.js b/js/src/abi/spec/paramType/paramType.js new file mode 100644 index 0000000000000000000000000000000000000000..b65465d94edd8171c57a989bfc178797ea0aad5f --- /dev/null +++ b/js/src/abi/spec/paramType/paramType.js @@ -0,0 +1,52 @@ +// 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 TYPES from './types'; + +export default class ParamType { + constructor (type, subtype = null, length = 0, indexed = false) { + ParamType.validateType(type); + + this._type = type; + this._subtype = subtype; + this._length = length; + this._indexed = indexed; + } + + get type () { + return this._type; + } + + get subtype () { + return this._subtype; + } + + get length () { + return this._length; + } + + get indexed () { + return this._indexed; + } + + static validateType (type) { + if (TYPES.filter((_type) => type === _type).length) { + return true; + } + + throw new Error(`Invalid type ${type} received for ParamType`); + } +} diff --git a/js/src/abi/spec/paramType/paramType.spec.js b/js/src/abi/spec/paramType/paramType.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..dc71147211441a5991c9d3abe2cde6490bb72422 --- /dev/null +++ b/js/src/abi/spec/paramType/paramType.spec.js @@ -0,0 +1,87 @@ +// 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 ParamType from './paramType'; + +describe('abi/spec/paramType/ParamType', () => { + describe('validateType', () => { + it('validates address', () => { + expect(ParamType.validateType('address')).to.be.true; + }); + + it('validates fixedArray', () => { + expect(ParamType.validateType('fixedArray')).to.be.true; + }); + + it('validates array', () => { + expect(ParamType.validateType('array')).to.be.true; + }); + + it('validates fixedBytes', () => { + expect(ParamType.validateType('fixedBytes')).to.be.true; + }); + + it('validates bytes', () => { + expect(ParamType.validateType('bytes')).to.be.true; + }); + + it('validates bool', () => { + expect(ParamType.validateType('bool')).to.be.true; + }); + + it('validates int', () => { + expect(ParamType.validateType('int')).to.be.true; + }); + + it('validates uint', () => { + expect(ParamType.validateType('uint')).to.be.true; + }); + + it('validates string', () => { + expect(ParamType.validateType('string')).to.be.true; + }); + + it('throws an error on invalid types', () => { + expect(() => ParamType.validateType('noMatch')).to.throw(/noMatch/); + }); + }); + + describe('constructor', () => { + it('throws an error on invalid types', () => { + expect(() => new ParamType('noMatch')).to.throw(/noMatch/); + }); + + it('sets the type of the object', () => { + expect((new ParamType('bool', null, 1)).type).to.equal('bool'); + }); + + it('sets the subtype of the object', () => { + expect((new ParamType('array', 'bool', 1)).subtype).to.equal('bool'); + }); + + it('sets the length of the object', () => { + expect((new ParamType('array', 'bool', 1)).length).to.equal(1); + }); + + it('sets the index of the object', () => { + expect((new ParamType('array', 'bool', 1, true)).indexed).to.be.true; + }); + + it('sets default values where none supplied', () => { + expect(Object.values(new ParamType('string'))).to.deep.equal(['string', null, 0, false]); + }); + }); +}); diff --git a/js/src/abi/spec/paramType/types.js b/js/src/abi/spec/paramType/types.js new file mode 100644 index 0000000000000000000000000000000000000000..e9749c5886fbb6787774b12c2af09482ca065944 --- /dev/null +++ b/js/src/abi/spec/paramType/types.js @@ -0,0 +1,19 @@ +// 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 . + +const TYPES = ['address', 'bytes', 'int', 'uint', 'bool', 'string', 'array', 'fixedBytes', 'fixedArray']; + +export default TYPES; diff --git a/js/src/abi/token/index.js b/js/src/abi/token/index.js new file mode 100644 index 0000000000000000000000000000000000000000..6a636c3c89f4314c14616993035b021746b517c5 --- /dev/null +++ b/js/src/abi/token/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 './token'; diff --git a/js/src/abi/token/token.js b/js/src/abi/token/token.js new file mode 100644 index 0000000000000000000000000000000000000000..7242248ab29272744cba03f25a812b8a67046875 --- /dev/null +++ b/js/src/abi/token/token.js @@ -0,0 +1,42 @@ +// 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 TYPES from '../spec/paramType/types'; + +export default class Token { + constructor (type, value) { + Token.validateType(type); + + this._type = type; + this._value = value; + } + + get type () { + return this._type; + } + + get value () { + return this._value; + } + + static validateType (type) { + if (TYPES.filter((_type) => type === _type).length) { + return true; + } + + throw new Error(`Invalid type ${type} received for Token`); + } +} diff --git a/js/src/abi/token/token.spec.js b/js/src/abi/token/token.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..cf5fc51bf798a82da16b27a2e7ccace52c809f7c --- /dev/null +++ b/js/src/abi/token/token.spec.js @@ -0,0 +1,75 @@ +// 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 Token from './token'; + +describe('abi/token/token', () => { + describe('validateType', () => { + it('validates address', () => { + expect(Token.validateType('address')).to.be.true; + }); + + it('validates fixedArray', () => { + expect(Token.validateType('fixedArray')).to.be.true; + }); + + it('validates array', () => { + expect(Token.validateType('array')).to.be.true; + }); + + it('validates fixedBytes', () => { + expect(Token.validateType('fixedBytes')).to.be.true; + }); + + it('validates bytes', () => { + expect(Token.validateType('bytes')).to.be.true; + }); + + it('validates bool', () => { + expect(Token.validateType('bool')).to.be.true; + }); + + it('validates int', () => { + expect(Token.validateType('int')).to.be.true; + }); + + it('validates uint', () => { + expect(Token.validateType('uint')).to.be.true; + }); + + it('validates string', () => { + expect(Token.validateType('string')).to.be.true; + }); + + it('throws an error on invalid types', () => { + expect(() => Token.validateType('noMatch')).to.throw(/noMatch/); + }); + }); + + describe('constructor', () => { + it('throws an error on invalid types', () => { + expect(() => new Token('noMatch', '1')).to.throw(/noMatch/); + }); + + it('sets the type of the object', () => { + expect((new Token('bool', '1')).type).to.equal('bool'); + }); + + it('sets the value of the object', () => { + expect((new Token('bool', '1')).value).to.equal('1'); + }); + }); +}); diff --git a/js/src/abi/util/address.js b/js/src/abi/util/address.js new file mode 100644 index 0000000000000000000000000000000000000000..c0153ee5daab229aadc18f0e21fe2e315b8ab33f --- /dev/null +++ b/js/src/abi/util/address.js @@ -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 . + +import { keccak_256 } from 'js-sha3'; // eslint-disable-line camelcase + +export function isChecksumValid (_address) { + const address = _address.replace('0x', ''); + const hash = keccak_256(address.toLowerCase()); + + for (let n = 0; n < 40; n++) { + const char = address[n]; + const isLower = char !== char.toUpperCase(); + const isUpper = char !== char.toLowerCase(); + const hashval = parseInt(hash[n], 16); + + if ((hashval > 7 && isLower) || (hashval <= 7 && isUpper)) { + return false; + } + } + + return true; +} + +export function isAddress (address) { + if (address && address.length === 42) { + if (!/^(0x)?[0-9a-f]{40}$/i.test(address)) { + return false; + } else if (/^(0x)?[0-9a-f]{40}$/.test(address) || /^(0x)?[0-9A-F]{40}$/.test(address)) { + return true; + } + + return isChecksumValid(address); + } + + return false; +} + +export function toChecksumAddress (_address) { + const address = (_address || '').toLowerCase(); + + if (!isAddress(address)) { + return ''; + } + + const hash = keccak_256(address.slice(-40)); + let result = '0x'; + + for (let n = 0; n < 40; n++) { + result = `${result}${parseInt(hash[n], 16) > 7 ? address[n + 2].toUpperCase() : address[n + 2]}`; + } + + return result; +} diff --git a/js/src/abi/util/address.spec.js b/js/src/abi/util/address.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..324ba163da9aa4a6d51d4263e9d6969a8e5b2b00 --- /dev/null +++ b/js/src/abi/util/address.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 { isChecksumValid, isAddress, toChecksumAddress } from './address'; + +describe('abi/util/address', () => { + const value = '63Cf90D3f0410092FC0fca41846f596223979195'; + const address = `0x${value}`; + const lowercase = `0x${value.toLowerCase()}`; + const uppercase = `0x${value.toUpperCase()}`; + const invalid = '0x' + value.split('').map((char) => { + if (char >= 'a' && char <= 'f') { + return char.toUpperCase(); + } else if (char >= 'A' && char <= 'F') { + return char.toLowerCase(); + } + + return char; + }).join(''); + const invalidhex = '0x01234567890123456789012345678901234567gh'; + + describe('isChecksumValid', () => { + it('returns false when fully lowercase', () => { + expect(isChecksumValid(lowercase)).to.be.false; + }); + + it('returns false when fully uppercase', () => { + expect(isChecksumValid(uppercase)).to.be.false; + }); + + it('returns false on a mixed-case address', () => { + expect(isChecksumValid(invalid)).to.be.false; + }); + + it('returns true on a checksummed address', () => { + expect(isChecksumValid(address)).to.be.true; + }); + }); + + describe('isAddress', () => { + it('returns true when fully lowercase', () => { + expect(isAddress(lowercase)).to.be.true; + }); + + it('returns true when fully uppercase', () => { + expect(isAddress(uppercase)).to.be.true; + }); + + it('returns true when checksummed', () => { + expect(isAddress(address)).to.be.true; + }); + + it('returns false when invalid checksum', () => { + expect(isAddress(invalid)).to.be.false; + }); + + it('returns false on valid length, non-hex', () => { + expect(isAddress(invalidhex)).to.be.false; + }); + }); + + describe('toChecksumAddress', () => { + it('returns empty when no address specified', () => { + expect(toChecksumAddress()).to.equal(''); + }); + + it('returns empty on invalid address structure', () => { + expect(toChecksumAddress('0xnotaddress')).to.equal(''); + }); + + it('returns formatted address on checksum input', () => { + expect(toChecksumAddress(address)).to.equal(address); + }); + + it('returns formatted address on lowercase input', () => { + expect(toChecksumAddress(lowercase)).to.equal(address); + }); + + it('returns formatted address on uppercase input', () => { + expect(toChecksumAddress(uppercase)).to.equal(address); + }); + + it('returns formatted address on mixed input', () => { + expect(toChecksumAddress(invalid)).to.equal(address); + }); + }); +}); diff --git a/js/src/abi/util/pad.js b/js/src/abi/util/pad.js new file mode 100644 index 0000000000000000000000000000000000000000..476a27445e2e3db3a1e0613e4c00a84470b9c2e6 --- /dev/null +++ b/js/src/abi/util/pad.js @@ -0,0 +1,77 @@ +// 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 BigNumber from 'bignumber.js'; +import utf8 from 'utf8'; + +import { isArray } from './types'; + +const ZERO_64 = '0000000000000000000000000000000000000000000000000000000000000000'; + +export function padAddress (_input) { + const input = _input.substr(0, 2) === '0x' ? _input.substr(2) : _input; + + return `${ZERO_64}${input}`.slice(-64); +} + +export function padBool (input) { + return `${ZERO_64}${input ? '1' : '0'}`.slice(-64); +} + +export function padU32 (input) { + let bn = new BigNumber(input); + + if (bn.lessThan(0)) { + bn = new BigNumber('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 16) + .plus(bn).plus(1); + } + + return `${ZERO_64}${bn.toString(16)}`.slice(-64); +} + +function stringToBytes (input) { + if (isArray(input)) { + return input; + } else if (input.substr(0, 2) === '0x') { + const matches = input.substr(2).toLowerCase().match(/.{1,2}/g) || []; + + return matches.map((value) => parseInt(value, 16)); + } else { + return input.split('').map((char) => char.charCodeAt(0)); + } +} + +export function padBytes (_input) { + const input = stringToBytes(_input); + + return `${padU32(input.length)}${padFixedBytes(input)}`; +} + +export function padFixedBytes (_input) { + const input = stringToBytes(_input); + const sinput = input.map((code) => `0${code.toString(16)}`.slice(-2)).join(''); + const max = Math.floor((sinput.length + 63) / 64) * 64; + + return `${sinput}${ZERO_64}`.substr(0, max); +} + +export function padString (input) { + const array = utf8.encode(input) + .split('') + .map((char) => char.charCodeAt(0)); + + return padBytes(array); +} diff --git a/js/src/abi/util/pad.spec.js b/js/src/abi/util/pad.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..74463b438c1ea0801bf42fe79aca2a44148ead6f --- /dev/null +++ b/js/src/abi/util/pad.spec.js @@ -0,0 +1,124 @@ +// 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 BigNumber from 'bignumber.js'; +import { padAddress, padBool, padBytes, padFixedBytes, padString, padU32 } from './pad'; + +describe('abi/util/pad', () => { + const SHORT15 = '1234567890abcdef'; + const BYTES15 = [0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef]; + const LONG15 = `${SHORT15}000000000000000000000000000000000000000000000000`; + const PAD123 = '0000000000000000000000000000000000000000000000000000000000000123'; + + describe('padAddress', () => { + it('pads to 64 characters', () => { + expect(padAddress('123')).to.equal(PAD123); + }); + + it('strips leading 0x when passed in', () => { + expect(padFixedBytes(`0x${PAD123}`)).to.equal(PAD123); + }); + }); + + describe('padBool', () => { + const TRUE = '0000000000000000000000000000000000000000000000000000000000000001'; + const FALSE = '0000000000000000000000000000000000000000000000000000000000000000'; + + it('pads true to 64 characters', () => { + expect(padBool(true)).to.equal(TRUE); + }); + + it('pads false to 64 characters', () => { + expect(padBool(false)).to.equal(FALSE); + }); + }); + + describe('padU32', () => { + it('left pads length < 64 bytes to 64 bytes', () => { + expect(padU32(1)).to.equal('0000000000000000000000000000000000000000000000000000000000000001'); + }); + + it('pads hex representation', () => { + expect(padU32(0x123)).to.equal(PAD123); + }); + + it('pads decimal representation', () => { + expect(padU32(291)).to.equal(PAD123); + }); + + it('pads string representation', () => { + expect(padU32('0x123')).to.equal(PAD123); + }); + + it('pads BigNumber representation', () => { + expect(padU32(new BigNumber(0x123))).to.equal(PAD123); + }); + + it('converts negative numbers to 2s complement', () => { + expect(padU32(-123)).to.equal('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff85'); + }); + }); + + describe('padFixedBytes', () => { + it('right pads length < 64 bytes to 64 bytes (string)', () => { + expect(padFixedBytes(`0x${SHORT15}`)).to.equal(LONG15); + }); + + it('right pads length < 64 bytes to 64 bytes (array)', () => { + expect(padFixedBytes(BYTES15)).to.equal(LONG15); + }); + + it('right pads length > 64 bytes (64 byte multiples)', () => { + expect(padFixedBytes(`0x${LONG15}${SHORT15}`)).to.equal(`${LONG15}${LONG15}`); + }); + + it('strips leading 0x when passed in', () => { + expect(padFixedBytes(`0x${SHORT15}`)).to.equal(LONG15); + }); + }); + + describe('padBytes', () => { + it('right pads length < 64, adding the length (string)', () => { + const result = padBytes(`0x${SHORT15}`); + + expect(result.length).to.equal(128); + expect(result).to.equal(`${padU32(8)}${LONG15}`); + }); + + it('right pads length < 64, adding the length (array)', () => { + const result = padBytes(BYTES15); + + expect(result.length).to.equal(128); + expect(result).to.equal(`${padU32(8)}${LONG15}`); + }); + + it('right pads length > 64, adding the length', () => { + const result = padBytes(`0x${LONG15}${SHORT15}`); + + expect(result.length).to.equal(192); + expect(result).to.equal(`${padU32(0x28)}${LONG15}${LONG15}`); + }); + }); + + describe('padString', () => { + it('correctly converts & pads strings', () => { + const result = padString('gavofyork'); + + expect(result.length).to.equal(128); + expect(result).to.equal(padBytes('0x6761766f66796f726b')); + }); + }); +}); diff --git a/js/src/abi/util/signature.js b/js/src/abi/util/signature.js new file mode 100644 index 0000000000000000000000000000000000000000..86ed6f2657fe4adbaae471099ba53db97408e946 --- /dev/null +++ b/js/src/abi/util/signature.js @@ -0,0 +1,49 @@ +// 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 { keccak_256 } from 'js-sha3'; // eslint-disable-line camelcase +import { fromParamType } from '../spec/paramType/format'; + +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 }; +} + +export function methodSignature (methodName, params) { + const { id, name, signature } = eventSignature(methodName, params); + + return { id, name, signature: signature.substr(0, 8) }; +} + +function parseName (name) { + const strName = `${name || ''}`; + const idx = strName.indexOf('('); + + if (idx === -1) { + return { strName, name }; + } + + const trimmedName = strName.slice(0, idx); + + return { + strName: trimmedName, + name: trimmedName + }; +} diff --git a/js/src/abi/util/signature.spec.js b/js/src/abi/util/signature.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..118ebf4e510faf77b7a2a30a99b8cba8f524c9fb --- /dev/null +++ b/js/src/abi/util/signature.spec.js @@ -0,0 +1,111 @@ +// 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 { eventSignature, methodSignature } from './signature'; + +describe('abi/util/signature', () => { + describe('eventSignature', () => { + it('encodes signature baz() correctly', () => { + expect(eventSignature('baz', [])).to.deep.equal({ + id: 'baz()', + name: 'baz', + signature: 'a7916fac4f538170f7cd12c148552e2cba9fcd72329a2dd5b07a6fa906488ddf' + }); + }); + + it('encodes signature baz(uint32) correctly', () => { + expect(eventSignature('baz', [{ type: 'uint', length: 32 }])).to.deep.equal({ + id: 'baz(uint32)', + name: 'baz', + signature: '7d68785e8fc871be024b75964bd86d093511d4bc2dc7cf7bea32c48a0efaecb1' + }); + }); + + it('encodes signature baz(uint32, bool) correctly', () => { + expect(eventSignature('baz', [{ type: 'uint', length: 32 }, { type: 'bool' }])).to.deep.equal({ + id: 'baz(uint32,bool)', + name: 'baz', + signature: 'cdcd77c0992ec5bbfc459984220f8c45084cc24d9b6efed1fae540db8de801d2' + }); + }); + + it('encodes no-name signature correctly as ()', () => { + expect(eventSignature(undefined, [])).to.deep.equal({ + id: '()', + name: undefined, + signature: '' + }); + }); + + it('encodes no-params signature correctly as ()', () => { + expect(eventSignature(undefined, undefined)).to.deep.equal({ + id: '()', + name: undefined, + signature: '' + }); + }); + }); + + describe('methodSignature', () => { + it('encodes signature baz() correctly', () => { + expect(methodSignature('baz', [])).to.deep.equal({ + id: 'baz()', + name: 'baz', + signature: 'a7916fac' + }); + }); + + it('encodes signature baz(uint32) correctly', () => { + expect(methodSignature('baz', [{ type: 'uint', length: 32 }])).to.deep.equal({ + id: 'baz(uint32)', + name: 'baz', + signature: '7d68785e' + }); + }); + + it('encodes signature baz(uint32, bool) correctly', () => { + expect(methodSignature('baz', [{ type: 'uint', length: 32 }, { type: 'bool' }])).to.deep.equal({ + id: 'baz(uint32,bool)', + name: 'baz', + signature: 'cdcd77c0' + }); + }); + + it('encodes signature in name correctly', () => { + expect(methodSignature('baz(uint32,bool)', [{ type: 'uint', length: 32 }, { type: 'bool' }])).to.deep.equal({ + id: 'baz(uint32,bool)', + name: 'baz', + signature: 'cdcd77c0' + }); + }); + + it('encodes no-name signature correctly as ()', () => { + expect(methodSignature(undefined, [])).to.deep.equal({ + id: '()', + name: undefined, + signature: '' + }); + }); + + it('encodes no-params signature correctly as ()', () => { + expect(methodSignature(undefined, undefined)).to.deep.equal({ + id: '()', + name: undefined, + signature: '' + }); + }); + }); +}); diff --git a/js/src/abi/util/slice.js b/js/src/abi/util/slice.js new file mode 100644 index 0000000000000000000000000000000000000000..daab54f1ce2d569bef6a60cdcbfb9fd1659172d6 --- /dev/null +++ b/js/src/abi/util/slice.js @@ -0,0 +1,31 @@ +// 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 { padAddress } from './pad'; + +export function sliceData (_data) { + if (!_data || !_data.length) { + return []; + } + + let data = (_data.substr(0, 2) === '0x') ? _data.substr(2) : _data; + + if (!data.length) { + data = padAddress(''); + } + + return data.match(/.{1,64}/g); +} diff --git a/js/src/abi/util/slice.spec.js b/js/src/abi/util/slice.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..1b754ccbb22fd88f7c244efd1bd4dcb7273c017d --- /dev/null +++ b/js/src/abi/util/slice.spec.js @@ -0,0 +1,44 @@ +// 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 { sliceData } from './slice'; + +describe('abi/util/slice', () => { + describe('sliceData', () => { + const slice1 = '131a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b'; + const slice2 = '2124768576358735263578356373526387638357635873563586353756358763'; + + it('returns an empty array when length === 0', () => { + expect(sliceData('')).to.deep.equal([]); + }); + + it('returns an array with the slices otherwise', () => { + const sliced = sliceData(`${slice1}${slice2}`); + + expect(sliced.length).to.equal(2); + expect(sliced[0]).to.equal(slice1); + expect(sliced[1]).to.equal(slice2); + }); + + it('removes leading 0x when passed in', () => { + const sliced = sliceData(`0x${slice1}${slice2}`); + + expect(sliced.length).to.equal(2); + expect(sliced[0]).to.equal(slice1); + expect(sliced[1]).to.equal(slice2); + }); + }); +}); diff --git a/js/src/abi/util/sliceAs.js b/js/src/abi/util/sliceAs.js new file mode 100644 index 0000000000000000000000000000000000000000..e177897a197f34f31dcc9c74e36a0f4e9254a335 --- /dev/null +++ b/js/src/abi/util/sliceAs.js @@ -0,0 +1,47 @@ +// 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 BigNumber from 'bignumber.js'; + +import { toChecksumAddress } from './address'; + +export function asU32 (slice) { + // TODO: validation + + return new BigNumber(slice, 16); +} + +export function asI32 (slice) { + if (new BigNumber(slice.substr(0, 1), 16).toString(2)[0] === '1') { + return new BigNumber(slice, 16) + .minus(new BigNumber('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 16)) + .minus(1); + } + + return new BigNumber(slice, 16); +} + +export function asAddress (slice) { + // TODO: address validation? + + return toChecksumAddress(`0x${slice.slice(-40)}`); +} + +export function asBool (slice) { + // TODO: everything else should be 0 + + return new BigNumber(slice[63]).eq(1); +} diff --git a/js/src/abi/util/sliceAs.spec.js b/js/src/abi/util/sliceAs.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..c29e345547bbc1684ae7e38eb37616352e1b0316 --- /dev/null +++ b/js/src/abi/util/sliceAs.spec.js @@ -0,0 +1,55 @@ +// 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 { asAddress, asBool, asI32, asU32 } from './sliceAs'; + +describe('abi/util/sliceAs', () => { + const MAX_INT = 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'; + + describe('asAddress', () => { + it('correctly returns the last 0x40 characters', () => { + const address = '1111111111222222222233333333334444444444'; + + expect(asAddress(`000000000000000000000000${address}`)).to.equal(`0x${address}`); + }); + }); + + describe('asBool', () => { + it('correctly returns true', () => { + expect(asBool('0000000000000000000000000000000000000000000000000000000000000001')).to.be.true; + }); + + it('correctly returns false', () => { + expect(asBool('0000000000000000000000000000000000000000000000000000000000000000')).to.be.false; + }); + }); + + describe('asI32', () => { + it('correctly decodes positive numbers', () => { + expect(asI32('000000000000000000000000000000000000000000000000000000000000007b').toString()).to.equal('123'); + }); + + it('correctly decodes negative numbers', () => { + expect(asI32('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff85').toString()).to.equal('-123'); + }); + }); + + describe('asU32', () => { + it('returns a maxium U32', () => { + expect(asU32(MAX_INT).toString(16)).to.equal(MAX_INT); + }); + }); +}); diff --git a/js/src/abi/util/types.js b/js/src/abi/util/types.js new file mode 100644 index 0000000000000000000000000000000000000000..dd408f3716fbc2e5bd065c2afcdbe94d07a6ae85 --- /dev/null +++ b/js/src/abi/util/types.js @@ -0,0 +1,27 @@ +// 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 function isArray (test) { + return Object.prototype.toString.call(test) === '[object Array]'; +} + +export function isString (test) { + return Object.prototype.toString.call(test) === '[object String]'; +} + +export function isInstanceOf (test, clazz) { + return test instanceof clazz; +} diff --git a/js/src/abi/util/types.spec.js b/js/src/abi/util/types.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..177662ae271479390e5309ed5a9dbd542c6399a8 --- /dev/null +++ b/js/src/abi/util/types.spec.js @@ -0,0 +1,62 @@ +// 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 { isArray, isString, isInstanceOf } from './types'; +import Token from '../token'; + +describe('abi/util/types', () => { + describe('isArray', () => { + it('correctly identifies empty arrays as Array', () => { + expect(isArray([])).to.be.true; + }); + + it('correctly identifies non-empty arrays as Array', () => { + expect(isArray([1, 2, 3])).to.be.true; + }); + + it('correctly identifies strings as non-Array', () => { + expect(isArray('not an array')).to.be.false; + }); + + it('correctly identifies objects as non-Array', () => { + expect(isArray({})).to.be.false; + }); + }); + + describe('isString', () => { + it('correctly identifies empty string as string', () => { + expect(isString('')).to.be.true; + }); + + it('correctly identifies string as string', () => { + expect(isString('123')).to.be.true; + }); + }); + + describe('isInstanceOf', () => { + it('correctly identifies build-in instanceof', () => { + expect(isInstanceOf(new String('123'), String)).to.be.true; // eslint-disable-line no-new-wrappers + }); + + it('correctly identifies own instanceof', () => { + expect(isInstanceOf(new Token('int', 123), Token)).to.be.true; + }); + + it('correctly reports false for own', () => { + expect(isInstanceOf({ type: 'int' }, Token)).to.be.false; + }); + }); +}); diff --git a/js/src/api/README.md b/js/src/api/README.md new file mode 100644 index 0000000000000000000000000000000000000000..1a2051eaf499ace119526ca218df7266d2e6f9c2 --- /dev/null +++ b/js/src/api/README.md @@ -0,0 +1,146 @@ +# ethapi-js + +A thin, fast, low-level Promise-based wrapper around the Ethereum APIs. + +[![Build Status](https://travis-ci.org/jacogr/ethapi-js.svg?branch=master)](https://travis-ci.org/jacogr/ethapi-js) +[![Coverage Status](https://coveralls.io/repos/github/jacogr/ethapi-js/badge.svg?branch=master)](https://coveralls.io/github/jacogr/ethapi-js?branch=master) +[![Dependency Status](https://david-dm.org/jacogr/ethapi-js.svg)](https://david-dm.org/jacogr/ethapi-js) +[![devDependency Status](https://david-dm.org/jacogr/ethapi-js/dev-status.svg)](https://david-dm.org/jacogr/ethapi-js#info=devDependencies) + +## contributing + +Clone the repo and install dependencies via `npm install`. Tests can be executed via + +- `npm run testOnce` (100% covered unit tests) +- `npm run testE2E` (E2E against a running RPC-enabled testnet Parity/Geth instance, `parity --testnet` and for WebScokets, `geth --testnet --ws --wsorigins '*' --rpc`) +- setting the environment `DEBUG=true` will display the RPC POST bodies and responses on E2E tests + +## installation + +Install the package with `npm install --save ethapi-js` from the [npm registry ethapi-js](https://www.npmjs.com/package/ethapi-js) + +## usage + +### initialisation + +```javascript +// import the actual EthApi class +import EthApi from 'ethapi-js'; + +// do the setup +const transport = new EthApi.Transport.Http('http://localhost:8545'); // or .Ws('ws://localhost:8546') +const ethapi = new EthApi(transport); +``` + +You will require native Promises and fetch support (latest browsers only), they can be utilised by + +```javascript +import 'isomorphic-fetch'; + +import es6Promise from 'es6-promise'; +es6Promise.polyfill(); +``` + +### making calls + +perform a call + +```javascript +ethapi.eth + .coinbase() + .then((coinbase) => { + console.log(`The coinbase is ${coinbase}`); + }); +``` + +multiple promises + +```javascript +Promise + .all([ + ethapi.eth.coinbase(), + ethapi.net.listening() + ]) + .then(([coinbase, listening]) => { + // do stuff here + }); +``` + +chaining promises + +```javascript +ethapi.eth + .newFilter({...}) + .then((filterId) => ethapi.eth.getFilterChanges(filterId)) + .then((changes) => { + console.log(changes); + }); +``` + +### contracts + +attach contract + +```javascript +const abi = [{ name: 'callMe', inputs: [{ type: 'bool', ...}, { type: 'string', ...}]}, ...abi...]; +const contract = new ethapi.newContract(abi); +``` + +deploy + +```javascript +contract + .deploy('0xc0de', [params], 'superPassword') + .then((address) => { + console.log(`the contract was deployed at ${address}`); + }); +``` + +attach a contract at address + +```javascript +// via the constructor & .at function +const contract = api.newContract(abi).at('0xa9280...7347b'); +// or on an already initialised contract +contract.at('0xa9280...7347b'); +// perform calls here +``` + +find & call a function + +```javascript +contract.instance + .myContractMethodName + .call({}, [myContractMethodParameter]) // or estimateGas or sendTransaction + .then((result) => { + console.log(`the result was ${result}`); + }); +``` + +parse events from transaction receipt + +```javascript +contract + .parseTransactionEvents(txReceipt) + .then((receipt) => { + receipt.logs.forEach((log) => { + console.log('log parameters', log.params); + }); + }); +``` + +## apis + +APIs implement the calls as exposed in the [Ethcore JSON Ethereum RPC](https://github.com/paritytech/ethereum-rpc-json/) definitions. Mapping follows the naming conventions of the originals, i.e. `eth_call` becomes `eth.call`, `personal_accounts` becomes `personal.accounts`, etc. + +- [ethapi.db](https://github.com/paritytech/ethereum-rpc-json/blob/master/interfaces.md#db) +- [ethapi.eth](https://github.com/paritytech/ethereum-rpc-json/blob/master/interfaces.md#eth) +- [ethapi.parity](https://github.com/paritytech/ethereum-rpc-json/blob/master/interfaces.md#parity) +- [ethapi.net](https://github.com/paritytech/ethereum-rpc-json/blob/master/interfaces.md#net) +- [ethapi.personal](https://github.com/paritytech/ethereum-rpc-json/blob/master/interfaces.md#personal) +- [ethapi.shh](https://github.com/paritytech/ethereum-rpc-json/blob/master/interfaces.md#shh) +- [ethapi.signer](https://github.com/paritytech/ethereum-rpc-json/blob/master/interfaces.md#signer) +- [ethapi.trace](https://github.com/paritytech/ethereum-rpc-json/blob/master/interfaces.md#trace) +- [ethapi.web3](https://github.com/paritytech/ethereum-rpc-json/blob/master/interfaces.md#web3) + +As a verification step, all exposed interfaces are tested for existing and pointing to the correct endpoints by using the generated interfaces from the above repo. diff --git a/js/src/api/api.js b/js/src/api/api.js new file mode 100644 index 0000000000000000000000000000000000000000..a1a7dbbb5d6d82705eeb0515d5a9c381215e4255 --- /dev/null +++ b/js/src/api/api.js @@ -0,0 +1,184 @@ +// 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 EventEmitter from 'eventemitter3'; + +import { Http, Ws } from './transport'; +import Contract from './contract'; + +import { Db, Eth, Parity, Net, Personal, Shh, Signer, Trace, Web3 } from './rpc'; +import Subscriptions from './subscriptions'; +import Pubsub from './pubsub'; +import util from './util'; +import { isFunction } from './util/types'; + +import LocalAccountsMiddleware from '~/api/local'; + +export default class Api extends EventEmitter { + constructor (transport, allowSubscriptions = true) { + super(); + + if (!transport || !isFunction(transport.execute)) { + throw new Error('EthApi needs transport with execute() function defined'); + } + + this._transport = transport; + + this._db = new Db(transport); + this._eth = new Eth(transport); + this._net = new Net(transport); + this._parity = new Parity(transport); + this._personal = new Personal(transport); + this._shh = new Shh(transport); + this._signer = new Signer(transport); + this._trace = new Trace(transport); + this._web3 = new Web3(transport); + + if (isFunction(transport.subscribe)) { + this._pubsub = new Pubsub(transport); + } + + if (allowSubscriptions) { + this._subscriptions = new Subscriptions(this); + } + // Doing a request here in test env would cause an error + if (LocalAccountsMiddleware && process.env.NODE_ENV !== 'test') { + const middleware = this.parity + .nodeKind() + .then((nodeKind) => { + if (nodeKind.availability === 'public') { + return LocalAccountsMiddleware; + } + + return null; + }) + .catch(() => null); + + transport.addMiddleware(middleware); + } + } + + get isPubSub () { + return !!this._pubsub; + } + + get pubsub () { + if (!this.isPubSub) { + throw Error('Pubsub is only available with a subscribing-supported transport injected!'); + } + + return this._pubsub; + } + + get db () { + return this._db; + } + + get eth () { + return this._eth; + } + + get parity () { + return this._parity; + } + + get net () { + return this._net; + } + + get personal () { + return this._personal; + } + + get shh () { + return this._shh; + } + + get signer () { + return this._signer; + } + + get trace () { + return this._trace; + } + + get transport () { + return this._transport; + } + + get web3 () { + return this._web3; + } + + get util () { + return util; + } + + newContract (abi, address) { + return new Contract(this, abi).at(address); + } + + subscribe (subscriptionName, callback) { + if (!this._subscriptions) { + return Promise.resolve(1); + } + + return this._subscriptions.subscribe(subscriptionName, callback); + } + + unsubscribe (subscriptionId) { + if (!this._subscriptions) { + return Promise.resolve(true); + } + + return this._subscriptions.unsubscribe(subscriptionId); + } + + pollMethod (method, input, validate) { + const [_group, endpoint] = method.split('_'); + const group = `_${_group}`; + + return new Promise((resolve, reject) => { + const timeout = () => { + this[group][endpoint](input) + .then((result) => { + if (validate ? validate(result) : result) { + resolve(result); + } else { + setTimeout(timeout, 500); + } + }) + .catch((error) => { + // Don't print if the request is rejected: that's ok + if (error.type !== 'REQUEST_REJECTED') { + console.error('pollMethod', error); + } + + reject(error); + }); + }; + + timeout(); + }); + } + + static util = util + + static Transport = { + Http: Http, + Ws: Ws + } +} diff --git a/js/src/api/api.spec.js b/js/src/api/api.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..c72b2284922cc5e0d069755bdbbd5847fd7328b9 --- /dev/null +++ b/js/src/api/api.spec.js @@ -0,0 +1,59 @@ +// 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 { TEST_HTTP_URL, endpointTest } from '../../test/mockRpc'; + +import util from './util'; +import Api from './api'; + +import ethereumRpc from '../jsonrpc/'; + +describe('api/Api', () => { + describe('constructor', () => { + it('requires defined/non-null transport object', () => { + expect(() => new Api()).to.throw(/Api needs transport/); + expect(() => new Api(null)).to.throw(/Api needs transport/); + }); + + it('requires an execute function on the transport object', () => { + expect(() => new Api({})).to.throw(/Api needs transport/); + expect(() => new Api({ execute: true })).to.throw(/Api needs transport/); + }); + }); + + describe('interface', () => { + const api = new Api(new Api.Transport.Http(TEST_HTTP_URL, -1)); + const ignored = [ + 'eth_subscribe', 'eth_unsubscribe', + 'parity_subscribe', 'parity_unsubscribe', + 'signer_subscribePending', 'signer_unsubscribePending' + ]; + + Object.keys(ethereumRpc).sort().forEach((endpoint) => { + describe(endpoint, () => { + Object.keys(ethereumRpc[endpoint]).sort() + .filter(method => ignored.indexOf(method) !== -1) + .forEach((method) => { + endpointTest(api, endpoint, method); + }); + }); + }); + }); + + it('exposes util as static property', () => { + expect(Api.util).to.equal(util); + }); +}); diff --git a/js/src/api/contract/contract.js b/js/src/api/contract/contract.js new file mode 100644 index 0000000000000000000000000000000000000000..dee4d6d777b6405ead63261d3db8a24bf8368c14 --- /dev/null +++ b/js/src/api/contract/contract.js @@ -0,0 +1,561 @@ +// 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 Abi from '~/abi'; + +let nextSubscriptionId = 0; + +export default class Contract { + constructor (api, abi) { + if (!api) { + throw new Error('API instance needs to be provided to Contract'); + } + + if (!abi) { + throw new Error('ABI needs to be provided to Contract instance'); + } + + this._api = api; + this._abi = new Abi(abi); + + this._subscriptions = {}; + this._constructors = this._abi.constructors.map(this._bindFunction); + this._functions = this._abi.functions.map(this._bindFunction); + this._events = this._abi.events.map(this._bindEvent); + + this._instance = {}; + + this._events.forEach((evt) => { + this._instance[evt.name] = evt; + this._instance[evt.signature] = evt; + }); + + this._functions.forEach((fn) => { + this._instance[fn.name] = fn; + this._instance[fn.signature] = fn; + }); + + this._subscribedToPendings = false; + this._pendingsSubscriptionId = null; + + this._subscribedToBlock = false; + this._blockSubscriptionId = null; + + if (api && api.patch && api.patch.contract) { + api.patch.contract(this); + } + } + + get address () { + return this._address; + } + + get constructors () { + return this._constructors; + } + + get events () { + return this._events; + } + + get functions () { + return this._functions; + } + + get receipt () { + return this._receipt; + } + + get instance () { + this._instance.address = this._address; + return this._instance; + } + + get api () { + return this._api; + } + + get abi () { + return this._abi; + } + + at (address) { + this._address = address; + return this; + } + + deployEstimateGas (options, values) { + const _options = this._encodeOptions(this.constructors[0], options, values); + + return this._api.eth + .estimateGas(_options) + .then((gasEst) => { + return [gasEst, gasEst.mul(1.2)]; + }); + } + + deploy (options, values, statecb = () => {}, skipGasEstimate = false) { + let gasEstPromise; + + if (skipGasEstimate) { + gasEstPromise = Promise.resolve(null); + } else { + statecb(null, { state: 'estimateGas' }); + + gasEstPromise = this.deployEstimateGas(options, values) + .then(([gasEst, gas]) => gas); + } + + return gasEstPromise + .then((_gas) => { + if (_gas) { + options.gas = _gas.toFixed(0); + } + + const gas = _gas || options.gas; + + statecb(null, { state: 'postTransaction', gas }); + + const encodedOptions = this._encodeOptions(this.constructors[0], options, values); + + return this._api.parity + .postTransaction(encodedOptions) + .then((requestId) => { + statecb(null, { state: 'checkRequest', requestId }); + return this._pollCheckRequest(requestId); + }) + .then((txhash) => { + statecb(null, { state: 'getTransactionReceipt', txhash }); + return this._pollTransactionReceipt(txhash, gas); + }) + .then((receipt) => { + if (receipt.gasUsed.eq(gas)) { + throw new Error(`Contract not deployed, gasUsed == ${gas.toFixed(0)}`); + } + + statecb(null, { state: 'hasReceipt', receipt }); + this._receipt = receipt; + this._address = receipt.contractAddress; + return this._address; + }) + .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'); + } + + statecb(null, { state: 'completed' }); + return this._address; + }); + }); + } + + parseEventLogs (logs) { + return logs + .map((log) => { + const signature = log.topics[0].substr(2); + const event = this.events.find((evt) => evt.signature === signature); + + if (!event) { + console.warn(`Unable to find event matching signature ${signature}`); + return null; + } + + try { + const decoded = event.decodeLog(log.topics, log.data); + + log.params = {}; + log.event = event.name; + + decoded.params.forEach((param, index) => { + const { type, value } = param.token; + const key = param.name || index; + + log.params[key] = { type, value }; + }); + + return log; + } catch (error) { + console.warn('Error decoding log', log); + console.warn(error); + + return null; + } + }) + .filter((log) => log); + } + + parseTransactionEvents (receipt) { + receipt.logs = this.parseEventLogs(receipt.logs); + + return receipt; + } + + _pollCheckRequest = (requestId) => { + return this._api.pollMethod('parity_checkRequest', requestId); + } + + _pollTransactionReceipt = (txhash, gas) => { + return this.api.pollMethod('eth_getTransactionReceipt', txhash, (receipt) => { + if (!receipt || !receipt.blockNumber || receipt.blockNumber.eq(0)) { + return false; + } + + return true; + }); + } + + getCallData = (func, options, values) => { + let data = options.data; + + const tokens = func ? Abi.encodeTokens(func.inputParamTypes(), values) : null; + const call = tokens ? func.encodeCall(tokens) : null; + + if (data && data.substr(0, 2) === '0x') { + data = data.substr(2); + } + + return `0x${data || ''}${call || ''}`; + } + + _encodeOptions (func, options, values) { + const data = this.getCallData(func, options, values); + + return { + ...options, + data + }; + } + + _addOptionsTo (options = {}) { + return { + to: this._address, + ...options + }; + } + + _bindFunction = (func) => { + func.contract = this; + + func.call = (_options = {}, values = []) => { + const rawTokens = !!_options.rawTokens; + const options = { + ..._options + }; + + delete options.rawTokens; + + let callParams; + + try { + callParams = this._encodeOptions(func, this._addOptionsTo(options), values); + } catch (error) { + return Promise.reject(error); + } + + return this._api.eth + .call(callParams) + .then((encoded) => func.decodeOutput(encoded)) + .then((tokens) => { + if (rawTokens) { + return tokens; + } + + return tokens.map((token) => token.value); + }) + .then((returns) => returns.length === 1 ? returns[0] : returns) + .catch((error) => { + console.warn(`${func.name}.call`, values, error); + throw error; + }); + }; + + if (!func.constant) { + func.postTransaction = (options, values = []) => { + let _options; + + try { + _options = this._encodeOptions(func, this._addOptionsTo(options), values); + } catch (error) { + return Promise.reject(error); + } + + return this._api.parity + .postTransaction(_options) + .catch((error) => { + console.warn(`${func.name}.postTransaction`, values, error); + throw error; + }); + }; + + func.estimateGas = (options, values = []) => { + const _options = this._encodeOptions(func, this._addOptionsTo(options), values); + + return this._api.eth + .estimateGas(_options) + .catch((error) => { + console.warn(`${func.name}.estimateGas`, values, error); + throw error; + }); + }; + } + + return func; + } + + _bindEvent = (event) => { + event.subscribe = (options = {}, callback, autoRemove) => { + return this._subscribe(event, options, callback, autoRemove); + }; + + event.unsubscribe = (subscriptionId) => { + return this.unsubscribe(subscriptionId); + }; + + event.getAllLogs = (options = {}) => { + return this.getAllLogs(event); + }; + + return event; + } + + getAllLogs (event, _options) { + // Options as first parameter + if (!_options && event && event.topics) { + return this.getAllLogs(null, event); + } + + const options = this._getFilterOptions(event, _options); + + options.fromBlock = 0; + options.toBlock = 'latest'; + + return this._api.eth + .getLogs(options) + .then((logs) => this.parseEventLogs(logs)); + } + + _findEvent (eventName = null) { + const event = eventName + ? this._events.find((evt) => evt.name === eventName) + : null; + + if (eventName && !event) { + const events = this._events.map((evt) => evt.name).join(', '); + + throw new Error(`${eventName} is not a valid eventName, subscribe using one of ${events} (or null to include all)`); + } + + return event; + } + + _getFilterOptions (event = null, _options = {}) { + const optionTopics = _options.topics || []; + const signature = event && event.signature || null; + + // If event provided, remove the potential event signature + // as the first element of the topics + const topics = signature + ? [ signature ].concat(optionTopics.filter((t, idx) => idx > 0 || t !== signature)) + : optionTopics; + + const options = Object.assign({}, _options, { + address: this._address, + topics + }); + + return options; + } + + _createEthFilter (event = null, _options) { + const options = this._getFilterOptions(event, _options); + + return this._api.eth.newFilter(options); + } + + subscribe (eventName = null, options = {}, callback, autoRemove) { + try { + const event = this._findEvent(eventName); + + return this._subscribe(event, options, callback, autoRemove); + } catch (e) { + return Promise.reject(e); + } + } + + _sendData (subscriptionId, error, logs) { + const { autoRemove, callback } = this._subscriptions[subscriptionId]; + let result = true; + + try { + result = callback(error, logs); + } catch (error) { + console.warn('_sendData', subscriptionId, error); + } + + if (autoRemove && result && typeof result === 'boolean') { + this.unsubscribe(subscriptionId); + } + } + + _subscribe (event = null, _options, callback, autoRemove = false) { + const subscriptionId = nextSubscriptionId++; + const { skipInitFetch } = _options; + + delete _options['skipInitFetch']; + + return this + ._createEthFilter(event, _options) + .then((filterId) => { + this._subscriptions[subscriptionId] = { + options: _options, + autoRemove, + callback, + filterId, + id: subscriptionId + }; + + if (skipInitFetch) { + this._subscribeToChanges(); + return subscriptionId; + } + + return this._api.eth + .getFilterLogs(filterId) + .then((logs) => { + this._sendData(subscriptionId, null, this.parseEventLogs(logs)); + this._subscribeToChanges(); + return subscriptionId; + }); + }) + .catch((error) => { + console.warn('subscribe', event, _options, error); + throw error; + }); + } + + unsubscribe (subscriptionId) { + return this._api.eth + .uninstallFilter(this._subscriptions[subscriptionId].filterId) + .catch((error) => { + console.error('unsubscribe', error); + }) + .then(() => { + delete this._subscriptions[subscriptionId]; + this._unsubscribeFromChanges(); + }); + } + + _subscribeToChanges = () => { + const subscriptions = Object.values(this._subscriptions); + + const pendingSubscriptions = subscriptions + .filter((s) => s.options.toBlock && s.options.toBlock === 'pending'); + + const otherSubscriptions = subscriptions + .filter((s) => !(s.options.toBlock && s.options.toBlock === 'pending')); + + if (pendingSubscriptions.length > 0 && !this._subscribedToPendings) { + this._subscribedToPendings = true; + this._subscribeToPendings(); + } + + if (otherSubscriptions.length > 0 && !this._subscribedToBlock) { + this._subscribedToBlock = true; + this._subscribeToBlock(); + } + } + + _unsubscribeFromChanges = () => { + const subscriptions = Object.values(this._subscriptions); + + const pendingSubscriptions = subscriptions + .filter((s) => s.options.toBlock && s.options.toBlock === 'pending'); + + const otherSubscriptions = subscriptions + .filter((s) => !(s.options.toBlock && s.options.toBlock === 'pending')); + + if (pendingSubscriptions.length === 0 && this._subscribedToPendings) { + this._subscribedToPendings = false; + clearTimeout(this._pendingsSubscriptionId); + } + + if (otherSubscriptions.length === 0 && this._subscribedToBlock) { + this._subscribedToBlock = false; + this._api.unsubscribe(this._blockSubscriptionId); + } + } + + _subscribeToBlock = () => { + this._api + .subscribe('eth_blockNumber', (error) => { + if (error) { + console.error('::_subscribeToBlock', error, error && error.stack); + } + + const subscriptions = Object.values(this._subscriptions) + .filter((s) => !(s.options.toBlock && s.options.toBlock === 'pending')); + + this._sendSubscriptionChanges(subscriptions); + }) + .then((blockSubId) => { + this._blockSubscriptionId = blockSubId; + }) + .catch((e) => { + console.error('::_subscribeToBlock', e, e && e.stack); + }); + } + + _subscribeToPendings = () => { + const subscriptions = Object.values(this._subscriptions) + .filter((s) => s.options.toBlock && s.options.toBlock === 'pending'); + + const timeout = () => setTimeout(() => this._subscribeToPendings(), 1000); + + this._sendSubscriptionChanges(subscriptions) + .then(() => { + this._pendingsSubscriptionId = timeout(); + }); + } + + _sendSubscriptionChanges = (subscriptions) => { + return Promise + .all( + subscriptions.map((subscription) => { + return this._api.eth.getFilterChanges(subscription.filterId); + }) + ) + .then((logsArray) => { + logsArray.forEach((logs, index) => { + if (!logs || !logs.length) { + return; + } + + try { + this._sendData(subscriptions[index].id, null, this.parseEventLogs(logs)); + } catch (error) { + console.error('_sendSubscriptionChanges', error); + } + }); + }) + .catch((error) => { + console.error('_sendSubscriptionChanges', error); + }); + } +} diff --git a/js/src/api/contract/contract.spec.js b/js/src/api/contract/contract.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..bd8b0fbdae711a384b37bc1f3e4ae81d8e824ccc --- /dev/null +++ b/js/src/api/contract/contract.spec.js @@ -0,0 +1,597 @@ +// 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 BigNumber from 'bignumber.js'; +import sinon from 'sinon'; + +import { TEST_HTTP_URL, mockHttp } from '../../../test/mockRpc'; + +import Abi from '../../abi'; +import { sha3 } from '../util/sha3'; + +import Api from '../api'; +import Contract from './contract'; +import { isInstanceOf, isFunction } from '../util/types'; + +const transport = new Api.Transport.Http(TEST_HTTP_URL, -1); +const eth = new Api(transport); + +describe('api/contract/Contract', () => { + const ADDR = '0x0123456789'; + + const ABI = [ + { + type: 'function', name: 'test', + inputs: [{ name: 'boolin', type: 'bool' }, { name: 'stringin', type: 'string' }], + outputs: [{ type: 'uint' }] + }, + { + type: 'function', name: 'test2', + outputs: [{ type: 'uint' }, { type: 'uint' }] + }, + { + type: 'constructor', + inputs: [{ name: 'boolin', type: 'bool' }, { name: 'stringin', type: 'string' }] + }, + { type: 'event', name: 'baz' }, + { type: 'event', name: 'foo' } + ]; + + const ABI_NO_PARAMS = [ + { + type: 'function', name: 'test', + inputs: [{ name: 'boolin', type: 'bool' }, { name: 'stringin', type: 'string' }], + outputs: [{ type: 'uint' }] + }, + { + type: 'function', name: 'test2', + outputs: [{ type: 'uint' }, { type: 'uint' }] + }, + { + type: 'constructor' + }, + { type: 'event', name: 'baz' }, + { type: 'event', name: 'foo' } + ]; + + const VALUES = [ true, 'jacogr' ]; + const CALLDATA = ` + 0000000000000000000000000000000000000000000000000000000000000001 + 0000000000000000000000000000000000000000000000000000000000000040 + 0000000000000000000000000000000000000000000000000000000000000006 + 6a61636f67720000000000000000000000000000000000000000000000000000 + `.replace(/\s/g, ''); + const SIGNATURE = '02356205'; + + const ENCODED = `0x${SIGNATURE}${CALLDATA}`; + + const RETURN1 = '0000000000000000000000000000000000000000000000000000000000123456'; + const RETURN2 = '0000000000000000000000000000000000000000000000000000000000456789'; + let scope; + + describe('constructor', () => { + it('needs an EthAbi instance', () => { + expect(() => new Contract()).to.throw(/API instance needs to be provided to Contract/); + }); + + it('needs an ABI', () => { + expect(() => new Contract(eth)).to.throw(/ABI needs to be provided to Contract instance/); + }); + + describe('internal setup', () => { + const contract = new Contract(eth, ABI); + + it('sets EthApi & parsed interface', () => { + expect(contract.address).to.not.be.ok; + expect(contract.api).to.deep.equal(eth); + expect(isInstanceOf(contract.abi, Abi)).to.be.ok; + }); + + it('attaches functions', () => { + expect(contract.functions.length).to.equal(2); + expect(contract.functions[0].name).to.equal('test'); + }); + + it('attaches constructors', () => { + expect(contract.constructors.length).to.equal(1); + }); + + it('attaches events', () => { + expect(contract.events.length).to.equal(2); + expect(contract.events[0].name).to.equal('baz'); + }); + }); + }); + + describe('at', () => { + it('sets returns the functions, events & sets the address', () => { + const contract = new Contract(eth, [ + { + constant: true, + inputs: [{ + name: '_who', + type: 'address' + }], + name: 'balanceOf', + outputs: [{ + name: '', + type: 'uint256' + }], + type: 'function' + }, + { + anonymous: false, + inputs: [{ + indexed: false, + name: 'amount', + type: 'uint256' + }], + name: 'Drained', + type: 'event' + } + ]); + + contract.at('6789'); + + expect(Object.keys(contract.instance)).to.deep.equal([ + 'Drained', + /^(?:0x)(.+)$/.exec(sha3('Drained(uint256)'))[1], + 'balanceOf', + /^(?:0x)(.+)$/.exec(sha3('balanceOf(address)'))[1].substr(0, 8), + 'address' + ]); + expect(contract.address).to.equal('6789'); + }); + }); + + describe('parseTransactionEvents', () => { + it('parses a transaction log into the data', () => { + const contract = new Contract(eth, [ + { + anonymous: false, name: 'Message', type: 'event', + inputs: [ + { indexed: true, name: 'postId', type: 'uint256' }, + { indexed: false, name: 'parentId', type: 'uint256' }, + { indexed: false, name: 'sender', type: 'address' }, + { indexed: false, name: 'at', type: 'uint256' }, + { indexed: false, name: 'messageId', type: 'uint256' }, + { indexed: false, name: 'message', type: 'string' } + ] + } + ]); + const decoded = contract.parseTransactionEvents({ + blockHash: '0xa9280530a3b47bee2fc80f2862fd56502ae075350571d724d6442ea4c597347b', + blockNumber: '0x4fcd', + cumulativeGasUsed: '0xb57f', + gasUsed: '0xb57f', + logs: [{ + address: '0x22bff18ec62281850546a664bb63a5c06ac5f76c', + blockHash: '0xa9280530a3b47bee2fc80f2862fd56502ae075350571d724d6442ea4c597347b', + blockNumber: '0x4fcd', + data: '0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000063cf90d3f0410092fc0fca41846f5962239791950000000000000000000000000000000000000000000000000000000056e6c85f0000000000000000000000000000000000000000000000000001000000004fcd00000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000d706f7374286d6573736167652900000000000000000000000000000000000000', + logIndex: '0x0', + topics: [ + '0x954ba6c157daf8a26539574ffa64203c044691aa57251af95f4b48d85ec00dd5', + '0x0000000000000000000000000000000000000000000000000001000000004fe0' + ], + transactionHash: '0xca16f537d761d13e4e80953b754e2b15541f267d6cad9381f750af1bae1e4917', + transactionIndex: '0x0' + }], + to: '0x22bff18ec62281850546a664bb63a5c06ac5f76c', + transactionHash: '0xca16f537d761d13e4e80953b754e2b15541f267d6cad9381f750af1bae1e4917', + transactionIndex: '0x0' + }); + const log = decoded.logs[0]; + + expect(log.event).to.equal('Message'); + expect(log.address).to.equal('0x22bff18ec62281850546a664bb63a5c06ac5f76c'); + expect(log.params).to.deep.equal({ + at: { type: 'uint', value: new BigNumber('1457965151') }, + message: { type: 'string', value: 'post(message)' }, + messageId: { type: 'uint', value: new BigNumber('281474976731085') }, + parentId: { type: 'uint', value: new BigNumber(0) }, + postId: { type: 'uint', value: new BigNumber('281474976731104') }, + sender: { type: 'address', value: '0x63Cf90D3f0410092FC0fca41846f596223979195' } + }); + }); + }); + + describe('_pollTransactionReceipt', () => { + const contract = new Contract(eth, ABI); + const ADDRESS = '0xD337e80eEdBdf86eDBba021797d7e4e00Bb78351'; + const BLOCKNUMBER = '555000'; + const RECEIPT = { contractAddress: ADDRESS.toLowerCase(), blockNumber: BLOCKNUMBER }; + const EXPECT = { contractAddress: ADDRESS, blockNumber: new BigNumber(BLOCKNUMBER) }; + + let scope; + let receipt; + + describe('success', () => { + before(() => { + scope = mockHttp([ + { method: 'eth_getTransactionReceipt', reply: { result: null } }, + { method: 'eth_getTransactionReceipt', reply: { result: null } }, + { method: 'eth_getTransactionReceipt', reply: { result: RECEIPT } } + ]); + + return contract + ._pollTransactionReceipt('0x123') + .then((_receipt) => { + receipt = _receipt; + }); + }); + + it('sends multiple getTransactionReceipt calls', () => { + expect(scope.isDone()).to.be.true; + }); + + it('passes the txhash through', () => { + expect(scope.body.eth_getTransactionReceipt.params[0]).to.equal('0x123'); + }); + + it('receives the final receipt', () => { + expect(receipt).to.deep.equal(EXPECT); + }); + }); + + describe('error', () => { + before(() => { + scope = mockHttp([{ method: 'eth_getTransactionReceipt', reply: { error: { code: -1, message: 'failure' } } }]); + }); + + it('returns the errors', () => { + return contract + ._pollTransactionReceipt('0x123') + .catch((error) => { + expect(error.message).to.match(/failure/); + }); + }); + }); + }); + + describe('deploy without parameters', () => { + const contract = new Contract(eth, ABI_NO_PARAMS); + const CODE = '0x123'; + const ADDRESS = '0xD337e80eEdBdf86eDBba021797d7e4e00Bb78351'; + const RECEIPT_DONE = { contractAddress: ADDRESS.toLowerCase(), gasUsed: 50, blockNumber: 2500 }; + + let scope; + + describe('success', () => { + before(() => { + scope = mockHttp([ + { method: 'eth_estimateGas', reply: { result: 1000 } }, + { method: 'parity_postTransaction', reply: { result: '0x678' } }, + { method: 'parity_checkRequest', reply: { result: '0x890' } }, + { method: 'eth_getTransactionReceipt', reply: { result: RECEIPT_DONE } }, + { method: 'eth_getCode', reply: { result: CODE } } + ]); + + return contract.deploy({ data: CODE }, []); + }); + + it('passes the options through to postTransaction (incl. gas calculation)', () => { + expect(scope.body.parity_postTransaction.params[0].data).to.equal(CODE); + }); + }); + }); + + describe('deploy', () => { + const contract = new Contract(eth, ABI); + const ADDRESS = '0xD337e80eEdBdf86eDBba021797d7e4e00Bb78351'; + const RECEIPT_PEND = { contractAddress: ADDRESS.toLowerCase(), gasUsed: 50, blockNumber: 0 }; + const RECEIPT_DONE = { contractAddress: ADDRESS.toLowerCase(), gasUsed: 50, blockNumber: 2500 }; + const RECEIPT_EXCP = { contractAddress: ADDRESS.toLowerCase(), gasUsed: 1200, blockNumber: 2500 }; + + let scope; + + describe('success', () => { + before(() => { + scope = mockHttp([ + { method: 'eth_estimateGas', reply: { result: 1000 } }, + { method: 'parity_postTransaction', reply: { result: '0x678' } }, + { method: 'parity_checkRequest', reply: { result: null } }, + { method: 'parity_checkRequest', reply: { result: '0x890' } }, + { method: 'eth_getTransactionReceipt', reply: { result: null } }, + { method: 'eth_getTransactionReceipt', reply: { result: RECEIPT_PEND } }, + { method: 'eth_getTransactionReceipt', reply: { result: RECEIPT_DONE } }, + { method: 'eth_getCode', reply: { result: '0x456' } } + ]); + + return contract.deploy({ data: '0x123' }, VALUES); + }); + + it('calls estimateGas, postTransaction, checkRequest, getTransactionReceipt & getCode in order', () => { + expect(scope.isDone()).to.be.true; + }); + + it('passes the options through to postTransaction (incl. gas calculation)', () => { + expect(scope.body.parity_postTransaction.params).to.deep.equal([ + { data: `0x123${CALLDATA}`, gas: '0x4b0' } + ]); + }); + + it('sets the address of the contract', () => { + expect(contract.address).to.equal(ADDRESS); + }); + }); + + describe('error', () => { + it('fails when gasUsed == gas', () => { + mockHttp([ + { method: 'eth_estimateGas', reply: { result: 1000 } }, + { method: 'parity_postTransaction', reply: { result: '0x678' } }, + { method: 'parity_checkRequest', reply: { result: '0x789' } }, + { method: 'eth_getTransactionReceipt', reply: { result: RECEIPT_EXCP } } + ]); + + return contract + .deploy({ data: '0x123' }, VALUES) + .catch((error) => { + expect(error.message).to.match(/not deployed, gasUsed/); + }); + }); + + it('fails when no code was deployed', () => { + mockHttp([ + { method: 'eth_estimateGas', reply: { result: 1000 } }, + { method: 'parity_postTransaction', reply: { result: '0x678' } }, + { method: 'parity_checkRequest', reply: { result: '0x789' } }, + { method: 'eth_getTransactionReceipt', reply: { result: RECEIPT_DONE } }, + { method: 'eth_getCode', reply: { result: '0x' } } + ]); + + return contract + .deploy({ data: '0x123' }, VALUES) + .catch((error) => { + expect(error.message).to.match(/not deployed, getCode/); + }); + }); + }); + }); + + describe('bindings', () => { + let contract; + let cons; + let func; + + beforeEach(() => { + contract = new Contract(eth, ABI); + contract.at(ADDR); + cons = contract.constructors[0]; + func = contract.functions.find((fn) => fn.name === 'test'); + }); + + describe('_addOptionsTo', () => { + it('works on no object specified', () => { + expect(contract._addOptionsTo()).to.deep.equal({ to: ADDR }); + }); + + it('uses the contract address when none specified', () => { + expect(contract._addOptionsTo({ from: 'me' })).to.deep.equal({ to: ADDR, from: 'me' }); + }); + + it('overrides the contract address when specified', () => { + expect(contract._addOptionsTo({ to: 'you', from: 'me' })).to.deep.equal({ to: 'you', from: 'me' }); + }); + }); + + describe('attachments', () => { + it('attaches .call, .postTransaction & .estimateGas to constructors', () => { + expect(isFunction(cons.call)).to.be.true; + expect(isFunction(cons.postTransaction)).to.be.true; + expect(isFunction(cons.estimateGas)).to.be.true; + }); + + it('attaches .call, .postTransaction & .estimateGas to functions', () => { + expect(isFunction(func.call)).to.be.true; + expect(isFunction(func.postTransaction)).to.be.true; + expect(isFunction(func.estimateGas)).to.be.true; + }); + + it('attaches .call only to constant functions', () => { + func = (new Contract(eth, [{ type: 'function', name: 'test', constant: true }])).functions[0]; + + expect(isFunction(func.call)).to.be.true; + expect(isFunction(func.postTransaction)).to.be.false; + expect(isFunction(func.estimateGas)).to.be.false; + }); + }); + + describe('postTransaction', () => { + beforeEach(() => { + scope = mockHttp([{ method: 'parity_postTransaction', reply: { result: ['hashId'] } }]); + }); + + it('encodes options and mades an parity_postTransaction call', () => { + return func + .postTransaction({ someExtras: 'foo' }, VALUES) + .then(() => { + expect(scope.isDone()).to.be.true; + expect(scope.body.parity_postTransaction.params[0]).to.deep.equal({ + someExtras: 'foo', + to: ADDR, + data: ENCODED + }); + }); + }); + }); + + describe('estimateGas', () => { + beforeEach(() => { + scope = mockHttp([{ method: 'eth_estimateGas', reply: { result: ['0x123'] } }]); + }); + + it('encodes options and mades an eth_estimateGas call', () => { + return func + .estimateGas({ someExtras: 'foo' }, VALUES) + .then((amount) => { + expect(scope.isDone()).to.be.true; + expect(amount.toString(16)).to.equal('123'); + expect(scope.body.eth_estimateGas.params).to.deep.equal([{ + someExtras: 'foo', + to: ADDR, + data: ENCODED + }]); + }); + }); + }); + + describe('call', () => { + it('encodes options and mades an eth_call call', () => { + scope = mockHttp([{ method: 'eth_call', reply: { result: RETURN1 } }]); + + return func + .call({ someExtras: 'foo' }, VALUES) + .then((result) => { + expect(scope.isDone()).to.be.true; + expect(scope.body.eth_call.params).to.deep.equal([{ + someExtras: 'foo', + to: ADDR, + data: ENCODED + }, 'latest']); + expect(result.toString(16)).to.equal('123456'); + }); + }); + + it('encodes options and mades an eth_call call (multiple returns)', () => { + scope = mockHttp([{ method: 'eth_call', reply: { result: `${RETURN1}${RETURN2}` } }]); + + return contract.functions[1] + .call({}, []) + .then((result) => { + expect(scope.isDone()).to.be.true; + expect(result.length).to.equal(2); + expect(result[0].toString(16)).to.equal('123456'); + expect(result[1].toString(16)).to.equal('456789'); + }); + }); + }); + }); + + describe('subscribe', () => { + const abi = [ + { + anonymous: false, name: 'Message', type: 'event', + inputs: [ + { indexed: true, name: 'postId', type: 'uint256' }, + { indexed: false, name: 'parentId', type: 'uint256' }, + { indexed: false, name: 'sender', type: 'address' }, + { indexed: false, name: 'at', type: 'uint256' }, + { indexed: false, name: 'messageId', type: 'uint256' }, + { indexed: false, name: 'message', type: 'string' } + ] + } + ]; + + const logs = [{ + address: '0x22bff18ec62281850546a664bb63a5c06ac5f76c', + blockHash: '0xa9280530a3b47bee2fc80f2862fd56502ae075350571d724d6442ea4c597347b', + blockNumber: '0x4fcd', + data: '0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000063cf90d3f0410092fc0fca41846f5962239791950000000000000000000000000000000000000000000000000000000056e6c85f0000000000000000000000000000000000000000000000000001000000004fcd00000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000d706f7374286d6573736167652900000000000000000000000000000000000000', + logIndex: '0x0', + topics: [ + '0x954ba6c157daf8a26539574ffa64203c044691aa57251af95f4b48d85ec00dd5', + '0x0000000000000000000000000000000000000000000000000001000000004fe0' + ], + transactionHash: '0xca16f537d761d13e4e80953b754e2b15541f267d6cad9381f750af1bae1e4917', + transactionIndex: '0x0' + }]; + + const parsed = [{ + address: '0x22bfF18ec62281850546a664bb63a5C06AC5F76C', + blockHash: '0xa9280530a3b47bee2fc80f2862fd56502ae075350571d724d6442ea4c597347b', + blockNumber: new BigNumber(20429), + data: '0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000063cf90d3f0410092fc0fca41846f5962239791950000000000000000000000000000000000000000000000000000000056e6c85f0000000000000000000000000000000000000000000000000001000000004fcd00000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000d706f7374286d6573736167652900000000000000000000000000000000000000', + event: 'Message', + logIndex: new BigNumber(0), + params: { + at: { type: 'uint', value: new BigNumber(1457965151) }, + message: { type: 'string', value: 'post(message)' }, + messageId: { type: 'uint', value: new BigNumber(281474976731085) }, + parentId: { type: 'uint', value: new BigNumber(0) }, + postId: { type: 'uint', value: new BigNumber(281474976731104) }, + sender: { type: 'address', value: '0x63Cf90D3f0410092FC0fca41846f596223979195' } + }, + topics: [ + '0x954ba6c157daf8a26539574ffa64203c044691aa57251af95f4b48d85ec00dd5', + '0x0000000000000000000000000000000000000000000000000001000000004fe0' + ], + transactionHash: '0xca16f537d761d13e4e80953b754e2b15541f267d6cad9381f750af1bae1e4917', + transactionIndex: new BigNumber(0) + }]; + + let contract; + + beforeEach(() => { + contract = new Contract(eth, abi); + contract.at(ADDR); + }); + + describe('invalid events', () => { + it('fails to subscribe to an invalid names', () => { + return contract + .subscribe('invalid') + .catch((error) => { + expect(error.message).to.match(/invalid is not a valid eventName/); + }); + }); + }); + + describe('valid events', () => { + let cbb; + let cbe; + + beforeEach(() => { + scope = mockHttp([ + { method: 'eth_newFilter', reply: { result: '0x123' } }, + { method: 'eth_getFilterLogs', reply: { result: logs } }, + { method: 'eth_getFilterChanges', reply: { result: logs } }, + { method: 'eth_newFilter', reply: { result: '0x123' } }, + { method: 'eth_getFilterLogs', reply: { result: logs } } + ]); + cbb = sinon.stub(); + cbe = sinon.stub(); + + return contract.subscribe('Message', { toBlock: 'pending' }, cbb); + }); + + it('sets the subscriptionId returned', () => { + return contract + .subscribe('Message', { toBlock: 'pending' }, cbe) + .then((subscriptionId) => { + expect(subscriptionId).to.equal(1); + }); + }); + + it('creates a new filter and retrieves the logs on it', () => { + return contract + .subscribe('Message', { toBlock: 'pending' }, cbe) + .then((subscriptionId) => { + expect(scope.isDone()).to.be.true; + }); + }); + + it('returns the logs to the callback', () => { + return contract + .subscribe('Message', { toBlock: 'pending' }, cbe) + .then((subscriptionId) => { + expect(cbe).to.have.been.calledWith(null, parsed); + }); + }); + }); + }); +}); diff --git a/js/src/api/contract/index.js b/js/src/api/contract/index.js new file mode 100644 index 0000000000000000000000000000000000000000..74c58e942d4f23ba7637a544b403a1e1f88ffb37 --- /dev/null +++ b/js/src/api/contract/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 './contract'; diff --git a/js/src/api/format/input.js b/js/src/api/format/input.js new file mode 100644 index 0000000000000000000000000000000000000000..0ccd10604bfe3d218553d006b9caf89b7e10b001 --- /dev/null +++ b/js/src/api/format/input.js @@ -0,0 +1,242 @@ +// 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 BigNumber from 'bignumber.js'; + +import { isArray, isHex, isInstanceOf, isString } from '../util/types'; +import { padLeft, toHex } from '../util/format'; + +export function inAddress (address) { + // TODO: address validation if we have upper-lower addresses + return inHex(address); +} + +export function inAddresses (addresses) { + return (addresses || []).map(inAddress); +} + +export function inBlockNumber (blockNumber) { + if (isString(blockNumber)) { + switch (blockNumber) { + case 'earliest': + case 'latest': + case 'pending': + return blockNumber; + } + } + + return inNumber16(blockNumber); +} + +export function inData (data) { + if (data && data.length && !isHex(data)) { + data = data.split('').map((chr) => { + return `0${chr.charCodeAt(0).toString(16)}`.slice(-2); + }).join(''); + } + + return inHex(data); +} + +export function inHash (hash) { + return inHex(hash); +} + +export function inTopics (_topics) { + let topics = (_topics || []) + .filter((topic) => topic === null || topic) + .map((topic) => { + if (topic === null) { + return null; + } + + if (Array.isArray(topic)) { + return inTopics(topic); + } + + return padLeft(topic, 32); + }); + + return topics; +} + +export function inFilter (options) { + if (options) { + Object.keys(options).forEach((key) => { + switch (key) { + case 'address': + if (isArray(options[key])) { + options[key] = options[key].map(inAddress); + } else { + options[key] = inAddress(options[key]); + } + break; + + case 'fromBlock': + case 'toBlock': + options[key] = inBlockNumber(options[key]); + break; + + case 'limit': + options[key] = inNumber10(options[key]); + break; + + case 'topics': + options[key] = inTopics(options[key]); + } + }); + } + + return options; +} + +export function inHex (str) { + return toHex(str); +} + +export function inNumber10 (number) { + if (isInstanceOf(number, BigNumber)) { + return number.toNumber(); + } + + return (new BigNumber(number || 0)).toNumber(); +} + +export function inNumber16 (number) { + const bn = isInstanceOf(number, BigNumber) + ? number + : (new BigNumber(number || 0)); + + if (!bn.isInteger()) { + throw new Error(`[format/input::inNumber16] the given number is not an integer: ${bn.toFormat()}`); + } + + return inHex(bn.toString(16)); +} + +export function inOptionsCondition (condition) { + if (condition) { + if (condition.block) { + condition.block = condition.block ? inNumber10(condition.block) : null; + } else if (condition.time) { + condition.time = inNumber10(Math.floor(condition.time.getTime() / 1000)); + } + } + + return condition; +} + +export function inOptions (_options = {}) { + const options = { ..._options }; + + Object.keys(options).forEach((key) => { + switch (key) { + case 'to': + // Don't encode the `to` option if it's empty + // (eg. contract deployments) + if (options[key]) { + options.to = inAddress(options[key]); + } + break; + + case 'from': + options[key] = inAddress(options[key]); + break; + + case 'condition': + options[key] = inOptionsCondition(options[key]); + break; + + case 'gas': + case 'gasPrice': + options[key] = inNumber16((new BigNumber(options[key])).round()); + break; + + case 'value': + case 'nonce': + options[key] = inNumber16(options[key]); + break; + + case 'data': + options[key] = inData(options[key]); + break; + } + }); + + return options; +} + +export function inTraceFilter (filterObject) { + if (filterObject) { + Object.keys(filterObject).forEach((key) => { + switch (key) { + case 'fromAddress': + case 'toAddress': + filterObject[key] = [].concat(filterObject[key]) + .map(address => inAddress(address)); + break; + + case 'toBlock': + case 'fromBlock': + filterObject[key] = inBlockNumber(filterObject[key]); + break; + } + }); + } + + return filterObject; +} + +export function inTraceType (whatTrace) { + if (isString(whatTrace)) { + return [whatTrace]; + } + + return whatTrace; +} + +function inDeriveType (derive) { + return derive && derive.type === 'hard' ? 'hard' : 'soft'; +} + +export function inDeriveHash (derive) { + const hash = derive && derive.hash ? derive.hash : derive; + const type = inDeriveType(derive); + + return { + hash: inHex(hash), + type + }; +} + +export function inDeriveIndex (derive) { + if (!derive) { + return []; + } + + if (!isArray(derive)) { + derive = [derive]; + } + + return derive.map(item => { + const index = inNumber10(item && item.index ? item.index : item); + + return { + index, + type: inDeriveType(item) + }; + }); +} diff --git a/js/src/api/format/input.spec.js b/js/src/api/format/input.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..eb74b7bd67cef4c96f0d35273107076e0c5cb478 --- /dev/null +++ b/js/src/api/format/input.spec.js @@ -0,0 +1,341 @@ +// 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 BigNumber from 'bignumber.js'; + +import { + inAddress, inBlockNumber, inData, inFilter, inHex, + inNumber10, inNumber16, inOptions, inTraceType, + inDeriveHash, inDeriveIndex +} from './input'; +import { isAddress } from '../../../test/types'; + +describe('api/format/input', () => { + const address = '0x63cf90d3f0410092fc0fca41846f596223979195'; + + describe('inAddress', () => { + const address = '63cf90d3f0410092fc0fca41846f596223979195'; + + it('adds the leading 0x as required', () => { + expect(inAddress(address)).to.equal(`0x${address}`); + }); + + it('returns verified addresses as-is', () => { + expect(inAddress(`0x${address}`)).to.equal(`0x${address}`); + }); + + it('returns lowercase equivalents', () => { + expect(inAddress(address.toUpperCase())).to.equal(`0x${address}`); + }); + + it('returns 0x on null addresses', () => { + expect(inAddress()).to.equal('0x'); + }); + }); + + describe('inBlockNumber()', () => { + it('returns earliest as-is', () => { + expect(inBlockNumber('earliest')).to.equal('earliest'); + }); + + it('returns latest as-is', () => { + expect(inBlockNumber('latest')).to.equal('latest'); + }); + + it('returns pending as-is', () => { + expect(inBlockNumber('pending')).to.equal('pending'); + }); + + it('formats existing BigNumber into hex', () => { + expect(inBlockNumber(new BigNumber(0x123456))).to.equal('0x123456'); + }); + + it('formats hex strings into hex', () => { + expect(inBlockNumber('0x123456')).to.equal('0x123456'); + }); + + it('formats numbers into hex', () => { + expect(inBlockNumber(0x123456)).to.equal('0x123456'); + }); + }); + + describe('inData', () => { + it('formats to hex', () => { + expect(inData('123456')).to.equal('0x123456'); + }); + + it('converts a string to a hex representation', () => { + expect(inData('jaco')).to.equal('0x6a61636f'); + }); + }); + + describe('inHex', () => { + it('leaves leading 0x as-is', () => { + expect(inHex('0x123456')).to.equal('0x123456'); + }); + + it('adds a leading 0x', () => { + expect(inHex('123456')).to.equal('0x123456'); + }); + + it('returns uppercase as lowercase (leading 0x)', () => { + expect(inHex('0xABCDEF')).to.equal('0xabcdef'); + }); + + it('returns uppercase as lowercase (no leading 0x)', () => { + expect(inHex('ABCDEF')).to.equal('0xabcdef'); + }); + + it('handles empty & null', () => { + expect(inHex()).to.equal('0x'); + expect(inHex('')).to.equal('0x'); + }); + }); + + describe('inFilter', () => { + ['address'].forEach((input) => { + it(`formats ${input} address as address`, () => { + const block = {}; + + block[input] = address; + const formatted = inFilter(block)[input]; + + expect(isAddress(formatted)).to.be.true; + expect(formatted).to.equal(address); + }); + }); + + ['fromBlock', 'toBlock'].forEach((input) => { + it(`formats ${input} number as blockNumber`, () => { + const block = {}; + + block[input] = 0x123; + const formatted = inFilter(block)[input]; + + expect(formatted).to.equal('0x123'); + }); + }); + + it('ignores and passes through unknown keys', () => { + expect(inFilter({ someRandom: 'someRandom' })).to.deep.equal({ someRandom: 'someRandom' }); + }); + + it('formats an filter options object with relevant entries converted', () => { + expect( + inFilter({ + address: address, + fromBlock: 'latest', + toBlock: 0x101, + extraData: 'someExtraStuffInHere', + limit: 0x32 + }) + ).to.deep.equal({ + address: address, + fromBlock: 'latest', + toBlock: '0x101', + extraData: 'someExtraStuffInHere', + limit: 50 + }); + }); + }); + + describe('inNumber10()', () => { + it('formats existing BigNumber into number', () => { + expect(inNumber10(new BigNumber(123))).to.equal(123); + }); + + it('formats hex strings into decimal', () => { + expect(inNumber10('0x0a')).to.equal(10); + }); + + it('formats numbers into number', () => { + expect(inNumber10(123)).to.equal(123); + }); + + it('formats undefined into 0', () => { + expect(inNumber10()).to.equal(0); + }); + }); + + describe('inNumber16()', () => { + it('formats existing BigNumber into hex', () => { + expect(inNumber16(new BigNumber(0x123456))).to.equal('0x123456'); + }); + + it('formats hex strings into hex', () => { + expect(inNumber16('0x123456')).to.equal('0x123456'); + }); + + it('formats numbers into hex', () => { + expect(inNumber16(0x123456)).to.equal('0x123456'); + }); + + it('formats undefined into 0', () => { + expect(inNumber16()).to.equal('0x0'); + }); + }); + + describe('inOptions', () => { + ['data'].forEach((input) => { + it(`converts ${input} to hex data`, () => { + const block = {}; + + block[input] = '1234'; + const formatted = inData(block[input]); + + expect(formatted).to.equal('0x1234'); + }); + }); + + ['from', 'to'].forEach((input) => { + it(`formats ${input} address as address`, () => { + const block = {}; + + block[input] = address; + const formatted = inOptions(block)[input]; + + expect(isAddress(formatted)).to.be.true; + expect(formatted).to.equal(address); + }); + }); + + it('does not encode an empty `to` value', () => { + const options = { to: '' }; + const formatted = inOptions(options); + + expect(formatted.to).to.equal(''); + }); + + ['gas', 'gasPrice', 'value', 'nonce'].forEach((input) => { + it(`formats ${input} number as hexnumber`, () => { + const block = {}; + + block[input] = 0x123; + const formatted = inOptions(block)[input]; + + expect(formatted).to.equal('0x123'); + }); + }); + + it('passes condition as null when specified as such', () => { + expect(inOptions({ condition: null })).to.deep.equal({ condition: null }); + }); + + it('ignores and passes through unknown keys', () => { + expect(inOptions({ someRandom: 'someRandom' })).to.deep.equal({ someRandom: 'someRandom' }); + }); + + it('formats an options object with relevant entries converted', () => { + expect( + inOptions({ + from: address, + to: address, + gas: new BigNumber('0x100'), + gasPrice: 0x101, + value: 258, + nonce: '0x104', + data: '0123456789', + extraData: 'someExtraStuffInHere' + }) + ).to.deep.equal({ + from: address, + to: address, + gas: '0x100', + gasPrice: '0x101', + value: '0x102', + nonce: '0x104', + data: '0x0123456789', + extraData: 'someExtraStuffInHere' + }); + }); + }); + + describe('inTraceType', () => { + it('returns array of types as is', () => { + const types = ['vmTrace', 'trace', 'stateDiff']; + + expect(inTraceType(types)).to.deep.equal(types); + }); + + it('formats single string type into array', () => { + const type = 'vmTrace'; + + expect(inTraceType(type)).to.deep.equal([type]); + }); + }); + + describe('inDeriveHash', () => { + it('returns derive hash', () => { + expect(inDeriveHash(1)).to.deep.equal({ + hash: '0x1', + type: 'soft' + }); + + expect(inDeriveHash(null)).to.deep.equal({ + hash: '0x', + type: 'soft' + }); + + expect(inDeriveHash({ + hash: 5 + })).to.deep.equal({ + hash: '0x5', + type: 'soft' + }); + + expect(inDeriveHash({ + hash: 5, + type: 'hard' + })).to.deep.equal({ + hash: '0x5', + type: 'hard' + }); + }); + }); + + describe('inDeriveIndex', () => { + it('returns derive hash', () => { + expect(inDeriveIndex(null)).to.deep.equal([]); + expect(inDeriveIndex([])).to.deep.equal([]); + + expect(inDeriveIndex([1])).to.deep.equal([{ + index: 1, + type: 'soft' + }]); + + expect(inDeriveIndex({ + index: 1 + })).to.deep.equal([{ + index: 1, + type: 'soft' + }]); + + expect(inDeriveIndex([{ + index: 1, + type: 'hard' + }, 5])).to.deep.equal([ + { + index: 1, + type: 'hard' + }, + { + index: 5, + type: 'soft' + } + ]); + }); + }); +}); diff --git a/js/src/api/format/output.js b/js/src/api/format/output.js new file mode 100644 index 0000000000000000000000000000000000000000..7bf39d17c675b8292c7428776067915d54a0c255 --- /dev/null +++ b/js/src/api/format/output.js @@ -0,0 +1,414 @@ +// 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 BigNumber from 'bignumber.js'; + +import { toChecksumAddress } from '../../abi/util/address'; +import { isString } from '../util/types'; + +export function outAccountInfo (infos) { + return Object + .keys(infos) + .reduce((ret, _address) => { + const info = infos[_address]; + const address = outAddress(_address); + + ret[address] = { + name: info.name + }; + + if (info.meta) { + ret[address].uuid = info.uuid; + ret[address].meta = JSON.parse(info.meta); + } + + return ret; + }, {}); +} + +export function outAddress (address) { + return toChecksumAddress(address); +} + +export function outAddresses (addresses) { + return (addresses || []).map(outAddress); +} + +export function outBlock (block) { + if (block) { + Object.keys(block).forEach((key) => { + switch (key) { + case 'author': + case 'miner': + block[key] = outAddress(block[key]); + break; + + case 'difficulty': + case 'gasLimit': + case 'gasUsed': + case 'nonce': + case 'number': + case 'totalDifficulty': + block[key] = outNumber(block[key]); + break; + + case 'timestamp': + block[key] = outDate(block[key]); + break; + } + }); + } + + return block; +} + +export function outChainStatus (status) { + if (status) { + Object.keys(status).forEach((key) => { + switch (key) { + case 'blockGap': + status[key] = status[key] + ? status[key].map(outNumber) + : status[key]; + break; + } + }); + } + + return status; +} + +export function outDate (date) { + if (typeof date.toISOString === 'function') { + return date; + } + + try { + if (typeof date === 'string' && (new Date(date)).toISOString() === date) { + return new Date(date); + } + } catch (error) {} + + return new Date(outNumber(date).toNumber() * 1000); +} + +export function outHistogram (histogram) { + if (histogram) { + Object.keys(histogram).forEach((key) => { + switch (key) { + case 'bucketBounds': + case 'counts': + histogram[key] = histogram[key].map(outNumber); + break; + } + }); + } + + return histogram; +} + +export function outLog (log) { + Object.keys(log).forEach((key) => { + switch (key) { + case 'blockNumber': + case 'logIndex': + case 'transactionIndex': + log[key] = outNumber(log[key]); + break; + + case 'address': + log[key] = outAddress(log[key]); + break; + } + }); + + 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 outNodeKind (info) { + return info; +} + +export function outNumber (number) { + return new BigNumber(number || 0); +} + +export function outPeer (peer) { + const protocols = Object.keys(peer.protocols) + .reduce((obj, key) => { + if (peer.protocols[key]) { + obj[key] = { + ...peer.protocols[key], + difficulty: outNumber(peer.protocols[key].difficulty) + }; + } + + return obj; + }, {}); + + return { + ...peer, + protocols + }; +} + +export function outPeers (peers) { + return { + active: outNumber(peers.active), + connected: outNumber(peers.connected), + max: outNumber(peers.max), + peers: peers.peers.map((peer) => outPeer(peer)) + }; +} + +export function outReceipt (receipt) { + if (receipt) { + Object.keys(receipt).forEach((key) => { + switch (key) { + case 'blockNumber': + case 'cumulativeGasUsed': + case 'gasUsed': + case 'transactionIndex': + receipt[key] = outNumber(receipt[key]); + break; + + case 'contractAddress': + receipt[key] = outAddress(receipt[key]); + break; + } + }); + } + + return receipt; +} + +export function outRecentDapps (recentDapps) { + if (recentDapps) { + Object.keys(recentDapps).forEach((url) => { + recentDapps[url] = outDate(recentDapps[url]); + }); + } + + return recentDapps; +} + +export function outSignerRequest (request) { + if (request) { + Object.keys(request).forEach((key) => { + switch (key) { + case 'id': + request[key] = outNumber(request[key]); + break; + + case 'payload': + request[key].decrypt = outSigningPayload(request[key].decrypt); + request[key].sign = outSigningPayload(request[key].sign); + request[key].signTransaction = outTransaction(request[key].signTransaction); + request[key].sendTransaction = outTransaction(request[key].sendTransaction); + break; + + case 'origin': + const type = Object.keys(request[key])[0]; + const details = request[key][type]; + + request[key] = { type, details }; + break; + } + }); + } + + return request; +} + +export function outSyncing (syncing) { + if (syncing && syncing !== 'false') { + Object.keys(syncing).forEach((key) => { + switch (key) { + case 'currentBlock': + case 'highestBlock': + case 'startingBlock': + case 'warpChunksAmount': + case 'warpChunksProcessed': + syncing[key] = outNumber(syncing[key]); + break; + + case 'blockGap': + syncing[key] = syncing[key] ? syncing[key].map(outNumber) : syncing[key]; + break; + } + }); + } + + return syncing; +} + +export function outTransactionCondition (condition) { + if (condition) { + if (condition.block) { + condition.block = outNumber(condition.block); + } else if (condition.time) { + condition.time = outDate(condition.time); + } + } + + return condition; +} + +export function outTransaction (tx) { + if (tx) { + Object.keys(tx).forEach((key) => { + switch (key) { + case 'blockNumber': + case 'gasPrice': + case 'gas': + case 'nonce': + case 'transactionIndex': + case 'value': + tx[key] = outNumber(tx[key]); + break; + + case 'condition': + tx[key] = outTransactionCondition(tx[key]); + break; + + case 'creates': + case 'from': + case 'to': + tx[key] = outAddress(tx[key]); + break; + } + }); + } + + return tx; +} + +export function outSigningPayload (payload) { + if (payload) { + Object.keys(payload).forEach((key) => { + switch (key) { + case 'address': + payload[key] = outAddress(payload[key]); + break; + } + }); + } + + return payload; +} + +export function outTrace (trace) { + if (trace) { + if (trace.action) { + Object.keys(trace.action).forEach(key => { + switch (key) { + case 'gas': + case 'value': + case 'balance': + trace.action[key] = outNumber(trace.action[key]); + break; + + case 'from': + case 'to': + case 'address': + case 'refundAddress': + trace.action[key] = outAddress(trace.action[key]); + break; + } + }); + } + + if (trace.result) { + Object.keys(trace.result).forEach(key => { + switch (key) { + case 'gasUsed': + trace.result[key] = outNumber(trace.result[key]); + break; + + case 'address': + trace.action[key] = outAddress(trace.action[key]); + break; + } + }); + } + + if (trace.traceAddress) { + trace.traceAddress.forEach((address, index) => { + trace.traceAddress[index] = outNumber(address); + }); + } + + Object.keys(trace).forEach((key) => { + switch (key) { + case 'subtraces': + case 'transactionPosition': + case 'blockNumber': + trace[key] = outNumber(trace[key]); + break; + } + }); + } + + return trace; +} + +export function outTraces (traces) { + if (traces) { + return traces.map(outTrace); + } + + return traces; +} + +export function outTraceReplay (trace) { + if (trace) { + Object.keys(trace).forEach((key) => { + switch (key) { + case 'trace': + trace[key] = outTraces(trace[key]); + break; + } + }); + } + + return trace; +} + +export function outVaultMeta (meta) { + if (isString(meta)) { + try { + const obj = JSON.parse(meta); + + return obj; + } catch (error) { + return {}; + } + } + + return meta || {}; +} diff --git a/js/src/api/format/output.spec.js b/js/src/api/format/output.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..6ffefaedcc7e5a646546962defab3f1681ea0ce9 --- /dev/null +++ b/js/src/api/format/output.spec.js @@ -0,0 +1,502 @@ +// 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 BigNumber from 'bignumber.js'; + +import { outBlock, outAccountInfo, outAddress, outChainStatus, outDate, outHistogram, outHwAccountInfo, outNodeKind, outNumber, outPeer, outPeers, outReceipt, outRecentDapps, outSyncing, outTransaction, outTrace, outVaultMeta } from './output'; +import { isAddress, isBigNumber, isInstanceOf } from '../../../test/types'; + +describe('api/format/output', () => { + const address = '0x63cf90d3f0410092fc0fca41846f596223979195'; + const checksum = '0x63Cf90D3f0410092FC0fca41846f596223979195'; + + describe('outAccountInfo', () => { + it('returns meta objects parsed', () => { + expect(outAccountInfo( + { '0x63cf90d3f0410092fc0fca41846f596223979195': { + name: 'name', uuid: 'uuid', meta: '{"name":"456"}' } + } + )).to.deep.equal({ + '0x63Cf90D3f0410092FC0fca41846f596223979195': { + name: 'name', uuid: 'uuid', meta: { name: '456' } + } + }); + }); + + it('returns objects without meta & uuid as required', () => { + expect(outAccountInfo( + { '0x63cf90d3f0410092fc0fca41846f596223979195': { name: 'name' } } + )).to.deep.equal({ + '0x63Cf90D3f0410092FC0fca41846f596223979195': { name: 'name' } + }); + }); + }); + + describe('outAddress', () => { + it('retuns the address as checksummed', () => { + expect(outAddress(address)).to.equal(checksum); + }); + + it('retuns the checksum as checksummed', () => { + expect(outAddress(checksum)).to.equal(checksum); + }); + }); + + describe('outBlock', () => { + ['author', 'miner'].forEach((input) => { + it(`formats ${input} address as address`, () => { + const block = {}; + + block[input] = address; + const formatted = outBlock(block)[input]; + + expect(isAddress(formatted)).to.be.true; + expect(formatted).to.equal(checksum); + }); + }); + + ['difficulty', 'gasLimit', 'gasUsed', 'number', 'nonce', 'totalDifficulty'].forEach((input) => { + it(`formats ${input} number as hexnumber`, () => { + const block = {}; + + block[input] = 0x123; + const formatted = outBlock(block)[input]; + + expect(isInstanceOf(formatted, BigNumber)).to.be.true; + expect(formatted.toString(16)).to.equal('123'); + }); + }); + + ['timestamp'].forEach((input) => { + it(`formats ${input} number as Date`, () => { + const block = {}; + + block[input] = 0x57513668; + const formatted = outBlock(block)[input]; + + expect(isInstanceOf(formatted, Date)).to.be.true; + expect(formatted.getTime()).to.equal(1464940136000); + }); + }); + + it('ignores and passes through unknown keys', () => { + expect(outBlock({ someRandom: 'someRandom' })).to.deep.equal({ someRandom: 'someRandom' }); + }); + + it('formats a block with all the info converted', () => { + expect( + outBlock({ + author: address, + miner: address, + difficulty: '0x100', + gasLimit: '0x101', + gasUsed: '0x102', + number: '0x103', + nonce: '0x104', + totalDifficulty: '0x105', + timestamp: '0x57513668', + extraData: 'someExtraStuffInHere' + }) + ).to.deep.equal({ + author: checksum, + miner: checksum, + difficulty: new BigNumber('0x100'), + gasLimit: new BigNumber('0x101'), + gasUsed: new BigNumber('0x102'), + number: new BigNumber('0x103'), + nonce: new BigNumber('0x104'), + totalDifficulty: new BigNumber('0x105'), + timestamp: new Date('2016-06-03T07:48:56.000Z'), + extraData: 'someExtraStuffInHere' + }); + }); + }); + + describe('outChainStatus', () => { + it('formats blockGap values', () => { + const status = { + blockGap: [0x1234, '0x5678'] + }; + + expect(outChainStatus(status)).to.deep.equal({ + blockGap: [new BigNumber(0x1234), new BigNumber(0x5678)] + }); + }); + + it('handles null blockGap values', () => { + const status = { + blockGap: null + }; + + expect(outChainStatus(status)).to.deep.equal(status); + }); + }); + + describe('outDate', () => { + it('converts a second date in unix timestamp', () => { + expect(outDate(0x57513668)).to.deep.equal(new Date('2016-06-03T07:48:56.000Z')); + }); + }); + + describe('outHistogram', () => { + ['bucketBounds', 'counts'].forEach((type) => { + it(`formats ${type} as number arrays`, () => { + expect( + outHistogram({ [type]: [0x123, 0x456, 0x789] }) + ).to.deep.equal({ + [type]: [new BigNumber(0x123), new BigNumber(0x456), new BigNumber(0x789)] + }); + }); + }); + }); + + describe('outHwAccountInfo', () => { + it('returns objects with formatted addresses', () => { + expect(outHwAccountInfo( + { '0x63cf90d3f0410092fc0fca41846f596223979195': { manufacturer: 'mfg', name: 'type' } } + )).to.deep.equal({ + '0x63Cf90D3f0410092FC0fca41846f596223979195': { manufacturer: 'mfg', name: 'type' } + }); + }); + }); + + describe('outNodeKind', () => { + it('formats the input as received', () => { + const kind = { availability: 'personal', capability: 'full' }; + + expect(outNodeKind(kind)).to.deep.equal(kind); + }); + }); + + describe('outNumber', () => { + it('returns a BigNumber equalling the value', () => { + const bn = outNumber('0x123456'); + + expect(isBigNumber(bn)).to.be.true; + expect(bn.eq(0x123456)).to.be.true; + }); + + it('assumes 0 when ivalid input', () => { + expect(outNumber().eq(0)).to.be.true; + }); + }); + + describe('outPeer', () => { + it('converts all internal numbers to BigNumbers', () => { + expect(outPeer({ + caps: ['par/1'], + id: '0x01', + name: 'Parity', + network: { + localAddress: '10.0.0.1', + remoteAddress: '10.0.0.1' + }, + protocols: { + par: { + difficulty: '0x0f', + head: '0x02', + version: 63 + } + } + })).to.deep.equal({ + caps: ['par/1'], + id: '0x01', + name: 'Parity', + network: { + localAddress: '10.0.0.1', + remoteAddress: '10.0.0.1' + }, + protocols: { + par: { + difficulty: new BigNumber(15), + head: '0x02', + version: 63 + } + } + }); + }); + + it('does not output null protocols', () => { + expect(outPeer({ + caps: ['par/1'], + id: '0x01', + name: 'Parity', + network: { + localAddress: '10.0.0.1', + remoteAddress: '10.0.0.1' + }, + protocols: { + les: null + } + })).to.deep.equal({ + caps: ['par/1'], + id: '0x01', + name: 'Parity', + network: { + localAddress: '10.0.0.1', + remoteAddress: '10.0.0.1' + }, + protocols: {} + }); + }); + }); + + describe('outPeers', () => { + it('converts all internal numbers to BigNumbers', () => { + expect(outPeers({ + active: 789, + connected: '456', + max: 0x7b, + peers: [ + { + caps: ['par/1'], + id: '0x01', + name: 'Parity', + network: { + localAddress: '10.0.0.1', + remoteAddress: '10.0.0.1' + }, + protocols: { + par: { + difficulty: '0x0f', + head: '0x02', + version: 63 + }, + les: null + } + } + ] + })).to.deep.equal({ + active: new BigNumber(789), + connected: new BigNumber(456), + max: new BigNumber(123), + peers: [ + { + caps: ['par/1'], + id: '0x01', + name: 'Parity', + network: { + localAddress: '10.0.0.1', + remoteAddress: '10.0.0.1' + }, + protocols: { + par: { + difficulty: new BigNumber(15), + head: '0x02', + version: 63 + } + } + } + ] + }); + }); + }); + + describe('outReceipt', () => { + ['contractAddress'].forEach((input) => { + it(`formats ${input} address as address`, () => { + const block = {}; + + block[input] = address; + const formatted = outReceipt(block)[input]; + + expect(isAddress(formatted)).to.be.true; + expect(formatted).to.equal(checksum); + }); + }); + + ['blockNumber', 'cumulativeGasUsed', 'cumulativeGasUsed', 'gasUsed', 'transactionIndex'].forEach((input) => { + it(`formats ${input} number as hexnumber`, () => { + const block = {}; + + block[input] = 0x123; + const formatted = outReceipt(block)[input]; + + expect(isInstanceOf(formatted, BigNumber)).to.be.true; + expect(formatted.toString(16)).to.equal('123'); + }); + }); + + it('ignores and passes through unknown keys', () => { + expect(outReceipt({ someRandom: 'someRandom' })).to.deep.equal({ someRandom: 'someRandom' }); + }); + + it('formats a receipt with all the info converted', () => { + expect( + outReceipt({ + contractAddress: address, + blockNumber: '0x100', + cumulativeGasUsed: '0x101', + gasUsed: '0x102', + transactionIndex: '0x103', + extraData: 'someExtraStuffInHere' + }) + ).to.deep.equal({ + contractAddress: checksum, + blockNumber: new BigNumber('0x100'), + cumulativeGasUsed: new BigNumber('0x101'), + gasUsed: new BigNumber('0x102'), + transactionIndex: new BigNumber('0x103'), + extraData: 'someExtraStuffInHere' + }); + }); + }); + + describe('outRecentDapps', () => { + it('formats the URLs with timestamps', () => { + expect(outRecentDapps({ testing: 0x57513668 })).to.deep.equal({ + testing: new Date('2016-06-03T07:48:56.000Z') + }); + }); + }); + + describe('outSyncing', () => { + ['currentBlock', 'highestBlock', 'startingBlock', 'warpChunksAmount', 'warpChunksProcessed'].forEach((input) => { + it(`formats ${input} numbers as a number`, () => { + expect(outSyncing({ [input]: '0x123' })).to.deep.equal({ + [input]: new BigNumber('0x123') + }); + }); + }); + + it('formats blockGap properly', () => { + expect(outSyncing({ blockGap: [0x123, 0x456] })).to.deep.equal({ + blockGap: [new BigNumber(0x123), new BigNumber(0x456)] + }); + }); + }); + + describe('outTransaction', () => { + ['from', 'to'].forEach((input) => { + it(`formats ${input} address as address`, () => { + const block = {}; + + block[input] = address; + const formatted = outTransaction(block)[input]; + + expect(isAddress(formatted)).to.be.true; + expect(formatted).to.equal(checksum); + }); + }); + + ['blockNumber', 'gasPrice', 'gas', 'nonce', 'transactionIndex', 'value'].forEach((input) => { + it(`formats ${input} number as hexnumber`, () => { + const block = {}; + + block[input] = 0x123; + const formatted = outTransaction(block)[input]; + + expect(isInstanceOf(formatted, BigNumber)).to.be.true; + expect(formatted.toString(16)).to.equal('123'); + }); + }); + + it('passes condition as null when null', () => { + expect(outTransaction({ condition: null })).to.deep.equal({ condition: null }); + }); + + it('ignores and passes through unknown keys', () => { + expect(outTransaction({ someRandom: 'someRandom' })).to.deep.equal({ someRandom: 'someRandom' }); + }); + + it('formats a transaction with all the info converted', () => { + expect( + outTransaction({ + from: address, + to: address, + blockNumber: '0x100', + gasPrice: '0x101', + gas: '0x102', + nonce: '0x103', + transactionIndex: '0x104', + value: '0x105', + extraData: 'someExtraStuffInHere' + }) + ).to.deep.equal({ + from: checksum, + to: checksum, + blockNumber: new BigNumber('0x100'), + gasPrice: new BigNumber('0x101'), + gas: new BigNumber('0x102'), + nonce: new BigNumber('0x103'), + transactionIndex: new BigNumber('0x104'), + value: new BigNumber('0x105'), + extraData: 'someExtraStuffInHere' + }); + }); + }); + + describe('outTrace', () => { + it('ignores and passes through unknown keys', () => { + expect(outTrace({ someRandom: 'someRandom' })).to.deep.equal({ someRandom: 'someRandom' }); + }); + + it('formats a trace with all the info converted', () => { + const formatted = outTrace({ + type: 'call', + action: { + from: address, + to: address, + value: '0x06', + gas: '0x07', + input: '0x1234', + callType: 'call' + }, + result: { + gasUsed: '0x08', + output: '0x5678' + }, + traceAddress: [ '0x2' ], + subtraces: 3, + transactionPosition: '0xb', + transactionHash: '0x000000000000000000000000000000000000000000000000000000000000000c', + blockNumber: '0x0d', + blockHash: '0x000000000000000000000000000000000000000000000000000000000000000e' + }); + + expect(isBigNumber(formatted.action.gas)).to.be.true; + expect(formatted.action.gas.toNumber()).to.equal(7); + expect(isBigNumber(formatted.action.value)).to.be.true; + expect(formatted.action.value.toNumber()).to.equal(6); + + expect(formatted.action.from).to.equal(checksum); + expect(formatted.action.to).to.equal(checksum); + + expect(isBigNumber(formatted.blockNumber)).to.be.true; + expect(formatted.blockNumber.toNumber()).to.equal(13); + expect(isBigNumber(formatted.transactionPosition)).to.be.true; + expect(formatted.transactionPosition.toNumber()).to.equal(11); + }); + }); + + describe('outVaultMeta', () => { + it('returns an exmpt object on null', () => { + expect(outVaultMeta(null)).to.deep.equal({}); + }); + + it('returns the original value if not string', () => { + expect(outVaultMeta({ test: 123 })).to.deep.equal({ test: 123 }); + }); + + it('returns an object from JSON string', () => { + expect(outVaultMeta('{"test":123}')).to.deep.equal({ test: 123 }); + }); + + it('returns an empty object on invalid JSON', () => { + expect(outVaultMeta('{"test"}')).to.deep.equal({}); + }); + }); +}); diff --git a/js/src/api/index.js b/js/src/api/index.js new file mode 100644 index 0000000000000000000000000000000000000000..978d98fecaf151b8755a8d9869287774f7a63200 --- /dev/null +++ b/js/src/api/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 './api'; diff --git a/js/src/api/local/accounts/account.js b/js/src/api/local/accounts/account.js new file mode 100644 index 0000000000000000000000000000000000000000..224a05cd9f81f1e399d727615298f4840c68e822 --- /dev/null +++ b/js/src/api/local/accounts/account.js @@ -0,0 +1,116 @@ +// 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 { createKeyObject, decryptPrivateKey } from '../ethkey'; + +export default class Account { + constructor (persist, data = {}) { + const { + keyObject = null, + meta = {}, + name = '' + } = data; + + this._persist = persist; + this._keyObject = keyObject; + this._name = name; + this._meta = meta; + } + + isValidPassword (password) { + if (!this._keyObject) { + return false; + } + + return decryptPrivateKey(this._keyObject, password) + .then((privateKey) => { + if (!privateKey) { + return false; + } + + return true; + }); + } + + export () { + const exported = Object.assign({}, this._keyObject); + + exported.meta = JSON.stringify(this._meta); + exported.name = this._name; + + return exported; + } + + get address () { + return `0x${this._keyObject.address.toLowerCase()}`; + } + + get name () { + return this._name; + } + + set name (name) { + this._name = name; + + this._persist(); + } + + get meta () { + return JSON.stringify(this._meta); + } + + set meta (meta) { + this._meta = JSON.parse(meta); + + this._persist(); + } + + get uuid () { + if (!this._keyObject) { + return null; + } + + return this._keyObject.id; + } + + decryptPrivateKey (password) { + return decryptPrivateKey(this._keyObject, password); + } + + changePassword (key, password) { + return createKeyObject(key, password).then((keyObject) => { + this._keyObject = keyObject; + + this._persist(); + }); + } + + static fromPrivateKey (persist, key, password) { + return createKeyObject(key, password).then((keyObject) => { + const account = new Account(persist, { keyObject }); + + return account; + }); + } + + toJSON () { + return { + keyObject: this._keyObject, + name: this._name, + meta: this._meta + }; + } +} diff --git a/js/src/api/local/accounts/accounts.js b/js/src/api/local/accounts/accounts.js new file mode 100644 index 0000000000000000000000000000000000000000..d11ea2badb65c571dea36bb45bda8b4196b5c787 --- /dev/null +++ b/js/src/api/local/accounts/accounts.js @@ -0,0 +1,229 @@ +// 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 Account from './account'; +import localStore from 'store'; +import { debounce } from 'lodash'; +import { decryptPrivateKey } from '../ethkey'; + +const NULL_ADDRESS = '0x0000000000000000000000000000000000000000'; +const LS_STORE_KEY = '_parity::localAccounts'; + +export default class Accounts { + persist = debounce(() => { + this._lastState = JSON.stringify(this); + + localStore.set(LS_STORE_KEY, this); + }, 100); + + constructor (data = localStore.get(LS_STORE_KEY) || {}) { + this._lastState = JSON.stringify(data); + + window.addEventListener('storage', ({ key, newValue }) => { + if (key !== LS_STORE_KEY) { + return; + } + + if (newValue !== this._lastState) { + console.log('Data changed in a second tab, syncing state'); + + this.restore(JSON.parse(newValue)); + } + }); + + this.restore(data); + } + + restore (data) { + const { + last = NULL_ADDRESS, + dappsDefault = NULL_ADDRESS, + store = {} + } = data; + + this._last = last; + this._dappsDefaultAddress = dappsDefault; + this._store = {}; + + if (Array.isArray(store)) { + // Recover older version that stored accounts as an array + store.forEach((data) => { + const account = new Account(this.persist, data); + + this._store[account.address] = account; + }); + } else { + Object.keys(store).forEach((key) => { + this._store[key] = new Account(this.persist, store[key]); + }); + } + } + + _addAccount = (account) => { + const { address } = account; + + if (address in this._store && this._store[address].uuid) { + throw new Error(`Account ${address} already exists!`); + } + + this._store[address] = account; + this.lastAddress = address; + + this.persist(); + + return account.address; + } + + create (secret, password) { + const privateKey = Buffer.from(secret.slice(2), 'hex'); + + return Account + .fromPrivateKey(this.persist, privateKey, password) + .then(this._addAccount); + } + + restoreFromWallet (wallet, password) { + return decryptPrivateKey(wallet, password) + .then((privateKey) => { + if (!privateKey) { + throw new Error('Invalid password'); + } + + return Account.fromPrivateKey(this.persist, privateKey, password); + }) + .then(this._addAccount); + } + + set lastAddress (value) { + this._last = value.toLowerCase(); + } + + get lastAddress () { + return this._last; + } + + get dappsDefaultAddress () { + if (this._dappsDefaultAddress === NULL_ADDRESS) { + return this._last; + } + + if (this._dappsDefaultAddress in this._store) { + return this._dappsDefaultAddress; + } + + return NULL_ADDRESS; + } + + set dappsDefaultAddress (value) { + this._dappsDefaultAddress = value.toLowerCase(); + + this.persist(); + } + + get (address) { + address = address.toLowerCase(); + + const account = this._store[address]; + + if (!account) { + throw new Error(`Account not found: ${address}`); + } + + this.lastAddress = address; + + return account; + } + + getLazyCreate (address) { + address = address.toLowerCase(); + + this.lastAddress = address; + + if (!(address in this._store)) { + this._store[address] = new Account(this.persist); + } + + return this._store[address]; + } + + remove (address, password) { + address = address.toLowerCase(); + + const account = this.get(address); + + if (!account) { + return false; + } + + if (!account.uuid) { + this.removeUnsafe(address); + + return true; + } + + return account + .isValidPassword(password) + .then((isValid) => { + if (!isValid) { + return false; + } + + if (address === this.lastAddress) { + this.lastAddress = NULL_ADDRESS; + } + + this.removeUnsafe(address); + + return true; + }); + } + + removeUnsafe (address) { + address = address.toLowerCase(); + + delete this._store[address]; + + this.persist(); + } + + allAddresses () { + return Object.keys(this._store); + } + + accountAddresses () { + return Object + .keys(this._store) + .filter((address) => this._store[address].uuid); + } + + map (mapper) { + const result = {}; + + Object.keys(this._store).forEach((key) => { + result[key] = mapper(this._store[key]); + }); + + return result; + } + + toJSON () { + return { + last: this._last, + dappsDefault: this._dappsDefaultAddress, + store: this._store + }; + } +} diff --git a/js/src/api/local/accounts/index.js b/js/src/api/local/accounts/index.js new file mode 100644 index 0000000000000000000000000000000000000000..800cf95f6a02f7c5eb978fadf7cdc2932d40ec37 --- /dev/null +++ b/js/src/api/local/accounts/index.js @@ -0,0 +1,21 @@ +// 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 Accounts from './accounts'; + +const accounts = new Accounts(); + +export default accounts; diff --git a/js/src/api/local/ethkey/ethkey.js b/js/src/api/local/ethkey/ethkey.js new file mode 100644 index 0000000000000000000000000000000000000000..f8395b007abc3e0be2daccfff388baf07c6c1218 --- /dev/null +++ b/js/src/api/local/ethkey/ethkey.js @@ -0,0 +1,147 @@ +// 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 . + +/* global WebAssembly */ + +import wasmBuffer from './ethkey.wasm.js'; + +const NOOP = () => {}; + +// WASM memory setup +const WASM_PAGE_SIZE = 65536; +const STATIC_BASE = 1024; +const STATICTOP = STATIC_BASE + WASM_PAGE_SIZE * 2; +const STACK_BASE = align(STATICTOP + 16); +const STACKTOP = STACK_BASE; +const TOTAL_STACK = 5 * 1024 * 1024; +const TOTAL_MEMORY = 16777216; +const STACK_MAX = STACK_BASE + TOTAL_STACK; +const DYNAMIC_BASE = STACK_MAX + 64; +const DYNAMICTOP_PTR = STACK_MAX; + +function mockWebAssembly () { + function throwWasmError () { + throw new Error('Missing WebAssembly support'); + } + + // Simple mock replacement + return { + Memory: class { buffer = new ArrayBuffer(2048) }, + Table: class {}, + Module: class {}, + Instance: class { + exports = { + '_input_ptr': () => 0, + '_secret_ptr': () => 0, + '_public_ptr': () => 0, + '_address_ptr': () => 0, + '_ecpointg': NOOP, + '_brain': throwWasmError, + '_verify_secret': throwWasmError + } + } + }; +} + +const { Memory, Table, Module, Instance } = typeof WebAssembly !== 'undefined' ? WebAssembly : mockWebAssembly(); + +const wasmMemory = new Memory({ + initial: TOTAL_MEMORY / WASM_PAGE_SIZE, + maximum: TOTAL_MEMORY / WASM_PAGE_SIZE +}); + +const wasmTable = new Table({ + initial: 8, + maximum: 8, + element: 'anyfunc' +}); + +// TypedArray views into the memory +const wasmMemoryU8 = new Uint8Array(wasmMemory.buffer); +const wasmMemoryU32 = new Uint32Array(wasmMemory.buffer); + +// Keep DYNAMIC_BASE in memory +wasmMemoryU32[DYNAMICTOP_PTR >> 2] = align(DYNAMIC_BASE); + +function align (mem) { + const ALIGN_SIZE = 16; + + return (Math.ceil(mem / ALIGN_SIZE) * ALIGN_SIZE) | 0; +} + +export function slice (ptr, len) { + return wasmMemoryU8.subarray(ptr, ptr + len); +} + +// Required by emscripten +function abort (what) { + throw new Error(what || 'WASM abort'); +} + +// Required by emscripten +function abortOnCannotGrowMemory () { + abort(`Cannot enlarge memory arrays.`); +} + +// Required by emscripten +function enlargeMemory () { + abortOnCannotGrowMemory(); +} + +// Required by emscripten +function getTotalMemory () { + return TOTAL_MEMORY; +} + +// Required by emscripten - used to perform memcpy on large data +function memcpy (dest, src, len) { + wasmMemoryU8.set(wasmMemoryU8.subarray(src, src + len), dest); + + return dest; +} + +// Synchronously compile WASM from the buffer +const module = new Module(wasmBuffer); + +// Instantiated WASM module +const instance = new Instance(module, { + global: {}, + env: { + DYNAMICTOP_PTR, + STACKTOP, + STACK_MAX, + abort, + enlargeMemory, + getTotalMemory, + abortOnCannotGrowMemory, + ___lock: NOOP, + ___syscall6: () => 0, + ___setErrNo: (no) => no, + _abort: abort, + ___syscall140: () => 0, + _emscripten_memcpy_big: memcpy, + ___syscall54: () => 0, + ___unlock: NOOP, + _llvm_trap: abort, + ___syscall146: () => 0, + 'memory': wasmMemory, + 'table': wasmTable, + tableBase: 0, + memoryBase: STATIC_BASE + } +}); + +export const extern = instance.exports; diff --git a/js/src/api/local/ethkey/ethkey.wasm.js b/js/src/api/local/ethkey/ethkey.wasm.js new file mode 100644 index 0000000000000000000000000000000000000000..d426f0e0c46e5a947183538170fb44a56e01fb1b --- /dev/null +++ b/js/src/api/local/ethkey/ethkey.wasm.js @@ -0,0 +1 @@ +module.exports = new Buffer('AGFzbQEAAAABOgpgA39/fwF/YAF/AX9gAn9/AX9gAX8AYAABf2AAAGACf38AYAR/f39/AGADf39/AGAGf39/f39/AX8CyQELA2VudghTVEFDS1RPUAN/AANlbnYFYWJvcnQAAwNlbnYLX19fc3lzY2FsbDYAAgNlbnYNX19fc3lzY2FsbDE0MAACA2VudhZfZW1zY3JpcHRlbl9tZW1jcHlfYmlnAAADZW52DF9fX3N5c2NhbGw1NAACA2VudgpfbGx2bV90cmFwAAUDZW52DV9fX3N5c2NhbGwxNDYAAgNlbnYGbWVtb3J5AgGAAoACA2VudgV0YWJsZQFwAQgIA2Vudgl0YWJsZUJhc2UDfwADOjkEBAQEBAQDBwMICAgIAwYGBgIIAwMDBgMDCAYGBgMCBwYIAgICBgkDBgEAAAEEBAQAAQAAAQABAAIGBgF/ASMACwdfBw5fdmVyaWZ5X3NlY3JldAAMC19wdWJsaWNfcHRyAAoLX3NlY3JldF9wdHIACQpfaW5wdXRfcHRyAAgJX2VjcG9pbnRnAAcGX2JyYWluAA0MX2FkZHJlc3NfcHRyAAsJDgEAIwELCD0wPjEyNz8rCpClAzlJAQJ/An8jAiEAIwJB0JgBaiQCQagZKAIAQQFGBEAgACQCQawZDwsgACIBEB9BqBlBATYCAEGsGSABQdCYARA8GiAAJAJBrBkLCwYAQbi2AQsGAEG4vgELBgBB2L4BCwYAQZi/AQsKAEG4vgFBIBAlC74HAQt/AkAjAiEIIwJBoJwBaiQCIABBgAhLBEAgAEGACBAnCyAIQaACaiEFQagZKAIAQQFHBEAgBRAfQagZQQE2AgBBrBkgBUHQmAEQPBoLIAghCSAIQcgAaiIEEBQgCEH4mwFqIgFCADcAACABQgA3AAggAUIANwAQIAFCADcAGCAEQbi2ASAAEBAgBSAEQdgBEDwaIAUgAUEgEBIgCEHwmgFqIgMgASkAADcAACADIAEpAAg3AAggAyABKQAQNwAQIAMgASkAGDcAGCAIQZCbAWoiBkEBaiEHIAhB2JsBaiICQQxqIQpBACEAA0AgBBAUIAFCADcAACABQgA3AAggAUIANwAQIAFCADcAGCAEIANBIBAQIAUgBEHYARA8GiAFIAFBIBASIAIgASkAADcAACACIAEpAAg3AAggAiABKQAQNwAQIAIgASkAGDcAGCADIAIpAAA3AAAgAyACKQAINwAIIAMgAikAEDcAECADIAIpABg3ABggAEEBaiELIABBgYABRwRAIAshAAwBCwsgCUEBaiEAA0ACQCAJQawZIANBIBAmIAksAABBAUYEQCAGIAApAAA3AAAgBiAAKQAINwAIIAYgACkAEDcAECAGIAApABg3ABggBiAAKQAgNwAgIAYgACkAKDcAKCAGIAApADA3ADAgBiAAKQA4NwA4IAYgACwAQDoAQCAEEBQgAUIANwAAIAFCADcACCABQgA3ABAgAUIANwAYIAQgB0HAABAQIAUgBEHYARA8GiAFIAFBIBASIAIgASkAADcAACACIAEpAAg3AAggAiABKQAQNwAQIAIgASkAGDcAGEGYvwEgCikAADcAAEGgvwEgCikACDcAAEGovwEgCigAEDYAAEGYvwEsAABFDQELIAQQFCABQgA3AAAgAUIANwAIIAFCADcAECABQgA3ABggBCADQSAQECAFIARB2AEQPBogBSABQSAQEiACIAEpAAA3AAAgAiABKQAINwAIIAIgASkAEDcAECACIAEpABg3ABggAyACKQAANwAAIAMgAikACDcACCADIAIpABA3ABAgAyACKQAYNwAYDAELC0HYvgEgBykAADcAAEHgvgEgBykACDcAAEHovgEgBykAEDcAAEHwvgEgBykAGDcAAEH4vgEgBykAIDcAAEGAvwEgBykAKDcAAEGIvwEgBykAMDcAAEGQvwEgBykAODcAAEG4vgEgAykAADcAAEHAvgEgAykACDcAAEHIvgEgAykAEDcAAEHQvgEgAykAGDcAACAIJAILCwQAEAUL1wsCGX8tfgJAIAApAwAhJCAAQShqIgIpAwAhJSAAQdAAaiIDKQMAITAgAEH4AGoiBCkDACE+IABBoAFqIgUpAwAhJiAAQQhqIgYpAwAhMSAAQTBqIgcpAwAhMiAAQdgAaiIIKQMAIScgAEGAAWoiCSkDACEgIABBqAFqIgopAwAhPyAAQRBqIgspAwAhMyAAQThqIgwpAwAhHSAAQeAAaiINKQMAISEgAEGIAWoiDikDACE0IABBsAFqIg8pAwAhHiAAQRhqIhApAwAhNSAAQcAAaiIRKQMAISggAEHoAGoiEikDACE2IABBkAFqIhMpAwAhGyAAQbgBaiIUKQMAISIgAEEgaiIVKQMAITcgAEHIAGoiFikDACE4IABB8ABqIhcpAwAhHCAAQZgBaiIYKQMAISMgAEHAAWoiGSkDACEaQQAhAQNAICQgGiAjIBwgOCA3hYWFhSIphSA/ICAgJyAyIDGFhYWFIipCAYYgKkI/iIQiOYUhQCAxICYgPiAwICUgJIWFhYUiK4UgHiA0ICEgHSAzhYWFhSIsQgGGICxCP4iEIjqFIUEgMyAqhSAiIBsgNiAoIDWFhYWFIi1CAYYgLUI/iIQiO4UhQiA1ICyFIClCAYYgKUI/iIQiPIUhLiA3IC2FICtCAYYgK0I/iIQiPYUhQyAbICyFIDyFIhtCFYYgG0IriIQiHyAhICqFIDuFIhtCK4YgG0IViIQiREJ/hYMgMiArhSA6hSIbQiyGIBtCFIiEIiSFITEgGiAthSA9hSIaQg6GIBpCMoiEIhogH0J/hYMgRIUhMyBAIBpCf4WDIB+FITUgJCBAQn+FgyAahSE3IDAgKYUgOYUiGkIDhiAaQj2IhCIvIDggLYUgPYUiGkIUhiAaQiyIhCIhQn+FgyAuQhyGIC5CJIiEIh+FIUUgICArhSA6hSIaQi2GIBpCE4iEIhsgL0J/hYMgIYUhMiAeICqFIDuFIhpCPYYgGkIDiIQiGiAbQn+FgyAvhSFGIB8gGkJ/hYMgG4UhLiAhIB9Cf4WDIBqFITggNiAshSA8hSIaQhmGIBpCJ4iEIiAgHSAqhSA7hSIaQgaGIBpCOoiEIhtCf4WDIEFCAYYgQUI/iIQiHYUhMCAjIC2FID2FIhpCCIYgGkI4iIQiHiAgQn+FgyAbhSEvICYgKYUgOYUiGkIShiAaQi6IhCIaIB5Cf4WDICCFISEgHSAaQn+FgyAehSE2IBsgHUJ/hYMgGoUhHyAnICuFIDqFIhpCCoYgGkI2iIQiGyAlICmFIDmFIhpCJIYgGkIciIQiI0J/hYMgQ0IbhiBDQiWIhCIdhSEnIDQgKoUgO4UiGkIPhiAaQjGIhCIeIBtCf4WDICOFISAgIiAshSA8hSIaQjiGIBpCCIiEIhogHkJ/hYMgG4UhNCAdIBpCf4WDIB6FIRsgIyAdQn+FgyAahSEjIBwgLYUgPYUiHEInhiAcQhmIhCIiICggLIUgPIUiHEI3hiAcQgmIhCIlQn+FgyBCQj6GIEJCAoiEIiaFIR0gPiAphSA5hSIcQimGIBxCF4iEIhogIkJ/hYMgJYUhHiA/ICuFIDqFIhxCAoYgHEI+iIQiHCAaQn+FgyAihSEoICYgHEJ/hYMgGoUhIiAlICZCf4WDIByFIRogRCAkQn+FgyBAhSABQQN0QYAIaikDAIUhHCABQQFqIgFBGEcEQCAcISQgRSElICchPiAdISYgLyEnIB4hPyBGIR0gKCEeIC4hKCAfIRwMAQsLIAAgHDcDACACIEU3AwAgAyAwNwMAIAQgJzcDACAFIB03AwAgBiAxNwMAIAcgMjcDACAIIC83AwAgCSAgNwMAIAogHjcDACALIDM3AwAgDCBGNwMAIA0gITcDACAOIDQ3AwAgDyAoNwMAIBAgNTcDACARIC43AwAgEiA2NwMAIBMgGzcDACAUICI3AwAgFSA3NwMAIBYgODcDACAXIB83AwAgGCAjNwMAIBkgGjcDAAsLCgAgACABIAIQEQu9AwEPfwJAIABByAFqIgooAgAiA0HIAUsEQCADQcgBEC8LIAAgA2ohC0HIASADayEEAkAgAEHMAWoiECgCACADayIFIAJLBEAgBCEMIAshDSADIQ9BACEIIAIhBgUgBCEJQQAhByACIQ4gBSEEAkACQAJAA0AgBCAJSw0BIAcgAksNAiABIAdqIREgBCACIAdrIgNNBH8gBAUgAwsiCQRAIAshA0EAIQUDQCADIAMsAAAgESAFaiwAAHM6AAAgCyAFQQFqIgVqIQMgBSAJSQ0ACwsgABAPIAQgB2ohAyAQKAIAIQUgCkEANgIAIA4gBGsiDiAFSQRAQcgBIQwgACENQQAhDyADIQggDiEGDAYFQcgBIQkgACELIAMhByAFIQQMAQsACwALIAQgCRAnDAELIAcgAhAvCwsLIAYgDEsEQCAGIAwQJwsgCCACSwRAIAggAhAvCyAGIAIgCGsiAE0EfyAGBSAACyIEBEAgDSEAQQAhAgUgCiAPIAZqNgIADwsgASAIaiEBA0AgACAALAAAIAEgAmosAABzOgAAIA0gAkEBaiICaiEAIAIgBEkNAAsgCiAKKAIAIAZqNgIACwuSAQEDfwJAIwIhBCMCQeABaiQCIAQiAyAAQdgBEDwaIAMoAsgBIgBByAFPBEBB3AogAEHIARAoCyADKALMASEFIAMgAGoiACAALAAAIAMsANABczoAACAFQX9qIgBByAFJBEAgAyAAaiIAIAAsAABBgH9zOgAAIAMQDyADIAEgAhATIAQkAgVB0AogAEHIARAoCwsLygEBB38CQAJAIAAoAswBIgNByAFLBEAgAyACSwRAIAEhCCACIQYFIANByAEQJwsFIAIhBEEAIQUCQAJAAkACQANAIAEgBWohCSACIAVrIQcgBCADSQ0DIAMgB0sNAiAJIAAgAxA8GiAAEA8gBCADayEEIAUgA2oiBSACTQ0ADAELAAsgBSACEC8MAgsgAyAHECcMAQsgBCAHTQRAIAkhCCAEIQYMAwsgBCAHECcLCwsgBkHIAUsEQCAGQcgBECcFIAggACAGEDwaCwsLIAACQCAAQQBBzAEQOhogAEGIATYCzAEgAEEBOgDQAQsLgQUCCH8DfgJAIwIhBSMCQdAAaiQCIAUhAiAAQcQAaiIDLAAAIgcgASwAREcEQCAHBEAgA0EAOgAAIAAgARAWIAMgAywAAEEBczoAAAUgAiABKQIANwIAIAIgASkCCDcCCCACIAEpAhA3AhAgAiABKQIYNwIYIAIgASkCIDcCICACIAEpAig3AiggAiABKQIwNwIwIAIgASkCODcCOCACIAEpAkA3AkAgAkEAOgBEIAAgAhAWCyAAKAIABEAgBSQCDwsgACgCBARAIAUkAg8LIANBADoAACAFJAIPCwJAIAEoAgAiAyAAKAIAIgJLBEACQAJAA0AgAkEQTw0CIABBBGogAkECdGpBADYCACACQQFqIgIgA0kNAAwBCwALIAAgAzYCAEIAIQtBACEEDAILQcgLIAJBEBAoBSADBEBCACELQQAhBAUgBSQCDwsLCwJAAkADQCAEQRBPDQEgAEEEaiAEQQJ0aiICKAIArSALfCABQQRqIARBAnRqKAIArXwhCiACIAo+AgAgCkIgiCEKIARBAWoiBiADSQRAIAohCyAGIQQMAQsLDAELQdQLIARBEBAoCyAKQgBSIgQgBiAAKAIAIgFJcQRAIAYhAQJAAkADQCABQRBPDQEgAEEEaiABQQJ0aiICKAIArSAKfCEKIAIgCj4CACAKQiCIIgxCAFIiCCABQQFqIgEgACgCACIJSXEEQCAMIQoMAQsLDAELQeALIAFBEBAoCyAMpyECIAgEQCAJIQEFIAUkAg8LBSAKpyECIARFBEAgBSQCDwsLIAFBEE8EQEHsCyABQRAQKAsgAEEEaiABQQJ0aiACNgIAIAAgACgCAEEBajYCACAFJAILC+YHAg1/An4CQCMCIQUjAkHQAGokAiAFIQIgAEHEAGoiBiwAACIDIAEsAERHBEAgAwRAIAZBADoAACAAIAEQFSAGQQE6AAAFIAIgASkCADcCACACIAEpAgg3AgggAiABKQIQNwIQIAIgASkCGDcCGCACIAEpAiA3AiAgAiABKQIoNwIoIAIgASkCMDcCMCACIAEpAjg3AjggAiABKQJANwJAIAJBADoARCAAIAIQFQsgACgCAARAIAUkAg8LIAAoAgQEQCAFJAIPCyAGQQA6AAAgBSQCDwsgACgCACIHQRBLBEAgB0EQECcLIAEoAgAiCEEQSwRAIAhBEBAnCyAAQQRqIQsCQAJAIAcgCEYEQCAAIAFHBEAgCyABQQRqIAdBAnQQOQRAIAchAwJAAkACQANAIANBf2ohCSADRQ0IIAlBEE8NASABQQRqIAlBAnRqKAIAIgMgAEEEaiAJQQJ0aigCACIORw0CIAkhAwwACwALQbwLIAlBEBAoDAELIAMgDkkNBQwECwsLIABBATYCACAGQQA6AAAgC0EANgIAIAUkAg8FIAggB08NAQsMAQsgAiAAKQIANwIAIAIgACkCCDcCCCACIAApAhA3AhAgAiAAKQIYNwIYIAIgACkCIDcCICACIAApAig3AiggAiAAKQIwNwIwIAIgACkCODcCOCACIAApAkA3AkAgACABKQIANwIAIAAgASkCCDcCCCAAIAEpAhA3AhAgACABKQIYNwIYIAAgASkCIDcCICAAIAEpAig3AiggACABKQIwNwIwIAAgASkCODcCOCAAIAEpAkA3AkAgACACEBYgBkEBOgAAIAUkAg8LAkAgCAR/QgAhD0EAIQMCQAJAA0AgA0EQTw0BIABBBGogA0ECdGoiBCgCAK0gAUEEaiADQQJ0aigCAK19IA98Ig9CIIchECAEIA8+AgAgA0EBaiIEIAhJBEAgECEPIAQhAwwBCwsMAQtB+AsgA0EQECgLAkAgEEIAUiAEIAAoAgAiAUlxBEADQCAEQRBJBEAgAEEEaiAEQQJ0aiIBKAIArSAQfCIPQiCHIRAgASAPPgIAIBBCAFIgBEEBaiIEIAAoAgAiAUlxDQEgASEMIAQhCgwDCwtBhAwgBEEQECgFIAEhDCAEIQoLCyAKIAxLBH8gACAKNgIAIAoFIAwLBSAHCyIBQQFLBEACQAJAA0AgAUF/aiIBQRBPDQIgAEEEaiABQQJ0aigCAA0BIAAgATYCACABQQFLDQAgASENDAQLAAsgBSQCDwtBsAsgAUEQECgFIAEhDQsLIA0gCygCAHIEQCAFJAIPCyAGQQA6AAAgBSQCCwv1GwIofwN+AkAjAiEgIwJB0ABqJAIgACgCACIKQQhGIAEoAgAiA0EIRnEEQCAAQQRqIg0oAgAiDkH//wNxIQQgAEEIaiIPKAIAIhBB//8DcSEIIABBDGoiESgCACISQf//A3EhBSAAQRBqIhMoAgAiFEH//wNxIQkgAEEUaiIVKAIAIhZB//8DcSEGIABBGGoiFygCACIYQf//A3EhAiAAQRxqIhkoAgAiGkH//wNxIQcgAEEgaiIbKAIAIiFB//8DcSELIAEoAgQiIkH//wNxIQMgASgCCCIjQf//A3EhCiABKAIMIiRB//8DcSEMIAEoAhAiJUH//wNxIRwgASgCFCImQf//A3EhHSABKAIYIidB//8DcSEeIAEoAhwiKEH//wNxIR8gASgCICIpQf//A3EhASANICJBEHYiDSAEbK0gAyAOQRB2Ig5srXwiKkIQhkKAgPz/D4MgAyAEbK18Iis+AgAgDyANIA5srSADIAhsrXwgCiAEbK18ICpCEIh8ICtCIIh8IA0gCGytIAMgEEEQdiIPbK18ICNBEHYiECAEbK18IAogDmytfCIqQhCGQoCA/P8Pg3wiKz4CACARIA0gD2ytIAMgBWytfCAKIAhsrXwgECAObK18IAwgBGytfCAqQhCIfCANIAVsrSADIBJBEHYiEWytfCAQIAhsrXwgCiAPbK18ICRBEHYiEiAEbK18IAwgDmytfCIqQhCGQoCA/P8Pg3wgK0IgiHwiKz4CACATIA0gEWytIAMgCWytfCAKIAVsrXwgECAPbK18IAwgCGytfCASIA5srXwgHCAEbK18ICpCEIh8IA0gCWytIAMgFEEQdiITbK18IBAgBWytfCAKIBFsrXwgEiAIbK18IAwgD2ytfCAlQRB2IhQgBGytfCAcIA5srXwiKkIQhkKAgPz/D4N8ICtCIIh8Iis+AgAgFSANIBNsrSADIAZsrXwgCiAJbK18IBAgEWytfCAMIAVsrXwgEiAPbK18IBwgCGytfCAUIA5srXwgHSAEbK18ICpCEIh8IA0gBmytIAMgFkEQdiIVbK18IBAgCWytfCAKIBNsrXwgEiAFbK18IAwgEWytfCAUIAhsrXwgHCAPbK18ICZBEHYiFiAEbK18IB0gDmytfCIqQhCGQoCA/P8Pg3wgK0IgiHwiKz4CACAXIA0gFWytIAMgAmytfCAKIAZsrXwgECATbK18IAwgCWytfCASIBFsrXwgHCAFbK18IBQgD2ytfCAdIAhsrXwgFiAObK18IB4gBGytfCAqQhCIfCANIAJsrSADIBhBEHYiF2ytfCAQIAZsrXwgCiAVbK18IBIgCWytfCAMIBNsrXwgFCAFbK18IBwgEWytfCAWIAhsrXwgHSAPbK18ICdBEHYiGCAEbK18IB4gDmytfCIqQhCGQoCA/P8Pg3wgK0IgiHwiKz4CACAZIA0gF2ytIAMgB2ytfCAKIAJsrXwgECAVbK18IAwgBmytfCASIBNsrXwgHCAJbK18IBQgEWytfCAdIAVsrXwgFiAPbK18IB4gCGytfCAYIA5srXwgHyAEbK18ICpCEIh8IA0gB2ytIAMgGkEQdiIZbK18IBAgAmytfCAKIBdsrXwgEiAGbK18IAwgFWytfCAUIAlsrXwgHCATbK18IBYgBWytfCAdIBFsrXwgGCAIbK18IB4gD2ytfCAoQRB2IhogBGytfCAfIA5srXwiKkIQhkKAgPz/D4N8ICtCIIh8Iis+AgAgGyANIBlsrSADIAtsrXwgCiAHbK18IBAgF2ytfCAMIAJsrXwgEiAVbK18IBwgBmytfCAUIBNsrXwgHSAJbK18IBYgEWytfCAeIAVsrXwgGCAPbK18IB8gCGytfCAaIA5srXwgASAEbK18ICpCEIh8IA0gC2ytIAMgIUEQdiIDbK18IBAgB2ytfCAKIBlsrXwgEiACbK18IAwgF2ytfCAUIAZsrXwgHCAVbK18IBYgCWytfCAdIBNsrXwgGCAFbK18IB4gEWytfCAaIAhsrXwgHyAPbK18IClBEHYiGyAEbK18IAEgDmytfCIqQhCGQoCA/P8Pg3wgK0IgiHwiKz4CACAAIAogC2ytIA0gA2ytfCAQIBlsrXwgDCAHbK18IBIgF2ytfCAcIAJsrXwgFCAVbK18IB0gBmytfCAWIBNsrXwgHiAJbK18IBggEWytfCAfIAVsrXwgGiAPbK18IAEgCGytfCAbIA5srXwgKkIQiHwgECALbK0gCiADbK18IBIgB2ytfCAMIBlsrXwgFCACbK18IBwgF2ytfCAWIAZsrXwgHSAVbK18IBggCWytfCAeIBNsrXwgGiAFbK18IB8gEWytfCAbIAhsrXwgASAPbK18IipCEIZCgID8/w+DfCArQiCIfCIrPgIkIAAgDCALbK0gECADbK18IBIgGWytfCAcIAdsrXwgFCAXbK18IB0gAmytfCAWIBVsrXwgHiAGbK18IBggE2ytfCAfIAlsrXwgGiARbK18IAEgBWytfCAbIA9srXwgKkIQiHwgEiALbK0gDCADbK18IBQgB2ytfCAcIBlsrXwgFiACbK18IB0gF2ytfCAYIAZsrXwgHiAVbK18IBogCWytfCAfIBNsrXwgGyAFbK18IAEgEWytfCIqQhCGQoCA/P8Pg3wgK0IgiHwiKz4CKCAAIBwgC2ytIBIgA2ytfCAUIBlsrXwgHSAHbK18IBYgF2ytfCAeIAJsrXwgGCAVbK18IB8gBmytfCAaIBNsrXwgASAJbK18IBsgEWytfCAqQhCIfCAUIAtsrSAcIANsrXwgFiAHbK18IB0gGWytfCAYIAJsrXwgHiAXbK18IBogBmytfCAfIBVsrXwgGyAJbK18IAEgE2ytfCIqQhCGQoCA/P8Pg3wgK0IgiHwiKz4CLCAAIB0gC2ytIBQgA2ytfCAWIBlsrXwgHiAHbK18IBggF2ytfCAfIAJsrXwgGiAVbK18IAEgBmytfCAbIBNsrXwgKkIQiHwgFiALbK0gHSADbK18IBggB2ytfCAeIBlsrXwgGiACbK18IB8gF2ytfCAbIAZsrXwgASAVbK18IipCEIZCgID8/w+DfCArQiCIfCIrPgIwIAAgHiALbK0gFiADbK18IBggGWytfCAfIAdsrXwgGiAXbK18IAEgAmytfCAbIBVsrXwgKkIQiHwgGCALbK0gHiADbK18IBogB2ytfCAfIBlsrXwgGyACbK18IAEgF2ytfCIqQhCGQoCA/P8Pg3wgK0IgiHwiKz4CNCAAIB8gC2ytIBggA2ytfCAaIBlsrXwgASAHbK18IBsgF2ytfCAqQhCIfCAaIAtsrSAfIANsrXwgGyAHbK18IAEgGWytfCIqQhCGQoCA/P8Pg3wgK0IgiHwiKz4COCAAIBsgGWytIBogA2ytfCABIAtsrXwgKkIQiHwgGyALbK0gASADbK18IipCEIZCgID8/w+DfCArQiCIfCIrPgI8IABBDzYCACAqQhCIIBsgA2ytfCArQiCIfCIqQgBRBEAgICQCDwsgACAqPgJAIABBEDYCACAgJAIPCyAgIQIgA0EBRgRAIAEoAgQhCCACIAApAgA3AgAgAiAAKQIINwIIIAIgACkCEDcCECACIAApAhg3AhggAiAAKQIgNwIgIAIgACkCKDcCKCACIAApAjA3AjAgAiAAKQI4NwI4IAIgACkCQDcCQCACKAIAIgFBEEsEQCABQRAQJwsgAkEEaiABQQJ0aiEEIAEEQCAIrSErQgAhKiACQQRqIQADQCAAIAAoAgCtICt+IixC/////w+DICp8Iio+AgAgKkIgiCAsQiCIfCEqIABBBGoiACAERw0ACyAqQgBSBEAgAUEQSQRAIAQgKj4CACACIAFBAWo2AgAFQZwMIAFBEBAoCwsLICAkAg8LIAJBADoARCACIApBf2ogA2oiBzYCACACQQhqIgRCADcCACAEQgA3AgggBEIANwIQIARCADcCGCAEQgA3AiAgBEIANwIoIARCADcCMCAEQQA2AjggAiABKAIErSAAKAIErX4iKj4CBCAqQiCIISoCQCAHQQFLBEBBAiEEQQEhBgJAAkACQAJAA0AgKkIgiCEsICqnIQkgBkEBaiILIAprIQUgBiAKSwR/IAUFQQAiBQsgAyAGSwR/IAsFIAMiCwtJBEAgLCEqA0AgBiAFayIMQRBPDQMgBUEQTw0EIAFBBGogBUECdGooAgCtIABBBGogDEECdGooAgCtfiAJrXwiLEIgiCAqfCEqICynIQkgBUEBaiIFIAtJDQALBSAsISoLIAZBEE8NAyACQQRqIAZBAnRqIAk2AgAgBEEBaiEJIAQgB0kEQCAEIQUgCSEEIAUhBgwBBSAqISsMBwsACwALQagMIAxBEBAoDAILQbQMIAVBEBAoDAELQcAMIAZBEBAoCwUgKiErCwsgK0IAUQRAIAchCAUgB0EQSQRAIAJBBGogB0ECdGogKz4CACACIAogA2oiCDYCAAVBzAwgB0EQECgLCyAIQQFLBEAgCCEBAkACQAJAA0AgAUF/aiIEQRBPDQEgAkEEaiAEQQJ0aigCAA0CIARBAUsEQCAEIQEMAQUgBCEBDAMLAAsAC0GwCyAEQRAQKAwBCyACIAE2AgALCyAAIAIpAgA3AgAgACACKQIINwIIIAAgAikCEDcCECAAIAIpAhg3AhggACACKQIgNwIgIAAgAikCKDcCKCAAIAIpAjA3AjAgACACKQI4NwI4IAAgAikCQDcCQCAgJAILCyQAIAEEfyAAQYCAgIB4RiABQX9GcQR/QQAFIAAgAW0LBUEACwv5CQIOfwZ+AkAjAiEJIwJB0ARqJAIgCSIIQQBBhAQQOhpBASACQQFqQRh0QRh1QR9xdCIFQQIQGCEOIAlBiARqIgMgASkCADcCACADIAEpAgg3AgggAyABKQIQNwIQIAMgASkCGDcCGCADIAEpAiA3AiAgAyABKQIoNwIoIAMgASkCMDcCMCADIAEpAjg3AjggAyABKQJANwJAIAMoAgAiAUEBRyADQQRqIgcoAgAiBEEAR3JFBEAgACAIQYQEEDwaIAkkAg8LIAVBf2ohECAIQYAEaiEFIAJB/wFxIg9BP3GtIRNBICAPa0E/ca0hFCABIQIDQAJAAkACQCAEaCIBBEAgBSAFKAIAIAFqNgIAIAJBEEsEQEEGIQEMBAtBICABayEEIAIEQCABrSEVIARBP3GtIRZCACERIANBBGogAkECdGohAQNAIAFBfGoiASgCAK0hEiABIBIgFYggEYQ+AgAgEiAWhiERIAEgB0cNAAsgAygCACIBQQFNDQMgAUF/aiINQRBPBEBBDCEBDAULIANBBGogDUECdGooAgAEQCABIQIFIAMgDTYCACANIQEMBAsFQQAhAgsFIAUoAgAiCkGABEkhBiAEIBBxIgEgDkwEQCAGRQRAQSUhAQwFCyAIIApqIAE6AAAgBSAFKAIAIgZBAWo2AgAgByAEIAFrIgE2AgAgAUEARyACQQFHckUEQEEBIQEMBAsgBSAPIAZqNgIAIAJBEEsEQEEqIQEMBQsgAkUEQEEAIQIMAwtCACERIANBBGogAkECdGohAQNAIAFBfGoiASgCAK0hEiABIBIgE4ggEYQ+AgAgEiAUhiERIAEgB0cNAAsgAygCACIBQQFNDQMgAUF/aiILQRBPBEBBMCEBDAULIANBBGogC0ECdGooAgAEQCABIQIMAwsgAyALNgIAIAshAQwDCyAGRQRAQREhAQwECyAIIApqIA4gAWs6AAAgBSAFKAIAQQFqNgIAIAJBEEsEQEETIQEMBAsgA0EEaiACQQJ0aiEGIAEgDmutIREgByEBAkACQAJAA0AgASAGRg0BIAFBBGohBCABIAEoAgCtIBF8IhE+AgAgEUIgiCIRQgBRDQIgBCEBDAALAAsgAkEQTwRAQRkhAQwGCyAGIBE+AgAgAyACQQFqIgE2AgAMAQsgAkEQSwRAQRshAQwFCyACBEAgAiEBBUEAIQIMAwsLQQAhBCADQQRqIAFBAnRqIQEDQCABQXxqIgEoAgAhBiABIAZBAXYgBHI2AgAgBkEfdCEEIAEgB0cNAAsgAygCACIBQQFNDQIgAUF/aiIMQRBPBEBBISEBDAQLIANBBGogDEECdGooAgAEQCABIQIFIAMgDDYCACAMIQEMAwsLCyAHKAIAIQQMAgsgAUEBRyAHKAIAIgRyBEAgASECDAIFQQMhAQsLCwJAAkACQAJAAkACQAJAAkACQAJAAkACQCABQQNrDi4ACwsBCwsLCwsCCwsLCwMLBAsLCwsLBQsGCwsLCwsHCwsLCAsLCwsJCwsLCwsKCwsgACAIQYQEEDwaIAkkAg8LIAJBEBAnDAkLQbALIA1BEBAoDAgLQawNIApBgAQQKAwHCyACQRAQJwwGC0GQDCACQRAQKAwFCyACQRAQJwwEC0GwCyAMQRAQKAwDC0GsDSAKQYAEECgMAgsgAkEQECcMAQtBsAsgC0EQECgLCwuhAgIFfwF+AkAgACgCACIBQRBLBEAgAUEQECcLIAFFBEAPCyAAQQRqIAFBAnRqIQNCACEGIABBBGohAQNAIAEgASgCAK1CAYYgBnwiBj4CACAGQiCIIQYgAUEEaiIBIANHDQALIAAoAgAhASAGQgBRBEAgASECBSABQRBJBEAgAEEEaiABQQJ0aiAGPgIAIAAgACgCAEEBaiICNgIABUGgDSABQRAQKAsLAkAgAkEIRgRAQQghAgJAAkADQCACQX9qIQEgAkUNBCABQRBPDQEgAEEEaiABQQJ0aigCACIEIAFBAnRBxAlqKAIAIgVGBEAgASECDAELCwwBC0G8CyABQRAQKAsgBCAFSQRADwsFIAJBCEkEQA8LCwsgAEHACRAWCwukBQEHfwJAIwIhBCMCQZABaiQCIAQhASAAKAIAIgJBCUkEQCABQdgMKQIANwIAIAFB4AwpAgA3AgggAUHoDCkCADcCECABQfAMKQIANwIYIAFB+AwpAgA3AiAgAUGADSkCADcCKCABQYgNKQIANwIwIAFBkA0pAgA3AjggAUGYDSkCADcCQAUgAEEINgIAIAEgAkF4ajYCACABQQRqIgIgAEEkaiIDKQIANwIAIAIgAykCCDcCCCACIAMpAhA3AhAgAiADKQIYNwIYIAFBJGoiAkIANwIAIAJCADcCCCACQgA3AhAgAkIANwIYIAJBADoAIAsgBEHIAGohAiABEBwgACABEBUgACgCACIBQQhLBEAgAEEINgIAIAIgAUF4ajYCACACQQRqIgEgAEEkaiIDKQIANwIAIAEgAykCCDcCCCABIAMpAhA3AhAgASADKQIYNwIYIAJBJGoiAUIANwIAIAFCADcCCCABQgA3AhAgAUIANwIYIAFBADoAICACEBwgACACEBUgACgCACEBCyABQQhGBEBBCCEDA0ACQCADQX9qIQIgA0UEQEEUIQMMAQsgAkEQTwRAQQshAwwBCyAAQQRqIAJBAnRqKAIAIgYgAkECdEHECWooAgAiB0YEQCACIQMMAgVBDCEDCwsLIANBC0YEQEG8CyACQRAQKAUgA0EMRgRAIAYgB0khBQUgA0EURgRAIAQkAg8LCwsFIAFBCEkhBQsgBUUEQCAAQcAJEBYgBCQCDwsgAUEBTQRAIAQkAg8LA0ACQCABQX9qIgFBEE8EQEERIQMMAQsgAEEEaiABQQJ0aigCAARAQRQhAwwBCyAAIAE2AgAgAUEBSw0BQRQhAwsLIANBEUYEQEGwCyABQRAQKAUgA0EURgRAIAQkAgsLCwuQAgICfwJ+AkAgACgCACIBQRBPBEBBxA0gAUEQECgLIABBBGogAUECdGpBADYCACAAKAIAQQFqIgFBEE8EQEG4DSABQRAQKAsgAEEEaiABQQJ0akEANgIAIAAgACgCAEECaiICNgIAIAJBEEsEQCACQRAQJwsgAEEEaiEBIAIEQEIAIQMFDwsgAEEEaiACQQJ0aiECA0AgASABKAIArSIEQtEHfiADfCIDPgIAIANCIIggBHwhAyABQQRqIgEgAkcNAAsgACgCACIBQQFNBEAPCwJAAkADQCABQX9qIgFBEE8NASAAQQRqIAFBAnRqKAIADQIgACABNgIAIAFBAUsNAAwCCwALQbALIAFBEBAoCwsL0BACD38EfgJAIwIhByMCQfACaiQCIAciAyABKQIANwIAIAMgASkCCDcCCCADIAEpAhA3AhAgAyABKQIYNwIYIAMgASkCIDcCICADIAEpAig3AiggAyABKQIwNwIwIAMgASkCODcCOCADIAEpAkA3AkAgB0HIAGoiBkHACSkDADcDACAGQcgJKQMANwMIIAZB0AkpAwA3AxAgBkHYCSkDADcDGCAGQeAJKQMANwMgIAZB6AkpAwA3AyggBkHwCSkDADcDMCAGQfgJKQMANwM4IAZBgAopAwA3A0AgB0GQAWoiBEGICikDADcDACAEQZAKKQMANwMIIARBmAopAwA3AxAgBEGgCikDADcDGCAEQagKKQMANwMgIARBsAopAwA3AyggBEG4CikDADcDMCAEQcAKKQMANwM4IARByAopAwA3A0AgB0HYAWoiBUHYDCkCADcCACAFQeAMKQIANwIIIAVB6AwpAgA3AhAgBUHwDCkCADcCGCAFQfgMKQIANwIgIAVBgA0pAgA3AiggBUGIDSkCADcCMCAFQZANKQIANwI4IAVBmA0pAgA3AkACQCADQQRqIgsoAgAiASADKAIAIgJyQQFLBEAgBkEEaiEMIAVBBGohDyAEQQRqIRACQAJAAkACQAJAAkACQAJAAkACQANAIAFoIggEQCACQRBLDQJBICAIayEBIAIEfyAIrSETIAFBP3GtIRRCACERIANBBGogAkECdGohAQNAIAFBfGoiASgCAK0hEiABIBIgE4ggEYQ+AgAgEiAUhiERIAEgC0cNAAsgAygCACIBQQFLBH8gAUF/aiIBQRBPDQUgA0EEaiABQQJ0aigCAAR/QQEFIAMgATYCAEEBCwVBAQsFQQELIQEDQCAQKAIAQQFxBEAgBEHACRAVCyAEKAIAIgJBEEsNBSACBEBBACEJIARBBGogAkECdGohAgNAIAJBfGoiAigCACEKIAIgCkEBdiAJcjYCACAKQR90IQkgAiAQRw0ACyAEKAIAIgJBAUsEQCACQX9qIgJBEE8NCCAEQQRqIAJBAnRqKAIARQRAIAQgAjYCAAsLCyABQQFqIQIgASAISQRAIAIhAQwBCwsLIAwoAgBoIggEQCAGKAIAIgFBEEsNBkEgIAhrIQIgAQR/IAitIRMgAkE/ca0hFEIAIREgBkEEaiABQQJ0aiEBA0AgAUF8aiIBKAIArSESIAEgEiATiCARhD4CACASIBSGIREgASAMRw0ACyAGKAIAIgFBAUsEfyABQX9qIgFBEE8NCSAGQQRqIAFBAnRqKAIABH9BAQUgBiABNgIAQQELBUEBCwVBAQshAQNAIA8oAgBBAXEEQCAFQcAJEBULIAUoAgAiAkEQSw0JIAIEQEEAIQkgBUEEaiACQQJ0aiECA0AgAkF8aiICKAIAIQogAiAKQQF2IAlyNgIAIApBH3QhCSACIA9HDQALIAUoAgAiAkEBSwRAIAJBf2oiAkEQTw0MIAVBBGogAkECdGooAgBFBEAgBSACNgIACwsLIAFBAWohAiABIAhJBEAgAiEBDAELCwsCQAJAAkAgAygCACIBIAYoAgAiAkYEQANAIAFBf2ohAiABRQ0CIAJBEE8NDiADQQRqIAJBAnRqKAIAIgEgBkEEaiACQQJ0aigCACIJRgRAIAIhAQwBCwsgASAJSQ0CBSABIAJJDQILCyADIAYQFiAEIAUQFgwBCyAGIAMQFiAFIAQQFgsgCygCACIBIAMoAgAiAnJBAU0EQCACIQ0gASEODA0LIAwoAgAgBigCAHJBAUsNACACIQ0gASEODAwLAAsgAkEQECcMCAtBsAsgAUEQECgMBwsgAkEQECcMBgtBsAsgAkEQECgMBQsgAUEQECcMBAtBsAsgAUEQECgMAwsgAkEQECcMAgtBsAsgAkEQECgMAQtBvAsgAkEQECgLBSACIQ0gASEOCwsgDUEBRiAOQQFGcQRAIAMgBCkDADcDACADIAQpAwg3AwggAyAEKQMQNwMQIAMgBCkDGDcDGCADIAQpAyA3AyAgAyAEKQMoNwMoIAMgBCkDMDcDMCADIAQpAzg3AzggAyAEKQNANwNABSADIAUpAwA3AwAgAyAFKQMINwMIIAMgBSkDEDcDECADIAUpAxg3AxggAyAFKQMgNwMgIAMgBSkDKDcDKCADIAUpAzA3AzAgAyAFKQM4NwM4IAMgBSkDQDcDQAsgB0GgAmohASADQcQAaiICLAAABEAgA0HACRAVIAIsAAAEQCACQQA6AAAgAxAbIAMoAgBBAUYgCygCAEVxBEAgAEHYDCkCADcCACAAQeAMKQIANwIIIABB6AwpAgA3AhAgAEHwDCkCADcCGCAAQfgMKQIANwIgIABBgA0pAgA3AiggAEGIDSkCADcCMCAAQZANKQIANwI4IABBmA0pAgA3AkAgByQCDwUgAUHACSkDADcDACABQcgJKQMANwMIIAFB0AkpAwA3AxAgAUHYCSkDADcDGCABQeAJKQMANwMgIAFB6AkpAwA3AyggAUHwCSkDADcDMCABQfgJKQMANwM4IAFBgAopAwA3A0AgASADEBYgACABKQIANwIAIAAgASkCCDcCCCAAIAEpAhA3AhAgACABKQIYNwIYIAAgASkCIDcCICAAIAEpAig3AiggACABKQIwNwIwIAAgASkCODcCOCAAIAEpAkA3AkAgByQCDwsACwsgAxAbIAAgAykCADcCACAAIAMpAgg3AgggACADKQIQNwIQIAAgAykCGDcCGCAAIAMpAiA3AiAgACADKQIoNwIoIAAgAykCMDcCMCAAIAMpAjg3AjggACADKQJANwJAIAckAgsLzx8BD38CQCMCIQwjAkGABWokAiAAQZABaiIELAAABEAgDCQCDwsgDCELIAxBuARqIgIgAEHIAGoiCikCADcCACACIAopAgg3AgggAiAKKQIQNwIQIAIgCikCGDcCGCACIAopAiA3AiAgAiAKKQIoNwIoIAIgCikCMDcCMCACIAopAjg3AjggAiAKKQJANwJAIAIgChAVIAxB8ANqIgEgAikDADcDACABIAIpAwg3AwggASACKQMQNwMQIAEgAikDGDcDGCABIAIpAyA3AyAgASACKQMoNwMoIAEgAikDMDcDMCABIAIpAzg3AzggASACKQNANwNAAkACQCABKAIAIgNBCEYEQEEIIQcCQAJAAkADQCAHQX9qIQMgB0UNBSADQRBPDQEgAUEEaiADQQJ0aigCACIHIANBAnRBxAlqKAIAIgVHDQIgAyEHDAALAAtBvAsgA0EQECgMAQsgByAFTw0CQQghDQsFIANBCE8NASADIQ0LDAELIAFBwAkQFiABKAIAIQ0LIAEoAgQhDiALIAFBCGoiAykCADcCACALIAMpAgg3AgggCyADKQIQNwIQIAsgAykCGDcCGCALIAMpAiA3AiAgCyADKQIoNwIoIAsgAykCMDcCMCALIAMpAjg3AjggDUEBRiAORXEEQCAEQQE6AAAgDCQCDwsgDEGoA2ohCCAMQeACaiEEIAxBmAJqIQUgDEGIAWohBiACIAApAgA3AgAgAiAAKQIINwIIIAIgACkCEDcCECACIAApAhg3AhggAiAAKQIgNwIgIAIgACkCKDcCKCACIAApAjA3AjAgAiAAKQI4NwI4IAIgACkCQDcCQCACIAAQFyABIAIpAwA3AwAgASACKQMINwMIIAEgAikDEDcDECABIAIpAxg3AxggASACKQMgNwMgIAEgAikDKDcDKCABIAIpAzA3AzAgASACKQM4NwM4IAEgAikDQDcDQCABEBsgDEHAAGoiCSABKQMANwMAIAkgASkDCDcDCCAJIAEpAxA3AxAgCSABKQMYNwMYIAkgASkDIDcDICAJIAEpAyg3AyggCSABKQMwNwMwIAkgASkDODcDOCAJIAEpA0A3A0AgAiAJKQMANwMAIAIgCSkDCDcDCCACIAkpAxA3AxAgAiAJKQMYNwMYIAIgCSkDIDcDICACIAkpAyg3AyggAiAJKQMwNwMwIAIgCSkDODcDOCACIAkpA0A3A0AgAiAJEBUgASACKQMANwMAIAEgAikDCDcDCCABIAIpAxA3AxAgASACKQMYNwMYIAEgAikDIDcDICABIAIpAyg3AyggASACKQMwNwMwIAEgAikDODcDOCABIAIpA0A3A0ACQAJAIAEoAgAiA0EIRgRAQQghBwJAAkACQANAIAdBf2ohAyAHRQ0FIANBEE8NASABQQRqIANBAnRqKAIAIgcgA0ECdEHECWooAgAiD0cNAiADIQcMAAsAC0G8CyADQRAQKAwBCyAHIA9PDQILBSADQQhPDQELDAELIAFBwAkQFgsgBCABKQMANwMAIAQgASkDCDcDCCAEIAEpAxA3AxAgBCABKQMYNwMYIAQgASkDIDcDICAEIAEpAyg3AyggBCABKQMwNwMwIAQgASkDODcDOCAEIAEpA0A3A0AgAiAEKQMANwMAIAIgBCkDCDcDCCACIAQpAxA3AxAgAiAEKQMYNwMYIAIgBCkDIDcDICACIAQpAyg3AyggAiAEKQMwNwMwIAIgBCkDODcDOCACIAQpA0A3A0AgAiAJEBUgASACKQMANwMAIAEgAikDCDcDCCABIAIpAxA3AxAgASACKQMYNwMYIAEgAikDIDcDICABIAIpAyg3AyggASACKQMwNwMwIAEgAikDODcDOCABIAIpA0A3A0ACQAJAIAEoAgAiA0EIRgRAQQghBwJAAkACQANAIAdBf2ohAyAHRQ0FIANBEE8NASABQQRqIANBAnRqKAIAIgcgA0ECdEHECWooAgAiCUcNAiADIQcMAAsAC0G8CyADQRAQKAwBCyAHIAlPDQILBSADQQhPDQELDAELIAFBwAkQFgsgBSABKQMANwMAIAUgASkDCDcDCCAFIAEpAxA3AxAgBSABKQMYNwMYIAUgASkDIDcDICAFIAEpAyg3AyggBSABKQMwNwMwIAUgASkDODcDOCAFIAEpA0A3A0AgAiANNgIAIAIgDjYCBCACQQhqIgMgCykDADcDACADIAspAwg3AwggAyALKQMQNwMQIAMgCykDGDcDGCADIAspAyA3AyAgAyALKQMoNwMoIAMgCykDMDcDMCADIAspAzg3AzggCCACEB0gAiAFKQMANwMAIAIgBSkDCDcDCCACIAUpAxA3AxAgAiAFKQMYNwMYIAIgBSkDIDcDICACIAUpAyg3AyggAiAFKQMwNwMwIAIgBSkDODcDOCACIAUpA0A3A0AgAiAIEBcgASACKQMANwMAIAEgAikDCDcDCCABIAIpAxA3AxAgASACKQMYNwMYIAEgAikDIDcDICABIAIpAyg3AyggASACKQMwNwMwIAEgAikDODcDOCABIAIpA0A3A0AgARAbIAYgASkDADcDACAGIAEpAwg3AwggBiABKQMQNwMQIAYgASkDGDcDGCAGIAEpAyA3AyAgBiABKQMoNwMoIAYgASkDMDcDMCAGIAEpAzg3AzggBiABKQNANwNAIAIgBikDADcDACACIAYpAwg3AwggAiAGKQMQNwMQIAIgBikDGDcDGCACIAYpAyA3AyAgAiAGKQMoNwMoIAIgBikDMDcDMCACIAYpAzg3AzggAiAGKQNANwNAIAIgBhAXIAEgAikDADcDACABIAIpAwg3AwggASACKQMQNwMQIAEgAikDGDcDGCABIAIpAyA3AyAgASACKQMoNwMoIAEgAikDMDcDMCABIAIpAzg3AzggASACKQNANwNAIAEQGyAEIAEpAwA3AwAgBCABKQMINwMIIAQgASkDEDcDECAEIAEpAxg3AxggBCABKQMgNwMgIAQgASkDKDcDKCAEIAEpAzA3AzAgBCABKQM4NwM4IAQgASkDQDcDQCACIAApAgA3AgAgAiAAKQIINwIIIAIgACkCEDcCECACIAApAhg3AhggAiAAKQIgNwIgIAIgACkCKDcCKCACIAApAjA3AjAgAiAAKQI4NwI4IAIgACkCQDcCQCACIAAQFSABIAIpAwA3AwAgASACKQMINwMIIAEgAikDEDcDECABIAIpAxg3AxggASACKQMgNwMgIAEgAikDKDcDKCABIAIpAzA3AzAgASACKQM4NwM4IAEgAikDQDcDQAJAAkAgASgCACIDQQhGBEBBCCEHAkACQAJAA0AgB0F/aiEDIAdFDQUgA0EQTw0BIAFBBGogA0ECdGooAgAiByADQQJ0QcQJaigCACILRw0CIAMhBwwACwALQbwLIANBEBAoDAELIAcgC08NAgsFIANBCE8NAQsMAQsgAUHACRAWCyAIIAEpAwA3AwAgCCABKQMINwMIIAggASkDEDcDECAIIAEpAxg3AxggCCABKQMgNwMgIAggASkDKDcDKCAIIAEpAzA3AzAgCCABKQM4NwM4IAggASkDQDcDQCACIAQpAwA3AwAgAiAEKQMINwMIIAIgBCkDEDcDECACIAQpAxg3AxggAiAEKQMgNwMgIAIgBCkDKDcDKCACIAQpAzA3AzAgAiAEKQM4NwM4IAIgBCkDQDcDQCACIAgQFiABIAIpAwA3AwAgASACKQMINwMIIAEgAikDEDcDECABIAIpAxg3AxggASACKQMgNwMgIAEgAikDKDcDKCABIAIpAzA3AzAgASACKQM4NwM4IAEgAikDQDcDQCABLABEBEAgAUHACRAVCyAMQdABaiIDIAEpAwA3AwAgAyABKQMINwMIIAMgASkDEDcDECADIAEpAxg3AxggAyABKQMgNwMgIAMgASkDKDcDKCADIAEpAzA3AzAgAyABKQM4NwM4IAMgASkDQDcDQCACIAApAgA3AgAgAiAAKQIINwIIIAIgACkCEDcCECACIAApAhg3AhggAiAAKQIgNwIgIAIgACkCKDcCKCACIAApAjA3AjAgAiAAKQI4NwI4IAIgACkCQDcCQCACIAMQFiABIAIpAwA3AwAgASACKQMINwMIIAEgAikDEDcDECABIAIpAxg3AxggASACKQMgNwMgIAEgAikDKDcDKCABIAIpAzA3AzAgASACKQM4NwM4IAEgAikDQDcDQCABLABEBEAgAUHACRAVCyAIIAEpAwA3AwAgCCABKQMINwMIIAggASkDEDcDECAIIAEpAxg3AxggCCABKQMgNwMgIAggASkDKDcDKCAIIAEpAzA3AzAgCCABKQM4NwM4IAggASkDQDcDQCACIAYpAwA3AwAgAiAGKQMINwMIIAIgBikDEDcDECACIAYpAxg3AxggAiAGKQMgNwMgIAIgBikDKDcDKCACIAYpAzA3AzAgAiAGKQM4NwM4IAIgBikDQDcDQCACIAgQFyABIAIpAwA3AwAgASACKQMINwMIIAEgAikDEDcDECABIAIpAxg3AxggASACKQMgNwMgIAEgAikDKDcDKCABIAIpAzA3AzAgASACKQM4NwM4IAEgAikDQDcDQCABEBsgBCABKQMANwMAIAQgASkDCDcDCCAEIAEpAxA3AxAgBCABKQMYNwMYIAQgASkDIDcDICAEIAEpAyg3AyggBCABKQMwNwMwIAQgASkDODcDOCAEIAEpA0A3A0AgAiAEKQMANwMAIAIgBCkDCDcDCCACIAQpAxA3AxAgAiAEKQMYNwMYIAIgBCkDIDcDICACIAQpAyg3AyggAiAEKQMwNwMwIAIgBCkDODcDOCACIAQpA0A3A0AgAiAKEBYgASACKQMANwMAIAEgAikDCDcDCCABIAIpAxA3AxAgASACKQMYNwMYIAEgAikDIDcDICABIAIpAyg3AyggASACKQMwNwMwIAEgAikDODcDOCABIAIpA0A3A0AgASwARARAIAFBwAkQFQsgBSABKQMANwMAIAUgASkDCDcDCCAFIAEpAxA3AxAgBSABKQMYNwMYIAUgASkDIDcDICAFIAEpAyg3AyggBSABKQMwNwMwIAUgASkDODcDOCAFIAEpA0A3A0AgCiAFKQIANwIAIAogBSkCCDcCCCAKIAUpAhA3AhAgCiAFKQIYNwIYIAogBSkCIDcCICAKIAUpAig3AiggCiAFKQIwNwIwIAogBSkCODcCOCAKIAUpAkA3AkAgACADKQIANwIAIAAgAykCCDcCCCAAIAMpAhA3AhAgACADKQIYNwIYIAAgAykCIDcCICAAIAMpAig3AiggACADKQIwNwIwIAAgAykCODcCOCAAIAMpAkA3AkAgDCQCCwvCFgETfwJAIwIhCSMCQfC1AmokAiAJQZC0AmohAyAJQcizAmohBSAJQbiyAmohByAJQey1AmohCyAJIgRBCDYCACAEQZiv4LcBNgIEIARB24LKzwU2AgggBEHZ0bjuAjYCDCAEQdv57xQ2AhAgBEGHlpz0fDYCFCAEQZXFga0FNgIYIARBrPfyTjYCHCAEQf7M+c0HNgIgIARBJGoiBkIANwIAIAZCADcCCCAGQgA3AhAgBkIANwIYIAZBADoAICAEQQg2AkggBEHMAGoiD0G4qcNYNgIAIARBj6Gf4nk2AlAgBEGZqJW0ejYCVCAEQcjo3mg2AlggBEGokcTwADYCXCAEQfz3k+0FNgJgIARB5YiPtQI2AmQgBEH3tOvBBDYCaCAEQewAaiIGQgA3AgAgBkIANwIIIAZCADcCECAGQgA3AhggBkEAOgAgIARBkAFqIhBBADoAACAJQdi0AmoiASAEQZQBEDwaIAlB6JkBaiICIARBlAEQPBogAkGUAWogAUGUARA8GiACQagCaiABQZQBEDwaIAJBvANqIAFBlAEQPBogAkHQBGogAUGUARA8GiACQeQFaiABQZQBEDwaIAJB+AZqIAFBlAEQPBogAkGMCGogAUGUARA8GiACQaAJaiABQZQBEDwaIAJBtApqIAFBlAEQPBogAkHIC2ogAUGUARA8GiACQdwMaiABQZQBEDwaIAJB8A1qIAFBlAEQPBogAkGED2ogAUGUARA8GiACQZgQaiABQZQBEDwaIAJBrBFqIAFBlAEQPBogAkHAEmogAUGUARA8GiACQdQTaiABQZQBEDwaIAJB6BRqIAFBlAEQPBogAkH8FWogAUGUARA8GiACQZAXaiABQZQBEDwaIAJBpBhqIAFBlAEQPBogAkG4GWogAUGUARA8GiACQcwaaiABQZQBEDwaIAJB4BtqIAFBlAEQPBogAkH0HGogAUGUARA8GiACQYgeaiABQZQBEDwaIAJBnB9qIAFBlAEQPBogAkGwIGogAUGUARA8GiACQcQhaiABQZQBEDwaIAJB2CJqIAFBlAEQPBogAkHsI2ogAUGUARA8GiACQYAlaiABQZQBEDwaIAJBlCZqIAFBlAEQPBogAkGoJ2ogAUGUARA8GiACQbwoaiABQZQBEDwaIAJB0ClqIAFBlAEQPBogAkHkKmogAUGUARA8GiACQfgraiABQZQBEDwaIAJBjC1qIAFBlAEQPBogAkGgLmogAUGUARA8GiACQbQvaiABQZQBEDwaIAJByDBqIAFBlAEQPBogAkHcMWogAUGUARA8GiACQfAyaiABQZQBEDwaIAJBhDRqIAFBlAEQPBogAkGYNWogAUGUARA8GiACQaw2aiABQZQBEDwaIAJBwDdqIAFBlAEQPBogAkHUOGogAUGUARA8GiACQeg5aiABQZQBEDwaIAJB/DpqIAFBlAEQPBogAkGQPGogAUGUARA8GiACQaQ9aiABQZQBEDwaIAJBuD5qIAFBlAEQPBogAkHMP2ogAUGUARA8GiACQeDAAGogAUGUARA8GiACQfTBAGogAUGUARA8GiACQYjDAGogAUGUARA8GiACQZzEAGogAUGUARA8GiACQbDFAGogAUGUARA8GiACQcTGAGogAUGUARA8GiACQdjHAGogAUGUARA8GiACQezIAGogAUGUARA8GiACQYDKAGogAUGUARA8GiACQZTLAGogAUGUARA8GiAQLAAABH8gByAEQZABEDwaIAsgBEGRAWoiBi4AADsAACALIAYsAAI6AAJBAQUgBSAEKQMANwMAIAUgBCkDCDcDCCAFIAQpAxA3AxAgBSAEKQMYNwMYIAUgBCkDIDcDICAFIAQpAyg3AyggBSAEKQMwNwMwIAUgBCkDODcDOCAFIAQpA0A3A0AgBEHIAGoiBigCAEEBRiAPKAIARXEEQCADQdgMKQIANwIAIANB4AwpAgA3AgggA0HoDCkCADcCECADQfAMKQIANwIYIANB+AwpAgA3AiAgA0GADSkCADcCKCADQYgNKQIANwIwIANBkA0pAgA3AjggA0GYDSkCADcCQAUgAUHACSkDADcDACABQcgJKQMANwMIIAFB0AkpAwA3AxAgAUHYCSkDADcDGCABQeAJKQMANwMgIAFB6AkpAwA3AyggAUHwCSkDADcDMCABQfgJKQMANwM4IAFBgAopAwA3A0AgASAGEBYgAyABKQMANwMAIAMgASkDCDcDCCADIAEpAxA3AxAgAyABKQMYNwMYIAMgASkDIDcDICADIAEpAyg3AyggAyABKQMwNwMwIAMgASkDODcDOCADIAEpA0A3A0ALIAcgBSkDADcDACAHIAUpAwg3AwggByAFKQMQNwMQIAcgBSkDGDcDGCAHIAUpAyA3AyAgByAFKQMoNwMoIAcgBSkDMDcDMCAHIAUpAzg3AzggByAFKQNANwNAIAdByABqIgYgAykDADcDACAGIAMpAwg3AwggBiADKQMQNwMQIAYgAykDGDcDGCAGIAMpAyA3AyAgBiADKQMoNwMoIAYgAykDMDcDMCAGIAMpAzg3AzggBiADKQNANwNAQQALIQwgCUGYAWohDSAJQZDmAWoiCkGozABqIQggCiEGA0AgBiAHQZABEDwaIAYgDDoAkAEgBkGRAWoiDiALLgAAOwAAIA4gCywAAjoAAiAGQZQBaiIGIAhHDQALIA0gAkGozAAQPBogDUGozABqIApBqMwAEDwaIA1BlAFqIRMgBEHIAGohESAHQcgAaiEIIARBkQFqIRIgDUG8zQBqIg4hAkEBIQZBACEKA0AgBBAeIAQQHiAEEB4gBBAeIBMgCkGUAWxqIARBlAEQPBogECwAAAR/IAcgBEGQARA8GiALIBIuAAA7AAAgCyASLAACOgACQQEFIAUgBCkDADcDACAFIAQpAwg3AwggBSAEKQMQNwMQIAUgBCkDGDcDGCAFIAQpAyA3AyAgBSAEKQMoNwMoIAUgBCkDMDcDMCAFIAQpAzg3AzggBSAEKQNANwNAIBEoAgBBAUYgDygCAEVxBEAgA0HYDCkCADcCACADQeAMKQIANwIIIANB6AwpAgA3AhAgA0HwDCkCADcCGCADQfgMKQIANwIgIANBgA0pAgA3AiggA0GIDSkCADcCMCADQZANKQIANwI4IANBmA0pAgA3AkAFIAFBwAkpAwA3AwAgAUHICSkDADcDCCABQdAJKQMANwMQIAFB2AkpAwA3AxggAUHgCSkDADcDICABQegJKQMANwMoIAFB8AkpAwA3AzAgAUH4CSkDADcDOCABQYAKKQMANwNAIAEgERAWIAMgASkDADcDACADIAEpAwg3AwggAyABKQMQNwMQIAMgASkDGDcDGCADIAEpAyA3AyAgAyABKQMoNwMoIAMgASkDMDcDMCADIAEpAzg3AzggAyABKQNANwNACyAHIAUpAwA3AwAgByAFKQMINwMIIAcgBSkDEDcDECAHIAUpAxg3AxggByAFKQMgNwMgIAcgBSkDKDcDKCAHIAUpAzA3AzAgByAFKQM4NwM4IAcgBSkDQDcDQCAIIAMpAwA3AwAgCCADKQMINwMIIAggAykDEDcDECAIIAMpAxg3AxggCCADKQMgNwMgIAggAykDKDcDKCAIIAMpAzA3AzAgCCADKQM4NwM4IAggAykDQDcDQEEACyEMIAIgB0GQARA8GiAOIApBlAFsaiAMOgCQASAOIApBlAFsakGRAWoiCiALLgAAOwAAIAogCywAAjoAAiAOIAZBlAFsaiECIAZBAWoiDEHCAEcEQCAGIQogDCEGDAELCyAAIA1B0JgBEDwaIAkkAgsLwgwBEH8CQCMCIQsjAkGQCmokAiALIgcgAkEBEBkgC0GQCWoiBkIANwAAIAZCADcACCAGQgA3ABAgBkIANwAYIAZCADcAICAGQgA3ACggBkIANwAwIAZCADcAOCAGQgA3AEAgBkIANwBIIAZCADcAUCAGQgA3AFggBkIANwBgIAZCADcAaCAGQgA3AHAgBkIANwB4IAcoAoAEIglBgARLBEAgCUGABBAnCyALQbgHaiEOIAtB4AVqIQQgC0GIBGohBSAJRSEDIAlBBEkEfyAJBUEECyECAkAgAwRAQQAhDQUgCSACayEIIAchAyACIQkgByACaiECQQAhBwJAAkACQANAAkACQAJAAkACQAJAIAlBAWsOBAMCAQAECyADLAACQQJ0Qf8BcSADLAADQQN0Qf8BcWpBGHRBGHUgAywAAUEBdEH/AXFqQRh0QRh1IAMsAABqQRh0QRh1IQMMBAsgAywAAUEBdEH/AXEgAywAAkECdEH/AXFqQRh0QRh1IAMsAABqQRh0QRh1IQMMAwsgAywAAUEBdEH/AXEgAywAAGpBGHRBGHUhAwwCCyADLAAAIQMMAQsMAgsgB0GAAU8NAiAGIAdqIAM6AAAgB0EBaiEHIAhFIQwgCCAIQQRJBH8gCAVBBAsiCmshCCACIgMgCmohDyAMBEBBACEICyAMBEAgAiEPCyAMRQRAIAohCQsgDCACRXIEQCAHIQ0MBgUgDyECDAELAAsAC0HQDRAuDAELQeQNIAdBgAEQKAsLCyAFQYgKKQMANwMAIAVBkAopAwA3AwggBUGYCikDADcDECAFQaAKKQMANwMYIAVBqAopAwA3AyAgBUGwCikDADcDKCAFQbgKKQMANwMwIAVBwAopAwA3AzggBUHICikDADcDQCAFQcgAaiICQYgKKQMANwMAIAJBkAopAwA3AwggAkGYCikDADcDECACQaAKKQMANwMYIAJBqAopAwA3AyAgAkGwCikDADcDKCACQbgKKQMANwMwIAJBwAopAwA3AzggAkHICikDADcDQCAFQZABaiICQdgMKQIANwIAIAJB4AwpAgA3AgggAkHoDCkCADcCECACQfAMKQIANwIYIAJB+AwpAgA3AiAgAkGADSkCADcCKCACQYgNKQIANwIwIAJBkA0pAgA3AjggAkGYDSkCADcCQCAEQYgKKQMANwMAIARBkAopAwA3AwggBEGYCikDADcDECAEQaAKKQMANwMYIARBqAopAwA3AyAgBEGwCikDADcDKCAEQbgKKQMANwMwIARBwAopAwA3AzggBEHICikDADcDQCAEQcgAaiICQYgKKQMANwMAIAJBkAopAwA3AwggAkGYCikDADcDECACQaAKKQMANwMYIAJBqAopAwA3AyAgAkGwCikDADcDKCACQbgKKQMANwMwIAJBwAopAwA3AzggAkHICikDADcDQCAEQZABaiICQdgMKQIANwIAIAJB4AwpAgA3AgggAkHoDCkCADcCECACQfAMKQIANwIYIAJB+AwpAgA3AiAgAkGADSkCADcCKCACQYgNKQIANwIwIAJBkA0pAgA3AjggAkGYDSkCADcCQCANRSICBEAgBSAEECEgBSAEECEgBSAEECEgBSAEECEgBSAEECEgBSAEECEgBSAEECEgBSAEECEgBSAEECEgBSAEECEgDiAFQdgBEDwaIAAgDhAiIAskAg8FQQohCgsgBiANaiERIAYhCSAGQQFqIQMgAgR/IAkFIAMLIQYgAkEBc0EBcSEMAkACQAJAAkADQEEAIAprQRh0QRh1IRJBACEIIAYhAiAJIQMgDCEHA0AgAywAACIDIApBGHRBGHVGBEAgCEHCAE8NBCAEIAEgCEGUAWxqECMFIAMgEkYEQCAIQcIATw0GIAQgAUGozABqIAhBlAFsahAjCwsgAiIDIBFGIRAgA0EBaiEDIBAEfyACBSADCyEPIBBBAXMgB2ohDSAQRQRAIAchCAsgAkUgEHJFBEAgAiEDIA8hAiANIQcMAQsLIAUgBBAhIApBf2pBGHRBGHUhAiAKQRh0QRh1QQFMDQEgAiEKDAALAAsgDiAFQdgBEDwaIAAgDhAiIAskAg8LQfANIAhBwgAQKAwBC0H8DSAIQcIAECgLCwu9OgEPfwJAIwIhDCMCQeAKaiQCIAAoApABQQFGBEAgACgClAFFBEAgACABQdgBEDwaIAwkAg8LCyABKAKQAUEBRgRAIAEoApQBRQRAIAwkAg8LCyAMQYAJaiICIAFBkAFqIg4pAgA3AgAgAiAOKQIINwIIIAIgDikCEDcCECACIA4pAhg3AhggAiAOKQIgNwIgIAIgDikCKDcCKCACIA4pAjA3AjAgAiAOKQI4NwI4IAIgDikCQDcCQCACIA4QFyAMQbgIaiIDIAIpAwA3AwAgAyACKQMINwMIIAMgAikDEDcDECADIAIpAxg3AxggAyACKQMgNwMgIAMgAikDKDcDKCADIAIpAzA3AzAgAyACKQM4NwM4IAMgAikDQDcDQCADEBsgDCIGIAMpAwA3AwAgBiADKQMINwMIIAYgAykDEDcDECAGIAMpAxg3AxggBiADKQMgNwMgIAYgAykDKDcDKCAGIAMpAzA3AzAgBiADKQM4NwM4IAYgAykDQDcDQCACIABBkAFqIgkpAgA3AgAgAiAJKQIINwIIIAIgCSkCEDcCECACIAkpAhg3AhggAiAJKQIgNwIgIAIgCSkCKDcCKCACIAkpAjA3AjAgAiAJKQI4NwI4IAIgCSkCQDcCQCACIAkQFyADIAIpAwA3AwAgAyACKQMINwMIIAMgAikDEDcDECADIAIpAxg3AxggAyACKQMgNwMgIAMgAikDKDcDKCADIAIpAzA3AzAgAyACKQM4NwM4IAMgAikDQDcDQCADEBsgDEHIAGoiBSADKQMANwMAIAUgAykDCDcDCCAFIAMpAxA3AxAgBSADKQMYNwMYIAUgAykDIDcDICAFIAMpAyg3AyggBSADKQMwNwMwIAUgAykDODcDOCAFIAMpA0A3A0AgAiAAKQIANwIAIAIgACkCCDcCCCACIAApAhA3AhAgAiAAKQIYNwIYIAIgACkCIDcCICACIAApAig3AiggAiAAKQIwNwIwIAIgACkCODcCOCACIAApAkA3AkAgAiAGEBcgAyACKQMANwMAIAMgAikDCDcDCCADIAIpAxA3AxAgAyACKQMYNwMYIAMgAikDIDcDICADIAIpAyg3AyggAyACKQMwNwMwIAMgAikDODcDOCADIAIpA0A3A0AgAxAbIAxBkAFqIgcgAykDADcDACAHIAMpAwg3AwggByADKQMQNwMQIAcgAykDGDcDGCAHIAMpAyA3AyAgByADKQMoNwMoIAcgAykDMDcDMCAHIAMpAzg3AzggByADKQNANwNAIAIgASkCADcCACACIAEpAgg3AgggAiABKQIQNwIQIAIgASkCGDcCGCACIAEpAiA3AiAgAiABKQIoNwIoIAIgASkCMDcCMCACIAEpAjg3AjggAiABKQJANwJAIAIgBRAXIAMgAikDADcDACADIAIpAwg3AwggAyACKQMQNwMQIAMgAikDGDcDGCADIAIpAyA3AyAgAyACKQMoNwMoIAMgAikDMDcDMCADIAIpAzg3AzggAyACKQNANwNAIAMQGyAMQdgBaiIIIAMpAwA3AwAgCCADKQMINwMIIAggAykDEDcDECAIIAMpAxg3AxggCCADKQMgNwMgIAggAykDKDcDKCAIIAMpAzA3AzAgCCADKQM4NwM4IAggAykDQDcDQCACIABByABqIg0pAgA3AgAgAiANKQIINwIIIAIgDSkCEDcCECACIA0pAhg3AhggAiANKQIgNwIgIAIgDSkCKDcCKCACIA0pAjA3AjAgAiANKQI4NwI4IAIgDSkCQDcCQCACIAYQFyADIAIpAwA3AwAgAyACKQMINwMIIAMgAikDEDcDECADIAIpAxg3AxggAyACKQMgNwMgIAMgAikDKDcDKCADIAIpAzA3AzAgAyACKQM4NwM4IAMgAikDQDcDQCADEBsgDEHwB2oiBCADKQMANwMAIAQgAykDCDcDCCAEIAMpAxA3AxAgBCADKQMYNwMYIAQgAykDIDcDICAEIAMpAyg3AyggBCADKQMwNwMwIAQgAykDODcDOCAEIAMpA0A3A0AgAiAEKQMANwMAIAIgBCkDCDcDCCACIAQpAxA3AxAgAiAEKQMYNwMYIAIgBCkDIDcDICACIAQpAyg3AyggAiAEKQMwNwMwIAIgBCkDODcDOCACIAQpA0A3A0AgAiAOEBcgAyACKQMANwMAIAMgAikDCDcDCCADIAIpAxA3AxAgAyACKQMYNwMYIAMgAikDIDcDICADIAIpAyg3AyggAyACKQMwNwMwIAMgAikDODcDOCADIAIpA0A3A0AgAxAbIAxBoAJqIgogAykDADcDACAKIAMpAwg3AwggCiADKQMQNwMQIAogAykDGDcDGCAKIAMpAyA3AyAgCiADKQMoNwMoIAogAykDMDcDMCAKIAMpAzg3AzggCiADKQNANwNAIAIgAUHIAGoiASkCADcCACACIAEpAgg3AgggAiABKQIQNwIQIAIgASkCGDcCGCACIAEpAiA3AiAgAiABKQIoNwIoIAIgASkCMDcCMCACIAEpAjg3AjggAiABKQJANwJAIAIgBRAXIAMgAikDADcDACADIAIpAwg3AwggAyACKQMQNwMQIAMgAikDGDcDGCADIAIpAyA3AyAgAyACKQMoNwMoIAMgAikDMDcDMCADIAIpAzg3AzggAyACKQNANwNAIAMQGyAEIAMpAwA3AwAgBCADKQMINwMIIAQgAykDEDcDECAEIAMpAxg3AxggBCADKQMgNwMgIAQgAykDKDcDKCAEIAMpAzA3AzAgBCADKQM4NwM4IAQgAykDQDcDQCACIAQpAwA3AwAgAiAEKQMINwMIIAIgBCkDEDcDECACIAQpAxg3AxggAiAEKQMgNwMgIAIgBCkDKDcDKCACIAQpAzA3AzAgAiAEKQM4NwM4IAIgBCkDQDcDQCACIAkQFyADIAIpAwA3AwAgAyACKQMINwMIIAMgAikDEDcDECADIAIpAxg3AxggAyACKQMgNwMgIAMgAikDKDcDKCADIAIpAzA3AzAgAyACKQM4NwM4IAMgAikDQDcDQCADEBsgDEHoAmoiASADKQMANwMAIAEgAykDCDcDCCABIAMpAxA3AxAgASADKQMYNwMYIAEgAykDIDcDICABIAMpAyg3AyggASADKQMwNwMwIAEgAykDODcDOCABIAMpA0A3A0AgAiAHKQMANwMAIAIgBykDCDcDCCACIAcpAxA3AxAgAiAHKQMYNwMYIAIgBykDIDcDICACIAcpAyg3AyggAiAHKQMwNwMwIAIgBykDODcDOCACIAcpA0A3A0AgAiAIEBYgAyACKQMANwMAIAMgAikDCDcDCCADIAIpAxA3AxAgAyACKQMYNwMYIAMgAikDIDcDICADIAIpAyg3AyggAyACKQMwNwMwIAMgAikDODcDOCADIAIpA0A3A0AgAywARARAIANBwAkQFQsgDEGwA2oiCyADKQMANwMAIAsgAykDCDcDCCALIAMpAxA3AxAgCyADKQMYNwMYIAsgAykDIDcDICALIAMpAyg3AyggCyADKQMwNwMwIAsgAykDODcDOCALIAMpA0A3A0AgAiAKKQMANwMAIAIgCikDCDcDCCACIAopAxA3AxAgAiAKKQMYNwMYIAIgCikDIDcDICACIAopAyg3AyggAiAKKQMwNwMwIAIgCikDODcDOCACIAopA0A3A0AgAiABEBYgAyACKQMANwMAIAMgAikDCDcDCCADIAIpAxA3AxAgAyACKQMYNwMYIAMgAikDIDcDICADIAIpAyg3AyggAyACKQMwNwMwIAMgAikDODcDOCADIAIpA0A3A0AgAywARARAIANBwAkQFQsgDEH4A2oiCCADKQMANwMAIAggAykDCDcDCCAIIAMpAxA3AxAgCCADKQMYNwMYIAggAykDIDcDICAIIAMpAyg3AyggCCADKQMwNwMwIAggAykDODcDOCAIIAMpA0A3A0AgCygCAEEBRgRAIAsoAgRFBEACQAJAIAgoAgBBAUcNACAIKAIEDQAgABAkDAELIAJBiAopAwA3AwAgAkGQCikDADcDCCACQZgKKQMANwMQIAJBoAopAwA3AxggAkGoCikDADcDICACQbAKKQMANwMoIAJBuAopAwA3AzAgAkHACikDADcDOCACQcgKKQMANwNAIAJByABqIgFBiAopAwA3AwAgAUGQCikDADcDCCABQZgKKQMANwMQIAFBoAopAwA3AxggAUGoCikDADcDICABQbAKKQMANwMoIAFBuAopAwA3AzAgAUHACikDADcDOCABQcgKKQMANwNAIAJBkAFqIgFB2AwpAgA3AgAgAUHgDCkCADcCCCABQegMKQIANwIQIAFB8AwpAgA3AhggAUH4DCkCADcCICABQYANKQIANwIoIAFBiA0pAgA3AjAgAUGQDSkCADcCOCABQZgNKQIANwJAIAAgAkHYARA8GgsgDCQCDwsLIAxBqAdqIQUgAiALKQMANwMAIAIgCykDCDcDCCACIAspAxA3AxAgAiALKQMYNwMYIAIgCykDIDcDICACIAspAyg3AyggAiALKQMwNwMwIAIgCykDODcDOCACIAspA0A3A0AgAiALEBcgAyACKQMANwMAIAMgAikDCDcDCCADIAIpAxA3AxAgAyACKQMYNwMYIAMgAikDIDcDICADIAIpAyg3AyggAyACKQMwNwMwIAMgAikDODcDOCADIAIpA0A3A0AgAxAbIAxBwARqIgEgAykDADcDACABIAMpAwg3AwggASADKQMQNwMQIAEgAykDGDcDGCABIAMpAyA3AyAgASADKQMoNwMoIAEgAykDMDcDMCABIAMpAzg3AzggASADKQNANwNAIAIgBykDADcDACACIAcpAwg3AwggAiAHKQMQNwMQIAIgBykDGDcDGCACIAcpAyA3AyAgAiAHKQMoNwMoIAIgBykDMDcDMCACIAcpAzg3AzggAiAHKQNANwNAIAIgARAXIAMgAikDADcDACADIAIpAwg3AwggAyACKQMQNwMQIAMgAikDGDcDGCADIAIpAyA3AyAgAyACKQMoNwMoIAMgAikDMDcDMCADIAIpAzg3AzggAyACKQNANwNAIAMQGyAMQYgFaiIHIAMpAwA3AwAgByADKQMINwMIIAcgAykDEDcDECAHIAMpAxg3AxggByADKQMgNwMgIAcgAykDKDcDKCAHIAMpAzA3AzAgByADKQM4NwM4IAcgAykDQDcDQCACIAEpAwA3AwAgAiABKQMINwMIIAIgASkDEDcDECACIAEpAxg3AxggAiABKQMgNwMgIAIgASkDKDcDKCACIAEpAzA3AzAgAiABKQM4NwM4IAIgASkDQDcDQCACIAsQFyADIAIpAwA3AwAgAyACKQMINwMIIAMgAikDEDcDECADIAIpAxg3AxggAyACKQMgNwMgIAMgAikDKDcDKCADIAIpAzA3AzAgAyACKQM4NwM4IAMgAikDQDcDQCADEBsgDEHQBWoiDyADKQMANwMAIA8gAykDCDcDCCAPIAMpAxA3AxAgDyADKQMYNwMYIA8gAykDIDcDICAPIAMpAyg3AyggDyADKQMwNwMwIA8gAykDODcDOCAPIAMpA0A3A0AgAiAIKQMANwMAIAIgCCkDCDcDCCACIAgpAxA3AxAgAiAIKQMYNwMYIAIgCCkDIDcDICACIAgpAyg3AyggAiAIKQMwNwMwIAIgCCkDODcDOCACIAgpA0A3A0AgAiAIEBcgAyACKQMANwMAIAMgAikDCDcDCCADIAIpAxA3AxAgAyACKQMYNwMYIAMgAikDIDcDICADIAIpAyg3AyggAyACKQMwNwMwIAMgAikDODcDOCADIAIpA0A3A0AgAxAbIAQgAykDADcDACAEIAMpAwg3AwggBCADKQMQNwMQIAQgAykDGDcDGCAEIAMpAyA3AyAgBCADKQMoNwMoIAQgAykDMDcDMCAEIAMpAzg3AzggBCADKQNANwNAIAIgBCkDADcDACACIAQpAwg3AwggAiAEKQMQNwMQIAIgBCkDGDcDGCACIAQpAyA3AyAgAiAEKQMoNwMoIAIgBCkDMDcDMCACIAQpAzg3AzggAiAEKQNANwNAIAIgDxAVIAMgAikDADcDACADIAIpAwg3AwggAyACKQMQNwMQIAMgAikDGDcDGCADIAIpAyA3AyAgAyACKQMoNwMoIAMgAikDMDcDMCADIAIpAzg3AzggAyACKQNANwNAAkACQCADKAIAIgFBCEYEQEEIIQYCQAJAAkADQCAGQX9qIQEgBkUNBSABQRBPDQEgA0EEaiABQQJ0aigCACIGIAFBAnRBxAlqKAIAIhBHDQIgASEGDAALAAtBvAsgAUEQECgMAQsgBiAQTw0CCwUgAUEITw0BCwwBCyADQcAJEBYLIAUgAykDADcDACAFIAMpAwg3AwggBSADKQMQNwMQIAUgAykDGDcDGCAFIAMpAyA3AyAgBSADKQMoNwMoIAUgAykDMDcDMCAFIAMpAzg3AzggBSADKQNANwNAIAIgBSkDADcDACACIAUpAwg3AwggAiAFKQMQNwMQIAIgBSkDGDcDGCACIAUpAyA3AyAgAiAFKQMoNwMoIAIgBSkDMDcDMCACIAUpAzg3AzggAiAFKQNANwNAIAIgBxAWIAMgAikDADcDACADIAIpAwg3AwggAyACKQMQNwMQIAMgAikDGDcDGCADIAIpAyA3AyAgAyACKQMoNwMoIAMgAikDMDcDMCADIAIpAzg3AzggAyACKQNANwNAIAMsAEQEQCADQcAJEBULIAxB4AZqIgEgAykDADcDACABIAMpAwg3AwggASADKQMQNwMQIAEgAykDGDcDGCABIAMpAyA3AyAgASADKQMoNwMoIAEgAykDMDcDMCABIAMpAzg3AzggASADKQNANwNAIAIgASkDADcDACACIAEpAwg3AwggAiABKQMQNwMQIAIgASkDGDcDGCACIAEpAyA3AyAgAiABKQMoNwMoIAIgASkDMDcDMCACIAEpAzg3AzggAiABKQNANwNAIAIgBxAWIAMgAikDADcDACADIAIpAwg3AwggAyACKQMQNwMQIAMgAikDGDcDGCADIAIpAyA3AyAgAyACKQMoNwMoIAMgAikDMDcDMCADIAIpAzg3AzggAyACKQNANwNAIAMsAEQEQCADQcAJEBULIAxBmAZqIgYgAykDADcDACAGIAMpAwg3AwggBiADKQMQNwMQIAYgAykDGDcDGCAGIAMpAyA3AyAgBiADKQMoNwMoIAYgAykDMDcDMCAGIAMpAzg3AzggBiADKQNANwNAIAAgBikCADcCACAAIAYpAgg3AgggACAGKQIQNwIQIAAgBikCGDcCGCAAIAYpAiA3AiAgACAGKQIoNwIoIAAgBikCMDcCMCAAIAYpAjg3AjggACAGKQJANwJAIAIgBykDADcDACACIAcpAwg3AwggAiAHKQMQNwMQIAIgBykDGDcDGCACIAcpAyA3AyAgAiAHKQMoNwMoIAIgBykDMDcDMCACIAcpAzg3AzggAiAHKQNANwNAIAIgABAWIAMgAikDADcDACADIAIpAwg3AwggAyACKQMQNwMQIAMgAikDGDcDGCADIAIpAyA3AyAgAyACKQMoNwMoIAMgAikDMDcDMCADIAIpAzg3AzggAyACKQNANwNAIAMsAEQEQCADQcAJEBULIAUgAykDADcDACAFIAMpAwg3AwggBSADKQMQNwMQIAUgAykDGDcDGCAFIAMpAyA3AyAgBSADKQMoNwMoIAUgAykDMDcDMCAFIAMpAzg3AzggBSADKQNANwNAIAIgCCkDADcDACACIAgpAwg3AwggAiAIKQMQNwMQIAIgCCkDGDcDGCACIAgpAyA3AyAgAiAIKQMoNwMoIAIgCCkDMDcDMCACIAgpAzg3AzggAiAIKQNANwNAIAIgBRAXIAMgAikDADcDACADIAIpAwg3AwggAyACKQMQNwMQIAMgAikDGDcDGCADIAIpAyA3AyAgAyACKQMoNwMoIAMgAikDMDcDMCADIAIpAzg3AzggAyACKQNANwNAIAMQGyABIAMpAwA3AwAgASADKQMINwMIIAEgAykDEDcDECABIAMpAxg3AxggASADKQMgNwMgIAEgAykDKDcDKCABIAMpAzA3AzAgASADKQM4NwM4IAEgAykDQDcDQCACIAopAwA3AwAgAiAKKQMINwMIIAIgCikDEDcDECACIAopAxg3AxggAiAKKQMgNwMgIAIgCikDKDcDKCACIAopAzA3AzAgAiAKKQM4NwM4IAIgCikDQDcDQCACIA8QFyADIAIpAwA3AwAgAyACKQMINwMIIAMgAikDEDcDECADIAIpAxg3AxggAyACKQMgNwMgIAMgAikDKDcDKCADIAIpAzA3AzAgAyACKQM4NwM4IAMgAikDQDcDQCADEBsgBCADKQMANwMAIAQgAykDCDcDCCAEIAMpAxA3AxAgBCADKQMYNwMYIAQgAykDIDcDICAEIAMpAyg3AyggBCADKQMwNwMwIAQgAykDODcDOCAEIAMpA0A3A0AgAiABKQMANwMAIAIgASkDCDcDCCACIAEpAxA3AxAgAiABKQMYNwMYIAIgASkDIDcDICACIAEpAyg3AyggAiABKQMwNwMwIAIgASkDODcDOCACIAEpA0A3A0AgAiAEEBYgAyACKQMANwMAIAMgAikDCDcDCCADIAIpAxA3AxAgAyACKQMYNwMYIAMgAikDIDcDICADIAIpAyg3AyggAyACKQMwNwMwIAMgAikDODcDOCADIAIpA0A3A0AgAywARARAIANBwAkQFQsgBiADKQMANwMAIAYgAykDCDcDCCAGIAMpAxA3AxAgBiADKQMYNwMYIAYgAykDIDcDICAGIAMpAyg3AyggBiADKQMwNwMwIAYgAykDODcDOCAGIAMpA0A3A0AgDSAGKQIANwIAIA0gBikCCDcCCCANIAYpAhA3AhAgDSAGKQIYNwIYIA0gBikCIDcCICANIAYpAig3AiggDSAGKQIwNwIwIA0gBikCODcCOCANIAYpAkA3AkAgAiAJKQIANwIAIAIgCSkCCDcCCCACIAkpAhA3AhAgAiAJKQIYNwIYIAIgCSkCIDcCICACIAkpAig3AiggAiAJKQIwNwIwIAIgCSkCODcCOCACIAkpAkA3AkAgAiAOEBcgAyACKQMANwMAIAMgAikDCDcDCCADIAIpAxA3AxAgAyACKQMYNwMYIAMgAikDIDcDICADIAIpAyg3AyggAyACKQMwNwMwIAMgAikDODcDOCADIAIpA0A3A0AgAxAbIAQgAykDADcDACAEIAMpAwg3AwggBCADKQMQNwMQIAQgAykDGDcDGCAEIAMpAyA3AyAgBCADKQMoNwMoIAQgAykDMDcDMCAEIAMpAzg3AzggBCADKQNANwNAIAIgBCkDADcDACACIAQpAwg3AwggAiAEKQMQNwMQIAIgBCkDGDcDGCACIAQpAyA3AyAgAiAEKQMoNwMoIAIgBCkDMDcDMCACIAQpAzg3AzggAiAEKQNANwNAIAIgCxAXIAMgAikDADcDACADIAIpAwg3AwggAyACKQMQNwMQIAMgAikDGDcDGCADIAIpAyA3AyAgAyACKQMoNwMoIAMgAikDMDcDMCADIAIpAzg3AzggAyACKQNANwNAIAMQGyAFIAMpAwA3AwAgBSADKQMINwMIIAUgAykDEDcDECAFIAMpAxg3AxggBSADKQMgNwMgIAUgAykDKDcDKCAFIAMpAzA3AzAgBSADKQM4NwM4IAUgAykDQDcDQCAJIAUpAgA3AgAgCSAFKQIINwIIIAkgBSkCEDcCECAJIAUpAhg3AhggCSAFKQIgNwIgIAkgBSkCKDcCKCAJIAUpAjA3AjAgCSAFKQI4NwI4IAkgBSkCQDcCQCAMJAILC94KAQt/AkAjAiEJIwJBgAVqJAIgCUG4BGohAiAJQfADaiEDIAlBqANqIQQgCUHgAmohBSAJQZgCaiEIIAlB0AFqIQcgCUHAAGoiCiABQZABEDwaIAEoApABIQsgASgClAEhDCAJIgYgAUGYAWoiASkCADcCACAGIAEpAgg3AgggBiABKQIQNwIQIAYgASkCGDcCGCAGIAEpAiA3AiAgBiABKQIoNwIoIAYgASkCMDcCMCAGIAEpAjg3AjggC0EBRiAMRXEEQCAAQYgOQZQBEDwaIAkkAgUgAiALNgIAIAIgDDYCBCACQQhqIgEgBikDADcDACABIAYpAwg3AwggASAGKQMQNwMQIAEgBikDGDcDGCABIAYpAyA3AyAgASAGKQMoNwMoIAEgBikDMDcDMCABIAYpAzg3AzggByACEB0gAiAHKQMANwMAIAIgBykDCDcDCCACIAcpAxA3AxAgAiAHKQMYNwMYIAIgBykDIDcDICACIAcpAyg3AyggAiAHKQMwNwMwIAIgBykDODcDOCACIAcpA0A3A0AgAiAHEBcgAyACKQMANwMAIAMgAikDCDcDCCADIAIpAxA3AxAgAyACKQMYNwMYIAMgAikDIDcDICADIAIpAyg3AyggAyACKQMwNwMwIAMgAikDODcDOCADIAIpA0A3A0AgAxAbIAggAykDADcDACAIIAMpAwg3AwggCCADKQMQNwMQIAggAykDGDcDGCAIIAMpAyA3AyAgCCADKQMoNwMoIAggAykDMDcDMCAIIAMpAzg3AzggCCADKQNANwNAIAIgCikDADcDACACIAopAwg3AwggAiAKKQMQNwMQIAIgCikDGDcDGCACIAopAyA3AyAgAiAKKQMoNwMoIAIgCikDMDcDMCACIAopAzg3AzggAiAKKQNANwNAIAIgCBAXIAMgAikDADcDACADIAIpAwg3AwggAyACKQMQNwMQIAMgAikDGDcDGCADIAIpAyA3AyAgAyACKQMoNwMoIAMgAikDMDcDMCADIAIpAzg3AzggAyACKQNANwNAIAMQGyAFIAMpAwA3AwAgBSADKQMINwMIIAUgAykDEDcDECAFIAMpAxg3AxggBSADKQMgNwMgIAUgAykDKDcDKCAFIAMpAzA3AzAgBSADKQM4NwM4IAUgAykDQDcDQCACIApByABqIgEpAwA3AwAgAiABKQMINwMIIAIgASkDEDcDECACIAEpAxg3AxggAiABKQMgNwMgIAIgASkDKDcDKCACIAEpAzA3AzAgAiABKQM4NwM4IAIgASkDQDcDQCACIAgQFyADIAIpAwA3AwAgAyACKQMINwMIIAMgAikDEDcDECADIAIpAxg3AxggAyACKQMgNwMgIAMgAikDKDcDKCADIAIpAzA3AzAgAyACKQM4NwM4IAMgAikDQDcDQCADEBsgBCADKQMANwMAIAQgAykDCDcDCCAEIAMpAxA3AxAgBCADKQMYNwMYIAQgAykDIDcDICAEIAMpAyg3AyggBCADKQMwNwMwIAQgAykDODcDOCAEIAMpA0A3A0AgBCAHEBcgBBAbIAAgBSkCADcCACAAIAUpAgg3AgggACAFKQIQNwIQIAAgBSkCGDcCGCAAIAUpAiA3AiAgACAFKQIoNwIoIAAgBSkCMDcCMCAAIAUpAjg3AjggACAFKQJANwJAIABByABqIgEgBCkCADcCACABIAQpAgg3AgggASAEKQIQNwIQIAEgBCkCGDcCGCABIAQpAiA3AiAgASAEKQIoNwIoIAEgBCkCMDcCMCABIAQpAjg3AjggASAEKQJANwJAIABBADoAkAEgCSQCCwsLhC0BDX8CQCMCIQwjAkHQCWokAiAMQbgIaiEDIAxB4AZqIQIgACgCkAFBAUYEQCAAKAKUAUUEQCABLACQASEHIAMgAUGQARA8GiAHQQFxBEAgAkGICikDADcDACACQZAKKQMANwMIIAJBmAopAwA3AxAgAkGgCikDADcDGCACQagKKQMANwMgIAJBsAopAwA3AyggAkG4CikDADcDMCACQcAKKQMANwM4IAJByAopAwA3A0AgAkHIAGoiAUGICikDADcDACABQZAKKQMANwMIIAFBmAopAwA3AxAgAUGgCikDADcDGCABQagKKQMANwMgIAFBsAopAwA3AyggAUG4CikDADcDMCABQcAKKQMANwM4IAFByAopAwA3A0AgAkGQAWoiAUHYDCkCADcCACABQeAMKQIANwIIIAFB6AwpAgA3AhAgAUHwDCkCADcCGCABQfgMKQIANwIgIAFBgA0pAgA3AiggAUGIDSkCADcCMCABQZANKQIANwI4IAFBmA0pAgA3AkAFIAIgAykDADcDACACIAMpAwg3AwggAiADKQMQNwMQIAIgAykDGDcDGCACIAMpAyA3AyAgAiADKQMoNwMoIAIgAykDMDcDMCACIAMpAzg3AzggAiADKQNANwNAIAJByABqIgEgA0HIAGoiBykDADcDACABIAcpAwg3AwggASAHKQMQNwMQIAEgBykDGDcDGCABIAcpAyA3AyAgASAHKQMoNwMoIAEgBykDMDcDMCABIAcpAzg3AzggASAHKQNANwNAIAJBkAFqIgFBiAopAwA3AwAgAUGQCikDADcDCCABQZgKKQMANwMQIAFBoAopAwA3AxggAUGoCikDADcDICABQbAKKQMANwMoIAFBuAopAwA3AzAgAUHACikDADcDOCABQcgKKQMANwNACyAAIAJB2AEQPBogDCQCDwsLIAEsAJABBEAgDCQCDwsgAyAAQZABaiIKKQIANwIAIAMgCikCCDcCCCADIAopAhA3AhAgAyAKKQIYNwIYIAMgCikCIDcCICADIAopAig3AiggAyAKKQIwNwIwIAMgCikCODcCOCADIAopAkA3AkAgAyAKEBcgAiADKQMANwMAIAIgAykDCDcDCCACIAMpAxA3AxAgAiADKQMYNwMYIAIgAykDIDcDICACIAMpAyg3AyggAiADKQMwNwMwIAIgAykDODcDOCACIAMpA0A3A0AgAhAbIAwiByACKQMANwMAIAcgAikDCDcDCCAHIAIpAxA3AxAgByACKQMYNwMYIAcgAikDIDcDICAHIAIpAyg3AyggByACKQMwNwMwIAcgAikDODcDOCAHIAIpA0A3A0AgAyABKQIANwIAIAMgASkCCDcCCCADIAEpAhA3AhAgAyABKQIYNwIYIAMgASkCIDcCICADIAEpAig3AiggAyABKQIwNwIwIAMgASkCODcCOCADIAEpAkA3AkAgAyAHEBcgAiADKQMANwMAIAIgAykDCDcDCCACIAMpAxA3AxAgAiADKQMYNwMYIAIgAykDIDcDICACIAMpAyg3AyggAiADKQMwNwMwIAIgAykDODcDOCACIAMpA0A3A0AgAhAbIAxByABqIgQgAikDADcDACAEIAIpAwg3AwggBCACKQMQNwMQIAQgAikDGDcDGCAEIAIpAyA3AyAgBCACKQMoNwMoIAQgAikDMDcDMCAEIAIpAzg3AzggBCACKQNANwNAIAMgAUHIAGoiASkCADcCACADIAEpAgg3AgggAyABKQIQNwIQIAMgASkCGDcCGCADIAEpAiA3AiAgAyABKQIoNwIoIAMgASkCMDcCMCADIAEpAjg3AjggAyABKQJANwJAIAMgBxAXIAIgAykDADcDACACIAMpAwg3AwggAiADKQMQNwMQIAIgAykDGDcDGCACIAMpAyA3AyAgAiADKQMoNwMoIAIgAykDMDcDMCACIAMpAzg3AzggAiADKQNANwNAIAIQGyAMQZABaiIBIAIpAwA3AwAgASACKQMINwMIIAEgAikDEDcDECABIAIpAxg3AxggASACKQMgNwMgIAEgAikDKDcDKCABIAIpAzA3AzAgASACKQM4NwM4IAEgAikDQDcDQCABIAoQFyABEBsgAyAAKQIANwIAIAMgACkCCDcCCCADIAApAhA3AhAgAyAAKQIYNwIYIAMgACkCIDcCICADIAApAig3AiggAyAAKQIwNwIwIAMgACkCODcCOCADIAApAkA3AkAgAyAEEBYgAiADKQMANwMAIAIgAykDCDcDCCACIAMpAxA3AxAgAiADKQMYNwMYIAIgAykDIDcDICACIAMpAyg3AyggAiADKQMwNwMwIAIgAykDODcDOCACIAMpA0A3A0AgAiwARARAIAJBwAkQFQsgDEHYAWoiCSACKQMANwMAIAkgAikDCDcDCCAJIAIpAxA3AxAgCSACKQMYNwMYIAkgAikDIDcDICAJIAIpAyg3AyggCSACKQMwNwMwIAkgAikDODcDOCAJIAIpA0A3A0AgAyAAQcgAaiIEKQIANwIAIAMgBCkCCDcCCCADIAQpAhA3AhAgAyAEKQIYNwIYIAMgBCkCIDcCICADIAQpAig3AiggAyAEKQIwNwIwIAMgBCkCODcCOCADIAQpAkA3AkAgAyABEBYgAiADKQMANwMAIAIgAykDCDcDCCACIAMpAxA3AxAgAiADKQMYNwMYIAIgAykDIDcDICACIAMpAyg3AyggAiADKQMwNwMwIAIgAykDODcDOCACIAMpA0A3A0AgAiwARARAIAJBwAkQFQsgDEGgAmoiBSACKQMANwMAIAUgAikDCDcDCCAFIAIpAxA3AxAgBSACKQMYNwMYIAUgAikDIDcDICAFIAIpAyg3AyggBSACKQMwNwMwIAUgAikDODcDOCAFIAIpA0A3A0AgCSgCAEEBRgRAIAkoAgRFBEACQAJAIAUoAgBBAUcNACAFKAIEDQAgABAkDAELIABBiAopAgA3AgAgAEGQCikCADcCCCAAQZgKKQIANwIQIABBoAopAgA3AhggAEGoCikCADcCICAAQbAKKQIANwIoIABBuAopAgA3AjAgAEHACikCADcCOCAAQcgKKQIANwJAIARBiAopAgA3AgAgBEGQCikCADcCCCAEQZgKKQIANwIQIARBoAopAgA3AhggBEGoCikCADcCICAEQbAKKQIANwIoIARBuAopAgA3AjAgBEHACikCADcCOCAEQcgKKQIANwJAIApB2AwpAgA3AgAgCkHgDCkCADcCCCAKQegMKQIANwIQIApB8AwpAgA3AhggCkH4DCkCADcCICAKQYANKQIANwIoIApBiA0pAgA3AjAgCkGQDSkCADcCOCAKQZgNKQIANwJACyAMJAIPCwsgDEHQBWohBiADIAkpAwA3AwAgAyAJKQMINwMIIAMgCSkDEDcDECADIAkpAxg3AxggAyAJKQMgNwMgIAMgCSkDKDcDKCADIAkpAzA3AzAgAyAJKQM4NwM4IAMgCSkDQDcDQCADIAkQFyACIAMpAwA3AwAgAiADKQMINwMIIAIgAykDEDcDECACIAMpAxg3AxggAiADKQMgNwMgIAIgAykDKDcDKCACIAMpAzA3AzAgAiADKQM4NwM4IAIgAykDQDcDQCACEBsgDEHoAmoiASACKQMANwMAIAEgAikDCDcDCCABIAIpAxA3AxAgASACKQMYNwMYIAEgAikDIDcDICABIAIpAyg3AyggASACKQMwNwMwIAEgAikDODcDOCABIAIpA0A3A0AgAyAAKQIANwIAIAMgACkCCDcCCCADIAApAhA3AhAgAyAAKQIYNwIYIAMgACkCIDcCICADIAApAig3AiggAyAAKQIwNwIwIAMgACkCODcCOCADIAApAkA3AkAgAyABEBcgAiADKQMANwMAIAIgAykDCDcDCCACIAMpAxA3AxAgAiADKQMYNwMYIAIgAykDIDcDICACIAMpAyg3AyggAiADKQMwNwMwIAIgAykDODcDOCACIAMpA0A3A0AgAhAbIAxBsANqIgsgAikDADcDACALIAIpAwg3AwggCyACKQMQNwMQIAsgAikDGDcDGCALIAIpAyA3AyAgCyACKQMoNwMoIAsgAikDMDcDMCALIAIpAzg3AzggCyACKQNANwNAIAMgASkDADcDACADIAEpAwg3AwggAyABKQMQNwMQIAMgASkDGDcDGCADIAEpAyA3AyAgAyABKQMoNwMoIAMgASkDMDcDMCADIAEpAzg3AzggAyABKQNANwNAIAMgCRAXIAIgAykDADcDACACIAMpAwg3AwggAiADKQMQNwMQIAIgAykDGDcDGCACIAMpAyA3AyAgAiADKQMoNwMoIAIgAykDMDcDMCACIAMpAzg3AzggAiADKQNANwNAIAIQGyAMQfgDaiINIAIpAwA3AwAgDSACKQMINwMIIA0gAikDEDcDECANIAIpAxg3AxggDSACKQMgNwMgIA0gAikDKDcDKCANIAIpAzA3AzAgDSACKQM4NwM4IA0gAikDQDcDQCADIAUpAwA3AwAgAyAFKQMINwMIIAMgBSkDEDcDECADIAUpAxg3AxggAyAFKQMgNwMgIAMgBSkDKDcDKCADIAUpAzA3AzAgAyAFKQM4NwM4IAMgBSkDQDcDQCADIAUQFyACIAMpAwA3AwAgAiADKQMINwMIIAIgAykDEDcDECACIAMpAxg3AxggAiADKQMgNwMgIAIgAykDKDcDKCACIAMpAzA3AzAgAiADKQM4NwM4IAIgAykDQDcDQCACEBsgDEGYBmoiCCACKQMANwMAIAggAikDCDcDCCAIIAIpAxA3AxAgCCACKQMYNwMYIAggAikDIDcDICAIIAIpAyg3AyggCCACKQMwNwMwIAggAikDODcDOCAIIAIpA0A3A0AgAyAIKQMANwMAIAMgCCkDCDcDCCADIAgpAxA3AxAgAyAIKQMYNwMYIAMgCCkDIDcDICADIAgpAyg3AyggAyAIKQMwNwMwIAMgCCkDODcDOCADIAgpA0A3A0AgAyANEBUgAiADKQMANwMAIAIgAykDCDcDCCACIAMpAxA3AxAgAiADKQMYNwMYIAIgAykDIDcDICACIAMpAyg3AyggAiADKQMwNwMwIAIgAykDODcDOCACIAMpA0A3A0ACQAJAIAIoAgAiAUEIRgRAQQghBwJAAkACQANAIAdBf2ohASAHRQ0FIAFBEE8NASACQQRqIAFBAnRqKAIAIgcgAUECdEHECWooAgAiDkcNAiABIQcMAAsAC0G8CyABQRAQKAwBCyAHIA5PDQILBSABQQhPDQELDAELIAJBwAkQFgsgBiACKQMANwMAIAYgAikDCDcDCCAGIAIpAxA3AxAgBiACKQMYNwMYIAYgAikDIDcDICAGIAIpAyg3AyggBiACKQMwNwMwIAYgAikDODcDOCAGIAIpA0A3A0AgAyAGKQMANwMAIAMgBikDCDcDCCADIAYpAxA3AxAgAyAGKQMYNwMYIAMgBikDIDcDICADIAYpAyg3AyggAyAGKQMwNwMwIAMgBikDODcDOCADIAYpA0A3A0AgAyALEBYgAiADKQMANwMAIAIgAykDCDcDCCACIAMpAxA3AxAgAiADKQMYNwMYIAIgAykDIDcDICACIAMpAyg3AyggAiADKQMwNwMwIAIgAykDODcDOCACIAMpA0A3A0AgAiALEBYgAkHEAGoiASwAAARAA0AgAkHACRAVIAEsAAANAAsLIAxBiAVqIgEgAikDADcDACABIAIpAwg3AwggASACKQMQNwMQIAEgAikDGDcDGCABIAIpAyA3AyAgASACKQMoNwMoIAEgAikDMDcDMCABIAIpAzg3AzggASACKQNANwNAIAAgASkCADcCACAAIAEpAgg3AgggACABKQIQNwIQIAAgASkCGDcCGCAAIAEpAiA3AiAgACABKQIoNwIoIAAgASkCMDcCMCAAIAEpAjg3AjggACABKQJANwJAIAMgCykDADcDACADIAspAwg3AwggAyALKQMQNwMQIAMgCykDGDcDGCADIAspAyA3AyAgAyALKQMoNwMoIAMgCykDMDcDMCADIAspAzg3AzggAyALKQNANwNAIAMgABAWIAIgAykDADcDACACIAMpAwg3AwggAiADKQMQNwMQIAIgAykDGDcDGCACIAMpAyA3AyAgAiADKQMoNwMoIAIgAykDMDcDMCACIAMpAzg3AzggAiADKQNANwNAIAIsAEQEQCACQcAJEBULIAYgAikDADcDACAGIAIpAwg3AwggBiACKQMQNwMQIAYgAikDGDcDGCAGIAIpAyA3AyAgBiACKQMoNwMoIAYgAikDMDcDMCAGIAIpAzg3AzggBiACKQNANwNAIAMgBSkDADcDACADIAUpAwg3AwggAyAFKQMQNwMQIAMgBSkDGDcDGCADIAUpAyA3AyAgAyAFKQMoNwMoIAMgBSkDMDcDMCADIAUpAzg3AzggAyAFKQNANwNAIAMgBhAXIAIgAykDADcDACACIAMpAwg3AwggAiADKQMQNwMQIAIgAykDGDcDGCACIAMpAyA3AyAgAiADKQMoNwMoIAIgAykDMDcDMCACIAMpAzg3AzggAiADKQNANwNAIAIQGyABIAIpAwA3AwAgASACKQMINwMIIAEgAikDEDcDECABIAIpAxg3AxggASACKQMgNwMgIAEgAikDKDcDKCABIAIpAzA3AzAgASACKQM4NwM4IAEgAikDQDcDQCADIAQpAgA3AgAgAyAEKQIINwIIIAMgBCkCEDcCECADIAQpAhg3AhggAyAEKQIgNwIgIAMgBCkCKDcCKCADIAQpAjA3AjAgAyAEKQI4NwI4IAMgBCkCQDcCQCADIA0QFyACIAMpAwA3AwAgAiADKQMINwMIIAIgAykDEDcDECACIAMpAxg3AxggAiADKQMgNwMgIAIgAykDKDcDKCACIAMpAzA3AzAgAiADKQM4NwM4IAIgAykDQDcDQCACEBsgCCACKQMANwMAIAggAikDCDcDCCAIIAIpAxA3AxAgCCACKQMYNwMYIAggAikDIDcDICAIIAIpAyg3AyggCCACKQMwNwMwIAggAikDODcDOCAIIAIpA0A3A0AgAyABKQMANwMAIAMgASkDCDcDCCADIAEpAxA3AxAgAyABKQMYNwMYIAMgASkDIDcDICADIAEpAyg3AyggAyABKQMwNwMwIAMgASkDODcDOCADIAEpA0A3A0AgAyAIEBYgAiADKQMANwMAIAIgAykDCDcDCCACIAMpAxA3AxAgAiADKQMYNwMYIAIgAykDIDcDICACIAMpAyg3AyggAiADKQMwNwMwIAIgAykDODcDOCACIAMpA0A3A0AgAiwARARAIAJBwAkQFQsgDEHABGoiACACKQMANwMAIAAgAikDCDcDCCAAIAIpAxA3AxAgACACKQMYNwMYIAAgAikDIDcDICAAIAIpAyg3AyggACACKQMwNwMwIAAgAikDODcDOCAAIAIpA0A3A0AgBCAAKQIANwIAIAQgACkCCDcCCCAEIAApAhA3AhAgBCAAKQIYNwIYIAQgACkCIDcCICAEIAApAig3AiggBCAAKQIwNwIwIAQgACkCODcCOCAEIAApAkA3AkAgCiAJEBcgChAbIAwkAgsLklcBE38CQCMCIREjAkHwB2okAiAAKAKQAUEBRiIIBEAgACgClAFFBEAgESQCDwsLIBFBqAdqIQIgEUHgBmohASARQZgGaiEDIBFB0AVqIQUgEUGIBWohBiARQcAEaiEOIBFB+ANqIQcgEUGwA2ohCyARQegCaiENIBFBoAJqIQQgEUHYAWohCSARQZABaiEKIBFByABqIRAgESEPIABBkAFqIRICfwJAIAhFDQAgACgClAFBAUcNACACIAApAgA3AgAgAiAAKQIINwIIIAIgACkCEDcCECACIAApAhg3AhggAiAAKQIgNwIgIAIgACkCKDcCKCACIAApAjA3AjAgAiAAKQI4NwI4IAIgACkCQDcCQCACIAAQFyABIAIpAwA3AwAgASACKQMINwMIIAEgAikDEDcDECABIAIpAxg3AxggASACKQMgNwMgIAEgAikDKDcDKCABIAIpAzA3AzAgASACKQM4NwM4IAEgAikDQDcDQCABEBsgCSABKQMANwMAIAkgASkDCDcDCCAJIAEpAxA3AxAgCSABKQMYNwMYIAkgASkDIDcDICAJIAEpAyg3AyggCSABKQMwNwMwIAkgASkDODcDOCAJIAEpA0A3A0AgAiAAQcgAaiIMKQIANwIAIAIgDCkCCDcCCCACIAwpAhA3AhAgAiAMKQIYNwIYIAIgDCkCIDcCICACIAwpAig3AiggAiAMKQIwNwIwIAIgDCkCODcCOCACIAwpAkA3AkAgAiAMEBcgASACKQMANwMAIAEgAikDCDcDCCABIAIpAxA3AxAgASACKQMYNwMYIAEgAikDIDcDICABIAIpAyg3AyggASACKQMwNwMwIAEgAikDODcDOCABIAIpA0A3A0AgARAbIAQgASkDADcDACAEIAEpAwg3AwggBCABKQMQNwMQIAQgASkDGDcDGCAEIAEpAyA3AyAgBCABKQMoNwMoIAQgASkDMDcDMCAEIAEpAzg3AzggBCABKQNANwNAIAIgBCkDADcDACACIAQpAwg3AwggAiAEKQMQNwMQIAIgBCkDGDcDGCACIAQpAyA3AyAgAiAEKQMoNwMoIAIgBCkDMDcDMCACIAQpAzg3AzggAiAEKQNANwNAIAIgBBAXIAEgAikDADcDACABIAIpAwg3AwggASACKQMQNwMQIAEgAikDGDcDGCABIAIpAyA3AyAgASACKQMoNwMoIAEgAikDMDcDMCABIAIpAzg3AzggASACKQNANwNAIAEQGyANIAEpAwA3AwAgDSABKQMINwMIIA0gASkDEDcDECANIAEpAxg3AxggDSABKQMgNwMgIA0gASkDKDcDKCANIAEpAzA3AzAgDSABKQM4NwM4IA0gASkDQDcDQCACIAApAgA3AgAgAiAAKQIINwIIIAIgACkCEDcCECACIAApAhg3AhggAiAAKQIgNwIgIAIgACkCKDcCKCACIAApAjA3AjAgAiAAKQI4NwI4IAIgACkCQDcCQCACIAQQFSABIAIpAwA3AwAgASACKQMINwMIIAEgAikDEDcDECABIAIpAxg3AxggASACKQMgNwMgIAEgAikDKDcDKCABIAIpAzA3AzAgASACKQM4NwM4IAEgAikDQDcDQAJAAkAgASgCACIEQQhGBEBBCCEIAkACQAJAA0AgCEF/aiEEIAhFDQUgBEEQTw0BIAFBBGogBEECdGooAgAiCCAEQQJ0QcQJaigCACITRw0CIAQhCAwACwALQbwLIARBEBAoDAELIAggE08NAgsFIARBCE8NAQsMAQsgAUHACRAWCyADIAEpAwA3AwAgAyABKQMINwMIIAMgASkDEDcDECADIAEpAxg3AxggAyABKQMgNwMgIAMgASkDKDcDKCADIAEpAzA3AzAgAyABKQM4NwM4IAMgASkDQDcDQCACIAMpAwA3AwAgAiADKQMINwMIIAIgAykDEDcDECACIAMpAxg3AxggAiADKQMgNwMgIAIgAykDKDcDKCACIAMpAzA3AzAgAiADKQM4NwM4IAIgAykDQDcDQCACIAMQFyABIAIpAwA3AwAgASACKQMINwMIIAEgAikDEDcDECABIAIpAxg3AxggASACKQMgNwMgIAEgAikDKDcDKCABIAIpAzA3AzAgASACKQM4NwM4IAEgAikDQDcDQCABEBsgBSABKQMANwMAIAUgASkDCDcDCCAFIAEpAxA3AxAgBSABKQMYNwMYIAUgASkDIDcDICAFIAEpAyg3AyggBSABKQMwNwMwIAUgASkDODcDOCAFIAEpA0A3A0AgAiAFKQMANwMAIAIgBSkDCDcDCCACIAUpAxA3AxAgAiAFKQMYNwMYIAIgBSkDIDcDICACIAUpAyg3AyggAiAFKQMwNwMwIAIgBSkDODcDOCACIAUpA0A3A0AgAiAJEBYgASACKQMANwMAIAEgAikDCDcDCCABIAIpAxA3AxAgASACKQMYNwMYIAEgAikDIDcDICABIAIpAyg3AyggASACKQMwNwMwIAEgAikDODcDOCABIAIpA0A3A0AgASwARARAIAFBwAkQFQsgBiABKQMANwMAIAYgASkDCDcDCCAGIAEpAxA3AxAgBiABKQMYNwMYIAYgASkDIDcDICAGIAEpAyg3AyggBiABKQMwNwMwIAYgASkDODcDOCAGIAEpA0A3A0AgAiAGKQMANwMAIAIgBikDCDcDCCACIAYpAxA3AxAgAiAGKQMYNwMYIAIgBikDIDcDICACIAYpAyg3AyggAiAGKQMwNwMwIAIgBikDODcDOCACIAYpA0A3A0AgAiANEBYgASACKQMANwMAIAEgAikDCDcDCCABIAIpAxA3AxAgASACKQMYNwMYIAEgAikDIDcDICABIAIpAyg3AyggASACKQMwNwMwIAEgAikDODcDOCABIAIpA0A3A0AgASwARARAIAFBwAkQFQsgCyABKQMANwMAIAsgASkDCDcDCCALIAEpAxA3AxAgCyABKQMYNwMYIAsgASkDIDcDICALIAEpAyg3AyggCyABKQMwNwMwIAsgASkDODcDOCALIAEpA0A3A0AgCxAaIAIgCSkDADcDACACIAkpAwg3AwggAiAJKQMQNwMQIAIgCSkDGDcDGCACIAkpAyA3AyAgAiAJKQMoNwMoIAIgCSkDMDcDMCACIAkpAzg3AzggAiAJKQNANwNAIAIgCRAVIAEgAikDADcDACABIAIpAwg3AwggASACKQMQNwMQIAEgAikDGDcDGCABIAIpAyA3AyAgASACKQMoNwMoIAEgAikDMDcDMCABIAIpAzg3AzggASACKQNANwNAAkACQCABKAIAIgRBCEYEQEEIIQgCQAJAAkADQCAIQX9qIQQgCEUNBSAEQRBPDQEgAUEEaiAEQQJ0aigCACIIIARBAnRBxAlqKAIAIhNHDQIgBCEIDAALAAtBvAsgBEEQECgMAQsgCCATTw0CCwUgBEEITw0BCwwBCyABQcAJEBYLIAMgASkDADcDACADIAEpAwg3AwggAyABKQMQNwMQIAMgASkDGDcDGCADIAEpAyA3AyAgAyABKQMoNwMoIAMgASkDMDcDMCADIAEpAzg3AzggAyABKQNANwNAIAIgAykDADcDACACIAMpAwg3AwggAiADKQMQNwMQIAIgAykDGDcDGCACIAMpAyA3AyAgAiADKQMoNwMoIAIgAykDMDcDMCACIAMpAzg3AzggAiADKQNANwNAIAIgCRAVIAEgAikDADcDACABIAIpAwg3AwggASACKQMQNwMQIAEgAikDGDcDGCABIAIpAyA3AyAgASACKQMoNwMoIAEgAikDMDcDMCABIAIpAzg3AzggASACKQNANwNAAkACQCABKAIAIgRBCEYEQEEIIQgCQAJAAkADQCAIQX9qIQQgCEUNBSAEQRBPDQEgAUEEaiAEQQJ0aigCACIIIARBAnRBxAlqKAIAIglHDQIgBCEIDAALAAtBvAsgBEEQECgMAQsgCCAJTw0CCwUgBEEITw0BCwwBCyABQcAJEBYLIAcgASkDADcDACAHIAEpAwg3AwggByABKQMQNwMQIAcgASkDGDcDGCAHIAEpAyA3AyAgByABKQMoNwMoIAcgASkDMDcDMCAHIAEpAzg3AzggByABKQNANwNAIAIgBykDADcDACACIAcpAwg3AwggAiAHKQMQNwMQIAIgBykDGDcDGCACIAcpAyA3AyAgAiAHKQMoNwMoIAIgBykDMDcDMCACIAcpAzg3AzggAiAHKQNANwNAIAIgBxAXIAEgAikDADcDACABIAIpAwg3AwggASACKQMQNwMQIAEgAikDGDcDGCABIAIpAyA3AyAgASACKQMoNwMoIAEgAikDMDcDMCABIAIpAzg3AzggASACKQNANwNAIAEQGyADIAEpAwA3AwAgAyABKQMINwMIIAMgASkDEDcDECADIAEpAxg3AxggAyABKQMgNwMgIAMgASkDKDcDKCADIAEpAzA3AzAgAyABKQM4NwM4IAMgASkDQDcDQCACIAMpAwA3AwAgAiADKQMINwMIIAIgAykDEDcDECACIAMpAxg3AxggAiADKQMgNwMgIAIgAykDKDcDKCACIAMpAzA3AzAgAiADKQM4NwM4IAIgAykDQDcDQCACIAsQFiABIAIpAwA3AwAgASACKQMINwMIIAEgAikDEDcDECABIAIpAxg3AxggASACKQMgNwMgIAEgAikDKDcDKCABIAIpAzA3AzAgASACKQM4NwM4IAEgAikDQDcDQCABLABEBEAgAUHACRAVCyAFIAEpAwA3AwAgBSABKQMINwMIIAUgASkDEDcDECAFIAEpAxg3AxggBSABKQMgNwMgIAUgASkDKDcDKCAFIAEpAzA3AzAgBSABKQM4NwM4IAUgASkDQDcDQCACIAUpAwA3AwAgAiAFKQMINwMIIAIgBSkDEDcDECACIAUpAxg3AxggAiAFKQMgNwMgIAIgBSkDKDcDKCACIAUpAzA3AzAgAiAFKQM4NwM4IAIgBSkDQDcDQCACIAsQFiABIAIpAwA3AwAgASACKQMINwMIIAEgAikDEDcDECABIAIpAxg3AxggASACKQMgNwMgIAEgAikDKDcDKCABIAIpAzA3AzAgASACKQM4NwM4IAEgAikDQDcDQCABLABEBEAgAUHACRAVCyAOIAEpAwA3AwAgDiABKQMINwMIIA4gASkDEDcDECAOIAEpAxg3AxggDiABKQMgNwMgIA4gASkDKDcDKCAOIAEpAzA3AzAgDiABKQM4NwM4IA4gASkDQDcDQCAGIA0pAwA3AwAgBiANKQMINwMIIAYgDSkDEDcDECAGIA0pAxg3AxggBiANKQMgNwMgIAYgDSkDKDcDKCAGIA0pAzA3AzAgBiANKQM4NwM4IAYgDSkDQDcDQCAGEBogBhAaIAYQGiAPIA4pAwA3AwAgDyAOKQMINwMIIA8gDikDEDcDECAPIA4pAxg3AxggDyAOKQMgNwMgIA8gDikDKDcDKCAPIA4pAzA3AzAgDyAOKQM4NwM4IA8gDikDQDcDQCACIAspAwA3AwAgAiALKQMINwMIIAIgCykDEDcDECACIAspAxg3AxggAiALKQMgNwMgIAIgCykDKDcDKCACIAspAzA3AzAgAiALKQM4NwM4IAIgCykDQDcDQCACIA4QFiABIAIpAwA3AwAgASACKQMINwMIIAEgAikDEDcDECABIAIpAxg3AxggASACKQMgNwMgIAEgAikDKDcDKCABIAIpAzA3AzAgASACKQM4NwM4IAEgAikDQDcDQCABLABEBEAgAUHACRAVCyADIAEpAwA3AwAgAyABKQMINwMIIAMgASkDEDcDECADIAEpAxg3AxggAyABKQMgNwMgIAMgASkDKDcDKCADIAEpAzA3AzAgAyABKQM4NwM4IAMgASkDQDcDQCACIAcpAwA3AwAgAiAHKQMINwMIIAIgBykDEDcDECACIAcpAxg3AxggAiAHKQMgNwMgIAIgBykDKDcDKCACIAcpAzA3AzAgAiAHKQM4NwM4IAIgBykDQDcDQCACIAMQFyABIAIpAwA3AwAgASACKQMINwMIIAEgAikDEDcDECABIAIpAxg3AxggASACKQMgNwMgIAEgAikDKDcDKCABIAIpAzA3AzAgASACKQM4NwM4IAEgAikDQDcDQCABEBsgBSABKQMANwMAIAUgASkDCDcDCCAFIAEpAxA3AxAgBSABKQMYNwMYIAUgASkDIDcDICAFIAEpAyg3AyggBSABKQMwNwMwIAUgASkDODcDOCAFIAEpA0A3A0AgAiAFKQMANwMAIAIgBSkDCDcDCCACIAUpAxA3AxAgAiAFKQMYNwMYIAIgBSkDIDcDICACIAUpAyg3AyggAiAFKQMwNwMwIAIgBSkDODcDOCACIAUpA0A3A0AgAiAGEBYgASACKQMANwMAIAEgAikDCDcDCCABIAIpAxA3AxAgASACKQMYNwMYIAEgAikDIDcDICABIAIpAyg3AyggASACKQMwNwMwIAEgAikDODcDOCABIAIpA0A3A0AgASwARARAIAFBwAkQFQsgECABKQMANwMAIBAgASkDCDcDCCAQIAEpAxA3AxAgECABKQMYNwMYIBAgASkDIDcDICAQIAEpAyg3AyggECABKQMwNwMwIBAgASkDODcDOCAQIAEpA0A3A0AgAiAMKQIANwIAIAIgDCkCCDcCCCACIAwpAhA3AhAgAiAMKQIYNwIYIAIgDCkCIDcCICACIAwpAig3AiggAiAMKQIwNwIwIAIgDCkCODcCOCACIAwpAkA3AkAgAiAMEBUgASACKQMANwMAIAEgAikDCDcDCCABIAIpAxA3AxAgASACKQMYNwMYIAEgAikDIDcDICABIAIpAyg3AyggASACKQMwNwMwIAEgAikDODcDOCABIAIpA0A3A0ACQAJAIAEoAgAiBEEIRgRAQQghCAJAAkACQANAIAhBf2ohBCAIRQ0FIARBEE8NASABQQRqIARBAnRqKAIAIgggBEECdEHECWooAgAiAkcNAiAEIQgMAAsAC0G8CyAEQRAQKAwBCyAIIAJPDQILBSAEQQhPDQELDAELIAFBwAkQFgsgCiABKQMANwMAIAogASkDCDcDCCAKIAEpAxA3AxAgCiABKQMYNwMYIAogASkDIDcDICAKIAEpAyg3AyggCiABKQMwNwMwIAogASkDODcDOCAKIAEpA0A3A0AgDAwBCyACIAApAgA3AgAgAiAAKQIINwIIIAIgACkCEDcCECACIAApAhg3AhggAiAAKQIgNwIgIAIgACkCKDcCKCACIAApAjA3AjAgAiAAKQI4NwI4IAIgACkCQDcCQCACIAAQFyABIAIpAwA3AwAgASACKQMINwMIIAEgAikDEDcDECABIAIpAxg3AxggASACKQMgNwMgIAEgAikDKDcDKCABIAIpAzA3AzAgASACKQM4NwM4IAEgAikDQDcDQCABEBsgCSABKQMANwMAIAkgASkDCDcDCCAJIAEpAxA3AxAgCSABKQMYNwMYIAkgASkDIDcDICAJIAEpAyg3AyggCSABKQMwNwMwIAkgASkDODcDOCAJIAEpA0A3A0AgAiAAQcgAaiIMKQIANwIAIAIgDCkCCDcCCCACIAwpAhA3AhAgAiAMKQIYNwIYIAIgDCkCIDcCICACIAwpAig3AiggAiAMKQIwNwIwIAIgDCkCODcCOCACIAwpAkA3AkAgAiAMEBcgASACKQMANwMAIAEgAikDCDcDCCABIAIpAxA3AxAgASACKQMYNwMYIAEgAikDIDcDICABIAIpAyg3AyggASACKQMwNwMwIAEgAikDODcDOCABIAIpA0A3A0AgARAbIAQgASkDADcDACAEIAEpAwg3AwggBCABKQMQNwMQIAQgASkDGDcDGCAEIAEpAyA3AyAgBCABKQMoNwMoIAQgASkDMDcDMCAEIAEpAzg3AzggBCABKQNANwNAIAIgBCkDADcDACACIAQpAwg3AwggAiAEKQMQNwMQIAIgBCkDGDcDGCACIAQpAyA3AyAgAiAEKQMoNwMoIAIgBCkDMDcDMCACIAQpAzg3AzggAiAEKQNANwNAIAIgBBAXIAEgAikDADcDACABIAIpAwg3AwggASACKQMQNwMQIAEgAikDGDcDGCABIAIpAyA3AyAgASACKQMoNwMoIAEgAikDMDcDMCABIAIpAzg3AzggASACKQNANwNAIAEQGyANIAEpAwA3AwAgDSABKQMINwMIIA0gASkDEDcDECANIAEpAxg3AxggDSABKQMgNwMgIA0gASkDKDcDKCANIAEpAzA3AzAgDSABKQM4NwM4IA0gASkDQDcDQCACIAApAgA3AgAgAiAAKQIINwIIIAIgACkCEDcCECACIAApAhg3AhggAiAAKQIgNwIgIAIgACkCKDcCKCACIAApAjA3AjAgAiAAKQI4NwI4IAIgACkCQDcCQCACIAQQFSABIAIpAwA3AwAgASACKQMINwMIIAEgAikDEDcDECABIAIpAxg3AxggASACKQMgNwMgIAEgAikDKDcDKCABIAIpAzA3AzAgASACKQM4NwM4IAEgAikDQDcDQAJAAkAgASgCACIEQQhGBEBBCCEIAkACQAJAA0AgCEF/aiEEIAhFDQUgBEEQTw0BIAFBBGogBEECdGooAgAiCCAEQQJ0QcQJaigCACITRw0CIAQhCAwACwALQbwLIARBEBAoDAELIAggE08NAgsFIARBCE8NAQsMAQsgAUHACRAWCyADIAEpAwA3AwAgAyABKQMINwMIIAMgASkDEDcDECADIAEpAxg3AxggAyABKQMgNwMgIAMgASkDKDcDKCADIAEpAzA3AzAgAyABKQM4NwM4IAMgASkDQDcDQCACIAMpAwA3AwAgAiADKQMINwMIIAIgAykDEDcDECACIAMpAxg3AxggAiADKQMgNwMgIAIgAykDKDcDKCACIAMpAzA3AzAgAiADKQM4NwM4IAIgAykDQDcDQCACIAMQFyABIAIpAwA3AwAgASACKQMINwMIIAEgAikDEDcDECABIAIpAxg3AxggASACKQMgNwMgIAEgAikDKDcDKCABIAIpAzA3AzAgASACKQM4NwM4IAEgAikDQDcDQCABEBsgBSABKQMANwMAIAUgASkDCDcDCCAFIAEpAxA3AxAgBSABKQMYNwMYIAUgASkDIDcDICAFIAEpAyg3AyggBSABKQMwNwMwIAUgASkDODcDOCAFIAEpA0A3A0AgAiAFKQMANwMAIAIgBSkDCDcDCCACIAUpAxA3AxAgAiAFKQMYNwMYIAIgBSkDIDcDICACIAUpAyg3AyggAiAFKQMwNwMwIAIgBSkDODcDOCACIAUpA0A3A0AgAiAJEBYgASACKQMANwMAIAEgAikDCDcDCCABIAIpAxA3AxAgASACKQMYNwMYIAEgAikDIDcDICABIAIpAyg3AyggASACKQMwNwMwIAEgAikDODcDOCABIAIpA0A3A0AgASwARARAIAFBwAkQFQsgBiABKQMANwMAIAYgASkDCDcDCCAGIAEpAxA3AxAgBiABKQMYNwMYIAYgASkDIDcDICAGIAEpAyg3AyggBiABKQMwNwMwIAYgASkDODcDOCAGIAEpA0A3A0AgAiAGKQMANwMAIAIgBikDCDcDCCACIAYpAxA3AxAgAiAGKQMYNwMYIAIgBikDIDcDICACIAYpAyg3AyggAiAGKQMwNwMwIAIgBikDODcDOCACIAYpA0A3A0AgAiANEBYgASACKQMANwMAIAEgAikDCDcDCCABIAIpAxA3AxAgASACKQMYNwMYIAEgAikDIDcDICABIAIpAyg3AyggASACKQMwNwMwIAEgAikDODcDOCABIAIpA0A3A0AgASwARARAIAFBwAkQFQsgCyABKQMANwMAIAsgASkDCDcDCCALIAEpAxA3AxAgCyABKQMYNwMYIAsgASkDIDcDICALIAEpAyg3AyggCyABKQMwNwMwIAsgASkDODcDOCALIAEpA0A3A0AgCxAaIAIgCSkDADcDACACIAkpAwg3AwggAiAJKQMQNwMQIAIgCSkDGDcDGCACIAkpAyA3AyAgAiAJKQMoNwMoIAIgCSkDMDcDMCACIAkpAzg3AzggAiAJKQNANwNAIAIgCRAVIAEgAikDADcDACABIAIpAwg3AwggASACKQMQNwMQIAEgAikDGDcDGCABIAIpAyA3AyAgASACKQMoNwMoIAEgAikDMDcDMCABIAIpAzg3AzggASACKQNANwNAAkACQCABKAIAIgRBCEYEQEEIIQgCQAJAAkADQCAIQX9qIQQgCEUNBSAEQRBPDQEgAUEEaiAEQQJ0aigCACIIIARBAnRBxAlqKAIAIhNHDQIgBCEIDAALAAtBvAsgBEEQECgMAQsgCCATTw0CCwUgBEEITw0BCwwBCyABQcAJEBYLIAMgASkDADcDACADIAEpAwg3AwggAyABKQMQNwMQIAMgASkDGDcDGCADIAEpAyA3AyAgAyABKQMoNwMoIAMgASkDMDcDMCADIAEpAzg3AzggAyABKQNANwNAIAIgAykDADcDACACIAMpAwg3AwggAiADKQMQNwMQIAIgAykDGDcDGCACIAMpAyA3AyAgAiADKQMoNwMoIAIgAykDMDcDMCACIAMpAzg3AzggAiADKQNANwNAIAIgCRAVIAEgAikDADcDACABIAIpAwg3AwggASACKQMQNwMQIAEgAikDGDcDGCABIAIpAyA3AyAgASACKQMoNwMoIAEgAikDMDcDMCABIAIpAzg3AzggASACKQNANwNAAkACQCABKAIAIgRBCEYEQEEIIQgCQAJAAkADQCAIQX9qIQQgCEUNBSAEQRBPDQEgAUEEaiAEQQJ0aigCACIIIARBAnRBxAlqKAIAIglHDQIgBCEIDAALAAtBvAsgBEEQECgMAQsgCCAJTw0CCwUgBEEITw0BCwwBCyABQcAJEBYLIAcgASkDADcDACAHIAEpAwg3AwggByABKQMQNwMQIAcgASkDGDcDGCAHIAEpAyA3AyAgByABKQMoNwMoIAcgASkDMDcDMCAHIAEpAzg3AzggByABKQNANwNAIAIgBykDADcDACACIAcpAwg3AwggAiAHKQMQNwMQIAIgBykDGDcDGCACIAcpAyA3AyAgAiAHKQMoNwMoIAIgBykDMDcDMCACIAcpAzg3AzggAiAHKQNANwNAIAIgBxAXIAEgAikDADcDACABIAIpAwg3AwggASACKQMQNwMQIAEgAikDGDcDGCABIAIpAyA3AyAgASACKQMoNwMoIAEgAikDMDcDMCABIAIpAzg3AzggASACKQNANwNAIAEQGyAOIAEpAwA3AwAgDiABKQMINwMIIA4gASkDEDcDECAOIAEpAxg3AxggDiABKQMgNwMgIA4gASkDKDcDKCAOIAEpAzA3AzAgDiABKQM4NwM4IA4gASkDQDcDQCAGIA0pAwA3AwAgBiANKQMINwMIIAYgDSkDEDcDECAGIA0pAxg3AxggBiANKQMgNwMgIAYgDSkDKDcDKCAGIA0pAzA3AzAgBiANKQM4NwM4IAYgDSkDQDcDQCAGEBogBhAaIAYQGiACIA4pAwA3AwAgAiAOKQMINwMIIAIgDikDEDcDECACIA4pAxg3AxggAiAOKQMgNwMgIAIgDikDKDcDKCACIA4pAzA3AzAgAiAOKQM4NwM4IAIgDikDQDcDQCACIAsQFiABIAIpAwA3AwAgASACKQMINwMIIAEgAikDEDcDECABIAIpAxg3AxggASACKQMgNwMgIAEgAikDKDcDKCABIAIpAzA3AzAgASACKQM4NwM4IAEgAikDQDcDQCABLABEBEAgAUHACRAVCyADIAEpAwA3AwAgAyABKQMINwMIIAMgASkDEDcDECADIAEpAxg3AxggAyABKQMgNwMgIAMgASkDKDcDKCADIAEpAzA3AzAgAyABKQM4NwM4IAMgASkDQDcDQCACIAMpAwA3AwAgAiADKQMINwMIIAIgAykDEDcDECACIAMpAxg3AxggAiADKQMgNwMgIAIgAykDKDcDKCACIAMpAzA3AzAgAiADKQM4NwM4IAIgAykDQDcDQCACIAsQFiABIAIpAwA3AwAgASACKQMINwMIIAEgAikDEDcDECABIAIpAxg3AxggASACKQMgNwMgIAEgAikDKDcDKCABIAIpAzA3AzAgASACKQM4NwM4IAEgAikDQDcDQCABLABEBEAgAUHACRAVCyAPIAEpAwA3AwAgDyABKQMINwMIIA8gASkDEDcDECAPIAEpAxg3AxggDyABKQMgNwMgIA8gASkDKDcDKCAPIAEpAzA3AzAgDyABKQM4NwM4IA8gASkDQDcDQCACIAspAwA3AwAgAiALKQMINwMIIAIgCykDEDcDECACIAspAxg3AxggAiALKQMgNwMgIAIgCykDKDcDKCACIAspAzA3AzAgAiALKQM4NwM4IAIgCykDQDcDQCACIA8QFiABIAIpAwA3AwAgASACKQMINwMIIAEgAikDEDcDECABIAIpAxg3AxggASACKQMgNwMgIAEgAikDKDcDKCABIAIpAzA3AzAgASACKQM4NwM4IAEgAikDQDcDQCABLABEBEAgAUHACRAVCyADIAEpAwA3AwAgAyABKQMINwMIIAMgASkDEDcDECADIAEpAxg3AxggAyABKQMgNwMgIAMgASkDKDcDKCADIAEpAzA3AzAgAyABKQM4NwM4IAMgASkDQDcDQCACIAcpAwA3AwAgAiAHKQMINwMIIAIgBykDEDcDECACIAcpAxg3AxggAiAHKQMgNwMgIAIgBykDKDcDKCACIAcpAzA3AzAgAiAHKQM4NwM4IAIgBykDQDcDQCACIAMQFyABIAIpAwA3AwAgASACKQMINwMIIAEgAikDEDcDECABIAIpAxg3AxggASACKQMgNwMgIAEgAikDKDcDKCABIAIpAzA3AzAgASACKQM4NwM4IAEgAikDQDcDQCABEBsgBSABKQMANwMAIAUgASkDCDcDCCAFIAEpAxA3AxAgBSABKQMYNwMYIAUgASkDIDcDICAFIAEpAyg3AyggBSABKQMwNwMwIAUgASkDODcDOCAFIAEpA0A3A0AgAiAFKQMANwMAIAIgBSkDCDcDCCACIAUpAxA3AxAgAiAFKQMYNwMYIAIgBSkDIDcDICACIAUpAyg3AyggAiAFKQMwNwMwIAIgBSkDODcDOCACIAUpA0A3A0AgAiAGEBYgASACKQMANwMAIAEgAikDCDcDCCABIAIpAxA3AxAgASACKQMYNwMYIAEgAikDIDcDICABIAIpAyg3AyggASACKQMwNwMwIAEgAikDODcDOCABIAIpA0A3A0AgASwARARAIAFBwAkQFQsgECABKQMANwMAIBAgASkDCDcDCCAQIAEpAxA3AxAgECABKQMYNwMYIBAgASkDIDcDICAQIAEpAyg3AyggECABKQMwNwMwIBAgASkDODcDOCAQIAEpA0A3A0AgAiAMKQIANwIAIAIgDCkCCDcCCCACIAwpAhA3AhAgAiAMKQIYNwIYIAIgDCkCIDcCICACIAwpAig3AiggAiAMKQIwNwIwIAIgDCkCODcCOCACIAwpAkA3AkAgAiASEBcgASACKQMANwMAIAEgAikDCDcDCCABIAIpAxA3AxAgASACKQMYNwMYIAEgAikDIDcDICABIAIpAyg3AyggASACKQMwNwMwIAEgAikDODcDOCABIAIpA0A3A0AgARAbIAogASkDADcDACAKIAEpAwg3AwggCiABKQMQNwMQIAogASkDGDcDGCAKIAEpAyA3AyAgCiABKQMoNwMoIAogASkDMDcDMCAKIAEpAzg3AzggCiABKQNANwNAIAIgCikDADcDACACIAopAwg3AwggAiAKKQMQNwMQIAIgCikDGDcDGCACIAopAyA3AyAgAiAKKQMoNwMoIAIgCikDMDcDMCACIAopAzg3AzggAiAKKQNANwNAIAIgChAVIAEgAikDADcDACABIAIpAwg3AwggASACKQMQNwMQIAEgAikDGDcDGCABIAIpAyA3AyAgASACKQMoNwMoIAEgAikDMDcDMCABIAIpAzg3AzggASACKQNANwNAAkACQCABKAIAIgRBCEYEQEEIIQgCQAJAAkADQCAIQX9qIQQgCEUNBSAEQRBPDQEgAUEEaiAEQQJ0aigCACIIIARBAnRBxAlqKAIAIgJHDQIgBCEIDAALAAtBvAsgBEEQECgMAQsgCCACTw0CCwUgBEEITw0BCwwBCyABQcAJEBYLIAogASkDADcDACAKIAEpAwg3AwggCiABKQMQNwMQIAogASkDGDcDGCAKIAEpAyA3AyAgCiABKQMoNwMoIAogASkDMDcDMCAKIAEpAzg3AzggCiABKQNANwNAIAwLIQQgACAPKQIANwIAIAAgDykCCDcCCCAAIA8pAhA3AhAgACAPKQIYNwIYIAAgDykCIDcCICAAIA8pAig3AiggACAPKQIwNwIwIAAgDykCODcCOCAAIA8pAkA3AkAgBCAQKQIANwIAIAQgECkCCDcCCCAEIBApAhA3AhAgBCAQKQIYNwIYIAQgECkCIDcCICAEIBApAig3AiggBCAQKQIwNwIwIAQgECkCODcCOCAEIBApAkA3AkAgEiAKKQIANwIAIBIgCikCCDcCCCASIAopAhA3AhAgEiAKKQIYNwIYIBIgCikCIDcCICASIAopAig3AiggEiAKKQIwNwIwIBIgCikCODcCOCASIAopAkA3AkAgESQCCwukBAEQfwJ/IwIhAyMCQZABaiQCIAFBIEcEQCADJAJBAA8LIAAoAhwQOyEBIAAoAhgiAhA7IQQgACgCFCIFEDshByAAKAIQIggQOyEJIAAoAgwiChA7IQsgACgCCCIMEDshDSAAKAIEIg4QOyEPIAAoAgAiEBA7IREgA0HIAGoiAEEAOgBEIABBCDYCACAAIAE2AgQgACAENgIIIAAgBzYCDCAAIAk2AhAgACALNgIUIAAgDTYCGCAAIA82AhwgACARNgIgIABBJGoiAUIANwIAIAFCADcCCCABQgA3AhAgAUIANwIYIAAgEAR/QQgFIA4Ef0EHBSAMBH9BBgUgCgR/QQUFIAgEf0EEBSAFBH9BAwUgAgR/QQIFQQELCwsLCwsLIgE2AgAgAyICIAApAwA3AwAgAiAAKQMINwMIIAIgACkDEDcDECACIAApAxg3AxggAiAAKQMgNwMgIAIgACkDKDcDKCACIAApAzA3AzAgAiAAKQM4NwM4IAIgACkDQDcDQAJAAkACQCACKAIAIgRBCEYEQEEIIQECQAJAAkADQCABQX9qIQAgAUUNBiAAQRBPDQEgAkEEaiAAQQJ0aigCACIBIABBAnRB7ApqKAIAIgVHDQIgACEBDAALAAtBvAsgAEEQECgMAQsgASAFSQ0CDAMLBSAEQQhJDQEMAgsMAgsgBEEBRyACKAIEQQBHckUNAEEBIQYMAQtBACEGCyADJAIgBgsLvQYBD38CQCMCIQgjAkHgAWokAiADQSBJBEBBICADECcLIAghBSACKAIcEDshCSACKAIYEDshCiACKAIUEDshCyACKAIQEDshDCACKAIMEDshDSACKAIIEDshDiACKAIEEDshDyACKAIAEDshBiAIQcgAaiIEQQA6AEQgBCADQQJ2IgI2AgAgBCAJNgIEIAQgCjYCCCAEIAs2AgwgBCAMNgIQIAQgDTYCFCAEIA42AhggBCAPNgIcIAQgBjYCICAEQSRqIgNCADcCACADQgA3AgggA0IANwIQIANCADcCGAJAAkADQAJAIAJBf2oiA0EQTw0CIARBBGogA0ECdGooAgAEQCACIQcMAQsgA0EBSwRAIAMhAgwCBSADIQcLCwsMAQtBsAsgA0EQECgLIAQgBzYCACAFIAQpAwA3AwAgBSAEKQMINwMIIAUgBCkDEDcDECAFIAQpAxg3AxggBSAEKQMgNwMgIAUgBCkDKDcDKCAFIAQpAzA3AzAgBSAEKQM4NwM4IAUgBCkDQDcDQAJAAkAgBSgCACIGQQhGBEBBCCECAkACQAJAA0AgAkF/aiEDIAJFDQYgA0EQTw0BIAVBBGogA0ECdGooAgAiByADQQJ0QewKaigCACICRw0CIAMhAgwACwALQbwLIANBEBAoDAELIAcgAkkNAgsFIAZBCEkNAQsMAQsgBkEBRgRAIAUoAgRFDQELIAQgASAFECAgBCgCBBA7IRAgBCgCCBA7IREgBCgCDBA7IRIgBCgCEBA7IQkgBCgCFBA7IQogBCgCGBA7IQsgBCgCHBA7IQwgBCgCIBA7IQ0gBCgCTBA7IQ4gBCgCUBA7IQ8gBCgCVBA7IQYgBCgCWBA7IQUgBCgCXBA7IQcgBCgCYBA7IQMgBCgCZBA7IQIgBCgCaBA7IQEgAEEBOgAAIABBBDoAASAAIA02AAIgACAMNgAGIAAgCzYACiAAIAo2AA4gACAJNgASIAAgEjYAFiAAIBE2ABogACAQNgAeIAAgATYAIiAAIAI2ACYgACADNgAqIAAgBzYALiAAIAU2ADIgACAGNgA2IAAgDzYAOiAAIA42AD4gCCQCDwsgAEEAOgAAIAgkAgsLdgECfwJAIwIhAiMCQTBqJAIgAkEoaiIDIAA2AgAgAkEsaiIAIAE2AgAgAkEYaiIBIAM2AgAgAUEBNgIEIAEgADYCCCABQQE2AgwgAkGsDzYCACACQQI2AgQgAkEANgIIIAIgATYCECACQQI2AhQgAkG8DxAsCwt1AQJ/AkAjAiEDIwJBMGokAiADQShqIgQgATYCACADQSxqIgEgAjYCACADQRhqIgIgATYCACACQQE2AgQgAiAENgIIIAJBATYCDCADQZwPNgIAIANBAjYCBCADQQA2AgggAyACNgIQIANBAjYCFCADIAAQLAsLDwAgAQR/IAAgAXAFQQALCw8AIAEEfyAAIAFuBUEACwvNAgEHfwJ/IwIhBSMCQTBqJAIgBSEEIAAoAgAiAEGPzgBLBEBBJyEGA0AgAEGQzgAQKSECIABBkM4AECohAyACQeQAECpBAXQhByACQeQAEClBAXQhCCAEIAZBfGoiAmogB0GQF2ouAAA7AAAgBCAGQX5qaiAIQZAXai4AADsAACAAQf/B1y9LBEAgAiEGIAMhAAwBBSADIQALCwVBJyECCyAAQeMASgRAIABB5AAQKUEBdCEDIABB5AAQKiEAIAQgAkF+aiICaiADQZAXai4AADsAAAsgAEEKSAR/IAQgAkF/aiIDaiAAQf8BcUEwajoAACABQQFBrL8BQQAgBCADIgBqQScgAGsQLSEAIAUkAiAABSAEIAJBfmoiA2ogAEEBdEGQF2ouAAA7AAAgAUEBQay/AUEAIAQgAyIAakEnIABrEC0hACAFJAIgAAsLC3QBBH8CQCMCIQIjAkEwaiQCIAEoAgAhAyABKAIEIQQgASgCCCEFIAIiASAAKQIANwIAIAEgACkCCDcCCCABIAApAhA3AhAgAkEYaiIAIAEpAgA3AgAgACABKQIINwIIIAAgASkCEDcCECAAIAMgBCAFEA4LC/cRAQ1/An8jAiEMIwJBEGokAiAAKAIAIQgCQAJAIAEEQCAIQQFxBEBBKyEGDAIFQQAhDkEAIQYgBSEHCwVBLSEGDAELDAELQQEhDiAFQQFqIQcLIAhBBHEEfyACIANqIQsgAwRAIAIhAUEAIQkDQCABLAAAQcABcUGAAUYgCWohCSABQQFqIgEgC0cNAAsFQQAhCQsgByADaiAJayEHQQEFQQALIREgDEEEaiEBIAAoAghFBEAgDkEBRgRAIAAoAhghDSAAKAIcIQogAUEANgIAIAZBgAFJBEAgASAGOgAAQQEhBwUgBkGAEEkEf0FAIQhBASELQQIhByABBSAGQYCABEkEfyABQWA6AABBgH8hCEECIQtBAyEHIAFBAWoFIAFBcDoAACABQYB/OgABQYB/IQhBAyELQQQhByABQQJqCwsiCSAIOgAAIAEgC2ogBkE/cUGAf3I6AAALIA0gASAHIAooAgxBA3FBAmoRAABB/wFxBEAgDCQCQQEPCwsgAEEYaiEBIBEEQCABKAIAIAIgAyAAQRxqIgAoAgAoAgxBA3FBAmoRAABB/wFxBEAgDCQCQQEPCwUgAEEcaiEACyABKAIAIAQgBSAAKAIAKAIMQQNxQQJqEQAAIQAgDCQCIAAPCyAAKAIMIgogB00EQCAOQQFGBEAgACgCGCENIAAoAhwhCiABQQA2AgAgBkGAAUkEQCABIAY6AABBASEHBSAGQYAQSQR/QUAhCEEBIQtBAiEHIAEFIAZBgIAESQR/IAFBYDoAAEGAfyEIQQIhC0EDIQcgAUEBagUgAUFwOgAAIAFBgH86AAFBgH8hCEEDIQtBBCEHIAFBAmoLCyIJIAg6AAAgASALaiAGQT9xQYB/cjoAAAsgDSABIAcgCigCDEEDcUECahEAAEH/AXEEQCAMJAJBAQ8LCyAAQRhqIQEgEQRAIAEoAgAgAiADIABBHGoiACgCACgCDEEDcUECahEAAEH/AXEEQCAMJAJBAQ8LBSAAQRxqIQALIAEoAgAgBCAFIAAoAgAoAgxBA3FBAmoRAAAhACAMJAIgAA8LIAwhCSAIQQhxRQRAIAogB2shBwJAAkACQAJAIAAsADAiCEEDRgR/QQEFIAgLQQNxDgMAAgECCyAHIQhBACEHDAILIAdBAWpBAXYhCCAHQQF2IQcMAQtBACEICyAJQQA2AgAgACgCBCIKQYABSQR/IAkgCjoAAEEBBSAKQYAQSQR/IApBBnZBH3FBQHIhD0EBIRBBAiENIAkFIApBgIAESQR/IAkgCkEMdkEPcUFgcjoAACAKQQZ2QT9xQYB/ciEPQQIhEEEDIQ0gCUEBagUgCSAKQRJ2Qf8BcUFwcjoAACAJIApBDHZBP3FBgH9yOgABIApBBnZBP3FBgH9yIQ9BAyEQQQQhDSAJQQJqCwsiCyAPOgAAIAkgEGogCkE/cUGAf3I6AAAgDQshCyAAQRhqIQ0gAEEcaiEKQQAhAAJAAkADQCAAIAdJBEAgAEEBaiEAIA0oAgAgCSALIAooAgAoAgxBA3FBAmoRAABB/wFxDQIMAQsLDAELIAwkAkEBDwsCQAJAIA5BAUcNACANKAIAIRAgCigCACESIAFBADYCACAGQYABSQRAIAEgBjoAAEEBIQcFIAZBgBBJBH9BQCEOQQEhD0ECIQcgAQUgBkGAgARJBH8gAUFgOgAAQYB/IQ5BAiEPQQMhByABQQFqBSABQXA6AAAgAUGAfzoAAUGAfyEOQQMhD0EEIQcgAUECagsLIgAgDjoAACABIA9qIAZBP3FBgH9yOgAACyAQIAEgByASKAIMQQNxQQJqEQAAQf8BcUUNAAwBCyARBEAgDSgCACACIAMgCigCACgCDEEDcUECahEAAEH/AXENAQsgDSgCACAEIAUgCigCACgCDEEDcUECahEAAEH/AXFFBEBBACEAAkACQANAIAAgCE8NASAAQQFqIQAgDSgCACAJIAsgCigCACgCDEEDcUECahEAAEH/AXFFDQAMAgsACyAMJAJBAA8LIAwkAkEBDwsLIAwkAkEBDwsgAEEEaiIPQTA2AgAgAEEwaiIQQQE6AAAgDkEBRgRAIAAoAhghDiAAKAIcIRIgAUEANgIAIAZBgAFJBEAgASAGOgAAQQEhCAUgBkGAEEkEf0FAIQtBASENQQIhCCABBSAGQYCABEkEfyABQWA6AABBgH8hC0ECIQ1BAyEIIAFBAWoFIAFBcDoAACABQYB/OgABQYB/IQtBAyENQQQhCCABQQJqCwsiCSALOgAAIAEgDWogBkE/cUGAf3I6AAALIA4gASAIIBIoAgxBA3FBAmoRAABB/wFxBEAgDCQCQQEPCwsgEQRAIAAoAhggAiADIAAoAhwoAgxBA3FBAmoRAABB/wFxBEAgDCQCQQEPCwsgCiAHayECAkACQAJAAkAgECwAACIDQQNGBH9BAQUgAwtBA3EOAwACAQILIAIhA0EAIQIMAgsgAkEBakEBdiEDIAJBAXYhAgwBC0EAIQMLIAFBADYCACAPKAIAIgZBgAFJBH8gASAGOgAAQQEFIAZBgBBJBH8gBkEGdkEfcUFAciEIQQEhC0ECIQcgAQUgBkGAgARJBH8gASAGQQx2QQ9xQWByOgAAIAZBBnZBP3FBgH9yIQhBAiELQQMhByABQQFqBSABIAZBEnZB/wFxQXByOgAAIAEgBkEMdkE/cUGAf3I6AAEgBkEGdkE/cUGAf3IhCEEDIQtBBCEHIAFBAmoLCyIJIAg6AAAgASALaiAGQT9xQYB/cjoAACAHCyEJIABBGGohByAAQRxqIQZBACEAAkACQANAIAAgAkkEQCAAQQFqIQAgBygCACABIAkgBigCACgCDEEDcUECahEAAEH/AXENAgwBCwsMAQsgDCQCQQEPCyAHKAIAIAQgBSAGKAIAKAIMQQNxQQJqEQAAQf8BcQRAIAwkAkEBDwVBACEACwJAAkADQCAAIANPDQEgAEEBaiEAIAcoAgAgASAJIAYoAgAoAgxBA3FBAmoRAABB/wFxRQ0ADAILAAsgDCQCQQAPCyAMJAJBAQsLiwEBBn8CQCMCIQEjAkEwaiQCIAAoAgQhAyAAKAIIIQQgACgCDCEFIAAoAhAhBiABQRhqIgIgACgCADYCACACIAM2AgQgASIAIAI2AgAgAEEBNgIEIABBADYCCCAAQfyxATYCECAAQQA2AhQgAUEgaiIBIAQ2AgAgASAFNgIEIAEgBjYCCCAAIAEQLAsLdgECfwJAIwIhAiMCQTBqJAIgAkEoaiIDIAA2AgAgAkEsaiIAIAE2AgAgAkEYaiIBIAM2AgAgAUEBNgIEIAEgADYCCCABQQE2AgwgAkHIDzYCACACQQI2AgQgAkEANgIIIAIgATYCECACQQI2AhQgAkHYDxAsCwswAQJ/An8jAiEBIwJBEGokAiABIgIgACgCPBA4NgIAQQYgAhABEDMhACABJAIgAAsLawEDfwJ/IwIhBCMCQSBqJAIgBCEDIARBEGohBSAAQQM2AiQgACgCAEHAAHFFBEAgAyAAKAI8NgIAIANBk6gBNgIEIAMgBTYCCEE2IAMQBARAIABBfzoASwsLIAAgASACEDchACAEJAIgAAsLZQECfwJ/IwIhBCMCQSBqJAIgBCIDIAAoAjw2AgAgA0EANgIEIAMgATYCCCADIARBFGoiADYCDCADIAI2AhBBjAEgAxACEDNBAEgEfyAAQX82AgBBfwUgACgCAAshACAEJAIgAAsLGgAgAEGAYEsEfxA0QQAgAGs2AgBBfwUgAAsLCAAQNUHAAGoLBAAQNgsFAEHgEAv9AgELfwJ/IwIhBSMCQTBqJAIgBUEQaiEGIAVBIGoiAyAAQRxqIgkoAgAiBDYCACADIABBFGoiCigCACAEayIENgIEIAMgATYCCCADIAI2AgwgBSIBIABBPGoiDCgCADYCACABIAM2AgQgAUECNgIIAkACQCAEIAJqIgRBkgEgARAGEDMiAUYNAEECIQcDQCABQQBOBEAgBCABayEEIANBCGohCCABIAMoAgQiDUsiCwRAIAghAwsgC0EfdEEfdSAHaiEHIAMgAygCACABIAsEfyANBUEAC2siAWo2AgAgA0EEaiIIIAgoAgAgAWs2AgAgBiAMKAIANgIAIAYgAzYCBCAGIAc2AgggBEGSASAGEAYQMyIBRg0CDAELCyAAQQA2AhAgCUEANgIAIApBADYCACAAIAAoAgBBIHI2AgAgB0ECRgR/QQAFIAIgAygCBGsLIQIMAQsgACAAKAIsIgEgACgCMGo2AhAgCSABNgIAIAogATYCAAsgBSQCIAILCwQAIAALUAECfwJ/IAIEfwNAIAAsAAAiAyABLAAAIgRGBEAgAEEBaiEAIAFBAWohAUEAIAJBf2oiAkUNAxoMAQsLIANB/wFxIARB/wFxawVBAAsLIgALnQIBBH8CfyAAIAJqIQQgAUH/AXEhASACQcMATgRAA0AgAEEDcQRAIAAgAToAACAAQQFqIQAMAQsLIARBfHEiBUHAAGshBiABIAFBCHRyIAFBEHRyIAFBGHRyIQMDQCAAIAZMBEAgACADNgIAIAAgAzYCBCAAIAM2AgggACADNgIMIAAgAzYCECAAIAM2AhQgACADNgIYIAAgAzYCHCAAIAM2AiAgACADNgIkIAAgAzYCKCAAIAM2AiwgACADNgIwIAAgAzYCNCAAIAM2AjggACADNgI8IABBwABqIQAMAQsLA0AgACAFSARAIAAgAzYCACAAQQRqIQAMAQsLCwNAIAAgBEgEQCAAIAE6AAAgAEEBaiEADAELCyAEIAJrCwsrACAAQf8BcUEYdCAAQQh1Qf8BcUEQdHIgAEEQdUH/AXFBCHRyIABBGHZyC8kDAQN/An8gAkGAwABOBEAgACABIAIQAw8LIAAhBCAAIAJqIQMgAEEDcSABQQNxRgRAA0AgAEEDcQRAIAJFBEAgBA8LIAAgASwAADoAACAAQQFqIQAgAUEBaiEBIAJBAWshAgwBCwsgA0F8cSICQcAAayEFA0AgACAFTARAIAAgASgCADYCACAAIAEoAgQ2AgQgACABKAIINgIIIAAgASgCDDYCDCAAIAEoAhA2AhAgACABKAIUNgIUIAAgASgCGDYCGCAAIAEoAhw2AhwgACABKAIgNgIgIAAgASgCJDYCJCAAIAEoAig2AiggACABKAIsNgIsIAAgASgCMDYCMCAAIAEoAjQ2AjQgACABKAI4NgI4IAAgASgCPDYCPCAAQcAAaiEAIAFBwABqIQEMAQsLA0AgACACSARAIAAgASgCADYCACAAQQRqIQAgAUEEaiEBDAELCwUgA0EEayECA0AgACACSARAIAAgASwAADoAACAAIAEsAAE6AAEgACABLAACOgACIAAgASwAAzoAAyAAQQRqIQAgAUEEaiEBDAELCwsDQCAAIANIBEAgACABLAAAOgAAIABBAWohACABQQFqIQEMAQsLIAQLCwsAAn9BABAAQQALCwsAAn9BARAAQQALCwsAAn9BAhAAQQALCwu9DA0AQYAIC+QBAQAAAAAAAACCgAAAAAAAAIqAAAAAAACAAIAAgAAAAICLgAAAAAAAAAEAAIAAAAAAgYAAgAAAAIAJgAAAAAAAgIoAAAAAAAAAiAAAAAAAAAAJgACAAAAAAAoAAIAAAAAAi4AAgAAAAACLAAAAAAAAgImAAAAAAACAA4AAAAAAAIACgAAAAAAAgIAAAAAAAACACoAAAAAAAAAKAACAAAAAgIGAAIAAAACAgIAAAAAAAIABAACAAAAAAAiAAIAAAACACAAAAC/8///+////////////////////////////////////AEGICgsFAQAAAAEAQdAKCzyxCQAAIgAAAPYCAABYCQAAWQAAADwBAAAIAAAAQUE20Ixe0r87oEiv5tyuuv7///////////////////8AQbALC6kBMwoAACIAAADwAgAA0wkAAGAAAAA9AAAA0wkAAGAAAACBAAAA0wkAAGAAAACLAAAA0wkAAGAAAACTAAAA0wkAAGAAAACbAAAA0wkAAGAAAADvAAAA0wkAAGAAAAD3AAAA0wkAAGAAAAC6AAAA0wkAAGAAAABeAQAA0wkAAGAAAAAxAQAA0wkAAGAAAAAyAQAA0wkAAGAAAAA4AQAA0wkAAGAAAAA9AQAAAQBBoA0LadMJAABgAAAAbgMAAFUKAABcAAAAEAAAADMKAAAiAAAA9gIAANMJAABgAAAAHgQAABQLAAAoAAAAsQoAAGMAAABCAAAAsQoAAGMAAAA9AAAAsQoAAGMAAABQAAAAsQoAAGMAAABSAAAAAQBB0A4LAQEAQZgPC00BAAAAXgsAACAAAAB+CwAAEgAAAFgMAAAGAAAAXgwAACIAAAA8CwAAIgAAAKACAACADAAAFgAAAJYMAAANAAAAPAsAACIAAACmAgAABQBB8A8LAQEAQYgQCw4BAAAAAgAAALRfAAAABABBoBALAQEAQa8QCwUK/////wBBnBILAiRZAEHUEgvPBuQHAAAvaG9tZS9tYWNpZWovLmNhcmdvL3JlZ2lzdHJ5L3NyYy9naXRodWIuY29tLTFlY2M2Mjk5ZGI5ZWM4MjMvdGlueS1rZWNjYWstMS4yLjEvc3JjL2xpYi5ycy9jaGVja291dC9zcmMvbGliY29yZS9zbGljZS9tb2QucnMvaG9tZS9tYWNpZWovLmNhcmdvL3JlZ2lzdHJ5L3NyYy9naXRodWIuY29tLTFlY2M2Mjk5ZGI5ZWM4MjMvdGlueS1zZWNwMjU2azEtMC4xLjAvc3JjL2JpZ19udW0ucnMvY2hlY2tvdXQvc3JjL2xpYmNvcmUvc2xpY2UvbW9kLnJzL2hvbWUvbWFjaWVqLy5jYXJnby9yZWdpc3RyeS9zcmMvZ2l0aHViLmNvbS0xZWNjNjI5OWRiOWVjODIzL3Rpbnktc2VjcDI1NmsxLTAuMS4wL3NyYy9uYWYucnMvaG9tZS9tYWNpZWovLmNhcmdvL3JlZ2lzdHJ5L3NyYy9naXRodWIuY29tLTFlY2M2Mjk5ZGI5ZWM4MjMvdGlueS1zZWNwMjU2azEtMC4xLjAvc3JjL2VjX3BvaW50X2cucnNpbnRlcm5hbCBlcnJvcjogZW50ZXJlZCB1bnJlYWNoYWJsZSBjb2RlL2NoZWNrb3V0L3NyYy9saWJjb3JlL3NsaWNlL21vZC5yc2luZGV4IG91dCBvZiBib3VuZHM6IHRoZSBsZW4gaXMgIGJ1dCB0aGUgaW5kZXggaXMgMDAwMTAyMDMwNDA1MDYwNzA4MDkxMDExMTIxMzE0MTUxNjE3MTgxOTIwMjEyMjIzMjQyNTI2MjcyODI5MzAzMTMyMzMzNDM1MzYzNzM4Mzk0MDQxNDI0MzQ0NDU0NjQ3NDg0OTUwNTE1MjUzNTQ1NTU2NTc1ODU5NjA2MTYyNjM2NDY1NjY2NzY4Njk3MDcxNzI3Mzc0NzU3Njc3Nzg3OTgwODE4MjgzODQ4NTg2ODc4ODg5OTA5MTkyOTM5NDk1OTY5Nzk4OTlpbmRleCAgb3V0IG9mIHJhbmdlIGZvciBzbGljZSBvZiBsZW5ndGggc2xpY2UgaW5kZXggc3RhcnRzIGF0ICBidXQgZW5kcyBhdCA=', 'base64'); diff --git a/js/src/api/local/ethkey/index.js b/js/src/api/local/ethkey/index.js new file mode 100644 index 0000000000000000000000000000000000000000..07ca34ba11ed1128d5c9e8d7ad6e9732eb95e780 --- /dev/null +++ b/js/src/api/local/ethkey/index.js @@ -0,0 +1,47 @@ +// 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 workerPool from './workerPool'; + +export function createKeyObject (key, password) { + return workerPool.action('createKeyObject', { key, password }) + .then((obj) => JSON.parse(obj)); +} + +export function decryptPrivateKey (keyObject, password) { + return workerPool + .action('decryptPrivateKey', { keyObject, password }) + .then((privateKey) => { + if (privateKey) { + return Buffer.from(privateKey); + } + + return null; + }); +} + +export function phraseToAddress (phrase) { + return phraseToWallet(phrase) + .then((wallet) => wallet.address); +} + +export function phraseToWallet (phrase) { + return workerPool.action('phraseToWallet', phrase); +} + +export function verifySecret (secret) { + return workerPool.action('verifySecret', secret); +} diff --git a/js/src/api/local/ethkey/index.spec.js b/js/src/api/local/ethkey/index.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..ce4cc29df9ba1ed70481740a70e9b1a5e1cc525e --- /dev/null +++ b/js/src/api/local/ethkey/index.spec.js @@ -0,0 +1,58 @@ +// 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 { randomPhrase } from '@parity/wordlist'; +import { phraseToAddress, phraseToWallet } from './'; + +// TODO: Skipping until Node.js 8.0 comes out and we can test WebAssembly +describe.skip('api/local/ethkey', () => { + describe('phraseToAddress', function () { + this.timeout(30000); + + it('generates a valid address', () => { + const phrase = randomPhrase(12); + + return phraseToAddress(phrase).then((address) => { + expect(address.length).to.be.equal(42); + expect(address.slice(0, 4)).to.be.equal('0x00'); + }); + }); + + it('generates valid address for empty phrase', () => { + return phraseToAddress('').then((address) => { + expect(address).to.be.equal('0x00a329c0648769a73afac7f9381e08fb43dbea72'); + }); + }); + }); + + describe('phraseToWallet', function () { + this.timeout(30000); + + it('generates a valid wallet object', () => { + const phrase = randomPhrase(12); + + return phraseToWallet(phrase).then((wallet) => { + expect(wallet.address.length).to.be.equal(42); + expect(wallet.secret.length).to.be.equal(66); + expect(wallet.public.length).to.be.equal(130); + + expect(wallet.address.slice(0, 4)).to.be.equal('0x00'); + expect(wallet.secret.slice(0, 2)).to.be.equal('0x'); + expect(wallet.public.slice(0, 2)).to.be.equal('0x'); + }); + }); + }); +}); diff --git a/js/src/api/local/ethkey/worker.js b/js/src/api/local/ethkey/worker.js new file mode 100644 index 0000000000000000000000000000000000000000..76ccf06b2271a6645d7bb365b065ed37116e7aa0 --- /dev/null +++ b/js/src/api/local/ethkey/worker.js @@ -0,0 +1,138 @@ +// 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 { bytesToHex } from '~/api/util/format'; +import { extern, slice } from './ethkey.js'; + +const isWorker = typeof self !== 'undefined'; + +// Stay compatible between environments +if (!isWorker) { + const scope = typeof global === 'undefined' ? window : global; + + scope.self = scope; +} + +// keythereum should never be used outside of the browser +let keythereum = require('keythereum'); + +if (isWorker) { + keythereum = self.keythereum; +} + +function route ({ action, payload }) { + if (action in actions) { + return actions[action](payload); + } + + return null; +} + +const input = slice(extern._input_ptr(), 1024); +const secret = slice(extern._secret_ptr(), 32); +const publicKey = slice(extern._public_ptr(), 64); +const address = slice(extern._address_ptr(), 20); + +extern._ecpointg(); + +const actions = { + phraseToWallet (phrase) { + const phraseUtf8 = Buffer.from(phrase, 'utf8'); + + if (phraseUtf8.length > input.length) { + throw new Error('Phrase is too long!'); + } + + input.set(phraseUtf8); + + extern._brain(phraseUtf8.length); + + const wallet = { + secret: bytesToHex(secret), + public: bytesToHex(publicKey), + address: bytesToHex(address) + }; + + return wallet; + }, + + verifySecret (key) { + const keyBuf = Buffer.from(key.slice(2), 'hex'); + + secret.set(keyBuf); + + return extern._verify_secret(); + }, + + createKeyObject ({ key, password }) { + key = Buffer.from(key); + password = Buffer.from(password); + + const iv = keythereum.crypto.randomBytes(16); + const salt = keythereum.crypto.randomBytes(32); + const keyObject = keythereum.dump(password, key, salt, iv); + + return JSON.stringify(keyObject); + }, + + decryptPrivateKey ({ keyObject, password }) { + password = Buffer.from(password); + + try { + const key = keythereum.recover(password, keyObject); + + // Convert to array to safely send from the worker + return Array.from(key); + } catch (e) { + return null; + } + } +}; + +self.onmessage = function ({ data }) { + try { + const result = route(data); + + postMessage([null, result]); + } catch (err) { + console.error(err); + postMessage([err.toString(), null]); + } +}; + +// Emulate a web worker in Node.js +class KeyWorker { + postMessage (data) { + // Force async + setTimeout(() => { + try { + const result = route(data); + + this.onmessage({ data: [null, result] }); + } catch (err) { + this.onmessage({ data: [err, null] }); + } + }, 0); + } + + onmessage (event) { + // no-op to be overriden + } +} + +if (exports != null) { + exports.KeyWorker = KeyWorker; +} diff --git a/js/src/api/local/ethkey/workerPool.js b/js/src/api/local/ethkey/workerPool.js new file mode 100644 index 0000000000000000000000000000000000000000..d7db73b419be0ac35fd9cddf67ff5bf24c37802b --- /dev/null +++ b/js/src/api/local/ethkey/workerPool.js @@ -0,0 +1,105 @@ +// 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 . + +// Allow a web worker in the browser, with a fallback for Node.js +const hasWebWorkers = typeof Worker !== 'undefined'; +const KeyWorker = hasWebWorkers ? require('worker-loader!./worker') + : require('./worker').KeyWorker; + +class WorkerContainer { + busy = false; + _worker = new KeyWorker(); + + action (action, payload) { + if (this.busy) { + throw new Error('Cannot issue an action on a busy worker!'); + } + + this.busy = true; + + return new Promise((resolve, reject) => { + this._worker.postMessage({ action, payload }); + this._worker.onmessage = ({ data }) => { + const [err, result] = data; + + this.busy = false; + + if (err) { + // `err` ought to be a String + reject(new Error(err)); + } else { + resolve(result); + } + }; + }); + } +} + +class WorkerPool { + pool = [ + new WorkerContainer(), + new WorkerContainer() + ]; + + queue = []; + + _getContainer () { + return this.pool.find((container) => !container.busy); + } + + action (action, payload) { + let container = this.pool.find((container) => !container.busy); + + let promise; + + // const start = Date.now(); + + if (container) { + promise = container.action(action, payload); + } else { + promise = new Promise((resolve, reject) => { + this.queue.push([action, payload, resolve]); + }); + } + + return promise + .catch((err) => { + this.processQueue(); + + throw err; + }) + .then((result) => { + this.processQueue(); + + // console.log('Work done in ', Date.now() - start); + + return result; + }); + } + + processQueue () { + let container = this._getContainer(); + + while (container && this.queue.length > 0) { + const [action, payload, resolve] = this.queue.shift(); + + resolve(container.action(action, payload)); + container = this._getContainer(); + } + } +} + +export default new WorkerPool(); diff --git a/js/src/api/local/index.js b/js/src/api/local/index.js new file mode 100644 index 0000000000000000000000000000000000000000..190a38ea4f612115624cd8533a842c4cecfbe24b --- /dev/null +++ b/js/src/api/local/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 null; diff --git a/js/src/api/local/localAccountsMiddleware.js b/js/src/api/local/localAccountsMiddleware.js new file mode 100644 index 0000000000000000000000000000000000000000..c452f541afa5fcb332ccb1b465b4ec86908b83f5 --- /dev/null +++ b/js/src/api/local/localAccountsMiddleware.js @@ -0,0 +1,288 @@ +// 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 EthereumTx from 'ethereumjs-tx'; +import accounts from './accounts'; +import transactions from './transactions'; +import { Middleware } from '../transport'; +import { inNumber16 } from '../format/input'; +import { phraseToWallet, phraseToAddress, verifySecret } from './ethkey'; +import { randomPhrase } from '@parity/wordlist'; + +export default class LocalAccountsMiddleware extends Middleware { + constructor (transport) { + super(transport); + + const register = this.register.bind(this); + + register('eth_accounts', () => { + return accounts.accountAddresses(); + }); + + register('eth_coinbase', () => { + return accounts.lastAddress; + }); + + register('parity_accountsInfo', () => { + return accounts.map(({ name }) => { + return { name }; + }); + }); + + register('parity_allAccountsInfo', () => { + return accounts.map(({ name, meta, uuid }) => { + return { name, meta, uuid }; + }); + }); + + register('parity_changePassword', ([address, oldPassword, newPassword]) => { + const account = accounts.get(address); + + return account + .decryptPrivateKey(oldPassword) + .then((privateKey) => { + if (!privateKey) { + return false; + } + + account.changePassword(privateKey, newPassword); + + return true; + }); + }); + + register('parity_checkRequest', ([id]) => { + return transactions.hash(id) || Promise.resolve(null); + }); + + register('parity_dappsList', () => { + return []; + }); + + register('parity_defaultAccount', () => { + return accounts.dappsDefaultAddress; + }); + + register('parity_exportAccount', ([address, password]) => { + const account = accounts.get(address); + + if (!password) { + password = ''; + } + + return account.isValidPassword(password) + .then((isValid) => { + if (!isValid) { + throw new Error('Invalid password'); + } + + return account.export(); + }); + }); + + register('parity_generateSecretPhrase', () => { + return randomPhrase(12); + }); + + register('parity_getNewDappsAddresses', () => { + return accounts.accountAddresses(); + }); + + register('parity_getNewDappsDefaultAddress', () => { + return accounts.dappsDefaultAddress; + }); + + register('parity_hardwareAccountsInfo', () => { + return {}; + }); + + register('parity_newAccountFromPhrase', ([phrase, password]) => { + return phraseToWallet(phrase) + .then((wallet) => { + return accounts.create(wallet.secret, password); + }); + }); + + register('parity_newAccountFromSecret', ([secret, password]) => { + return verifySecret(secret) + .then((isValid) => { + if (!isValid) { + throw new Error('Invalid secret key'); + } + + return accounts.create(secret, password); + }); + }); + + register('parity_newAccountFromWallet', ([json, password]) => { + if (!password) { + password = ''; + } + + return accounts.restoreFromWallet(JSON.parse(json), password); + }); + + register('parity_setAccountMeta', ([address, meta]) => { + accounts.getLazyCreate(address).meta = meta; + + return true; + }); + + register('parity_setAccountName', ([address, name]) => { + accounts.getLazyCreate(address).name = name; + + return true; + }); + + register('parity_setNewDappsDefaultAddress', ([address]) => { + accounts.dappsDefaultAddress = address; + + return true; + }); + + register('parity_postTransaction', ([tx]) => { + if (!tx.from) { + tx.from = accounts.lastAddress; + } + + tx.nonce = null; + tx.condition = null; + + return transactions.add(tx); + }); + + register('parity_phraseToAddress', ([phrase]) => { + return phraseToAddress(phrase); + }); + + register('parity_useLocalAccounts', () => { + return true; + }); + + register('parity_listGethAccounts', () => { + return []; + }); + + register('parity_listOpenedVaults', () => { + return []; + }); + + register('parity_listRecentDapps', () => { + return {}; + }); + + register('parity_listVaults', () => { + return []; + }); + + register('parity_wsUrl', () => { + // This is a hack, will be replaced by a `hostname` setting on the node itself + return `${window.location.hostname}:8546`; + }); + + register('parity_dappsUrl', () => { + // This is a hack, will be replaced by a `hostname` setting on the node itself + return `${window.location.hostname}:8545`; + }); + + register('parity_hashContent', () => { + throw new Error('Functionality unavailable on a public wallet.'); + }); + + register('parity_killAccount', ([address, password]) => { + return accounts.remove(address, password); + }); + + register('parity_removeAddress', ([address]) => { + return accounts.remove(address, null); + }); + + register('parity_testPassword', ([address, password]) => { + const account = accounts.get(address); + + return account.isValidPassword(password); + }); + + register('parity_upgradeReady', () => { + return false; + }); + + register('signer_confirmRequest', ([id, modify, password]) => { + const { + gasPrice, + gas: gasLimit, + from, + to, + value, + data + } = Object.assign(transactions.get(id), modify); + + transactions.lock(id); + + const account = accounts.get(from); + + return Promise.all([ + this.rpcRequest('parity_nextNonce', [from]), + account.decryptPrivateKey(password) + ]) + .catch((err) => { + transactions.unlock(id); + + // transaction got unlocked, can propagate rejection further + throw err; + }) + .then(([nonce, privateKey]) => { + if (!privateKey) { + transactions.unlock(id); + + throw new Error('Invalid password'); + } + + const tx = new EthereumTx({ + nonce, + to, + data, + gasLimit: inNumber16(gasLimit), + gasPrice: inNumber16(gasPrice), + value: inNumber16(value) + }); + + tx.sign(privateKey); + + const serializedTx = `0x${tx.serialize().toString('hex')}`; + + return this.rpcRequest('eth_sendRawTransaction', [serializedTx]); + }) + .then((hash) => { + transactions.confirm(id, hash); + + return {}; + }); + }); + + register('signer_generateAuthorizationToken', () => { + return ''; + }); + + register('signer_rejectRequest', ([id]) => { + return transactions.reject(id); + }); + + register('signer_requestsToConfirm', () => { + return transactions.requestsToConfirm(); + }); + } +} diff --git a/js/src/api/local/localAccountsMiddleware.spec.js b/js/src/api/local/localAccountsMiddleware.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..b3de3992d1d064137d844333d33b30d800422899 --- /dev/null +++ b/js/src/api/local/localAccountsMiddleware.spec.js @@ -0,0 +1,157 @@ +// 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 LocalAccountsMiddleware from './localAccountsMiddleware'; +import JsonRpcBase from '../transport/jsonRpcBase'; + +const RPC_RESPONSE = Symbol('RPC response'); +const ADDRESS = '0x00a329c0648769a73afac7f9381e08fb43dbea72'; +const SECRET = '0x4d5db4107d237df6a3d58ee5f70ae63d73d7658d4026f2eefd2f204c81682cb7'; +const PASSWORD = 'password'; + +const FOO_PHRASE = 'foobar'; +const FOO_PASSWORD = 'foopass'; +const FOO_ADDRESS = '0x007ef7ac1058e5955e366ab9d6b6c4ebcc937e7e'; + +class MockedTransport extends JsonRpcBase { + _execute (method, params) { + return RPC_RESPONSE; + } +} + +// Skip till all CI runs on Node 8+ +describe.skip('api/local/LocalAccountsMiddleware', function () { + this.timeout(30000); + + let transport; + + beforeEach(() => { + transport = new MockedTransport(); + transport.addMiddleware(LocalAccountsMiddleware); + + // Same as `parity_newAccountFromPhrase` with empty phrase + return transport + .execute('parity_newAccountFromSecret', SECRET, PASSWORD) + .catch((_err) => { + // Ignore the error - all instances of LocalAccountsMiddleware + // share account storage + }); + }); + + it('registers all necessary methods', () => { + return Promise + .all([ + 'eth_accounts', + 'eth_coinbase', + 'parity_accountsInfo', + 'parity_allAccountsInfo', + 'parity_changePassword', + 'parity_checkRequest', + 'parity_defaultAccount', + 'parity_generateSecretPhrase', + 'parity_getNewDappsAddresses', + 'parity_hardwareAccountsInfo', + 'parity_newAccountFromPhrase', + 'parity_newAccountFromSecret', + 'parity_setAccountMeta', + 'parity_setAccountName', + 'parity_postTransaction', + 'parity_phraseToAddress', + 'parity_useLocalAccounts', + 'parity_listGethAccounts', + 'parity_listOpenedVaults', + 'parity_listRecentDapps', + 'parity_listVaults', + 'parity_killAccount', + 'parity_testPassword', + 'signer_confirmRequest', + 'signer_rejectRequest', + 'signer_requestsToConfirm' + ].map((method) => { + return transport + .execute(method) + .then((result) => { + expect(result).not.to.be.equal(RPC_RESPONSE); + }) + // Some errors are expected here since we are calling methods + // without parameters. + .catch((_) => {}); + })); + }); + + it('allows non-registered methods through', () => { + return transport + .execute('eth_getBalance', '0x407d73d8a49eeb85d32cf465507dd71d507100c1') + .then((result) => { + expect(result).to.be.equal(RPC_RESPONSE); + }); + }); + + it('can handle `eth_accounts`', () => { + return transport + .execute('eth_accounts') + .then((accounts) => { + expect(accounts.length).to.be.equal(1); + expect(accounts[0]).to.be.equal(ADDRESS); + }); + }); + + it('can handle `parity_defaultAccount`', () => { + return transport + .execute('parity_defaultAccount') + .then((address) => { + expect(address).to.be.equal(ADDRESS); + }); + }); + + it('can handle `parity_phraseToAddress`', () => { + return transport + .execute('parity_phraseToAddress', '') + .then((address) => { + expect(address).to.be.equal(ADDRESS); + + return transport.execute('parity_phraseToAddress', FOO_PHRASE); + }) + .then((address) => { + expect(address).to.be.equal(FOO_ADDRESS); + }); + }); + + it('can create and kill an account', () => { + return transport + .execute('parity_newAccountFromPhrase', FOO_PHRASE, FOO_PASSWORD) + .then((address) => { + expect(address).to.be.equal(FOO_ADDRESS); + + return transport.execute('eth_accounts'); + }) + .then((accounts) => { + expect(accounts.length).to.be.equal(2); + expect(accounts.includes(FOO_ADDRESS)).to.be.true; + + return transport.execute('parity_killAccount', FOO_ADDRESS, FOO_PASSWORD); + }) + .then((result) => { + expect(result).to.be.true; + + return transport.execute('eth_accounts'); + }) + .then((accounts) => { + expect(accounts.length).to.be.equal(1); + expect(accounts.includes(FOO_ADDRESS)).to.be.false; + }); + }); +}); diff --git a/js/src/api/local/transactions.js b/js/src/api/local/transactions.js new file mode 100644 index 0000000000000000000000000000000000000000..421e730123d9eadb6ecd3d0140a1bc53d1d4e986 --- /dev/null +++ b/js/src/api/local/transactions.js @@ -0,0 +1,147 @@ +// 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 { toHex } from '../util/format'; +import { TransportError } from '../transport'; + +const AWAITING = Symbol('awaiting'); +const LOCKED = Symbol('locked'); +const CONFIRMED = Symbol('confirmed'); +const REJECTED = Symbol('rejected'); + +class Transactions { + constructor () { + this.reset(); + } + + // should only really be needed in the constructor and tests + reset () { + this._id = 1; + this._states = {}; + } + + nextId () { + return toHex(this._id++); + } + + add (tx) { + const id = this.nextId(); + + this._states[id] = { + status: AWAITING, + transaction: tx + }; + + return id; + } + + get (id) { + const state = this._states[id]; + + if (!state || state.status !== AWAITING) { + return null; + } + + return state.transaction; + } + + lock (id) { + const state = this._states[id]; + + if (!state || state.status !== AWAITING) { + throw new Error('Trying to lock an invalid transaction'); + } + + state.status = LOCKED; + } + + unlock (id) { + const state = this._states[id]; + + if (!state || state.status !== LOCKED) { + throw new Error('Trying to unlock an invalid transaction'); + } + + state.status = AWAITING; + } + + hash (id) { + const state = this._states[id]; + + if (!state) { + return null; + } + + switch (state.status) { + case REJECTED: + throw TransportError.requestRejected(); + case CONFIRMED: + return state.hash; + default: + return null; + } + } + + confirm (id, hash) { + const state = this._states[id]; + const status = state ? state.status : null; + + switch (status) { + case AWAITING: break; + case LOCKED: break; + default: throw new Error('Trying to confirm an invalid transaction'); + } + + state.hash = hash; + state.status = CONFIRMED; + } + + reject (id) { + const state = this._states[id]; + + if (!state) { + return false; + } + + state.status = REJECTED; + + return true; + } + + requestsToConfirm () { + const result = []; + + Object.keys(this._states).forEach((id) => { + const state = this._states[id]; + + if (state.status === AWAITING) { + result.push({ + id, + origin: { + signer: '0x0' + }, + payload: { + sendTransaction: state.transaction + } + }); + } + }); + + return result; + } +} + +export default new Transactions(); diff --git a/js/src/api/local/transactions.spec.js b/js/src/api/local/transactions.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..65f2d8ddc3951d66d35bc83c38037e14002de647 --- /dev/null +++ b/js/src/api/local/transactions.spec.js @@ -0,0 +1,85 @@ +// 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 transactions from './transactions'; +import { TransportError } from '../transport/error'; + +const DUMMY_TX = 'dummy'; + +describe('api/local/transactions', () => { + beforeEach(() => { + transactions.reset(); + }); + + it('can store transactions', () => { + const id1 = transactions.add(DUMMY_TX); + const id2 = transactions.add(DUMMY_TX); + const requests = transactions.requestsToConfirm(); + + expect(id1).to.be.equal('0x1'); + expect(id2).to.be.equal('0x2'); + expect(requests.length).to.be.equal(2); + expect(requests[0].id).to.be.equal(id1); + expect(requests[1].id).to.be.equal(id2); + expect(requests[0].payload.sendTransaction).to.be.equal(DUMMY_TX); + expect(requests[1].payload.sendTransaction).to.be.equal(DUMMY_TX); + }); + + it('can confirm transactions', () => { + const id1 = transactions.add(DUMMY_TX); + const id2 = transactions.add(DUMMY_TX); + + const hash1 = '0x1111111111111111111111111111111111111111'; + const hash2 = '0x2222222222222222222222222222222222222222'; + + transactions.confirm(id1, hash1); + transactions.confirm(id2, hash2); + + const requests = transactions.requestsToConfirm(); + + expect(requests.length).to.be.equal(0); + expect(transactions.hash(id1)).to.be.equal(hash1); + expect(transactions.hash(id2)).to.be.equal(hash2); + }); + + it('can reject transactions', () => { + const id = transactions.add(DUMMY_TX); + + transactions.reject(id); + + const requests = transactions.requestsToConfirm(); + + expect(requests.length).to.be.equal(0); + expect(() => transactions.hash(id)).to.throw(TransportError); + }); + + it('can lock and confirm transactions', () => { + const id = transactions.add(DUMMY_TX); + const hash = '0x1111111111111111111111111111111111111111'; + + transactions.lock(id); + + const requests = transactions.requestsToConfirm(); + + expect(requests.length).to.be.equal(0); + expect(transactions.get(id)).to.be.null; + expect(transactions.hash(id)).to.be.null; + + transactions.confirm(id, hash); + + expect(transactions.hash(id)).to.be.equal(hash); + }); +}); diff --git a/js/src/api/pubsub/eth/eth.js b/js/src/api/pubsub/eth/eth.js new file mode 100644 index 0000000000000000000000000000000000000000..044473ec6313f0b25f09e6801c19bf98886b5359 --- /dev/null +++ b/js/src/api/pubsub/eth/eth.js @@ -0,0 +1,227 @@ +// 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 PubsubBase from '../pubsubBase'; + +import { inAddress, inBlockNumber, inHex, inNumber16, inOptions, inFilter } from '../../format/input'; +import { outAddress, outBlock, outNumber, outTransaction, outSyncing, outReceipt, outLog } from '../../format/output'; + +export default class Eth extends PubsubBase { + constructor (transport) { + super(transport); + this._api = 'parity'; + } + + newHeads (callback) { + return this.addListener('eth', 'newHeads', callback, null); + } + + logs (callback) { + throw Error('not supported yet'); + } + + // eth API + protocolVersion (callback) { + return this.addListener(this._api, 'eth_protocolVersion', callback); + } + + syncing (callback) { + return this.addListener(this._api, 'eth_syncing', (error, data) => { + error + ? callback(error) + : callback(null, outSyncing(data)); + }); + } + + hashrate (callback) { + return this.addListener(this._api, 'eth_hashrate', (error, data) => { + error + ? callback(error) + : callback(null, outNumber(data)); + }); + } + + coinbase (callback) { + return this.addListener(this._api, 'eth_coinbase', (error, data) => { + error + ? callback(error) + : callback(null, outAddress(data)); + }); + } + + mining (callback) { + return this.addListener(this._api, 'eth_mining', callback); + } + + gasPrice (callback) { + return this.addListener(this._api, 'eth_gasPrice', (error, data) => { + error + ? callback(error) + : callback(null, outNumber(data)); + }); + } + + accounts (callback) { + return this.addListener(this._api, 'eth_accounts', (error, accounts) => { + error + ? callback(error) + : callback(null, (accounts || []).map(outAddress)); + }); + } + + blockNumber (callback) { + return this.addListener(this._api, 'eth_blockNumber', (error, data) => { + error + ? callback(error) + : callback(null, outNumber(data)); + }); + } + + getBalance (callback, address, blockNumber = 'latest') { + return this.addListener(this._api, 'eth_getBalance', (error, data) => { + error + ? callback(error) + : callback(null, outNumber(data)); + }, [inAddress(address), inBlockNumber(blockNumber)]); + } + + getStorageAt (callback, address, index = 0, blockNumber = 'latest') { + return this.addListener(this._api, 'eth_getStorageAt', callback, [inAddress(address), inNumber16(index), inBlockNumber(blockNumber)]); + } + + getBlockByHash (callback, hash, full = false) { + return this.addListener(this._api, 'eth_getBlockByHash', (error, data) => { + error + ? callback(error) + : callback(null, outBlock(data)); + }, [inHex(hash), full]); + } + + getBlockByNumber (callback, blockNumber = 'latest', full = false) { + return this.addListener(this._api, 'eth_getBlockByNumber', (error, data) => { + error + ? callback(error) + : callback(null, outBlock(data)); + }, [inBlockNumber(blockNumber), full]); + } + + getTransactionCount (callback, address, blockNumber = 'latest') { + return this.addListener(this._api, 'eth_getTransactionCount', (error, data) => { + error + ? callback(error) + : callback(null, outNumber(data)); + }, [inAddress(address), inBlockNumber(blockNumber)]); + } + + getBlockTransactionCountByHash (callback, hash) { + return this.addListener(this._api, 'eth_getBlockTransactionCountByHash', (error, data) => { + error + ? callback(error) + : callback(null, outNumber(data)); + }, [inHex(hash)]); + } + + getBlockTransactionCountByNumber (callback, blockNumber = 'latest') { + return this.addListener(this._api, 'eth_getBlockTransactionCountByNumber', (error, data) => { + error + ? callback(error) + : callback(null, outNumber(data)); + }, [inBlockNumber(blockNumber)]); + } + + getUncleCountByBlockHash (callback, hash) { + return this.addListener(this._api, 'eth_getUncleCountByBlockHash', (error, data) => { + error + ? callback(error) + : callback(null, outNumber(data)); + }, [inHex(hash)]); + } + + getUncleCountByBlockNumber (callback, blockNumber = 'latest') { + return this.addListener(this._api, 'eth_getUncleCountByBlockNumber', (error, data) => { + error + ? callback(error) + : callback(null, outNumber(data)); + }, [inBlockNumber(blockNumber)]); + } + + getCode (callback, address, blockNumber = 'latest') { + return this.addListener(this._api, 'eth_getCode', callback, [inAddress(address), inBlockNumber(blockNumber)]); + } + + call (callback, options, blockNumber = 'latest') { + return this.addListener(this._api, 'eth_call', callback, [inOptions(options), inBlockNumber(blockNumber)]); + } + + estimateGas (callback, options) { + return this.addListener(this._api, 'eth_estimateGas', (error, data) => { + error + ? callback(error) + : callback(null, outNumber(data)); + }, [inOptions(options)]); + } + + getTransactionByHash (callback, hash) { + return this.addListener(this._api, 'eth_getTransactionByHash', (error, data) => { + error + ? callback(error) + : callback(null, outTransaction(data)); + }, [inHex(hash)]); + } + + getTransactionByBlockHashAndIndex (callback, hash, index = 0) { + return this.addListener(this._api, 'eth_getTransactionByBlockHashAndIndex', (error, data) => { + error + ? callback(error) + : callback(null, outTransaction(data)); + }, [inHex(hash), inNumber16(index)]); + } + + getTransactionByBlockNumberAndIndex (callback, blockNumber = 'latest', index = 0) { + return this.addListener(this._api, 'eth_getTransactionByBlockNumberAndIndex', (error, data) => { + error + ? callback(error) + : callback(null, outTransaction(data)); + }, [inBlockNumber(blockNumber), inNumber16(index)]); + } + + getTransactionReceipt (callback, txhash) { + return this.addListener(this._api, 'eth_getTransactionReceipt', (error, data) => { + error + ? callback(error) + : callback(null, outReceipt(data)); + }, [inHex(txhash)]); + } + + getUncleByBlockHashAndIndex (callback, hash, index = 0) { + return this.addListener(this._api, 'eth_getUncleByBlockHashAndIndex', callback, [inHex(hash), inNumber16(index)]); + } + + getUncleByBlockNumberAndIndex (callback, blockNumber = 'latest', index = 0) { + return this.addListener(this._api, 'eth_getUncleByBlockNumberAndIndex', callback, [inBlockNumber(blockNumber), inNumber16(index)]); + } + + getLogs (callback, options) { + return this.addListener(this._api, 'eth_getLogs', (error, logs) => { + error + ? callback(error) + : callback(null, (logs) => logs.map(outLog)); + }, [inFilter(options)]); + } + + getWork (callback) { + return this.addListener(this._api, 'eth_getWork', callback); + } +} diff --git a/js/src/api/pubsub/eth/index.js b/js/src/api/pubsub/eth/index.js new file mode 100644 index 0000000000000000000000000000000000000000..3b8d1994c3401a1acd24fbe0a336682141bca548 --- /dev/null +++ b/js/src/api/pubsub/eth/index.js @@ -0,0 +1,16 @@ +// 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 './eth'; diff --git a/js/src/api/pubsub/index.js b/js/src/api/pubsub/index.js new file mode 100644 index 0000000000000000000000000000000000000000..bf342e1de95317686b7b6458499e96cbcbd81e0e --- /dev/null +++ b/js/src/api/pubsub/index.js @@ -0,0 +1,16 @@ +// 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 './pubsub'; diff --git a/js/src/api/pubsub/net/index.js b/js/src/api/pubsub/net/index.js new file mode 100644 index 0000000000000000000000000000000000000000..6d13847c5352357752b98cb9e9c0a62c24c679d5 --- /dev/null +++ b/js/src/api/pubsub/net/index.js @@ -0,0 +1,16 @@ +// 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 './net'; diff --git a/js/src/api/pubsub/net/net.js b/js/src/api/pubsub/net/net.js new file mode 100644 index 0000000000000000000000000000000000000000..e1dc3c4e16e8b5f0a83340c5e2db7e7a295836eb --- /dev/null +++ b/js/src/api/pubsub/net/net.js @@ -0,0 +1,42 @@ +// 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 PubsubBase from '../pubsubBase'; + +import { outNumber } from '../../format/output'; + +export default class Net extends PubsubBase { + constructor (transport) { + super(transport); + this._api = 'parity'; + } + + // net API + version (callback) { + return this.addListener(this._api, 'net_version', callback); + } + + peerCount (callback) { + return this.addListener(this._api, 'net_peerCount', (error, data) => { + error + ? callback(error) + : callback(null, outNumber(data)); + }); + } + + listening (callback) { + return this.addListener(this._api, 'net_listening', callback); + } +} diff --git a/js/src/api/pubsub/parity/index.js b/js/src/api/pubsub/parity/index.js new file mode 100644 index 0000000000000000000000000000000000000000..33ce7aa7fa412dec16c81ee75a722a841eaeb4ba --- /dev/null +++ b/js/src/api/pubsub/parity/index.js @@ -0,0 +1,16 @@ +// 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 './parity'; diff --git a/js/src/api/pubsub/parity/parity.js b/js/src/api/pubsub/parity/parity.js new file mode 100644 index 0000000000000000000000000000000000000000..042d8dc8b489ae8f503e68a6b01e2f703a915fb1 --- /dev/null +++ b/js/src/api/pubsub/parity/parity.js @@ -0,0 +1,364 @@ +// 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 PubsubBase from '../pubsubBase'; +import { inAddress, inBlockNumber, inData, inHex, inDeriveHash, inDeriveIndex } from '../../format/input'; +import { outAccountInfo, outAddress, outBlock, outChainStatus, outHistogram, outHwAccountInfo, outNodeKind, outNumber, outPeers, outTransaction, outAddresses, outRecentDapps, outVaultMeta } from '../../format/output'; + +export default class Parity extends PubsubBase { + constructor (transport) { + super(transport); + this._api = 'parity'; + } + + // parity API + accountsInfo (callback) { + return this.addListener(this._api, 'parity_accountsInfo', (error, data) => { + error + ? callback(error) + : callback(null, outAccountInfo(data)); + }); + } + + hardwareAccountsInfo (callback) { + return this.addListener(this._api, 'parity_hardwareAccountsInfo', (error, data) => { + error + ? callback(error) + : callback(null, outHwAccountInfo(data)); + }); + } + + defaultAccount (callback) { + return this.addListener(this._api, 'parity_defaultAccount', (error, data) => { + error + ? callback(error) + : callback(null, outAddress(data)); + }); + } + + transactionsLimit (callback) { + return this.addListener(this._api, 'parity_transactionsLimit', (error, data) => { + error + ? callback(error) + : callback(null, outNumber(data)); + }); + } + + extraData (callback) { + return this.addListener(this._api, 'parity_extraData', callback); + } + + gasFloorTarget (callback) { + return this.addListener(this._api, 'parity_gasFloorTarget', (error, data) => { + error + ? callback(error) + : callback(null, outNumber(data)); + }); + } + + gasCeilTarget (callback) { + return this.addListener(this._api, 'parity_gasCeilTarget', (error, data) => { + error + ? callback(error) + : callback(null, outNumber(data)); + }); + } + + minGasPrice (callback) { + return this.addListener(this._api, 'parity_minGasPrice', (error, data) => { + error + ? callback(error) + : callback(null, outNumber(data)); + }); + } + + devLogs (callback) { + return this.addListener(this._api, 'parity_devLogs', callback); + } + + devLogsLevels (callback) { + return this.addListener(this._api, 'parity_devLogsLevels', callback); + } + + netChain (callback) { + return this.addListener(this._api, 'parity_netChain', callback); + } + + netPeers (callback) { + return this.addListener(this._api, 'parity_netPeers', (error, data) => { + error + ? callback(error) + : callback(null, outPeers(data)); + }); + } + + netPort (callback) { + return this.addListener(this._api, 'parity_netPort', (error, data) => { + error + ? callback(error) + : callback(null, outNumber(data)); + }); + } + + rpcSettings (callback) { + return this.addListener(this._api, 'parity_rpcSettings', callback); + } + + nodeName (callback) { + return this.addListener(this._api, 'parity_nodeName', callback); + } + + defaultExtraData (callback) { + return this.addListener(this._api, 'parity_defaultExtraData', callback); + } + + gasPriceHistogram (callback) { + return this.addListener(this._api, 'parity_gasPriceHistogram', (error, data) => { + error + ? callback(error) + : callback(null, outHistogram(data)); + }); + } + + unsignedTransactionsCount (callback) { + return this.addListener(this._api, 'parity_unsignedTransactionsCount', (error, data) => { + error + ? callback(error) + : callback(null, outNumber(data)); + }); + } + + registryAddress (callback) { + return this.addListener(this._api, 'parity_registryAddress', (error, data) => { + error + ? callback(error) + : callback(null, outAddress(data)); + }); + } + + listAccounts (callback, count, offset = null, blockNumber = 'latest') { + return this.addListener(this._api, 'parity_listAccounts', (error, data) => { + error + ? callback(error) + : callback(null, (data) => (data || []).map(outAddress)); + }, [count, inAddress(offset), inBlockNumber(blockNumber)]); + } + + listStorageKeys (callback, address, count, hash = null, blockNumber = 'latest') { + return this.addListener(this._api, 'parity_listStorageKeys', callback, [inAddress(address), count, inHex(hash), inBlockNumber(blockNumber)]); + } + + pendingTransactions (callback) { + return this.addListener(this._api, 'parity_pendingTransactions', (error, data) => { + error + ? callback(error) + : callback(null, outTransaction(data)); + }); + } + + futureTransactions (callback) { + return this.addListener(this._api, 'parity_futureTransactions', (error, data) => { + error + ? callback(error) + : callback(null, outTransaction(data)); + }); + } + + pendingTransactionsStats (callback) { + return this.addListener(this._api, 'parity_pendingTransactionsStats', callback); + } + + localTransactions (callback) { + return this.addListener(this._api, 'parity_localTransactions', (error, transactions) => { + if (error) { + return callback(error); + } + + Object.values(transactions) + .filter(tx => tx.transaction) + .map(tx => { + tx.transaction = outTransaction(tx.transaction); + }); + + callback(null, transactions); + }); + } + + dappsUrl (callback) { + return this.addListener(this._api, 'parity_dappsUrl', callback); + } + + wsUrl (callback) { + return this.addListener(this._api, 'parity_wsUrl', callback); + } + + nextNonce (callback, account) { + return this.addListener(this._api, 'parity_nextNonce', (error, data) => { + error + ? callback(error) + : callback(null, outNumber(data)); + }, [inAddress(account)]); + } + + mode (callback) { + return this.addListener(this._api, 'parity_mode', callback); + } + + chain (callback) { + return this.addListener(this._api, 'parity_chain', callback); + } + + enode (callback) { + return this.addListener(this._api, 'parity_enode', callback); + } + + consensusCapability (callback) { + return this.addListener(this._api, 'parity_consensusCapability', callback); + } + + versionInfo (callback) { + return this.addListener(this._api, 'parity_versionInfo', callback); + } + + releasesInfo (callback) { + return this.addListener(this._api, 'parity_releasesInfo', callback); + } + + chainStatus (callback) { + return this.addListener(this._api, 'parity_chainStatus', (error, data) => { + error + ? callback(error) + : callback(null, outChainStatus(data)); + }); + } + + nodeKind (callback) { + return this.addListener(this._api, 'parity_nodeKind', (error, data) => { + error + ? callback(error) + : callback(null, outNodeKind(data)); + }); + } + + getBlockHeaderByNumber (callback, blockNumber = 'latest') { + return this.addListener(this._api, 'parity_getBlockHeaderByNumber', (error, data) => { + error + ? callback(error) + : callback(null, outBlock(data)); + }, [inBlockNumber(blockNumber)]); + } + + cidV0 (callback, data) { + return this.addListener(this._api, 'parity_cidV0', callback, [inData(data)]); + } + + // parity accounts API (only secure API or configured to be exposed) + allAccountsInfo (callback) { + return this.addListener(this._api, 'parity_allAccountsInfo', (error, data) => { + error + ? callback(error) + : callback(null, outAccountInfo(data)); + }); + } + + getDappAddresses (callback, dappId) { + return this.addListener(this._api, 'parity_getDappAddresses', (error, data) => { + error + ? callback(error) + : callback(null, outAddresses(data)); + }, [dappId]); + } + + getDappDefaultAddress (callback, dappId) { + return this.addListener(this._api, 'parity_getDappDefaultAddress', (error, data) => { + error + ? callback(error) + : callback(null, outAddress(data)); + }, [dappId]); + } + + getNewDappsAddresses (callback) { + return this.addListener(this._api, 'parity_getDappDefaultAddress', (error, addresses) => { + error + ? callback(error) + : callback(null, addresses ? addresses.map(outAddress) : null); + }); + } + + getNewDappsDefaultAddress (callback) { + return this.addListener(this._api, 'parity_getNewDappsDefaultAddress', (error, data) => { + error + ? callback(error) + : callback(null, outAddress(data)); + }); + } + + listRecentDapps (callback) { + return this.addListener(this._api, 'parity_listRecentDapps', (error, data) => { + error + ? callback(error) + : callback(null, outRecentDapps(data)); + }); + } + + listGethAccounts (callback) { + return this.addListener(this._api, 'parity_listGethAccounts', (error, data) => { + error + ? callback(error) + : callback(null, outAddresses(data)); + }); + } + + listVaults (callback) { + return this.addListener(this._api, 'parity_listVaults', callback); + } + + listOpenedVaults (callback) { + return this.addListener(this._api, 'parity_listOpenedVaults', callback); + } + + getVaultMeta (callback, vaultName) { + return this.addListener(this._api, 'parity_getVaultMeta', (error, data) => { + error + ? callback(error) + : callback(null, outVaultMeta(data)); + }, [vaultName]); + } + + deriveAddressHash (callback, address, password, hash, shouldSave) { + return this.addListener(this._api, 'parity_deriveAddressHash', (error, data) => { + error + ? callback(error) + : callback(null, outAddress(data)); + }, [inAddress(address), password, inDeriveHash(hash), !!shouldSave]); + } + + deriveAddressIndex (callback, address, password, index, shouldSave) { + return this.addListener(this._api, 'parity_deriveAddressIndex', (error, data) => { + error + ? callback(error) + : callback(null, outAddress(data)); + }, [inAddress(address), password, inDeriveIndex(index), !!shouldSave]); + } + + nodeHealth (callback) { + return this.addListener(this._api, 'parity_nodeHealth', (error, data) => { + error + ? callback(error) + : callback(null, data); + }); + } +} diff --git a/js/src/api/pubsub/pubsub.js b/js/src/api/pubsub/pubsub.js new file mode 100644 index 0000000000000000000000000000000000000000..4420967ee7590fb3ec333007e1761b39b9313620 --- /dev/null +++ b/js/src/api/pubsub/pubsub.js @@ -0,0 +1,79 @@ +// 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 Eth from './eth'; +import Parity from './parity'; +import Signer from './signer'; +import Net from './net'; + +import { isFunction } from '../util/types'; + +export default class Pubsub { + constructor (transport) { + if (!transport || !isFunction(transport.subscribe)) { + throw new Error('Pubsub API needs transport with subscribe() function defined. (WebSocket)'); + } + + this._eth = new Eth(transport); + this._net = new Net(transport); + this._parity = new Parity(transport); + this._signer = new Signer(transport); + } + + get net () { + return this._net; + } + + get eth () { + return this._eth; + } + + get parity () { + return this._parity; + } + + get signer () { + return this._signer; + } + + unsubscribe (subscriptionIds) { + // subscriptions are namespace independent. Thus we can simply removeListener from any. + return this._parity.removeListener(subscriptionIds); + } + + subscribeAndGetResult (f, callback) { + return new Promise((resolve, reject) => { + let isFirst = true; + let onSubscription = (error, data) => { + const p1 = error ? Promise.reject(error) : Promise.resolve(data); + const p2 = p1.then(callback); + + if (isFirst) { + isFirst = false; + p2 + .then(resolve) + .catch(reject); + } + }; + + try { + f.call(this, onSubscription).catch(reject); + } catch (err) { + reject(err); + } + }); + } +} diff --git a/js/src/api/pubsub/pubsub.spec.js b/js/src/api/pubsub/pubsub.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..10fcd2edb7a58c91ad5d3fd732755fda8c3dcd63 --- /dev/null +++ b/js/src/api/pubsub/pubsub.spec.js @@ -0,0 +1,613 @@ +// 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 BigNumber from 'bignumber.js'; +import { TEST_WS_URL, mockWs } from '../../../test/mockRpc'; +import { isBigNumber } from '../../../test/types'; + +import Ws from '../transport/ws'; +import Pubsub from './pubsub'; + +describe('api/pubsub/Pubsub', () => { + let scope; + let instance; + const address = '0x63Cf90D3f0410092FC0fca41846f596223979195'; + + describe('accountsInfo', () => { + beforeEach(() => { + scope = mockWs([{ method: 'parity_subscribe', reply: 2, subscription: { + method: 'parity_subscription', + params: { + result: { + '0x63cf90d3f0410092fc0fca41846f596223979195': { + name: 'name', uuid: 'uuid', meta: '{"data":"data"}' + } + }, + subscription: 2 + } + } }]); + instance = new Pubsub(new Ws(TEST_WS_URL)); + }); + + afterEach(() => { + scope.stop(); + }); + + it('retrieves the available account info', (done) => { + instance.parity.accountsInfo((error, result) => { + expect(error).to.be.null; + expect(result).to.deep.equal({ + '0x63Cf90D3f0410092FC0fca41846f596223979195': { + name: 'name', uuid: 'uuid', meta: { + data: 'data' + } + } + }); + done(); + }); + }); + }); + + describe('Unsubscribe', () => { + beforeEach(() => { + scope = mockWs([{ method: 'parity_subscribe', reply: 2 }, + { method: 'parity_unsubscribe', reply: true }]); + instance = new Pubsub(new Ws(TEST_WS_URL)); + }); + + afterEach(() => { + scope.stop(); + }); + + it('Promise gets resolved on success.', (done) => { + instance.parity.accountsInfo().then(s => { + instance.parity.unsubscribe(s).then(b => { + expect(b).to.be.true; + }); + }); + done(); + }); + }); + + describe('chainStatus', () => { + beforeEach(() => { + scope = mockWs([{ method: 'parity_subscribe', reply: 2, subscription: { + method: 'parity_subscription', + params: { + result: { + 'blockGap': [0x123, 0x456] + }, + subscription: 2 + } + } }]); + instance = new Pubsub(new Ws(TEST_WS_URL)); + }); + + afterEach(() => { + scope.stop(); + }); + + it('retrieves the chain status', (done) => { + instance.parity.chainStatus((error, result) => { + expect(error).to.be.null; + expect(result).to.deep.equal({ + 'blockGap': [new BigNumber(0x123), new BigNumber(0x456)] + }); + done(); + }); + }); + }); + + describe('gasFloorTarget', () => { + beforeEach(() => { + scope = mockWs([{ method: 'parity_subscribe', reply: 2, subscription: { + method: 'parity_subscription', + params: { + result: '0x123456', + subscription: 2 + } + } }]); + instance = new Pubsub(new Ws(TEST_WS_URL)); + }); + + afterEach(() => { + scope.stop(); + }); + + it('returns the gasfloor, formatted', (done) => { + instance.parity.gasFloorTarget((error, result) => { + expect(error).to.be.null; + expect(isBigNumber(result)).to.be.true; + expect(result.eq(0x123456)).to.be.true; + done(); + }); + }); + }); + + describe('transactionsLimit', () => { + beforeEach(() => { + scope = mockWs([{ method: 'parity_subscribe', reply: 2, subscription: { + method: 'parity_subscription', + params: { + result: 1024, + subscription: 2 + } + } }]); + instance = new Pubsub(new Ws(TEST_WS_URL)); + }); + + afterEach(() => { + scope.stop(); + }); + + it('returns the tx limit, formatted', (done) => { + instance.parity.transactionsLimit((error, result) => { + expect(error).to.be.null; + expect(isBigNumber(result)).to.be.true; + expect(result.eq(1024)).to.be.true; + done(); + }); + }); + }); + + describe('minGasPrice', () => { + beforeEach(() => { + scope = mockWs([{ method: 'parity_subscribe', reply: 2, subscription: { + method: 'parity_subscription', + params: { + result: '0x123456', + subscription: 2 + } + } }]); + instance = new Pubsub(new Ws(TEST_WS_URL)); + }); + + afterEach(() => { + scope.stop(); + }); + + it('returns the min gasprice, formatted', (done) => { + instance.parity.minGasPrice((error, result) => { + expect(error).to.be.null; + expect(isBigNumber(result)).to.be.true; + expect(result.eq(0x123456)).to.be.true; + done(); + }); + }); + }); + + describe('netPeers', () => { + beforeEach(() => { + scope = mockWs([{ method: 'parity_subscribe', reply: 2, subscription: { + method: 'parity_subscription', + params: { + result: { active: 123, connected: 456, max: 789, peers: [] }, + subscription: 2 + } + } }]); + instance = new Pubsub(new Ws(TEST_WS_URL)); + }); + + afterEach(() => { + scope.stop(); + }); + + it('returns the peer structure, formatted', (done) => { + instance.parity.netPeers((error, peers) => { + expect(error).to.be.null; + expect(peers.active.eq(123)).to.be.true; + expect(peers.connected.eq(456)).to.be.true; + expect(peers.max.eq(789)).to.be.true; + done(); + }); + }); + }); + + describe('netPort', () => { + beforeEach(() => { + scope = mockWs([{ method: 'parity_subscribe', reply: 2, subscription: { + method: 'parity_subscription', + params: { + result: 33030, + subscription: 2 + } + } }]); + instance = new Pubsub(new Ws(TEST_WS_URL)); + }); + + afterEach(() => { + scope.stop(); + }); + + it('returns the connected port, formatted', (done) => { + instance.parity.netPort((error, count) => { + expect(error).to.be.null; + expect(isBigNumber(count)).to.be.true; + expect(count.eq(33030)).to.be.true; + done(); + }); + }); + }); + +// Eth API + describe('accounts', () => { + beforeEach(() => { + scope = mockWs([{ method: 'parity_subscribe', reply: 2, subscription: { + method: 'parity_subscription', + params: { + result: [address.toLowerCase()], + subscription: 2 + } + } }]); + instance = new Pubsub(new Ws(TEST_WS_URL)); + }); + + afterEach(() => { + scope.stop(); + }); + + it('returns a list of accounts, formatted', (done) => { + instance.eth.accounts((error, accounts) => { + expect(error).to.be.null; + expect(accounts).to.deep.equal([address]); + done(); + }); + }); + }); + + describe('newHeads', () => { + beforeEach(() => { + scope = mockWs([{ method: 'eth_subscribe', reply: 2, subscription: { + method: 'eth_subscription', + params: { + result: '0x123456', + subscription: 2 + } + } }]); + instance = new Pubsub(new Ws(TEST_WS_URL)); + }); + + afterEach(() => { + scope.stop(); + }); + + it('returns newHeads for eth_subscribe', (done) => { + instance.eth.newHeads((error, blockNumber) => { + expect(error).to.be.null; + expect(blockNumber).to.equal('0x123456'); + done(); + }); + }); + }); + + describe('blockNumber', () => { + beforeEach(() => { + scope = mockWs([{ method: 'parity_subscribe', reply: 2, subscription: { + method: 'parity_subscription', + params: { + result: '0x123456', + subscription: 2 + } + } }]); + instance = new Pubsub(new Ws(TEST_WS_URL)); + }); + + afterEach(() => { + scope.stop(); + }); + + it('returns the current blockNumber, formatted', (done) => { + instance.eth.blockNumber((error, blockNumber) => { + expect(error).to.be.null; + expect(isBigNumber(blockNumber)).to.be.true; + expect(blockNumber.toString(16)).to.equal('123456'); + done(); + }); + }); + }); + + describe('call', () => { + beforeEach(() => { + scope = mockWs([{ method: 'parity_subscribe', reply: 2, subscription: { + method: 'parity_subscription', + params: { + result: [], + subscription: 2 + } + } }]); + instance = new Pubsub(new Ws(TEST_WS_URL)); + }); + + afterEach(() => { + scope.stop(); + }); + + it('formats the input options & blockNumber', (done) => { + instance.eth.call((error) => { + expect(error).to.be.null; + expect(scope.body.parity_subscribe.params).to.deep.equal(['eth_call', [{ data: '0x12345678' }, 'earliest']]); + done(); + }, { data: '12345678' }, 'earliest'); + }); + + it('provides a latest blockNumber when not specified', (done) => { + instance.eth.call((error) => { + expect(error).to.be.null; + expect(scope.body.parity_subscribe.params).to.deep.equal(['eth_call', [{ data: '0x12345678' }, 'latest']]); + done(); + }, { data: '12345678' }); + }); + }); + + describe('coinbase', () => { + beforeEach(() => { + scope = mockWs([{ method: 'parity_subscribe', reply: 2, subscription: { + method: 'parity_subscription', + params: { + result: address.toLowerCase(), + subscription: 2 + } + } }]); + instance = new Pubsub(new Ws(TEST_WS_URL)); + }); + + afterEach(() => { + scope.stop(); + }); + + it('returns the coinbase, formatted', (done) => { + instance.eth.coinbase((error, account) => { + expect(error).to.be.null; + expect(account).to.deep.equal(address); + done(); + }); + }); + }); + + describe('estimateGas', () => { + beforeEach(() => { + scope = mockWs([{ method: 'parity_subscribe', reply: 2, subscription: { + method: 'parity_subscription', + params: { + result: '0x123', + subscription: 2 + } + } }]); + instance = new Pubsub(new Ws(TEST_WS_URL)); + }); + + afterEach(() => { + scope.stop(); + }); + + it('converts the options correctly', (done) => { + instance.eth.estimateGas((error) => { + expect(error).to.be.null; + expect(scope.body.parity_subscribe.params).to.deep.equal(['eth_estimateGas', [{ gas: '0x5208' }]]); + done(); + }, { gas: 21000 }); + }); + + it('returns the gas used, formatted', (done) => { + instance.eth.estimateGas((error, gas) => { + expect(error).to.be.null; + expect(isBigNumber(gas)).to.be.true; + expect(gas.toString(16)).to.deep.equal('123'); + done(); + }); + }); + }); + + describe('gasPrice', () => { + beforeEach(() => { + scope = mockWs([{ method: 'parity_subscribe', reply: 2, subscription: { + method: 'parity_subscription', + params: { + result: '0x123', + subscription: 2 + } + } }]); + instance = new Pubsub(new Ws(TEST_WS_URL)); + }); + + afterEach(() => { + scope.stop(); + }); + + it('returns the gas price, formatted', (done) => { + instance.eth.gasPrice((error, price) => { + expect(error).to.be.null; + expect(isBigNumber(price)).to.be.true; + expect(price.toString(16)).to.deep.equal('123'); + done(); + }); + }); + }); + + describe('getBalance', () => { + beforeEach(() => { + scope = mockWs([{ method: 'parity_subscribe', reply: 2, subscription: { + method: 'parity_subscription', + params: { + result: '0x123', + subscription: 2 + } + } }]); + instance = new Pubsub(new Ws(TEST_WS_URL)); + }); + + afterEach(() => { + scope.stop(); + }); + + it('passes in the address (default blockNumber)', (done) => { + instance.eth.getBalance((error) => { + expect(error).to.be.null; + expect(scope.body.parity_subscribe.params).to.deep.equal(['eth_getBalance', [address.toLowerCase(), 'latest']]); + done(); + }, address); + }); + + it('passes in the address & blockNumber', (done) => { + instance.eth.getBalance((error) => { + expect(error).to.be.null; + expect(scope.body.parity_subscribe.params).to.deep.equal(['eth_getBalance', [address.toLowerCase(), '0x456']]); + done(); + }, address, 0x456); + }); + + it('returns the balance', (done) => { + instance.eth.getBalance((error, balance) => { + expect(error).to.be.null; + expect(isBigNumber(balance)).to.be.true; + expect(balance.toString(16)).to.deep.equal('123'); + done(); + }, address); + }); + }); + + describe('getBlockByHash', () => { + beforeEach(() => { + scope = mockWs([{ method: 'parity_subscribe', reply: 2, subscription: { + method: 'parity_subscription', + params: { + result: { miner: address.toLowerCase() }, + subscription: 2 + } + } }]); + instance = new Pubsub(new Ws(TEST_WS_URL)); + }); + + afterEach(() => { + scope.stop(); + }); + + it('formats the input hash as a hash, default full', (done) => { + instance.eth.getBlockByHash((error) => { + expect(error).to.be.null; + expect(scope.body.parity_subscribe.params).to.deep.equal(['eth_getBlockByHash', ['0x1234', false]]); + done(); + }, '1234'); + }); + + it('formats the input hash as a hash, full true', (done) => { + instance.eth.getBlockByHash((error) => { + expect(error).to.be.null; + expect(scope.body.parity_subscribe.params).to.deep.equal(['eth_getBlockByHash', ['0x1234', true]]); + done(); + }, '1234', true); + }); + + it('formats the output into block', (done) => { + instance.eth.getBlockByHash((error, block) => { + expect(error).to.be.null; + expect(block.miner).to.equal(address); + done(); + }, '1234'); + }); + }); + + describe('getBlockByNumber', () => { + beforeEach(() => { + scope = mockWs([{ method: 'parity_subscribe', reply: 2, subscription: { + method: 'parity_subscription', + params: { + result: { miner: address.toLowerCase() }, + subscription: 2 + } + } }]); + instance = new Pubsub(new Ws(TEST_WS_URL)); + }); + + afterEach(() => { + scope.stop(); + }); + + it('assumes blockNumber latest & full false', (done) => { + instance.eth.getBlockByNumber((error) => { + expect(error).to.be.null; + expect(scope.body.parity_subscribe.params).to.deep.equal(['eth_getBlockByNumber', ['latest', false]]); + done(); + }); + }); + + it('uses input blockNumber & full false', (done) => { + instance.eth.getBlockByNumber((error) => { + expect(error).to.be.null; + expect(scope.body.parity_subscribe.params).to.deep.equal(['eth_getBlockByNumber', ['0x1234', false]]); + done(); + }, '0x1234'); + }); + + it('formats the input blockNumber, full true', (done) => { + instance.eth.getBlockByNumber((error) => { + expect(error).to.be.null; + expect(scope.body.parity_subscribe.params).to.deep.equal(['eth_getBlockByNumber', ['0x1234', true]]); + done(); + }, 0x1234, true); + }); + + it('formats the output into block', (done) => { + instance.eth.getBlockByNumber((error, block) => { + expect(error).to.be.null; + expect(block.miner).to.equal(address); + done(); + }, 0x1234); + }); + }); + + describe('getTransactionCount', () => { + beforeEach(() => { + scope = mockWs([{ method: 'parity_subscribe', reply: 2, subscription: { + method: 'parity_subscription', + params: { + result: '0x123', + subscription: 2 + } + } }]); + instance = new Pubsub(new Ws(TEST_WS_URL)); + }); + + afterEach(() => { + scope.stop(); + }); + + it('passes in the address (default blockNumber)', (done) => { + instance.eth.getTransactionCount((error) => { + expect(error).to.be.null; + expect(scope.body.parity_subscribe.params).to.deep.equal(['eth_getTransactionCount', [address.toLowerCase(), 'latest']]); + done(); + }, address); + }); + + it('passes in the address & blockNumber', (done) => { + instance.eth.getTransactionCount((error) => { + expect(error).to.be.null; + expect(scope.body.parity_subscribe.params).to.deep.equal(['eth_getTransactionCount', [address.toLowerCase(), '0x456']]); + done(); + }, address, 0x456); + }); + + it('returns the count, formatted', (done) => { + instance.eth.getTransactionCount((error, count) => { + expect(error).to.be.null; + expect(isBigNumber(count)).to.be.true; + expect(count.toString(16)).to.equal('123'); + done(); + }, address, 0x456); + }); + }); +}); diff --git a/js/src/api/pubsub/pubsubBase.js b/js/src/api/pubsub/pubsubBase.js new file mode 100644 index 0000000000000000000000000000000000000000..c50f45775a43b87e6518f5d106387b39894306c9 --- /dev/null +++ b/js/src/api/pubsub/pubsubBase.js @@ -0,0 +1,38 @@ +// 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 class PubsubBase { + // Provider for websocket pubsub transport + constructor (transport) { + this._transport = transport; + } + + addListener (module, eventName, callback, eventParams = []) { + if (eventName) { + return this._transport.subscribe(module, callback, eventParams ? [eventName, eventParams] : [eventName]); + } + + return this._transport.subscribe(module, callback, eventParams); + } + + removeListener (subscriptionIds) { + return this._transport.unsubscribe(subscriptionIds); + } + + unsubscribe (subscriptionIds) { + return this.removeListener(subscriptionIds); + } +} diff --git a/js/src/api/pubsub/signer/index.js b/js/src/api/pubsub/signer/index.js new file mode 100644 index 0000000000000000000000000000000000000000..caa410722e3dd722f5498ec5d41ed45500955feb --- /dev/null +++ b/js/src/api/pubsub/signer/index.js @@ -0,0 +1,16 @@ +// 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 './signer'; diff --git a/js/src/api/pubsub/signer/signer.js b/js/src/api/pubsub/signer/signer.js new file mode 100644 index 0000000000000000000000000000000000000000..266da6b8abbcf267b075213857bddf92594cb7d5 --- /dev/null +++ b/js/src/api/pubsub/signer/signer.js @@ -0,0 +1,37 @@ +// 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 PubsubBase from '../pubsubBase'; + +import { outSignerRequest } from '../../format/output'; + +export default class Net extends PubsubBase { + constructor (transport) { + super(transport); + this._api = { + subscribe: 'signer_subscribePending', + unsubscribe: 'signer_unsubscribePending', + subscription: 'signer_pending' + }; + } + + pendingRequests (callback) { + return this.addListener(this._api, null, (error, data) => { + error + ? callback(error) + : callback(null, data.map(outSignerRequest)); + }); + } +} diff --git a/js/src/api/rpc/db/db.js b/js/src/api/rpc/db/db.js new file mode 100644 index 0000000000000000000000000000000000000000..c87911b8d76ab6484f6048bdd0d43682bfead41f --- /dev/null +++ b/js/src/api/rpc/db/db.js @@ -0,0 +1,43 @@ +// 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 { inHex } from '../../format/input'; + +export default class Db { + constructor (transport) { + this._transport = transport; + } + + getHex (dbName, keyName) { + return this._transport + .execute('db_getHex', dbName, keyName); + } + + getString (dbName, keyName) { + return this._transport + .execute('db_getString', dbName, keyName); + } + + putHex (dbName, keyName, hexData) { + return this._transport + .execute('db_putHex', dbName, keyName, inHex(hexData)); + } + + putString (dbName, keyName, stringData) { + return this._transport + .execute('db_putString', dbName, keyName, stringData); + } +} diff --git a/js/src/api/rpc/db/db.spec.js b/js/src/api/rpc/db/db.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..94256931b97e1e338a14562770bba79548ce717e --- /dev/null +++ b/js/src/api/rpc/db/db.spec.js @@ -0,0 +1,38 @@ +// 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 { TEST_HTTP_URL, mockHttp } from '../../../../test/mockRpc'; + +import Http from '../../transport/http'; +import Db from './db'; + +const instance = new Db(new Http(TEST_HTTP_URL, -1)); + +describe('api/rpc/Db', () => { + let scope; + + describe('putHex', () => { + beforeEach(() => { + scope = mockHttp([{ method: 'db_putHex', reply: { result: [] } }]); + }); + + it('formats the inputs correctly', () => { + return instance.putHex('db', 'key', '1234').then(() => { + expect(scope.body.db_putHex.params).to.deep.equal(['db', 'key', '0x1234']); + }); + }); + }); +}); diff --git a/js/src/api/rpc/db/index.js b/js/src/api/rpc/db/index.js new file mode 100644 index 0000000000000000000000000000000000000000..0dc25441b61df7d795517891723c5ee987095590 --- /dev/null +++ b/js/src/api/rpc/db/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 './db'; diff --git a/js/src/api/rpc/eth/eth.e2e.js b/js/src/api/rpc/eth/eth.e2e.js new file mode 100644 index 0000000000000000000000000000000000000000..1c8d95a0b9701566b8a27827a07744f19b9c4f43 --- /dev/null +++ b/js/src/api/rpc/eth/eth.e2e.js @@ -0,0 +1,170 @@ +// 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 { createHttpApi } from '../../../../test/e2e/ethapi'; +import { isAddress } from '../../../../test/types'; + +describe('ethapi.eth', () => { + const ethapi = createHttpApi(); + const address = '0x63cf90d3f0410092fc0fca41846f596223979195'; + + let latestBlockNumber; + let latestBlockHash; + + describe('accounts', () => { + it('returns the available accounts', () => { + return ethapi.eth.accounts().then((accounts) => { + accounts.forEach((account) => { + expect(isAddress(account)).to.be.true; + }); + }); + }); + }); + + describe('blockNumber', () => { + it('returns the current blockNumber', () => { + return ethapi.eth.blockNumber().then((blockNumber) => { + latestBlockNumber = blockNumber; + expect(blockNumber.gt(0xabcde)).to.be.true; + }); + }); + }); + + describe('coinbase', () => { + it('returns the coinbase', () => { + return ethapi.eth.coinbase().then((coinbase) => { + expect(isAddress(coinbase)).to.be.true; + }); + }); + }); + + describe('gasPrice', () => { + it('returns the current gasPrice', () => { + return ethapi.eth.gasPrice().then((gasPrice) => { + expect(gasPrice.gt(0)).to.be.true; + }); + }); + }); + + describe('getBalance', () => { + it('returns the balance for latest block', () => { + return ethapi.eth.getBalance(address).then((balance) => { + expect(balance.gt(0)).to.be.true; + }); + }); + + it('returns the balance for a very early block', () => { + const atBlock = '0x65432'; + const atValue = '18e07120a6e164fee1b'; + + return ethapi.eth + .getBalance(address, atBlock) + .then((balance) => { + expect(balance.toString(16)).to.equal(atValue); + }) + .catch((error) => { + // Parity doesn't support pruned-before-block balance lookups + expect(error.message).to.match(/not supported/); + }); + }); + + it('returns the balance for a recent/out-of-pruning-range block', () => { + return ethapi.eth + .getBalance(address, latestBlockNumber.minus(1000)) + .then((balance) => { + expect(balance.gt(0)).to.be.true; + }); + }); + }); + + describe('getBlockByNumber', () => { + it('returns the latest block', () => { + return ethapi.eth.getBlockByNumber().then((block) => { + expect(block).to.be.ok; + }); + }); + + it('returns a block by blockNumber', () => { + return ethapi.eth.getBlockByNumber(latestBlockNumber).then((block) => { + latestBlockHash = block.hash; + expect(block).to.be.ok; + }); + }); + + it('returns a block by blockNumber (full)', () => { + return ethapi.eth.getBlockByNumber(latestBlockNumber, true).then((block) => { + expect(block).to.be.ok; + }); + }); + }); + + describe('getBlockByHash', () => { + it('returns the specified block', () => { + return ethapi.eth.getBlockByHash(latestBlockHash).then((block) => { + expect(block).to.be.ok; + expect(block.hash).to.equal(latestBlockHash); + }); + }); + + it('returns the specified block (full)', () => { + return ethapi.eth.getBlockByHash(latestBlockHash, true).then((block) => { + expect(block).to.be.ok; + expect(block.hash).to.equal(latestBlockHash); + }); + }); + }); + + describe('getBlockTransactionCountByHash', () => { + it('returns the transactions of the specified hash', () => { + return ethapi.eth.getBlockTransactionCountByHash(latestBlockHash).then((count) => { + expect(count).to.be.ok; + expect(count.gte(0)).to.be.true; + }); + }); + }); + + describe('getBlockTransactionCountByNumber', () => { + it('returns the transactions of latest', () => { + return ethapi.eth.getBlockTransactionCountByNumber().then((count) => { + expect(count).to.be.ok; + expect(count.gte(0)).to.be.true; + }); + }); + + it('returns the transactions of a specified number', () => { + return ethapi.eth.getBlockTransactionCountByNumber(latestBlockNumber).then((count) => { + expect(count).to.be.ok; + expect(count.gte(0)).to.be.true; + }); + }); + }); + + describe('getTransactionCount', () => { + it('returns the count for an address', () => { + return ethapi.eth.getTransactionCount(address).then((count) => { + expect(count).to.be.ok; + expect(count.gte(0x1000c2)).to.be.ok; + }); + }); + + it('returns the count for an address at specified blockNumber', () => { + return ethapi.eth.getTransactionCount(address, latestBlockNumber).then((count) => { + expect(count).to.be.ok; + expect(count.gte(0x1000c2)).to.be.ok; + }); + }); + }); +}); diff --git a/js/src/api/rpc/eth/eth.js b/js/src/api/rpc/eth/eth.js new file mode 100644 index 0000000000000000000000000000000000000000..ffde938ea954ca5fe8d1182ae4d4bcad0a424d26 --- /dev/null +++ b/js/src/api/rpc/eth/eth.js @@ -0,0 +1,321 @@ +// 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 { inAddress, inBlockNumber, inData, inFilter, inHash, inHex, inNumber16, inOptions } from '../../format/input'; +import { outAddress, outBlock, outLog, outNumber, outReceipt, outSyncing, outTransaction } from '../../format/output'; + +export default class Eth { + constructor (transport) { + this._transport = transport; + } + + accounts () { + return this._transport + .execute('eth_accounts') + .then((accounts) => (accounts || []).map(outAddress)); + } + + blockNumber () { + return this._transport + .execute('eth_blockNumber') + .then(outNumber); + } + + call (options, blockNumber = 'latest') { + return this._transport + .execute('eth_call', inOptions(options), inBlockNumber(blockNumber)); + } + + coinbase () { + return this._transport + .execute('eth_coinbase') + .then(outAddress); + } + + compileLLL (code) { + return this._transport + .execute('eth_compileLLL', inData(code)); + } + + compileSerpent (code) { + return this._transport + .execute('eth_compileSerpent', inData(code)); + } + + compileSolidity (code) { + return this._transport + .execute('eth_compileSolidity', inData(code)); + } + + estimateGas (options) { + return this._transport + .execute('eth_estimateGas', inOptions(options)) + .then(outNumber); + } + + fetchQueuedTransactions () { + return this._transport + .execute('eth_fetchQueuedTransactions'); + } + + flush () { + return this._transport + .execute('eth_flush'); + } + + gasPrice () { + return this._transport + .execute('eth_gasPrice') + .then(outNumber); + } + + getBalance (address, blockNumber = 'latest') { + return this._transport + .execute('eth_getBalance', inAddress(address), inBlockNumber(blockNumber)) + .then(outNumber); + } + + getBlockByHash (hash, full = false) { + return this._transport + .execute('eth_getBlockByHash', inHex(hash), full) + .then(outBlock); + } + + getBlockByNumber (blockNumber = 'latest', full = false) { + return this._transport + .execute('eth_getBlockByNumber', inBlockNumber(blockNumber), full) + .then(outBlock); + } + + getBlockTransactionCountByHash (hash) { + return this._transport + .execute('eth_getBlockTransactionCountByHash', inHex(hash)) + .then(outNumber); + } + + getBlockTransactionCountByNumber (blockNumber = 'latest') { + return this._transport + .execute('eth_getBlockTransactionCountByNumber', inBlockNumber(blockNumber)) + .then(outNumber); + } + + getCode (address, blockNumber = 'latest') { + return this._transport + .execute('eth_getCode', inAddress(address), inBlockNumber(blockNumber)); + } + + getCompilers () { + return this._transport + .execute('eth_getCompilers'); + } + + getFilterChanges (filterId) { + return this._transport + .execute('eth_getFilterChanges', inNumber16(filterId)) + .then((logs) => logs.map(outLog)); + } + + getFilterChangesEx (filterId) { + return this._transport + .execute('eth_getFilterChangesEx', inNumber16(filterId)); + } + + getFilterLogs (filterId) { + return this._transport + .execute('eth_getFilterLogs', inNumber16(filterId)) + .then((logs) => logs.map(outLog)); + } + + getFilterLogsEx (filterId) { + return this._transport + .execute('eth_getFilterLogsEx', inNumber16(filterId)); + } + + getLogs (options) { + return this._transport + .execute('eth_getLogs', inFilter(options)) + .then((logs) => logs.map(outLog)); + } + + getLogsEx (options) { + return this._transport + .execute('eth_getLogsEx', inFilter(options)); + } + + getStorageAt (address, index = 0, blockNumber = 'latest') { + return this._transport + .execute('eth_getStorageAt', inAddress(address), inNumber16(index), inBlockNumber(blockNumber)); + } + + getTransactionByBlockHashAndIndex (hash, index = 0) { + return this._transport + .execute('eth_getTransactionByBlockHashAndIndex', inHex(hash), inNumber16(index)) + .then(outTransaction); + } + + getTransactionByBlockNumberAndIndex (blockNumber = 'latest', index = 0) { + return this._transport + .execute('eth_getTransactionByBlockNumberAndIndex', inBlockNumber(blockNumber), inNumber16(index)) + .then(outTransaction); + } + + getTransactionByHash (hash) { + return this._transport + .execute('eth_getTransactionByHash', inHex(hash)) + .then(outTransaction); + } + + getTransactionCount (address, blockNumber = 'latest') { + return this._transport + .execute('eth_getTransactionCount', inAddress(address), inBlockNumber(blockNumber)) + .then(outNumber); + } + + getTransactionReceipt (txhash) { + return this._transport + .execute('eth_getTransactionReceipt', inHex(txhash)) + .then(outReceipt); + } + + getUncleByBlockHashAndIndex (hash, index = 0) { + return this._transport + .execute('eth_getUncleByBlockHashAndIndex', inHex(hash), inNumber16(index)); + } + + getUncleByBlockNumberAndIndex (blockNumber = 'latest', index = 0) { + return this._transport + .execute('eth_getUncleByBlockNumberAndIndex', inBlockNumber(blockNumber), inNumber16(index)); + } + + getUncleCountByBlockHash (hash) { + return this._transport + .execute('eth_getUncleCountByBlockHash', inHex(hash)) + .then(outNumber); + } + + getUncleCountByBlockNumber (blockNumber = 'latest') { + return this._transport + .execute('eth_getUncleCountByBlockNumber', inBlockNumber(blockNumber)) + .then(outNumber); + } + + getWork () { + return this._transport + .execute('eth_getWork'); + } + + hashrate () { + return this._transport + .execute('eth_hashrate') + .then(outNumber); + } + + inspectTransaction () { + return this._transport + .execute('eth_inspectTransaction'); + } + + mining () { + return this._transport + .execute('eth_mining'); + } + + newBlockFilter () { + return this._transport + .execute('eth_newBlockFilter'); + } + + newFilter (options) { + return this._transport + .execute('eth_newFilter', inFilter(options)); + } + + newFilterEx (options) { + return this._transport + .execute('eth_newFilterEx', inFilter(options)); + } + + newPendingTransactionFilter () { + return this._transport + .execute('eth_newPendingTransactionFilter'); + } + + notePassword () { + return this._transport + .execute('eth_notePassword'); + } + + pendingTransactions () { + return this._transport + .execute('eth_pendingTransactions'); + } + + protocolVersion () { + return this._transport + .execute('eth_protocolVersion'); + } + + register () { + return this._transport + .execute('eth_register'); + } + + sendRawTransaction (data) { + return this._transport + .execute('eth_sendRawTransaction', inData(data)); + } + + sendTransaction (options) { + return this._transport + .execute('eth_sendTransaction', inOptions(options)); + } + + sign (address, hash) { + return this._transport + .execute('eth_sign', inAddress(address), inHash(hash)); + } + + signTransaction (options) { + return this._transport + .execute('eth_signTransaction', inOptions(options)); + } + + submitHashrate (hashrate, clientId) { + return this._transport + .execute('eth_submitHashrate', inNumber16(hashrate), clientId); + } + + submitWork (nonce, powHash, mixDigest) { + return this._transport + .execute('eth_submitWork', inNumber16(nonce), powHash, mixDigest); + } + + syncing () { + return this._transport + .execute('eth_syncing') + .then(outSyncing); + } + + uninstallFilter (filterId) { + return this._transport + .execute('eth_uninstallFilter', inHex(filterId)); + } + + unregister () { + return this._transport + .execute('eth_unregister'); + } +} diff --git a/js/src/api/rpc/eth/eth.spec.js b/js/src/api/rpc/eth/eth.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..2738fd2dea73e9e57d0635bbd5c8dabd54130fe2 --- /dev/null +++ b/js/src/api/rpc/eth/eth.spec.js @@ -0,0 +1,474 @@ +// 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 { TEST_HTTP_URL, mockHttp } from '../../../../test/mockRpc'; +import { isBigNumber } from '../../../../test/types'; + +import Http from '../../transport/http'; +import Eth from './eth'; + +const instance = new Eth(new Http(TEST_HTTP_URL, -1)); + +describe('rpc/Eth', () => { + const address = '0x63Cf90D3f0410092FC0fca41846f596223979195'; + let scope; + + describe('accounts', () => { + beforeEach(() => { + scope = mockHttp([{ method: 'eth_accounts', reply: { result: [address.toLowerCase()] } }]); + }); + + it('returns a list of accounts, formatted', () => { + return instance.accounts().then((accounts) => { + expect(accounts).to.deep.equal([address]); + }); + }); + }); + + describe('blockNumber', () => { + beforeEach(() => { + scope = mockHttp([{ method: 'eth_blockNumber', reply: { result: '0x123456' } }]); + }); + + it('returns the current blockNumber, formatted', () => { + return instance.blockNumber().then((blockNumber) => { + expect(isBigNumber(blockNumber)).to.be.true; + expect(blockNumber.toString(16)).to.equal('123456'); + }); + }); + }); + + describe('call', () => { + beforeEach(() => { + scope = mockHttp([{ method: 'eth_call', reply: { result: [] } }]); + }); + + it('formats the input options & blockNumber', () => { + return instance.call({ data: '12345678' }, 'earliest').then(() => { + expect(scope.body.eth_call.params).to.deep.equal([{ data: '0x12345678' }, 'earliest']); + }); + }); + + it('provides a latest blockNumber when not specified', () => { + return instance.call({ data: '12345678' }).then(() => { + expect(scope.body.eth_call.params).to.deep.equal([{ data: '0x12345678' }, 'latest']); + }); + }); + }); + + describe('coinbase', () => { + beforeEach(() => { + scope = mockHttp([{ method: 'eth_coinbase', reply: { result: address.toLowerCase() } }]); + }); + + it('returns the coinbase, formatted', () => { + return instance.coinbase().then((account) => { + expect(account).to.deep.equal(address); + }); + }); + }); + + ['LLL', 'Serpent', 'Solidity'].forEach((type) => { + const method = `compile${type}`; + + describe(method, () => { + beforeEach(() => { + scope = mockHttp([{ method: `eth_${method}`, reply: { result: '0x123' } }]); + }); + + it('formats the input as data, returns the output', () => { + return instance[method]('0xabcdef').then((result) => { + expect(scope.body[`eth_${method}`].params).to.deep.equal(['0xabcdef']); + expect(result).to.equal('0x123'); + }); + }); + }); + }); + + describe('estimateGas', () => { + beforeEach(() => { + scope = mockHttp([{ method: 'eth_estimateGas', reply: { result: '0x123' } }]); + }); + + it('converts the options correctly', () => { + return instance.estimateGas({ gas: 21000 }).then(() => { + expect(scope.body.eth_estimateGas.params).to.deep.equal([{ gas: '0x5208' }]); + }); + }); + + it('returns the gas used', () => { + return instance.estimateGas({}).then((gas) => { + expect(isBigNumber(gas)).to.be.true; + expect(gas.toString(16)).to.deep.equal('123'); + }); + }); + }); + + describe('gasPrice', () => { + beforeEach(() => { + scope = mockHttp([{ method: 'eth_gasPrice', reply: { result: '0x123' } }]); + }); + + it('returns the fomratted price', () => { + return instance.gasPrice().then((price) => { + expect(isBigNumber(price)).to.be.true; + expect(price.toString(16)).to.deep.equal('123'); + }); + }); + }); + + describe('getBalance', () => { + beforeEach(() => { + scope = mockHttp([{ method: 'eth_getBalance', reply: { result: '0x123' } }]); + }); + + it('passes in the address (default blockNumber)', () => { + return instance.getBalance(address).then(() => { + expect(scope.body.eth_getBalance.params).to.deep.equal([address.toLowerCase(), 'latest']); + }); + }); + + it('passes in the address & blockNumber', () => { + return instance.getBalance(address, 0x456).then(() => { + expect(scope.body.eth_getBalance.params).to.deep.equal([address.toLowerCase(), '0x456']); + }); + }); + + it('returns the balance', () => { + return instance.getBalance(address, 0x123).then((balance) => { + expect(isBigNumber(balance)).to.be.true; + expect(balance.toString(16)).to.deep.equal('123'); + }); + }); + }); + + describe('getBlockByHash', () => { + beforeEach(() => { + scope = mockHttp([{ method: 'eth_getBlockByHash', reply: { result: { miner: address.toLowerCase() } } }]); + }); + + it('formats the input hash as a hash, default full', () => { + return instance.getBlockByHash('1234').then(() => { + expect(scope.body.eth_getBlockByHash.params).to.deep.equal(['0x1234', false]); + }); + }); + + it('formats the input hash as a hash, full true', () => { + return instance.getBlockByHash('1234', true).then(() => { + expect(scope.body.eth_getBlockByHash.params).to.deep.equal(['0x1234', true]); + }); + }); + + it('formats the output into block', () => { + return instance.getBlockByHash('1234').then((block) => { + expect(block.miner).to.equal(address); + }); + }); + }); + + describe('getBlockByNumber', () => { + beforeEach(() => { + scope = mockHttp([{ method: 'eth_getBlockByNumber', reply: { result: { miner: address.toLowerCase() } } }]); + }); + + it('assumes blockNumber latest & full false', () => { + return instance.getBlockByNumber().then(() => { + expect(scope.body.eth_getBlockByNumber.params).to.deep.equal(['latest', false]); + }); + }); + + it('uses input blockNumber & full false', () => { + return instance.getBlockByNumber('0x1234').then(() => { + expect(scope.body.eth_getBlockByNumber.params).to.deep.equal(['0x1234', false]); + }); + }); + + it('formats the input blockNumber, full true', () => { + return instance.getBlockByNumber(0x1234, true).then(() => { + expect(scope.body.eth_getBlockByNumber.params).to.deep.equal(['0x1234', true]); + }); + }); + + it('formats the output into block', () => { + return instance.getBlockByNumber(0x1234).then((block) => { + expect(block.miner).to.equal(address); + }); + }); + }); + + describe('getBlockTransactionCountByHash', () => { + beforeEach(() => { + scope = mockHttp([{ method: 'eth_getBlockTransactionCountByHash', reply: { result: '0x123' } }]); + }); + + it('formats input hash properly', () => { + return instance.getBlockTransactionCountByHash('abcdef').then(() => { + expect(scope.body.eth_getBlockTransactionCountByHash.params).to.deep.equal(['0xabcdef']); + }); + }); + + it('formats the output number', () => { + return instance.getBlockTransactionCountByHash('0x1234').then((count) => { + expect(isBigNumber(count)).to.be.true; + expect(count.toString(16)).to.equal('123'); + }); + }); + }); + + describe('getBlockTransactionCountByNumber', () => { + beforeEach(() => { + scope = mockHttp([{ method: 'eth_getBlockTransactionCountByNumber', reply: { result: '0x123' } }]); + }); + + it('specified blockNumber latest when none specified', () => { + return instance.getBlockTransactionCountByNumber().then(() => { + expect(scope.body.eth_getBlockTransactionCountByNumber.params).to.deep.equal(['latest']); + }); + }); + + it('formats input blockNumber properly', () => { + return instance.getBlockTransactionCountByNumber(0xabcdef).then(() => { + expect(scope.body.eth_getBlockTransactionCountByNumber.params).to.deep.equal(['0xabcdef']); + }); + }); + + it('formats the output number', () => { + return instance.getBlockTransactionCountByNumber('0x1234').then((count) => { + expect(isBigNumber(count)).to.be.true; + expect(count.toString(16)).to.equal('123'); + }); + }); + }); + + describe('getCode', () => { + beforeEach(() => { + scope = mockHttp([{ method: 'eth_getCode', reply: { result: '0x1234567890' } }]); + }); + + it('passes in the address (default blockNumber)', () => { + return instance.getCode(address).then(() => { + expect(scope.body.eth_getCode.params).to.deep.equal([address.toLowerCase(), 'latest']); + }); + }); + + it('passes in the address & blockNumber', () => { + return instance.getCode(address, 0x456).then(() => { + expect(scope.body.eth_getCode.params).to.deep.equal([address.toLowerCase(), '0x456']); + }); + }); + + it('returns the code', () => { + return instance.getCode(address, 0x123).then((code) => { + expect(code).to.equal('0x1234567890'); + }); + }); + }); + + describe('getStorageAt', () => { + beforeEach(() => { + scope = mockHttp([{ method: 'eth_getStorageAt', reply: { result: '0x1234567890' } }]); + }); + + it('passes in the address (default index& blockNumber)', () => { + return instance.getStorageAt(address).then(() => { + expect(scope.body.eth_getStorageAt.params).to.deep.equal([address.toLowerCase(), '0x0', 'latest']); + }); + }); + + it('passes in the address, index & blockNumber', () => { + return instance.getStorageAt(address, 15, 0x456).then(() => { + expect(scope.body.eth_getStorageAt.params).to.deep.equal([address.toLowerCase(), '0xf', '0x456']); + }); + }); + + it('returns the storage', () => { + return instance.getStorageAt(address, 0x123).then((storage) => { + expect(storage).to.equal('0x1234567890'); + }); + }); + }); + + describe('getTransactionByBlockHashAndIndex', () => { + beforeEach(() => { + scope = mockHttp([{ method: 'eth_getTransactionByBlockHashAndIndex', reply: { result: { to: address.toLowerCase() } } }]); + }); + + it('passes in the hash (default index)', () => { + return instance.getTransactionByBlockHashAndIndex('12345').then(() => { + expect(scope.body.eth_getTransactionByBlockHashAndIndex.params).to.deep.equal(['0x12345', '0x0']); + }); + }); + + it('passes in the hash & specified index', () => { + return instance.getTransactionByBlockHashAndIndex('6789', 0x456).then(() => { + expect(scope.body.eth_getTransactionByBlockHashAndIndex.params).to.deep.equal(['0x6789', '0x456']); + }); + }); + + it('returns the formatted transaction', () => { + return instance.getTransactionByBlockHashAndIndex('6789', 0x123).then((tx) => { + expect(tx).to.deep.equal({ to: address }); + }); + }); + }); + + describe('getTransactionByBlockNumberAndIndex', () => { + beforeEach(() => { + scope = mockHttp([{ method: 'eth_getTransactionByBlockNumberAndIndex', reply: { result: { to: address.toLowerCase() } } }]); + }); + + it('passes in the default parameters', () => { + return instance.getTransactionByBlockNumberAndIndex().then(() => { + expect(scope.body.eth_getTransactionByBlockNumberAndIndex.params).to.deep.equal(['latest', '0x0']); + }); + }); + + it('passes in the blockNumber & specified index', () => { + return instance.getTransactionByBlockNumberAndIndex('0x6789', 0x456).then(() => { + expect(scope.body.eth_getTransactionByBlockNumberAndIndex.params).to.deep.equal(['0x6789', '0x456']); + }); + }); + + it('returns the formatted transaction', () => { + return instance.getTransactionByBlockNumberAndIndex('0x6789', 0x123).then((tx) => { + expect(tx).to.deep.equal({ to: address }); + }); + }); + }); + + describe('getTransactionByHash', () => { + beforeEach(() => { + scope = mockHttp([{ method: 'eth_getTransactionByHash', reply: { result: { to: address.toLowerCase() } } }]); + }); + + it('passes in the hash', () => { + return instance.getTransactionByHash('12345').then(() => { + expect(scope.body.eth_getTransactionByHash.params).to.deep.equal(['0x12345']); + }); + }); + + it('returns the formatted transaction', () => { + return instance.getTransactionByHash('6789').then((tx) => { + expect(tx).to.deep.equal({ to: address }); + }); + }); + }); + + describe('getTransactionCount', () => { + beforeEach(() => { + scope = mockHttp([{ method: 'eth_getTransactionCount', reply: { result: '0x123' } }]); + }); + + it('passes in the address (default blockNumber)', () => { + return instance.getTransactionCount(address).then(() => { + expect(scope.body.eth_getTransactionCount.params).to.deep.equal([address.toLowerCase(), 'latest']); + }); + }); + + it('passes in the address & blockNumber', () => { + return instance.getTransactionCount(address, 0x456).then(() => { + expect(scope.body.eth_getTransactionCount.params).to.deep.equal([address.toLowerCase(), '0x456']); + }); + }); + + it('returns the count, formatted', () => { + return instance.getTransactionCount(address, 0x123).then((count) => { + expect(isBigNumber(count)).to.be.true; + expect(count.toString(16)).to.equal('123'); + }); + }); + }); + + describe('getUncleByBlockHashAndIndex', () => { + beforeEach(() => { + scope = mockHttp([{ method: 'eth_getUncleByBlockHashAndIndex', reply: { result: [] } }]); + }); + + it('passes in the hash (default index)', () => { + return instance.getUncleByBlockHashAndIndex('12345').then(() => { + expect(scope.body.eth_getUncleByBlockHashAndIndex.params).to.deep.equal(['0x12345', '0x0']); + }); + }); + + it('passes in the hash & specified index', () => { + return instance.getUncleByBlockHashAndIndex('6789', 0x456).then(() => { + expect(scope.body.eth_getUncleByBlockHashAndIndex.params).to.deep.equal(['0x6789', '0x456']); + }); + }); + }); + + describe('getUncleByBlockNumberAndIndex', () => { + beforeEach(() => { + scope = mockHttp([{ method: 'eth_getUncleByBlockNumberAndIndex', reply: { result: [] } }]); + }); + + it('passes in the default parameters', () => { + return instance.getUncleByBlockNumberAndIndex().then(() => { + expect(scope.body.eth_getUncleByBlockNumberAndIndex.params).to.deep.equal(['latest', '0x0']); + }); + }); + + it('passes in the blockNumber & specified index', () => { + return instance.getUncleByBlockNumberAndIndex('0x6789', 0x456).then(() => { + expect(scope.body.eth_getUncleByBlockNumberAndIndex.params).to.deep.equal(['0x6789', '0x456']); + }); + }); + }); + + describe('getUncleCountByBlockHash', () => { + beforeEach(() => { + scope = mockHttp([{ method: 'eth_getUncleCountByBlockHash', reply: { result: '0x123' } }]); + }); + + it('passes in the hash', () => { + return instance.getUncleCountByBlockHash('12345').then(() => { + expect(scope.body.eth_getUncleCountByBlockHash.params).to.deep.equal(['0x12345']); + }); + }); + + it('formats the output number', () => { + return instance.getUncleCountByBlockHash('0x1234').then((count) => { + expect(isBigNumber(count)).to.be.true; + expect(count.toString(16)).to.equal('123'); + }); + }); + }); + + describe('getUncleCountByBlockNumber', () => { + beforeEach(() => { + scope = mockHttp([{ method: 'eth_getUncleCountByBlockNumber', reply: { result: '0x123' } }]); + }); + + it('passes in the default parameters', () => { + return instance.getUncleCountByBlockNumber().then(() => { + expect(scope.body.eth_getUncleCountByBlockNumber.params).to.deep.equal(['latest']); + }); + }); + + it('passes in the blockNumber', () => { + return instance.getUncleCountByBlockNumber('0x6789').then(() => { + expect(scope.body.eth_getUncleCountByBlockNumber.params).to.deep.equal(['0x6789']); + }); + }); + + it('formats the output number', () => { + return instance.getUncleCountByBlockNumber('0x1234').then((count) => { + expect(isBigNumber(count)).to.be.true; + expect(count.toString(16)).to.equal('123'); + }); + }); + }); +}); diff --git a/js/src/api/rpc/eth/index.js b/js/src/api/rpc/eth/index.js new file mode 100644 index 0000000000000000000000000000000000000000..25a7b3cb459cc2d1bb6d624fca666fe10fa1b889 --- /dev/null +++ b/js/src/api/rpc/eth/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 './eth'; diff --git a/js/src/api/rpc/index.js b/js/src/api/rpc/index.js new file mode 100644 index 0000000000000000000000000000000000000000..c8fa0032bc749a172242a8d69cab7a47032eec49 --- /dev/null +++ b/js/src/api/rpc/index.js @@ -0,0 +1,25 @@ +// 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 Db from './db'; +export Eth from './eth'; +export Parity from './parity'; +export Net from './net'; +export Personal from './personal'; +export Shh from './shh'; +export Signer from './signer'; +export Trace from './trace'; +export Web3 from './web3'; diff --git a/js/src/api/rpc/net/index.js b/js/src/api/rpc/net/index.js new file mode 100644 index 0000000000000000000000000000000000000000..457eeed79a2dd4c7aff4bcc891fc2ea8b010586f --- /dev/null +++ b/js/src/api/rpc/net/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 './net'; diff --git a/js/src/api/rpc/net/net.e2e.js b/js/src/api/rpc/net/net.e2e.js new file mode 100644 index 0000000000000000000000000000000000000000..c477e2605befb9e6931f36f8dda32e6fb9bfdee2 --- /dev/null +++ b/js/src/api/rpc/net/net.e2e.js @@ -0,0 +1,46 @@ +// 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 { createHttpApi } from '../../../../test/e2e/ethapi'; +import { isBoolean } from '../../../../test/types'; + +describe('ethapi.net', () => { + const ethapi = createHttpApi(); + + describe('listening', () => { + it('returns the listening status', () => { + return ethapi.net.listening().then((status) => { + expect(isBoolean(status)).to.be.true; + }); + }); + }); + + describe('peerCount', () => { + it('returns the peer count', () => { + return ethapi.net.peerCount().then((count) => { + expect(count.gte(0)).to.be.true; + }); + }); + }); + + describe('version', () => { + it('returns the version', () => { + return ethapi.net.version().then((version) => { + expect(version).to.be.ok; + }); + }); + }); +}); diff --git a/js/src/api/rpc/net/net.js b/js/src/api/rpc/net/net.js new file mode 100644 index 0000000000000000000000000000000000000000..9b14f69d520003d8101c0e861dabdd9c2fbc5ded --- /dev/null +++ b/js/src/api/rpc/net/net.js @@ -0,0 +1,39 @@ +// 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 { outNumber } from '../../format/output'; + +export default class Net { + constructor (transport) { + this._transport = transport; + } + + listening () { + return this._transport + .execute('net_listening'); + } + + peerCount () { + return this._transport + .execute('net_peerCount') + .then(outNumber); + } + + version () { + return this._transport + .execute('net_version'); + } +} diff --git a/js/src/api/rpc/net/net.spec.js b/js/src/api/rpc/net/net.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..07ad52076d9500696245a4ae430a6cc35a51b958 --- /dev/null +++ b/js/src/api/rpc/net/net.spec.js @@ -0,0 +1,36 @@ +// 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 { TEST_HTTP_URL, mockHttp } from '../../../../test/mockRpc'; +import { isBigNumber } from '../../../../test/types'; + +import Http from '../../transport/http'; +import Net from './net'; + +const instance = new Net(new Http(TEST_HTTP_URL, -1)); + +describe('api/rpc/Net', () => { + describe('peerCount', () => { + it('returns the connected peers, formatted', () => { + mockHttp([{ method: 'net_peerCount', reply: { result: '0x123456' } }]); + + return instance.peerCount().then((count) => { + expect(isBigNumber(count)).to.be.true; + expect(count.eq(0x123456)).to.be.true; + }); + }); + }); +}); diff --git a/js/src/api/rpc/parity/index.js b/js/src/api/rpc/parity/index.js new file mode 100644 index 0000000000000000000000000000000000000000..7fe520c606fa64436bb8b833267a26d839f943f4 --- /dev/null +++ b/js/src/api/rpc/parity/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 './parity'; diff --git a/js/src/api/rpc/parity/parity.e2e.js b/js/src/api/rpc/parity/parity.e2e.js new file mode 100644 index 0000000000000000000000000000000000000000..8820d570a648e6f4eb51ad1b21c3945a62f16f9a --- /dev/null +++ b/js/src/api/rpc/parity/parity.e2e.js @@ -0,0 +1,79 @@ +// 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 { createHttpApi } from '../../../../test/e2e/ethapi'; + +describe('ethapi.parity', () => { + const ethapi = createHttpApi(); + + describe('chainStatus', () => { + it('returns and translates the status', () => { + return ethapi.parity.chainStatus().then((value) => { + expect(value).to.be.ok; + }); + }); + }); + + describe('gasFloorTarget', () => { + it('returns and translates the target', () => { + return ethapi.parity.gasFloorTarget().then((value) => { + expect(value.gt(0)).to.be.true; + }); + }); + }); + + describe('gasPriceHistogram', () => { + it('returns and translates the target', () => { + return ethapi.parity.gasPriceHistogram().then((result) => { + expect(Object.keys(result)).to.deep.equal(['bucketBounds', 'counts']); + expect(result.bucketBounds.length > 0).to.be.true; + expect(result.counts.length > 0).to.be.true; + }); + }); + }); + + describe('netChain', () => { + it('returns and the chain', () => { + return ethapi.parity.netChain().then((value) => { + expect(value).to.equal('morden'); + }); + }); + }); + + describe('netPort', () => { + it('returns and translates the port', () => { + return ethapi.parity.netPort().then((value) => { + expect(value.gt(0)).to.be.true; + }); + }); + }); + + describe('transactionsLimit', () => { + it('returns and translates the limit', () => { + return ethapi.parity.transactionsLimit().then((value) => { + expect(value.gt(0)).to.be.true; + }); + }); + }); + + describe('rpcSettings', () => { + it('returns and translates the settings', () => { + return ethapi.parity.rpcSettings().then((value) => { + expect(value).to.be.ok; + }); + }); + }); +}); diff --git a/js/src/api/rpc/parity/parity.js b/js/src/api/rpc/parity/parity.js new file mode 100644 index 0000000000000000000000000000000000000000..3a502afabf7c2df39cd6e54b3c75045d8007c853 --- /dev/null +++ b/js/src/api/rpc/parity/parity.js @@ -0,0 +1,587 @@ +// 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 { inAddress, inAddresses, inBlockNumber, inData, inDeriveHash, inDeriveIndex, inHex, inNumber16, inOptions } from '../../format/input'; +import { outAccountInfo, outAddress, outAddresses, outBlock, outChainStatus, outHistogram, outHwAccountInfo, outNodeKind, outNumber, outPeers, outRecentDapps, outTransaction, outVaultMeta } from '../../format/output'; + +export default class Parity { + constructor (transport) { + this._transport = transport; + } + + acceptNonReservedPeers () { + return this._transport + .execute('parity_acceptNonReservedPeers'); + } + + accountsInfo () { + return this._transport + .execute('parity_accountsInfo') + .then(outAccountInfo); + } + + allAccountsInfo () { + return this._transport + .execute('parity_allAccountsInfo') + .then(outAccountInfo); + } + + addReservedPeer (enode) { + return this._transport + .execute('parity_addReservedPeer', enode); + } + + call (requests, blockNumber = 'latest') { + return this._transport + .execute( + 'parity_call', + requests.map((options) => inOptions(options)), + inBlockNumber(blockNumber) + ); + } + + chainStatus () { + return this._transport + .execute('parity_chainStatus') + .then(outChainStatus); + } + + changePassword (account, password, newPassword) { + return this._transport + .execute('parity_changePassword', inAddress(account), password, newPassword); + } + + changeVault (account, vaultName) { + return this._transport + .execute('parity_changeVault', inAddress(account), vaultName); + } + + changeVaultPassword (vaultName, password) { + return this._transport + .execute('parity_changeVaultPassword', vaultName, password); + } + + checkRequest (requestId) { + return this._transport + .execute('parity_checkRequest', inNumber16(requestId)); + } + + cidV0 (data) { + return this._transport + .execute('parity_cidV0', inData(data)); + } + + closeVault (vaultName) { + return this._transport + .execute('parity_closeVault', vaultName); + } + + composeTransaction (options) { + return this._transport + .execute('parity_composeTransaction', inOptions(options)); + } + + consensusCapability () { + return this._transport + .execute('parity_consensusCapability'); + } + + dappsList () { + return this._transport + .execute('parity_dappsList'); + } + + dappsRefresh () { + return this._transport + .execute('parity_dappsRefresh'); + } + + dappsUrl () { + return this._transport + .execute('parity_dappsUrl'); + } + + decryptMessage (address, data) { + return this._transport + .execute('parity_decryptMessage', inAddress(address), inHex(data)); + } + + defaultAccount () { + return this._transport + .execute('parity_defaultAccount') + .then(outAddress); + } + + defaultExtraData () { + return this._transport + .execute('parity_defaultExtraData'); + } + + devLogs () { + return this._transport + .execute('parity_devLogs'); + } + + devLogsLevels () { + return this._transport + .execute('parity_devLogsLevels'); + } + + deriveAddressHash (address, password, hash, shouldSave) { + return this._transport + .execute('parity_deriveAddressHash', inAddress(address), password, inDeriveHash(hash), !!shouldSave) + .then(outAddress); + } + + deriveAddressIndex (address, password, index, shouldSave) { + return this._transport + .execute('parity_deriveAddressIndex', inAddress(address), password, inDeriveIndex(index), !!shouldSave) + .then(outAddress); + } + + dropNonReservedPeers () { + return this._transport + .execute('parity_dropNonReservedPeers'); + } + + enode () { + return this._transport + .execute('parity_enode'); + } + + encryptMessage (pubkey, data) { + return this._transport + .execute('parity_encryptMessage', inHex(pubkey), inHex(data)); + } + + executeUpgrade () { + return this._transport + .execute('parity_executeUpgrade'); + } + + exportAccount (address, password) { + return this._transport + .execute('parity_exportAccount', inAddress(address), password); + } + + extraData () { + return this._transport + .execute('parity_extraData'); + } + + futureTransactions () { + return this._transport + .execute('parity_futureTransactions'); + } + + gasCeilTarget () { + return this._transport + .execute('parity_gasCeilTarget') + .then(outNumber); + } + + gasFloorTarget () { + return this._transport + .execute('parity_gasFloorTarget') + .then(outNumber); + } + + gasPriceHistogram () { + return this._transport + .execute('parity_gasPriceHistogram') + .then(outHistogram); + } + + generateSecretPhrase () { + return this._transport + .execute('parity_generateSecretPhrase'); + } + + getBlockHeaderByNumber (blockNumber = 'latest') { + return this._transport + .execute('parity_getBlockHeaderByNumber', inBlockNumber(blockNumber)) + .then(outBlock); + } + + getDappAddresses (dappId) { + return this._transport + .execute('parity_getDappAddresses', dappId) + .then(outAddresses); + } + + getDappDefaultAddress (dappId) { + return this._transport + .execute('parity_getDappDefaultAddress', dappId) + .then(outAddress); + } + + getNewDappsAddresses () { + return this._transport + .execute('parity_getNewDappsAddresses') + .then((addresses) => addresses ? addresses.map(outAddress) : null); + } + + getNewDappsDefaultAddress () { + return this._transport + .execute('parity_getNewDappsDefaultAddress') + .then(outAddress); + } + + getVaultMeta (vaultName) { + return this._transport + .execute('parity_getVaultMeta', vaultName) + .then(outVaultMeta); + } + + hardwareAccountsInfo () { + return this._transport + .execute('parity_hardwareAccountsInfo') + .then(outHwAccountInfo); + } + + hashContent (url) { + return this._transport + .execute('parity_hashContent', url); + } + + importGethAccounts (accounts) { + return this._transport + .execute('parity_importGethAccounts', inAddresses(accounts)) + .then(outAddresses); + } + + killAccount (account, password) { + return this._transport + .execute('parity_killAccount', inAddress(account), password); + } + + listAccounts (count, offset = null, blockNumber = 'latest') { + return this._transport + .execute('parity_listAccounts', count, inAddress(offset), inBlockNumber(blockNumber)) + .then((accounts) => (accounts || []).map(outAddress)); + } + + listOpenedVaults () { + return this._transport + .execute('parity_listOpenedVaults'); + } + + listVaults () { + return this._transport + .execute('parity_listVaults'); + } + + listRecentDapps () { + return this._transport + .execute('parity_listRecentDapps') + .then(outRecentDapps); + } + + listStorageKeys (address, count, hash = null, blockNumber = 'latest') { + return this._transport + .execute('parity_listStorageKeys', inAddress(address), count, inHex(hash), inBlockNumber(blockNumber)); + } + + removeAddress (address) { + return this._transport + .execute('parity_removeAddress', inAddress(address)); + } + + listGethAccounts () { + return this._transport + .execute('parity_listGethAccounts') + .then(outAddresses); + } + + localTransactions () { + return this._transport + .execute('parity_localTransactions') + .then(transactions => { + Object.values(transactions) + .filter(tx => tx.transaction) + .map(tx => { + tx.transaction = outTransaction(tx.transaction); + }); + return transactions; + }); + } + + minGasPrice () { + return this._transport + .execute('parity_minGasPrice') + .then(outNumber); + } + + mode () { + return this._transport + .execute('parity_mode'); + } + + // DEPRECATED - use chain instead. + netChain () { + return this._transport + .execute('parity_chain'); + } + + nodeKind () { + return this._transport + .execute('parity_nodeKind') + .then(outNodeKind); + } + + chain () { + return this._transport + .execute('parity_chain'); + } + + netPeers () { + return this._transport + .execute('parity_netPeers') + .then(outPeers); + } + + netMaxPeers () { + return this._transport + .execute('parity_netMaxPeers') + .then(outNumber); + } + + netPort () { + return this._transport + .execute('parity_netPort') + .then(outNumber); + } + + newAccountFromPhrase (phrase, password) { + return this._transport + .execute('parity_newAccountFromPhrase', phrase, password) + .then(outAddress); + } + + newAccountFromSecret (secret, password) { + return this._transport + .execute('parity_newAccountFromSecret', inHex(secret), password) + .then(outAddress); + } + + newAccountFromWallet (json, password) { + return this._transport + .execute('parity_newAccountFromWallet', json, password) + .then(outAddress); + } + + newVault (vaultName, password) { + return this._transport + .execute('parity_newVault', vaultName, password); + } + + nextNonce (account) { + return this._transport + .execute('parity_nextNonce', inAddress(account)) + .then(outNumber); + } + + nodeHealth () { + return this._transport + .execute('parity_nodeHealth'); + } + + nodeName () { + return this._transport + .execute('parity_nodeName'); + } + + openVault (vaultName, password) { + return this._transport + .execute('parity_openVault', vaultName, password); + } + + pendingTransactions () { + return this._transport + .execute('parity_pendingTransactions') + .then(data => data.map(outTransaction)); + } + + pendingTransactionsStats () { + return this._transport + .execute('parity_pendingTransactionsStats'); + } + + phraseToAddress (phrase) { + return this._transport + .execute('parity_phraseToAddress', phrase) + .then(outAddress); + } + + postSign (address, hash) { + return this._transport + .execute('parity_postSign', inAddress(address), inHex(hash)); + } + + postTransaction (options = {}) { + return this._transport + .execute('parity_postTransaction', inOptions(options)); + } + + registryAddress () { + return this._transport + .execute('parity_registryAddress') + .then(outAddress); + } + + releasesInfo () { + return this._transport + .execute('parity_releasesInfo'); + } + + removeReservedPeer (enode) { + return this._transport + .execute('parity_removeReservedPeer', enode); + } + + removeTransaction (hash) { + return this._transport + .execute('parity_removeTransaction', inHex(hash)) + .then(outTransaction); + } + + rpcSettings () { + return this._transport + .execute('parity_rpcSettings'); + } + + setAccountName (address, name) { + return this._transport + .execute('parity_setAccountName', inAddress(address), name); + } + + setAccountMeta (address, meta) { + return this._transport + .execute('parity_setAccountMeta', inAddress(address), JSON.stringify(meta)); + } + + setAuthor (address) { + return this._transport + .execute('parity_setAuthor', inAddress(address)); + } + + setDappAddresses (dappId, addresses) { + return this._transport + .execute('parity_setDappAddresses', dappId, inAddresses(addresses)); + } + + setDappDefaultAddress (dappId, address) { + return this._transport + .execute('parity_setDappDefaultAddress', dappId, address ? inAddress(address) : null); + } + + setEngineSigner (address, password) { + return this._transport + .execute('parity_setEngineSigner', inAddress(address), password); + } + + setExtraData (data) { + return this._transport + .execute('parity_setExtraData', inData(data)); + } + + setGasCeilTarget (quantity) { + return this._transport + .execute('parity_setGasCeilTarget', inNumber16(quantity)); + } + + setGasFloorTarget (quantity) { + return this._transport + .execute('parity_setGasFloorTarget', inNumber16(quantity)); + } + + setMaxTransactionGas (quantity) { + return this._transport + .execute('parity_setMaxTransactionGas', inNumber16(quantity)); + } + + setMinGasPrice (quantity) { + return this._transport + .execute('parity_setMinGasPrice', inNumber16(quantity)); + } + + setMode (mode) { + return this._transport + .execute('parity_setMode', mode); + } + + setChain (specName) { + return this._transport + .execute('parity_setChain', specName); + } + + setNewDappsAddresses (addresses) { + return this._transport + .execute('parity_setNewDappsAddresses', addresses ? inAddresses(addresses) : null); + } + + setNewDappsDefaultAddress (address) { + return this._transport + .execute('parity_setNewDappsDefaultAddress', inAddress(address)); + } + + setTransactionsLimit (quantity) { + return this._transport + .execute('parity_setTransactionsLimit', inNumber16(quantity)); + } + + setVaultMeta (vaultName, meta) { + return this._transport + .execute('parity_setVaultMeta', vaultName, JSON.stringify(meta)); + } + + signMessage (address, password, messageHash) { + return this._transport + .execute('parity_signMessage', inAddress(address), password, inHex(messageHash)); + } + + testPassword (account, password) { + return this._transport + .execute('parity_testPassword', inAddress(account), password); + } + + transactionsLimit () { + return this._transport + .execute('parity_transactionsLimit') + .then(outNumber); + } + + unsignedTransactionsCount () { + return this._transport + .execute('parity_unsignedTransactionsCount') + .then(outNumber); + } + + upgradeReady () { + return this._transport + .execute('parity_upgradeReady'); + } + + versionInfo () { + return this._transport + .execute('parity_versionInfo'); + } + + wsUrl () { + return this._transport + .execute('parity_wsUrl'); + } +} diff --git a/js/src/api/rpc/parity/parity.spec.js b/js/src/api/rpc/parity/parity.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..6ba7a1c83f9ab40488fcf7986fd42f617e78f490 --- /dev/null +++ b/js/src/api/rpc/parity/parity.spec.js @@ -0,0 +1,146 @@ +// 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 BigNumber from 'bignumber.js'; +import { TEST_HTTP_URL, mockHttp } from '../../../../test/mockRpc'; +import { isBigNumber } from '../../../../test/types'; + +import Http from '../../transport/http'; +import Parity from './parity'; + +const instance = new Parity(new Http(TEST_HTTP_URL, -1)); + +describe('api/rpc/parity', () => { + describe('accountsInfo', () => { + it('retrieves the available account info', () => { + mockHttp([{ method: 'parity_accountsInfo', reply: { + result: { + '0x63cf90d3f0410092fc0fca41846f596223979195': { + name: 'name', uuid: 'uuid', meta: '{"data":"data"}' + } + } + } }]); + + return instance.accountsInfo().then((result) => { + expect(result).to.deep.equal({ + '0x63Cf90D3f0410092FC0fca41846f596223979195': { + name: 'name', uuid: 'uuid', meta: { + data: 'data' + } + } + }); + }); + }); + }); + + describe('chainStatus', () => { + it('retrieves the chain status', () => { + mockHttp([{ method: 'parity_chainStatus', reply: { + result: { + 'blockGap': [0x123, 0x456] + } + } }]); + + return instance.chainStatus().then((result) => { + expect(result).to.deep.equal({ + 'blockGap': [new BigNumber(0x123), new BigNumber(0x456)] + }); + }); + }); + }); + + describe('gasFloorTarget', () => { + it('returns the gasfloor, formatted', () => { + mockHttp([{ method: 'parity_gasFloorTarget', reply: { result: '0x123456' } }]); + + return instance.gasFloorTarget().then((count) => { + expect(isBigNumber(count)).to.be.true; + expect(count.eq(0x123456)).to.be.true; + }); + }); + }); + + describe('importGethAccounts', () => { + const ACCOUNTS = ['0x63cf90d3f0410092fc0fca41846f596223979195']; + let scope; + + beforeEach(() => { + scope = mockHttp([{ method: 'parity_importGethAccounts', reply: { result: ACCOUNTS } }]); + }); + + it('passes the addresses through', () => { + return instance.importGethAccounts(ACCOUNTS).then((result) => { + expect(scope.body['parity_importGethAccounts'].params).to.deep.equal([ACCOUNTS]); + }); + }); + }); + + describe('minGasPrice', () => { + it('returns the min gasprice, formatted', () => { + mockHttp([{ method: 'parity_minGasPrice', reply: { result: '0x123456' } }]); + + return instance.minGasPrice().then((count) => { + expect(isBigNumber(count)).to.be.true; + expect(count.eq(0x123456)).to.be.true; + }); + }); + }); + + describe('netMaxPeers', () => { + it('returns the max peers, formatted', () => { + mockHttp([{ method: 'parity_netMaxPeers', reply: { result: 25 } }]); + + return instance.netMaxPeers().then((count) => { + expect(isBigNumber(count)).to.be.true; + expect(count.eq(25)).to.be.true; + }); + }); + }); + + describe('netPeers', () => { + it('returns the peer structure, formatted', () => { + mockHttp([{ method: 'parity_netPeers', reply: { result: { active: 123, connected: 456, max: 789, peers: [] } } }]); + + return instance.netPeers().then((peers) => { + expect(peers.active.eq(123)).to.be.true; + expect(peers.connected.eq(456)).to.be.true; + expect(peers.max.eq(789)).to.be.true; + }); + }); + }); + + describe('netPort', () => { + it('returns the connected port, formatted', () => { + mockHttp([{ method: 'parity_netPort', reply: { result: 33030 } }]); + + return instance.netPort().then((count) => { + expect(isBigNumber(count)).to.be.true; + expect(count.eq(33030)).to.be.true; + }); + }); + }); + + describe('transactionsLimit', () => { + it('returns the tx limit, formatted', () => { + mockHttp([{ method: 'parity_transactionsLimit', reply: { result: 1024 } }]); + + return instance.transactionsLimit().then((count) => { + expect(isBigNumber(count)).to.be.true; + expect(count.eq(1024)).to.be.true; + }); + }); + }); +}); diff --git a/js/src/api/rpc/personal/index.js b/js/src/api/rpc/personal/index.js new file mode 100644 index 0000000000000000000000000000000000000000..ec999344202ffe8de88b0b14c6fb9f0ad42862e1 --- /dev/null +++ b/js/src/api/rpc/personal/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 './personal'; diff --git a/js/src/api/rpc/personal/personal.e2e.js b/js/src/api/rpc/personal/personal.e2e.js new file mode 100644 index 0000000000000000000000000000000000000000..d83e9b81114b4e34087d714dfa37188a060f242a --- /dev/null +++ b/js/src/api/rpc/personal/personal.e2e.js @@ -0,0 +1,53 @@ +// 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 { createHttpApi } from '../../../../test/e2e/ethapi'; +import { isAddress, isBoolean } from '../../../../test/types'; + +describe.skip('ethapi.personal', () => { + const ethapi = createHttpApi(); + const password = 'P@55word'; + let address; + + describe('newAccount', () => { + it('creates a new account', () => { + return ethapi.personal.newAccount(password).then((_address) => { + address = _address; + expect(isAddress(address)).to.be.ok; + }); + }); + }); + + describe('listAccounts', () => { + it('has the newly-created account', () => { + return ethapi.personal.listAccounts(password).then((accounts) => { + expect(accounts.filter((_address) => _address === address)).to.deep.equal([address]); + accounts.forEach((account) => { + expect(isAddress(account)).to.be.true; + }); + }); + }); + }); + + describe('unlockAccount', () => { + it('unlocks the newly-created account', () => { + return ethapi.personal.unlockAccount(address, password).then((result) => { + expect(isBoolean(result)).to.be.true; + expect(result).to.be.true; + }); + }); + }); +}); diff --git a/js/src/api/rpc/personal/personal.js b/js/src/api/rpc/personal/personal.js new file mode 100644 index 0000000000000000000000000000000000000000..dd9c40f6262a8f1a9eaa28b65e437b3827ec4f6f --- /dev/null +++ b/js/src/api/rpc/personal/personal.js @@ -0,0 +1,46 @@ +// 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 { inAddress, inNumber10, inOptions } from '../../format/input'; +import { outAddress } from '../../format/output'; + +export default class Personal { + constructor (transport) { + this._transport = transport; + } + + listAccounts () { + return this._transport + .execute('personal_listAccounts') + .then((accounts) => (accounts || []).map(outAddress)); + } + + newAccount (password) { + return this._transport + .execute('personal_newAccount', password) + .then(outAddress); + } + + sendTransaction (options, password) { + return this._transport + .execute('personal_sendTransaction', inOptions(options), password); + } + + unlockAccount (account, password, duration = 1) { + return this._transport + .execute('personal_unlockAccount', inAddress(account), password, inNumber10(duration)); + } +} diff --git a/js/src/api/rpc/personal/personal.spec.js b/js/src/api/rpc/personal/personal.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..d67bb9dec8ff4f9d9477dc8aa79ce42a2c5a6900 --- /dev/null +++ b/js/src/api/rpc/personal/personal.spec.js @@ -0,0 +1,75 @@ +// 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 { TEST_HTTP_URL, mockHttp } from '../../../../test/mockRpc'; + +import Http from '../../transport/http'; +import Personal from './personal'; + +const instance = new Personal(new Http(TEST_HTTP_URL, -1)); + +describe('rpc/Personal', () => { + const account = '0x63cf90d3f0410092fc0fca41846f596223979195'; + const checksum = '0x63Cf90D3f0410092FC0fca41846f596223979195'; + let scope; + + describe('listAccounts', () => { + it('retrieves a list of available accounts', () => { + scope = mockHttp([{ method: 'personal_listAccounts', reply: { result: [account] } }]); + + return instance.listAccounts().then((result) => { + expect(result).to.deep.equal([checksum]); + }); + }); + + it('returns an empty list when none available', () => { + scope = mockHttp([{ method: 'personal_listAccounts', reply: { result: null } }]); + + return instance.listAccounts().then((result) => { + expect(result).to.deep.equal([]); + }); + }); + }); + + describe('newAccount', () => { + it('passes the password, returning the address', () => { + scope = mockHttp([{ method: 'personal_newAccount', reply: { result: account } }]); + + return instance.newAccount('password').then((result) => { + expect(scope.body.personal_newAccount.params).to.deep.equal(['password']); + expect(result).to.equal(checksum); + }); + }); + }); + + describe('unlockAccount', () => { + beforeEach(() => { + scope = mockHttp([{ method: 'personal_unlockAccount', reply: { result: [] } }]); + }); + + it('passes account, password & duration', () => { + return instance.unlockAccount(account, 'password', 0xf).then(() => { + expect(scope.body.personal_unlockAccount.params).to.deep.equal([account, 'password', 15]); + }); + }); + + it('provides a default duration when not specified', () => { + return instance.unlockAccount(account, 'password').then(() => { + expect(scope.body.personal_unlockAccount.params).to.deep.equal([account, 'password', 1]); + }); + }); + }); +}); diff --git a/js/src/api/rpc/shh/index.js b/js/src/api/rpc/shh/index.js new file mode 100644 index 0000000000000000000000000000000000000000..1094fe4379e2b57a51017feec499ee61485de497 --- /dev/null +++ b/js/src/api/rpc/shh/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 './shh'; diff --git a/js/src/api/rpc/shh/shh.js b/js/src/api/rpc/shh/shh.js new file mode 100644 index 0000000000000000000000000000000000000000..c58754abfb0059aaad898e93bc6c3a536d894f3a --- /dev/null +++ b/js/src/api/rpc/shh/shh.js @@ -0,0 +1,91 @@ +// 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 class Shh { + constructor (transport) { + this._transport = transport; + } + + info () { + return this._transport + .execute('shh_info'); + } + + newKeyPair () { + return this._transport + .execute('shh_newKeyPair'); + } + + addPrivateKey (privKey) { + return this._transport + .execute('shh_addPrivateKey', privKey); + } + + newSymKey () { + return this._transport + .execute('shh_newSymKey'); + } + + getPublicKey (identity) { + return this._transport + .execute('shh_getPublicKey', identity); + } + + getPrivateKey (identity) { + return this._transport + .execute('shh_getPrivateKey', identity); + } + + getSymKey (identity) { + return this._transport + .execute('shh_getSymKey', identity); + } + + deleteKey (identity) { + return this._transport + .execute('shh_deleteKey', identity); + } + + post (messageObj) { + return this._transport + .execute('shh_post', messageObj); + } + + newMessageFilter (filterObj) { + return this._transport + .execute('shh_newMessageFilter', filterObj); + } + + getFilterMessages (filterId) { + return this._transport + .execute('shh_getFilterMessages', filterId); + } + + deleteMessageFilter (filterId) { + return this._transport + .execute('shh_deleteMessageFilter', filterId); + } + + subscribe (filterObj, callback) { + return this._transport + .subscribe('shh', callback, filterObj); + } + + unsubscribe (subscriptionId) { + return this._transport + .unsubscribe(subscriptionId); + } +} diff --git a/js/src/api/rpc/signer/index.js b/js/src/api/rpc/signer/index.js new file mode 100644 index 0000000000000000000000000000000000000000..cc1e4dc1c57e2cb118cec7f9694fb48cf160351f --- /dev/null +++ b/js/src/api/rpc/signer/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 './signer'; diff --git a/js/src/api/rpc/signer/signer.js b/js/src/api/rpc/signer/signer.js new file mode 100644 index 0000000000000000000000000000000000000000..915b811589e75b3541fcadb28d90ea2b989e93d3 --- /dev/null +++ b/js/src/api/rpc/signer/signer.js @@ -0,0 +1,65 @@ +// 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 { inData, inNumber16, inOptions } from '../../format/input'; +import { outSignerRequest } from '../../format/output'; + +export default class Signer { + constructor (transport) { + this._transport = transport; + } + + confirmRequest (requestId, options, password) { + return this._transport + .execute('signer_confirmRequest', inNumber16(requestId), inOptions(options), password); + } + + confirmRequestRaw (requestId, data) { + return this._transport + .execute('signer_confirmRequestRaw', inNumber16(requestId), inData(data)); + } + + confirmRequestWithToken (requestId, options, password) { + return this._transport + .execute('signer_confirmRequestWithToken', inNumber16(requestId), inOptions(options), password); + } + + generateAuthorizationToken () { + return this._transport + .execute('signer_generateAuthorizationToken'); + } + + generateWebProxyAccessToken (domain) { + return this._transport + .execute('signer_generateWebProxyAccessToken', domain); + } + + rejectRequest (requestId) { + return this._transport + .execute('signer_rejectRequest', inNumber16(requestId)); + } + + requestsToConfirm () { + return this._transport + .execute('signer_requestsToConfirm') + .then((requests) => (requests || []).map(outSignerRequest)); + } + + signerEnabled () { + return this._transport + .execute('signer_signerEnabled'); + } +} diff --git a/js/src/api/rpc/trace/index.js b/js/src/api/rpc/trace/index.js new file mode 100644 index 0000000000000000000000000000000000000000..8d02aed452147c04224bec40853d04fd0a405951 --- /dev/null +++ b/js/src/api/rpc/trace/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 './trace'; diff --git a/js/src/api/rpc/trace/trace.e2e.js b/js/src/api/rpc/trace/trace.e2e.js new file mode 100644 index 0000000000000000000000000000000000000000..ffbef0ed68b5f3e530384e84b1d93515741ef620 --- /dev/null +++ b/js/src/api/rpc/trace/trace.e2e.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 . + +import { createHttpApi } from '../../../../test/e2e/ethapi'; + +describe('ethapi.trace', () => { + const ethapi = createHttpApi(); + + describe('block', () => { + it('returns the latest block traces', () => { + return ethapi.trace.block().then((traces) => { + expect(traces).to.be.ok; + }); + }); + + it('returns traces for a specified block', () => { + return ethapi.trace.block('0x65432').then((traces) => { + expect(traces).to.be.ok; + }); + }); + }); + + describe('replayTransaction', () => { + it('returns traces for a specific transaction', () => { + return ethapi.eth.getBlockByNumber().then((latestBlock) => { + return ethapi.trace.replayTransaction(latestBlock.transactions[0]).then((traces) => { + expect(traces).to.be.ok; + }); + }); + }); + }); +}); diff --git a/js/src/api/rpc/trace/trace.js b/js/src/api/rpc/trace/trace.js new file mode 100644 index 0000000000000000000000000000000000000000..26b19b378926cb666e2c629f855855c7ea2a039b --- /dev/null +++ b/js/src/api/rpc/trace/trace.js @@ -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 . + +import { inBlockNumber, inData, inHex, inNumber16, inOptions, inTraceFilter, inTraceType } from '../../format/input'; +import { outTraces, outTraceReplay } from '../../format/output'; + +export default class Trace { + constructor (transport) { + this._transport = transport; + } + + block (blockNumber = 'latest') { + return this._transport + .execute('trace_block', inBlockNumber(blockNumber)) + .then(outTraces); + } + + call (options, whatTrace = ['trace'], blockNumber = 'latest') { + return this._transport + .execute('trace_call', inOptions(options), inTraceType(whatTrace), inBlockNumber(blockNumber)) + .then(outTraceReplay); + } + + filter (filterObj) { + return this._transport + .execute('trace_filter', inTraceFilter(filterObj)) + .then(outTraces); + } + + get (txHash, position) { + return this._transport + .execute('trace_get', inHex(txHash), inNumber16(position)) + .then(outTraces); + } + + rawTransaction (data, whatTrace = ['trace']) { + return this._transport + .execute('trace_rawTransaction', inData(data), inTraceType(whatTrace)) + .then(outTraceReplay); + } + + replayTransaction (txHash, whatTrace = ['trace']) { + return this._transport + .execute('trace_replayTransaction', txHash, inTraceType(whatTrace)) + .then(outTraceReplay); + } + + transaction (txHash) { + return this._transport + .execute('trace_transaction', inHex(txHash)) + .then(outTraces); + } +} diff --git a/js/src/api/rpc/trace/trace.spec.js b/js/src/api/rpc/trace/trace.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..267d1240f1b0c2b14668b7d01dc00744f05115fe --- /dev/null +++ b/js/src/api/rpc/trace/trace.spec.js @@ -0,0 +1,44 @@ +// 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 { TEST_HTTP_URL, mockHttp } from '../../../../test/mockRpc'; + +import Http from '../../transport/http'; +import Trace from './trace'; + +const instance = new Trace(new Http(TEST_HTTP_URL, -1)); + +describe('api/rpc/Trace', () => { + let scope; + + describe('block', () => { + beforeEach(() => { + scope = mockHttp([{ method: 'trace_block', reply: { result: [] } }]); + }); + + it('assumes latest blockNumber when not specified', () => { + return instance.block().then(() => { + expect(scope.body.trace_block.params).to.deep.equal(['latest']); + }); + }); + + it('passed specified blockNumber', () => { + return instance.block(0x123).then(() => { + expect(scope.body.trace_block.params).to.deep.equal(['0x123']); + }); + }); + }); +}); diff --git a/js/src/api/rpc/web3/index.js b/js/src/api/rpc/web3/index.js new file mode 100644 index 0000000000000000000000000000000000000000..c2590ec1150dd0ed0c57c1ed3cd9f7deb287e183 --- /dev/null +++ b/js/src/api/rpc/web3/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 './web3'; diff --git a/js/src/api/rpc/web3/web3.e2e.js b/js/src/api/rpc/web3/web3.e2e.js new file mode 100644 index 0000000000000000000000000000000000000000..2c8243a4fb933fb263d2acc13ecc5cf44a7cd591 --- /dev/null +++ b/js/src/api/rpc/web3/web3.e2e.js @@ -0,0 +1,44 @@ +// 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 { createHttpApi } from '../../../../test/e2e/ethapi'; +import { isHexNumber } from '../../../../test/types'; + +describe('ethapi.web3', () => { + const ethapi = createHttpApi(); + + describe('clientVersion', () => { + it('returns the client version', () => { + return ethapi.web3.clientVersion().then((version) => { + const [client] = version.split('/'); + + expect(client === 'Parity' || client === 'Geth').to.be.ok; + }); + }); + }); + + describe('sha3', () => { + it('returns a keccak256 sha', () => { + const sha = '0xa7916fac4f538170f7cd12c148552e2cba9fcd72329a2dd5b07a6fa906488ddf'; + const hexStr = 'baz()'.split('').map((char) => char.charCodeAt(0).toString(16)).join(''); + + return ethapi.web3.sha3(`0x${hexStr}`).then((hash) => { + expect(isHexNumber(hash)).to.be.true; + expect(hash).to.equal(sha); + }); + }); + }); +}); diff --git a/js/src/api/rpc/web3/web3.js b/js/src/api/rpc/web3/web3.js new file mode 100644 index 0000000000000000000000000000000000000000..21b2d77f1e359b10e0df5da14185ad948656e630 --- /dev/null +++ b/js/src/api/rpc/web3/web3.js @@ -0,0 +1,33 @@ +// 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 { inHex } from '../../format/input'; + +export default class Web3 { + constructor (transport) { + this._transport = transport; + } + + clientVersion () { + return this._transport + .execute('web3_clientVersion'); + } + + sha3 (hexStr) { + return this._transport + .execute('web3_sha3', inHex(hexStr)); + } +} diff --git a/js/src/api/rpc/web3/web3.spec.js b/js/src/api/rpc/web3/web3.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..b46679b82a7a8a2285ba7afc0f07bf9f135a20d2 --- /dev/null +++ b/js/src/api/rpc/web3/web3.spec.js @@ -0,0 +1,38 @@ +// 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 { TEST_HTTP_URL, mockHttp } from '../../../../test/mockRpc'; + +import Http from '../../transport/http'; +import Web3 from './web3'; + +const instance = new Web3(new Http(TEST_HTTP_URL, -1)); + +describe('api/rpc/Web3', () => { + let scope; + + describe('sha3', () => { + beforeEach(() => { + scope = mockHttp([{ method: 'web3_sha3', reply: { result: [] } }]); + }); + + it('formats the inputs correctly', () => { + return instance.sha3('1234').then(() => { + expect(scope.body.web3_sha3.params).to.deep.equal(['0x1234']); + }); + }); + }); +}); diff --git a/js/src/api/subscriptions/eth.js b/js/src/api/subscriptions/eth.js new file mode 100644 index 0000000000000000000000000000000000000000..bbab9567248f6aac608152ae5f2afc96cbb8eff8 --- /dev/null +++ b/js/src/api/subscriptions/eth.js @@ -0,0 +1,95 @@ +// 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 BigNumber from 'bignumber.js'; + +export default class Eth { + constructor (updateSubscriptions, api) { + this._api = api; + this._updateSubscriptions = updateSubscriptions; + this._started = false; + + this._lastBlock = new BigNumber(-1); + this._pollTimerId = null; + + // Try to restart subscription if transport is closed + this._api.transport.on('close', () => { + if (this.isStarted) { + this.start(); + } + }); + } + + get isStarted () { + return this._started; + } + + start () { + this._started = true; + + if (this._api.isPubSub) { + return Promise.all([ + this._pollBlockNumber(false), + this._api.pubsub + .subscribeAndGetResult( + callback => this._api.pubsub.eth.newHeads(callback), + () => { + return this._api.eth + .blockNumber() + .then(blockNumber => { + this.updateBlock(blockNumber); + return blockNumber; + }); + } + ) + ]); + } + + // fallback to polling + return this._pollBlockNumber(true); + } + + _pollBlockNumber = (doTimeout) => { + const nextTimeout = (timeout = 1000, forceTimeout = doTimeout) => { + if (forceTimeout) { + this._pollTimerId = setTimeout(() => { + this._pollBlockNumber(doTimeout); + }, timeout); + } + }; + + if (!this._api.transport.isConnected) { + nextTimeout(500, true); + return; + } + + return this._api.eth + .blockNumber() + .then((blockNumber) => { + this.updateBlock(blockNumber); + + nextTimeout(); + }) + .catch(() => nextTimeout()); + } + + updateBlock (blockNumber) { + if (!blockNumber.eq(this._lastBlock)) { + this._lastBlock = blockNumber; + this._updateSubscriptions('eth_blockNumber', null, blockNumber); + } + } +} diff --git a/js/src/api/subscriptions/eth.spec.js b/js/src/api/subscriptions/eth.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..2893a14dcdc1e3f7573daf5b90feec0ab6deccf3 --- /dev/null +++ b/js/src/api/subscriptions/eth.spec.js @@ -0,0 +1,102 @@ +// 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 BigNumber from 'bignumber.js'; +import sinon from 'sinon'; + +import Eth from './eth'; + +const START_BLOCK = 5000; + +function stubApi (blockNumber) { + const _calls = { + blockNumber: [] + }; + + return { + _calls, + transport: { + isConnected: true, + on: () => {} + }, + eth: { + blockNumber: () => { + const stub = sinon.stub().resolves(new BigNumber(blockNumber || START_BLOCK))(); + + _calls.blockNumber.push(stub); + return stub; + } + } + }; +} + +describe('api/subscriptions/eth', () => { + let api; + let eth; + let cb; + + beforeEach(() => { + api = stubApi(); + cb = sinon.stub(); + eth = new Eth(cb, api); + }); + + describe('constructor', () => { + it('starts the instance in a stopped state', () => { + expect(eth.isStarted).to.be.false; + }); + }); + + describe('start', () => { + describe('blockNumber available', () => { + beforeEach(() => { + return eth.start(); + }); + + it('sets the started status', () => { + expect(eth.isStarted).to.be.true; + }); + + it('calls eth_blockNumber', () => { + expect(api._calls.blockNumber.length).to.be.ok; + }); + + it('updates subscribers', () => { + expect(cb).to.have.been.calledWith('eth_blockNumber', null, new BigNumber(START_BLOCK)); + }); + }); + + describe('blockNumber not available', () => { + beforeEach(() => { + api = stubApi(-1); + eth = new Eth(cb, api); + return eth.start(); + }); + + it('sets the started status', () => { + expect(eth.isStarted).to.be.true; + }); + + it('calls eth_blockNumber', () => { + expect(api._calls.blockNumber.length).to.be.ok; + }); + + it('does not update subscribers', () => { + expect(cb).not.to.been.called; + }); + }); + }); +}); diff --git a/js/src/api/subscriptions/index.js b/js/src/api/subscriptions/index.js new file mode 100644 index 0000000000000000000000000000000000000000..07856faefd7c84c6d4fd71d229c7728f281ab5c1 --- /dev/null +++ b/js/src/api/subscriptions/index.js @@ -0,0 +1,19 @@ +// 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 Logging from './logging'; + +export default from './manager'; diff --git a/js/src/api/subscriptions/logging.js b/js/src/api/subscriptions/logging.js new file mode 100644 index 0000000000000000000000000000000000000000..545742dff7f75f7ebdf41fc7e608ed6288ced2ee --- /dev/null +++ b/js/src/api/subscriptions/logging.js @@ -0,0 +1,44 @@ +// 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 . + +let instance = null; + +export default class Logging { + constructor (updateSubscriptions) { + this._updateSubscriptions = updateSubscriptions; + + instance = this; + } + + get isStarted () { + return true; + } + + start () { + } + + static send (method, params, json) { + if (!instance) { + return; + } + + return instance._updateSubscriptions('logging', null, { + method, + params, + json + }); + } +} diff --git a/js/src/api/subscriptions/logging.spec.js b/js/src/api/subscriptions/logging.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..f155696b779277dc9851f6a04f66c90e4d02db13 --- /dev/null +++ b/js/src/api/subscriptions/logging.spec.js @@ -0,0 +1,49 @@ +// 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 Logging from './logging'; + +describe('api/subscriptions/logging', () => { + let cb; + let logging; + + beforeEach(() => { + cb = sinon.stub(); + logging = new Logging(cb); + }); + + describe('constructor', () => { + it('starts the instance in a started state', () => { + expect(logging.isStarted).to.be.true; + }); + }); + + describe('send', () => { + const method = 'method'; + const params = 'params'; + const json = 'json'; + + beforeEach(() => { + Logging.send(method, params, json); + }); + + it('calls the subscription update', () => { + expect(cb).to.have.been.calledWith('logging', null, { method, params, json }); + }); + }); +}); diff --git a/js/src/api/subscriptions/manager.js b/js/src/api/subscriptions/manager.js new file mode 100644 index 0000000000000000000000000000000000000000..6ca2f401502be3c45dff9c19de5174b457aad8b6 --- /dev/null +++ b/js/src/api/subscriptions/manager.js @@ -0,0 +1,137 @@ +// 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 { isError } from '../util/types'; + +import Eth from './eth'; +import Logging from './logging'; +import Personal from './personal'; +import Signer from './signer'; + +const events = { + 'logging': { module: 'logging' }, + 'eth_blockNumber': { module: 'eth' }, + 'parity_accountsInfo': { module: 'personal' }, + 'parity_allAccountsInfo': { module: 'personal' }, + 'parity_defaultAccount': { module: 'personal' }, + 'parity_postTransaction': { module: 'signer' }, + 'eth_accounts': { module: 'personal' }, + 'signer_requestsToConfirm': { module: 'signer' } +}; + +export default class Manager { + constructor (api) { + this._api = api; + + this.subscriptions = []; + this.values = {}; + + Object.keys(events).forEach((subscriptionName) => { + this.values[subscriptionName] = { + error: null, + data: null + }; + }); + + this._logging = new Logging(this._updateSubscriptions); + this._eth = new Eth(this._updateSubscriptions, api); + this._personal = new Personal(this._updateSubscriptions, api, this); + this._signer = new Signer(this._updateSubscriptions, api, this); + } + + _validateType (subscriptionName) { + const subscription = events[subscriptionName]; + + if (!subscription) { + return new Error(`${subscriptionName} is not a valid interface, subscribe using one of ${Object.keys(events).join(', ')}`); + } + + return subscription; + } + + subscribe (subscriptionName, callback, autoRemove = false) { + return new Promise((resolve, reject) => { + const subscription = this._validateType(subscriptionName); + + if (isError(subscription)) { + reject(subscription); + return; + } + + const subscriptionId = this.subscriptions.length; + const { error, data } = this.values[subscriptionName]; + const engine = this[`_${subscription.module}`]; + + this.subscriptions[subscriptionId] = { + name: subscriptionName, + id: subscriptionId, + autoRemove, + callback + }; + + if (!engine.isStarted) { + engine.start(); + } else if (error !== null || data !== null) { + this._sendData(subscriptionId, error, data); + } + + resolve(subscriptionId); + }); + } + + unsubscribe (subscriptionId) { + return new Promise((resolve, reject) => { + if (!this.subscriptions[subscriptionId]) { + reject(new Error(`Cannot find subscription ${subscriptionId}`)); + return; + } + + delete this.subscriptions[subscriptionId]; + resolve(); + }); + } + + _sendData (subscriptionId, error, data) { + const { autoRemove, callback } = this.subscriptions[subscriptionId]; + let result = true; + + try { + result = callback(error, data); + } catch (error) { + console.error(`Unable to update callback for subscriptionId ${subscriptionId}`, error); + } + + if (autoRemove && result && typeof result === 'boolean') { + this.unsubscribe(subscriptionId); + } + } + + _updateSubscriptions = (subscriptionName, error, data) => { + const subscriptions = this.subscriptions + .filter(subscription => subscription.name === subscriptionName); + + this.values[subscriptionName] = { error, data }; + + subscriptions + .forEach((subscription) => { + this._sendData(subscription.id, error, data); + }); + } +} + +export { + events +}; diff --git a/js/src/api/subscriptions/manager.spec.js b/js/src/api/subscriptions/manager.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..df708a36dcbb933f23c20185c07ccae3dabb3874 --- /dev/null +++ b/js/src/api/subscriptions/manager.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 . + +import sinon from 'sinon'; + +import Manager, { events } from './manager'; + +function newStub () { + const start = () => manager._updateSubscriptions(manager.__test, null, 'test'); + + const manager = new Manager({ + transport: { + isConnected: true + } + }); + + manager._eth = { + isStarted: false, + start + }; + + manager._personal = { + isStarted: false, + start + }; + + manager._signer = { + isStarted: false, + start + }; + + return manager; +} + +describe('api/subscriptions/manager', () => { + let manager; + + beforeEach(() => { + manager = newStub(); + }); + + describe('constructor', () => { + it('sets up the subscription types & defaults', () => { + expect(manager.subscriptions).to.be.an.array; + expect(Object.keys(manager.values)).to.deep.equal(Object.keys(events)); + }); + }); + + describe('subscriptions', () => { + Object + .keys(events) + .filter((eventName) => eventName.indexOf('_') !== -1) + .forEach((eventName) => { + const { module } = events[eventName]; + let engine; + let cb; + let subscriptionId; + + describe(eventName, () => { + beforeEach(() => { + engine = manager[`_${module}`]; + manager.__test = eventName; + cb = sinon.stub(); + sinon.spy(engine, 'start'); + + return manager + .subscribe(eventName, cb) + .then((_subscriptionId) => { + subscriptionId = _subscriptionId; + }); + }); + + it(`puts the ${module} engine in a started state`, () => { + expect(engine.start).to.have.been.called; + }); + + it('returns a subscriptionId', () => { + expect(subscriptionId).to.be.a.number; + }); + + it('calls the subscription callback with updated values', () => { + expect(cb).to.have.been.calledWith(null, 'test'); + }); + }); + }); + }); + + describe('unsubscriptions', () => { + Object + .keys(events) + .filter((eventName) => eventName.indexOf('_') !== -1) + .forEach((eventName) => { + const { module } = events[eventName]; + let engine; + let cb; + + describe(eventName, () => { + beforeEach(() => { + engine = manager[`_${module}`]; + manager.__test = eventName; + cb = sinon.stub(); + sinon.spy(engine, 'start'); + + return manager + .subscribe(eventName, cb) + .then((_subscriptionId) => { + manager.unsubscribe(_subscriptionId); + }) + .then(() => { + manager._updateSubscriptions(manager.__test, null, 'test2'); + }); + }); + + it('does not call the callback after unsubscription', () => { + expect(cb).to.have.been.calledWith(null, 'test'); + expect(cb).to.not.have.been.calledWith(null, 'test2'); + }); + }); + }); + }); +}); diff --git a/js/src/api/subscriptions/personal.js b/js/src/api/subscriptions/personal.js new file mode 100644 index 0000000000000000000000000000000000000000..8b2b826d0e38100faa5798dda14b5c9fb57c95d2 --- /dev/null +++ b/js/src/api/subscriptions/personal.js @@ -0,0 +1,160 @@ +// 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 class Personal { + constructor (updateSubscriptions, api, subscriber) { + this._subscriber = subscriber; + this._api = api; + this._updateSubscriptions = updateSubscriptions; + this._started = false; + + this._lastDefaultAccount = '0x0'; + this._pollTimerId = null; + + // Try to restart subscription if transport is closed + this._api.transport.on('close', () => { + if (this.isStarted) { + this.start(); + } + }); + } + + get isStarted () { + return this._started; + } + + start () { + this._started = true; + + let defaultAccount = null; + + if (this._api.isPubSub) { + defaultAccount = this._api.pubsub + .subscribeAndGetResult( + callback => this._api.pubsub.parity.defaultAccount(callback), + (defaultAccount) => { + this.updateDefaultAccount(defaultAccount); + return defaultAccount; + } + ); + } else { + defaultAccount = this._defaultAccount(); + } + + return Promise.all([ + defaultAccount, + this._listAccounts(), + this._accountsInfo(), + this._loggingSubscribe() + ]); + } + + updateDefaultAccount (defaultAccount) { + if (this._lastDefaultAccount !== defaultAccount) { + this._lastDefaultAccount = defaultAccount; + this._updateSubscriptions('parity_defaultAccount', null, defaultAccount); + } + } + + // FIXME: Because of the different API instances, the "wait for valid changes" approach + // doesn't work. Since the defaultAccount is critical to operation, we poll in exactly + // same way we do in ../eth (ala eth_blockNumber) and update. This should be moved + // to pub-sub as it becomes available + _defaultAccount = (timerDisabled = false) => { + const nextTimeout = (timeout = 3000) => { + if (!timerDisabled) { + this._pollTimerId = setTimeout(() => { + this._defaultAccount(); + }, timeout); + } + }; + + if (!this._api.transport.isConnected) { + nextTimeout(500); + return; + } + + return this._api.parity + .defaultAccount() + .then((defaultAccount) => { + this.updateDefaultAccount(defaultAccount); + nextTimeout(); + }) + .catch(() => nextTimeout()); + } + + _listAccounts = () => { + return this._api.eth + .accounts() + .then((accounts) => { + this._updateSubscriptions('eth_accounts', null, accounts); + }); + } + + _accountsInfo = () => { + return this._api.parity + .accountsInfo() + .then((info) => { + this._updateSubscriptions('parity_accountsInfo', null, info); + + return this._api.parity + .allAccountsInfo() + .catch(() => { + // NOTE: This fails on non-secure APIs, swallow error + return {}; + }) + .then((allInfo) => { + this._updateSubscriptions('parity_allAccountsInfo', null, allInfo); + }); + }); + } + + _loggingSubscribe () { + return this._subscriber.subscribe('logging', (error, data) => { + if (error || !data) { + return; + } + + switch (data.method) { + case 'parity_closeVault': + case 'parity_openVault': + case 'parity_killAccount': + case 'parity_importGethAccounts': + case 'parity_newAccountFromPhrase': + case 'parity_newAccountFromWallet': + case 'personal_newAccount': + this._defaultAccount(true); + this._listAccounts(); + this._accountsInfo(); + return; + + case 'parity_removeAddress': + case 'parity_setAccountName': + case 'parity_setAccountMeta': + this._accountsInfo(); + return; + + case 'parity_setDappAddresses': + case 'parity_setDappDefaultAddress': + case 'parity_setNewDappsAddresses': + case 'parity_setNewDappsDefaultAddress': + this._defaultAccount(true); + this._listAccounts(); + return; + } + }); + } +} diff --git a/js/src/api/subscriptions/personal.spec.js b/js/src/api/subscriptions/personal.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..ac046d25099dc78af3c4e2b54b191d008e58ab64 --- /dev/null +++ b/js/src/api/subscriptions/personal.spec.js @@ -0,0 +1,156 @@ +// 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 Personal from './personal'; + +const TEST_DEFAULT = '0xfa64203C044691aA57251aF95f4b48d85eC00Dd5'; +const TEST_INFO = { + [TEST_DEFAULT]: { + name: 'test' + } +}; +const TEST_LIST = [TEST_DEFAULT]; + +function stubApi (accounts, info) { + const _calls = { + accountsInfo: [], + allAccountsInfo: [], + listAccounts: [], + defaultAccount: [] + }; + + return { + _calls, + transport: { + isConnected: true + }, + parity: { + accountsInfo: () => { + const stub = sinon.stub().resolves(info || TEST_INFO)(); + + _calls.accountsInfo.push(stub); + return stub; + }, + allAccountsInfo: () => { + const stub = sinon.stub().resolves(info || TEST_INFO)(); + + _calls.allAccountsInfo.push(stub); + return stub; + }, + defaultAccount: () => { + const stub = sinon.stub().resolves(Object.keys(info || TEST_INFO)[0])(); + + _calls.defaultAccount.push(stub); + return stub; + } + }, + eth: { + accounts: () => { + const stub = sinon.stub().resolves(accounts || TEST_LIST)(); + + _calls.listAccounts.push(stub); + return stub; + } + } + }; +} + +function stubLogging () { + return { + subscribe: sinon.stub() + }; +} + +describe('api/subscriptions/personal', () => { + let api; + let cb; + let logging; + let personal; + + beforeEach(() => { + api = stubApi(); + cb = sinon.stub(); + logging = stubLogging(); + personal = new Personal(cb, api, logging); + }); + + describe('constructor', () => { + it('starts the instance in a stopped state', () => { + expect(personal.isStarted).to.be.false; + }); + }); + + describe('start', () => { + describe('info available', () => { + beforeEach(() => { + return personal.start(); + }); + + it('sets the started status', () => { + expect(personal.isStarted).to.be.true; + }); + + it('calls parity_accountsInfo', () => { + expect(api._calls.accountsInfo.length).to.be.ok; + }); + + it('calls parity_allAccountsInfo', () => { + expect(api._calls.allAccountsInfo.length).to.be.ok; + }); + + it('calls eth_accounts', () => { + expect(api._calls.listAccounts.length).to.be.ok; + }); + + it('updates subscribers', () => { + expect(cb).to.have.been.calledWith('parity_defaultAccount', null, TEST_DEFAULT); + expect(cb).to.have.been.calledWith('eth_accounts', null, TEST_LIST); + expect(cb).to.have.been.calledWith('parity_accountsInfo', null, TEST_INFO); + expect(cb).to.have.been.calledWith('parity_allAccountsInfo', null, TEST_INFO); + }); + }); + + describe('info not available', () => { + beforeEach(() => { + api = stubApi([], {}); + personal = new Personal(cb, api, logging); + return personal.start(); + }); + + it('sets the started status', () => { + expect(personal.isStarted).to.be.true; + }); + + it('calls parity_defaultAccount', () => { + expect(api._calls.defaultAccount.length).to.be.ok; + }); + + it('calls personal_accountsInfo', () => { + expect(api._calls.accountsInfo.length).to.be.ok; + }); + + it('calls personal_allAccountsInfo', () => { + expect(api._calls.allAccountsInfo.length).to.be.ok; + }); + + it('calls personal_listAccounts', () => { + expect(api._calls.listAccounts.length).to.be.ok; + }); + }); + }); +}); diff --git a/js/src/api/subscriptions/signer.js b/js/src/api/subscriptions/signer.js new file mode 100644 index 0000000000000000000000000000000000000000..a0c202c1b220a40e57145fdd9a1efaed234564f1 --- /dev/null +++ b/js/src/api/subscriptions/signer.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 { outTransaction } from '../format/output'; + +export default class Signer { + constructor (updateSubscriptions, api, subscriber) { + this._subscriber = subscriber; + this._api = api; + this._updateSubscriptions = updateSubscriptions; + this._started = false; + + // Try to restart subscription if transport is closed + this._api.transport.on('close', () => { + if (this.isStarted) { + this.start(); + } + }); + } + + get isStarted () { + return this._started; + } + + start () { + this._started = true; + + if (this._api.isPubSub) { + const subscription = this._api.pubsub + .subscribeAndGetResult( + callback => this._api.pubsub.signer.pendingRequests(callback), + requests => { + this.updateSubscriptions(requests); + return requests; + } + ); + + return Promise.all([ + this._listRequests(false), + subscription + ]); + } + + return Promise.all([ + this._listRequests(true), + this._loggingSubscribe() + ]); + } + + updateSubscriptions (requests) { + return this._updateSubscriptions('signer_requestsToConfirm', null, requests); + } + + _listRequests = (doTimeout) => { + const nextTimeout = (timeout = 1000, forceTimeout = doTimeout) => { + if (forceTimeout) { + setTimeout(() => { + this._listRequests(doTimeout); + }, timeout); + } + }; + + if (!this._api.transport.isConnected) { + nextTimeout(500, true); + return; + } + + return this._api.signer + .requestsToConfirm() + .then((requests) => { + this.updateSubscriptions(requests); + nextTimeout(); + }) + .catch(() => nextTimeout()); + } + + _postTransaction (data) { + const request = { + transaction: outTransaction(data.params[0]), + requestId: data.json.result.result + }; + + this._updateSubscriptions('parity_postTransaction', null, request); + } + + _loggingSubscribe () { + return this._subscriber.subscribe('logging', (error, data) => { + if (error || !data) { + return; + } + + switch (data.method) { + case 'eth_sendTransaction': + case 'eth_sendRawTransaction': + this._listRequests(false); + return; + + case 'parity_postTransaction': + this._postTransaction(data); + this._listRequests(false); + return; + } + }); + } +} diff --git a/js/src/api/transport/error.js b/js/src/api/transport/error.js new file mode 100644 index 0000000000000000000000000000000000000000..9049705fab5bcca78a0d63d20a3b73076bf8a4de --- /dev/null +++ b/js/src/api/transport/error.js @@ -0,0 +1,61 @@ +// 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 ExtendableError from 'es6-error'; + +export const ERROR_CODES = { + UNSUPPORTED_REQUEST: -32000, + NO_WORK: -32001, + NO_AUTHOR: -32002, + NO_NEW_WORK: -32003, + NO_WORK_REQUIRED: -32004, + NOT_ENOUGH_DATA: -32006, + UNKNOWN_ERROR: -32009, + TRANSACTION_ERROR: -32010, + EXECUTION_ERROR: -32015, + EXCEPTION_ERROR: -32016, + ACCOUNT_LOCKED: -32020, + PASSWORD_INVALID: -32021, + ACCOUNT_ERROR: -32023, + SIGNER_DISABLED: -32030, + DAPPS_DISABLED: -32031, + NETWORK_DISABLED: -32035, + REQUEST_REJECTED: -32040, + REQUEST_REJECTED_LIMIT: -32041, + REQUEST_NOT_FOUND: -32042, + COMPILATION_ERROR: -32050, + ENCRYPTION_ERROR: -32055, + FETCH_ERROR: -32060, + INVALID_PARAMS: -32602 +}; + +export default class TransportError extends ExtendableError { + static requestRejected (method = null) { + return new TransportError(method, ERROR_CODES.REQUEST_REJECTED, 'Request has been rejected.'); + } + + constructor (method, code, message) { + const m = `${method}: ${code}: ${message}`; + + super(m); + + this.code = code; + this.type = Object.keys(ERROR_CODES).find((k) => ERROR_CODES[k] === code) || ''; + + this.method = method; + this.text = message; + } +} diff --git a/js/src/api/transport/http/http.e2e.js b/js/src/api/transport/http/http.e2e.js new file mode 100644 index 0000000000000000000000000000000000000000..af2fb1fbb31332c3bce258212b0e1a32fd4226cb --- /dev/null +++ b/js/src/api/transport/http/http.e2e.js @@ -0,0 +1,29 @@ +// 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 Http from './http'; + +const http = new Http('http://localhost:8545'); + +describe('transport/Http', () => { + it('connects and makes a call to web3_clientVersion', () => { + return http.execute('web3_clientVersion').then((version) => { + const [client] = version.split('/'); + + expect(client === 'Geth' || client === 'Parity').to.be.ok; + }); + }); +}); diff --git a/js/src/api/transport/http/http.js b/js/src/api/transport/http/http.js new file mode 100644 index 0000000000000000000000000000000000000000..66ef2dec7908543f2191d45dd89708563d58681c --- /dev/null +++ b/js/src/api/transport/http/http.js @@ -0,0 +1,103 @@ +// 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 { Logging } from '../../subscriptions'; +import JsonRpcBase from '../jsonRpcBase'; +import TransportError from '../error'; + +/* global fetch */ +export default class Http extends JsonRpcBase { + constructor (url, connectTimeout = 1000) { + super(); + + this._connected = true; + this._url = url; + this._connectTimeout = connectTimeout; + + this._pollConnection(); + } + + _encodeOptions (method, params) { + const json = this.encode(method, params); + + this.log(json); + + return { + method: 'POST', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'Content-Length': json.length + }, + body: json + }; + } + + _execute (method, params) { + const request = this._encodeOptions(method, params); + + return fetch(this._url, request) + .catch((error) => { + this._setDisconnected(); + throw error; + }) + .then((response) => { + this._setConnected(); + + if (response.status !== 200) { + this._setDisconnected(); + this.error(JSON.stringify({ status: response.status, statusText: response.statusText })); + console.error(`${method}(${JSON.stringify(params)}): ${response.status}: ${response.statusText}`); + + throw new Error(`${response.status}: ${response.statusText}`); + } + + return response.json(); + }) + .then((response) => { + Logging.send(method, params, { request, response }); + + if (response.error) { + this.error(JSON.stringify(response)); + console.error(`${method}(${JSON.stringify(params)}): ${response.error.code}: ${response.error.message}`); + + const error = new TransportError(method, response.error.code, response.error.message); + + throw error; + } + + this.log(JSON.stringify(response)); + return response.result; + }); + } + + _pollConnection = () => { + if (this._connectTimeout <= 0) { + return; + } + + const nextTimeout = () => setTimeout(this._pollConnection, this._connectTimeout); + + this + .execute('net_listening') + .then(() => nextTimeout()) + .catch(() => nextTimeout()); + } + + set url (url) { + this._url = url; + } +} diff --git a/js/src/api/transport/http/http.spec.js b/js/src/api/transport/http/http.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..3d08e17a4493cbf16b87851b011f9ae4986ccdc8 --- /dev/null +++ b/js/src/api/transport/http/http.spec.js @@ -0,0 +1,142 @@ +// 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 { TEST_HTTP_URL, mockHttp } from '../../../../test/mockRpc'; +import Http from './http'; + +const transport = new Http(TEST_HTTP_URL, -1); + +describe('api/transport/Http', () => { + describe('instance', () => { + it('encodes the options correctly', () => { + const opt = transport._encodeOptions('someMethod', ['param']); + const enc = { + method: 'POST', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'Content-Length': 65 + }, + body: `{"jsonrpc":"2.0","method":"someMethod","params":["param"],"id":${transport._id - 1}}` + }; + + expect(opt).to.deep.equal(enc); + }); + }); + + describe('transport emitter', () => { + it('emits close event', (done) => { + transport.once('close', () => { + done(); + }); + + transport.execute('eth_call'); + }); + + it('emits open event', (done) => { + mockHttp([{ method: 'eth_call', reply: { result: '' } }]); + + transport.once('open', () => { + done(); + }); + + transport.execute('eth_call'); + }); + }); + + describe('transport', () => { + const RESULT = ['this is some result']; + + let scope; + let result; + + beforeEach(() => { + scope = mockHttp([{ method: 'eth_call', reply: { result: RESULT } }]); + + return transport + .execute('eth_call', 1, 2, 3, 'test') + .then((_result) => { + result = _result; + }); + }); + + it('makes POST', () => { + expect(scope.isDone()).to.be.true; + }); + + it('sets jsonrpc', () => { + expect(scope.body.eth_call.jsonrpc).to.equal('2.0'); + }); + + it('sets the method', () => { + expect(scope.body.eth_call.method).to.equal('eth_call'); + }); + + it('passes the params', () => { + expect(scope.body.eth_call.params).to.deep.equal([1, 2, 3, 'test']); + }); + + it('increments the id', () => { + expect(scope.body.eth_call.id).not.to.equal(0); + }); + + it('passes the actual result back', () => { + expect(result).to.deep.equal(RESULT); + }); + }); + + describe('HTTP errors', () => { + let scope; + let error; + + beforeEach(() => { + scope = mockHttp([{ method: 'eth_call', reply: {}, code: 500 }]); + + return transport + .execute('eth_call') + .catch((_error) => { + error = _error; + }); + }); + + it('returns HTTP errors as throws', () => { + expect(scope.isDone()).to.be.true; + expect(error.message).to.match(/Internal Server Error/); + }); + }); + + describe('RPC errors', () => { + const ERROR = { code: -1, message: 'ERROR: RPC failure' }; + + let scope; + let error; + + beforeEach(() => { + scope = mockHttp([{ method: 'eth_call', reply: { error: ERROR } }]); + + return transport + .execute('eth_call') + .catch((_error) => { + error = _error; + }); + }); + + it('returns RPC errors as throws', () => { + expect(scope.isDone()).to.be.true; + expect(error.message).to.match(/RPC failure/); + }); + }); +}); diff --git a/js/src/api/transport/http/index.js b/js/src/api/transport/http/index.js new file mode 100644 index 0000000000000000000000000000000000000000..dfd1721653582cd273d0313bdadb6de30c251ed8 --- /dev/null +++ b/js/src/api/transport/http/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 './http'; diff --git a/js/src/api/transport/index.js b/js/src/api/transport/index.js new file mode 100644 index 0000000000000000000000000000000000000000..fdd3861a84e051a32470025ac32f1a10a78c85ea --- /dev/null +++ b/js/src/api/transport/index.js @@ -0,0 +1,20 @@ +// 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 Http from './http'; +export Ws from './ws'; +export TransportError from './error'; +export Middleware from './middleware'; diff --git a/js/src/api/transport/jsonRpcBase.js b/js/src/api/transport/jsonRpcBase.js new file mode 100644 index 0000000000000000000000000000000000000000..1b96347d3a1d1bf7f8314e0097226ea817178520 --- /dev/null +++ b/js/src/api/transport/jsonRpcBase.js @@ -0,0 +1,168 @@ +// 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 EventEmitter from 'eventemitter3'; + +import { Logging } from '../subscriptions'; +import logger from './logger'; + +const LOGGER_ENABLED = process.env.NODE_ENV !== 'production'; + +export default class JsonRpcBase extends EventEmitter { + constructor () { + super(); + + this._id = 1; + this._debug = false; + this._connected = false; + this._middlewareList = Promise.resolve([]); + } + + encode (method, params) { + const json = JSON.stringify({ + jsonrpc: '2.0', + method: method, + params: params, + id: this._id++ + }); + + return json; + } + + addMiddleware (Middleware) { + this._middlewareList = Promise + .all([ + Middleware, + this._middlewareList + ]) + .then(([Middleware, middlewareList]) => { + // Do nothing if `handlerPromise` resolves to a null-y value. + if (Middleware == null) { + return middlewareList; + } + + // don't mutate the original array + return middlewareList.concat([new Middleware(this)]); + }); + } + + _wrapSuccessResult (result) { + return { + id: this._id, + jsonrpc: '2.0', + result + }; + } + + _wrapErrorResult (error) { + return { + id: this._id, + jsonrpc: '2.0', + error: { + code: error.code, + message: error.text + } + }; + } + + execute (method, ...params) { + let start; + let logId; + + if (LOGGER_ENABLED) { + start = Date.now(); + logId = logger.log({ method, params }); + } + + return this._middlewareList.then((middlewareList) => { + for (const middleware of middlewareList) { + const res = middleware.handle(method, params); + + if (res != null) { + return Promise + .resolve(res) + .then((res) => { + const result = this._wrapSuccessResult(res); + const json = this.encode(method, params); + + Logging.send(method, params, { json, result }); + + return res; + }); + } + } + + const result = this._execute(method, params); + + if (!LOGGER_ENABLED) { + return result; + } + + return result + .then((result) => { + logger.set(logId, { result, time: Date.now() - start }); + + return result; + }); + }); + } + + _execute () { + throw new Error('Missing implementation of JsonRpcBase#_execute'); + } + + _setConnected () { + if (!this._connected) { + this._connected = true; + this.emit('open'); + } + } + + _setDisconnected () { + if (this._connected) { + this._connected = false; + this.emit('close'); + } + } + + get id () { + return this._id; + } + + get isDebug () { + return this._debug; + } + + get isConnected () { + return this._connected; + } + + setDebug (flag) { + this._debug = flag; + } + + error (error) { + if (this.isDebug) { + console.error(error); + } + } + + log (log) { + if (this.isDebug) { + console.log(log); + } + } +} diff --git a/js/src/api/transport/jsonRpcBase.spec.js b/js/src/api/transport/jsonRpcBase.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..6c3b955647eaa1dcc3b81a8d4d1a62edf3a40360 --- /dev/null +++ b/js/src/api/transport/jsonRpcBase.spec.js @@ -0,0 +1,88 @@ +// 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 JsonRpcBase from './jsonRpcBase'; + +const base = new JsonRpcBase(); + +describe('api/transport/JsonRpcBase', () => { + describe('encode', () => { + it('encodes the body correctly, incrementing id', () => { + const id = base.id; + const bdy = base.encode('someMethod', ['param1', 'param2']); + const enc = `{"jsonrpc":"2.0","method":"someMethod","params":["param1","param2"],"id":${id}}`; + + expect(bdy).to.equal(enc); + expect(base.id - id).to.equal(1); + }); + }); + + describe('setDebug', () => { + it('starts with disabled flag', () => { + expect(base.isDebug).to.be.false; + }); + + it('true flag switches on', () => { + base.setDebug(true); + expect(base.isDebug).to.be.true; + }); + + it('false flag switches off', () => { + base.setDebug(true); + expect(base.isDebug).to.be.true; + base.setDebug(false); + expect(base.isDebug).to.be.false; + }); + + describe('logging', () => { + beforeEach(() => { + sinon.spy(console, 'log'); + sinon.spy(console, 'error'); + }); + + afterEach(() => { + console.log.restore(); + console.error.restore(); + }); + + it('does not log errors with flag off', () => { + base.setDebug(false); + base.log('error'); + expect(console.log).to.not.be.called; + }); + + it('does not log errors with flag off', () => { + base.setDebug(false); + base.error('error'); + expect(console.error).to.not.be.called; + }); + + it('does log errors with flag on', () => { + base.setDebug(true); + base.log('error'); + expect(console.log).to.be.called; + }); + + it('does log errors with flag on', () => { + base.setDebug(true); + base.error('error'); + expect(console.error).to.be.called; + }); + }); + }); +}); diff --git a/js/src/api/transport/logger.js b/js/src/api/transport/logger.js new file mode 100644 index 0000000000000000000000000000000000000000..930c4a34ebcb3107d6985e89489cfca829e8812b --- /dev/null +++ b/js/src/api/transport/logger.js @@ -0,0 +1,150 @@ +// 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 MethodDecodingStore from '~/ui/MethodDecoding/methodDecodingStore'; + +const LOGGER_ENABLED = process.env.NODE_ENV !== 'production'; + +let logger; + +if (LOGGER_ENABLED) { + class Logger { + _logs = {}; + _id = 0; + + log ({ method, params }) { + const logId = this._id++; + + this._logs[logId] = { method, params, date: Date.now() }; + return logId; + } + + set (logId, data) { + this._logs[logId] = { + ...this._logs[logId], + ...data + }; + } + + static sorter (logA, logB) { + return logA.date - logB.date; + } + + get calls () { + const calls = this.methods['eth_call'] || []; + const decoding = MethodDecodingStore.get(window.secureApi); + const contracts = {}; + + const progress = Math.round(calls.length / 20); + + return calls + .reduce((promise, call, index) => { + const { data, to } = call.params[0]; + + contracts[to] = contracts[to] || []; + + return promise + .then(() => decoding.lookup(null, { data, to })) + .then((lookup) => { + if (!lookup.name) { + contracts[to].push(data); + return; + } + + const inputs = lookup.inputs.map((input) => { + if (/bytes/.test(input.type)) { + return '0x' + input.value.map((v) => v.toString(16).padStart(2, 0)).join(''); + } + + return input.value; + }); + + const called = `${lookup.name}(${inputs.join(', ')})`; + + contracts[to].push(called); + + if (index % progress === 0) { + console.warn(`progress: ${Math.round(100 * index / calls.length)}%`); + } + }); + }, Promise.resolve()) + .then(() => { + return Object.keys(contracts) + .map((address) => { + const count = contracts[address].length; + + return { + count, + calls: contracts[address], + to: address + }; + }) + .sort((cA, cB) => cB.count - cA.count); + }); + } + + get logs () { + return Object.values(this._logs).sort(Logger.sorter); + } + + get methods () { + return this.logs.reduce((methods, log) => { + methods[log.method] = methods[log.method] || []; + methods[log.method].push(log); + return methods; + }, {}); + } + + get stats () { + const logs = this.logs; + const methods = this.methods; + + const start = logs[0].date; + const end = logs[logs.length - 1].date; + + // Duration in seconds + const duration = (end - start) / 1000; + const speed = logs.length / duration; + + const sortedMethods = Object.keys(methods) + .map((method) => { + const methodLogs = methods[method].sort(Logger.sorter); + const methodSpeed = methodLogs.length / duration; + + return { + speed: methodSpeed, + count: methodLogs.length, + logs: methodLogs, + method + }; + }) + .sort((mA, mB) => mB.count - mA.count); + + return { + methods: sortedMethods, + speed + }; + } + } + + logger = new Logger(); + + if (window) { + window._logger = logger; + } +} + +export default logger; diff --git a/js/src/api/transport/middleware.js b/js/src/api/transport/middleware.js new file mode 100644 index 0000000000000000000000000000000000000000..ef029b1cbc8db9b3ca94c2cd4915e77ec2426a67 --- /dev/null +++ b/js/src/api/transport/middleware.js @@ -0,0 +1,44 @@ +// 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 class Middleware { + constructor (transport) { + this._transport = transport; + this._handlers = {}; + } + + register (method, handler) { + if (method in this._handlers) { + throw new Error(`${method} is already defined in the middleware!`); + } + + this._handlers[method] = handler; + } + + handle (method, params) { + const handler = this._handlers[method]; + + if (handler != null) { + return handler(params); + } + + return null; + } + + rpcRequest (method, params) { + return this._transport._execute(method, params); + } +} diff --git a/js/src/api/transport/middleware.spec.js b/js/src/api/transport/middleware.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..4ae894135bcc79c7bb8177eb8ac26cc5e1fc3cca --- /dev/null +++ b/js/src/api/transport/middleware.spec.js @@ -0,0 +1,62 @@ +// 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 Middleware from './middleware'; +import JsonRpcBase from './jsonRpcBase'; + +const MOCKED = 'mocked!'; + +class MockTransport extends JsonRpcBase { + _execute () { + return Promise.resolve(MOCKED); + } +} + +class MockMiddleware extends Middleware { + constructor (transport) { + super(transport); + + this.register('mock_rpc', ([num]) => num); + this.register('mock_null', () => null); + } +} + +describe('api/transport/Middleware', () => { + let transport; + + beforeEach(() => { + transport = new MockTransport(); + transport.addMiddleware(MockMiddleware); + }); + + it('Routes requests to middleware', () => { + return transport.execute('mock_rpc', 100).then((num) => { + expect(num).to.be.equal(100); + }); + }); + + it('Passes non-mocked requests through', () => { + return transport.execute('not_moced', 200).then((result) => { + expect(result).to.be.equal(MOCKED); + }); + }); + + it('Passes mocked requests through, if middleware returns null', () => { + return transport.execute('mock_null', 300).then((result) => { + expect(result).to.be.equal(MOCKED); + }); + }); +}); diff --git a/js/src/api/transport/ws/index.js b/js/src/api/transport/ws/index.js new file mode 100644 index 0000000000000000000000000000000000000000..7ab0be1310c387b669817e2bc9f1387a86d7b68a --- /dev/null +++ b/js/src/api/transport/ws/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 './ws'; diff --git a/js/src/api/transport/ws/ws.e2e.js b/js/src/api/transport/ws/ws.e2e.js new file mode 100644 index 0000000000000000000000000000000000000000..42e47451c3bba3d8c44436d73997f94e21653274 --- /dev/null +++ b/js/src/api/transport/ws/ws.e2e.js @@ -0,0 +1,29 @@ +// 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 Ws from './ws'; + +const ws = new Ws('ws://localhost:8546/'); + +describe('transport/Ws', () => { + it('connects and makes a call to web3_clientVersion', () => { + return ws.execute('web3_clientVersion').then((version) => { + const [client] = version.split('/'); + + expect(client === 'Geth' || client === 'Parity').to.be.ok; + }); + }); +}); diff --git a/js/src/api/transport/ws/ws.js b/js/src/api/transport/ws/ws.js new file mode 100644 index 0000000000000000000000000000000000000000..63abecb839d9d386426cd6ba8c9b0302546d4889 --- /dev/null +++ b/js/src/api/transport/ws/ws.js @@ -0,0 +1,398 @@ +// 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 { keccak_256 } from 'js-sha3'; // eslint-disable-line camelcase + +import { Logging } from '../../subscriptions'; +import JsonRpcBase from '../jsonRpcBase'; +import TransportError from '../error'; + +/* global WebSocket */ +export default class Ws extends JsonRpcBase { + // token is optional (secure API) + constructor (url, token = null, autoconnect = true) { + super(); + + this._url = url; + this._token = token; + this._messages = {}; + this._subscriptions = {}; + this._sessionHash = null; + + this._connecting = false; + this._connected = false; + this._lastError = null; + this._autoConnect = autoconnect; + this._retries = 0; + this._reconnectTimeoutId = null; + + this._connectPromise = null; + this._connectPromiseFunctions = {}; + + if (autoconnect) { + this.connect(); + } + } + + updateToken (token, connect = true) { + this._token = token; + // this._autoConnect = true; + + if (connect) { + this.connect(); + } + } + + connect () { + if (this._connected) { + return Promise.resolve(); + } + + if (this._connecting) { + return this._connectPromise || Promise.resolve(); + } + + if (this._reconnectTimeoutId) { + window.clearTimeout(this._reconnectTimeoutId); + this._reconnectTimeoutId = null; + } + + if (this._ws) { + this._ws.onerror = null; + this._ws.onopen = null; + this._ws.onclose = null; + this._ws.onmessage = null; + this._ws.close(); + this._ws = null; + this._sessionHash = null; + } + this._connecting = true; + this._connected = false; + this._lastError = null; + + // rpc secure API + if (this._token) { + const time = parseInt(new Date().getTime() / 1000, 10); + const sha3 = keccak_256(`${this._token}:${time}`); + const hash = `${sha3}_${time}`; + + this._sessionHash = sha3; + this._ws = new WebSocket(this._url, hash); + // non-secure API + } else { + this._ws = new WebSocket(this._url); + } + + this._ws.onerror = this._onError; + this._ws.onopen = this._onOpen; + this._ws.onclose = this._onClose; + this._ws.onmessage = this._onMessage; + + // Get counts in dev mode only + if (process.env.NODE_ENV === 'development') { + this._count = 0; + this._lastCount = { + timestamp: Date.now(), + count: 0 + }; + + window.setInterval(() => { + const n = this._count - this._lastCount.count; + const t = (Date.now() - this._lastCount.timestamp) / 1000; + const s = Math.round(1000 * n / t) / 1000; + + if (this._debug) { + console.log('::parityWS', `speed: ${s} req/s`, `count: ${this._count}`, `(+${n})`); + } + + this._lastCount = { + timestamp: Date.now(), + count: this._count + }; + }, 5000); + + window._parityWS = this; + } + + this._connectPromise = new Promise((resolve, reject) => { + this._connectPromiseFunctions = { resolve, reject }; + }); + + return this._connectPromise; + } + + _onOpen = (event) => { + this._setConnected(); + this._connecting = false; + this._retries = 0; + + Object.keys(this._messages) + .filter((id) => this._messages[id].queued) + .forEach(this._send); + + this._connectPromiseFunctions.resolve(); + + this._connectPromise = null; + this._connectPromiseFunctions = {}; + } + + _onClose = (event) => { + this._setDisconnected(); + this._connecting = false; + + event.timestamp = Date.now(); + this._lastError = event; + + if (this._autoConnect) { + const timeout = this.retryTimeout; + + const time = timeout < 1000 + ? Math.round(timeout) + 'ms' + : (Math.round(timeout / 10) / 100) + 's'; + + console.log('ws:onClose', `trying again in ${time}...`); + + this._reconnectTimeoutId = setTimeout(() => { + this.connect(); + }, timeout); + + return; + } + + if (this._connectPromise) { + this._connectPromiseFunctions.reject(event); + + this._connectPromise = null; + this._connectPromiseFunctions = {}; + } + + console.log('ws:onClose'); + } + + _onError = (event) => { + // Only print error if the WS is connected + // ie. don't print if error == closed + window.setTimeout(() => { + if (this._connected) { + console.error('ws:onError'); + + event.timestamp = Date.now(); + this._lastError = event; + + if (this._connectPromise) { + this._connectPromiseFunctions.reject(event); + + this._connectPromise = null; + this._connectPromiseFunctions = {}; + } + } + }, 50); + } + + _extract = (result) => { + const { result: res, id, method, params } = result; + const msg = this._messages[id]; + + // initial pubsub ACK + if (id && msg.subscription) { + // save subscription to map subId -> messageId + this._subscriptions[msg.subscription] = this._subscriptions[msg.subscription] || {}; + this._subscriptions[msg.subscription][res] = id; + // resolve promise with messageId because subId's can collide (eth/parity) + msg.resolve(id); + // save subId for unsubscribing later + msg.subId = res; + return msg; + } + + // normal message + if (id) { + return msg; + } + + // pubsub format + if (this._subscriptions[method]) { + const messageId = this._messages[this._subscriptions[method][params.subscription]]; + + if (messageId) { + return messageId; + } else { + throw Error(`Received Subscription which is already unsubscribed ${JSON.stringify(result)}`); + } + } + + throw Error(`Unknown message format: No ID or subscription ${JSON.stringify(result)}`); + } + + _onMessage = (event) => { + try { + const result = JSON.parse(event.data); + const { method, params, json, resolve, reject, callback, subscription } = this._extract(result); + + Logging.send(method, params, { json, result }); + + result.error = (result.params && result.params.error) || result.error; + if (result.error) { + this.error(event.data); + + // Don't print error if request rejected or not is not yet up... + if (!/(rejected|not yet up)/.test(result.error.message)) { + console.error(`${method}(${JSON.stringify(params)}): ${result.error.code}: ${result.error.message}`); + } + + const error = new TransportError(method, result.error.code, result.error.message); + + if (result.id) { + reject(error); + } else { + callback(error); + } + + delete this._messages[result.id]; + return; + } + + // if not initial subscription message resolve & delete + if (result.id && !subscription) { + resolve(result.result); + delete this._messages[result.id]; + } else if (result.params) { + callback(null, result.params.result); + } + } catch (e) { + console.error('ws::_onMessage', event.data, e); + } + } + + _send = (id) => { + const message = this._messages[id]; + + if (this._connected) { + if (process.env.NODE_ENV === 'development') { + this._count++; + } + + return this._ws.send(message.json); + } + + message.queued = !this._connected; + message.timestamp = Date.now(); + } + + _execute (method, params) { + return new Promise((resolve, reject) => { + const id = this.id; + const json = this.encode(method, params); + + this._messages[id] = { id, method, params, json, resolve, reject }; + this._send(id); + }); + } + + _methodsFromApi (api) { + if (api.subscription) { + const { subscribe, unsubscribe, subscription } = api; + + return { + method: subscribe, + uMethod: unsubscribe, + subscription + }; + } + + const method = `${api}_subscribe`; + const uMethod = `${api}_unsubscribe`; + const subscription = `${api}_subscription`; + + return { method, uMethod, subscription }; + } + + subscribe (api, callback, params) { + return new Promise((resolve, reject) => { + const id = this.id; + const { method, uMethod, subscription } = this._methodsFromApi(api); + const json = this.encode(method, params); + + this._messages[id] = { id, method, uMethod, params, json, resolve, reject, callback, subscription }; + + this._send(id); + }); + } + + unsubscribe (messageId) { + return new Promise((resolve, reject) => { + const id = this.id; + const { subId, uMethod, subscription } = this._messages[messageId]; + const params = [subId]; + const json = this.encode(uMethod, params); + const uResolve = (v) => { + delete this._messages[messageId]; + delete this._subscriptions[subscription][subId]; + resolve(v); + }; + + this._messages[id] = { id, method: uMethod, params, json, resolve: uResolve, reject }; + this._send(id); + }); + } + + set url (url) { + this._url = url; + } + + get token () { + return this._token; + } + + get sessionHash () { + return this._sessionHash; + } + + get isAutoConnect () { + return this._autoConnect; + } + + get isConnecting () { + return this._connecting; + } + + get lastError () { + return this._lastError; + } + + /** + * Exponential Timeout for Retries + * + * @see http://dthain.blogspot.de/2009/02/exponential-backoff-in-distributed.html + */ + get retryTimeout () { + // R between 1 and 2 + const R = Math.random() + 1; + // Initial timeout (100ms) + const T = 100; + // Exponential Factor + const F = 2; + // Max timeout (4s) + const M = 4000; + // Current number of retries + const N = this._retries; + + // Increase retries number + this._retries++; + + return Math.min(R * T * Math.pow(F, N), M); + } +} diff --git a/js/src/api/transport/ws/ws.spec.js b/js/src/api/transport/ws/ws.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..0d3c9836f5e7941af3544f19845427b2c0fd3d26 --- /dev/null +++ b/js/src/api/transport/ws/ws.spec.js @@ -0,0 +1,116 @@ +// 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 { TEST_WS_URL, mockWs } from '../../../../test/mockRpc'; +import Ws from './ws'; + +describe('api/transport/Ws', () => { + let transport; + let scope; + + describe('transport emitter', () => { + const connect = () => { + const scope = mockWs(); + const transport = new Ws(TEST_WS_URL); + + return { transport, scope }; + }; + + it('emits open event', (done) => { + const { transport, scope } = connect(); + + transport.once('open', () => { + done(); + }); + + scope.stop(); + }); + + it('emits close event', (done) => { + const { transport, scope } = connect(); + + transport.once('open', () => { + scope.server.close(); + }); + + transport.once('close', () => { + done(); + }); + }); + }); + + describe('transport', () => { + let result; + + beforeEach(() => { + scope = mockWs([{ method: 'test_anyCall', reply: 'TestResult' }]); + transport = new Ws(TEST_WS_URL); + + return transport + .execute('test_anyCall', 1, 2, 3) + .then((_result) => { + result = _result; + }); + }); + + afterEach(() => { + scope.stop(); + }); + + it('makes call', () => { + expect(scope.isDone()).to.be.true; + }); + + it('sets jsonrpc', () => { + expect(scope.body.test_anyCall.jsonrpc).to.equal('2.0'); + }); + + it('sets the method', () => { + expect(scope.body.test_anyCall.method).to.equal('test_anyCall'); + }); + + it('passes the params', () => { + expect(scope.body.test_anyCall.params).to.deep.equal([1, 2, 3]); + }); + + it('increments the id', () => { + expect(scope.body.test_anyCall.id).not.to.equal(0); + }); + + it('passes the actual result back', () => { + expect(result).to.equal('TestResult'); + }); + }); + + describe('errors', () => { + beforeEach(() => { + scope = mockWs([{ method: 'test_anyCall', reply: { error: { code: 1, message: 'TestError' } } }]); + transport = new Ws(TEST_WS_URL); + }); + + afterEach(() => { + scope.stop(); + }); + + it('returns RPC errors when encountered', () => { + return transport + .execute('test_anyCall') + .catch((error) => { + expect(error).to.match(/TestError/); + }); + }); + }); +}); diff --git a/js/src/api/util/decode.js b/js/src/api/util/decode.js new file mode 100644 index 0000000000000000000000000000000000000000..926a5a8a15439b8c49ac3b27cd66c2e190ece91d --- /dev/null +++ b/js/src/api/util/decode.js @@ -0,0 +1,95 @@ +// 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 { isHex } from './types'; + +import Func from '../../abi/spec/function'; +import { fromParamType, toParamType } from '../../abi/spec/paramType/format'; + +export function decodeCallData (data) { + if (!isHex(data)) { + throw new Error('Input to decodeCallData should be a hex value'); + } + + if (data.substr(0, 2) === '0x') { + return decodeCallData(data.slice(2)); + } + + if (data.length < 8) { + throw new Error('Input to decodeCallData should be method signature + data'); + } + + const signature = data.substr(0, 8); + const paramdata = data.substr(8); + + return { + signature: `0x${signature}`, + paramdata: `0x${paramdata}` + }; +} + +export function decodeMethodInput (methodAbi, paramdata) { + if (!methodAbi) { + throw new Error('decodeMethodInput should receive valid method-specific ABI'); + } + + if (paramdata && paramdata.length) { + if (!isHex(paramdata)) { + throw new Error('Input to decodeMethodInput should be a hex value'); + } + + if (paramdata.substr(0, 2) === '0x') { + return decodeMethodInput(methodAbi, paramdata.slice(2)); + } + } + + return new Func(methodAbi).decodeInput(paramdata).map((decoded) => decoded.value); +} + +// takes a method in form name(...,types) and returns the inferred abi definition +export function methodToAbi (method) { + const length = method.length; + const typesStart = method.indexOf('('); + const typesEnd = method.indexOf(')'); + + if (typesStart === -1) { + throw new Error(`Missing start ( in call to decodeMethod with ${method}`); + } else if (typesEnd === -1) { + throw new Error(`Missing end ) in call to decodeMethod with ${method}`); + } else if (typesEnd < typesStart) { + throw new Error(`End ) is before start ( in call to decodeMethod with ${method}`); + } else if (typesEnd !== length - 1) { + throw new Error(`Extra characters after end ) in call to decodeMethod with ${method}`); + } + + const name = method.substr(0, typesStart); + const types = method.substr(typesStart + 1, length - (typesStart + 1) - 1).split(','); + const inputs = types.filter((_type) => _type.length).map((_type) => { + const type = fromParamType(toParamType(_type)); + + return { type }; + }); + + return { type: 'function', name, inputs }; +} + +export function abiDecode (inputTypes, data) { + return decodeMethodInput({ + inputs: inputTypes.map((type) => { + return { type }; + }) + }, data); +} diff --git a/js/src/api/util/decode.spec.js b/js/src/api/util/decode.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..006c6a86392f78c99123e399b16d19ab195a94f7 --- /dev/null +++ b/js/src/api/util/decode.spec.js @@ -0,0 +1,105 @@ +// 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 BigNumber from 'bignumber.js'; +import { abiDecode, decodeCallData, decodeMethodInput, methodToAbi } from './decode'; + +describe('api/util/decode', () => { + const METH = '0x70a08231'; + const ENCO = '0x70a082310000000000000000000000005A5eFF38DA95b0D58b6C616f2699168B480953C9'; + const DATA = '0x0000000000000000000000005A5eFF38DA95b0D58b6C616f2699168B480953C9'; + + describe('decodeCallData', () => { + it('throws on non-hex inputs', () => { + expect(() => decodeCallData('invalid')).to.throw(/should be a hex value/); + }); + + it('throws when invalid signature length', () => { + expect(() => decodeCallData(METH.slice(-6))).to.throw(/should be method signature/); + }); + + it('splits valid inputs properly', () => { + expect(decodeCallData(ENCO)).to.deep.equal({ + signature: METH, + paramdata: DATA + }); + }); + }); + + describe('decodeMethodInput', () => { + it('expects a valid ABI', () => { + expect(() => decodeMethodInput(null, null)).to.throw(/should receive valid method/); + }); + + it('expect valid hex parameter data', () => { + expect(() => decodeMethodInput({}, 'invalid')).to.throw(/should be a hex value/); + }); + + it('correctly decodes valid inputs', () => { + expect( + decodeMethodInput({ + type: 'function', + inputs: [ + { type: 'uint' } + ] + }, DATA) + ).to.deep.equal( + [ new BigNumber('0x5a5eff38da95b0d58b6c616f2699168b480953c9') ] + ); + }); + }); + + describe('methodToAbi', () => { + it('throws when no start ( specified', () => { + expect(() => methodToAbi('invalid,uint,bool)')).to.throw(/Missing start \(/); + }); + + it('throws when no end ) specified', () => { + expect(() => methodToAbi('invalid(uint,bool')).to.throw(/Missing end \)/); + }); + + it('throws when end ) is not in the last position', () => { + expect(() => methodToAbi('invalid(uint,bool)2')).to.throw(/Extra characters after end \)/); + }); + + it('throws when start ( is after end )', () => { + expect(() => methodToAbi('invalid)uint,bool(')).to.throw(/End \) is before start \(/); + }); + + it('throws when invalid types are present', () => { + expect(() => methodToAbi('method(invalidType,bool,uint)')).to.throw(/Cannot convert invalidType/); + }); + + it('returns a valid methodabi for a valid method', () => { + expect(methodToAbi('valid(uint,bool)')).to.deep.equals({ + type: 'function', + name: 'valid', + inputs: [ + { type: 'uint256' }, + { type: 'bool' } + ] + }); + }); + }); + + describe('abiDecode', () => { + it('correctly decodes valid inputs', () => { + expect(abiDecode(['uint'], DATA)).to.deep.equal( + [ new BigNumber('0x5a5eff38da95b0d58b6c616f2699168b480953c9') ] + ); + }); + }); +}); diff --git a/js/src/api/util/encode.js b/js/src/api/util/encode.js new file mode 100644 index 0000000000000000000000000000000000000000..f18fbbe2b7b1045765449333ce052861df995440 --- /dev/null +++ b/js/src/api/util/encode.js @@ -0,0 +1,69 @@ +// 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 Abi from '~/abi'; +import Func from '~/abi/spec/function'; + +import { abiDecode } from './decode'; +import { cleanupValue } from './format'; +import { sha3 } from './sha3'; + +export function encodeMethodCallAbi (methodAbi = {}, values = []) { + const func = new Func(methodAbi); + const tokens = Abi.encodeTokens(func.inputParamTypes(), values); + const call = func.encodeCall(tokens); + + return `0x${call}`; +} + +export function abiEncode (methodName, inputTypes, data) { + const result = encodeMethodCallAbi({ + name: methodName || '', + type: 'function', + inputs: inputTypes.map((type) => { + return { type }; + }) + }, data); + + return result; +} + +export function abiUnencode (abi, data) { + const callsig = data.substr(2, 8); + const op = abi.find((field) => { + return field.type === 'function' && + abiSignature(field.name, field.inputs.map((input) => input.type)).substr(2, 8) === callsig; + }); + + if (!op) { + console.warn(`Unknown function ID: ${callsig}`); + return null; + } + + let argsByIndex = abiDecode(op.inputs.map((field) => field.type), '0x' + data.substr(10)) + .map((value, index) => cleanupValue(value, op.inputs[index].type)); + const argsByName = op.inputs.reduce((result, field, index) => { + result[field.name] = argsByIndex[index]; + + return result; + }, {}); + + return [op.name, argsByName, argsByIndex]; +} + +export function abiSignature (name, inputs) { + return sha3(`${name}(${inputs.join()})`); +} diff --git a/js/src/api/util/encode.spec.js b/js/src/api/util/encode.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..7847f06ecc804fe6bd89c731c279e114e0086419 --- /dev/null +++ b/js/src/api/util/encode.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 { abiEncode, abiUnencode, abiSignature, encodeMethodCallAbi } from './encode'; + +const ABI = { + type: 'function', + name: 'valid', + inputs: [ + { type: 'uint256' }, + { type: 'bool' } + ] +}; + +const RESULT = [ + 'f87fa141', + '0000000000000000000000000000000000000000000000000000000000000123', + '0000000000000000000000000000000000000000000000000000000000000001' +].join(''); +const VARIABLE = [ + '5a6fbce0', + 'c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470', + '0000000000000000000000000000000000000000000000000000000000000040', + '000000000000000000000000000000000000000000000000000000000000000f', + '687474703a2f2f666f6f2e6261722f0000000000000000000000000000000000' +].join(''); + +describe('api/util/encode', () => { + describe('encodeMethodCallAbi', () => { + it('encodes calls with the correct result', () => { + expect(encodeMethodCallAbi(ABI, [0x123, true])).to.equal(`0x${RESULT}`); + }); + }); + + describe('abiEncode', () => { + it('encodes calls with the correct result', () => { + expect(abiEncode('valid', ['uint256', 'bool'], [0x123, true])).to.equal(`0x${RESULT}`); + }); + + it('encodes variable values', () => { + expect( + abiEncode( + 'hintUrl', ['bytes32', 'string'], ['0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470', 'http://foo.bar/'] + ) + ).to.equal(`0x${VARIABLE}`); + }); + + it('encodes only the data with null name', () => { + expect( + abiEncode(null, ['uint256', 'bool'], [0x123, true]) + ).to.equal(`0x${RESULT.substr(8)}`); + }); + }); + + describe('abiUnencode', () => { + it('decodes data correctly from abi', () => { + expect( + abiUnencode([{ + name: 'test', + type: 'function', + inputs: [ + { type: 'uint', name: 'arga' } + ] + }], '0x1acb6f7700000000000000000000000000000038') + ).to.deep.equal(['test', { arga: 56 }, [56]]); + }); + }); + + // Same example as in abi/util/signature.spec.js + describe('abiSignature', () => { + it('encodes baz(uint32,bool) correctly', () => { + expect( + abiSignature('baz', ['uint32', 'bool']) + ).to.equal('0xcdcd77c0992ec5bbfc459984220f8c45084cc24d9b6efed1fae540db8de801d2'); + }); + }); +}); diff --git a/js/src/api/util/format.js b/js/src/api/util/format.js new file mode 100644 index 0000000000000000000000000000000000000000..6a4a81d4004dee944817fde894c2eaeb9169269f --- /dev/null +++ b/js/src/api/util/format.js @@ -0,0 +1,106 @@ +// 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 { range } from 'lodash'; + +export function bytesToHex (bytes) { + return '0x' + Buffer.from(bytes).toString('hex'); +} + +export function cleanupValue (value, type) { + // TODO: make work with arbitrary depth arrays + if (value instanceof Array && type.match(/bytes[0-9]+/)) { + // figure out if it's an ASCII string hiding in there: + let ascii = ''; + + for (let index = 0, ended = false; index < value.length && ascii !== null; ++index) { + const val = value[index]; + + if (val === 0) { + ended = true; + } else { + ascii += String.fromCharCode(val); + } + + if ((ended && val !== 0) || (!ended && (val < 32 || val >= 128))) { + ascii = null; + } + } + + value = ascii === null + ? bytesToHex(value) + : ascii; + } + + if (type.substr(0, 4) === 'uint' && +type.substr(4) <= 48) { + value = +value; + } + + return value; +} + +export function hexToBytes (hex) { + const raw = toHex(hex).slice(2); + const bytes = []; + + for (let i = 0; i < raw.length; i += 2) { + bytes.push(parseInt(raw.substr(i, 2), 16)); + } + + return bytes; +} + +export function hexToAscii (hex) { + const bytes = hexToBytes(hex); + const str = bytes.map((byte) => String.fromCharCode(byte)).join(''); + + return str; +} + +export function bytesToAscii (bytes) { + return bytes.map((b) => String.fromCharCode(b % 512)).join(''); +} + +export function asciiToHex (string) { + return '0x' + string.split('') + .map(s => s.charCodeAt(0)) + .map(s => s < 0x10 ? '0' + s.toString(16) : s.toString(16)) + .join(''); +} + +export function padRight (input, length) { + const value = toHex(input).substr(2, length * 2); + + return '0x' + value + range(length * 2 - value.length).map(() => '0').join(''); +} + +export function padLeft (input, length) { + const value = toHex(input).substr(2, length * 2); + + return '0x' + range(length * 2 - value.length).map(() => '0').join('') + value; +} + +export function toHex (str) { + if (str && str.toString) { + str = str.toString(16); + } + + if (str && str.substr(0, 2) === '0x') { + return str.toLowerCase(); + } + + return `0x${(str || '').toLowerCase()}`; +} diff --git a/js/src/api/util/format.spec.js b/js/src/api/util/format.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..ba7a3994e049b7762626df5dbca12c599de8bc8c --- /dev/null +++ b/js/src/api/util/format.spec.js @@ -0,0 +1,94 @@ +// 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 { bytesToHex, cleanupValue, hexToBytes, hexToAscii, bytesToAscii, asciiToHex } from './format'; + +describe('api/util/format', () => { + describe('bytesToHex', () => { + it('correctly converts an empty array', () => { + expect(bytesToHex([])).to.equal('0x'); + }); + + it('correctly converts a non-empty array', () => { + expect(bytesToHex([0, 15, 16])).to.equal('0x000f10'); + }); + }); + + describe('cleanupValue', () => { + it('returns unknown values as the original', () => { + expect(cleanupValue('original', 'unknown')).to.equal('original'); + }); + + it('returns ascii arrays as ascii', () => { + expect(cleanupValue([97, 115, 99, 105, 105, 0], 'bytes32')).to.equal('ascii'); + }); + + it('returns non-ascii arrays as hex strings', () => { + expect(cleanupValue([97, 200, 0, 0], 'bytes4')).to.equal('0x61c80000'); + }); + + it('returns uint (>48) as the original', () => { + expect(cleanupValue('original', 'uint49')).to.equal('original'); + }); + + it('returns uint (<=48) as the number value', () => { + expect(cleanupValue('12345', 'uint48')).to.equal(12345); + }); + }); + + describe('hexToBytes', () => { + it('correctly converts an empty string', () => { + expect(hexToBytes('')).to.deep.equal([]); + expect(hexToBytes('0x')).to.deep.equal([]); + }); + + it('correctly converts a non-empty string', () => { + expect(hexToBytes('0x000f10')).to.deep.equal([0, 15, 16]); + }); + }); + + describe('asciiToHex', () => { + it('correctly converts an empty string', () => { + expect(asciiToHex('')).to.equal('0x'); + }); + + it('correctly converts a non-empty string', () => { + expect(asciiToHex('abc')).to.equal('0x616263'); + expect(asciiToHex('a\nb')).to.equal('0x610a62'); + }); + }); + + describe('hexToAscii', () => { + it('correctly converts an empty string', () => { + expect(hexToAscii('')).to.equal(''); + expect(hexToAscii('0x')).to.equal(''); + }); + + it('correctly converts a non-empty string', () => { + expect(hexToAscii('0x616263')).to.equal('abc'); + }); + }); + + describe('bytesToAscii', () => { + it('correctly converts an empty string', () => { + expect(bytesToAscii([])).to.equal(''); + }); + + it('correctly converts a non-empty string', () => { + expect(bytesToAscii([97, 98, 99])).to.equal('abc'); + }); + }); +}); diff --git a/js/src/api/util/identity.js b/js/src/api/util/identity.js new file mode 100644 index 0000000000000000000000000000000000000000..4df7ab9bb23a4af8e7eed248aed752a432831256 --- /dev/null +++ b/js/src/api/util/identity.js @@ -0,0 +1,30 @@ +// 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 blockies from 'blockies'; + +// jsdom doesn't have all the browser features, blockies fail +const TEST_ENV = process.env.NODE_ENV === 'test'; + +export function createIdentityImg (address, scale = 8) { + return TEST_ENV + ? 'test-createIdentityImg' + : blockies({ + seed: (address || '').toLowerCase(), + size: 8, + scale + }).toDataURL(); +} diff --git a/js/src/api/util/index.js b/js/src/api/util/index.js new file mode 100644 index 0000000000000000000000000000000000000000..30328856bd59215d6a9d0b853d1080ed5c9f87ab --- /dev/null +++ b/js/src/api/util/index.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 { isAddress as isAddressValid, toChecksumAddress } from '../../abi/util/address'; +import { abiDecode, decodeCallData, decodeMethodInput, methodToAbi } from './decode'; +import { abiEncode, abiUnencode, abiSignature, encodeMethodCallAbi } from './encode'; +import { bytesToHex, hexToAscii, hexToBytes, asciiToHex, cleanupValue } from './format'; +import { fromWei, toWei } from './wei'; +import { sha3 } from './sha3'; +import { isArray, isFunction, isHex, isInstanceOf, isString } from './types'; +import { createIdentityImg } from './identity'; + +export default { + abiDecode, + abiEncode, + abiUnencode, + abiSignature, + cleanupValue, + isAddressValid, + isArray, + isFunction, + isHex, + isInstanceOf, + isString, + bytesToHex, + hexToAscii, + hexToBytes, + asciiToHex, + createIdentityImg, + decodeCallData, + decodeMethodInput, + encodeMethodCallAbi, + methodToAbi, + fromWei, + toChecksumAddress, + toWei, + sha3 +}; diff --git a/js/src/api/util/sha3.js b/js/src/api/util/sha3.js new file mode 100644 index 0000000000000000000000000000000000000000..3ca49cc8e6f54aa60bd66801769ab4c5938803a8 --- /dev/null +++ b/js/src/api/util/sha3.js @@ -0,0 +1,36 @@ +// 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 { keccak_256 } from 'js-sha3'; // eslint-disable-line + +import { hexToBytes } from './format'; +import { isHex } from './types'; + +export function sha3 (value, options) { + const forceHex = options && options.encoding === 'hex'; + + if (forceHex || (!options && isHex(value))) { + const bytes = hexToBytes(value); + + return sha3(bytes); + } + + const hash = keccak_256(value); + + return `0x${hash}`; +} + +sha3.text = (val) => sha3(val, { encoding: 'raw' }); diff --git a/js/src/api/util/sha3.spec.js b/js/src/api/util/sha3.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..a5c93d9404aa157a7cd000c983b1f2799ee45d41 --- /dev/null +++ b/js/src/api/util/sha3.spec.js @@ -0,0 +1,46 @@ +// 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 { sha3 } from './sha3'; + +describe('api/util/sha3', () => { + describe('sha3', () => { + it('constructs a correct sha3 value', () => { + expect(sha3('jacogr')).to.equal('0x2f4ff4b5a87abbd2edfed699db48a97744e028c7f7ce36444d40d29d792aa4dc'); + }); + + it('constructs a correct sha3 encoded as hex', () => { + const key = '000000000000000000000000391694e7e0b0cce554cb130d723a9d27458f9298' + '0000000000000000000000000000000000000000000000000000000000000001'; + + expect(sha3(key, { encoding: 'hex' })).to.equal('0x6661e9d6d8b923d5bbaab1b96e1dd51ff6ea2a93520fdc9eb75d059238b8c5e9'); + expect(sha3(`0x${key}`, { encoding: 'hex' })).to.equal('0x6661e9d6d8b923d5bbaab1b96e1dd51ff6ea2a93520fdc9eb75d059238b8c5e9'); + }); + + it('constructs a correct sha3 from Uint8Array', () => { + expect(sha3('01020304', { encoding: 'hex' })).to.equal('0xa6885b3731702da62e8e4a8f584ac46a7f6822f4e2ba50fba902f67b1588d23b'); + expect(sha3(Uint8Array.from([1, 2, 3, 4]))).to.equal('0xa6885b3731702da62e8e4a8f584ac46a7f6822f4e2ba50fba902f67b1588d23b'); + }); + + it('should interpret as bytes by default', () => { + expect(sha3('0x01020304')).to.equal('0xa6885b3731702da62e8e4a8f584ac46a7f6822f4e2ba50fba902f67b1588d23b'); + }); + + it('should force text if option is passed', () => { + expect(sha3('0x01020304', { encoding: 'raw' })).to.equal('0x16bff43de576d28857dcba65a56fc17c5e93c09bd6a709268eff8e62025ae869'); + expect(sha3.text('0x01020304')).to.equal('0x16bff43de576d28857dcba65a56fc17c5e93c09bd6a709268eff8e62025ae869'); + }); + }); +}); diff --git a/js/src/api/util/types.js b/js/src/api/util/types.js new file mode 100644 index 0000000000000000000000000000000000000000..f4278cb5179de20bcb1c0668de9c4c58eab304bd --- /dev/null +++ b/js/src/api/util/types.js @@ -0,0 +1,60 @@ +// 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 . + +const HEXDIGITS = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']; + +export function isArray (test) { + return Object.prototype.toString.call(test) === '[object Array]'; +} + +export function isError (test) { + return Object.prototype.toString.call(test) === '[object Error]'; +} + +export function isFunction (test) { + return Object.prototype.toString.call(test) === '[object Function]'; +} + +export function isHex (_test) { + if (!isString(_test)) { + return false; + } + + if (_test.substr(0, 2) === '0x') { + return isHex(_test.slice(2)); + } + + const test = _test.toLowerCase(); + let hex = true; + + for (let idx = 0; hex && idx < test.length; idx++) { + hex = HEXDIGITS.includes(test[idx]); + } + + return hex; +} + +export function isObject (test) { + return Object.prototype.toString.call(test) === '[object Object]'; +} + +export function isString (test) { + return Object.prototype.toString.call(test) === '[object String]'; +} + +export function isInstanceOf (test, clazz) { + return test instanceof clazz; +} diff --git a/js/src/api/util/types.spec.js b/js/src/api/util/types.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..c8672f8884ffb43300bc2a5f426c115b41c1f1ae --- /dev/null +++ b/js/src/api/util/types.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 sinon from 'sinon'; + +import { isArray, isError, isFunction, isHex, isInstanceOf, isObject, isString } from './types'; +import Eth from '../rpc/eth'; + +describe('api/util/types', () => { + describe('isArray', () => { + it('correctly identifies null as false', () => { + expect(isArray(null)).to.be.false; + }); + + it('correctly identifies empty array as true', () => { + expect(isArray([])).to.be.true; + }); + + it('correctly identifies array as true', () => { + expect(isArray([1, 2, 3])).to.be.true; + }); + }); + + describe('isError', () => { + it('correctly identifies null as false', () => { + expect(isError(null)).to.be.false; + }); + + it('correctly identifies Error as true', () => { + expect(isError(new Error('an error'))).to.be.true; + }); + }); + + describe('isFunction', () => { + it('correctly identifies null as false', () => { + expect(isFunction(null)).to.be.false; + }); + + it('correctly identifies function as true', () => { + expect(isFunction(sinon.stub())).to.be.true; + }); + }); + + describe('isHex', () => { + it('correctly identifies hex by leading 0x', () => { + expect(isHex('0x123')).to.be.true; + }); + + it('correctly identifies hex without leading 0x', () => { + expect(isHex('123')).to.be.true; + }); + + it('correctly identifies non-hex values', () => { + expect(isHex('123j')).to.be.false; + }); + + it('correctly indentifies non-string values', () => { + expect(isHex(false)).to.be.false; + expect(isHex()).to.be.false; + expect(isHex([1, 2, 3])).to.be.false; + }); + }); + + describe('isInstanceOf', () => { + it('correctly identifies build-in instanceof', () => { + expect(isInstanceOf(new String('123'), String)).to.be.true; // eslint-disable-line no-new-wrappers + }); + + it('correctly identifies own instanceof', () => { + expect(isInstanceOf(new Eth({}), Eth)).to.be.true; + }); + + it('correctly reports false for own', () => { + expect(isInstanceOf({}, Eth)).to.be.false; + }); + }); + + describe('isObject', () => { + it('correctly identifies empty object as object', () => { + expect(isObject({})).to.be.true; + }); + + it('correctly identifies non-empty object as object', () => { + expect(isObject({ data: '123' })).to.be.true; + }); + + it('correctly identifies Arrays as non-objects', () => { + expect(isObject([1, 2, 3])).to.be.false; + }); + + it('correctly identifies Strings as non-objects', () => { + expect(isObject('123')).to.be.false; + }); + }); + + describe('isString', () => { + it('correctly identifies empty string as string', () => { + expect(isString('')).to.be.true; + }); + + it('correctly identifies string as string', () => { + expect(isString('123')).to.be.true; + }); + }); +}); diff --git a/js/src/api/util/wei.js b/js/src/api/util/wei.js new file mode 100644 index 0000000000000000000000000000000000000000..d17df69583715ded6e8af71673c0fedf723a649e --- /dev/null +++ b/js/src/api/util/wei.js @@ -0,0 +1,37 @@ +// 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 BigNumber from 'bignumber.js'; + +const UNITS = ['wei', 'ada', 'babbage', 'shannon', 'szabo', 'finney', 'ether', 'kether', 'mether', 'gether', 'tether']; + +export function _getUnitMultiplier (unit) { + const position = UNITS.indexOf(unit.toLowerCase()); + + if (position === -1) { + throw new Error(`Unknown unit ${unit} passed to wei formatter`); + } + + return 10 ** (position * 3); +} + +export function fromWei (value, unit = 'ether') { + return new BigNumber(value).div(_getUnitMultiplier(unit)); +} + +export function toWei (value, unit = 'ether') { + return new BigNumber(value).mul(_getUnitMultiplier(unit)); +} diff --git a/js/src/api/util/wei.spec.js b/js/src/api/util/wei.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..4476a4db6106e68ef5b3412dae12ab7772a71e6e --- /dev/null +++ b/js/src/api/util/wei.spec.js @@ -0,0 +1,57 @@ +// 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 { _getUnitMultiplier, fromWei, toWei } from './wei'; + +describe('api/util/wei', () => { + describe('_getUnitMultiplier', () => { + it('returns 10^0 for wei', () => { + expect(_getUnitMultiplier('wei')).to.equal(10 ** 0); + }); + + it('returns 10^15 for finney', () => { + expect(_getUnitMultiplier('finney')).to.equal(10 ** 15); + }); + + it('returns 10^18 for ether', () => { + expect(_getUnitMultiplier('ether')).to.equal(10 ** 18); + }); + + it('throws an error on invalid units', () => { + expect(() => _getUnitMultiplier('invalid')).to.throw(/passed to wei formatter/); + }); + }); + + describe('fromWei', () => { + it('formats into ether when nothing specified', () => { + expect(fromWei('1230000000000000000').toString()).to.equal('1.23'); + }); + + it('formats into finney when specified', () => { + expect(fromWei('1230000000000000000', 'finney').toString()).to.equal('1230'); + }); + }); + + describe('toWei', () => { + it('formats from ether when nothing specified', () => { + expect(toWei(1.23).toString()).to.equal('1230000000000000000'); + }); + + it('formats from finney when specified', () => { + expect(toWei(1230, 'finney').toString()).to.equal('1230000000000000000'); + }); + }); +}); diff --git a/js/src/config.js b/js/src/config.js new file mode 100644 index 0000000000000000000000000000000000000000..5113e9399b68530fe7a9c96e3b2bbf568447271f --- /dev/null +++ b/js/src/config.js @@ -0,0 +1,40 @@ +// 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 LogLevel from 'loglevel'; + +export const LOG_KEYS = { + Balances: { + key: 'balances', + desc: 'Balances fetching' + }, + CertificationsMiddleware: { + key: 'certifications.middleware', + desc: 'Certifications Middleware' + }, + TransferModalStore: { + key: 'modalsTransferStore', + desc: 'Transfer modal MobX store' + }, + Signer: { + key: 'secureApi', + desc: 'The Signer and the Secure API' + } +}; + +export const getLogger = (LOG_KEY) => { + return LogLevel.getLogger(LOG_KEY.key); +}; diff --git a/js/src/contracts/abi/badgereg.json b/js/src/contracts/abi/badgereg.json new file mode 100644 index 0000000000000000000000000000000000000000..6ae56393be3b3c4a091b7fe56e885521d34a57b7 --- /dev/null +++ b/js/src/contracts/abi/badgereg.json @@ -0,0 +1 @@ +[{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_addr","type":"address"},{"name":"_name","type":"bytes32"}],"name":"register","outputs":[{"name":"","type":"bool"}],"payable":true,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"fromName","outputs":[{"name":"id","type":"uint256"},{"name":"addr","type":"address"},{"name":"owner","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"badgeCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_fee","type":"uint256"}],"name":"setFee","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_id","type":"uint256"},{"name":"_key","type":"bytes32"}],"name":"meta","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"drain","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_id","type":"uint256"}],"name":"unregister","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_id","type":"uint256"},{"name":"_newAddr","type":"address"}],"name":"setAddress","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_addr","type":"address"}],"name":"fromAddress","outputs":[{"name":"id","type":"uint256"},{"name":"name","type":"bytes32"},{"name":"owner","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_id","type":"uint256"}],"name":"badge","outputs":[{"name":"addr","type":"address"},{"name":"name","type":"bytes32"},{"name":"owner","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_id","type":"uint256"},{"name":"_key","type":"bytes32"},{"name":"_value","type":"bytes32"}],"name":"setMeta","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_addr","type":"address"},{"name":"_name","type":"bytes32"},{"name":"_owner","type":"address"}],"name":"registerAs","outputs":[{"name":"","type":"bool"}],"payable":true,"type":"function"},{"constant":true,"inputs":[],"name":"fee","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"bytes32"},{"indexed":true,"name":"id","type":"uint256"},{"indexed":false,"name":"addr","type":"address"}],"name":"Registered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"bytes32"},{"indexed":true,"name":"id","type":"uint256"}],"name":"Unregistered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"id","type":"uint256"},{"indexed":true,"name":"key","type":"bytes32"},{"indexed":false,"name":"value","type":"bytes32"}],"name":"MetaChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"id","type":"uint256"},{"indexed":false,"name":"addr","type":"address"}],"name":"AddressChanged","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/basiccoin.json b/js/src/contracts/abi/basiccoin.json new file mode 100644 index 0000000000000000000000000000000000000000..0bdc66666fcb4e4f15ca48dbe128c62474367415 --- /dev/null +++ b/js/src/contracts/abi/basiccoin.json @@ -0,0 +1 @@ +[{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"base","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"inputs":[{"name":"_totalSupply","type":"uint256"},{"name":"_owner","type":"address"}],"type":"constructor"},{"payable":false,"type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","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/basiccoinmanager.json b/js/src/contracts/abi/basiccoinmanager.json new file mode 100644 index 0000000000000000000000000000000000000000..cafe09735c6d4f425e302f60001402d668fad002 --- /dev/null +++ b/js/src/contracts/abi/basiccoinmanager.json @@ -0,0 +1 @@ +[{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"countByOwner","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"count","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"base","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_index","type":"uint256"}],"name":"get","outputs":[{"name":"coin","type":"address"},{"name":"owner","type":"address"},{"name":"tokenreg","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"drain","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_totalSupply","type":"uint256"},{"name":"_tla","type":"string"},{"name":"_name","type":"string"},{"name":"_tokenreg","type":"address"}],"name":"deploy","outputs":[{"name":"","type":"bool"}],"payable":true,"type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_index","type":"uint256"}],"name":"getByOwner","outputs":[{"name":"coin","type":"address"},{"name":"owner","type":"address"},{"name":"tokenreg","type":"address"}],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"tokenreg","type":"address"},{"indexed":true,"name":"coin","type":"address"}],"name":"Created","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/certifier.json b/js/src/contracts/abi/certifier.json new file mode 100644 index 0000000000000000000000000000000000000000..905ddde6cb3346948ddedaaa0f089058d1c5c5f0 --- /dev/null +++ b/js/src/contracts/abi/certifier.json @@ -0,0 +1 @@ +[{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"getAddress","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"getUint","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"}],"name":"certified","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"get","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"}],"name":"Confirmed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"}],"name":"Revoked","type":"event"}] \ No newline at end of file diff --git a/js/src/contracts/abi/consensys-multisig-wallet.json b/js/src/contracts/abi/consensys-multisig-wallet.json new file mode 100644 index 0000000000000000000000000000000000000000..79623637d1c7e051e15aba65ae59c020f9821b20 --- /dev/null +++ b/js/src/contracts/abi/consensys-multisig-wallet.json @@ -0,0 +1,510 @@ +[ + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "uint256" + } + ], + "name": "owners", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "owner", + "type": "address" + } + ], + "name": "removeOwner", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "transactionId", + "type": "uint256" + } + ], + "name": "revokeConfirmation", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "address" + } + ], + "name": "isOwner", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "uint256" + }, + { + "name": "", + "type": "address" + } + ], + "name": "confirmations", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "pending", + "type": "bool" + }, + { + "name": "executed", + "type": "bool" + } + ], + "name": "getTransactionCount", + "outputs": [ + { + "name": "count", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "owner", + "type": "address" + } + ], + "name": "addOwner", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "transactionId", + "type": "uint256" + } + ], + "name": "isConfirmed", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "transactionId", + "type": "uint256" + } + ], + "name": "getConfirmationCount", + "outputs": [ + { + "name": "count", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "uint256" + } + ], + "name": "transactions", + "outputs": [ + { + "name": "destination", + "type": "address" + }, + { + "name": "value", + "type": "uint256" + }, + { + "name": "data", + "type": "bytes" + }, + { + "name": "executed", + "type": "bool" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getOwners", + "outputs": [ + { + "name": "", + "type": "address[]" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "from", + "type": "uint256" + }, + { + "name": "to", + "type": "uint256" + }, + { + "name": "pending", + "type": "bool" + }, + { + "name": "executed", + "type": "bool" + } + ], + "name": "getTransactionIds", + "outputs": [ + { + "name": "_transactionIds", + "type": "uint256[]" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "transactionId", + "type": "uint256" + } + ], + "name": "getConfirmations", + "outputs": [ + { + "name": "_confirmations", + "type": "address[]" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "transactionCount", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_required", + "type": "uint256" + } + ], + "name": "changeRequirement", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "transactionId", + "type": "uint256" + } + ], + "name": "confirmTransaction", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "destination", + "type": "address" + }, + { + "name": "value", + "type": "uint256" + }, + { + "name": "data", + "type": "bytes" + } + ], + "name": "submitTransaction", + "outputs": [ + { + "name": "transactionId", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "MAX_OWNER_COUNT", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "required", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "owner", + "type": "address" + }, + { + "name": "newOwner", + "type": "address" + } + ], + "name": "replaceOwner", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "transactionId", + "type": "uint256" + } + ], + "name": "executeTransaction", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "inputs": [ + { + "name": "_owners", + "type": "address[]" + }, + { + "name": "_required", + "type": "uint256" + } + ], + "payable": false, + "type": "constructor" + }, + { + "payable": true, + "type": "fallback" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "name": "transactionId", + "type": "uint256" + } + ], + "name": "Confirmation", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "name": "transactionId", + "type": "uint256" + } + ], + "name": "Revocation", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "transactionId", + "type": "uint256" + } + ], + "name": "Submission", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "transactionId", + "type": "uint256" + } + ], + "name": "Execution", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "transactionId", + "type": "uint256" + } + ], + "name": "ExecutionFailure", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + } + ], + "name": "Deposit", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "owner", + "type": "address" + } + ], + "name": "OwnerAddition", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "owner", + "type": "address" + } + ], + "name": "OwnerRemoval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "required", + "type": "uint256" + } + ], + "name": "RequirementChange", + "type": "event" + } +] diff --git a/js/src/contracts/abi/dappreg.json b/js/src/contracts/abi/dappreg.json new file mode 100644 index 0000000000000000000000000000000000000000..f6bec35af13d798dd239543bc1b0673ef12a4fdd --- /dev/null +++ b/js/src/contracts/abi/dappreg.json @@ -0,0 +1 @@ +[{"constant":true,"inputs":[{"name":"_id","type":"bytes32"},{"name":"_key","type":"bytes32"}],"name":"meta","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"count","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_id","type":"bytes32"}],"name":"unregister","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_fee","type":"uint256"}],"name":"setFee","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_id","type":"bytes32"}],"name":"get","outputs":[{"name":"id","type":"bytes32"},{"name":"owner","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_id","type":"bytes32"},{"name":"_key","type":"bytes32"},{"name":"_value","type":"bytes32"}],"name":"setMeta","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"drain","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_id","type":"bytes32"},{"name":"_owner","type":"address"}],"name":"setDappOwner","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"fee","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_index","type":"uint256"}],"name":"at","outputs":[{"name":"id","type":"bytes32"},{"name":"owner","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_id","type":"bytes32"}],"name":"register","outputs":[],"payable":true,"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"id","type":"bytes32"},{"indexed":true,"name":"key","type":"bytes32"},{"indexed":false,"name":"value","type":"bytes32"}],"name":"MetaChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"id","type":"bytes32"},{"indexed":true,"name":"owner","type":"address"}],"name":"OwnerChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"id","type":"bytes32"},{"indexed":true,"name":"owner","type":"address"}],"name":"Registered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"id","type":"bytes32"}],"name":"Unregistered","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/eip20.json b/js/src/contracts/abi/eip20.json new file mode 100644 index 0000000000000000000000000000000000000000..6937e28c85b0e428e092fe89ae54c950b6899baa --- /dev/null +++ b/js/src/contracts/abi/eip20.json @@ -0,0 +1,163 @@ +[ + { + "constant": false, + "inputs": [ + { + "name": "_spender", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "name": "success", + "type": "bool" + } + ], + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "name": "total", + "type": "uint256" + } + ], + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_from", + "type": "address" + }, + { + "name": "_to", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "name": "success", + "type": "bool" + } + ], + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_owner", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "name": "balance", + "type": "uint256" + } + ], + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_to", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "name": "success", + "type": "bool" + } + ], + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_owner", + "type": "address" + }, + { + "name": "_spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "name": "remaining", + "type": "uint256" + } + ], + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "from", + "type": "address" + }, + { + "indexed": true, + "name": "to", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + } +] diff --git a/js/src/contracts/abi/email-verification.json b/js/src/contracts/abi/email-verification.json new file mode 100644 index 0000000000000000000000000000000000000000..3bb0fe65004bc729912526b39c6e4d5573bc7288 --- /dev/null +++ b/js/src/contracts/abi/email-verification.json @@ -0,0 +1 @@ +[{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"reverse","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_who","type":"address"},{"name":"_puzzle","type":"bytes32"},{"name":"_emailHash","type":"bytes32"}],"name":"puzzle","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"getAddress","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_emailHash","type":"bytes32"}],"name":"request","outputs":[],"payable":true,"type":"function"},{"constant":false,"inputs":[{"name":"_new","type":"uint256"}],"name":"setFee","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_code","type":"bytes32"}],"name":"confirm","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"drain","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"getUint","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"}],"name":"certified","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"fee","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"get","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"},{"indexed":true,"name":"emailHash","type":"bytes32"}],"name":"Requested","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"},{"indexed":true,"name":"emailHash","type":"bytes32"},{"indexed":false,"name":"puzzle","type":"bytes32"}],"name":"Puzzled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"}],"name":"Confirmed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"}],"name":"Revoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"old","type":"address"},{"indexed":true,"name":"current","type":"address"}],"name":"NewOwner","type":"event"}] \ No newline at end of file diff --git a/js/src/contracts/abi/foundation-multisig-wallet.json b/js/src/contracts/abi/foundation-multisig-wallet.json new file mode 100644 index 0000000000000000000000000000000000000000..752407e62521735187cfb94d932bf37cbd30baef --- /dev/null +++ b/js/src/contracts/abi/foundation-multisig-wallet.json @@ -0,0 +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" + }, + { + "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/abi/gavcoin.json b/js/src/contracts/abi/gavcoin.json new file mode 100644 index 0000000000000000000000000000000000000000..5170326f5a4b58fe3b64ff40b306ae2e297e062a --- /dev/null +++ b/js/src/contracts/abi/gavcoin.json @@ -0,0 +1 @@ +[{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[{"name":"success","type":"bool"}],"type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"success","type":"bool"}],"type":"function"},{"constant":false,"inputs":[{"name":"_who","type":"address"},{"name":"_maxPrice","type":"uint256"}],"name":"buyin","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"remaining","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[{"name":"_price","type":"uint256"},{"name":"_units","type":"uint256"}],"name":"refund","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[],"name":"price","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"remaining","type":"uint256"}],"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"buyer","type":"address"},{"indexed":true,"name":"price","type":"uint256"},{"indexed":true,"name":"amount","type":"uint256"}],"name":"Buyin","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"buyer","type":"address"},{"indexed":true,"name":"price","type":"uint256"},{"indexed":true,"name":"amount","type":"uint256"}],"name":"Refund","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"price","type":"uint256"}],"name":"NewTranch","type":"event"}] diff --git a/js/src/contracts/abi/githubhint.json b/js/src/contracts/abi/githubhint.json new file mode 100644 index 0000000000000000000000000000000000000000..bdbc162e4e516fa8732371a846465d199b1504cd --- /dev/null +++ b/js/src/contracts/abi/githubhint.json @@ -0,0 +1,81 @@ +[ + { + "constant":false, + "inputs":[ + { + "name":"_content", + "type":"bytes32" + }, + { + "name":"_url", + "type":"string" + } + ], + "name":"hintURL", + "outputs":[ + + ], + "type":"function" + }, + { + "constant":false, + "inputs":[ + { + "name":"_content", + "type":"bytes32" + }, + { + "name":"_accountSlashRepo", + "type":"string" + }, + { + "name":"_commit", + "type":"bytes20" + } + ], + "name":"hint", + "outputs":[ + + ], + "type":"function" + }, + { + "constant":true, + "inputs":[ + { + "name":"", + "type":"bytes32" + } + ], + "name":"entries", + "outputs":[ + { + "name":"accountSlashRepo", + "type":"string" + }, + { + "name":"commit", + "type":"bytes20" + }, + { + "name":"owner", + "type":"address" + } + ], + "type":"function" + }, + { + "constant":false, + "inputs":[ + { + "name":"_content", + "type":"bytes32" + } + ], + "name":"unhint", + "outputs":[ + + ], + "type":"function" + } +] diff --git a/js/src/contracts/abi/index.js b/js/src/contracts/abi/index.js new file mode 100644 index 0000000000000000000000000000000000000000..f475cce070b8b813008721987ccaa19c3497be0e --- /dev/null +++ b/js/src/contracts/abi/index.js @@ -0,0 +1,31 @@ +// 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 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 foundationWallet from './foundation-multisig-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 0000000000000000000000000000000000000000..9300697422f5c730aff1d7a802cf6b2e6777b04f --- /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/owned.json b/js/src/contracts/abi/owned.json new file mode 100644 index 0000000000000000000000000000000000000000..ccfeed85d06bc7e2bc78498968eaab93d88fde24 --- /dev/null +++ b/js/src/contracts/abi/owned.json @@ -0,0 +1 @@ +[{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"type":"function"},{"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/registry.json b/js/src/contracts/abi/registry.json new file mode 100644 index 0000000000000000000000000000000000000000..f97dc20c72abad7ffb31de847af50f1a26b4e0af --- /dev/null +++ b/js/src/contracts/abi/registry.json @@ -0,0 +1 @@ +[{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"string"}],"name":"confirmReverse","outputs":[{"name":"success","type":"bool"}],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"}],"name":"reserve","outputs":[{"name":"success","type":"bool"}],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"bytes32"}],"name":"set","outputs":[{"name":"success","type":"bool"}],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"}],"name":"drop","outputs":[{"name":"success","type":"bool"}],"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getAddress","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":false,"inputs":[{"name":"_amount","type":"uint256"}],"name":"setFee","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_to","type":"address"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"reserved","outputs":[{"name":"reserved","type":"bool"}],"type":"function"},{"constant":false,"inputs":[],"name":"drain","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"string"},{"name":"_who","type":"address"}],"name":"proposeReverse","outputs":[{"name":"success","type":"bool"}],"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getUint","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"get","outputs":[{"name":"","type":"bytes32"}],"type":"function"},{"constant":true,"inputs":[],"name":"fee","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"reverse","outputs":[{"name":"","type":"string"}],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"uint256"}],"name":"setUint","outputs":[{"name":"success","type":"bool"}],"type":"function"},{"constant":false,"inputs":[],"name":"removeReverse","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"address"}],"name":"setAddress","outputs":[{"name":"success","type":"bool"}],"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"}] diff --git a/js/src/contracts/abi/registry2.json b/js/src/contracts/abi/registry2.json new file mode 100644 index 0000000000000000000000000000000000000000..922b9b7e24ab05ff864e83fda4ddb124acfc246b --- /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/signaturereg.json b/js/src/contracts/abi/signaturereg.json new file mode 100644 index 0000000000000000000000000000000000000000..a9b109ecd463a4873c3597eec690e20819132ef7 --- /dev/null +++ b/js/src/contracts/abi/signaturereg.json @@ -0,0 +1,128 @@ +[ + { + "constant": false, + "inputs": [ + { + "name": "_new", + "type": "address" + } + ], + "name": "setOwner", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "totalSignatures", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "owner", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "drain", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "bytes4" + } + ], + "name": "entries", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_method", + "type": "string" + } + ], + "name": "register", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "type": "function" + }, + { + "inputs": [], + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "creator", + "type": "address" + }, + { + "indexed": true, + "name": "signature", + "type": "bytes4" + }, + { + "indexed": false, + "name": "method", + "type": "string" + } + ], + "name": "Registered", + "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/sms-verification.json b/js/src/contracts/abi/sms-verification.json new file mode 100644 index 0000000000000000000000000000000000000000..3eb5492a41d893bfc4ba09d63e5879be9f44f826 --- /dev/null +++ b/js/src/contracts/abi/sms-verification.json @@ -0,0 +1 @@ +[{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_who","type":"address"}],"name":"certify","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"request","outputs":[],"payable":true,"type":"function"},{"constant":false,"inputs":[{"name":"_who","type":"address"},{"name":"_puzzle","type":"bytes32"}],"name":"puzzle","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"getAddress","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_new","type":"uint256"}],"name":"setFee","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_who","type":"address"}],"name":"revoke","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_code","type":"bytes32"}],"name":"confirm","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"drain","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"delegate","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"getUint","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setDelegate","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"}],"name":"certified","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"fee","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"get","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"}],"name":"Requested","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"},{"indexed":false,"name":"puzzle","type":"bytes32"}],"name":"Puzzled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"}],"name":"Confirmed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"}],"name":"Revoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"old","type":"address"},{"indexed":true,"name":"current","type":"address"}],"name":"NewOwner","type":"event"}] \ No newline at end of file diff --git a/js/src/contracts/abi/tokenreg.json b/js/src/contracts/abi/tokenreg.json new file mode 100644 index 0000000000000000000000000000000000000000..e56a13eec48aa4dc1ae9f558b3a080c24174c065 --- /dev/null +++ b/js/src/contracts/abi/tokenreg.json @@ -0,0 +1 @@ +[{"constant":true,"inputs":[{"name":"_id","type":"uint256"}],"name":"token","outputs":[{"name":"addr","type":"address"},{"name":"tla","type":"string"},{"name":"base","type":"uint256"},{"name":"name","type":"string"},{"name":"owner","type":"address"}],"type":"function"},{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_addr","type":"address"},{"name":"_tla","type":"string"},{"name":"_base","type":"uint256"},{"name":"_name","type":"string"}],"name":"register","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":false,"inputs":[{"name":"_fee","type":"uint256"}],"name":"setFee","outputs":[],"type":"function"},{"constant":true,"inputs":[{"name":"_id","type":"uint256"},{"name":"_key","type":"bytes32"}],"name":"meta","outputs":[{"name":"","type":"bytes32"}],"type":"function"},{"constant":false,"inputs":[{"name":"_addr","type":"address"},{"name":"_tla","type":"string"},{"name":"_base","type":"uint256"},{"name":"_name","type":"string"},{"name":"_owner","type":"address"}],"name":"registerAs","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":true,"inputs":[{"name":"_tla","type":"string"}],"name":"fromTLA","outputs":[{"name":"id","type":"uint256"},{"name":"addr","type":"address"},{"name":"base","type":"uint256"},{"name":"name","type":"string"},{"name":"owner","type":"address"}],"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":false,"inputs":[],"name":"drain","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"tokenCount","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[{"name":"_id","type":"uint256"}],"name":"unregister","outputs":[],"type":"function"},{"constant":true,"inputs":[{"name":"_addr","type":"address"}],"name":"fromAddress","outputs":[{"name":"id","type":"uint256"},{"name":"tla","type":"string"},{"name":"base","type":"uint256"},{"name":"name","type":"string"},{"name":"owner","type":"address"}],"type":"function"},{"constant":false,"inputs":[{"name":"_id","type":"uint256"},{"name":"_key","type":"bytes32"},{"name":"_value","type":"bytes32"}],"name":"setMeta","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"fee","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"tla","type":"string"},{"indexed":true,"name":"id","type":"uint256"},{"indexed":false,"name":"addr","type":"address"},{"indexed":false,"name":"name","type":"string"}],"name":"Registered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"tla","type":"string"},{"indexed":true,"name":"id","type":"uint256"}],"name":"Unregistered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"id","type":"uint256"},{"indexed":true,"name":"key","type":"bytes32"},{"indexed":false,"name":"value","type":"bytes32"}],"name":"MetaChanged","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/badgereg.js b/js/src/contracts/badgereg.js new file mode 100644 index 0000000000000000000000000000000000000000..a1782cb30174443013ffcad17f7b00f9c9dec50c --- /dev/null +++ b/js/src/contracts/badgereg.js @@ -0,0 +1,128 @@ +// 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 { bytesToHex, hexToAscii } from '~/api/util/format'; + +import ABI from './abi/certifier.json'; + +const ZERO20 = '0x0000000000000000000000000000000000000000'; +const ZERO32 = '0x0000000000000000000000000000000000000000000000000000000000000000'; + +export default class BadgeReg { + constructor (api, registry) { + this._api = api; + this._registry = registry; + + registry.getContract('badgereg'); + this.certifiers = []; // by id + this.contracts = {}; // by name + } + + getContract () { + return this._registry.getContract('badgereg'); + } + + certifierCount () { + return this + .getContract() + .then((badgeReg) => { + return badgeReg.instance.badgeCount.call({}, []) + .then((count) => count.valueOf()); + }); + } + + fetchCertifier (id) { + if (this.certifiers[id]) { + return Promise.resolve(this.certifiers[id]); + } + + return this + .getContract() + .then((badgeReg) => { + return badgeReg.instance.badge.call({}, [ id ]); + }) + .then(([ address, name ]) => { + if (address === ZERO20) { + throw new Error(`Certifier ${id} does not exist.`); + } + + name = bytesToHex(name); + name = name === ZERO32 + ? null + : hexToAscii(name); + + return this.fetchMeta(id) + .then(({ title, icon }) => { + const data = { address, id, name, title, icon }; + + this.certifiers[id] = data; + return data; + }); + }); + } + + fetchCertifierByName (name) { + return this + .getContract() + .then((badgeReg) => { + return badgeReg.instance.fromName.call({}, [ name ]); + }) + .then(([ id, address, owner ]) => { + if (address === ZERO20) { + throw new Error(`Certifier ${name} does not exist.`); + } + + return this.fetchMeta(id) + .then(({ title, icon }) => { + const data = { address, id, name, title, icon }; + + this.certifiers[id] = data; + return data; + }); + }); + } + + fetchMeta (id) { + return this + .getContract() + .then((badgeReg) => { + return Promise.all([ + badgeReg.instance.meta.call({}, [id, 'TITLE']), + badgeReg.instance.meta.call({}, [id, 'IMG']) + ]); + }) + .then(([ title, icon ]) => { + title = bytesToHex(title); + title = title === ZERO32 ? null : hexToAscii(title); + + if (bytesToHex(icon) === ZERO32) { + icon = null; + } + + return { title, icon }; + }); + } + + checkIfCertified (certifier, address) { + if (!this.contracts[certifier]) { + this.contracts[certifier] = this._api.newContract(ABI, certifier); + } + + const contract = this.contracts[certifier]; + + return contract.instance.certified.call({}, [address]); + } +} diff --git a/js/src/contracts/code/index.js b/js/src/contracts/code/index.js new file mode 100644 index 0000000000000000000000000000000000000000..1081c9afa4752209a12e5035b1ecba24de7cf7f2 --- /dev/null +++ b/js/src/contracts/code/index.js @@ -0,0 +1,21 @@ +// 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 { wallet } from './wallet'; + +export { + wallet +}; diff --git a/js/src/contracts/code/wallet.js b/js/src/contracts/code/wallet.js new file mode 100644 index 0000000000000000000000000000000000000000..ab60bfc9831c2df74c905372da3ad5f482baacb5 --- /dev/null +++ b/js/src/contracts/code/wallet.js @@ -0,0 +1,32 @@ +// 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 . + +/** + * @version Solidity v0.4.10 - Optimized + * @from https://github.com/paritytech/parity/blob/6b0e4f9098be6b841353e7c4f116aa86b7c2e3d6/js/src/contracts/snippets/enhanced-wallet.sol + * @date 20-Jul-2017 @ 16h00 UTC + */ +export const walletCompiler = 'v0.4.10+commit.f0d539ae'; +export const walletSource = 'https://github.com/paritytech/parity/blob/c4196a5de31e0b97ec42d5263d9db404cea4a776/js/src/contracts/snippets/enhanced-wallet.sol'; +export const wallet = '0x6060604052341561000c57fe5b60405161048538038061048583398101604090815281516020830151918301519201915b604080517f696e697457616c6c657428616464726573735b5d2c75696e743235362c75696e81527f7432353629000000000000000000000000000000000000000000000000000000602080830191909152915190819003602501902084516000829052909173_____________WalletLibrary______________91600281019160049182010290819038829003903960006000600483016000866127105a03f45b505050505050505b61039d806100e86000396000f300606060405236156100725763ffffffff60e060020a6000350416632f54bf6e811461012d5780634123cb6b1461015d578063523750931461017f578063659010e7146101a1578063746c9171146101c3578063c2cf7326146101e5578063c41a360a14610218578063f1736d8614610247575b61012b5b60003411156100c75760408051600160a060020a033316815234602082015281517fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c929181900390910190a1610127565b60003611156101275773_____________WalletLibrary______________600160a060020a0316600036600060405160200152604051808383808284378201915050925050506020604051808303818560325a03f4151561012457fe5b50505b5b5b565b005b341561013557fe5b610149600160a060020a0360043516610269565b604080519115158252519081900360200190f35b341561016557fe5b61016d6102cd565b60408051918252519081900360200190f35b341561018757fe5b61016d6102d3565b60408051918252519081900360200190f35b34156101a957fe5b61016d6102d9565b60408051918252519081900360200190f35b34156101cb57fe5b61016d6102df565b60408051918252519081900360200190f35b34156101ed57fe5b610149600435600160a060020a03602435166102e5565b604080519115158252519081900360200190f35b341561022057fe5b61022b60043561034a565b60408051600160a060020a039092168252519081900360200190f35b341561024f57fe5b61016d61036b565b60408051918252519081900360200190f35b600073_____________WalletLibrary______________600160a060020a0316600036600060405160200152604051808383808284378201915050925050506020604051808303818560325a03f415156102bf57fe5b50506040515190505b919050565b60015481565b60045481565b60035481565b60005481565b600073_____________WalletLibrary______________600160a060020a0316600036600060405160200152604051808383808284378201915050925050506020604051808303818560325a03f4151561033b57fe5b50506040515190505b92915050565b6000600560018301610100811061035d57fe5b0160005b505490505b919050565b600254815600a165627a7a72305820c20a8475c42598c198f6629bada37e1b234da85ac2c0cbac3d96089030b180400029'; +export const walletLibrary = '0x6060604052341561000c57fe5b5b61170f8061001c6000396000f300606060405236156101015763ffffffff60e060020a600035041663173825d981146101575780632f54bf6e146101755780634123cb6b146101a557806352375093146101c75780635c52c2f5146101e9578063659010e7146101fb5780637065cb481461021d578063746c91711461023b578063797af6271461025d5780639da5e0eb14610284578063b20d30a914610299578063b61d27f6146102ae578063b75c7dc6146102ec578063ba51a6df14610301578063c2cf732614610316578063c41a360a14610349578063c57c5f6014610378578063cbf0b0c0146103cf578063e46dcfeb146103ed578063f00d4b5d14610449578063f1736d861461046d575b6101555b60003411156101525760408051600160a060020a033316815234602082015281517fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c929181900390910190a15b5b565b005b341561015f57fe5b610155600160a060020a036004351661048f565b005b341561017d57fe5b610191600160a060020a036004351661057d565b604080519115158252519081900360200190f35b34156101ad57fe5b6101b561059e565b60408051918252519081900360200190f35b34156101cf57fe5b6101b56105a4565b60408051918252519081900360200190f35b34156101f157fe5b6101556105aa565b005b341561020357fe5b6101b56105e1565b60408051918252519081900360200190f35b341561022557fe5b610155600160a060020a03600435166105e7565b005b341561024357fe5b6101b56106d7565b60408051918252519081900360200190f35b341561026557fe5b6101916004356106dd565b604080519115158252519081900360200190f35b341561028c57fe5b610155600435610a30565b005b34156102a157fe5b610155600435610a56565b005b34156102b657fe5b6101b560048035600160a060020a0316906024803591604435918201910135610a8e565b60408051918252519081900360200190f35b34156102f457fe5b610155600435610d71565b005b341561030957fe5b610155600435610e1c565b005b341561031e57fe5b610191600435600160a060020a0360243516610e9e565b604080519115158252519081900360200190f35b341561035157fe5b61035c600435610ef3565b60408051600160a060020a039092168252519081900360200190f35b341561038057fe5b6101556004808035906020019082018035906020019080806020026020016040519081016040528093929190818152602001838360200280828437509496505093359350610f1492505050565b005b34156103d757fe5b610155600160a060020a0360043516610ff9565b005b34156103f557fe5b6101556004808035906020019082018035906020019080806020026020016040519081016040528093929190818152602001838360200280828437509496505084359460200135935061103792505050565b005b341561045157fe5b610155600160a060020a0360043581169060243516611062565b005b341561047557fe5b6101b561115c565b60408051918252519081900360200190f35b60006000366040518083838082843782019150509250505060405180910390206104b881611162565b1561057657600160a060020a0383166000908152610105602052604090205491508115156104e557610576565b60016001540360005411156104f957610576565b6000600583610100811061050957fe5b0160005b5055600160a060020a038316600090815261010560205260408120556105316112cd565b6105396113bd565b60408051600160a060020a038516815290517f58619076adf5bb0943d100ef88d52d7c3fd691b19d3a9071b555b651fbf418da9181900360200190a15b5b5b505050565b600160a060020a03811660009081526101056020526040812054115b919050565b60015481565b60045481565b6000366040518083838082843782019150509250505060405180910390206105d181611162565b156105dc5760006003555b5b5b50565b60035481565b60003660405180838380828437820191505092505050604051809103902061060e81611162565b156106d15761061c8261057d565b15610626576106d1565b61062e6112cd565b60015460fa9010610641576106416113bd565b5b60015460fa9010610652576106d1565b60018054810190819055600160a060020a03831690600590610100811061067557fe5b0160005b5055600154600160a060020a03831660008181526101056020908152604091829020939093558051918252517f994a936646fe87ffe4f1e469d3d6aa417d6b855598397f323de5b449f765f0c3929181900390910190a15b5b5b5050565b60005481565b60006000826106eb81611162565b15610a265760008481526101086020526040902054600160a060020a031615158061072757506000848152610108602052604090206001015415155b80610754575060008481526101086020526040902060029081015461010060018216150260001901160415155b15610a265760008481526101086020526040902054600160a060020a0316151561082c57600084815261010860209081526040918290206001808201546002928301805486516000199482161561010002949094011693909304601f810185900485028301850190955284825261082594909391929183018282801561081b5780601f106107f05761010080835404028352916020019161081b565b820191906000526020600020905b8154815290600101906020018083116107fe57829003601f168201915b50505050506114f9565b91506108e4565b60008481526101086020526040908190208054600180830154935160029384018054600160a060020a0390941695949093919283928592918116156101000260001901160480156108be5780601f10610893576101008083540402835291602001916108be565b820191906000526020600020905b8154815290600101906020018083116108a157829003601f168201915b505091505060006040518083038185876185025a03f19250505015156108e45760006000fd5b5b6000848152610108602090815260409182902060018082015482548551600160a060020a033381811683529682018c90529681018390529086166060820181905295881660a082015260c06080820181815260029586018054958616156101000260001901909516959095049082018190527fe3a3a4111a84df27d76b68dc721e65c7711605ea5eee4afd3a9c58195217365c968b959394909390928a9290919060e0830190859080156109da5780601f106109af576101008083540402835291602001916109da565b820191906000526020600020905b8154815290600101906020018083116109bd57829003601f168201915b505097505050505050505060405180910390a16000848152610108602052604081208054600160a060020a03191681556001810182905590610a1f600283018261158e565b5050600192505b5b5b5b5050919050565b60006001541115610a415760006000fd5b6002819055610a4e611513565b6004555b5b50565b600036604051808383808284378201915050925050506040518091039020610a7d81611162565b156106d15760028290555b5b5b5050565b60006000610a9b3361057d565b15610d645782158015610ab25750610ab285611522565b5b80610ac057506000546001145b15610c0357600160a060020a0386161515610b1657610b0f8585858080601f016020809104026020016040519081016040528093929190818152602001838380828437506114f9945050505050565b9050610b57565b85600160a060020a03168585856040518083838082843782019150509250505060006040518083038185876185025a03f1925050501515610b575760006000fd5b5b7f9738cd1a8777c86b011f7b01d87d484217dc6ab5154a9d41eda5d14af8caf2923386888787866040518087600160a060020a0316600160a060020a0316815260200186815260200185600160a060020a0316600160a060020a031681526020018060200183600160a060020a0316600160a060020a0316815260200182810382528585828181526020019250808284376040519201829003995090975050505050505050a1610d64565b600036436040518084848082843791909101928352505060408051602092819003830190206000818152610108909352912054909450600160a060020a0316159150508015610c62575060008281526101086020526040902060010154155b8015610c8f5750600082815261010860205260409020600290810154610100600182161502600019011604155b15610cd3576000828152610108602052604090208054600160a060020a031916600160a060020a03881617815560018101869055610cd19060020185856115d6565b505b610cdc826106dd565b1515610d645760408051838152600160a060020a033381811660208401529282018890528816606082015260a0608082018181529082018690527f1733cbb53659d713b79580f79f3f9ff215f78a7c7aa45890f3b89fc5cddfbf32928592909189918b918a918a9160c082018484808284376040519201829003995090975050505050505050a15b5b5b5b5b50949350505050565b600160a060020a033316600090815261010560205260408120549080821515610d9957610e15565b50506000828152610106602052604081206001810154600284900a929083161115610e155780546001908101825581018054839003905560408051600160a060020a03331681526020810186905281517fc7fb647e59b18047309aa15aad418e5d7ca96d173ad704f1031a2c3d7591734b929181900390910190a15b5b50505050565b600036604051808383808284378201915050925050506040518091039020610e4381611162565b156106d157600154821115610e57576106d1565b6000829055610e646112cd565b6040805183815290517facbdb084c721332ac59f9b8e392196c9eb0e4932862da8eb9beaf0dad4f550da9181900360200190a15b5b5b5050565b600082815261010660209081526040808320600160a060020a038516845261010590925282205482811515610ed65760009350610eea565b8160020a9050808360010154166000141593505b50505092915050565b60006005600183016101008110610f0657fe5b0160005b505490505b919050565b600060006001541115610f275760006000fd5b825160019081018155600160a060020a033316906005905b0160005b505550600160a060020a033316600090815261010560205260408120600190555b8251811015610fed578281815181101515610f7b57fe5b60209081029091010151600160a060020a03166005600283016101008110610f9f57fe5b0160005b50819055508060020161010560008584815181101515610fbf57fe5b90602001906020020151600160a060020a03168152602001908152602001600020819055505b600101610f64565b60008290555b5b505050565b60003660405180838380828437820191505092505050604051809103902061102081611162565b156106d15781600160a060020a0316ff5b5b5b5050565b600060015411156110485760006000fd5b61105181610a30565b6105768383610f14565b5b5b505050565b600060003660405180838380828437820191505092505050604051809103902061108b81611162565b15610e15576110998361057d565b156110a357610e15565b600160a060020a0384166000908152610105602052604090205491508115156110cb57610e15565b6110d36112cd565b600160a060020a03831660058361010081106110eb57fe5b0160005b5055600160a060020a0380851660008181526101056020908152604080832083905593871680835291849020869055835192835282015281517fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c929181900390910190a15b5b5b50505050565b60025481565b600160a060020a03331660009081526101056020526040812054818082151561118a576112c3565b600085815261010660205260409020805490925015156111ed576000805483556001808401919091556101078054916111c591908301611655565b60028301819055610107805487929081106111dc57fe5b906000526020600020900160005b50555b8260020a905080826001015416600014156112c35760408051600160a060020a03331681526020810187905281517fe1c52dc63b719ade82e8bea94cc41a0d5d28e4aaf536adb5e9cccc9ff8c1aeda929181900390910190a18154600190116112b057600085815261010660205260409020600201546101078054909190811061127357fe5b906000526020600020900160005b5060009081905585815261010660205260408120818155600180820183905560029091019190915593506112c3565b8154600019018255600182018054821790555b5b5b505050919050565b6101075460005b818110156113ab576101086000610107838154811015156112f157fe5b906000526020600020900160005b50548152602081019190915260400160009081208054600160a060020a03191681556001810182905590611336600283018261158e565b505061010780548290811061134757fe5b906000526020600020900160005b5054156113a25761010660006101078381548110151561137157fe5b906000526020600020900160005b505481526020810191909152604001600090812081815560018101829055600201555b5b6001016112d4565b6106d1610107600061167f565b5b5050565b60015b6001548110156105dc575b600154811080156113ee575060058161010081106113e557fe5b0160005b505415155b156113fb576001016113cb565b5b60016001541180156114225750600154600590610100811061141a57fe5b0160005b5054155b1561143657600180546000190190556113fb565b6001548110801561145c5750600154600590610100811061145357fe5b0160005b505415155b80156114795750600581610100811061147157fe5b0160005b5054155b156114f057600154600590610100811061148f57fe5b0160005b505460058261010081106114a357fe5b0160005b505580610105600060058361010081106114bd57fe5b0160005b505481526020019081526020016000208190555060006005600154610100811015156114e957fe5b0160005b50555b6113c0565b5b50565b600081516020830184f09050803b15610000575b92915050565b600062015180425b0490505b90565b600061152d3361057d565b156105995760045461153d611513565b1115611554576000600355611550611513565b6004555b6003548281011080159061156e5750600254826003540111155b15611583575060038054820190556001610599565b5060005b5b5b919050565b50805460018160011615610100020316600290046000825580601f106115b457506105dc565b601f0160209004906000526020600020908101906105dc91906116a1565b5b50565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106116175782800160ff19823516178555611644565b82800160010185558215611644579182015b82811115611644578235825591602001919060010190611629565b5b506116519291506116a1565b5090565b815481835581811511610576576000838152602090206105769181019083016116a1565b5b505050565b50805460008255906000526020600020908101906105dc91906116a1565b5b50565b61151f91905b8082111561165157600081556001016116a7565b5090565b90565b61151f91905b8082111561165157600081556001016116a7565b5090565b905600a165627a7a723058209a3af51063f944081cd5ee8601e4e655f25cf7bc219a7983cda60f163f1709a70029'; +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/paritytech/parity/blob/6b0e4f9098be6b841353e7c4f116aa86b7c2e3d6/js/src/contracts/snippets/enhanced-wallet.sol'; +export const walletLibraryRegKey = 'walletLibrary.v.2'; + +// Used if no Wallet Library found in registry... +// Compiled from `wallet.sol` using Solidity v0.4.9 - Optimized +export const fullWalletCode = '0x6060604052341561000c57fe5b60405161166d38038061166d83398101604090815281516020830151918301519201915b805b83835b815160019081018155600090600160a060020a033316906002905b0160005b505550600160a060020a033316600090815261010260205260408120600190555b82518110156100fd57828181518110151561008c57fe5b60209081029091010151600160a060020a0316600282810161010081106100af57fe5b0160005b508190555080600201610102600085848151811015156100cf57fe5b90602001906020020151600160a060020a03168152602001908152602001600020819055505b600101610075565b60008290555b50505061010581905561012164010000000061138f61013082021704565b610107555b505b50505061013f565b600062015180425b0490505b90565b61151f8061014e6000396000f300606060405236156100e05763ffffffff60e060020a600035041663173825d981146101365780632f54bf6e146101545780634123cb6b1461018457806352375093146101a65780635c52c2f5146101c8578063659010e7146101da5780637065cb48146101fc578063746c91711461021a578063797af6271461023c578063b20d30a914610263578063b61d27f614610278578063b75c7dc6146102b6578063ba51a6df146102cb578063c2cf7326146102e0578063c41a360a14610313578063cbf0b0c014610342578063f00d4b5d14610360578063f1736d8614610384575b6101345b60003411156101315760408051600160a060020a033316815234602082015281517fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c929181900390910190a15b5b565b005b341561013e57fe5b610134600160a060020a03600435166103a6565b005b341561015c57fe5b610170600160a060020a0360043516610494565b604080519115158252519081900360200190f35b341561018c57fe5b6101946104b5565b60408051918252519081900360200190f35b34156101ae57fe5b6101946104bb565b60408051918252519081900360200190f35b34156101d057fe5b6101346104c2565b005b34156101e257fe5b6101946104fa565b60408051918252519081900360200190f35b341561020457fe5b610134600160a060020a0360043516610501565b005b341561022257fe5b6101946105f1565b60408051918252519081900360200190f35b341561024457fe5b6101706004356105f7565b604080519115158252519081900360200190f35b341561026b57fe5b610134600435610949565b005b341561028057fe5b61019460048035600160a060020a0316906024803591604435918201910135610982565b60408051918252519081900360200190f35b34156102be57fe5b610134600435610c64565b005b34156102d357fe5b610134600435610d0f565b005b34156102e857fe5b610170600435600160a060020a0360243516610d91565b604080519115158252519081900360200190f35b341561031b57fe5b610326600435610de6565b60408051600160a060020a039092168252519081900360200190f35b341561034a57fe5b610134600160a060020a0360043516610e07565b005b341561036857fe5b610134600160a060020a0360043581169060243516610e45565b005b341561038c57fe5b610194610f3f565b60408051918252519081900360200190f35b60006000366040518083838082843782019150509250505060405180910390206103cf81610f46565b1561048d57600160a060020a0383166000908152610102602052604090205491508115156103fc5761048d565b60016001540360005411156104105761048d565b6000600283610100811061042057fe5b0160005b5055600160a060020a038316600090815261010260205260408120556104486110b1565b610450611132565b60408051600160a060020a038516815290517f58619076adf5bb0943d100ef88d52d7c3fd691b19d3a9071b555b651fbf418da9181900360200190a15b5b5b505050565b600160a060020a03811660009081526101026020526040812054115b919050565b60015481565b6101075481565b6000366040518083838082843782019150509250505060405180910390206104e981610f46565b156104f5576000610106555b5b5b50565b6101065481565b60003660405180838380828437820191505092505050604051809103902061052881610f46565b156105eb5761053682610494565b15610540576105eb565b6105486110b1565b60015460fa901061055b5761055b611132565b5b60015460fa901061056c576105eb565b60018054810190819055600160a060020a03831690600290610100811061058f57fe5b0160005b5055600154600160a060020a03831660008181526101026020908152604091829020939093558051918252517f994a936646fe87ffe4f1e469d3d6aa417d6b855598397f323de5b449f765f0c3929181900390910190a15b5b5b5050565b60005481565b600060008261060581610f46565b1561093f5760008481526101086020526040902054600160a060020a031615158061064157506000848152610108602052604090206001015415155b8061066e575060008481526101086020526040902060029081015461010060018216150260001901160415155b1561093f5760008481526101086020526040902054600160a060020a0316151561074657600084815261010860209081526040918290206001808201546002928301805486516000199482161561010002949094011693909304601f810185900485028301850190955284825261073f9490939192918301828280156107355780601f1061070a57610100808354040283529160200191610735565b820191906000526020600020905b81548152906001019060200180831161071857829003601f168201915b505050505061126e565b91506107fd565b60008481526101086020526040908190208054600180830154935160029384018054600160a060020a0390941695949093919283928592918116156101000260001901160480156107d85780601f106107ad576101008083540402835291602001916107d8565b820191906000526020600020905b8154815290600101906020018083116107bb57829003601f168201915b505091505060006040518083038185876185025a03f19250505015156107fd57610000565b5b6000848152610108602090815260409182902060018082015482548551600160a060020a033381811683529682018c90529681018390529086166060820181905295881660a082015260c06080820181815260029586018054958616156101000260001901909516959095049082018190527fe3a3a4111a84df27d76b68dc721e65c7711605ea5eee4afd3a9c58195217365c968b959394909390928a9290919060e0830190859080156108f35780601f106108c8576101008083540402835291602001916108f3565b820191906000526020600020905b8154815290600101906020018083116108d657829003601f168201915b505097505050505050505060405180910390a16000848152610108602052604081208054600160a060020a03191681556001810182905590610938600283018261139e565b5050600192505b5b5b5b5050919050565b60003660405180838380828437820191505092505050604051809103902061097081610f46565b156105eb576101058290555b5b5b5050565b6000600061098f33610494565b15610c5757821580156109a657506109a685611288565b5b806109b457506000546001145b15610af657600160a060020a0386161515610a0a57610a038585858080601f0160208091040260200160405190810160405280939291908181526020018383808284375061126e945050505050565b9050610a4a565b85600160a060020a03168585856040518083838082843782019150509250505060006040518083038185876185025a03f1925050501515610a4a57610000565b5b7f9738cd1a8777c86b011f7b01d87d484217dc6ab5154a9d41eda5d14af8caf2923386888787866040518087600160a060020a0316600160a060020a0316815260200186815260200185600160a060020a0316600160a060020a031681526020018060200183600160a060020a0316600160a060020a0316815260200182810382528585828181526020019250808284376040519201829003995090975050505050505050a1610c57565b600036436040518084848082843791909101928352505060408051602092819003830190206000818152610108909352912054909450600160a060020a0316159150508015610b55575060008281526101086020526040902060010154155b8015610b825750600082815261010860205260409020600290810154610100600182161502600019011604155b15610bc6576000828152610108602052604090208054600160a060020a031916600160a060020a03881617815560018101869055610bc49060020185856113e6565b505b610bcf826105f7565b1515610c575760408051838152600160a060020a033381811660208401529282018890528816606082015260a0608082018181529082018690527f1733cbb53659d713b79580f79f3f9ff215f78a7c7aa45890f3b89fc5cddfbf32928592909189918b918a918a9160c082018484808284376040519201829003995090975050505050505050a15b5b5b5b5b50949350505050565b600160a060020a033316600090815261010260205260408120549080821515610c8c57610d08565b50506000828152610103602052604081206001810154600284900a929083161115610d085780546001908101825581018054839003905560408051600160a060020a03331681526020810186905281517fc7fb647e59b18047309aa15aad418e5d7ca96d173ad704f1031a2c3d7591734b929181900390910190a15b5b50505050565b600036604051808383808284378201915050925050506040518091039020610d3681610f46565b156105eb57600154821115610d4a576105eb565b6000829055610d576110b1565b6040805183815290517facbdb084c721332ac59f9b8e392196c9eb0e4932862da8eb9beaf0dad4f550da9181900360200190a15b5b5b5050565b600082815261010360209081526040808320600160a060020a038516845261010290925282205482811515610dc95760009350610ddd565b8160020a9050808360010154166000141593505b50505092915050565b60006002600183016101008110610df957fe5b0160005b505490505b919050565b600036604051808383808284378201915050925050506040518091039020610e2e81610f46565b156105eb5781600160a060020a0316ff5b5b5b5050565b6000600036604051808383808284378201915050925050506040518091039020610e6e81610f46565b15610d0857610e7c83610494565b15610e8657610d08565b600160a060020a038416600090815261010260205260409020549150811515610eae57610d08565b610eb66110b1565b600160a060020a0383166002836101008110610ece57fe5b0160005b5055600160a060020a0380851660008181526101026020908152604080832083905593871680835291849020869055835192835282015281517fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c929181900390910190a15b5b5b50505050565b6101055481565b600160a060020a033316600090815261010260205260408120548180821515610f6e576110a7565b60008581526101036020526040902080549092501515610fd157600080548355600180840191909155610104805491610fa991908301611465565b6002830181905561010480548792908110610fc057fe5b906000526020600020900160005b50555b8260020a905080826001015416600014156110a75760408051600160a060020a03331681526020810187905281517fe1c52dc63b719ade82e8bea94cc41a0d5d28e4aaf536adb5e9cccc9ff8c1aeda929181900390910190a181546001901161109457600085815261010360205260409020600201546101048054909190811061105757fe5b906000526020600020900160005b5060009081905585815261010360205260408120818155600180820183905560029091019190915593506110a7565b8154600019018255600182018054821790555b5b5b505050919050565b6101045460005b81811015611125576101086000610104838154811015156110d557fe5b906000526020600020900160005b50548152602081019190915260400160009081208054600160a060020a0319168155600181018290559061111a600283018261139e565b50505b6001016110b8565b6105eb6112fb565b5b5050565b60015b6001548110156104f5575b600154811080156111635750600281610100811061115a57fe5b0160005b505415155b1561117057600101611140565b5b60016001541180156111975750600154600290610100811061118f57fe5b0160005b5054155b156111ab5760018054600019019055611170565b600154811080156111d1575060015460029061010081106111c857fe5b0160005b505415155b80156111ee575060028161010081106111e657fe5b0160005b5054155b1561126557600154600290610100811061120457fe5b0160005b5054600282610100811061121857fe5b0160005b5055806101026000600283610100811061123257fe5b0160005b5054815260200190815260200160002081905550600060026001546101008110151561125e57fe5b0160005b50555b611135565b5b50565b600081516020830184f09050803b15610000575b92915050565b600061129333610494565b156104b057610107546112a461138f565b11156112bd576000610106556112b861138f565b610107555b61010654828101108015906112da57506101055482610106540111155b156112f0575061010680548201905560016104b0565b5060005b5b5b919050565b6101045460005b8181101561137d5761010480548290811061131957fe5b906000526020600020900160005b5054156113745761010360006101048381548110151561134357fe5b906000526020600020900160005b505481526020810191909152604001600090812081815560018101829055600201555b5b600101611302565b6105eb610104600061148f565b5b5050565b600062015180425b0490505b90565b50805460018160011615610100020316600290046000825580601f106113c457506104f5565b601f0160209004906000526020600020908101906104f591906114b1565b5b50565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106114275782800160ff19823516178555611454565b82800160010185558215611454579182015b82811115611454578235825591602001919060010190611439565b5b506114619291506114b1565b5090565b81548183558181151161048d5760008381526020902061048d9181019083016114b1565b5b505050565b50805460008255906000526020600020908101906104f591906114b1565b5b50565b61139b91905b8082111561146157600081556001016114b7565b5090565b90565b61139b91905b8082111561146157600081556001016114b7565b5090565b905600a165627a7a723058203a7ac7072dc640002704b704af82b742650362cd55debf72fca105c2b916e01d0029'; diff --git a/js/src/contracts/contracts.js b/js/src/contracts/contracts.js new file mode 100644 index 0000000000000000000000000000000000000000..59b3bccfc3e69e7c2439ec18487f1c63d058b990 --- /dev/null +++ b/js/src/contracts/contracts.js @@ -0,0 +1,83 @@ +// 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 DappReg from './dappreg'; +import Registry from './registry'; +import SignatureReg from './signaturereg'; +import TokenReg from './tokenreg'; +import GithubHint from './githubhint'; +import * as verification from './verification'; +import BadgeReg from './badgereg'; + +let instance = null; + +export default class Contracts { + constructor (api) { + instance = this; + + this._api = api; + this._registry = new Registry(api); + this._dappreg = new DappReg(api, this._registry); + this._signaturereg = new SignatureReg(api, this._registry); + this._tokenreg = new TokenReg(api, this._registry); + this._githubhint = new GithubHint(api, this._registry); + this._badgeReg = new BadgeReg(api, this._registry); + } + + get registry () { + return this._registry; + } + + get badgeReg () { + return this._badgeReg; + } + + get dappReg () { + return this._dappreg; + } + + get signatureReg () { + return this._signaturereg; + } + + get tokenReg () { + return this._tokenreg; + } + + get githubHint () { + return this._githubhint; + } + + get smsVerification () { + return verification; + } + + get emailVerification () { + return verification; + } + + static create (api) { + if (instance) { + return instance; + } + + return new Contracts(api); + } + + static get () { + return instance; + } +} diff --git a/js/src/contracts/dappreg.js b/js/src/contracts/dappreg.js new file mode 100644 index 0000000000000000000000000000000000000000..18af9c1635eb6b9b60e6d4c0e52f38bdd782f168 --- /dev/null +++ b/js/src/contracts/dappreg.js @@ -0,0 +1,68 @@ +// 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 class DappReg { + constructor (api, registry) { + this._api = api; + this._registry = registry; + + this.getInstance(); + } + + getContract () { + return this._registry.getContract('dappreg'); + } + + getInstance () { + return this.getContract().then((contract) => contract.instance); + } + + count () { + return this.getInstance().then((instance) => { + return instance.count.call(); + }); + } + + at (index) { + return this.getInstance().then((instance) => { + return instance.at.call({}, [index]); + }); + } + + get (id) { + return this.getInstance().then((instance) => { + return instance.get.call({}, [id]); + }); + } + + meta (id, key) { + return this.getInstance().then((instance) => { + return instance.meta.call({}, [id, key]); + }); + } + + getImage (id) { + return this.meta(id, 'IMG'); + } + + getContent (id) { + return this.meta(id, 'CONTENT'); + } + + getManifest (id) { + return this.meta(id, 'MANIFEST'); + } +} diff --git a/js/src/contracts/githubhint.js b/js/src/contracts/githubhint.js new file mode 100644 index 0000000000000000000000000000000000000000..dfc3d5fb7e866daff48b0d6b495751a393f3b636 --- /dev/null +++ b/js/src/contracts/githubhint.js @@ -0,0 +1,46 @@ +// 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 class GithubHint { + constructor (api, registry) { + this._api = api; + this._registry = registry; + this._instance = null; + + this.getInstance(); + } + + getContract () { + return this._registry.getContract('githubhint'); + } + + getInstance () { + if (this._instance) { + return Promise.resolve(this._instance); + } + + return this.getContract().then((contract) => { + this._instance = contract.instance; + return this._instance; + }); + } + + getEntry (entryId) { + return this.getInstance().then((instance) => { + return instance.entries.call({}, [entryId]); + }); + } +} diff --git a/js/src/contracts/githubhint.spec.js b/js/src/contracts/githubhint.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..f3385078237633bd0fdca77cb73f10bc6b188046 --- /dev/null +++ b/js/src/contracts/githubhint.spec.js @@ -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 . + +import sinon from 'sinon'; + +import GithubHint from './githubhint'; + +let githubHint; +let instance; +let registry; + +function create () { + instance = { + __id: 'testInstance', + entries: { + call: sinon.stub().resolves('testValue') + } + }; + registry = { + getContract: sinon.stub().resolves({ instance }) + }; + githubHint = new GithubHint({}, registry); + + return githubHint; +} + +describe('contracts/GithubHint', () => { + beforeEach(() => { + create(); + + return githubHint.getInstance(); + }); + + it('instantiates successfully', () => { + expect(githubHint).to.be.ok; + }); + + it('attaches the instance on create', () => { + expect(githubHint._instance.__id).to.equal('testInstance'); + }); + + describe('interface', () => { + describe('getEntry', () => { + beforeEach(() => { + return githubHint.getEntry('testId'); + }); + + it('calls entries on the instance', () => { + expect(instance.entries.call).to.have.been.calledWith({}, ['testId']); + }); + }); + }); +}); diff --git a/js/src/contracts/index.js b/js/src/contracts/index.js new file mode 100644 index 0000000000000000000000000000000000000000..8714ff544deb1aa555d2086247c09fc8d99532de --- /dev/null +++ b/js/src/contracts/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 './contracts'; diff --git a/js/src/contracts/registry.js b/js/src/contracts/registry.js new file mode 100644 index 0000000000000000000000000000000000000000..cef31785e061210b8094fbee20e827259123eb14 --- /dev/null +++ b/js/src/contracts/registry.js @@ -0,0 +1,165 @@ +// 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 * as abis from './abi'; + +const REGISTRY_V1_HASHES = [ + '0x34f7c51bbb1b1902fbdabfdf04811100f5c9f998f26dd535d2f6f977492c748e', // ropsten + '0x64c3ee34851517a9faecd995c102b339f03e564ad6772dc43a26f993238b20ec' // homestead +]; + +export default class Registry { + _registryContract = null; + + constructor (api) { + this._api = api; + + this._contracts = {}; + this._pendingContracts = {}; + + this._instance = null; + this._fetching = false; + this._queue = []; + + this.getInstance(); + } + + getInstance () { + if (this._instance) { + return Promise.resolve(this._instance); + } + + if (this._fetching) { + return new Promise((resolve) => { + this._queue.push({ resolve }); + }); + } + + this._fetching = true; + + return this.fetchContract() + .then((contract) => { + this._fetching = false; + this._instance = contract.instance; + + this._queue.forEach((queued) => { + queued.resolve(this._instance); + }); + + this._queue = []; + + return this._instance; + }); + } + + getContract (_name) { + const name = _name.toLowerCase(); + + if (this._contracts[name]) { + return Promise.resolve(this._contracts[name]); + } + + if (this._pendingContracts[name]) { + return this._pendingContracts[name]; + } + + const promise = this + .lookupAddress(name) + .then((address) => { + this._contracts[name] = this._api.newContract(abis[name], address); + delete this._pendingContracts[name]; + return this._contracts[name]; + }); + + this._pendingContracts[name] = promise; + + return promise; + } + + getContractInstance (_name) { + return this + .getContract(_name) + .then((contract) => contract.instance); + } + + fetchContract () { + if (this._registryContract) { + return Promise.resolve(this._registryContract); + } + + return this._api.parity + .registryAddress() + .then((address) => Promise.all([ address, this._api.eth.getCode(address) ])) + .then(([ address, code ]) => { + const codeHash = this._api.util.sha3(code); + const version = REGISTRY_V1_HASHES.includes(codeHash) + ? 1 + : 2; + const abi = version === 1 + ? abis.registry + : abis.registry2; + const contract = this._api.newContract(abi, address); + + // Add support for previous `set` and `get` methods + if (!contract.instance.get && contract.instance.getData) { + contract.instance.get = contract.instance.getData; + } + + if (contract.instance.get && !contract.instance.getData) { + contract.instance.getData = contract.instance.get; + } + + if (!contract.instance.set && contract.instance.setData) { + contract.instance.set = contract.instance.setData; + } + + if (contract.instance.set && !contract.instance.setData) { + contract.instance.setData = contract.instance.set; + } + + console.log(`registry at ${address}, code ${codeHash}, version ${version}`); + this._registryContract = contract; + return this._registryContract; + }); + } + + _createGetParams (_name, key) { + const name = _name.toLowerCase(); + const sha3 = this._api.util.sha3.text(name); + + return [sha3, key]; + } + + lookupAddress (name) { + return this + .getInstance() + .then((instance) => { + return instance.getAddress.call({}, this._createGetParams(name, 'A')); + }) + .then((address) => { + console.log('[lookupAddress]', `${name}: ${address}`); + return address; + }); + } + + lookupMeta (name, key) { + return this + .getInstance() + .then((instance) => { + return instance.get.call({}, this._createGetParams(name, key)); + }); + } +} diff --git a/js/src/contracts/registry.spec.js b/js/src/contracts/registry.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..cf81a2b7210ab9d3b2e29a46147c68df371b8f19 --- /dev/null +++ b/js/src/contracts/registry.spec.js @@ -0,0 +1,86 @@ +// 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 apiutil from '~/api/util'; + +import Registry from './registry'; + +const GHH_NAME = 'githubhint'; +const GHH_SHA3 = '0x058740ee9a5a3fb9f1cfa10752baec87e09cc45cd7027fd54708271aca300c75'; + +let api; +let instance; +let registry; + +function create () { + instance = { + __id: 'testInstance', + get: { + call: sinon.stub().resolves('testGet') + } + }; + api = { + eth: { + getCode: sinon.stub().resolves('0x123456') + }, + parity: { + registryAddress: sinon.stub().resolves('testRegistryAddress') + }, + util: apiutil, + newContract: sinon.stub().returns({ instance }) + }; + registry = new Registry(api); + + return registry; +} + +describe('contracts/Registry', () => { + beforeEach(() => { + create(); + + return registry.getInstance(); + }); + + it('instantiates successfully', () => { + expect(registry).to.be.ok; + }); + + it('retrieves the registry on create', () => { + expect(api.parity.registryAddress).to.have.been.called; + }); + + it('attaches the instance on create', () => { + expect(registry._instance.__id).to.equal('testInstance'); + }); + + describe('interface', () => { + describe('lookupMeta', () => { + it('calls get on the contract', () => { + return registry.lookupMeta(GHH_NAME, 'key').then(() => { + expect(instance.get.call).to.have.been.calledWith({}, [GHH_SHA3, 'key']); + }); + }); + + it('converts names to lowercase', () => { + return registry.lookupMeta(GHH_NAME.toUpperCase(), 'key').then(() => { + expect(instance.get.call).to.have.been.calledWith({}, [GHH_SHA3, 'key']); + }); + }); + }); + }); +}); diff --git a/js/src/contracts/signaturereg.js b/js/src/contracts/signaturereg.js new file mode 100644 index 0000000000000000000000000000000000000000..9fa3eab069f639194c8bd04b5cf03fdc6aebd1c0 --- /dev/null +++ b/js/src/contracts/signaturereg.js @@ -0,0 +1,34 @@ +// 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 class SignatureReg { + constructor (api, registry) { + this._api = api; + this._registry = registry; + + this.getInstance(); + } + + getInstance () { + return this._registry.getContractInstance('signaturereg'); + } + + lookup (signature) { + return this.getInstance().then((instance) => { + return instance.entries.call({}, [signature]); + }); + } +} diff --git a/js/src/contracts/snippets/enhanced-wallet.sol b/js/src/contracts/snippets/enhanced-wallet.sol new file mode 100644 index 0000000000000000000000000000000000000000..90a15c070ba26db27c4190a6287f6c34446d123f --- /dev/null +++ b/js/src/contracts/snippets/enhanced-wallet.sol @@ -0,0 +1,464 @@ +//sol Wallet +// Multi-sig, daily-limited account proxy/wallet. +// @authors: +// Gav Wood +// inheritable "property" contract that enables methods to be protected by requiring the acquiescence of either a +// single, or, crucially, each of a number of, designated owners. +// usage: +// 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.9; + +contract WalletEvents { + // 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); + + // 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 WalletAbi { + // Revokes a prior confirmation of the given operation + function revoke(bytes32 _operation) external; + + // Replaces an owner `_from` with another `_to`. + function changeOwner(address _from, address _to) external; + + function addOwner(address _owner) external; + + function removeOwner(address _owner) external; + + function changeRequirement(uint _newRequired) external; + + function isOwner(address _addr) constant returns (bool); + + function hasConfirmed(bytes32 _operation, address _owner) external constant returns (bool); + + // (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 execute(address _to, uint _value, bytes _data) external returns (bytes32 o_hash); + function confirm(bytes32 _h) returns (bool o_success); +} + +contract WalletLibrary is WalletEvents { + // TYPES + + // 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) only_uninitialized { + 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) only_uninitialized { + 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; + } + + // throw unless the contract is not yet initialized. + modifier only_uninitialized { if (m_numOwners > 0) throw; _; } + + // constructor - just pass on the owner array to the multiowned and + // the limit to daylimit + function initWallet(address[] _owners, uint _required, uint _daylimit) only_uninitialized { + 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]]; + } + + delete m_pendingIndex; + } + + // 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; + + 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; +} + +contract Wallet is WalletEvents { + + // 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; + + 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 + + // 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]); + } + + // As return statement unavailable in fallback, explicit the method here + + function hasConfirmed(bytes32 _operation, address _owner) external constant returns (bool) { + return _walletLibrary.delegatecall(msg.data); + } + + function isOwner(address _addr) constant returns (bool) { + return _walletLibrary.delegatecall(msg.data); + } + + // 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; + + uint public m_dailyLimit; + uint public m_spentToday; + uint public m_lastDay; + + // list of owners + uint[256] m_owners; +} diff --git a/js/src/contracts/snippets/human-standard-token.sol b/js/src/contracts/snippets/human-standard-token.sol new file mode 100644 index 0000000000000000000000000000000000000000..db05bbc7df9fdbc78b40629c31a79eae1acf0ba2 --- /dev/null +++ b/js/src/contracts/snippets/human-standard-token.sol @@ -0,0 +1,60 @@ +/* +This Token Contract implements the standard token functionality (https://github.com/ethereum/EIPs/issues/20) as well as the following OPTIONAL extras intended for use by humans. + +In other words. This is intended for deployment in something like a Token Factory or Mist wallet, and then used by humans. +Imagine coins, currencies, shares, voting weight, etc. +Machine-based, rapid creation of many tokens would not necessarily need these extra features or will be minted in other manners. + +1) Initial Finite Supply (upon creation one specifies how much is minted). +2) In the absence of a token registry: Optional Decimal, Symbol & Name. +3) Optional approveAndCall() functionality to notify a contract if an approval() has occurred. + +.*/ + +import "StandardToken.sol"; + +contract HumanStandardToken is StandardToken { + + function () { + //if ether is sent to this address, send it back. + throw; + } + + /* Public variables of the token */ + + /* + NOTE: + The following variables are OPTIONAL vanities. One does not have to include them. + They allow one to customise the token contract & in no way influences the core functionality. + Some wallets/interfaces might not even bother to look at this information. + */ + string public name; //fancy name: eg Simon Bucks + uint8 public decimals; //How many decimals to show. ie. There could 1000 base units with 3 decimals. Meaning 0.980 SBX = 980 base units. It's like comparing 1 wei to 1 ether. + string public symbol; //An identifier: eg SBX + string public version = 'H0.1'; //human 0.1 standard. Just an arbitrary versioning scheme. + + function HumanStandardToken( + uint256 _initialAmount, + string _tokenName, + uint8 _decimalUnits, + string _tokenSymbol + ) { + balances[msg.sender] = _initialAmount; // Give the creator all initial tokens + totalSupply = _initialAmount; // Update total supply + name = _tokenName; // Set the name for display purposes + decimals = _decimalUnits; // Amount of decimals for display purposes + symbol = _tokenSymbol; // Set the symbol for display purposes + } + + /* Approves and then calls the receiving contract */ + function approveAndCall(address _spender, uint256 _value, bytes _extraData) returns (bool success) { + allowed[msg.sender][_spender] = _value; + Approval(msg.sender, _spender, _value); + + //call the receiveApproval function on the contract you want to be notified. This crafts the function signature manually so one doesn't have to include a contract in here just for this. + //receiveApproval(address _from, uint256 _value, address _tokenContract, bytes _extraData) + //it is assumed that when does this that the call *should* succeed, otherwise one would use vanilla approve instead. + if(!_spender.call(bytes4(bytes32(sha3("receiveApproval(address,uint256,address,bytes)"))), msg.sender, _value, this, _extraData)) { throw; } + return true; + } +} diff --git a/js/src/contracts/snippets/standard-token.sol b/js/src/contracts/snippets/standard-token.sol new file mode 100644 index 0000000000000000000000000000000000000000..3d91e5510d2956b13bbb46a979fddea53647af2d --- /dev/null +++ b/js/src/contracts/snippets/standard-token.sol @@ -0,0 +1,55 @@ +/* +You should inherit from StandardToken or, for a token like you would want to +deploy in something like Mist, see HumanStandardToken.sol. +(This implements ONLY the standard functions and NOTHING else. +If you deploy this, you won't have anything useful.) + +Implements ERC 20 Token standard: https://github.com/ethereum/EIPs/issues/20 +.*/ + +import "Token.sol"; + +contract StandardToken is Token { + + function transfer(address _to, uint256 _value) returns (bool success) { + //Default assumes totalSupply can't be over max (2^256 - 1). + //If your token leaves out totalSupply and can issue more tokens as time goes on, you need to check if it doesn't wrap. + //Replace the if with this one instead. + //if (balances[msg.sender] >= _value && balances[_to] + _value > balances[_to]) { + if (balances[msg.sender] >= _value && _value > 0) { + balances[msg.sender] -= _value; + balances[_to] += _value; + Transfer(msg.sender, _to, _value); + return true; + } else { return false; } + } + + function transferFrom(address _from, address _to, uint256 _value) returns (bool success) { + //same as above. Replace this line with the following if you want to protect against wrapping uints. + //if (balances[_from] >= _value && allowed[_from][msg.sender] >= _value && balances[_to] + _value > balances[_to]) { + if (balances[_from] >= _value && allowed[_from][msg.sender] >= _value && _value > 0) { + balances[_to] += _value; + balances[_from] -= _value; + allowed[_from][msg.sender] -= _value; + Transfer(_from, _to, _value); + return true; + } else { return false; } + } + + function balanceOf(address _owner) constant returns (uint256 balance) { + return balances[_owner]; + } + + function approve(address _spender, uint256 _value) returns (bool success) { + allowed[msg.sender][_spender] = _value; + Approval(msg.sender, _spender, _value); + return true; + } + + function allowance(address _owner, address _spender) constant returns (uint256 remaining) { + return allowed[_owner][_spender]; + } + + mapping (address => uint256) balances; + mapping (address => mapping (address => uint256)) allowed; +} diff --git a/js/src/contracts/snippets/token.sol b/js/src/contracts/snippets/token.sol new file mode 100644 index 0000000000000000000000000000000000000000..d54c5c42434c10a36bfafc80cf6781b28896a40b --- /dev/null +++ b/js/src/contracts/snippets/token.sol @@ -0,0 +1,47 @@ +// Abstract contract for the full ERC 20 Token standard +// https://github.com/ethereum/EIPs/issues/20 + +contract Token { + /* This is a slight change to the ERC20 base standard. + function totalSupply() constant returns (uint256 supply); + is replaced with: + uint256 public totalSupply; + This automatically creates a getter function for the totalSupply. + This is moved to the base contract since public getter functions are not + currently recognised as an implementation of the matching abstract + function by the compiler. + */ + /// total amount of tokens + uint256 public totalSupply; + + /// @param _owner The address from which the balance will be retrieved + /// @return The balance + function balanceOf(address _owner) constant returns (uint256 balance); + + /// @notice send `_value` token to `_to` from `msg.sender` + /// @param _to The address of the recipient + /// @param _value The amount of token to be transferred + /// @return Whether the transfer was successful or not + function transfer(address _to, uint256 _value) returns (bool success); + + /// @notice send `_value` token to `_to` from `_from` on the condition it is approved by `_from` + /// @param _from The address of the sender + /// @param _to The address of the recipient + /// @param _value The amount of token to be transferred + /// @return Whether the transfer was successful or not + function transferFrom(address _from, address _to, uint256 _value) returns (bool success); + + /// @notice `msg.sender` approves `_addr` to spend `_value` tokens + /// @param _spender The address of the account able to transfer the tokens + /// @param _value The amount of wei to be approved for transfer + /// @return Whether the approval was successful or not + function approve(address _spender, uint256 _value) returns (bool success); + + /// @param _owner The address of the account owning tokens + /// @param _spender The address of the account able to transfer the tokens + /// @return Amount of remaining tokens allowed to spent + function allowance(address _owner, address _spender) constant returns (uint256 remaining); + + event Transfer(address indexed _from, address indexed _to, uint256 _value); + event Approval(address indexed _owner, address indexed _spender, uint256 _value); +} diff --git a/js/src/contracts/snippets/wallet.sol b/js/src/contracts/snippets/wallet.sol new file mode 100644 index 0000000000000000000000000000000000000000..a8f1ba93872c20c659978347eff0130480ee51d6 --- /dev/null +++ b/js/src/contracts/snippets/wallet.sol @@ -0,0 +1,403 @@ +//sol Wallet +// Multi-sig, daily-limited account proxy/wallet. +// @authors: +// Gav Wood +// inheritable "property" contract that enables methods to be protected by requiring the acquiescence of either a +// single, or, crucially, each of a number of, designated owners. +// usage: +// 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.9; + +contract multiowned { + + // TYPES + + // 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; + } + + // 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) 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; +} + +// inheritable "property" contract that enables methods to be protected by placing a linear limit (specifiable) +// on a particular resource per calendar day. is multiowned to allow the limit to be altered. resource that method +// uses is specified in the modifier. +contract daylimit is multiowned { + + // 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; + } + + // 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; } + + // FIELDS + + 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 + + // 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 + + // TODO: document + function execute(address _to, uint _value, bytes _data) external returns (bytes32 o_hash); + function confirm(bytes32 _h) returns (bool o_success); +} + +// usage: +// bytes32 h = Wallet(w).from(oneOwner).execute(to, value, data); +// Wallet(w).from(anotherOwner).confirm(h); +contract Wallet is multisig, multiowned, daylimit { + + // 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; + } + + // 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); + } + } + } + + 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 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; +} diff --git a/js/src/contracts/tokenreg.js b/js/src/contracts/tokenreg.js new file mode 100644 index 0000000000000000000000000000000000000000..2bbf639bfd342d1c29739a5e612b9b398853726b --- /dev/null +++ b/js/src/contracts/tokenreg.js @@ -0,0 +1,44 @@ +// 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 class TokenReg { + constructor (api, registry) { + this._api = api; + this._registry = registry; + + this.getInstance(); + } + + getContract () { + return this._registry.getContract('tokenreg'); + } + + getInstance () { + return this.getContract().then((contract) => contract.instance); + } + + tokenCount () { + return this.getInstance().then((instance) => { + return instance.tokenCount.call(); + }); + } + + token (index) { + return this.getInstance().then((instance) => { + return instance.token.call({}, [index]); + }); + } +} diff --git a/js/src/contracts/verification.js b/js/src/contracts/verification.js new file mode 100644 index 0000000000000000000000000000000000000000..8101565dbdbeb748b4e39065ec7b778e91f7bec6 --- /dev/null +++ b/js/src/contracts/verification.js @@ -0,0 +1,88 @@ +// 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 subscribeToEvents from '../util/subscribe-to-events'; + +export const checkIfVerified = (contract, account) => { + return contract.instance.certified.call({}, [account]); +}; + +export const findLastRequested = (contract, account) => { + let subId = null; + let resolved = false; + + return new Promise((resolve, reject) => { + contract + .subscribe('Requested', { + fromBlock: 0, + toBlock: 'pending', + limit: 1, + topics: [account] + }, (err, logs) => { + if (err) { + return reject(err); + } + + resolve(logs[0] || null); + resolved = true; + + if (subId) { + contract.unsubscribe(subId); + } + }) + .then((_subId) => { + subId = _subId; + + if (resolved) { + contract.unsubscribe(subId); + } + }); + }); +}; + +const blockNumber = (api) => { + return new Promise((resolve, reject) => { + api.subscribe('eth_blockNumber', (err, block) => { + if (err) { + return reject(err); + } + resolve(block); + }) + .then((subscription) => { + api.unsubscribe(subscription); + }) + .catch(reject); + }); +}; + +export const awaitPuzzle = (api, contract, account) => { + return blockNumber(api) + .then((block) => { + return new Promise((resolve, reject) => { + const subscription = subscribeToEvents(contract, ['Puzzled'], { + from: block.toNumber(), + filter: (log) => log.params.who.value === account + }); + + subscription.once('error', reject); + subscription.once('log', subscription.unsubscribe); + subscription.once('log', resolve); + subscription.once('timeout', () => { + reject(new Error('Timed out waiting for the puzzle.')); + }); + }); + }); +}; diff --git a/js/src/dapps/chaindeploy.js b/js/src/dapps/chaindeploy.js new file mode 100644 index 0000000000000000000000000000000000000000..c3dd420085af151b3623b9f19947b058f88038fa --- /dev/null +++ b/js/src/dapps/chaindeploy.js @@ -0,0 +1,29 @@ +// 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 ReactDOM from 'react-dom'; +import React from 'react'; + +import Application from './chaindeploy/Application'; + +import '../../assets/fonts/Roboto/font.css'; +import '../../assets/fonts/RobotoMono/font.css'; +import './style.css'; + +ReactDOM.render( + , + document.querySelector('#container') +); diff --git a/js/src/dapps/chaindeploy/Application/application.css b/js/src/dapps/chaindeploy/Application/application.css new file mode 100644 index 0000000000000000000000000000000000000000..f6db5da2919079ca82b17ae52f0487a79aa877bb --- /dev/null +++ b/js/src/dapps/chaindeploy/Application/application.css @@ -0,0 +1,78 @@ +/* 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 . +*/ + +.body { + font-size: 0.8em; + + .buttons { + position: fixed; + right: 0.5em; + top: 0.5em; + z-index: 100; + + button { + background: rgb(0, 200, 255); + border: 2px solid rgba(0, 200, 255, 0.5); + border-radius: 0.25em; + color: white; + cursor: pointer; + font-size: 1em; + margin: 0 0 0 0.5em; + outline: none; + padding: 0.75em 1.5em; + white-space: nowrap; + + &:disabled { + background: rgb(230, 230, 230); + border: 2px solid rgba(230, 230, 230, 0.5); + cursor: default; + } + + .icon { + fill: white !important; + margin-right: 0.5em; + } + + .text { + display: inline-block; + line-height: 24px; + vertical-align: top; + } + } + } + + .section { + margin: 0 1em 3em 1em; + + h3 { + background: rgba(0, 0, 0, 0.025); + border-radius: 0.25em; + padding: 0.75em 1em; + margin-bottom: 0.5em; + + small { + vertical-align: middle; + } + } + + .list { + display: flex; + flex-direction: row; + flex-wrap: wrap; + } + } +} diff --git a/js/src/dapps/chaindeploy/Application/application.js b/js/src/dapps/chaindeploy/Application/application.js new file mode 100644 index 0000000000000000000000000000000000000000..9e6fa588f6de03da37d62a746b6009735198642d --- /dev/null +++ b/js/src/dapps/chaindeploy/Application/application.js @@ -0,0 +1,178 @@ +// 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 } from 'react'; + +import Contract from '../Contract'; +import Dapp from '../Dapp'; +import Store from '../store'; + +import styles from './application.css'; + +@observer +export default class Application extends Component { + store = new Store(); + + render () { + return ( +
+ { this.renderContracts(false) } + { this.renderContracts(true) } + { this.renderApps() } + { this.renderContracts(false, true) } + { this.renderApps(true) } + { this.renderButtons() } +
+ ); + } + + renderButton (text, clickHandler, disabled) { + const onClick = (event) => { + if (!disabled) { + clickHandler(event); + } + }; + + return ( + + ); + } + + renderButtons () { + const { contractBadgereg, contractDappreg, isBadgeDeploying, isContractDeploying, isDappDeploying, haveAllBadges, haveAllContracts, haveAllDapps, registry } = this.store; + const disableRegistry = registry.address || registry.isDeploying; + const disableContracts = !registry.address || isContractDeploying || haveAllContracts; + const disableDapps = !contractDappreg.address || isDappDeploying || haveAllDapps; + const disableBadges = !registry.address || !contractBadgereg.address || isBadgeDeploying || haveAllBadges; + + return ( +
+ { this.renderButton('registry', this.deployRegistry, disableRegistry) } + { this.renderButton('contracts', this.deployContracts, disableContracts) } + { this.renderButton('badges', this.deployBadges, disableBadges) } + { this.renderButton('apps', this.deployApps, disableDapps) } +
+ ); + } + + renderContracts (isBadges, isExternal) { + const { badges, contracts, contractBadgereg, registry } = this.store; + const regaddress = isBadges + ? contractBadgereg.address + : registry.address; + + return ( +
+

+ { + isExternal + ? 'External ' + : '' + }{ + isBadges + ? 'Badges ' + : 'Contracts ' + }(registry { regaddress || 'unknown' }) +

+
+ { + isExternal || isBadges + ? null + : ( + + ) + } + { + (isBadges ? badges : contracts) + .filter((contract) => contract.isExternal === isExternal) + .map((contract) => { + return ( + + ); + }) + } +
+
+ ); + } + + renderApps (isExternal) { + const { apps, contractDappreg, contractGithubhint } = this.store; + const isDisabled = !contractDappreg.isOnChain || !contractGithubhint.isOnChain; + + return ( +
+

+ { + isExternal + ? 'External ' + : '' + }Applications (registry { + contractDappreg.address + ? contractDappreg.address + : 'unknown' + }) +

+
+ { + apps + .filter((app) => app.isExternal === isExternal) + .map((app) => { + return ( + + ); + }) + } +
+
+ ); + } + + deployApps = () => { + return this.store.deployApps(); + } + + deployBadges = () => { + return this.store.deployBadges(); + } + + deployContracts = () => { + return this.store.deployContracts(); + } + + deployRegistry = () => { + return this.store.deployRegistry(); + } +} diff --git a/js/src/dapps/chaindeploy/Application/index.js b/js/src/dapps/chaindeploy/Application/index.js new file mode 100644 index 0000000000000000000000000000000000000000..3d8d1ca3b851e5bfed73373e82eff06b5d9ea46c --- /dev/null +++ b/js/src/dapps/chaindeploy/Application/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 './application'; diff --git a/js/src/dapps/chaindeploy/Contract/contract.js b/js/src/dapps/chaindeploy/Contract/contract.js new file mode 100644 index 0000000000000000000000000000000000000000..1f0b158d2b49ee843a5a415038874b1e4ee29b13 --- /dev/null +++ b/js/src/dapps/chaindeploy/Contract/contract.js @@ -0,0 +1,132 @@ +// 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 ListItem, { Header, Row } from '../ListItem'; + +export default class Contract extends Component { + static propTypes = { + contract: PropTypes.object.isRequired, + disabled: PropTypes.bool + } + + render () { + const { contract, disabled } = this.props; + const location = contract.id === 'registry' + ? 'chain' + : 'registry'; + + return ( + +
+ { contract.id } was { + contract.address + ? 'deployed' + : 'not found' + } +
+ + { + contract.address + ? contract.address + : 'no address' + } + + + { + contract.hasLatestCode + ? 'has latest available code' + : 'does not have latest code' + } + + + { + contract.isOnChain + ? `registered on ${location}` + : `not registered on ${location}` + } + + { this.renderBadgeInfo() } +
+ ); + } + + renderBadgeInfo () { + const { contract } = this.props; + + if (!contract.isBadge) { + return null; + } + + return [ + + { + contract.isBadgeRegistered + ? 'found in badgereg' + : 'not found in badgereg' + } + , + + { + contract.badgeImageHash + ? `badge imageHash ${contract.badgeImageHash}` + : 'has not registered a badge imageHash' + } + , + + { + contract.badgeImageMatch + ? 'has latest badge imageHash' + : 'does not have latest badge imageHash' + } + + ]; + } +} diff --git a/js/src/dapps/chaindeploy/Contract/index.js b/js/src/dapps/chaindeploy/Contract/index.js new file mode 100644 index 0000000000000000000000000000000000000000..74c58e942d4f23ba7637a544b403a1e1f88ffb37 --- /dev/null +++ b/js/src/dapps/chaindeploy/Contract/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 './contract'; diff --git a/js/src/dapps/chaindeploy/Dapp/dapp.js b/js/src/dapps/chaindeploy/Dapp/dapp.js new file mode 100644 index 0000000000000000000000000000000000000000..5a997c295afd827273421839d6194e45ec5189d8 --- /dev/null +++ b/js/src/dapps/chaindeploy/Dapp/dapp.js @@ -0,0 +1,98 @@ +// 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 ListItem, { Header, Row } from '../ListItem'; + +export default class Dapp extends Component { + static propTypes = { + dapp: PropTypes.object.isRequired, + disabled: PropTypes.bool + } + + render () { + const { dapp, disabled } = this.props; + + return ( + +
+ { dapp.name } +
+ + { + dapp.isOnChain + ? 'found in dappreg' + : 'not found in dappreg' + } + + { this.renderHash(dapp, 'image') } + { this.renderHash(dapp, 'manifest') } + { this.renderHash(dapp, 'content') } +
+ ); + } + + renderHash (dapp, type) { + if (!dapp.source[`${type}Hash`]) { + return null; + } + + const isMatch = dapp[`${type}Match`]; + const hash = dapp[`${type}Hash`]; + + return [ + + { + hash + ? `${type}Hash ${hash}` + : `has not registered an ${type}Hash` + } + , + + { + isMatch + ? `has latest ${type}Hash` + : `does not have latest ${type}Hash` + } + + ]; + } +} diff --git a/js/src/dapps/chaindeploy/Dapp/index.js b/js/src/dapps/chaindeploy/Dapp/index.js new file mode 100644 index 0000000000000000000000000000000000000000..3d32813894b4befb9df2b554942ed8998f7bc49b --- /dev/null +++ b/js/src/dapps/chaindeploy/Dapp/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 './dapp'; diff --git a/js/src/dapps/chaindeploy/ListItem/Header/header.js b/js/src/dapps/chaindeploy/ListItem/Header/header.js new file mode 100644 index 0000000000000000000000000000000000000000..e9be01eeab406226b8ef37fcb3762736ffa2f53f --- /dev/null +++ b/js/src/dapps/chaindeploy/ListItem/Header/header.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 . + +import React, { Component, PropTypes } from 'react'; + +import Icon from '../Icon'; + +import styles from '../listItem.css'; + +export default class Header extends Component { + static propTypes = { + children: PropTypes.node.isRequired, + isBusy: PropTypes.bool, + isOk: PropTypes.bool + } + + render () { + const { children, isBusy, isOk } = this.props; + + return ( +
+ +
+ { children } +
+
+ ); + } +} diff --git a/js/src/dapps/chaindeploy/ListItem/Header/index.js b/js/src/dapps/chaindeploy/ListItem/Header/index.js new file mode 100644 index 0000000000000000000000000000000000000000..aef90266f940a3c7f8915c7ba910b62bec54317c --- /dev/null +++ b/js/src/dapps/chaindeploy/ListItem/Header/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 './header'; diff --git a/js/src/dapps/chaindeploy/ListItem/Icon/icon.js b/js/src/dapps/chaindeploy/ListItem/Icon/icon.js new file mode 100644 index 0000000000000000000000000000000000000000..1f32622e54c4d013edc0e6f26dc4d9a893ab2f03 --- /dev/null +++ b/js/src/dapps/chaindeploy/ListItem/Icon/icon.js @@ -0,0 +1,53 @@ +// 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 styles from '../listItem.css'; + +export default class Icon extends Component { + static propTypes = { + isBusy: PropTypes.bool, + isOk: PropTypes.bool + } + + render () { + const { isBusy, isOk } = this.props; + + return ( +
+ { + isOk + ? '\u2714' + : ( + isBusy + ? '\u29d6' + : '\u2716' + ) + } +
+ ); + } +} diff --git a/js/src/dapps/chaindeploy/ListItem/Icon/index.js b/js/src/dapps/chaindeploy/ListItem/Icon/index.js new file mode 100644 index 0000000000000000000000000000000000000000..f43e02c49782af1cde49703f410b789d056b29d0 --- /dev/null +++ b/js/src/dapps/chaindeploy/ListItem/Icon/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 './icon'; diff --git a/js/src/dapps/chaindeploy/ListItem/Row/index.js b/js/src/dapps/chaindeploy/ListItem/Row/index.js new file mode 100644 index 0000000000000000000000000000000000000000..5f2c62dee40946e0794e10be34bbc9374435c2e2 --- /dev/null +++ b/js/src/dapps/chaindeploy/ListItem/Row/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 './row'; diff --git a/js/src/dapps/chaindeploy/ListItem/Row/row.js b/js/src/dapps/chaindeploy/ListItem/Row/row.js new file mode 100644 index 0000000000000000000000000000000000000000..0982e0321b0c1a1cb2dd49da3945152c9392ebf6 --- /dev/null +++ b/js/src/dapps/chaindeploy/ListItem/Row/row.js @@ -0,0 +1,54 @@ +// 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 Icon from '../Icon'; + +import styles from '../listItem.css'; + +export default class Row extends Component { + static propTypes = { + children: PropTypes.node.isRequired, + disabled: PropTypes.bool, + isBusy: PropTypes.bool, + isOk: PropTypes.bool + } + + render () { + const { children, disabled, isBusy, isOk } = this.props; + + return ( +
+ +
+ { children } +
+
+ ); + } +} diff --git a/js/src/dapps/chaindeploy/ListItem/index.js b/js/src/dapps/chaindeploy/ListItem/index.js new file mode 100644 index 0000000000000000000000000000000000000000..7c79e3241c49c0215f637097706c87f375616988 --- /dev/null +++ b/js/src/dapps/chaindeploy/ListItem/index.js @@ -0,0 +1,20 @@ +// 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 Header from './Header'; +export Row from './Row'; + +export default from './listItem'; diff --git a/js/src/dapps/chaindeploy/ListItem/listItem.css b/js/src/dapps/chaindeploy/ListItem/listItem.css new file mode 100644 index 0000000000000000000000000000000000000000..47d6d481d27b69e206eea6e3931043360642a4d0 --- /dev/null +++ b/js/src/dapps/chaindeploy/ListItem/listItem.css @@ -0,0 +1,94 @@ +/* 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 . +*/ + +.listItem { + box-sizing: border-box; + flex: 0 1 33.33%; + max-width: 33.33%; + padding: 0.5em; + position: relative; + + .body { + background: rgba(0, 0, 0, 0.025); + border-radius: 0.25em; + box-sizing: border-box; + display: flex; + flex-direction: column; + flex-wrap: nowrap; + overflow: hidden; + padding: 0.75em; + } + + .status { + background: #f80; + border-radius: 0.25em; + color: white; + font-size: 0.75em; + line-height: 1em; + opacity: 0.9; + padding: 0.5em; + position: absolute; + right: 1em; + top: 1em; + } +} + +.header, +.details { + display: flex; + line-height: 1.5em; + padding: 0.125em 0; + position: relative; + white-space: nowrap; + + .title { + display: inline-block; + overflow: hidden; + text-overflow: ellipsis; + vertical-align: top; + } +} + +.details { + margin-left: 2em; +} + +.icon { + border-radius: 0.25em; + display: inline-block; + flex: 0 0 1.5em; + height: 1.5em; + margin-right: 0.5em; + opacity: 0.75; + text-align: center; + vertical-align: middle; + width: 1.5em; + + &.error { + box-shadow: inset 0 0 0 2px rgb(200, 0, 0); + color: rgb(200, 0, 0); + } + + &.ok { + box-shadow: inset 0 0 0 2px rgb(0, 200, 0); + color: rgb(0, 200, 0); + } +} + +.muted { + opacity: 0.25; +} diff --git a/js/src/dapps/chaindeploy/ListItem/listItem.js b/js/src/dapps/chaindeploy/ListItem/listItem.js new file mode 100644 index 0000000000000000000000000000000000000000..c670123f9241a1c24ea0145857910c89a6ff2459 --- /dev/null +++ b/js/src/dapps/chaindeploy/ListItem/listItem.js @@ -0,0 +1,63 @@ +// 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 styles from './listItem.css'; + +export default class ListItem extends Component { + static propTypes = { + children: PropTypes.node.isRequired, + disabled: PropTypes.bool, + status: PropTypes.string + } + + render () { + const { children, disabled } = this.props; + + return ( +
+
+ { children } +
+ { this.renderStatus() } +
+ ); + } + + renderStatus () { + const { status } = this.props; + + if (!status) { + return null; + } + + return ( +
+ { status } +
+ ); + } +} diff --git a/js/src/dapps/chaindeploy/_dapps.js b/js/src/dapps/chaindeploy/_dapps.js new file mode 100644 index 0000000000000000000000000000000000000000..4a8da242f680d611ecea7f66324068a3a11ac0ce --- /dev/null +++ b/js/src/dapps/chaindeploy/_dapps.js @@ -0,0 +1,43 @@ +// 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 builtinsJson from '~/views/Dapps/builtin.json'; + +const REGISTER_URLS = { + console: 'https://raw.githubusercontent.com/paritytech/console/3ea0dbfefded359ccdbea37bc4cf350c0aa16948/console.jpeg', + dappreg: 'https://raw.githubusercontent.com/paritytech/dapp-assets/cdd6ac4f1e2f11619bed72a53ae71217dffe19ad/dapps/legos-64x64.png', + githubhint: 'https://raw.githubusercontent.com/paritytech/dapp-assets/b88e983abaa1a6a6345b8d9448c15b117ddb540e/dapps/link-64x64.jpg', + localtx: 'https://raw.githubusercontent.com/paritytech/dapp-assets/cdd6ac4f1e2f11619bed72a53ae71217dffe19ad/dapps/stack-64x64.png', + registry: 'https://raw.githubusercontent.com/paritytech/dapp-assets/b88e983abaa1a6a6345b8d9448c15b117ddb540e/dapps/register-64x64.jpg', + signaturereg: 'https://raw.githubusercontent.com/paritytech/dapp-assets/b88e983abaa1a6a6345b8d9448c15b117ddb540e/dapps/hex-64x64.jpg', + tokendeploy: 'https://raw.githubusercontent.com/paritytech/dapp-assets/b88e983abaa1a6a6345b8d9448c15b117ddb540e/dapps/interlock-64x64.png', + tokenreg: 'https://raw.githubusercontent.com/paritytech/dapp-assets/b88e983abaa1a6a6345b8d9448c15b117ddb540e/dapps/coins-64x64.jpg', + web: 'https://raw.githubusercontent.com/paritytech/dapp-assets/ec6138115d0e1f45258969cd90b3b274e0ff2258/dapps/earth-64x64.jpg' +}; + +const builtins = builtinsJson + .filter((app) => app.id) + .map((app) => { + app.source = { + imageUrl: REGISTER_URLS[app.id] + }; + + return app; + }); + +export { + builtins +}; diff --git a/js/src/dapps/chaindeploy/contracts/abi/jg-voting.json b/js/src/dapps/chaindeploy/contracts/abi/jg-voting.json new file mode 100644 index 0000000000000000000000000000000000000000..83a07ef7cbefb4e69d23dee9765dc2dd108229e1 --- /dev/null +++ b/js/src/dapps/chaindeploy/contracts/abi/jg-voting.json @@ -0,0 +1 @@ +[{"constant":true,"inputs":[],"name":"count","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"totalVotes","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_newOwner","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_index","type":"uint256"}],"name":"hasSenderVoted","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_index","type":"uint256"},{"name":"_answer","type":"uint256"}],"name":"newAnswer","outputs":[{"name":"","type":"bool"}],"payable":true,"type":"function"},{"constant":false,"inputs":[{"name":"_fee","type":"uint256"}],"name":"setQuestionFee","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"questionFee","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_index","type":"uint256"}],"name":"get","outputs":[{"name":"closed","type":"bool"},{"name":"owner","type":"address"},{"name":"question","type":"string"},{"name":"balanceNo","type":"uint256"},{"name":"balanceYes","type":"uint256"},{"name":"balanceMaybe","type":"uint256"},{"name":"votesNo","type":"uint256"},{"name":"votesYes","type":"uint256"},{"name":"votesMaybe","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"drain","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_question","type":"string"}],"name":"newQuestion","outputs":[{"name":"","type":"bool"}],"payable":true,"type":"function"},{"constant":true,"inputs":[],"name":"totalBalance","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"answerFee","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_index","type":"uint256"}],"name":"closeQuestion","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_fee","type":"uint256"}],"name":"setAnswerFee","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"inputs":[],"payable":false,"type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"index","type":"uint256"},{"indexed":false,"name":"question","type":"string"}],"name":"NewQuestion","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"index","type":"uint256"},{"indexed":true,"name":"answer","type":"uint256"},{"indexed":false,"name":"value","type":"uint256"}],"name":"NewAnswer","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/dapps/chaindeploy/contracts/badgereg.js b/js/src/dapps/chaindeploy/contracts/badgereg.js new file mode 100644 index 0000000000000000000000000000000000000000..ccd6b28c19b32faa0736e352cd78cafb30664af5 --- /dev/null +++ b/js/src/dapps/chaindeploy/contracts/badgereg.js @@ -0,0 +1,30 @@ +// 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 abi from '~/contracts/abi/badgereg'; +import { compiler, source as sourceUrl, output as byteCode } from './code/badgereg'; + +const id = 'badgereg'; +const deployParams = []; + +export { + abi, + byteCode, + compiler, + deployParams, + id, + sourceUrl +}; diff --git a/js/src/dapps/chaindeploy/contracts/code/badgereg.json b/js/src/dapps/chaindeploy/contracts/code/badgereg.json new file mode 100644 index 0000000000000000000000000000000000000000..e7801722a43c1f4e0abb7e58d8da9c7bba223145 --- /dev/null +++ b/js/src/dapps/chaindeploy/contracts/code/badgereg.json @@ -0,0 +1,5 @@ +{ + "compiler": "v0.4.9+commit.364da425", + "source": "https://github.com/paritytech/contracts/blob/58842b92c00e3c45a84b6d0ac9b842f016dde50a/BadgeReg.sol", + "output": "0x606060405260008054600160a060020a03191633600160a060020a0316179055670de0b6b3a7640000600455341561003357fe5b5b610a6f806100436000396000f300606060405236156100bf5763ffffffff60e060020a60003504166313af403581146100c15780631e7a505f146100df5780632c0f5f591461010a5780635b8066451461014557806369fe0e2d146101675780637958533a1461017c5780638da5cb5b146101a45780639890220b146101d0578063a02b161e146101e2578063ac4ce2c6146101f7578063b72e717d14610218578063c0f6faed1461025b578063dd93890b14610299578063ddbcb5cb146102b4578063ddca3f43146102e6575bfe5b34156100c957fe5b6100dd600160a060020a0360043516610308565b005b6100f6600160a060020a036004351660243561037c565b604080519115158252519081900360200190f35b341561011257fe5b61011d600435610392565b60408051938452600160a060020a039283166020850152911682820152519081900360600190f35b341561014d57fe5b6101556103ef565b60408051918252519081900360200190f35b341561016f57fe5b6100dd6004356103f6565b005b341561018457fe5b61015560043560243561041b565b60408051918252519081900360200190f35b34156101ac57fe5b6101b461045a565b60408051600160a060020a039092168252519081900360200190f35b34156101d857fe5b6100dd610469565b005b34156101ea57fe5b6100dd6004356104bc565b005b34156101ff57fe5b6100dd600435600160a060020a03602435166105ee565b005b341561022057fe5b610234600160a060020a0360043516610721565b604080519384526020840192909252600160a060020a031682820152519081900360600190f35b341561026357fe5b61026e600435610789565b60408051600160a060020a039485168152602081019390935292168183015290519081900360600190f35b34156102a157fe5b6100dd6004356024356044356107dc565b005b6100f6600160a060020a036004358116906024359060443516610899565b604080519115158252519081900360200190f35b34156102ee57fe5b6101556109cc565b60408051918252519081900360200190f35b60005433600160a060020a0390811691161461032357610378565b60008054604051600160a060020a03808516939216917f70aea8d848e8a90fb7661b227dc522eb6395c3dac71b63cb59edd5c9899b236491a360008054600160a060020a031916600160a060020a0383161790555b5b50565b6000610389838333610899565b90505b92915050565b60008181526002602052604081205460038054600019909201929182918291859081106103bb57fe5b906000526020600020906004020160005b5080546002820154600160a060020a03918216955016925090505b509193909250565b6003545b90565b60005433600160a060020a0390811691161461041157610378565b60048190555b5b50565b600060038381548110151561042c57fe5b906000526020600020906004020160005b506000838152600391909101602052604090205490505b92915050565b600054600160a060020a031681565b60005433600160a060020a03908116911614610484576104b8565b604051600160a060020a0333811691309091163180156108fc02916000818181858888f1935050505015156104b857610000565b5b5b565b60005433600160a060020a039081169116146104d757610378565b806003828154811015156104e757fe5b906000526020600020906004020160005b50600101546040517f844e89a9d524dabb877007aa0e9c395e8938fcfee93ece70c2cd0353db79c8e590600090a36001600060038381548110151561053957fe5b906000526020600020906004020160005b5054600160a060020a03168152602081019190915260400160009081208190556003805460029291908490811061057d57fe5b906000526020600020906004020160005b50600101548152602081019190915260400160009081205560038054829081106105b457fe5b906000526020600020906004020160005b508054600160a060020a03199081168255600060018301556002909101805490911690555b5b50565b60008233600160a060020a031660038281548110151561060a57fe5b906000526020600020906004020160005b5060020154600160a060020a0316146106335761071b565b600160a060020a03831660009081526001602052604090205483901561065857610718565b600380548690811061066657fe5b906000526020600020906004020160005b505460038054600160a060020a03909216945085918790811061069657fe5b906000526020600020906004020160005b508054600160a060020a031916600160a060020a039283161790558381166000908152600160209081526040808320839055928716808352918390208890558251918252915187927fa5d871c0e725767cd5aefc99c53aeca35f09dcc268145cbb13b74a7e2f48f196928290030190a25b5b505b50505050565b600160a060020a038116600090815260016020526040812054600380546000199092019291829182918590811061075457fe5b906000526020600020906004020160005b5060018101546002820154909450600160a060020a0316925090505b509193909250565b60006000600060006003858154811015156107a057fe5b906000526020600020906004020160005b50805460018201546002830154600160a060020a03928316975090955016925090505b509193909250565b8233600160a060020a03166003828154811015156107f657fe5b906000526020600020906004020160005b5060020154600160a060020a03161461081f5761071b565b8160038581548110151561082f57fe5b906000526020600020906004020160005b50600085815260039190910160209081526040918290209290925580518481529051859287927f7991c63a749706fd298fc2387764d640be6e714307b6357b1d3c2ce35cba3b52929081900390910190a35b5b50505050565b60006004543410156108aa576109c5565b600160a060020a0384166000908152600160205260409020548490156108cf576109c3565b6000848152600260205260409020548490156108ea576109c0565b60038054600181016108fc83826109d2565b916000526020600020906004020160005b5060408051606081018252600160a060020a03808b1680835260208084018c9052918a169284018390528454600160a060020a03199081168217865560018087018d905560029687018054909216909417905560035460008281529383528484208190558b8452948252918390208490558251918252915160001993909301935088927febbfb6376bef000063e6e33494e4c543a6197091a04eb6a6f55013d85a1c5386929181900390910190a3600192505b5b505b505b9392505050565b60045481565b8154818355818115116109fe576004028160040283600052602060002091820191016109fe9190610a04565b5b505050565b6103f391905b80821115610a3c578054600160a060020a03199081168255600060018301556002820180549091169055600401610a0a565b5090565b905600a165627a7a72305820526fc95faec325cec5dd3e1bb67c165265282c5fc6db21e96197060b823aaa490029" +} diff --git a/js/src/dapps/chaindeploy/contracts/code/dappreg.json b/js/src/dapps/chaindeploy/contracts/code/dappreg.json new file mode 100644 index 0000000000000000000000000000000000000000..48739a59d282b8aa9397c8a2b677ffa75f223044 --- /dev/null +++ b/js/src/dapps/chaindeploy/contracts/code/dappreg.json @@ -0,0 +1,5 @@ +{ + "compiler": "v0.4.9+commit.364da425", + "source": "https://github.com/paritytech/contracts/blob/225bf022ddd967af2b9ea188e8f611489ca5d7fe/DappReg.sol", + "output": "0x606060405260008054600160a060020a03191633600160a060020a0316179055670de0b6b3a7640000600355341561003357fe5b5b6106ee806100436000396000f300606060405236156100a95763ffffffff60e060020a6000350416630257c48c81146100ab57806306661abd146100d357806313af4035146100f55780631a0919dc1461011357806369fe0e2d146101285780638da5cb5b1461013d5780638eaa6ac01461016957806391cd242d1461019d5780639890220b146101b8578063c52bd836146101ca578063ddca3f43146101eb578063e0886f901461020d578063e1fa8e8414610241575bfe5b34156100b357fe5b6100c160043560243561024e565b60408051918252519081900360200190f35b34156100db57fe5b6100c1610272565b60408051918252519081900360200190f35b34156100fd57fe5b610111600160a060020a0360043516610279565b005b341561011b57fe5b6101116004356102ed565b005b341561013057fe5b610111600435610388565b005b341561014557fe5b61014d6103ad565b60408051600160a060020a039092168252519081900360200190f35b341561017157fe5b61017c6004356103bc565b60408051928352600160a060020a0390911660208301528051918290030190f35b34156101a557fe5b6101116004356024356044356103e4565b005b34156101c057fe5b61011161046d565b005b34156101d257fe5b610111600435600160a060020a03602435166104c0565b005b34156101f357fe5b6100c161054e565b60408051918252519081900360200190f35b341561021557fe5b61017c600435610554565b60408051928352600160a060020a0390911660208301528051918290030190f35b6101116004356105ab565b005b60008281526001602090815260408083208484526002019091529020545b92915050565b6002545b90565b60005433600160a060020a0390811691161461029457610000565b60008054604051600160a060020a03808516939216917f70aea8d848e8a90fb7661b227dc522eb6395c3dac71b63cb59edd5c9899b236491a360008054600160a060020a031916600160a060020a0383161790555b5b50565b60008181526001602081905260409091200154819033600160a060020a0390811691161480159061032d575060005433600160a060020a03908116911614155b1561033757610000565b60008281526001602081905260408083208381559091018054600160a060020a03191690555183917fe17fec26316aebe957e188549d659a89f359c49766bcc0ae2fb7ded274ffe14691a25b5b5050565b60005433600160a060020a039081169116146103a357610000565b60038190555b5b50565b600054600160a060020a031681565b6000818152600160208190526040909120805491810154600160a060020a0316905b50915091565b60008381526001602081905260409091200154839033600160a060020a0390811691161461041157610000565b600084815260016020908152604080832086845260020182529182902084905581518481529151859287927f4dcd4fb147bb133a0da8fbf4e5fc3ddd64f04d4b3f6cbee584374b889d28c78d92918290030190a35b5b50505050565b60005433600160a060020a0390811691161461048857610000565b604051600160a060020a0333811691309091163180156108fc02916000818181858888f1935050505015156104bc57610000565b5b5b565b60008281526001602081905260409091200154829033600160a060020a039081169116146104ed57610000565b60008381526001602081905260408083209091018054600160a060020a031916600160a060020a0386169081179091559051909185917fd3d10d874a10020c2bce719499d1fd8756d880b128eb2945dd01b3830854e7169190a35b5b505050565b60035481565b6000600060006001600060028681548110151561056d57fe5b906000526020600020900160005b50548152602081019190915260400160002080546001820154909450600160a060020a0316925090505b50915091565b6003543410156105ba57610000565b6000818152600160205260409020548190156105d557610000565b60028054600181016105e78382610677565b916000526020600020900160005b508390555060408051808201825283815233600160a060020a0390811660208084018281526000888152600192839052868120955186559051949091018054600160a060020a0319169490931693909317909155915184917f7d917fcbc9a29a9705ff9936ffa599500e4fd902e4486bae317414fe967b307c91a35b5b505b50565b815481835581811511610548576000838152602090206105489181019083016106a1565b5b505050565b61027691905b808211156106bb57600081556001016106a7565b5090565b905600a165627a7a7230582011d8a45e381635e9de17e14cc4de97a1e17758cfac1fd25e8a5bc1d5f4d1da9d0029" +} diff --git a/js/src/dapps/chaindeploy/contracts/code/gavcoin.json b/js/src/dapps/chaindeploy/contracts/code/gavcoin.json new file mode 100644 index 0000000000000000000000000000000000000000..f0ead4a35d64da94491a929a5e454c3d8e0983ce --- /dev/null +++ b/js/src/dapps/chaindeploy/contracts/code/gavcoin.json @@ -0,0 +1,5 @@ +{ + "compiler": "v0.4.9+commit.364da425", + "source": "https://github.com/paritytech/contracts/blob/58842b92c00e3c45a84b6d0ac9b842f016dde50a/GavCoin.sol", + "output": "0x606060405266038d7ea4c680006002556305f5e100600355341561001f57fe5b5b6108008061002f6000396000f300606060405236156100885763ffffffff60e060020a600035041663095ea7b3811461009f57806318160ddd146100d257806323b872dd146100f457806329cbdc861461012d57806355234ec0146101465780635af36e3e1461016857806370a0823114610192578063a035b1fe146101c0578063a9059cbb146101e2578063dd62ed3e14610215575b61009d5b61009a3360ff60020a610249565b5b565b005b34156100a757fe5b6100be600160a060020a0360043516602435610390565b604080519115158252519081900360200190f35b34156100da57fe5b6100e2610416565b60408051918252519081900360200190f35b34156100fc57fe5b6100be600160a060020a036004358116906024351660443561041c565b604080519115158252519081900360200190f35b61009d600160a060020a036004351660243561052a565b005b341561014e57fe5b6100e2610539565b60408051918252519081900360200190f35b341561017057fe5b6100be60043560243561053f565b604080519115158252519081900360200190f35b341561019a57fe5b6100e2600160a060020a03600435166106d0565b60408051918252519081900360200190f35b34156101c857fe5b6100e26106ef565b60408051918252519081900360200190f35b34156101ea57fe5b6100be600160a060020a03600435166024356106f5565b604080519115158252519081900360200190f35b341561021d57fe5b6100e2600160a060020a03600435811690602435166107a3565b60408051918252519081900360200190f35b34600080805b60008411801561026157508460025411155b1561038757600354600254620f424091025b0492508284116102835783610285565b825b9150600254620f4240830281151561029957fe5b33600160a060020a03166000818152600160208181526040808420805497909604968701865560028054855295830190915280832080548701905584548352808320909101805463ffffffff191662093a80420163ffffffff161790558154850182559254925193945084937f689dcb02b6a65e0e2f1d23ef47c1ec86604ffbed0bcb65f20150cfc7d5e5a9489190a4600380548290039081905593829003931515610382576002805466038d7ea4c6800001908190556305f5e1006003556040517f23c3dae768238f239632b5c4acb89485b440e0fa72481c4aad9f9b4f9b5a0a5f90600090a25b61024f565b5b505050505050565b600082600160a060020a031633600160a060020a03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040518082815260200191505060405180910390a350600160a060020a0333811660009081526001602081815260408084209487168452600290940190529190208054830190555b92915050565b60005481565b600160a060020a038316600090815260016020526040812054849083908190101561044657610521565b600160a060020a0380871660009081526001602090815260408083203394851684526002019091529020548791908690819010156104835761051c565b87600160a060020a031689600160a060020a03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef896040518082815260200191505060405180910390a3600160a060020a03808a166000908152600160208181526040808420338616855260028101835281852080548e900390559183905281548c9003909155928b16825291902080548901905595505b5b5050505b50509392505050565b6105348282610249565b5b5050565b60035481565b600160a060020a0333166000908152600160208181526040808420868552909201905281205483908390819010806105a35750600160a060020a03331660009081526001602081815260408084208685528301909152909120015463ffffffff1642105b156105ad576106c8565b33600160a060020a0381166000908152600160205260409020548590819010156105d6576106c4565b60405186908890600160a060020a033316907f73f04af9dcc582a923ec15d3eea990fe34adabfff2879e28d44572e01a54abb690600090a433600160a060020a0316600090815260016020818152604080842080548b9003815584548b0185558b855290920190529020805487900390819055151561068457600160a060020a03331660009081526001602081815260408084208b85528301909152822091825501805463ffffffff191690555b600160a060020a0333166108fc620f4240888a025b604051919004801590920291906000818181858888f1935050505015156106bf57610000565b600194505b5b50505b505092915050565b600160a060020a0381166000908152600160205260409020545b919050565b60025481565b33600160a060020a038116600090815260016020526040812054909190839081901015610721576106c8565b84600160a060020a031633600160a060020a03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef866040518082815260200191505060405180910390a3600160a060020a03338116600090815260016020526040808220805488900390559187168152208054850190555b5b505092915050565b600160a060020a03808316600090815260016020908152604080832093851683526002909301905220545b929150505600a165627a7a72305820ca533a37c92e41888bda66ae0e66415d21a61c60027b269bca633d85b727875c0029" +} diff --git a/js/src/dapps/chaindeploy/contracts/code/githubhint.json b/js/src/dapps/chaindeploy/contracts/code/githubhint.json new file mode 100644 index 0000000000000000000000000000000000000000..3470ccef0b4f346c233b1280794cfc447b27edd8 --- /dev/null +++ b/js/src/dapps/chaindeploy/contracts/code/githubhint.json @@ -0,0 +1,5 @@ +{ + "compiler": "v0.4.9+commit.364da425", + "source": "https://github.com/paritytech/contracts/blob/58842b92c00e3c45a84b6d0ac9b842f016dde50a/GithubHint.sol", + "output": "0x6060604052341561000c57fe5b5b6105868061001c6000396000f300606060405263ffffffff60e060020a60003504166302f2008d81146100425780632196ae0d1461009b578063267b6922146101055780637c8c6643146101c9575bfe5b341561004a57fe5b60408051602060046024803582810135601f810185900485028601850190965285855261009995833595939460449493929092019181908401838280828437509496506101de95505050505050565b005b34156100a357fe5b60408051602060046024803582810135601f81018590048502860185019096528585526100999583359593946044949392909201918190840183828082843750949650505092356bffffffffffffffffffffffff191692506102be915050565b005b341561010d57fe5b6101186004356103b1565b604080516bffffffffffffffffffffffff1984166020820152600160a060020a03831691810191909152606080825284546002600019610100600184161502019091160490820181905281906080820190869080156101b85780601f1061018d576101008083540402835291602001916101b8565b820191906000526020600020905b81548152906001019060200180831161019b57829003601f168201915b505094505050505060405180910390f35b34156101d157fe5b6100996004356103de565b005b6000828152602081905260409020600201548290600160a060020a031615801590610227575060008181526020819052604090206002015433600160a060020a03908116911614155b15610231576102b8565b6040805160608101825283815260006020808301829052600160a060020a0333168385015286825281815292902081518051929391926102749284920190610472565b506020820151600182018054606060020a909204600160a060020a031992831617905560409092015160029091018054600160a060020a0392909216919092161790555b5b505050565b6000838152602081905260409020600201548390600160a060020a031615801590610307575060008181526020819052604090206002015433600160a060020a03908116911614155b15610311576103aa565b604080516060810182528481526bffffffffffffffffffffffff198416602080830191909152600160a060020a0333168284015260008781528082529290922081518051929391926103669284920190610472565b506020820151600182018054606060020a909204600160a060020a031992831617905560409092015160029091018054600160a060020a0392909216919092161790555b5b50505050565b600060208190529081526040902060018101546002820154606060020a90910290600160a060020a031683565b6000818152602081905260409020600201548190600160a060020a031615801590610427575060008181526020819052604090206002015433600160a060020a03908116911614155b156104315761046d565b60008281526020819052604081209061044a82826104f1565b50600181018054600160a060020a03199081169091556002909101805490911690555b5b5050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106104b357805160ff19168380011785556104e0565b828001600101855582156104e0579182015b828111156104e05782518255916020019190600101906104c5565b5b506104ed929150610539565b5090565b50805460018160011615610100020316600290046000825580601f106105175750610535565b601f0160209004906000526020600020908101906105359190610539565b5b50565b61055791905b808211156104ed576000815560010161053f565b5090565b905600a165627a7a72305820a83571409e7b0cc4fe48edd09087f315930ab4e017c62b6d100462285a8f4ae70029" +} diff --git a/js/src/dapps/chaindeploy/contracts/code/jg-voting.json b/js/src/dapps/chaindeploy/contracts/code/jg-voting.json new file mode 100644 index 0000000000000000000000000000000000000000..08ec4d0ed4b17340d98e9002ccb6be0545d7a637 --- /dev/null +++ b/js/src/dapps/chaindeploy/contracts/code/jg-voting.json @@ -0,0 +1,5 @@ +{ + "compiler": "v0.4.9+commit.364da425", + "source": "https://github.com/jacogr/dapp-voting/blob/9b20754b13b9a387704c0955d88b51d2e0e1896d/src/solidity/Voting.sol", + "output": "0x606060405260008054600160a060020a03191633600160a060020a0316178155600281905560038190556004556611c37937e0800060055534156200004057fe5b5b60408051808201909152600781527f48756e6772793f0000000000000000000000000000000000000000000000000060208201526200008e906401000000006200090a6200009682021704565b505b620003af565b60008054819033600160a060020a03908116911614801590620000ba575060055434105b15620000c65762000000565b82600481511080620000d9575060a08151115b15620000e55762000000565b60018054925082810190620000fb908262000244565b50336001838154811015156200010d57fe5b906000526020600020906005020160005b508054600160a060020a03929092166101000261010060a860020a031990921691909117905560018054859190849081106200015657fe5b906000526020600020906005020160005b5060010190805190602001906200018092919062000279565b508133600160a060020a03167f7793f929911ad07e07894a20378f1eccce0fb493486c569d74045731fb583b8e866040518080602001828103825283818151815260200191508051906020019080838360008314620001fc575b805182526020831115620001fc57601f199092019160209182019101620001da565b505050905090810190601f168015620002295780820380516001836020036101000a031916815260200191505b509250505060405180910390a3600192505b5b505b50919050565b8154818355818115116200027357600502816005028360005260206000209182019101620002739190620002ff565b5b505050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10620002bc57805160ff1916838001178555620002ec565b82800160010185558215620002ec579182015b82811115620002ec578251825591602001919060010190620002cf565b5b50620002fb9291506200033f565b5090565b6200033c91905b80821115620002fb578054600160a860020a031916815560006200032e600183018262000363565b5060050162000306565b5090565b90565b6200033c91905b80821115620002fb576000815560010162000346565b5090565b90565b50805460018160011615610100020316600290046000825580601f106200038b5750620003ab565b601f016020900490600052602060002090810190620003ab91906200033f565b5b50565b610d2f80620003bf6000396000f300606060405236156100bf5763ffffffff60e060020a60003504166306661abd81146100c15780630d15fd77146100e357806313af40351461010557806331d3164714610123578063476c494c1461014a5780634df6ca2a1461016c5780638a55b54d146101935780638da5cb5b146101b55780639507d39a146101e15780639890220b146102c7578063a3f66b46146102eb578063ad7a672f1461034d578063bd12b4b51461036f578063c09f32e814610391578063f41c1c93146103b8575bfe5b34156100c957fe5b6100d16103df565b60408051918252519081900360200190f35b34156100eb57fe5b6100d16103e6565b60408051918252519081900360200190f35b341561010d57fe5b610121600160a060020a03600435166103ec565b005b341561012b57fe5b61013660043561046d565b604080519115158252519081900360200190f35b6101366004356024356104b8565b604080519115158252519081900360200190f35b341561017457fe5b6101366004356106c3565b604080519115158252519081900360200190f35b341561019b57fe5b6100d16106ee565b60408051918252519081900360200190f35b34156101bd57fe5b6101c56106f4565b60408051600160a060020a039092168252519081900360200190f35b34156101e957fe5b6101f4600435610703565b604051808a15151515815260200189600160a060020a0316600160a060020a0316815260200180602001888152602001878152602001868152602001858152602001848152602001838152602001828103825289818151815260200191508051906020019080838360008314610285575b80518252602083111561028557601f199092019160209182019101610265565b505050905090810190601f1680156102b15780820380516001836020036101000a031916815260200191505b509a505050505050505050505060405180910390f35b34156102cf57fe5b6101366108b2565b604080519115158252519081900360200190f35b610136600480803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284375094965061090a95505050505050565b604080519115158252519081900360200190f35b341561035557fe5b6100d1610ab5565b60408051918252519081900360200190f35b341561037757fe5b6100d1610abb565b60408051918252519081900360200190f35b341561039957fe5b610136600435610ac1565b604080519115158252519081900360200190f35b34156103c057fe5b610136600435610b63565b604080519115158252519081900360200190f35b6001545b90565b60035481565b60005433600160a060020a0390811691161461040757610000565b60008054604051600160a060020a03808516939216917f70aea8d848e8a90fb7661b227dc522eb6395c3dac71b63cb59edd5c9899b236491a36000805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a0383161790555b5b50565b600060018281548110151561047e57fe5b906000526020600020906005020160005b50600160a060020a0333166000908152600491909101602052604090205460ff1690505b919050565b6000805433600160a060020a039081169116148015906104d9575060045434105b156104e357610000565b600154839081106104f357610000565b8360018181548110151561050357fe5b906000526020600020906005020160005b505460ff1615156001141561052857610000565b8460018181548110151561053857fe5b906000526020600020906005020160005b50600160a060020a0333166000908152600491909101602052604090205460ff1615156001141561057957610000565b84600281111561058857610000565b60038054600190810190915560028054600160a060020a0333163101905580548190899081106105b457fe5b906000526020600020906005020160005b5033600160a060020a031660008181526004929092016020526040909120805460ff191692151592909217909155600180549131918990811061060457fe5b906000526020600020906005020160005b50600088815260029190910160205260409020805490910190556001805481908990811061063f57fe5b906000526020600020906005020160005b506000888152600391909101602090815260409182902080549093019092558051600160a060020a033316318152905188928a927f8b8ed2ef61b90da02f78bd8647287f46833d5b11467db4451e5c4b165485bf46929081900390910190a3600194505b5b505b505b505b505b92915050565b6000805433600160a060020a039081169116146106df57610000565b50600581905560015b5b919050565b60055481565b600054600160a060020a031681565b6000600061070f610b8e565b600060006000600060006000600060018b81548110151561072c57fe5b906000526020600020906005020160005b5090508060000160009054906101000a900460ff1699508060000160019054906101000a9004600160a060020a03169850806001018054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156108065780601f106107db57610100808354040283529160200191610806565b820191906000526020600020905b8154815290600101906020018083116107e957829003601f168201915b505050505097508060020160006000815260200190815260200160002054965080600201600060018152602001908152602001600020549550806002016000600281526020019081526020016000205494508060030160006000815260200190815260200160002054935080600301600060018152602001908152602001600020549250806003016000600281526020019081526020016000205491505b509193959799909294969850565b6000805433600160a060020a039081169116146108ce57610000565b604051600160a060020a0333811691309091163180156108fc02916000818181858888f19350505050151561090257610000565b5060015b5b90565b60008054819033600160a060020a0390811691161480159061092d575060055434105b1561093757610000565b82600481511080610949575060a08151115b1561095357610000565b600180549250828101906109679082610ba0565b503360018381548110151561097857fe5b906000526020600020906005020160005b508054600160a060020a03929092166101000274ffffffffffffffffffffffffffffffffffffffff001990921691909117905560018054859190849081106109cd57fe5b906000526020600020906005020160005b5060010190805190602001906109f5929190610bd2565b508133600160a060020a03167f7793f929911ad07e07894a20378f1eccce0fb493486c569d74045731fb583b8e866040518080602001828103825283818151815260200191508051906020019080838360008314610a6e575b805182526020831115610a6e57601f199092019160209182019101610a4e565b505050905090810190601f168015610a9a5780820380516001836020036101000a031916815260200191505b509250505060405180910390a3600192505b5b505b50919050565b60025481565b60045481565b60008133600160a060020a0316600182815481101515610add57fe5b906000526020600020906005020160005b50546101009004600160a060020a031614801590610b1b575060005433600160a060020a03908116911614155b15610b2557610000565b6001600184815481101515610b3657fe5b906000526020600020906005020160005b50805460ff1916911515919091179055600191505b5b50919050565b6000805433600160a060020a03908116911614610b7f57610000565b50600481905560015b5b919050565b60408051602081019091526000815290565b815481835581811511610bcc57600502816005028360005260206000209182019101610bcc9190610c51565b5b505050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10610c1357805160ff1916838001178555610c40565b82800160010185558215610c40579182015b82811115610c40578251825591602001919060010190610c25565b5b50610c4d929150610c9a565b5090565b6103e391905b80821115610c4d57805474ffffffffffffffffffffffffffffffffffffffffff191681556000610c8a6001830182610cbb565b50600501610c57565b5090565b90565b6103e391905b80821115610c4d5760008155600101610ca0565b5090565b90565b50805460018160011615610100020316600290046000825580601f10610ce15750610469565b601f0160209004906000526020600020908101906104699190610c9a565b5b505600a165627a7a72305820b084dcce4e3d78f8a86c925f6c3f2ed689e3674655482541076a1a22f6cdedad0029" +} diff --git a/js/src/dapps/chaindeploy/contracts/code/registry.json b/js/src/dapps/chaindeploy/contracts/code/registry.json new file mode 100644 index 0000000000000000000000000000000000000000..809e7bcfb3c1e37c2e452a0f13cea7e6ce0e3c97 --- /dev/null +++ b/js/src/dapps/chaindeploy/contracts/code/registry.json @@ -0,0 +1,5 @@ +{ + "compiler": "v0.4.9+commit.364da425", + "source": "https://github.com/paritytech/contracts/blob/b1e0348144793e4ce6f7d6d2c4c7d0bb4ae9765e/SimpleRegistry.sol", + "output": "0x606060405260008054600160a060020a03191633600160a060020a0316179055670de0b6b3a7640000600355341561003357fe5b5b611b2f806100436000396000f300606060405236156101225763ffffffff60e060020a60003504166306b2ff47811461012457806313af40351461015457806319362a28146101725780633f3935d1146101df578063432ced04146102495780634f39ca59146102685780636795dbcd1461028f57806369fe0e2d1461030257806379ce9fac146103295780638da5cb5b1461035c57806390b97fc11461038857806392698814146103f15780639890220b14610418578063ac4e73f91461043c578063ac72c120146104b1578063c3a3582514610388578063ddca3f4314610541578063deb931a214610563578063df57b74214610592578063e30bd740146105c1578063eadf976014610172578063ef5454d6146106ca578063f25eb5c11461073f578063f6d339e414610751575bfe5b341561012c57fe5b610140600160a060020a03600435166107c7565b604080519115158252519081900360200190f35b341561015c57fe5b610170600160a060020a03600435166107fa565b005b341561017a57fe5b60408051602060046024803582810135601f81018590048502860185019096528585526101409583359593946044949392909201918190840183828082843750949650509335935061086e92505050565b604080519115158252519081900360200190f35b34156101e757fe5b610140600480803590602001908201803590602001908080601f01602080910402602001604051908101604052809392919081815260200183838082843750949650610a1495505050505050565b604080519115158252519081900360200190f35b610140600435610b6e565b604080519115158252519081900360200190f35b341561027057fe5b610140600435610c03565b604080519115158252519081900360200190f35b341561029757fe5b60408051602060046024803582810135601f81018590048502860185019096528585526102e69583359593946044949392909201918190840183828082843750949650610cc495505050505050565b60408051600160a060020a039092168252519081900360200190f35b341561030a57fe5b610140600435610d40565b604080519115158252519081900360200190f35b341561033157fe5b610140600435600160a060020a0360243516610d9e565b604080519115158252519081900360200190f35b341561036457fe5b6102e6610e2e565b60408051600160a060020a039092168252519081900360200190f35b341561039057fe5b60408051602060046024803582810135601f81018590048502860185019096528585526103df9583359593946044949392909201918190840183828082843750949650610cc495505050505050565b60408051918252519081900360200190f35b34156103f957fe5b610140600435610eb9565b604080519115158252519081900360200190f35b341561042057fe5b610140610ed9565b604080519115158252519081900360200190f35b341561044457fe5b610140600480803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284375094965050509235600160a060020a03169250610f6e915050565b604080519115158252519081900360200190f35b34156104b957fe5b6101406004356112ac565b604080519115158252519081900360200190f35b341561039057fe5b60408051602060046024803582810135601f81018590048502860185019096528585526103df9583359593946044949392909201918190840183828082843750949650610cc495505050505050565b60408051918252519081900360200190f35b341561054957fe5b6103df61134c565b60408051918252519081900360200190f35b341561056b57fe5b6102e6600435611352565b60408051600160a060020a039092168252519081900360200190f35b341561059a57fe5b6102e6600435611370565b60408051600160a060020a039092168252519081900360200190f35b34156105c957fe5b6105dd600160a060020a0360043516611392565b604080516020808252835181830152835191928392908301918501908083838215610623575b80518252602083111561062357601f199092019160209182019101610603565b505050905090810190601f16801561064f5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561017a57fe5b60408051602060046024803582810135601f81018590048502860185019096528585526101409583359593946044949392909201918190840183828082843750949650509335935061086e92505050565b604080519115158252519081900360200190f35b34156106d257fe5b610140600480803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284375094965050509235600160a060020a031692506115e8915050565b604080519115158252519081900360200190f35b341561074757fe5b6101706116c6565b005b341561075957fe5b60408051602060046024803582810135601f8101859004850286018501909652858552610140958335959394604494939290920191819084018382808284375094965050509235600160a060020a0316925061185a915050565b604080519115158252519081900360200190f35b600160a060020a03811660009081526002602081905260409091205460001961010060018316150201160415155b919050565b60005433600160a060020a039081169116146108155761086a565b60008054604051600160a060020a03808516939216917f70aea8d848e8a90fb7661b227dc522eb6395c3dac71b63cb59edd5c9899b236491a360008054600160a060020a031916600160a060020a0383161790555b5b50565b600083815260016020526040812054849033600160a060020a0390811691161461089757610a0b565b6000858152600160209081526040918290209151865186936002019288929182918401908083835b602083106108de5780518252601f1990920191602091820191016108bf565b51815160209384036101000a60001901801990921691161790529201948552506040519384900381018420949094555050855186928291908401908083835b6020831061093c5780518252601f19909201916020918201910161091d565b51815160209384036101000a6000190180199092169116179052604080519290940182900382208183528a51838301528a519096508b95507fb829c3e412537bbe794c048ccb9e4605bb4aaaa8e4d4c15c1a6e0c2adc1716ea948b94508392908301919085019080838382156109cd575b8051825260208311156109cd57601f1990920191602091820191016109ad565b505050905090810190601f1680156109f95780820380516001836020036101000a031916815260200191505b509250505060405180910390a3600191505b5b509392505050565b60008133600160a060020a031660016000836040518082805190602001908083835b60208310610a555780518252601f199092019160209182019101610a36565b51815160209384036101000a6000190180199092169116179052604080519290940182900390912086528501959095529290920160002060010154600160a060020a0316939093149250610aab91505057610b67565b600160a060020a03331660009081526002602090815260409091208451610ad492860190611a09565b5033600160a060020a0316836040518082805190602001908083835b60208310610b0f5780518252601f199092019160209182019101610af0565b5181516020939093036101000a60001901801990911692169190911790526040519201829003822093507f098ae8581bb8bd9af1beaf7f2e9f51f31a8e5a8bfada4e303a645d71d9c9192092506000919050a3600191505b5b50919050565b6000818152600160205260408120548290600160a060020a031615610b9257610b67565b600354341015610ba157610b67565b6000838152600160205260408082208054600160a060020a03191633600160a060020a03169081179091559051909185917f4963513eca575aba66fdcd25f267aae85958fe6fb97e75fa25d783f1a091a2219190a3600191505b5b5b50919050565b600081815260016020526040812054829033600160a060020a03908116911614610c2c57610b67565b600083815260016020818152604080842090920154600160a060020a03168352600290528120610c5b91611a88565b60008381526001602081905260408083208054600160a060020a03199081168255920180549092169091555133600160a060020a03169185917fef1961b4d2909dc23643b309bfe5c3e5646842d98c3a58517037ef3871185af39190a3600191505b5b50919050565b600082815260016020908152604080832090518451600290920192859282918401908083835b60208310610d095780518252601f199092019160209182019101610cea565b51815160209384036101000a6000190180199092169116179052920194855250604051938490030190922054925050505b92915050565b6000805433600160a060020a03908116911614610d5c576107f5565b60038290556040805183815290517f6bbc57480a46553fa4d156ce702beef5f3ad66303b0ed1a5d4cb44966c6584c39181900360200190a15060015b5b919050565b600082815260016020526040812054839033600160a060020a03908116911614610dc757610e26565b6000848152600160205260408082208054600160a060020a031916600160a060020a0387811691821790925591519192339091169187917f7b97c62130aa09acbbcbf7482630e756592496f1759eaf702f469cf64dfb779491a4600191505b5b5092915050565b600054600160a060020a031681565b600082815260016020908152604080832090518451600290920192859282918401908083835b60208310610d095780518252601f199092019160209182019101610cea565b51815160209384036101000a6000190180199092169116179052920194855250604051938490030190922054925050505b92915050565b600081815260016020526040902054600160a060020a031615155b919050565b6000805433600160a060020a03908116911614610ef557610f6a565b60408051600160a060020a03301631815290517fdef931299fe61d176f949118058530c1f3f539dcb6950b4e372c9b835c33ca079181900360200190a1604051600160a060020a0333811691309091163180156108fc02916000818181858888f193505050501515610f6657610000565b5060015b5b90565b60006000836040518082805190602001908083835b60208310610fa25780518252601f199092019160209182019101610f83565b51815160209384036101000a6000190180199092169116179052604080519290940182900390912060008181526001909252929020549193505033600160a060020a039081169116149050610ff6576112a3565b846040518082805190602001908083835b602083106110265780518252601f199092019160209182019101611007565b51815160209384036101000a60001901801990921691161790526040805192909401829003909120600081815260019283905293909320015491955050600160a060020a03161580159250905061111257506000828152600160208181526040808420830154600160a060020a031684526002918290529283902092518354869493919283928592908116156101000260001901160480156110ff5780601f106110dd5761010080835404028352918201916110ff565b820191906000526020600020905b8154815290600101906020018083116110eb575b5050915050604051809103902060001916145b156111eb57600082815260016020818152604080842090920154600160a060020a0316835260029052812061114691611a88565b6000828152600160208181526040928390209091015491518751600160a060020a039093169288928291908401908083835b602083106111975780518252601f199092019160209182019101611178565b5181516020939093036101000a60001901801990911692169190911790526040519201829003822093507f12491ad95fd945e444d88a894ffad3c21959880a4dcd8af99d4ae4ffc71d4abd92506000919050a35b6000828152600160208181526040928390209091018054600160a060020a031916600160a060020a0388169081179091559151875188928291908401908083835b6020831061124b5780518252601f19909201916020918201910161122c565b5181516020939093036101000a60001901801990911692169190911790526040519201829003822093507f728435a0031f6a04538fcdd24922a7e06bc7bc945db03e83d22122d1bc5f28df92506000919050a3600192505b5b505092915050565b60008181526001602081905260409091200154600160a060020a031615155b919050565b600082815260016020908152604080832090518451600290920192859282918401908083835b60208310610d095780518252601f199092019160209182019101610cea565b51815160209384036101000a6000190180199092169116179052920194855250604051938490030190922054925050505b92915050565b60035481565b600081815260016020526040902054600160a060020a03165b919050565b60008181526001602081905260409091200154600160a060020a03165b919050565b61139a611ad0565b600160a060020a038216600090815260026020818152604092839020805484516001821615610100026000190190911693909304601f81018390048302840183019094528383529192908301828280156114355780601f1061140a57610100808354040283529160200191611435565b820191906000526020600020905b81548152906001019060200180831161141857829003601f168201915b505050505090505b919050565b600083815260016020526040812054849033600160a060020a0390811691161461089757610a0b565b6000858152600160209081526040918290209151865186936002019288929182918401908083835b602083106108de5780518252601f1990920191602091820191016108bf565b51815160209384036101000a60001901801990921691161790529201948552506040519384900381018420949094555050855186928291908401908083835b6020831061093c5780518252601f19909201916020918201910161091d565b51815160209384036101000a6000190180199092169116179052604080519290940182900382208183528a51838301528a519096508b95507fb829c3e412537bbe794c048ccb9e4605bb4aaaa8e4d4c15c1a6e0c2adc1716ea948b94508392908301919085019080838382156109cd575b8051825260208311156109cd57601f1990920191602091820191016109ad565b505050905090810190601f1680156109f95780820380516001836020036101000a031916815260200191505b509250505060405180910390a3600191505b5b509392505050565b6000805433600160a060020a0390811691161461160457610d3a565b600160a060020a0382166000908152600260209081526040909120845161162d92860190611a09565b5081600160a060020a0316836040518082805190602001908083835b602083106116685780518252601f199092019160209182019101611649565b5181516020939093036101000a60001901801990911692169190911790526040519201829003822093507f098ae8581bb8bd9af1beaf7f2e9f51f31a8e5a8bfada4e303a645d71d9c9192092506000919050a35060015b5b92915050565b33600160a060020a03166002600033600160a060020a0316600160a060020a0316815260200190815260200160002060405180828054600181600116156101000203166002900480156117505780601f1061172e576101008083540402835291820191611750565b820191906000526020600020905b81548152906001019060200180831161173c575b505060405190819003812092507f12491ad95fd945e444d88a894ffad3c21959880a4dcd8af99d4ae4ffc71d4abd9150600090a3600160006002600033600160a060020a0316600160a060020a0316815260200190815260200160002060405180828054600181600116156101000203166002900480156118085780601f106117e6576101008083540402835291820191611808565b820191906000526020600020905b8154815290600101906020018083116117f4575b50506040805191829003909120845260208085019590955292830160009081206001018054600160a060020a031916905533600160a060020a0316815260029094525050812061185791611a88565b5b565b600083815260016020526040812054849033600160a060020a0390811691161461188357610a0b565b60008581526001602090815260409182902091518651600160a060020a038716936002019288929182918401908083835b602083106108de5780518252601f1990920191602091820191016108bf565b51815160209384036101000a60001901801990921691161790529201948552506040519384900381018420949094555050855186928291908401908083835b6020831061093c5780518252601f19909201916020918201910161091d565b51815160209384036101000a6000190180199092169116179052604080519290940182900382208183528a51838301528a519096508b95507fb829c3e412537bbe794c048ccb9e4605bb4aaaa8e4d4c15c1a6e0c2adc1716ea948b94508392908301919085019080838382156109cd575b8051825260208311156109cd57601f1990920191602091820191016109ad565b505050905090810190601f1680156109f95780820380516001836020036101000a031916815260200191505b509250505060405180910390a3600191505b5b509392505050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10611a4a57805160ff1916838001178555611a77565b82800160010185558215611a77579182015b82811115611a77578251825591602001919060010190611a5c565b5b50611a84929150611ae2565b5090565b50805460018160011615610100020316600290046000825580601f10611aae575061086a565b601f01602090049060005260206000209081019061086a9190611ae2565b5b50565b60408051602081019091526000815290565b610f6a91905b80821115611a845760008155600101611ae8565b5090565b905600a165627a7a723058202a8f09bd2a20f43dfe4c6adb69bf4bfa211cda23787d972cd6e33eee989cafc50029" +} diff --git a/js/src/dapps/chaindeploy/contracts/code/signaturereg.json b/js/src/dapps/chaindeploy/contracts/code/signaturereg.json new file mode 100644 index 0000000000000000000000000000000000000000..3dd4fe07d28b3385291bc328d26d500dff694530 --- /dev/null +++ b/js/src/dapps/chaindeploy/contracts/code/signaturereg.json @@ -0,0 +1,5 @@ +{ + "compiler": "v0.4.9+commit.364da425", + "source": "https://github.com/paritytech/contracts/blob/58842b92c00e3c45a84b6d0ac9b842f016dde50a/SignatureReg.sol", + "output": "0x606060405260008054600160a060020a03191633600160a060020a0316178155600255341561002a57fe5b5b60408051808201909152601081527f726567697374657228737472696e67290000000000000000000000000000000060208201526100759064010000000061036661007c82021704565b505b61031c565b60006100f7826040518082805190602001908083835b602083106100b15780518252601f199092019160209182019101610092565b6001836020036101000a0380198251168184511680821785525050505050509050019150506040518091039020836100ff640100000000026103d6176401000000009004565b90505b919050565b7fffffffff000000000000000000000000000000000000000000000000000000008216600090815260016020819052604082205484916002908216156101000260001901909116041561015157610274565b7fffffffff000000000000000000000000000000000000000000000000000000008416600090815260016020908152604090912084516101939286019061027c565b5060028054600101905560408051602080825285518183015285517fffffffff00000000000000000000000000000000000000000000000000000000881693600160a060020a033316937f50e01e16719d6c699e516c57f4c514e77f6bc21a978d33f23980acdddbcbd0b293899391928392908301918501908083838215610236575b80518252602083111561023657601f199092019160209182019101610216565b505050905090810190601f1680156102625780820380516001836020036101000a031916815260200191505b509250505060405180910390a3600191505b5b5092915050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106102bd57805160ff19168380011785556102ea565b828001600101855582156102ea579182015b828111156102ea5782518255916020019190600101906102cf565b5b506102f79291506102fb565b5090565b61031991905b808211156102f75760008155600101610301565b5090565b90565b6105d78061032b6000396000f3006060604052361561005c5763ffffffff60e060020a60003504166313af4035811461005e5780633015a5211461007c5780638da5cb5b1461009e5780639890220b146100ca578063b46bcdaa146100dc578063f2c298be14610179575bfe5b341561006657fe5b61007a600160a060020a03600435166101e3565b005b341561008457fe5b61008c610264565b60408051918252519081900360200190f35b34156100a657fe5b6100ae61026a565b60408051600160a060020a039092168252519081900360200190f35b34156100d257fe5b61007a610279565b005b34156100e457fe5b6100f9600160e060020a0319600435166102cc565b60408051602080825283518183015283519192839290830191850190808383821561013f575b80518252602083111561013f57601f19909201916020918201910161011f565b505050905090810190601f16801561016b5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561018157fe5b6101cf600480803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284375094965061036695505050505050565b604080519115158252519081900360200190f35b60005433600160a060020a039081169116146101fe57610260565b60008054604051600160a060020a03808516939216917f70aea8d848e8a90fb7661b227dc522eb6395c3dac71b63cb59edd5c9899b236491a36000805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a0383161790555b5b50565b60025481565b600054600160a060020a031681565b60005433600160a060020a03908116911614610294576102c8565b604051600160a060020a0333811691309091163180156108fc02916000818181858888f1935050505015156102c857610000565b5b5b565b60016020818152600092835260409283902080548451600294821615610100026000190190911693909304601f810183900483028401830190945283835291929083018282801561035e5780601f106103335761010080835404028352916020019161035e565b820191906000526020600020905b81548152906001019060200180831161034157829003601f168201915b505050505081565b60006103ce826040518082805190602001908083835b6020831061039b5780518252601f19909201916020918201910161037c565b6001836020036101000a0380198251168184511680821785525050505050509050019150506040518091039020836103d6565b90505b919050565b600160e060020a03198216600090815260016020819052604082205484916002908216156101000260001901909116041561041057610503565b600160e060020a031984166000908152600160209081526040909120845161043a9286019061050b565b506002805460010190556040805160208082528551818301528551600160e060020a0319881693600160a060020a033316937f50e01e16719d6c699e516c57f4c514e77f6bc21a978d33f23980acdddbcbd0b2938993919283929083019185019080838382156104c5575b8051825260208311156104c557601f1990920191602091820191016104a5565b505050905090810190601f1680156104f15780820380516001836020036101000a031916815260200191505b509250505060405180910390a3600191505b5b5092915050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061054c57805160ff1916838001178555610579565b82800160010185558215610579579182015b8281111561057957825182559160200191906001019061055e565b5b5061058692915061058a565b5090565b6105a891905b808211156105865760008155600101610590565b5090565b905600a165627a7a723058206830357dde798fafa19dd78a4460c047f76cc132713db13442c5da7485fc0ff40029" +} diff --git a/js/src/dapps/chaindeploy/contracts/code/tokendeploy.json b/js/src/dapps/chaindeploy/contracts/code/tokendeploy.json new file mode 100644 index 0000000000000000000000000000000000000000..7dd0391b18c88113bf80094e59f236962417379b --- /dev/null +++ b/js/src/dapps/chaindeploy/contracts/code/tokendeploy.json @@ -0,0 +1,5 @@ +{ + "compiler": "v0.4.9+commit.364da425", + "source": "https://github.com/paritytech/contracts/blob/0ca02d60066202432305c8e9b1cbf33267478ab3/BasicCoin.sol", + "output": "0x606060405260008054600160a060020a03191633600160a060020a0316179055341561002757fe5b5b610f43806100376000396000f3006060604052361561007d5763ffffffff60e060020a600035041663061ea8cc811461007f57806306661abd146100ad57806313af4035146100cf5780635001f3b5146100ed5780638da5cb5b1461010f5780639507d39a1461013b5780639890220b14610179578063acfdfd1c1461018b578063c00ca38314610236575bfe5b341561008757fe5b61009b600160a060020a0360043516610280565b60408051918252519081900360200190f35b34156100b557fe5b61009b61029f565b60408051918252519081900360200190f35b34156100d757fe5b6100eb600160a060020a03600435166102a6565b005b34156100f557fe5b61009b610327565b60408051918252519081900360200190f35b341561011757fe5b61011f61032e565b60408051600160a060020a039092168252519081900360200190f35b341561014357fe5b61014e60043561033d565b60408051600160a060020a039485168152928416602084015292168183015290519081900360600190f35b341561018157fe5b6100eb610392565b005b60408051602060046024803582810135601f8101859004850286018501909652858552610222958335959394604494939290920191819084018382808284375050604080516020601f89358b0180359182018390048302840183019094528083529799988101979196509182019450925082915084018382808284375094965050509235600160a060020a031692506103e5915050565b604080519115158252519081900360200190f35b341561023e57fe5b61014e600160a060020a036004351660243561079b565b60408051600160a060020a039485168152928416602084015292168183015290519081900360600190f35b600160a060020a0381166000908152600260205260409020545b919050565b6001545b90565b60005433600160a060020a039081169116146102c157610000565b60008054604051600160a060020a03808516939216917f70aea8d848e8a90fb7661b227dc522eb6395c3dac71b63cb59edd5c9899b236491a36000805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a0383161790555b5b50565b620f424081565b600054600160a060020a031681565b600060006000600060018581548110151561035457fe5b906000526020600020906003020160005b50805460018201546002830154600160a060020a039283169750908216955016925090505b509193909250565b60005433600160a060020a039081169116146103ad57610000565b604051600160a060020a0333811691309091163180156108fc02916000818181858888f1935050505015156103e157610000565b5b5b565b60008181808088336103f56107eb565b918252600160a060020a03166020820152604080519182900301906000f080151561041c57fe5b925061042733610280565b915083600160a060020a031663ddca3f436000604051602001526040518163ffffffff1660e060020a028152600401809050602060405180830381600087803b151561046f57fe5b60325a03f1151561047c57fe5b50506040805151600160a060020a03331660009081526002602052919091209092506001840191506104ae90826107fb565b50600154600160a060020a03331660009081526002602052604090208054849081106104d657fe5b906000526020600020900160005b5055600180548082016104f78382610825565b916000526020600020906003020160005b60606040519081016040528087600160a060020a0316815260200133600160a060020a0316815260200188600160a060020a0316815250909190915060008201518160000160006101000a815481600160a060020a030219169083600160a060020a0316021790555060208201518160010160006101000a815481600160a060020a030219169083600160a060020a0316021790555060408201518160020160006101000a815481600160a060020a030219169083600160a060020a0316021790555050505083600160a060020a0316637b1a547c82858b620f42408c336000604051602001526040518763ffffffff1660e060020a0281526004018086600160a060020a0316600160a060020a03168152602001806020018581526020018060200184600160a060020a0316600160a060020a03168152602001838103835287818151815260200191508051906020019080838360008314610686575b80518252602083111561068657601f199092019160209182019101610666565b505050905090810190601f1680156106b25780820380516001836020036101000a031916815260200191505b50838103825285518152855160209182019187019080838382156106f1575b8051825260208311156106f157601f1990920191602091820191016106d1565b505050905090810190601f16801561071d5780820380516001836020036101000a031916815260200191505b509750505050505050506020604051808303818588803b151561073c57fe5b61235a5a03f1151561074a57fe5b5050604051600160a060020a03808716935087811692503316907f454b0172f64812df0cd504c2bd7df7aab8ff328a54d946b4bd0fa7c527adf9cc90600090a4600194505b50505050949350505050565b600160a060020a03821660009081526002602052604081208054829182916107dd9190869081106107c857fe5b906000526020600020900160005b505461033d565b9250925092505b9250925092565b604051610650806108c883390190565b81548183558181151161081f5760008381526020902061081f918101908301610857565b5b505050565b81548183558181151161081f5760030281600302836000526020600020918201910161081f9190610878565b5b505050565b6102a391905b80821115610871576000815560010161085d565b5090565b90565b6102a391905b8082111561087157805473ffffffffffffffffffffffffffffffffffffffff19908116825560018201805482169055600282018054909116905560030161087e565b5090565b905600606060405260008054600160a060020a03191633600160a060020a0316179055341561002757fe5b6040516040806106508339810160405280516020909101515b600034111561004e57610000565b8180151561005b57610000565b600183905560008054600160a060020a031916600160a060020a038416908117825581526002602052604090208390555b5b505b50505b6105af806100a16000396000f3006060604052361561007d5763ffffffff60e060020a600035041663095ea7b3811461009257806313af4035146100c557806318160ddd146100e357806323b872dd146101055780635001f3b51461013e57806370a08231146101605780638da5cb5b1461018e578063a9059cbb146101ba578063dd62ed3e146101ed575b341561008557fe5b6100905b610000565b565b005b341561009a57fe5b6100b1600160a060020a0360043516602435610221565b604080519115158252519081900360200190f35b34156100cd57fe5b610090600160a060020a03600435166102b6565b005b34156100eb57fe5b6100f3610337565b60408051918252519081900360200190f35b341561010d57fe5b6100b1600160a060020a036004358116906024351660443561033d565b604080519115158252519081900360200190f35b341561014657fe5b6100f361045d565b60408051918252519081900360200190f35b341561016857fe5b6100f3600160a060020a0360043516610464565b60408051918252519081900360200190f35b341561019657fe5b61019e610483565b60408051600160a060020a039092168252519081900360200190f35b34156101c257fe5b6100b1600160a060020a0360043516602435610492565b604080519115158252519081900360200190f35b34156101f557fe5b6100f3600160a060020a0360043581169060243516610552565b60408051918252519081900360200190f35b6000600034111561023157610000565b82600160a060020a031633600160a060020a03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040518082815260200191505060405180910390a350600160a060020a0333811660009081526002602090815260408083209386168352600193840190915290208054830190555b5b92915050565b60005433600160a060020a039081169116146102d157610000565b60008054604051600160a060020a03808516939216917f70aea8d848e8a90fb7661b227dc522eb6395c3dac71b63cb59edd5c9899b236491a36000805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a0383161790555b5b50565b60015481565b6000600034111561034d57610000565b600160a060020a038416600090815260026020526040902054849083908190101561037757610000565b600160a060020a0380871660009081526002602090815260408083203394851684526001019091529020548791908690819010156103b457610000565b87600160a060020a031689600160a060020a03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef896040518082815260200191505060405180910390a3600160a060020a03808a16600090815260026020818152604080842033861685526001808201845282862080548f900390559390925281548c9003909155928b16825291902080548901905595505b5b5050505b50505b9392505050565b620f424081565b600160a060020a0381166000908152600260205260409020545b919050565b600054600160a060020a031681565b600060003411156104a257610000565b33600160a060020a0381166000908152600260205260409020548390819010156104cb57610000565b84600160a060020a031633600160a060020a03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef866040518082815260200191505060405180910390a3600160a060020a0333811660009081526002602052604080822080548890039055918716815220805485019055600192505b5b50505b92915050565b600160a060020a03808316600090815260026020908152604080832093851683526001909301905220545b929150505600a165627a7a72305820b5bf89a0a85c15df1e9717e49be06fe1a4f9dcc1e0cebf5483dd1c0bcd14a0910029a165627a7a723058207f96b7ad40c02cfaeaf29e65c79456dd3fd9828c9d3fbaf801fb60010456c3880029" +} diff --git a/js/src/dapps/chaindeploy/contracts/code/tokenreg.json b/js/src/dapps/chaindeploy/contracts/code/tokenreg.json new file mode 100644 index 0000000000000000000000000000000000000000..bcba3412738ab5799e8a5e481926ba4e9f6d7b3a --- /dev/null +++ b/js/src/dapps/chaindeploy/contracts/code/tokenreg.json @@ -0,0 +1,5 @@ +{ + "compiler": "v0.4.9+commit.364da425", + "source": "https://github.com/paritytech/contracts/blob/58842b92c00e3c45a84b6d0ac9b842f016dde50a/TokenReg.sol", + "output": "0x606060405260008054600160a060020a03191633600160a060020a0316179055670de0b6b3a7640000600455341561003357fe5b5b611473806100436000396000f300606060405236156100b45763ffffffff60e060020a600035041663044215c681146100b657806313af4035146101df57806366b42dcb146101fd57806369fe0e2d146102ab5780637958533a146102c05780637b1a547c146102e8578063891de9ed146103a15780638da5cb5b146104bc5780639890220b146104e85780639f181b5e146104fa578063a02b161e1461051c578063b72e717d14610531578063dd93890b1461066a578063ddca3f4314610685575bfe5b34156100be57fe5b6100c96004356106a7565b60408051600160a060020a038088168252918101859052908216608082015260a060208083018281528751928401929092528651606084019160c0850191908901908083838215610135575b80518252602083111561013557601f199092019160209182019101610115565b505050905090810190601f1680156101615780820380516001836020036101000a031916815260200191505b50838103825285518152855160209182019187019080838382156101a0575b8051825260208311156101a057601f199092019160209182019101610180565b505050905090810190601f1680156101cc5780820380516001836020036101000a031916815260200191505b5097505050505050505060405180910390f35b34156101e757fe5b6101fb600160a060020a036004351661083a565b005b60408051602060046024803582810135601f8101859004850286018501909652858552610297958335600160a060020a0316959394604494939290920191819084018382808284375050604080516020601f818a01358b0180359182018390048302840183018552818452989a8a359a9099940197509195509182019350915081908401838280828437509496506108ae95505050505050565b604080519115158252519081900360200190f35b34156102b357fe5b6101fb6004356108c8565b005b34156102c857fe5b6102d66004356024356108ed565b60408051918252519081900360200190f35b60408051602060046024803582810135601f8101859004850286018501909652858552610297958335600160a060020a0316959394604494939290920191819084018382808284375050604080516020601f818a01358b0180359182018390048302840183018552818452989a8a359a90999401975091955091820193509150819084018382808284375094965050509235600160a060020a0316925061092c915050565b604080519115158252519081900360200190f35b34156103a957fe5b6103f7600480803590602001908201803590602001908080601f01602080910402602001604051908101604052809392919081815260200183838082843750949650610c6695505050505050565b6040518086815260200185600160a060020a0316600160a060020a031681526020018481526020018060200183600160a060020a0316600160a060020a0316815260200182810382528481815181526020019150805190602001908083836000831461047e575b80518252602083111561047e57601f19909201916020918201910161045e565b505050905090810190601f1680156104aa5780820380516001836020036101000a031916815260200191505b50965050505050505060405180910390f35b34156104c457fe5b6104cc610dca565b60408051600160a060020a039092168252519081900360200190f35b34156104f057fe5b6101fb610dd9565b005b341561050257fe5b6102d6610e2c565b60408051918252519081900360200190f35b341561052457fe5b6101fb600435610e33565b005b341561053957fe5b61054d600160a060020a0360043516611047565b60405180868152602001806020018581526020018060200184600160a060020a0316600160a060020a03168152602001838103835287818151815260200191508051906020019080838360008314610135575b80518252602083111561013557601f199092019160209182019101610115565b505050905090810190601f1680156101615780820380516001836020036101000a031916815260200191505b50838103825285518152855160209182019187019080838382156101a0575b8051825260208311156101a057601f199092019160209182019101610180565b505050905090810190601f1680156101cc5780820380516001836020036101000a031916815260200191505b5097505050505050505060405180910390f35b341561067257fe5b6101fb6004356024356044356111f3565b005b341561068d57fe5b6102d66112b0565b60408051918252519081900360200190f35b60006106b16112b6565b60006106bb6112b6565b600060006003878154811015156106ce57fe5b906000526020600020906006020160005b50805460018083018054604080516020601f600260001997861615610100029790970190941695909504928301859004850281018501909152818152600160a060020a039094169a5093945091929083018282801561077f5780601f106107545761010080835404028352916020019161077f565b820191906000526020600020905b81548152906001019060200180831161076257829003601f168201915b505050600280850154600386018054604080516020601f6000196101006001871615020190941696909604928301869004860281018601909152818152969b50919950935091508301828280156108175780601f106107ec57610100808354040283529160200191610817565b820191906000526020600020905b8154815290600101906020018083116107fa57829003601f168201915b50505050600483015491945050600160a060020a031691505b5091939590929450565b60005433600160a060020a03908116911614610855576108aa565b60008054604051600160a060020a03808516939216917f70aea8d848e8a90fb7661b227dc522eb6395c3dac71b63cb59edd5c9899b236491a360008054600160a060020a031916600160a060020a0383161790555b5b50565b60006108bd858585853361092c565b90505b949350505050565b60005433600160a060020a039081169116146108e3576108aa565b60048190555b5b50565b60006003838154811015156108fe57fe5b906000526020600020906006020160005b506000838152600591909101602052604090205490505b92915050565b600060045434101561093d57610c5d565b600160a060020a03861660009081526001602052604090205486901561096257610c5b565b8551869060031461097257610c59565b866002816040518082805190602001908083835b602083106109a55780518252601f199092019160209182019101610986565b51815160209384036101000a60001901801990921691161790529201948552506040519384900301909220541591506109df905057610c56565b60038054600181016109f183826112c8565b916000526020600020906006020160005b506040805160a081018252600160a060020a03808e1680835260208084018f90529383018d9052606083018c9052908a1660808301528354600160a060020a0319161783558b51909291610a5d9160018401918e01906112fa565b506040820151600282015560608201518051610a839160038401916020909101906112fa565b506080919091015160049091018054600160a060020a031916600160a060020a03928316179055600354908b1660009081526001602090815260409182902083905590518b519293506002928c928291908401908083835b60208310610afa5780518252601f199092019160209182019101610adb565b51815160001960209485036101000a81019182169119929092161790915293909101958652604051958690038101862096909655506003548d519101948d9493508392508401908083835b60208310610b645780518252601f199092019160209182019101610b45565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405180910390207f25074d730da65a10e171fe5589d2182313ef00da38d23a9ae3b78923568bdf2d8b896040518083600160a060020a0316600160a060020a0316815260200180602001828103825283818151815260200191508051906020019080838360008314610c17575b805182526020831115610c1757601f199092019160209182019101610bf7565b505050905090810190601f168015610c435780820380516001836020036101000a031916815260200191505b50935050505060405180910390a3600193505b5b505b505b505b95945050505050565b600060006000610c746112b6565b6000600060016002886040518082805190602001908083835b60208310610cac5780518252601f199092019160209182019101610c8d565b6001836020036101000a038019825116818451168082178552505050505050905001915050908152602001604051809103902054039550600386815481101515610cf257fe5b906000526020600020906006020160005b5080546002808301546003840180546040805160206101006001851615026000190190931695909504601f8101839004830286018301909152808552600160a060020a039095169a50919850939450909291908301828280156108175780601f106107ec57610100808354040283529160200191610817565b820191906000526020600020905b8154815290600101906020018083116107fa57829003601f168201915b50505050600483015491945050600160a060020a031691505b5091939590929450565b600054600160a060020a031681565b60005433600160a060020a03908116911614610df457610e28565b604051600160a060020a0333811691309091163180156108fc02916000818181858888f193505050501515610e2857610000565b5b5b565b6003545b90565b60005433600160a060020a03908116911614610e4e576108aa565b80600382815481101515610e5e57fe5b906000526020600020906006020160005b506001016040518082805460018160011615610100020316600290048015610ece5780601f10610eac576101008083540402835291820191610ece565b820191906000526020600020905b815481529060010190602001808311610eba575b505060405190819003812092507f96e76fa77fea85d8abeb7533fdb8288c214bb1dcf1f867c8f36a95f1f509c1759150600090a360016000600383815481101515610f1557fe5b906000526020600020906006020160005b5054600160a060020a031681526020810191909152604001600090812055600380546002919083908110610f5657fe5b906000526020600020906006020160005b506001016040518082805460018160011615610100020316600290048015610fc65780601f10610fa4576101008083540402835291820191610fc6565b820191906000526020600020905b815481529060010190602001808311610fb2575b50509283525050604051908190036020019020600090556003805482908110610feb57fe5b906000526020600020906006020160005b8154600160a060020a0319168255611018600183016000611379565b60028201600090556003820160006110309190611379565b506004018054600160a060020a03191690555b5b50565b60006110516112b6565b600061105b6112b6565b600160a060020a03851660009081526001602052604081205460038054600019909201965082918790811061108c57fe5b906000526020600020906006020160005b509050806001018054600181600116156101000203166002900480601f01602080910402602001604051908101604052809291908181526020018280546001816001161561010002031660029004801561077f5780601f106107545761010080835404028352916020019161077f565b820191906000526020600020905b81548152906001019060200180831161076257829003601f168201915b505050600280850154600386018054604080516020601f6000196101006001871615020190941696909604928301869004860281018601909152818152969b50919950935091508301828280156108175780601f106107ec57610100808354040283529160200191610817565b820191906000526020600020905b8154815290600101906020018083116107fa57829003601f168201915b50505050600483015491945050600160a060020a031691505b5091939590929450565b8233600160a060020a031660038281548110151561120d57fe5b906000526020600020906006020160005b5060040154600160a060020a031614611236576112a9565b8160038581548110151561124657fe5b906000526020600020906006020160005b50600085815260059190910160209081526040918290209290925580518481529051859287927f7991c63a749706fd298fc2387764d640be6e714307b6357b1d3c2ce35cba3b52929081900390910190a35b5b50505050565b60045481565b60408051602081019091526000815290565b8154818355818115116112f4576006028160060283600052602060002091820191016112f491906113c1565b5b505050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061133b57805160ff1916838001178555611368565b82800160010185558215611368579182015b8281111561136857825182559160200191906001019061134d565b5b50611375929150611426565b5090565b50805460018160011615610100020316600290046000825580601f1061139f57506108aa565b601f0160209004906000526020600020908101906108aa9190611426565b5b50565b610e3091905b80821115611375578054600160a060020a031916815560006113ec6001830182611379565b60028201600090556003820160006114049190611379565b50600481018054600160a060020a03191690556006016113c7565b5090565b90565b610e3091905b80821115611375576000815560010161142c565b5090565b905600a165627a7a72305820ab1d1a18270ba278cc2f74cd1b7b547cdcd6308a9df1dec1120fa9f6199b1f480029" +} diff --git a/js/src/dapps/chaindeploy/contracts/code/verifyEmail.json b/js/src/dapps/chaindeploy/contracts/code/verifyEmail.json new file mode 100644 index 0000000000000000000000000000000000000000..a87fb0a2284f0e161a19edf84fcf9bfd8eeb8885 --- /dev/null +++ b/js/src/dapps/chaindeploy/contracts/code/verifyEmail.json @@ -0,0 +1,5 @@ +{ + "compiler": "v0.4.9+commit.364da425", + "source": "https://github.com/paritytech/contracts/blob/e5afdacc716ca743ceddf80978d4e6b2b465dbe9/ProofOfEmail.sol", + "output": "0x606060405260008054600160a060020a03191633600160a060020a0316178155600355341561002a57fe5b5b6108a48061003a6000396000f300606060405236156100ca5763ffffffff60e060020a60003504166306b2ff4781146100cc57806313af4035146100fc5780632650b9881461011a5780634b59e8801461018c57806359c87d70146101b05780636795dbcd146101bd57806369fe0e2d1461023057806370c4d5f214610245578063797af627146103255780638da5cb5b1461034c5780639890220b14610378578063ac72c1201461038a578063cc1d4c02146103b1578063ddca3f43146103e1578063df57b74214610403578063e30bd74014610432575bfe5b34156100d457fe5b6100e8600160a060020a03600435166104ce565b604080519115158252519081900360200190f35b341561010457fe5b610118600160a060020a03600435166104d6565b005b341561012257fe5b60408051602060046024803582810135601f810185900485028601850190965285855261017a958335600160a060020a0316959394604494939290920191819084018382808284375094965061055795505050505050565b60408051918252519081900360200190f35b341561019457fe5b610118600160a060020a0360043516602435604435610577565b005b6101186004356105eb565b005b34156101c557fe5b60408051602060046024803582810135601f8101859004850286018501909652858552610214958335959394604494939290920191819084018382808284375094965061063595505050505050565b60408051600160a060020a039092168252519081900360200190f35b341561023857fe5b610118600435610654565b005b341561024d57fe5b60408051602060046024803582810135601f81018590048502860185019096528585526102a5958335600160a060020a0316959394604494939290920191819084018382808284375094965061067995505050505050565b6040805160208082528351818301528351919283929083019185019080838382156102eb575b8051825260208311156102eb57601f1990920191602091820191016102cb565b505050905090810190601f1680156103175780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561032d57fe5b6100e8600435610688565b604080519115158252519081900360200190f35b341561035457fe5b610214610791565b60408051600160a060020a039092168252519081900360200190f35b341561038057fe5b6101186107a0565b005b341561039257fe5b6100e86004356107f3565b604080519115158252519081900360200190f35b34156103b957fe5b6100e8600160a060020a0360043516610813565b604080519115158252519081900360200190f35b34156103e957fe5b61017a610834565b60408051918252519081900360200190f35b341561040b57fe5b61021460043561083a565b60408051600160a060020a039092168252519081900360200190f35b341561043a57fe5b6102a5600160a060020a0360043516610858565b6040805160208082528351818301528351919283929083019185019080838382156102eb575b8051825260208311156102eb57601f1990920191602091820191016102cb565b505050905090810190601f1680156103175780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b60005b919050565b60005433600160a060020a039081169116146104f157610553565b60008054604051600160a060020a03808516939216917f70aea8d848e8a90fb7661b227dc522eb6395c3dac71b63cb59edd5c9899b236491a36000805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a0383161790555b5b50565b600160a060020a0382166000908152600260205260409020545b92915050565b60005433600160a060020a03908116911614610592576105e5565b600082815260046020908152604091829020839055815184815291518392600160a060020a038716927f76babef7e9f1065118be3f9d7094016a1cc06dd12811501c7712deb22144f37b92918290030190a35b5b505050565b6003543410156105fa57610553565b6040518190600160a060020a033316907f070669e6be82aa9b077a096b0f9617893a4dc5cb05897e27fd7a6112c8e6629e90600090a35b5b50565b600082815260016020526040902054600160a060020a03165b92915050565b60005433600160a060020a0390811691161461066f57610553565b60038190555b5b50565b610571610866565b5b92915050565b6040805182815281516020918190038201902060009081526004909152908120548015156106b55761078b565b6040805184815281516020918190038201902060009081526004825282812081905583815260019091522054600160a060020a031615158061070e5750600160a060020a03331660009081526002602052604090205415155b156107185761078b565b6000818152600160209081526040808320805473ffffffffffffffffffffffffffffffffffffffff191633600160a060020a031690811790915580845260029092528083208490555190917fd415b905d4dd806bfba99a7a0e6351bd0c9db3a9912add21c0e6bef4479f673f91a2600191505b50919050565b600054600160a060020a031681565b60005433600160a060020a039081169116146107bb576107ef565b604051600160a060020a0333811691309091163180156108fc02916000818181858888f1935050505015156107ef57610000565b5b5b565b600081815260016020526040902054600160a060020a031615155b919050565b600160a060020a03811660009081526002602052604090205415155b919050565b60035481565b600081815260016020526040902054600160a060020a03165b919050565b6104d1610866565b5b919050565b604080516020810190915260008152905600a165627a7a7230582081d03388dd06c78ee4098c4f1e23cd3c25e38d249d5da59962c6b28ec6e20ea70029" +} diff --git a/js/src/dapps/chaindeploy/contracts/code/verifySms.json b/js/src/dapps/chaindeploy/contracts/code/verifySms.json new file mode 100644 index 0000000000000000000000000000000000000000..0cfc43f9d6a73897056edb27e97b15b149a07167 --- /dev/null +++ b/js/src/dapps/chaindeploy/contracts/code/verifySms.json @@ -0,0 +1,5 @@ +{ + "compiler": "v0.4.9+commit.364da425", + "source": "https://github.com/paritytech/contracts/blob/58842b92c00e3c45a84b6d0ac9b842f016dde50a/SMSVerification.sol", + "output": "0x606060405260008054600160a060020a033316600160a060020a03199182168117909255600280549091169091179055666a94d74f430000600455341561004257fe5b5b6109a0806100526000396000f300606060405236156100ca5763ffffffff60e060020a60003504166313af403581146100cc57806314253887146100ea578063338cdca1146101085780633da5c3ce146101125780635283f3391461013357806369fe0e2d146101af57806374a8f103146101c4578063797af627146101e25780638da5cb5b146102095780639890220b14610235578063c89e436114610247578063ca4cbf6714610273578063ca5eb5e1146102e5578063cc1d4c0214610303578063ddca3f4314610333578063fc2525ab14610273575bfe5b34156100d457fe5b6100e8600160a060020a03600435166103c7565b005b34156100f257fe5b6100e8600160a060020a0360043516610448565b005b6100e86104b4565b005b341561011a57fe5b6100e8600160a060020a0360043516602435610521565b005b341561013b57fe5b60408051602060046024803582810135601f8101859004850286018501909652858552610193958335600160a060020a0316959394604494939290920191819084018382808284375094965061059295505050505050565b60408051600160a060020a039092168252519081900360200190f35b34156101b757fe5b6100e8600435610623565b005b34156101cc57fe5b6100e8600160a060020a0360043516610648565b005b34156101ea57fe5b6101f56004356106d9565b604080519115158252519081900360200190f35b341561021157fe5b610193610771565b60408051600160a060020a039092168252519081900360200190f35b341561023d57fe5b6100e8610780565b005b341561024f57fe5b6101936107d3565b60408051600160a060020a039092168252519081900360200190f35b341561027b57fe5b60408051602060046024803582810135601f81018590048502860185019096528585526102d3958335600160a060020a0316959394604494939290920191819084018382808284375094965061059295505050505050565b60408051918252519081900360200190f35b34156102ed57fe5b6100e8600160a060020a0360043516610873565b005b341561030b57fe5b6101f5600160a060020a03600435166108bb565b604080519115158252519081900360200190f35b341561033b57fe5b6102d36108dd565b60408051918252519081900360200190f35b341561027b57fe5b60408051602060046024803582810135601f81018590048502860185019096528585526102d3958335600160a060020a0316959394604494939290920191819084018382808284375094965061059295505050505050565b60408051918252519081900360200190f35b60005433600160a060020a039081169116146103e257610444565b60008054604051600160a060020a03808516939216917f70aea8d848e8a90fb7661b227dc522eb6395c3dac71b63cb59edd5c9899b236491a36000805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a0383161790555b5b50565b60025433600160a060020a0390811691161461046357610444565b600160a060020a0381166000818152600160208190526040808320805460ff1916909217909155517fd415b905d4dd806bfba99a7a0e6351bd0c9db3a9912add21c0e6bef4479f673f9190a25b5b50565b6004543410156104c35761051e565b600160a060020a03331660009081526001602052604090205460ff16156104e95761051e565b604051600160a060020a033316907f039f711c9c18dd815b225b1424855e6118e746c6b5d688907f10c4dd29ebe92a90600090a25b5b565b60025433600160a060020a0390811691161461053c5761058d565b600160a060020a038216600081815260036020908152604091829020849055815184815291517fa9a343b39eac85ffb326e93ecd46785b814e72dc9f2b33bb0b4a315ba2859f439281900390910190a25b5b5050565b60006001600084600160a060020a0316600160a060020a03168152602001908152602001600020600101826040518082805190602001908083835b602083106105ec5780518252601f1990920191602091820191016105cd565b51815160209384036101000a6000190180199092169116179052920194855250604051938490030190922054925050505b92915050565b60005433600160a060020a0390811691161461063e57610444565b60048190555b5b50565b60025433600160a060020a0390811691161461066357610444565b600160a060020a038116600090815260016020526040902054819060ff16151561068c5761058d565b600160a060020a038216600081815260016020526040808220805460ff19169055517fb6fa8b8bd5eab60f292eca876e3ef90722275b785309d84b1de113ce0b8c4e749190a25b5b505b50565b6040805182815281516020918190038201902033600160a060020a031660009081526003909252918120549091146107105761076c565b600160a060020a0333166000818152600360209081526040808320839055600191829052808320805460ff1916909217909155517fd415b905d4dd806bfba99a7a0e6351bd0c9db3a9912add21c0e6bef4479f673f9190a25060015b919050565b600054600160a060020a031681565b60005433600160a060020a0390811691161461079b5761051e565b604051600160a060020a0333811691309091163180156108fc02916000818181858888f19350505050151561051e57610000565b5b5b565b600254600160a060020a031681565b60006001600084600160a060020a0316600160a060020a03168152602001908152602001600020600101826040518082805190602001908083835b602083106105ec5780518252601f1990920191602091820191016105cd565b51815160209384036101000a6000190180199092169116179052920194855250604051938490030190922054925050505b92915050565b60005433600160a060020a0390811691161461088e57610444565b6002805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a0383161790555b5b50565b600160a060020a03811660009081526001602052604090205460ff165b919050565b60045481565b60006001600084600160a060020a0316600160a060020a03168152602001908152602001600020600101826040518082805190602001908083835b602083106105ec5780518252601f1990920191602091820191016105cd565b51815160209384036101000a6000190180199092169116179052920194855250604051938490030190922054925050505b929150505600a165627a7a72305820f6beb9c0ae3b45609ad6fc26c1b74600cbaa5f0088ca07be3e9c392a12b2c6150029" +} diff --git a/js/src/dapps/chaindeploy/contracts/dappreg.js b/js/src/dapps/chaindeploy/contracts/dappreg.js new file mode 100644 index 0000000000000000000000000000000000000000..71379bf8bf873f44f718831b9a351ef21c99130b --- /dev/null +++ b/js/src/dapps/chaindeploy/contracts/dappreg.js @@ -0,0 +1,30 @@ +// 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 abi from '~/contracts/abi/dappreg'; +import { compiler, source as sourceUrl, output as byteCode } from './code/dappreg'; + +const id = 'dappreg'; // 7bbc4f1a27628781b96213e781a1b8eec6982c1db8fac739af6e4c5a55862c03 +const deployParams = []; + +export { + abi, + byteCode, + compiler, + deployParams, + id, + sourceUrl +}; diff --git a/js/src/dapps/chaindeploy/contracts/gavcoin.js b/js/src/dapps/chaindeploy/contracts/gavcoin.js new file mode 100644 index 0000000000000000000000000000000000000000..f7518ef53f01441fef1e343cbc3d9663a00162db --- /dev/null +++ b/js/src/dapps/chaindeploy/contracts/gavcoin.js @@ -0,0 +1,32 @@ +// 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 abi from '~/contracts/abi/gavcoin'; +import { compiler, source as sourceUrl, output as byteCode } from './code/gavcoin'; + +const isExternal = true; +const id = 'gavcoin'; +const deployParams = []; + +export { + abi, + byteCode, + compiler, + deployParams, + id, + isExternal, + sourceUrl +}; diff --git a/js/src/dapps/chaindeploy/contracts/githubhint.js b/js/src/dapps/chaindeploy/contracts/githubhint.js new file mode 100644 index 0000000000000000000000000000000000000000..5198e38b5db9e8520ff3870d33c7698dfe74c55c --- /dev/null +++ b/js/src/dapps/chaindeploy/contracts/githubhint.js @@ -0,0 +1,30 @@ +// 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 abi from '~/contracts/abi/githubhint'; +import { compiler, source as sourceUrl, output as byteCode } from './code/githubhint'; + +const id = 'githubhint'; +const deployParams = []; + +export { + abi, + byteCode, + compiler, + deployParams, + id, + sourceUrl +}; diff --git a/js/src/dapps/chaindeploy/contracts/index.js b/js/src/dapps/chaindeploy/contracts/index.js new file mode 100644 index 0000000000000000000000000000000000000000..5e066f758c449e08f8a2fad3b0448d7309921824 --- /dev/null +++ b/js/src/dapps/chaindeploy/contracts/index.js @@ -0,0 +1,52 @@ +// 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 * as badgereg from './badgereg'; +import * as dappreg from './dappreg'; +import * as gavcoin from './gavcoin'; +import * as githubhint from './githubhint'; +import * as jgvoting from './jg-voting'; +import * as registry from './registry'; +import * as signaturereg from './signaturereg'; +import * as tokendeployMgr from './tokendeployMgr'; +import * as tokendeployReg from './tokendeployReg'; +import * as tokenreg from './tokenreg'; +import * as verifyEmail from './verifyEmail'; +import * as verifySms from './verifySms'; +import * as wallet from './wallet'; + +const contracts = [ + // builtin + githubhint, + badgereg, + dappreg, + signaturereg, + tokenreg, + tokendeployReg, + tokendeployMgr, + verifyEmail, + verifySms, + wallet, + + // external + gavcoin, + jgvoting +]; + +export { + contracts, + registry +}; diff --git a/js/src/dapps/chaindeploy/contracts/jg-voting.js b/js/src/dapps/chaindeploy/contracts/jg-voting.js new file mode 100644 index 0000000000000000000000000000000000000000..8ffbed3c565bb0af593fce9d60ff16b42d58dff8 --- /dev/null +++ b/js/src/dapps/chaindeploy/contracts/jg-voting.js @@ -0,0 +1,32 @@ +// 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 abi from './abi/jg-voting'; +import { compiler, source as sourceUrl, output as byteCode } from './code/jg-voting'; + +const isExternal = true; +const id = 'jg-voting'; +const deployParams = []; + +export { + abi, + byteCode, + compiler, + deployParams, + id, + isExternal, + sourceUrl +}; diff --git a/js/src/dapps/chaindeploy/contracts/registry.js b/js/src/dapps/chaindeploy/contracts/registry.js new file mode 100644 index 0000000000000000000000000000000000000000..1b44f4245ac737dab241fdf6ca8b26784a2c5bb4 --- /dev/null +++ b/js/src/dapps/chaindeploy/contracts/registry.js @@ -0,0 +1,30 @@ +// 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 abi from '~/contracts/abi/registry2'; +import { compiler, source as sourceUrl, output as byteCode } from './code/registry'; + +const id = 'registry'; +const deployParams = []; + +export { + abi, + byteCode, + compiler, + deployParams, + id, + sourceUrl +}; diff --git a/js/src/dapps/chaindeploy/contracts/signaturereg.js b/js/src/dapps/chaindeploy/contracts/signaturereg.js new file mode 100644 index 0000000000000000000000000000000000000000..6d9f9bf609cbfd5b8a537fe60a88ed879ffa5417 --- /dev/null +++ b/js/src/dapps/chaindeploy/contracts/signaturereg.js @@ -0,0 +1,30 @@ +// 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 abi from '~/contracts/abi/signaturereg'; +import { compiler, source as sourceUrl, output as byteCode } from './code/signaturereg'; + +const id = 'signaturereg'; +const deployParams = []; + +export { + abi, + byteCode, + compiler, + deployParams, + id, + sourceUrl +}; diff --git a/js/src/dapps/chaindeploy/contracts/tokendeployMgr.js b/js/src/dapps/chaindeploy/contracts/tokendeployMgr.js new file mode 100644 index 0000000000000000000000000000000000000000..d07435fdddbda6842c8515dbae2bad5a5c6e01c8 --- /dev/null +++ b/js/src/dapps/chaindeploy/contracts/tokendeployMgr.js @@ -0,0 +1,30 @@ +// 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 abi from '~/contracts/abi/basiccoinmanager'; +import { compiler, source as sourceUrl, output as byteCode } from './code/tokendeploy'; + +const id = 'basiccoinmgr'; +const deployParams = []; + +export { + abi, + byteCode, + compiler, + deployParams, + id, + sourceUrl +}; diff --git a/js/src/dapps/chaindeploy/contracts/tokendeployReg.js b/js/src/dapps/chaindeploy/contracts/tokendeployReg.js new file mode 100644 index 0000000000000000000000000000000000000000..1db07e91a50d494982e8c081dfcca35476716b71 --- /dev/null +++ b/js/src/dapps/chaindeploy/contracts/tokendeployReg.js @@ -0,0 +1,28 @@ +// 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 { abi, sourceUrl, deployParams, compiler, byteCode } from './tokenreg'; + +const id = 'basiccoinreg'; + +export { + abi, + byteCode, + compiler, + deployParams, + id, + sourceUrl +}; diff --git a/js/src/dapps/chaindeploy/contracts/tokenreg.js b/js/src/dapps/chaindeploy/contracts/tokenreg.js new file mode 100644 index 0000000000000000000000000000000000000000..11ee29fa1f881c9e3bd7cfae1f77109a3fd8bb5c --- /dev/null +++ b/js/src/dapps/chaindeploy/contracts/tokenreg.js @@ -0,0 +1,30 @@ +// 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 abi from '~/contracts/abi/tokenreg'; +import { compiler, source as sourceUrl, output as byteCode } from './code/tokenreg'; + +const id = 'tokenreg'; +const deployParams = []; + +export { + abi, + byteCode, + compiler, + deployParams, + id, + sourceUrl +}; diff --git a/js/src/dapps/chaindeploy/contracts/verifyEmail.js b/js/src/dapps/chaindeploy/contracts/verifyEmail.js new file mode 100644 index 0000000000000000000000000000000000000000..0176d594954de0b3445c5bbcb95a34d0cd720758 --- /dev/null +++ b/js/src/dapps/chaindeploy/contracts/verifyEmail.js @@ -0,0 +1,37 @@ +// 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 abi from '~/contracts/abi/email-verification'; +import { compiler, source as sourceUrl, output as byteCode } from './code/verifyEmail'; + +const isBadge = true; +const id = 'emailverification'; +const deployParams = []; +const badgeSource = { + imageUrl: 'https://raw.githubusercontent.com/paritytech/dapp-assets/c4721a87cb95375da91f8699438d8d7907b3f5e9/certifications/email-verification.svg', + imageHash: '0x5617a14da2a0c210939da6eafb734e60906f64a504c3e107812668860a752dc6' +}; + +export { + abi, + badgeSource, + byteCode, + compiler, + deployParams, + id, + isBadge, + sourceUrl +}; diff --git a/js/src/dapps/chaindeploy/contracts/verifySms.js b/js/src/dapps/chaindeploy/contracts/verifySms.js new file mode 100644 index 0000000000000000000000000000000000000000..d7ea509022f90e8eddcd0922076f65aab044820e --- /dev/null +++ b/js/src/dapps/chaindeploy/contracts/verifySms.js @@ -0,0 +1,37 @@ +// 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 abi from '~/contracts/abi/sms-verification'; +import { compiler, source as sourceUrl, output as byteCode } from './code/verifySms'; + +const isBadge = true; +const id = 'smsverification'; +const deployParams = []; +const badgeSource = { + imageUrl: 'https://raw.githubusercontent.com/paritytech/dapp-assets/1b1beb57ab1f4d3a93a12711b233b5cded791a2f/certifications/sms-verification.svg', + imageHash: '0x49fa653c35c0a9ce128579883babd673ad4cfc94bf9f1cfe96a2bbc30a7552c6' +}; + +export { + abi, + badgeSource, + byteCode, + compiler, + deployParams, + id, + isBadge, + sourceUrl +}; diff --git a/js/src/dapps/chaindeploy/contracts/wallet.js b/js/src/dapps/chaindeploy/contracts/wallet.js new file mode 100644 index 0000000000000000000000000000000000000000..6639cba801716d00956b1ea689c8239b94425396 --- /dev/null +++ b/js/src/dapps/chaindeploy/contracts/wallet.js @@ -0,0 +1,29 @@ +// 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 { walletCompiler as compiler, walletLibrary as byteCode, walletLibraryABI as abiJson, walletLibraryRegKey as id, walletSource as sourceUrl } from '~/contracts/code/wallet'; + +const abi = JSON.parse(abiJson); +const deployParams = []; + +export { + abi, + byteCode, + compiler, + deployParams, + id, + sourceUrl +}; diff --git a/js/src/dapps/chaindeploy/dapps/console.js b/js/src/dapps/chaindeploy/dapps/console.js new file mode 100644 index 0000000000000000000000000000000000000000..4e599b2faa3e2506d92479beefc7a75b96289d21 --- /dev/null +++ b/js/src/dapps/chaindeploy/dapps/console.js @@ -0,0 +1,33 @@ +// 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 builtins from '~/views/Dapps/builtin.json'; + +const id = 'console'; +const app = builtins.find((app) => app.url === id); +const hashId = app.id; +const source = { + imageUrl: 'https://raw.githubusercontent.com/paritytech/console/3ea0dbfefded359ccdbea37bc4cf350c0aa16948/console.jpeg', + imageHash: '0xc3962e2eab7afaeb9cd11522381723afbafdc41dc7ba31bee472e187c4459e81' +}; +const name = app.name; + +export { + hashId, + id, + name, + source +}; diff --git a/js/src/dapps/chaindeploy/dapps/dappreg.js b/js/src/dapps/chaindeploy/dapps/dappreg.js new file mode 100644 index 0000000000000000000000000000000000000000..fec35ef763170e0b270ab79f76f0c535ac4cf014 --- /dev/null +++ b/js/src/dapps/chaindeploy/dapps/dappreg.js @@ -0,0 +1,33 @@ +// 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 builtins from '~/views/Dapps/builtin.json'; + +const id = 'dappreg'; +const app = builtins.find((app) => app.url === id); +const hashId = app.id; +const source = { + imageUrl: 'https://raw.githubusercontent.com/paritytech/dapp-assets/cdd6ac4f1e2f11619bed72a53ae71217dffe19ad/dapps/legos-64x64.png', + imageHash: '0xa8feea35c761cc6c2fe862fe336419f11ca5421f578757720a899b89fc1df154' +}; +const name = app.name; + +export { + hashId, + id, + name, + source +}; diff --git a/js/src/dapps/chaindeploy/dapps/gavcoin.js b/js/src/dapps/chaindeploy/dapps/gavcoin.js new file mode 100644 index 0000000000000000000000000000000000000000..148c6fb8ed04866eb6225bd4732030667515597c --- /dev/null +++ b/js/src/dapps/chaindeploy/dapps/gavcoin.js @@ -0,0 +1,41 @@ +// 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 { api } from '../parity'; + +const isExternal = true; +const id = 'gavcoin'; +const hashId = api.util.sha3(id); +const source = { + imageUrl: 'https://raw.githubusercontent.com/paritytech/dapp-assets/9e135f76fe9ba61e2d8ccbd72ed144c26c450780/tokens/gavcoin-64x64.png', + imageHash: '0xd40679a3a234d8421c678d64f4df3308859e8ad07ac95ce4a228aceb96955287', + manifestUrl: 'https://raw.githubusercontent.com/gavofyork/gavcoin/eb2f8dc4d3ad4dd5f4562690525b7cfedc9681ba/manifest.json', + manifestHash: '0xd582c572fbef8015837f2c1a8798f2c3149a1d9d655b4020edb1bbece725371d', + contentUrl: { + repo: 'gavofyork/gavcoin', + commit: '0xeb2f8dc4d3ad4dd5f4562690525b7cfedc9681ba' + }, + contentHash: '0x0b6c7b3fc8dad3edb39fd1465904ce9a11938ef18f08f8115f275047ba249530' +}; +const name = 'GavCoin'; + +export { + hashId, + id, + isExternal, + name, + source +}; diff --git a/js/src/dapps/chaindeploy/dapps/githubhint.js b/js/src/dapps/chaindeploy/dapps/githubhint.js new file mode 100644 index 0000000000000000000000000000000000000000..49fb4be9cd1351d5af19dfed81db86dafa5aa309 --- /dev/null +++ b/js/src/dapps/chaindeploy/dapps/githubhint.js @@ -0,0 +1,33 @@ +// 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 builtins from '~/views/Dapps/builtin.json'; + +const id = 'githubhint'; +const app = builtins.find((app) => app.url === id); +const hashId = app.id; +const source = { + imageUrl: 'https://raw.githubusercontent.com/paritytech/dapp-assets/b88e983abaa1a6a6345b8d9448c15b117ddb540e/dapps/link-64x64.jpg', + imageHash: '0x6568901e711886e2c61eef1bbc7e2d8d37a27b9eb3e9e270eda8548f2ec796e8' +}; +const name = app.name; + +export { + hashId, + id, + name, + source +}; diff --git a/js/src/dapps/chaindeploy/dapps/index.js b/js/src/dapps/chaindeploy/dapps/index.js new file mode 100644 index 0000000000000000000000000000000000000000..e95bf5648089806523bdf2e3aaa25aad157987d8 --- /dev/null +++ b/js/src/dapps/chaindeploy/dapps/index.js @@ -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 . + +import * as consolejs from './console'; +import * as dappreg from './dappreg'; +import * as gavcoin from './gavcoin'; +import * as githubhint from './githubhint'; +import * as jgvoting from './jg-voting'; +import * as jgwhenblock from './jg-whenblock'; +import * as localtx from './localtx'; +import * as registry from './registry'; +import * as signaturereg from './signaturereg'; +import * as tokendeploy from './tokendeploy'; +import * as tokenreg from './tokenreg'; +import * as web from './web'; + +const apps = [ + // builtin + consolejs, + dappreg, + githubhint, + localtx, + registry, + signaturereg, + tokendeploy, + tokenreg, + web, + + // external + gavcoin, + jgvoting, + jgwhenblock +]; + +export { + apps +}; diff --git a/js/src/dapps/chaindeploy/dapps/jg-voting.js b/js/src/dapps/chaindeploy/dapps/jg-voting.js new file mode 100644 index 0000000000000000000000000000000000000000..899e95894d25a38bcee3041b7a0816440359fb3f --- /dev/null +++ b/js/src/dapps/chaindeploy/dapps/jg-voting.js @@ -0,0 +1,41 @@ +// 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 { api } from '../parity'; + +const isExternal = true; +const id = 'jg-voting'; +const hashId = api.util.sha3(id); +const source = { + imageUrl: 'https://raw.githubusercontent.com/jacogr/dapp-voting/038ff4074544f2babc7aed9c4ac3dc070b85b804/assets/images/vote.jpg', + imageHash: '0x3620828e1a745d2714e9f37dc2d47cdab5ef9982190a845b5e7665b7a7767661', + manifestUrl: 'https://raw.githubusercontent.com/jacogr/dapp-voting/682f0fe4b86508a1a2487de6c7c61f7b100ba5b2/manifest.json', + manifestHash: '0x9b83e01f87d225e7bfdd305c51319504ff9b4cf8d517ca4b64f606762e72f59e', + contentUrl: { + repo: 'jacogr/dapp-voting', + commit: '0x7d941597e862a600a60b9d6ecd3a6f606d96cd7b' + }, + contentHash: '0x9fcc0910f6a8c4e45715d41aea2d287da31bf1d7321003fc66df6a012ce2d753' +}; +const name = 'Yes, No, Maybe'; + +export { + hashId, + id, + isExternal, + name, + source +}; diff --git a/js/src/dapps/chaindeploy/dapps/jg-whenblock.js b/js/src/dapps/chaindeploy/dapps/jg-whenblock.js new file mode 100644 index 0000000000000000000000000000000000000000..0005ca454a89473bd343efef0f7e63c5d03928d9 --- /dev/null +++ b/js/src/dapps/chaindeploy/dapps/jg-whenblock.js @@ -0,0 +1,39 @@ +// 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 . + +const isExternal = true; +const id = 'whenarewethere'; +const hashId = '0xfef3bfded03695e38a9ff476a0999e1fa421e72d1fb3b55a87d6c2bdb6fc18ef'; +const source = { + imageUrl: 'https://raw.githubusercontent.com/jacogr/dapp-when-are-we-there/167aa4d904c5aa6246d0d6d6f41c4ed8a56889cd/assets/images/clock.jpg', + imageHash: '0x2534b44f685b6399bf63f86679128216c43e9a58be1dfb58533923f0bcffeba7', + manifestUrl: 'https://raw.githubusercontent.com/jacogr/dapp-when-are-we-there/bf72dc3033711a3ab41bec3c1249638f70bae768/manifest.json', + manifestHash: '0xfe26f6a19ea9393d69bc5d8c73c5072ccf126f51c10c135b42d6bf162d774fd9', + contentUrl: { + repo: 'jacogr/dapp-when-are-we-there', + commit: '0xbf72dc3033711a3ab41bec3c1249638f70bae768' + }, + contentHash: '0x3505cbbef5c2243eedba07d340d4abccfaa3cfb799f51827e33c9721a5254d13' +}; +const name = 'When are we there'; + +export { + hashId, + id, + isExternal, + name, + source +}; diff --git a/js/src/dapps/chaindeploy/dapps/localtx.js b/js/src/dapps/chaindeploy/dapps/localtx.js new file mode 100644 index 0000000000000000000000000000000000000000..6fc275a134a6d8d6530787991803faf71bd62437 --- /dev/null +++ b/js/src/dapps/chaindeploy/dapps/localtx.js @@ -0,0 +1,33 @@ +// 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 builtins from '~/views/Dapps/builtin.json'; + +const id = 'localtx'; +const app = builtins.find((app) => app.url === id); +const hashId = app.id; +const source = { + imageUrl: 'https://raw.githubusercontent.com/paritytech/dapp-assets/cdd6ac4f1e2f11619bed72a53ae71217dffe19ad/dapps/stack-64x64.png', + imageHash: '0x22b924801e1971659a51956dcdfc5a2d592d8bdd03780dd72d5bc4c84b595b4c' +}; +const name = app.name; + +export { + hashId, + id, + name, + source +}; diff --git a/js/src/dapps/chaindeploy/dapps/registry.js b/js/src/dapps/chaindeploy/dapps/registry.js new file mode 100644 index 0000000000000000000000000000000000000000..49ff4757bb73b21ea2bee3a7905962e4764ab36c --- /dev/null +++ b/js/src/dapps/chaindeploy/dapps/registry.js @@ -0,0 +1,33 @@ +// 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 builtins from '~/views/Dapps/builtin.json'; + +const id = 'registry'; +const app = builtins.find((app) => app.url === id); +const hashId = app.id; +const source = { + imageUrl: 'https://raw.githubusercontent.com/paritytech/dapp-assets/b88e983abaa1a6a6345b8d9448c15b117ddb540e/dapps/register-64x64.jpg', + imageHash: '0xf7100024052cd78b5e043287c05392b5db0bfce5caedd6d39555d40283ef0a1c' +}; +const name = app.name; + +export { + hashId, + id, + name, + source +}; diff --git a/js/src/dapps/chaindeploy/dapps/signaturereg.js b/js/src/dapps/chaindeploy/dapps/signaturereg.js new file mode 100644 index 0000000000000000000000000000000000000000..12efcff3622759e63819f386d11db0a7cb6fd6d2 --- /dev/null +++ b/js/src/dapps/chaindeploy/dapps/signaturereg.js @@ -0,0 +1,33 @@ +// 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 builtins from '~/views/Dapps/builtin.json'; + +const id = 'signaturereg'; +const app = builtins.find((app) => app.url === id); +const hashId = app.id; +const source = { + imageUrl: 'https://raw.githubusercontent.com/paritytech/dapp-assets/b88e983abaa1a6a6345b8d9448c15b117ddb540e/dapps/hex-64x64.jpg', + imageHash: '0x26f7f2415cd5cbbffa58e8119fdbdd7181cac79119dd7f6ba6ee99d7f4445640' +}; +const name = app.name; + +export { + hashId, + id, + name, + source +}; diff --git a/js/src/dapps/chaindeploy/dapps/tokendeploy.js b/js/src/dapps/chaindeploy/dapps/tokendeploy.js new file mode 100644 index 0000000000000000000000000000000000000000..43ea8f22b0ef6c1353a00f5001f8c4a40ef2f352 --- /dev/null +++ b/js/src/dapps/chaindeploy/dapps/tokendeploy.js @@ -0,0 +1,33 @@ +// 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 builtins from '~/views/Dapps/builtin.json'; + +const id = 'tokendeploy'; +const app = builtins.find((app) => app.url === id); +const hashId = app.id; +const source = { + imageUrl: 'https://raw.githubusercontent.com/paritytech/dapp-assets/b88e983abaa1a6a6345b8d9448c15b117ddb540e/dapps/interlock-64x64.png', + imageHash: '0xde104baf02aec783e0bffc624514ee267dbcb455382375e3ffa715790c1c939f' +}; +const name = app.name; + +export { + hashId, + id, + name, + source +}; diff --git a/js/src/dapps/chaindeploy/dapps/tokenreg.js b/js/src/dapps/chaindeploy/dapps/tokenreg.js new file mode 100644 index 0000000000000000000000000000000000000000..784febba05b896604409537dd8177121f87c2baf --- /dev/null +++ b/js/src/dapps/chaindeploy/dapps/tokenreg.js @@ -0,0 +1,33 @@ +// 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 builtins from '~/views/Dapps/builtin.json'; + +const id = 'tokenreg'; +const app = builtins.find((app) => app.url === id); +const hashId = app.id; +const source = { + imageUrl: 'https://raw.githubusercontent.com/paritytech/dapp-assets/b88e983abaa1a6a6345b8d9448c15b117ddb540e/dapps/coins-64x64.jpg', + imageHash: '0xe23d429d15de98c7878d92bc90b79c7afabe1b04c2ad5e3e2c89adc8f439edc9' +}; +const name = app.name; + +export { + hashId, + id, + name, + source +}; diff --git a/js/src/dapps/chaindeploy/dapps/web.js b/js/src/dapps/chaindeploy/dapps/web.js new file mode 100644 index 0000000000000000000000000000000000000000..0726e2bf71e5bab32e9ce2c2dc5e1b24cd0a9e53 --- /dev/null +++ b/js/src/dapps/chaindeploy/dapps/web.js @@ -0,0 +1,33 @@ +// 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 builtins from '~/views/Dapps/builtin.json'; + +const id = 'web'; +const app = builtins.find((app) => app.url === id); +const hashId = app.id; +const source = { + imageUrl: 'https://raw.githubusercontent.com/paritytech/dapp-assets/ec6138115d0e1f45258969cd90b3b274e0ff2258/dapps/earth-64x64.jpg', + imageHash: '0x0b9b62a9262f75461191d4e8bf686c56528cbc0fe885c1f5878052ca8b2f65bf' +}; +const name = app.name; + +export { + hashId, + id, + name, + source +}; diff --git a/js/src/dapps/chaindeploy/parity.js b/js/src/dapps/chaindeploy/parity.js new file mode 100644 index 0000000000000000000000000000000000000000..1dea0d696b4e72b51441b269c560d7316bf64784 --- /dev/null +++ b/js/src/dapps/chaindeploy/parity.js @@ -0,0 +1,21 @@ +// 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 . + +const api = window.parity.api; + +export { + api +}; diff --git a/js/src/dapps/chaindeploy/store.js b/js/src/dapps/chaindeploy/store.js new file mode 100644 index 0000000000000000000000000000000000000000..07b6931354555308b91579db947c7d3622da02f1 --- /dev/null +++ b/js/src/dapps/chaindeploy/store.js @@ -0,0 +1,714 @@ +// 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 } from 'mobx'; + +import { contracts as contractsInfo, registry as registryInfo } from './contracts'; +import { apps } from './dapps'; +import { api } from './parity'; +import { executeContract, isValidNumber, validateCode } from './utils'; + +export default class ContractsStore { + @observable apps = null; + @observable badges = null; + @observable contracts = null; + @observable error = null; + @observable registry = null; + + constructor () { + this.apps = apps; + this.badges = contractsInfo.filter((contract) => contract.isBadge); + this.contracts = contractsInfo.filter((contract) => !contract.isBadge); + this.registry = registryInfo; + + api.subscribe('eth_blockNumber', this.onNewBlockNumber); + } + + @computed get contractBadgereg () { + return this.contracts.find((contract) => contract.id === 'badgereg'); + } + + @computed get contractDappreg () { + return this.contracts.find((contract) => contract.id === 'dappreg'); + } + + @computed get contractGithubhint () { + return this.contracts.find((contract) => contract.id === 'githubhint'); + } + + @computed get contractTokenreg () { + return this.contracts.find((contract) => contract.id === 'tokenreg'); + } + + @computed get isBadgeDeploying () { + return this.badges + .filter((contract) => contract.isDeploying) + .length !== 0; + } + + @computed get isContractDeploying () { + return this.contracts + .filter((contract) => contract.isDeploying) + .length !== 0; + } + + @computed get isDappDeploying () { + return this.apps + .filter((app) => app.isDeploying) + .length !== 0; + } + + @computed get haveAllBadges () { + return this.badges + .filter((contract) => !contract.instance || !contract.hasLatestCode || !contract.badgeImageHash || !contract.badgeImageMatch || !contract.isBadgeRegistered) + .length === 0; + } + + @computed get haveAllContracts () { + return this.contracts + .filter((contract) => !contract.instance || !contract.hasLatestCode) + .length === 0; + } + + @computed get haveAllDapps () { + return this.apps + .filter((app) => { + return !app.isOnChain || + !app.imageHash || !app.imageMatch || + (app.source.contentHash && !app.contentMatch) || + (app.source.manifestHash && !app.manifestMatch); + }) + .length === 0; + } + + @action refreshApps = () => { + this.apps = [].concat(this.apps.peek()); + } + + @action refreshContracts = () => { + this.badges = [].concat(this.badges.peek()); + this.contracts = [].concat(this.contracts.peek()); + } + + @action setError = (error) => { + console.error(error); + + this.error = error.message + ? error.message + : error; + } + + @action setRegistryAddress = (address, isOnChain = false) => { + if (this.registry.address !== address || !this.registry.instance) { + console.log(`registry found at ${address}`); + + this.registry = Object.assign({}, this.registry, { + address, + instance: api.newContract(this.registry.abi, address).instance, + isOnChain + }); + } + } + + @action setRegistryCode (byteCode) { + this.registry.hasLatestCode = validateCode(this.registry.byteCode, byteCode); + } + + @action setRegistryDeploying = (isDeploying = false) => { + this.registry = Object.assign({}, this.registry, { + isDeploying, + status: isDeploying + ? 'Deploying contract' + : null + }); + } + + @action setBadgeId = (badge, badgeId) => { + badge.badgeId = badgeId; + badge.isBadgeRegistered = true; + + this.refreshContracts(); + } + + @action setBadgeImageHash = (badge, imageHash) => { + badge.badgeImageHash = imageHash; + badge.badgeImageMatch = badge.badgeSource.imageHash === imageHash; + + this.refreshContracts(); + } + + @action setContractAddress = (contract, address, isOnChain = false) => { + if (contract.address !== address || !contract.instance || contract.isOnChain !== isOnChain) { + console.log(`${contract.id} found at ${address}`); + + contract.address = address; + contract.instance = api.newContract(contract.abi, address).instance; + contract.isOnChain = isOnChain; + + this.refreshContracts(); + } + } + + @action setContractCode (contract, byteCode) { + contract.hasLatestCode = validateCode(contract.byteCode, byteCode); + + this.refreshContracts(); + } + + @action setContractDeploying = (contract, isDeploying = false) => { + contract.isDeploying = isDeploying; + contract.status = isDeploying + ? 'Deploying contract' + : null; + + this.refreshContracts(); + } + + @action setContractStatus = (contract, status) => { + contract.status = status; + + this.refreshContracts(); + } + + @action setAppDeploying = (app, isDeploying = false) => { + app.isDeploying = isDeploying; + app.status = isDeploying + ? 'Registering app' + : null; + + this.refreshApps(); + } + + @action setAppFound = (app, isOnChain = false) => { + if (app.isOnChain !== isOnChain) { + console.log(`${app.name} found on dappreg`); + + app.isOnChain = isOnChain; + + this.refreshApps(); + } + } + + @action setAppContentHash = (app, contentHash) => { + if (app.contentHash !== contentHash) { + console.log(`${app.name} has contentHash ${contentHash}`); + + app.contentHash = contentHash; + app.contentMatch = contentHash === app.source.contentHash; + + this.refreshApps(); + } + } + + @action setAppImageHash = (app, imageHash) => { + if (app.imageHash !== imageHash) { + console.log(`${app.name} has imageHash ${imageHash}`); + + app.imageHash = imageHash; + app.imageMatch = imageHash === app.source.imageHash; + + this.refreshApps(); + } + } + + @action setAppManifestHash = (app, manifestHash) => { + if (app.manifestHash !== manifestHash) { + console.log(`${app.name} has manifestHash ${manifestHash}`); + + app.manifestHash = manifestHash; + app.manifestMatch = manifestHash === app.source.manifestHash; + + this.refreshApps(); + } + } + + @action setAppStatus = (app, status) => { + console.log(app.id, status); + + app.status = status; + + this.refreshApps(); + } + + deployApp = (app) => { + console.log(`Registering application ${app.id}`); + + this.setAppDeploying(app, true); + + const options = {}; + const values = [app.hashId]; + + return api.parity + .defaultAccount() + .then((defaultAccount) => { + options.from = defaultAccount; + + if (app.isOnChain) { + return true; + } + + return this.contractDappreg.instance + .fee.call({}, []) + .then((fee) => { + options.value = fee; + + return executeContract(app.id, this.contractDappreg, 'register', options, values); + }); + }) + .then(() => { + if (app.imageHash && app.imageMatch) { + return true; + } + + this.setAppStatus(app, 'Registering image url'); + + return this + .registerHash(app.source.imageHash, app.source.imageUrl, options.from) + .then(() => this.setAppMeta(app, 'IMG', app.source.imageHash, options.from)); + }) + .then(() => { + if (!app.source.manifestHash || app.manifestMatch) { + return true; + } + + this.setAppStatus(app, 'Registering manifest url'); + + return this + .registerHash(app.source.manifestHash, app.source.manifestUrl, options.from) + .then(() => this.setAppMeta(app, 'MANIFEST', app.source.manifestHash, options.from)); + }) + .then(() => { + if (!app.source.contentHash || app.contentMatch) { + return true; + } + + this.setAppStatus(app, 'Registering content url'); + + return this + .registerRepo(app.source.contentHash, app.source.contentUrl, options.from) + .then(() => this.setAppMeta(app, 'CONTENT', app.source.contentHash, options.from)); + }) + .catch(() => { + return null; + }) + .then(() => { + this.setAppDeploying(app, false); + }); + } + + deployApps = () => { + this.apps + .filter((app) => { + return !app.isDeploying && + ( + !app.isOnChain || + (!app.imageHash || !app.imageMatch) || + (app.source.contentHash && !app.contentMatch) || + (app.source.manifestHash && !app.manifestMatch) + ); + }) + .forEach(this.deployApp); + } + + _deployContract = (contract) => { + console.log(`Deploying contract ${contract.id}`); + + const options = { + data: contract.byteCode + }; + + return api.parity + .defaultAccount() + .then((defaultAccount) => { + options.from = defaultAccount; + + return api + .newContract(contract.abi) + .deploy(options, contract.deployParams, (error, data) => { + if (error) { + console.error(contract.id, error); + } else { + console.log(contract.id, data); + } + }) + .then((contractAddress) => { + return [contractAddress, defaultAccount]; + }); + }); + } + + deployContract = (contract) => { + if (contract.hasLatestCode) { + return Promise.resolve(false); + } + + let defaultAccount = '0x0'; + + this.setContractDeploying(contract, true); + + return this + ._deployContract(contract) + .then(([address, _defaultAccount]) => { + const isOnChain = contract.isOnChain; + + defaultAccount = _defaultAccount; + + this.setContractAddress(contract, address); + + return isOnChain + ? true + : this.reserveAddress(contract, defaultAccount); + }) + .then(() => { + return this.registerAddress(contract, defaultAccount); + }) + .catch(() => { + return null; + }) + .then(() => { + this.setContractDeploying(contract, false); + }); + } + + deployBadge = (badge) => { + let defaultAccount; + + return this + .deployContract(badge) + .then(() => { + this.setContractDeploying(badge, true); + + return api.parity.defaultAccount(); + }) + .then((_defaultAccount) => { + defaultAccount = _defaultAccount; + + if (badge.isBadgeRegistered) { + return true; + } + + this.setContractStatus(badge, 'Registering with badgereg'); + + return this.registerBadge(badge, defaultAccount); + }) + .then(() => { + if (badge.badgeImageMatch) { + return true; + } + + this.setContractStatus(badge, 'Registering image url'); + + return this + .registerHash(badge.badgeSource.imageHash, badge.badgeSource.imageUrl, defaultAccount) + .then(() => this.registerBadgeImage(badge, badge.badgeSource.imageHash, defaultAccount)); + }) + .then(() => { + this.setContractDeploying(badge, false); + }); + } + + deployContracts = () => { + this.contracts + .filter((contract) => !contract.isDeploying && (!contract.instance || !contract.hasLatestCode)) + .forEach(this.deployContract); + } + + deployBadges = () => { + this.badges + .filter((contract) => !contract.isDeploying && (!contract.instance || !contract.hasLatestCode || !contract.badgeImageHash || !contract.badgeImageMatch || !contract.isBadgeRegistered)) + .forEach(this.deployBadge); + } + + deployRegistry = () => { + this.setRegistryDeploying(true); + + return this + ._deployContract(this.registry) + .then(([address]) => { + this.setRegistryDeploying(false); + this.setRegistryAddress(address); + }); + } + + registerBadge = (badge, fromAddress) => { + const options = { + from: fromAddress + }; + const values = [badge.address, api.util.sha3.text(badge.id.toLowerCase())]; + + return this.contractBadgereg.instance + .fee.call({}, []) + .then((fee) => { + options.value = fee; + + return executeContract(badge.id, this.contractBadgereg, 'register', options, values); + }); + } + + registerBadgeImage = (badge, hash, fromAddress) => { + const options = { + from: fromAddress + }; + const values = [badge.badgeId, 'IMG', hash]; + + this.setContractStatus(badge, 'Setting meta IMG'); + + return executeContract(badge.id, this.contractBadgereg, 'setMeta', options, values); + } + + setAppMeta = (app, key, meta, fromAddress) => { + const options = { + from: fromAddress + }; + const values = [app.hashId, key, meta]; + + this.setAppStatus(app, `Setting meta ${key}`); + + return executeContract(app.id, this.contractDappreg, 'setMeta', options, values); + } + + reserveAddress = (contract, fromAddress) => { + const options = { from: fromAddress }; + const values = [api.util.sha3.text(contract.id.toLowerCase())]; + + this.setContractStatus(contract, 'Reserving name'); + + return this.registry.instance + .fee.call({}, []) + .then((value) => { + options.value = value; + + return executeContract(contract.id, this.registry, 'reserve', options, values); + }); + } + + registerAddress = (contract, fromAddress) => { + const options = { from: fromAddress }; + const values = [api.util.sha3.text(contract.id.toLowerCase()), 'A', contract.address]; + + this.setContractStatus(contract, 'Setting lookup address'); + + return executeContract(contract.id, this.registry, 'setAddress', options, values); + } + + registerRepo = (hash, content, fromAddress) => { + const options = { + from: fromAddress + }; + const values = [hash, content.repo || content, content.commit || 0]; + + return this.contractGithubhint.instance + .entries.call({}, [hash]) + .then(([imageUrl, commit, owner]) => { + if (isValidNumber(owner)) { + return true; + } + + return executeContract(hash, this.contractGithubhint, 'hint', options, values); + }) + .catch(() => false); + } + + registerHash = (hash, url, fromAddress) => { + const options = { + from: fromAddress + }; + const values = [hash, url]; + + return this.contractGithubhint.instance + .entries.call({}, [hash]) + .then(([imageUrl, commit, owner]) => { + if (isValidNumber(owner)) { + return true; + } + + return executeContract(hash, this.contractGithubhint, 'hintURL', options, values); + }) + .catch(() => false); + } + + findRegistry = () => { + if (this.registry.address && this.registry.hasLatestCode) { + return Promise.resolve(this.registry); + } + + return api.parity + .registryAddress() + .then((address) => { + if (isValidNumber(address)) { + this.setRegistryAddress(address, true); + } + + return api.eth.getCode(address); + }) + .then((byteCode) => { + this.setRegistryCode(byteCode); + }); + } + + findApps = () => { + if (!this.contractDappreg.instance) { + return Promise.resolve(false); + } + + return Promise + .all( + this.apps.map((app) => { + return app.isOnChain + ? Promise.resolve([[0]]) + : this.contractDappreg.instance.get.call({}, [app.hashId]); + }) + ) + .then((apps) => { + apps.forEach(([_id, owner], index) => { + const id = api.util.bytesToHex(_id); + + if (isValidNumber(id)) { + this.setAppFound(this.apps[index], true); + } + }); + + return Promise.all( + this.apps.map((app) => { + return !app.isOnChain || (app.imageHash && app.imageMatch) + ? Promise.resolve([[0], [0], [0]]) + : Promise.all([ + this.contractDappreg.instance.meta.call({}, [app.hashId, 'CONTENT']), + this.contractDappreg.instance.meta.call({}, [app.hashId, 'IMG']), + this.contractDappreg.instance.meta.call({}, [app.hashId, 'MANIFEST']) + ]); + }) + ); + }) + .then((hashes) => { + hashes.forEach(([content, image, manifest], index) => { + const contentHash = api.util.bytesToHex(content); + const imageHash = api.util.bytesToHex(image); + const manifestHash = api.util.bytesToHex(manifest); + + if (isValidNumber(contentHash)) { + this.setAppContentHash(this.apps[index], contentHash); + } + + if (isValidNumber(imageHash)) { + this.setAppImageHash(this.apps[index], imageHash); + } + + if (isValidNumber(manifestHash)) { + this.setAppManifestHash(this.apps[index], manifestHash); + } + }); + }); + } + + findBadges = () => { + if (!this.contractBadgereg.instance) { + return Promise.resolve(false); + } + + return this + .findContracts(this.badges) + .then(() => { + return Promise.all( + this.badges.map((badge) => { + return badge.isBadgeRegistered + ? Promise.resolve([0, 0, 0]) + : this.contractBadgereg.instance.fromAddress.call({}, [badge.address]); + }) + ); + }) + .then((badgeInfos) => { + badgeInfos.forEach(([id, name, owner], index) => { + if (isValidNumber(owner)) { + this.setBadgeId(this.badges[index], id); + } + }); + + return Promise + .all( + this.badges.map((badge) => { + return !badge.isBadgeRegistered + ? Promise.resolve([0]) + : this.contractBadgereg.instance.meta.call({}, [badge.badgeId, 'IMG']); + }) + ); + }) + .then((images) => { + images.forEach((imageBytes, index) => { + const imageHash = api.util.bytesToHex(imageBytes); + + if (isValidNumber(imageHash)) { + this.setBadgeImageHash(this.badges[index], imageHash); + } + }); + }); + } + + findContracts = (contracts = this.contracts) => { + if (!this.registry.instance) { + return Promise.resolve(false); + } + + return Promise + .all( + contracts.map((contract) => { + const hashId = api.util.sha3.text(contract.id.toLowerCase()); + + return contract.isOnChain + ? Promise.resolve([0, 0]) + : Promise.all([ + this.registry.instance.getAddress.call({}, [hashId, 'A']), + this.registry.instance.getOwner.call({}, [hashId]) + ]); + }) + ) + .then((addresses) => { + addresses.forEach(([address, owner], index) => { + if (isValidNumber(owner) && isValidNumber(address)) { + this.setContractAddress(contracts[index], address, true); + } + }); + + return Promise.all( + contracts.map((contract) => { + return !contract.address || contract.hasLatestCode + ? Promise.resolve(null) + : api.eth.getCode(contract.address); + }) + ); + }) + .then((codes) => { + codes.forEach((byteCode, index) => { + if (byteCode) { + this.setContractCode(contracts[index], byteCode); + } + }); + }); + } + + onNewBlockNumber = (error, blockNumber) => { + if (error) { + return; + } + + return this + .findRegistry() + .then(this.findContracts) + .then(this.findApps) + .then(this.findBadges) + .catch(this.setError); + } +} diff --git a/js/src/dapps/chaindeploy/utils.js b/js/src/dapps/chaindeploy/utils.js new file mode 100644 index 0000000000000000000000000000000000000000..87e7279e33fd6d9eab3ca0a82a42fbd0928ee44e --- /dev/null +++ b/js/src/dapps/chaindeploy/utils.js @@ -0,0 +1,82 @@ +// 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 BigNumber from 'bignumber.js'; + +import { api } from './parity'; + +export function validateCode (source, retrieved) { + const original = source.substr(12); + const bytecode = retrieved.substr(12); + + const knownHash = api.util.sha3(original.slice(-1 * bytecode.length)); + const codeHash = api.util.sha3(bytecode); + + return knownHash === codeHash; +} + +export function isValidNumber (number) { + return number && !(new BigNumber(number)).isZero(); +} + +export function executeContract (logId, contract, funcName, options, values) { + const func = contract.instance[funcName]; + + return func + .estimateGas(options, values) + .then((gasEst) => { + options.gas = gasEst.mul(1.2); + + return trackRequest( + func.postTransaction(options, values), + (error, data) => { + if (error) { + console.error(logId, error); + } else { + console.log(logId, data); + } + } + ); + }); +} + +export function trackRequest (promise, callback) { + return promise + .then((requestId) => { + callback(null, { state: 'checkRequest', requestId }); + + return api.pollMethod('parity_checkRequest', requestId); + }) + .then((txHash) => { + callback(null, { state: 'getTransactionReceipt', txHash }); + + return api.pollMethod('eth_getTransactionReceipt', txHash, (receipt) => { + if (!receipt || !receipt.blockNumber || receipt.blockNumber.eq(0)) { + return false; + } + + return true; + }); + }) + .then((receipt) => { + callback(null, { state: 'hasReceipt', receipt }); + }) + .catch((error) => { + callback(error); + + throw error; + }); +} diff --git a/js/src/dapps/console.js b/js/src/dapps/console.js new file mode 100644 index 0000000000000000000000000000000000000000..44b6dcb9c4dd7df4270e03cb8306cb3575c32feb --- /dev/null +++ b/js/src/dapps/console.js @@ -0,0 +1,59 @@ +// 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 from 'react'; +import ReactDOM from 'react-dom'; +import { AppContainer } from 'react-hot-loader'; + +import 'codemirror/addon/dialog/dialog'; +import 'codemirror/addon/dialog/dialog.css'; +import 'codemirror/addon/hint/javascript-hint'; +import 'codemirror/addon/hint/show-hint'; +import 'codemirror/addon/hint/show-hint.css'; +import 'codemirror/addon/search/match-highlighter'; +import 'codemirror/addon/search/search'; +import 'codemirror/addon/search/searchcursor'; +import 'codemirror/keymap/sublime'; +import 'codemirror/lib/codemirror.css'; +import 'codemirror/mode/javascript/javascript'; +// Custom codemirror style +import './console/codemirror.css'; + +import Application from './console/Application'; + +import '../../assets/fonts/Roboto/font.css'; +import '../../assets/fonts/RobotoMono/font.css'; +import './style.css'; + +ReactDOM.render( + + + , + document.querySelector('#container') +); + +if (module.hot) { + module.hot.accept('./console/Application/index.js', () => { + require('./console/Application/index.js'); + + ReactDOM.render( + + + , + document.querySelector('#container') + ); + }); +} diff --git a/js/src/dapps/console/Application/application.css b/js/src/dapps/console/Application/application.css new file mode 100644 index 0000000000000000000000000000000000000000..eea2c030db3db07e19f74482d5202c56b5442954 --- /dev/null +++ b/js/src/dapps/console/Application/application.css @@ -0,0 +1,65 @@ +/* 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 . +*/ + +.app { + display: flex; + flex-direction: column; + font-family: Arial, sans-serif; + font-size: 11px; + height: 100vh; + overflow: hidden; +} + +textarea, +input { + font-family: dejavu sans mono, monospace; + outline: none; +} + +code, +pre { + font-family: dejavu sans mono, monospace; + font-size: 11px; +} + +.header { + flex: 0 0 auto; +} + +.view { + display: flex; + flex: 1; + flex-direction: column; +} + +.eval { + flex: 0 1 auto; + font-family: dejavu sans mono, monospace; + overflow: auto; +} + +.input { + border-top: 1px solid #eee; + display: flex; + flex: 1 1 auto; + min-height: 50px; +} + +.status { + flex: 0 0 auto; + font-family: dejavu sans mono, monospace; +} diff --git a/js/src/dapps/console/Application/application.js b/js/src/dapps/console/Application/application.js new file mode 100644 index 0000000000000000000000000000000000000000..5a591e710a882c0c60e8ec3f2b21703266abcba3 --- /dev/null +++ b/js/src/dapps/console/Application/application.js @@ -0,0 +1,94 @@ +// 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 } from 'react'; + +import { api } from '../parity'; + +import Console from '../Console'; +import Header from '../Header'; +import Input from '../Input'; +import Settings from '../Settings'; +import Snippets from '../Snippets'; +import Watches from '../Watches'; + +import ApplicationStore from './application.store'; +import WatchesStore from '../Watches/watches.store'; + +import styles from './application.css'; + +@observer +export default class Application extends Component { + application = ApplicationStore.get(); + watches = WatchesStore.get(); + + componentWillMount () { + this.watches.add('time', () => new Date()); + this.watches.add('blockNumber', api.eth.blockNumber, api); + } + + render () { + return ( +
+
+
+
+ + { this.renderView() } + +
+ +
+
+ ); + } + + renderView () { + const { view } = this.application; + + if (view === 'console') { + return ( +
+
+ +
+
+ +
+
+ ); + } + + if (view === 'settings') { + return ( +
+ +
+ ); + } + + if (view === 'snippets') { + return ( +
+ +
+ ); + } + + return null; + } +} diff --git a/js/src/dapps/console/Application/application.store.js b/js/src/dapps/console/Application/application.store.js new file mode 100644 index 0000000000000000000000000000000000000000..c10be46c66404d1dd96361c6d0fa1cf5174ce2c5 --- /dev/null +++ b/js/src/dapps/console/Application/application.store.js @@ -0,0 +1,42 @@ +// 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, observable } from 'mobx'; + +let instance; + +export default class ApplicationStore { + @observable view = this.views[0].id; + + views = [ + { label: 'Console', id: 'console' }, + { label: 'Snippets', id: 'snippets' }, + { label: 'Settings', id: 'settings' } + ]; + + static get () { + if (!instance) { + instance = new ApplicationStore(); + } + + return instance; + } + + @action + setView (view) { + this.view = view; + } +} diff --git a/js/src/dapps/console/Application/index.js b/js/src/dapps/console/Application/index.js new file mode 100644 index 0000000000000000000000000000000000000000..3d8d1ca3b851e5bfed73373e82eff06b5d9ea46c --- /dev/null +++ b/js/src/dapps/console/Application/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 './application'; diff --git a/js/src/dapps/console/Autocomplete/autocomplete.css b/js/src/dapps/console/Autocomplete/autocomplete.css new file mode 100644 index 0000000000000000000000000000000000000000..8d4585e7a7ca86800e6f531d7598995960fe3c48 --- /dev/null +++ b/js/src/dapps/console/Autocomplete/autocomplete.css @@ -0,0 +1,55 @@ +/* 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 . +*/ + +.container { + background: #f8f8f8; + box-shadow: 0 0.125em 0.25em rgba(0, 0, 0, 0.5); + font-family: dejavu sans mono, monospace; + left: 20px; + position: absolute; + max-height: 300px; + overflow: auto; +} + +.item { + background-color: white; + padding: 0.25em 0.25em 0.25em 0.35em; + display: flex; + justify-content: space-between; + + &.selected { + background-color: rgb(64, 115, 244); + + &, + .proto { + color: white; + } + } + + &:hover { + cursor: default; + } + + &:hover:not(.selected) { + background-color: rgb(230, 236, 255); + } + + .proto { + color: gray; + margin-left: 1em; + } +} diff --git a/js/src/dapps/console/Autocomplete/autocomplete.js b/js/src/dapps/console/Autocomplete/autocomplete.js new file mode 100644 index 0000000000000000000000000000000000000000..e2938f23dc457ef9fe87d8a5b60dd097c14254d4 --- /dev/null +++ b/js/src/dapps/console/Autocomplete/autocomplete.js @@ -0,0 +1,96 @@ +// 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 } from 'react'; +import ReactDOM from 'react-dom'; + +import AutocompleteStore from './autocomplete.store'; + +import styles from './autocomplete.css'; + +@observer +export default class Autocomplete extends Component { + autocompleteStore = AutocompleteStore.get(); + + render () { + if (!this.autocompleteStore.show) { + return null; + } + + return ( +
+ { this.renderAutocompletes() } +
+ ); + } + + renderAutocompletes () { + const { selected, values } = this.autocompleteStore; + const displayedProto = {}; + + return values.map((autocomplete, index) => { + const { name, prototypeName } = autocomplete; + const onClick = () => this.handleClick(index); + const setRef = (node) => this.setRef(index, node); + + const proto = !displayedProto[prototypeName] + ? ( + + { prototypeName } + + ) + : null; + + if (!displayedProto[prototypeName]) { + displayedProto[prototypeName] = true; + } + + const classes = [ styles.item ]; + + if (index === selected) { + classes.push(styles.selected); + } + + return ( +
+ + { name } + + { proto } +
+ ); + }); + } + + handleClick = (index) => { + this.autocompleteStore.select(index); + }; + + setRef = (index, node) => { + const element = ReactDOM.findDOMNode(node); + + this.autocompleteStore.setElement(index, element); + }; +} diff --git a/js/src/dapps/console/Autocomplete/autocomplete.store.js b/js/src/dapps/console/Autocomplete/autocomplete.store.js new file mode 100644 index 0000000000000000000000000000000000000000..82ff2f24dc8e1c27312ce0bedb32e0d79e620e8d --- /dev/null +++ b/js/src/dapps/console/Autocomplete/autocomplete.store.js @@ -0,0 +1,234 @@ +// 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, observable } from 'mobx'; + +import { evaluate } from '../utils'; + +let instance; + +export default class AutocompleteStore { + @observable values = []; + @observable position = {}; + @observable show = false; + @observable selected = null; + + elements = {}; + inputNode = null; + lastObject = null; + lastObjectPropertyNames = []; + + static get () { + if (!instance) { + instance = new AutocompleteStore(); + } + + return instance; + } + + get hasSelected () { + return this.selected !== null; + } + + clearCache () { + this.lastObject = null; + this.lastObjectPropertyNames = null; + } + + @action + focus (offset = 1) { + if (this.values.length === 0) { + this.selected = null; + return; + } + + this.selected = this.selected === null + ? ( + offset === 1 + ? 0 + : this.values.length - 1 + ) + : (this.values.length + this.selected + offset) % (this.values.length); + + if (this.isVisible(this.selected)) { + return; + } + + const element = this.elements[this.selected]; + + if (!element) { + return; + } + + element.scrollIntoView(offset === -1); + } + + focusOnInput () { + if (!this.inputNode) { + return; + } + + this.inputNode.focus(); + } + + @action + hide () { + this.show = false; + this.selected = null; + } + + isVisible (index) { + const element = this.elements[index]; + + if (!element) { + return false; + } + + const eBoundings = element.getBoundingClientRect(); + const pBoundings = element.parentElement.getBoundingClientRect(); + + if (eBoundings.top < pBoundings.top || eBoundings.bottom > pBoundings.bottom) { + return false; + } + + return true; + } + + select (inputStore, _index = this.selected) { + const index = _index === null + ? 0 + : _index; + + if (!this.values[index]) { + console.warn(`autocomplete::select has been called on AutocompleteStore with wrong value ${index}`); + return; + } + + const { name } = this.values[index]; + const { input } = inputStore; + const objects = input.split('.'); + + objects[objects.length - 1] = name; + const nextInput = objects.join('.'); + + this.hide(); + this.focusOnInput(); + return inputStore.updateInput(nextInput, false); + } + + setElement (index, element) { + this.elements[index] = element; + } + + setInputNode (node) { + this.inputNode = node; + } + + @action + setPosition () { + if (!this.inputNode) { + return; + } + + const inputBoundings = this.inputNode.getBoundingClientRect(); + const bodyBoundings = document.body.getBoundingClientRect(); + + // display on bottom of input + if (inputBoundings.top < bodyBoundings.height / 2) { + const nextPosition = { + top: 20 + }; + + this.position = nextPosition; + return; + } + + // display on top of input + const nextPosition = { + bottom: inputBoundings.height + }; + + this.position = nextPosition; + return; + } + + @action + setValues (values) { + this.values = values; + this.selected = null; + const show = values.length > 0; + + // Reveal autocomplete + if (!this.show && show) { + this.setPosition(); + } + + this.show = show; + } + + update (input) { + if (input.length === 0) { + return this.setValues([]); + } + + const objects = input.split('.'); + const suffix = objects.pop().toLowerCase(); + const prefix = objects.join('.'); + const object = prefix.length > 0 + ? prefix + : 'window'; + + if (object !== this.lastObject) { + const evalResult = evaluate(object); + + if (evalResult.error) { + this.lastObjectProperties = []; + } else { + this.lastObjectProperties = getAllProperties(evalResult.result); + } + + this.lastObject = object; + } + + const autocompletes = this.lastObjectProperties.filter((property) => { + return property.name.toLowerCase().includes(suffix); + }); + + return this.setValues(autocompletes); + } +} + +function getAllProperties (object) { + const propertyNames = {}; + + while (object) { + const prototypeName = object && object.constructor && object.constructor.name || ''; + + Object.getOwnPropertyNames(object) + .sort() + .forEach((name) => { + if (Object.prototype.hasOwnProperty.call(propertyNames, name)) { + return; + } + + propertyNames[name] = { name, prototypeName }; + }); + + object = Object.getPrototypeOf(object); + } + + return Object.values(propertyNames); +} diff --git a/js/src/dapps/console/Autocomplete/index.js b/js/src/dapps/console/Autocomplete/index.js new file mode 100644 index 0000000000000000000000000000000000000000..5761be0e3ed50cb72fa427bf953c240a2de1bf80 --- /dev/null +++ b/js/src/dapps/console/Autocomplete/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 './autocomplete'; diff --git a/js/src/dapps/console/Console/console.css b/js/src/dapps/console/Console/console.css new file mode 100644 index 0000000000000000000000000000000000000000..a0b3db4ff36d9a2e2cb26e6268aadacdfa4e53a6 --- /dev/null +++ b/js/src/dapps/console/Console/console.css @@ -0,0 +1,58 @@ +/* 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 . +*/ + +.result { + border-top: 1px solid #eee; + display: flex; + font-family: dejavu sans mono, monospace; + padding: 0.35em 0.25em; + + &.error { + background-color: hsl(0, 100%, 97%); + + .text { + color: red; + } + } + + &.warn { + background-color: hsl(50, 100%, 95%); + } +} + +.type { + font-weight: bold !important; + font-size: 8pt; + padding: 0 0.5em 0 0.25em; +} + +.time { + color: gray; + padding: 0 1em 0 0.5em; +} + +.token { + white-space: pre-wrap; +} + +.text { + display: flex; +} + +.text .token:not(:first-child) { + margin-left: 0.5em; +} diff --git a/js/src/dapps/console/Console/console.js b/js/src/dapps/console/Console/console.js new file mode 100644 index 0000000000000000000000000000000000000000..75f9713a6661248f5b8ead2211a2a9d4453477c1 --- /dev/null +++ b/js/src/dapps/console/Console/console.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 { observer } from 'mobx-react'; +import React, { Component } from 'react'; +import ReactDOM from 'react-dom'; +import { ObjectInspector } from 'react-inspector'; + +import ConsoleStore from './console.store'; +import SettingsStore from '../Settings/settings.store'; + +import styles from './console.css'; + +const ICONS = { + debug: ' ', + error: '✖', + info: 'ℹ', + input: '>', + log: ' ', + result: '<', + warn: '⚠' +}; + +@observer +export default class Console extends Component { + consoleStore = ConsoleStore.get(); + settingsStore = SettingsStore.get(); + + render () { + return ( +
+ { this.renderResults() } +
+ ); + } + + renderResults () { + const { logs } = this.consoleStore; + + return logs.map((data, index) => { + const { type, timestamp } = data; + const values = this.consoleStore.logValues[index]; + const classes = [ styles.result, styles[type] ]; + + return ( +
+ + { this.renderTimestamp(timestamp) } + + { + values.map((value, valueIndex) => ( + + { this.toString(value) } + + )) + } + +
+ ); + }); + } + + renderTimestamp (timestamp) { + const { displayTimestamps } = this.settingsStore; + + if (!displayTimestamps) { + return null; + } + + return ( + + { new Date(timestamp).toISOString().slice(11, 23) } + + ); + } + + setRef = (node) => { + const element = ReactDOM.findDOMNode(node); + + this.consoleStore.setNode(element); + }; + + toString (value) { + if (typeof value === 'string') { + return value; + } + + if (value instanceof Error) { + return value.toString(); + } + + return ( + + ); + } +} diff --git a/js/src/dapps/console/Console/console.store.js b/js/src/dapps/console/Console/console.store.js new file mode 100644 index 0000000000000000000000000000000000000000..dc2fc6db4cac53fd26c31abc67e82f52559d3556 --- /dev/null +++ b/js/src/dapps/console/Console/console.store.js @@ -0,0 +1,126 @@ +// 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, observable } from 'mobx'; + +import AutocompleteStore from '../Autocomplete/autocomplete.store'; +import { evaluate } from '../utils'; + +let instance; + +export default class ConsoleStore { + @observable logs = []; + + autocompleteStore = AutocompleteStore.get(); + logValues = []; + node = null; + + constructor () { + this.attachConsole(); + } + + static get () { + if (!instance) { + instance = new ConsoleStore(); + } + + return instance; + } + + attachConsole () { + ['debug', 'error', 'info', 'log', 'warn'].forEach((level) => { + const old = window.console[level].bind(window.console); + + window.console[level] = (...args) => { + old(...args); + this.log({ type: level, values: args }); + }; + }); + } + + @action + clear () { + this.logs = []; + this.logValues = []; + } + + evaluate (input) { + this.log({ type: 'input', value: input }); + + setTimeout(() => { + const { result, error } = evaluate(input); + let value = error || result; + const type = error + ? 'error' + : 'result'; + + if (typeof value === 'string') { + value = `"${value}"`; + } + + if (value && typeof value === 'object' && typeof value.then === 'function') { + return value + .then((result) => { + this.log({ type: 'result', value: result }); + }) + .catch((error) => { + this.log({ type: 'error', value: error }); + }); + } + + this.log({ type, value }); + }); + } + + @action + log ({ type, value, values }) { + this.logs.push({ + type, + timestamp: Date.now() + }); + + if (values) { + this.logValues.push(values); + } else { + this.logValues.push([ value ]); + } + + this.autocompleteStore.setPosition(); + this.scroll(); + } + + setNode (node) { + this.node = node; + this.scroll(); + } + + scroll () { + if (!this.node) { + return; + } + + setTimeout(() => { + if (this.node.children.length === 0) { + return; + } + + // Scroll to the last child + this.node + .children[this.node.children.length - 1] + .scrollIntoView(false); + }, 50); + } +} diff --git a/js/src/dapps/console/Console/index.js b/js/src/dapps/console/Console/index.js new file mode 100644 index 0000000000000000000000000000000000000000..2956b330fe4e777e1a44304040d6b6b1324c8f16 --- /dev/null +++ b/js/src/dapps/console/Console/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 './console'; diff --git a/js/src/dapps/console/Header/header.css b/js/src/dapps/console/Header/header.css new file mode 100644 index 0000000000000000000000000000000000000000..116de6b8c91c8c4d285a1bf56ba5ec0cf8c107e9 --- /dev/null +++ b/js/src/dapps/console/Header/header.css @@ -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 . +*/ + +.container { + background-color: #f3f3f3; + border-bottom: 1px solid #ccc; + font-size: 12px; + padding: 0 0.5em; +} + +.tabs { + display: flex; +} + +.tab { + align-items: center; + box-sizing: border-box; + border: 1px solid transparent; + color: #333; + cursor: default; + display: flex; + height: 24px; + line-height: 15px; + margin-top: 2px; + padding: 2px 6px 2px 4px; + + &:hover, + &.active:hover { + background-color: #e5e5e5; + } + + &.active { + background-color: white; + border: 1px solid #ccc; + border-bottom: none; + } +} diff --git a/js/src/dapps/console/Header/header.js b/js/src/dapps/console/Header/header.js new file mode 100644 index 0000000000000000000000000000000000000000..c422b825604f9381d9797cac505e734107dbe693 --- /dev/null +++ b/js/src/dapps/console/Header/header.js @@ -0,0 +1,65 @@ +// 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 } from 'react'; + +import ApplicationStore from '../Application/application.store'; + +import styles from './header.css'; + +@observer +export default class Header extends Component { + application = ApplicationStore.get(); + + render () { + return ( +
+
+ { this.renderTabs() } +
+
+ ); + } + + renderTabs () { + const { view } = this.application; + + return this.application.views.map((tab) => { + const { label, id } = tab; + const classes = [ styles.tab ]; + const onClick = () => this.handleClickTab(id); + + if (id === view) { + classes.push(styles.active); + } + + return ( +
+ { label } +
+ ); + }); + } + + handleClickTab = (id) => { + this.application.setView(id); + }; +} diff --git a/js/src/dapps/console/Header/index.js b/js/src/dapps/console/Header/index.js new file mode 100644 index 0000000000000000000000000000000000000000..aef90266f940a3c7f8915c7ba910b62bec54317c --- /dev/null +++ b/js/src/dapps/console/Header/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 './header'; diff --git a/js/src/dapps/console/Input/index.js b/js/src/dapps/console/Input/index.js new file mode 100644 index 0000000000000000000000000000000000000000..29e00f72b19f9481ced013481cbec51dc551898c --- /dev/null +++ b/js/src/dapps/console/Input/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 './input'; diff --git a/js/src/dapps/console/Input/input.css b/js/src/dapps/console/Input/input.css new file mode 100644 index 0000000000000000000000000000000000000000..7b0c2306e48610966ee5a07638c1ad0fc9c45c51 --- /dev/null +++ b/js/src/dapps/console/Input/input.css @@ -0,0 +1,46 @@ +/* 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 . +*/ + +.type { + color: #59f; + font-weight: bold !important; + font-size: 11px; + padding: 0 0.5em 0 0.25em; +} + +.inputContainer { + flex: 1; +} + +.input { + border: 0; + margin: 0; + padding: 0; + color: black; + height: 100%; + font-size: 11px; + resize: none; + width: 100%; +} + +.container { + border-top: 1px solid lightgray; + display: flex; + flex: 1; + padding: 0.25em; + position: relative; +} diff --git a/js/src/dapps/console/Input/input.js b/js/src/dapps/console/Input/input.js new file mode 100644 index 0000000000000000000000000000000000000000..3263aff38487ef50c0fd84fe456611c2e981ff6f --- /dev/null +++ b/js/src/dapps/console/Input/input.js @@ -0,0 +1,145 @@ +// 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 keycode from 'keycode'; +import { observer } from 'mobx-react'; +import React, { Component } from 'react'; +import ReactDOM from 'react-dom'; + +import Autocomplete from '../Autocomplete'; + +import AutocompleteStore from '../Autocomplete/autocomplete.store'; +import ConsoleStore from '../Console/console.store'; +import InputStore from './input.store'; +import SettingsStore from '../Settings/settings.store'; + +import styles from './input.css'; + +@observer +export default class Input extends Component { + autocompleteStore = AutocompleteStore.get(); + consoleStore = ConsoleStore.get(); + inputStore = InputStore.get(); + settingsStore = SettingsStore.get(); + + render () { + const { input } = this.inputStore; + + return ( +
+ + > +
+