Merge branch 'master' into ng-array-parameters

This commit is contained in:
Nicolas Gotchac 2016-11-14 17:06:02 +01:00
commit 97378daee5
71 changed files with 3996 additions and 249 deletions

2
Cargo.lock generated
View File

@ -1249,7 +1249,7 @@ dependencies = [
[[package]] [[package]]
name = "parity-ui-precompiled" name = "parity-ui-precompiled"
version = "1.4.0" version = "1.4.0"
source = "git+https://github.com/ethcore/js-precompiled.git#76e3cbf0c68acb72174e666216d15a5fff5100d6" source = "git+https://github.com/ethcore/js-precompiled.git#7bf550e482ef274480d1fd7e1ceb2efddd747dbd"
dependencies = [ dependencies = [
"parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
] ]

View File

@ -89,7 +89,7 @@ impl From<ethjson::blockchain::Account> for PodAccount {
let key: U256 = key.into(); let key: U256 = key.into();
let value: U256 = value.into(); let value: U256 = value.into();
(H256::from(key), H256::from(value)) (H256::from(key), H256::from(value))
}).collect() }).collect(),
} }
} }
} }
@ -99,8 +99,12 @@ impl From<ethjson::spec::Account> for PodAccount {
PodAccount { PodAccount {
balance: a.balance.map_or_else(U256::zero, Into::into), balance: a.balance.map_or_else(U256::zero, Into::into),
nonce: a.nonce.map_or_else(U256::zero, Into::into), nonce: a.nonce.map_or_else(U256::zero, Into::into),
code: a.code.map(Into::into).or_else(|| Some(Vec::new())), code: Some(a.code.map_or_else(Vec::new, Into::into)),
storage: BTreeMap::new() storage: a.storage.map_or_else(BTreeMap::new, |s| s.into_iter().map(|(key, value)| {
let key: U256 = key.into();
let value: U256 = value.into();
(H256::from(key), H256::from(value))
}).collect()),
} }
} }
} }
@ -112,7 +116,7 @@ impl fmt::Display for PodAccount {
self.nonce, self.nonce,
self.code.as_ref().map_or(0, |c| c.len()), self.code.as_ref().map_or(0, |c| c.len()),
self.code.as_ref().map_or_else(H256::new, |c| c.sha3()), self.code.as_ref().map_or_else(H256::new, |c| c.sha3()),
self.storage.len() self.storage.len(),
) )
} }
} }

View File

@ -45,6 +45,8 @@ pub enum Error {
MissingCode(Vec<H256>), MissingCode(Vec<H256>),
/// Unrecognized code encoding. /// Unrecognized code encoding.
UnrecognizedCodeState(u8), UnrecognizedCodeState(u8),
/// Restoration aborted.
RestorationAborted,
/// Trie error. /// Trie error.
Trie(TrieError), Trie(TrieError),
/// Decoder error. /// Decoder error.
@ -67,6 +69,7 @@ impl fmt::Display for Error {
a pruned database. Please re-run with the --pruning archive flag."), a pruned database. Please re-run with the --pruning archive flag."),
Error::MissingCode(ref missing) => write!(f, "Incomplete snapshot: {} contract codes not found.", missing.len()), Error::MissingCode(ref missing) => write!(f, "Incomplete snapshot: {} contract codes not found.", missing.len()),
Error::UnrecognizedCodeState(state) => write!(f, "Unrecognized code encoding ({})", state), Error::UnrecognizedCodeState(state) => write!(f, "Unrecognized code encoding ({})", state),
Error::RestorationAborted => write!(f, "Snapshot restoration aborted."),
Error::Io(ref err) => err.fmt(f), Error::Io(ref err) => err.fmt(f),
Error::Decoder(ref err) => err.fmt(f), Error::Decoder(ref err) => err.fmt(f),
Error::Trie(ref err) => err.fmt(f), Error::Trie(ref err) => err.fmt(f),

View File

@ -407,30 +407,28 @@ impl StateRebuilder {
} }
/// Feed an uncompressed state chunk into the rebuilder. /// Feed an uncompressed state chunk into the rebuilder.
pub fn feed(&mut self, chunk: &[u8]) -> Result<(), ::error::Error> { pub fn feed(&mut self, chunk: &[u8], flag: &AtomicBool) -> Result<(), ::error::Error> {
let rlp = UntrustedRlp::new(chunk); let rlp = UntrustedRlp::new(chunk);
let empty_rlp = StateAccount::new_basic(U256::zero(), U256::zero()).rlp(); let empty_rlp = StateAccount::new_basic(U256::zero(), U256::zero()).rlp();
let account_fat_rlps: Vec<_> = rlp.iter().map(|r| r.as_raw()).collect();
let mut pairs = Vec::with_capacity(rlp.item_count()); let mut pairs = Vec::with_capacity(rlp.item_count());
// initialize the pairs vector with empty values so we have slots to write into. // initialize the pairs vector with empty values so we have slots to write into.
pairs.resize(rlp.item_count(), (H256::new(), Vec::new())); pairs.resize(rlp.item_count(), (H256::new(), Vec::new()));
let chunk_size = account_fat_rlps.len() / ::num_cpus::get() + 1; let status = try!(rebuild_accounts(
self.db.as_hashdb_mut(),
rlp,
&mut pairs,
&self.code_map,
flag
));
// new code contained within this chunk. for (addr_hash, code_hash) in status.missing_code {
let mut chunk_code = HashMap::new(); self.missing_code.entry(code_hash).or_insert_with(Vec::new).push(addr_hash);
for (account_chunk, out_pairs_chunk) in account_fat_rlps.chunks(chunk_size).zip(pairs.chunks_mut(chunk_size)) {
let code_map = &self.code_map;
let status = try!(rebuild_accounts(self.db.as_hashdb_mut(), account_chunk, out_pairs_chunk, code_map));
chunk_code.extend(status.new_code);
for (addr_hash, code_hash) in status.missing_code {
self.missing_code.entry(code_hash).or_insert_with(Vec::new).push(addr_hash);
}
} }
// patch up all missing code. must be done after collecting all new missing code entries. // patch up all missing code. must be done after collecting all new missing code entries.
for (code_hash, code) in chunk_code { for (code_hash, code) in status.new_code {
for addr_hash in self.missing_code.remove(&code_hash).unwrap_or_else(Vec::new) { for addr_hash in self.missing_code.remove(&code_hash).unwrap_or_else(Vec::new) {
let mut db = AccountDBMut::from_hash(self.db.as_hashdb_mut(), addr_hash); let mut db = AccountDBMut::from_hash(self.db.as_hashdb_mut(), addr_hash);
db.emplace(code_hash, DBValue::from_slice(&code)); db.emplace(code_hash, DBValue::from_slice(&code));
@ -450,6 +448,8 @@ impl StateRebuilder {
}; };
for (hash, thin_rlp) in pairs { for (hash, thin_rlp) in pairs {
if !flag.load(Ordering::SeqCst) { return Err(Error::RestorationAborted.into()) }
if &thin_rlp[..] != &empty_rlp[..] { if &thin_rlp[..] != &empty_rlp[..] {
self.bloom.set(&*hash); self.bloom.set(&*hash);
} }
@ -487,17 +487,18 @@ struct RebuiltStatus {
} }
// rebuild a set of accounts and their storage. // rebuild a set of accounts and their storage.
// returns // returns a status detailing newly-loaded code and accounts missing code.
fn rebuild_accounts( fn rebuild_accounts(
db: &mut HashDB, db: &mut HashDB,
account_chunk: &[&[u8]], account_fat_rlps: UntrustedRlp,
out_chunk: &mut [(H256, Bytes)], out_chunk: &mut [(H256, Bytes)],
code_map: &HashMap<H256, Bytes> code_map: &HashMap<H256, Bytes>,
abort_flag: &AtomicBool
) -> Result<RebuiltStatus, ::error::Error> ) -> Result<RebuiltStatus, ::error::Error>
{ {
let mut status = RebuiltStatus::default(); let mut status = RebuiltStatus::default();
for (account_pair, out) in account_chunk.into_iter().zip(out_chunk) { for (account_rlp, out) in account_fat_rlps.into_iter().zip(out_chunk) {
let account_rlp = UntrustedRlp::new(account_pair); if !abort_flag.load(Ordering::SeqCst) { return Err(Error::RestorationAborted.into()) }
let hash: H256 = try!(account_rlp.val_at(0)); let hash: H256 = try!(account_rlp.val_at(0));
let fat_rlp = try!(account_rlp.at(1)); let fat_rlp = try!(account_rlp.at(1));
@ -580,7 +581,7 @@ impl BlockRebuilder {
/// Feed the rebuilder an uncompressed block chunk. /// Feed the rebuilder an uncompressed block chunk.
/// Returns the number of blocks fed or any errors. /// Returns the number of blocks fed or any errors.
pub fn feed(&mut self, chunk: &[u8], engine: &Engine) -> Result<u64, ::error::Error> { pub fn feed(&mut self, chunk: &[u8], engine: &Engine, abort_flag: &AtomicBool) -> Result<u64, ::error::Error> {
use basic_types::Seal::With; use basic_types::Seal::With;
use util::U256; use util::U256;
use util::triehash::ordered_trie_root; use util::triehash::ordered_trie_root;
@ -601,6 +602,8 @@ impl BlockRebuilder {
let parent_total_difficulty = try!(rlp.val_at::<U256>(2)); let parent_total_difficulty = try!(rlp.val_at::<U256>(2));
for idx in 3..item_count { for idx in 3..item_count {
if !abort_flag.load(Ordering::SeqCst) { return Err(Error::RestorationAborted.into()) }
let pair = try!(rlp.at(idx)); let pair = try!(rlp.at(idx));
let abridged_rlp = try!(pair.at(0)).as_raw().to_owned(); let abridged_rlp = try!(pair.at(0)).as_raw().to_owned();
let abridged_block = AbridgedBlock::from_raw(abridged_rlp); let abridged_block = AbridgedBlock::from_raw(abridged_rlp);

View File

@ -118,12 +118,12 @@ impl Restoration {
}) })
} }
// feeds a state chunk // feeds a state chunk, aborts early if `flag` becomes false.
fn feed_state(&mut self, hash: H256, chunk: &[u8]) -> Result<(), Error> { fn feed_state(&mut self, hash: H256, chunk: &[u8], flag: &AtomicBool) -> Result<(), Error> {
if self.state_chunks_left.remove(&hash) { if self.state_chunks_left.remove(&hash) {
let len = try!(snappy::decompress_into(chunk, &mut self.snappy_buffer)); let len = try!(snappy::decompress_into(chunk, &mut self.snappy_buffer));
try!(self.state.feed(&self.snappy_buffer[..len])); try!(self.state.feed(&self.snappy_buffer[..len], flag));
if let Some(ref mut writer) = self.writer.as_mut() { if let Some(ref mut writer) = self.writer.as_mut() {
try!(writer.write_state_chunk(hash, chunk)); try!(writer.write_state_chunk(hash, chunk));
@ -134,11 +134,11 @@ impl Restoration {
} }
// feeds a block chunk // feeds a block chunk
fn feed_blocks(&mut self, hash: H256, chunk: &[u8], engine: &Engine) -> Result<(), Error> { fn feed_blocks(&mut self, hash: H256, chunk: &[u8], engine: &Engine, flag: &AtomicBool) -> Result<(), Error> {
if self.block_chunks_left.remove(&hash) { if self.block_chunks_left.remove(&hash) {
let len = try!(snappy::decompress_into(chunk, &mut self.snappy_buffer)); let len = try!(snappy::decompress_into(chunk, &mut self.snappy_buffer));
try!(self.blocks.feed(&self.snappy_buffer[..len], engine)); try!(self.blocks.feed(&self.snappy_buffer[..len], engine, flag));
if let Some(ref mut writer) = self.writer.as_mut() { if let Some(ref mut writer) = self.writer.as_mut() {
try!(writer.write_block_chunk(hash, chunk)); try!(writer.write_block_chunk(hash, chunk));
} }
@ -224,6 +224,7 @@ pub struct Service {
db_restore: Arc<DatabaseRestore>, db_restore: Arc<DatabaseRestore>,
progress: super::Progress, progress: super::Progress,
taking_snapshot: AtomicBool, taking_snapshot: AtomicBool,
restoring_snapshot: AtomicBool,
} }
impl Service { impl Service {
@ -244,6 +245,7 @@ impl Service {
db_restore: params.db_restore, db_restore: params.db_restore,
progress: Default::default(), progress: Default::default(),
taking_snapshot: AtomicBool::new(false), taking_snapshot: AtomicBool::new(false),
restoring_snapshot: AtomicBool::new(false),
}; };
// create the root snapshot dir if it doesn't exist. // create the root snapshot dir if it doesn't exist.
@ -436,6 +438,8 @@ impl Service {
state_chunks_done: self.state_chunks.load(Ordering::SeqCst) as u32, state_chunks_done: self.state_chunks.load(Ordering::SeqCst) as u32,
block_chunks_done: self.block_chunks.load(Ordering::SeqCst) as u32, block_chunks_done: self.block_chunks.load(Ordering::SeqCst) as u32,
}; };
self.restoring_snapshot.store(true, Ordering::SeqCst);
Ok(()) Ok(())
} }
@ -490,8 +494,8 @@ impl Service {
}; };
(match is_state { (match is_state {
true => rest.feed_state(hash, chunk), true => rest.feed_state(hash, chunk, &self.restoring_snapshot),
false => rest.feed_blocks(hash, chunk, &*self.engine), false => rest.feed_blocks(hash, chunk, &*self.engine, &self.restoring_snapshot),
}.map(|_| rest.is_done()), rest.db.clone()) }.map(|_| rest.is_done()), rest.db.clone())
}; };
@ -573,6 +577,7 @@ impl SnapshotService for Service {
} }
fn abort_restore(&self) { fn abort_restore(&self) {
self.restoring_snapshot.store(false, Ordering::SeqCst);
*self.restoration.lock() = None; *self.restoration.lock() = None;
*self.status.lock() = RestorationStatus::Inactive; *self.status.lock() = RestorationStatus::Inactive;
} }

View File

@ -17,10 +17,11 @@
//! Block chunker and rebuilder tests. //! Block chunker and rebuilder tests.
use devtools::RandomTempPath; use devtools::RandomTempPath;
use error::Error;
use blockchain::generator::{ChainGenerator, ChainIterator, BlockFinalizer}; use blockchain::generator::{ChainGenerator, ChainIterator, BlockFinalizer};
use blockchain::BlockChain; use blockchain::BlockChain;
use snapshot::{chunk_blocks, BlockRebuilder, Progress}; use snapshot::{chunk_blocks, BlockRebuilder, Error as SnapshotError, Progress};
use snapshot::io::{PackedReader, PackedWriter, SnapshotReader, SnapshotWriter}; use snapshot::io::{PackedReader, PackedWriter, SnapshotReader, SnapshotWriter};
use util::{Mutex, snappy}; use util::{Mutex, snappy};
@ -28,6 +29,7 @@ use util::kvdb::{Database, DatabaseConfig};
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use std::sync::atomic::AtomicBool;
fn chunk_and_restore(amount: u64) { fn chunk_and_restore(amount: u64) {
let mut canon_chain = ChainGenerator::default(); let mut canon_chain = ChainGenerator::default();
@ -75,10 +77,11 @@ fn chunk_and_restore(amount: u64) {
let mut rebuilder = BlockRebuilder::new(new_chain, new_db.clone(), &manifest).unwrap(); let mut rebuilder = BlockRebuilder::new(new_chain, new_db.clone(), &manifest).unwrap();
let reader = PackedReader::new(&snapshot_path).unwrap().unwrap(); let reader = PackedReader::new(&snapshot_path).unwrap().unwrap();
let engine = ::engines::NullEngine::new(Default::default(), Default::default()); let engine = ::engines::NullEngine::new(Default::default(), Default::default());
let flag = AtomicBool::new(true);
for chunk_hash in &reader.manifest().block_hashes { for chunk_hash in &reader.manifest().block_hashes {
let compressed = reader.chunk(*chunk_hash).unwrap(); let compressed = reader.chunk(*chunk_hash).unwrap();
let chunk = snappy::decompress(&compressed).unwrap(); let chunk = snappy::decompress(&compressed).unwrap();
rebuilder.feed(&chunk, &engine).unwrap(); rebuilder.feed(&chunk, &engine, &flag).unwrap();
} }
rebuilder.finalize(HashMap::new()).unwrap(); rebuilder.finalize(HashMap::new()).unwrap();
@ -93,3 +96,46 @@ fn chunk_and_restore_500() { chunk_and_restore(500) }
#[test] #[test]
fn chunk_and_restore_40k() { chunk_and_restore(40000) } fn chunk_and_restore_40k() { chunk_and_restore(40000) }
#[test]
fn checks_flag() {
use ::rlp::{RlpStream, Stream};
use util::H256;
let mut stream = RlpStream::new_list(5);
stream.append(&100u64)
.append(&H256::default())
.append(&(!0u64));
stream.append_empty_data().append_empty_data();
let genesis = {
let mut canon_chain = ChainGenerator::default();
let mut finalizer = BlockFinalizer::default();
canon_chain.generate(&mut finalizer).unwrap()
};
let chunk = stream.out();
let path = RandomTempPath::create_dir();
let db_cfg = DatabaseConfig::with_columns(::db::NUM_COLUMNS);
let db = Arc::new(Database::open(&db_cfg, path.as_str()).unwrap());
let chain = BlockChain::new(Default::default(), &genesis, db.clone());
let engine = ::engines::NullEngine::new(Default::default(), Default::default());
let manifest = ::snapshot::ManifestData {
state_hashes: Vec::new(),
block_hashes: Vec::new(),
state_root: ::util::sha3::SHA3_NULL_RLP,
block_number: 102,
block_hash: H256::default(),
};
let mut rebuilder = BlockRebuilder::new(chain, db.clone(), &manifest).unwrap();
match rebuilder.feed(&chunk, &engine, &AtomicBool::new(false)) {
Err(Error::Snapshot(SnapshotError::RestorationAborted)) => {}
_ => panic!("Wrong result on abort flag set")
}
}

View File

@ -16,10 +16,12 @@
//! State snapshotting tests. //! State snapshotting tests.
use snapshot::{chunk_state, Progress, StateRebuilder}; use snapshot::{chunk_state, Error as SnapshotError, Progress, StateRebuilder};
use snapshot::io::{PackedReader, PackedWriter, SnapshotReader, SnapshotWriter}; use snapshot::io::{PackedReader, PackedWriter, SnapshotReader, SnapshotWriter};
use super::helpers::{compare_dbs, StateProducer}; use super::helpers::{compare_dbs, StateProducer};
use error::Error;
use rand::{XorShiftRng, SeedableRng}; use rand::{XorShiftRng, SeedableRng};
use util::hash::H256; use util::hash::H256;
use util::journaldb::{self, Algorithm}; use util::journaldb::{self, Algorithm};
@ -29,6 +31,7 @@ use util::Mutex;
use devtools::RandomTempPath; use devtools::RandomTempPath;
use std::sync::Arc; use std::sync::Arc;
use std::sync::atomic::AtomicBool;
#[test] #[test]
fn snap_and_restore() { fn snap_and_restore() {
@ -65,11 +68,13 @@ fn snap_and_restore() {
let mut rebuilder = StateRebuilder::new(new_db.clone(), Algorithm::Archive); let mut rebuilder = StateRebuilder::new(new_db.clone(), Algorithm::Archive);
let reader = PackedReader::new(&snap_file).unwrap().unwrap(); let reader = PackedReader::new(&snap_file).unwrap().unwrap();
let flag = AtomicBool::new(true);
for chunk_hash in &reader.manifest().state_hashes { for chunk_hash in &reader.manifest().state_hashes {
let raw = reader.chunk(*chunk_hash).unwrap(); let raw = reader.chunk(*chunk_hash).unwrap();
let chunk = ::util::snappy::decompress(&raw).unwrap(); let chunk = ::util::snappy::decompress(&raw).unwrap();
rebuilder.feed(&chunk).unwrap(); rebuilder.feed(&chunk, &flag).unwrap();
} }
assert_eq!(rebuilder.state_root(), state_root); assert_eq!(rebuilder.state_root(), state_root);
@ -82,3 +87,52 @@ fn snap_and_restore() {
compare_dbs(&old_db, new_db.as_hashdb()); compare_dbs(&old_db, new_db.as_hashdb());
} }
#[test]
fn checks_flag() {
let mut producer = StateProducer::new();
let mut rng = XorShiftRng::from_seed([5, 6, 7, 8]);
let mut old_db = MemoryDB::new();
let db_cfg = DatabaseConfig::with_columns(::db::NUM_COLUMNS);
for _ in 0..10 {
producer.tick(&mut rng, &mut old_db);
}
let snap_dir = RandomTempPath::create_dir();
let mut snap_file = snap_dir.as_path().to_owned();
snap_file.push("SNAP");
let state_root = producer.state_root();
let writer = Mutex::new(PackedWriter::new(&snap_file).unwrap());
let state_hashes = chunk_state(&old_db, &state_root, &writer, &Progress::default()).unwrap();
writer.into_inner().finish(::snapshot::ManifestData {
state_hashes: state_hashes,
block_hashes: Vec::new(),
state_root: state_root,
block_number: 0,
block_hash: H256::default(),
}).unwrap();
let mut db_path = snap_dir.as_path().to_owned();
db_path.push("db");
{
let new_db = Arc::new(Database::open(&db_cfg, &db_path.to_string_lossy()).unwrap());
let mut rebuilder = StateRebuilder::new(new_db.clone(), Algorithm::Archive);
let reader = PackedReader::new(&snap_file).unwrap().unwrap();
let flag = AtomicBool::new(false);
for chunk_hash in &reader.manifest().state_hashes {
let raw = reader.chunk(*chunk_hash).unwrap();
let chunk = ::util::snappy::decompress(&raw).unwrap();
match rebuilder.feed(&chunk, &flag) {
Err(Error::Snapshot(SnapshotError::RestorationAborted)) => {},
_ => panic!("unexpected result when feeding with flag off"),
}
}
}
}

View File

@ -162,7 +162,7 @@ impl Spec {
/// Get the configured Network ID. /// Get the configured Network ID.
pub fn network_id(&self) -> usize { self.params.network_id } pub fn network_id(&self) -> usize { self.params.network_id }
/// Get the configured Network ID. /// Get the configured subprotocol name.
pub fn subprotocol_name(&self) -> String { self.params.subprotocol_name.clone() } pub fn subprotocol_name(&self) -> String { self.params.subprotocol_name.clone() }
/// Get the configured network fork block. /// Get the configured network fork block.

View File

@ -7,6 +7,6 @@ JavaScript APIs and UIs for Parity.
0. Install [Node](https://nodejs.org/) if not already available 0. Install [Node](https://nodejs.org/) if not already available
0. Change to the `js` directory inside `parity/` 0. Change to the `js` directory inside `parity/`
0. Install the npm modules via `npm install` 0. Install the npm modules via `npm install`
0. Parity should be run with `parity --signer-no-validation [...options]` (where `options` can be `--chain testnet`) 0. Parity should be run with `parity --ui-no-validation [...options]` (where `options` can be `--chain testnet`)
0. Start the development environment via `npm start` 0. Start the development environment via `npm start`
0. Connect to the [UI](http://localhost:3000) 0. Connect to the [UI](http://localhost:3000)

View File

@ -1,6 +1,6 @@
{ {
"name": "parity.js", "name": "parity.js",
"version": "0.2.31", "version": "0.2.38",
"main": "release/index.js", "main": "release/index.js",
"jsnext:main": "src/index.js", "jsnext:main": "src/index.js",
"author": "Parity Team <admin@parity.io>", "author": "Parity Team <admin@parity.io>",
@ -100,6 +100,7 @@
"postcss-loader": "^0.8.1", "postcss-loader": "^0.8.1",
"postcss-nested": "^1.0.0", "postcss-nested": "^1.0.0",
"postcss-simple-vars": "^3.0.0", "postcss-simple-vars": "^3.0.0",
"raw-loader": "^0.5.1",
"react-addons-test-utils": "^15.3.0", "react-addons-test-utils": "^15.3.0",
"react-copy-to-clipboard": "^4.2.3", "react-copy-to-clipboard": "^4.2.3",
"react-hot-loader": "^1.3.0", "react-hot-loader": "^1.3.0",
@ -118,6 +119,7 @@
"dependencies": { "dependencies": {
"bignumber.js": "^2.3.0", "bignumber.js": "^2.3.0",
"blockies": "0.0.2", "blockies": "0.0.2",
"brace": "^0.9.0",
"bytes": "^2.4.0", "bytes": "^2.4.0",
"chart.js": "^2.3.0", "chart.js": "^2.3.0",
"es6-promise": "^3.2.1", "es6-promise": "^3.2.1",
@ -138,9 +140,11 @@
"moment": "^2.14.1", "moment": "^2.14.1",
"qs": "^6.3.0", "qs": "^6.3.0",
"react": "^15.2.1", "react": "^15.2.1",
"react-ace": "^4.0.0",
"react-addons-css-transition-group": "^15.2.1", "react-addons-css-transition-group": "^15.2.1",
"react-chartjs-2": "^1.5.0", "react-chartjs-2": "^1.5.0",
"react-dom": "^15.2.1", "react-dom": "^15.2.1",
"react-dropzone": "^3.7.3",
"react-redux": "^4.4.5", "react-redux": "^4.4.5",
"react-router": "^2.6.1", "react-router": "^2.6.1",
"react-router-redux": "^4.0.5", "react-router-redux": "^4.0.5",
@ -152,10 +156,13 @@
"redux-thunk": "^2.1.0", "redux-thunk": "^2.1.0",
"rlp": "^2.0.0", "rlp": "^2.0.0",
"scryptsy": "^2.0.0", "scryptsy": "^2.0.0",
"solc": "ngotchac/solc-js",
"store": "^1.3.20", "store": "^1.3.20",
"utf8": "^2.1.1", "utf8": "^2.1.1",
"valid-url": "^1.0.9",
"validator": "^5.7.0", "validator": "^5.7.0",
"web3": "^0.17.0-beta", "web3": "^0.17.0-beta",
"whatwg-fetch": "^1.0.0" "whatwg-fetch": "^1.0.0",
"worker-loader": "^0.7.1"
} }
} }

View File

@ -309,7 +309,6 @@ export default class Contract {
try { try {
subscriptions[idx].callback(null, this.parseEventLogs(logs)); subscriptions[idx].callback(null, this.parseEventLogs(logs));
} catch (error) { } catch (error) {
this.unsubscribe(idx);
console.error('_sendSubscriptionChanges', error); console.error('_sendSubscriptionChanges', error);
} }
}); });

View File

@ -107,7 +107,6 @@ export default class Manager {
callback(error, data); callback(error, data);
} catch (error) { } catch (error) {
console.error(`Unable to update callback for subscriptionId ${subscriptionId}`, error); console.error(`Unable to update callback for subscriptionId ${subscriptionId}`, error);
this.unsubscribe(subscriptionId);
} }
} }

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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);
}

View File

@ -28,26 +28,26 @@ export function attachInterface () {
return Promise return Promise
.all([ .all([
registry.getAddress.call({}, [api.util.sha3('githubhint'), 'A']), registry.getAddress.call({}, [api.util.sha3('githubhint'), 'A']),
api.eth.accounts(),
api.parity.accounts() api.parity.accounts()
]); ]);
}) })
.then(([address, addresses, accountsInfo]) => { .then(([address, accountsInfo]) => {
accountsInfo = accountsInfo || {};
console.log(`githubhint was found at ${address}`); console.log(`githubhint was found at ${address}`);
const contract = api.newContract(abis.githubhint, address); const contract = api.newContract(abis.githubhint, address);
const accounts = addresses.reduce((obj, address) => { const accounts = Object
const info = accountsInfo[address] || {}; .keys(accountsInfo)
.filter((address) => accountsInfo[address].uuid)
.reduce((obj, address) => {
const account = accountsInfo[address];
return Object.assign(obj, { return Object.assign(obj, {
[address]: { [address]: {
address, address,
name: info.name, name: account.name
uuid: info.uuid }
} });
}); }, {});
}, {});
const fromAddress = Object.keys(accounts)[0]; const fromAddress = Object.keys(accounts)[0];
return { return {

View File

@ -49,3 +49,15 @@
padding-bottom: 0 !important; padding-bottom: 0 !important;
} }
} }
.warning {
background: #f80;
bottom: 0;
color: #fff;
left: 0;
opacity: 1;
padding: 1.5em;
position: fixed;
right: 50%;
z-index: 100;
}

View File

@ -53,6 +53,7 @@ export default class Application extends Component {
}; };
render () { render () {
const { api } = window.parity;
const { const {
actions, actions,
accounts, contacts, accounts, contacts,
@ -60,9 +61,11 @@ export default class Application extends Component {
lookup, lookup,
events events
} = this.props; } = this.props;
let warning = null;
return ( return (
<div> <div>
{ warning }
<div className={ styles.header }> <div className={ styles.header }>
<h1>RΞgistry</h1> <h1>RΞgistry</h1>
<Accounts { ...accounts } actions={ actions.accounts } /> <Accounts { ...accounts } actions={ actions.accounts } />
@ -70,13 +73,11 @@ export default class Application extends Component {
{ contract && fee ? ( { contract && fee ? (
<div> <div>
<Lookup { ...lookup } accounts={ accounts.all } contacts={ contacts } actions={ actions.lookup } /> <Lookup { ...lookup } accounts={ accounts.all } contacts={ contacts } actions={ actions.lookup } />
{ this.renderActions() } { this.renderActions() }
<Events { ...events } accounts={ accounts.all } contacts={ contacts } actions={ actions.events } /> <Events { ...events } accounts={ accounts.all } contacts={ contacts } actions={ actions.events } />
<p className={ styles.address }> <div className={ styles.warning }>
The Registry is provided by the contract at <code>{ contract.address }.</code> WARNING: The name registry is experimental. Please ensure that you understand the risks, benefits & consequences of registering a name before doing so. A non-refundable fee of { api.util.fromWei(fee).toFormat(3) }<small>ETH</small> is required for all registrations.
</p> </div>
</div> </div>
) : ( ) : (
<CircularProgress size={ 60 } /> <CircularProgress size={ 60 } />

View File

@ -19,18 +19,16 @@ import { api } from '../parity';
export const set = (addresses) => ({ type: 'addresses set', addresses }); export const set = (addresses) => ({ type: 'addresses set', addresses });
export const fetch = () => (dispatch) => { export const fetch = () => (dispatch) => {
return Promise return api.parity
.all([ .accounts()
api.eth.accounts(), .then((accountsInfo) => {
api.parity.accounts() const addresses = Object
]) .keys(accountsInfo)
.then(([ accounts, data ]) => { .filter((address) => accountsInfo[address] && !accountsInfo[address].meta.deleted)
data = data || {};
const addresses = Object.keys(data)
.filter((address) => data[address] && !data[address].meta.deleted)
.map((address) => ({ .map((address) => ({
...data[address], address, ...accountsInfo[address],
isAccount: accounts.includes(address) address,
isAccount: !!accountsInfo[address].uuid
})); }));
dispatch(set(addresses)); dispatch(set(addresses));
}) })

View File

@ -146,7 +146,7 @@ export default class Import extends Component {
} }
sortFunctions = (a, b) => { sortFunctions = (a, b) => {
return a.name.localeCompare(b.name); return (a.name || '').localeCompare(b.name || '');
} }
countFunctions () { countFunctions () {

View File

@ -49,26 +49,26 @@ export function attachInterface (callback) {
return Promise return Promise
.all([ .all([
registry.getAddress.call({}, [api.util.sha3('signaturereg'), 'A']), registry.getAddress.call({}, [api.util.sha3('signaturereg'), 'A']),
api.eth.accounts(),
api.parity.accounts() api.parity.accounts()
]); ]);
}) })
.then(([address, addresses, accountsInfo]) => { .then(([address, accountsInfo]) => {
accountsInfo = accountsInfo || {};
console.log(`signaturereg was found at ${address}`); console.log(`signaturereg was found at ${address}`);
const contract = api.newContract(abis.signaturereg, address); const contract = api.newContract(abis.signaturereg, address);
const accounts = addresses.reduce((obj, address) => { const accounts = Object
const info = accountsInfo[address] || {}; .keys(accountsInfo)
.filter((address) => accountsInfo[address].uuid)
.reduce((obj, address) => {
const info = accountsInfo[address] || {};
return Object.assign(obj, { return Object.assign(obj, {
[address]: { [address]: {
address, address,
name: info.name || 'Unnamed', name: info.name || 'Unnamed'
uuid: info.uuid }
} });
}); }, {});
}, {});
const fromAddress = Object.keys(accounts)[0]; const fromAddress = Object.keys(accounts)[0];
return { return {

View File

@ -35,16 +35,13 @@ export const setSelectedAccount = (address) => ({
}); });
export const loadAccounts = () => (dispatch) => { export const loadAccounts = () => (dispatch) => {
Promise api.parity
.all([ .accounts()
api.eth.accounts(), .then((accountsInfo) => {
api.parity.accounts() const accountsList = Object
]) .keys(accountsInfo)
.then(([ accounts, accountsInfo ]) => { .filter((address) => accountsInfo[address].uuid)
accountsInfo = accountsInfo || {}; .map((address) => ({
const accountsList = accounts
.map(address => ({
...accountsInfo[address], ...accountsInfo[address],
address address
})); }));

View File

@ -20,3 +20,15 @@
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
} }
.warning {
background: #f80;
bottom: 0;
color: #fff;
left: 0;
opacity: 1;
padding: 1.5em;
position: fixed;
right: 50%;
z-index: 100;
}

View File

@ -17,6 +17,8 @@
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import getMuiTheme from 'material-ui/styles/getMuiTheme'; import getMuiTheme from 'material-ui/styles/getMuiTheme';
import { api } from '../parity';
import Loading from '../Loading'; import Loading from '../Loading';
import Status from '../Status'; import Status from '../Status';
import Tokens from '../Tokens'; import Tokens from '../Tokens';
@ -59,6 +61,9 @@ export default class Application extends Component {
<Actions /> <Actions />
<Tokens /> <Tokens />
<div className={ styles.warning }>
WARNING: The token registry is experimental. Please ensure that you understand the steps, risks, benefits & consequences of registering a token before doing so. A non-refundable fee of { api.util.fromWei(contract.fee).toFormat(3) }<small>ETH</small> is required for all registrations.
</div>
</div> </div>
); );
} }

View File

@ -31,6 +31,12 @@
.title { .title {
font-size: 3rem; font-size: 3rem;
font-weight: 300; font-weight: 300;
margin-top: 0; margin: 0;
text-transform: uppercase; text-transform: uppercase;
} }
.byline {
font-size: 1.25em;
opacity: 0.75;
margin: 0 0 1.75em 0;
}

View File

@ -29,17 +29,12 @@ export default class Status extends Component {
}; };
render () { render () {
const { address, fee } = this.props; const { fee } = this.props;
return ( return (
<div className={ styles.status }> <div className={ styles.status }>
<h1 className={ styles.title }>Token Registry</h1> <h1 className={ styles.title }>Token Registry</h1>
<h3 className={ styles.byline }>A global registry of all recognised tokens on the network</h3>
<Chip
isAddress
value={ address }
label='Address' />
<Chip <Chip
isAddress={ false } isAddress={ false }
value={ api.util.fromWei(fee).toFixed(3) + 'ETH' } value={ api.util.fromWei(fee).toFixed(3) + 'ETH' }

View File

@ -31,7 +31,7 @@ import ContractInstances from './contracts';
import { initStore } from './redux'; import { initStore } from './redux';
import { ContextProvider, muiTheme } from './ui'; import { ContextProvider, muiTheme } from './ui';
import { Accounts, Account, Addresses, Address, Application, Contract, Contracts, Dapp, Dapps, Settings, SettingsBackground, SettingsParity, SettingsProxy, SettingsViews, Signer, Status } from './views'; import { Accounts, Account, Addresses, Address, Application, Contract, Contracts, WriteContract, Dapp, Dapps, Settings, SettingsBackground, SettingsParity, SettingsProxy, SettingsViews, Signer, Status } from './views';
import { setApi } from './redux/providers/apiActions'; import { setApi } from './redux/providers/apiActions';
@ -76,6 +76,7 @@ ReactDOM.render(
<Route path='apps' component={ Dapps } /> <Route path='apps' component={ Dapps } />
<Route path='app/:id' component={ Dapp } /> <Route path='app/:id' component={ Dapp } />
<Route path='contracts' component={ Contracts } /> <Route path='contracts' component={ Contracts } />
<Route path='contracts/write' component={ WriteContract } />
<Route path='contract/:address' component={ Contract } /> <Route path='contract/:address' component={ Contract } />
<Route path='settings' component={ Settings }> <Route path='settings' component={ Settings }>
<Route path='background' component={ SettingsBackground } /> <Route path='background' component={ SettingsBackground } />

View File

@ -235,7 +235,7 @@ class TypedInput extends Component {
export default class DetailsStep extends Component { export default class DetailsStep extends Component {
static contextTypes = { static contextTypes = {
api: PropTypes.object.isRequired api: PropTypes.object.isRequired
} };
static propTypes = { static propTypes = {
accounts: PropTypes.object.isRequired, accounts: PropTypes.object.isRequired,
@ -256,16 +256,33 @@ export default class DetailsStep extends Component {
onFromAddressChange: PropTypes.func.isRequired, onFromAddressChange: PropTypes.func.isRequired,
onDescriptionChange: PropTypes.func.isRequired, onDescriptionChange: PropTypes.func.isRequired,
onNameChange: PropTypes.func.isRequired, onNameChange: PropTypes.func.isRequired,
onParamsChange: PropTypes.func.isRequired onParamsChange: PropTypes.func.isRequired,
} readOnly: PropTypes.bool
};
static defaultProps = {
readOnly: false
};
state = { state = {
inputs: [] inputs: []
} }
componentDidMount () {
const { abi, code } = this.props;
if (abi) {
this.onAbiChange(abi);
}
if (code) {
this.onCodeChange(code);
}
}
render () { render () {
const { accounts } = this.props; const { accounts } = this.props;
const { abi, abiError, code, codeError, fromAddress, fromAddressError, name, nameError } = this.props; const { abi, abiError, code, codeError, fromAddress, fromAddressError, name, nameError, readOnly } = this.props;
return ( return (
<Form> <Form>
@ -287,13 +304,15 @@ export default class DetailsStep extends Component {
hint='the abi of the contract to deploy' hint='the abi of the contract to deploy'
error={ abiError } error={ abiError }
value={ abi } value={ abi }
onSubmit={ this.onAbiChange } /> onSubmit={ this.onAbiChange }
readOnly={ readOnly } />
<Input <Input
label='code' label='code'
hint='the compiled code of the contract to deploy' hint='the compiled code of the contract to deploy'
error={ codeError } error={ codeError }
value={ code } value={ code }
onSubmit={ this.onCodeChange } /> onSubmit={ this.onCodeChange }
readOnly={ readOnly } />
{ this.renderConstructorInputs() } { this.renderConstructorInputs() }
</Form> </Form>
); );

View File

@ -36,8 +36,17 @@ export default class DeployContract extends Component {
static propTypes = { static propTypes = {
accounts: PropTypes.object.isRequired, accounts: PropTypes.object.isRequired,
onClose: PropTypes.func.isRequired onClose: PropTypes.func.isRequired,
} abi: PropTypes.string,
code: PropTypes.string,
readOnly: PropTypes.bool,
source: PropTypes.string
};
static defaultProps = {
readOnly: false,
source: ''
};
state = { state = {
abi: '', abi: '',
@ -57,6 +66,31 @@ export default class DeployContract extends Component {
deployError: null deployError: null
} }
componentWillMount () {
const { abi, code } = this.props;
if (abi && code) {
this.setState({ abi, code });
}
}
componentWillReceiveProps (nextProps) {
const { abi, code } = nextProps;
const newState = {};
if (abi !== this.props.abi) {
newState.abi = abi;
}
if (code !== this.props.code) {
newState.code = code;
}
if (Object.keys(newState).length) {
this.setState(newState);
}
}
render () { render () {
const { step, deployError } = this.state; const { step, deployError } = this.state;
@ -116,7 +150,7 @@ export default class DeployContract extends Component {
} }
renderStep () { renderStep () {
const { accounts } = this.props; const { accounts, readOnly } = this.props;
const { address, deployError, step, deployState, txhash } = this.state; const { address, deployError, step, deployState, txhash } = this.state;
if (deployError) { if (deployError) {
@ -130,6 +164,7 @@ export default class DeployContract extends Component {
return ( return (
<DetailsStep <DetailsStep
{ ...this.state } { ...this.state }
readOnly={ readOnly }
accounts={ accounts } accounts={ accounts }
onAbiChange={ this.onAbiChange } onAbiChange={ this.onAbiChange }
onCodeChange={ this.onCodeChange } onCodeChange={ this.onCodeChange }
@ -201,6 +236,7 @@ export default class DeployContract extends Component {
onDeployStart = () => { onDeployStart = () => {
const { api, store } = this.context; const { api, store } = this.context;
const { source } = this.props;
const { abiParsed, code, description, name, params, fromAddress } = this.state; const { abiParsed, code, description, name, params, fromAddress } = this.state;
const options = { const options = {
data: code, data: code,
@ -220,6 +256,7 @@ export default class DeployContract extends Component {
contract: true, contract: true,
timestamp: Date.now(), timestamp: Date.now(),
deleted: false, deleted: false,
source,
description description
}) })
]) ])

View File

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

View File

@ -0,0 +1,52 @@
/* Copyright 2015, 2016 Ethcore (UK) Ltd.
/* This file is part of Parity.
/*
/* Parity is free software: you can redistribute it and/or modify
/* it under the terms of the GNU General Public License as published by
/* the Free Software Foundation, either version 3 of the License, or
/* (at your option) any later version.
/*
/* Parity is distributed in the hope that it will be useful,
/* but WITHOUT ANY WARRANTY; without even the implied warranty of
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
/* GNU General Public License for more details.
/*
/* You should have received a copy of the GNU General Public License
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/
.loadContainer {
display: flex;
flex-direction: row;
> * {
flex: 50%;
width: 0;
}
}
.editor {
display: flex;
flex-direction: column;
padding-left: 1em;
p {
line-height: 48px;
height: 48px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
margin: 0;
font-size: 1.2em;
}
}
.confirmRemoval {
text-align: center;
.editor {
text-align: left;
margin-top: 0.5em;
}
}

View File

@ -0,0 +1,284 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import ContentClear from 'material-ui/svg-icons/content/clear';
import CheckIcon from 'material-ui/svg-icons/navigation/check';
import DeleteIcon from 'material-ui/svg-icons/action/delete';
import { List, ListItem, makeSelectable } from 'material-ui/List';
import { Subheader, IconButton, Tabs, Tab } from 'material-ui';
import moment from 'moment';
import { Button, Modal, Editor } from '../../ui';
import styles from './loadContract.css';
const SelectableList = makeSelectable(List);
const SELECTED_STYLE = {
backgroundColor: 'rgba(255, 255, 255, 0.1)'
};
export default class LoadContract extends Component {
static propTypes = {
onClose: PropTypes.func.isRequired,
onLoad: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired,
contracts: PropTypes.object.isRequired,
snippets: PropTypes.object.isRequired
};
state = {
selected: -1,
deleteRequest: false,
deleteId: -1
};
render () {
const { deleteRequest } = this.state;
const title = deleteRequest
? 'confirm removal'
: 'view contracts';
return (
<Modal
title={ title }
actions={ this.renderDialogActions() }
visible
scroll
>
{ this.renderBody() }
</Modal>
);
}
renderBody () {
if (this.state.deleteRequest) {
return this.renderConfirmRemoval();
}
const { contracts, snippets } = this.props;
const contractsTab = Object.keys(contracts).length === 0
? null
: (
<Tab label='Local' >
{ this.renderEditor() }
<SelectableList
onChange={ this.onClickContract }
>
<Subheader>Saved Contracts</Subheader>
{ this.renderContracts(contracts) }
</SelectableList>
</Tab>
);
return (
<div className={ styles.loadContainer }>
<Tabs onChange={ this.handleChangeTab }>
{ contractsTab }
<Tab label='Snippets' >
{ this.renderEditor() }
<SelectableList
onChange={ this.onClickContract }
>
<Subheader>Contract Snippets</Subheader>
{ this.renderContracts(snippets, false) }
</SelectableList>
</Tab>
</Tabs>
</div>
);
}
renderConfirmRemoval () {
const { deleteId } = this.state;
const { name, timestamp, sourcecode } = this.props.contracts[deleteId];
return (
<div className={ styles.confirmRemoval }>
<p>
Are you sure you want to remove the following
contract from your saved contracts?
</p>
<ListItem
primaryText={ name }
secondaryText={ `Saved ${moment(timestamp).fromNow()}` }
style={ { backgroundColor: 'none', cursor: 'default' } }
/>
<div className={ styles.editor }>
<Editor
value={ sourcecode }
maxLines={ 20 }
readOnly
/>
</div>
</div>
);
}
renderEditor () {
const { contracts, snippets } = this.props;
const { selected } = this.state;
const mergedContracts = Object.assign({}, contracts, snippets);
if (selected === -1 || !mergedContracts[selected]) {
return null;
}
const { sourcecode, name } = mergedContracts[selected];
return (
<div className={ styles.editor }>
<p>{ name }</p>
<Editor
value={ sourcecode }
maxLines={ 20 }
readOnly
/>
</div>
);
}
renderContracts (contracts, removable = true) {
const { selected } = this.state;
return Object
.values(contracts)
.map((contract) => {
const { id, name, timestamp, description } = contract;
const onDelete = () => this.onDeleteRequest(id);
const secondaryText = description || `Saved ${moment(timestamp).fromNow()}`;
const remove = removable
? (
<IconButton onClick={ onDelete }>
<DeleteIcon />
</IconButton>
)
: null;
return (
<ListItem
value={ id }
key={ id }
primaryText={ name }
secondaryText={ secondaryText }
style={ selected === id ? SELECTED_STYLE : null }
rightIconButton={ remove }
/>
);
});
}
renderDialogActions () {
const { deleteRequest } = this.state;
if (deleteRequest) {
return [
<Button
icon={ <ContentClear /> }
label='No'
key='No'
onClick={ this.onRejectRemoval }
/>,
<Button
icon={ <DeleteIcon /> }
label='Yes'
key='Yes'
onClick={ this.onConfirmRemoval }
/>
];
}
const cancelBtn = (
<Button
icon={ <ContentClear /> }
label='Cancel'
onClick={ this.onClose }
/>
);
const loadBtn = (
<Button
icon={ <CheckIcon /> }
label='Load'
onClick={ this.onLoad }
disabled={ this.state.selected === -1 }
/>
);
return [ cancelBtn, loadBtn ];
}
handleChangeTab = () => {
this.setState({ selected: -1 });
}
onClickContract = (_, value) => {
this.setState({ selected: value });
}
onClose = () => {
this.props.onClose();
}
onLoad = () => {
const { contracts, snippets } = this.props;
const { selected } = this.state;
const mergedContracts = Object.assign({}, contracts, snippets);
const contract = mergedContracts[selected];
this.props.onLoad(contract);
this.props.onClose();
}
onDeleteRequest = (id) => {
this.setState({
deleteRequest: true,
deleteId: id
});
}
onConfirmRemoval = () => {
const { deleteId } = this.state;
this.props.onDelete(deleteId);
this.setState({
deleteRequest: false,
deleteId: -1,
selected: -1
});
}
onRejectRemoval = () => {
this.setState({
deleteRequest: false,
deleteId: -1
});
}
}

View File

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

View File

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

View File

@ -0,0 +1,109 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import SaveIcon from 'material-ui/svg-icons/content/save';
import ContentClear from 'material-ui/svg-icons/content/clear';
import { Button, Modal, Editor, Form, Input } from '../../ui';
import { ERRORS, validateName } from '../../util/validation';
import styles from './saveContract.css';
export default class SaveContract extends Component {
static propTypes = {
sourcecode: PropTypes.string.isRequired,
onClose: PropTypes.func.isRequired,
onSave: PropTypes.func.isRequired
};
state = {
name: '',
nameError: ERRORS.invalidName
};
render () {
const { sourcecode } = this.props;
const { name, nameError } = this.state;
return (
<Modal
title='save contract'
actions={ this.renderDialogActions() }
visible
>
<div>
<Form>
<Input
label='contract name'
hint='choose a name for this contract'
value={ name }
error={ nameError }
onChange={ this.onChangeName }
/>
</Form>
<Editor
className={ styles.source }
value={ sourcecode }
maxLines={ 20 }
readOnly
/>
</div>
</Modal>
);
}
renderDialogActions () {
const cancelBtn = (
<Button
icon={ <ContentClear /> }
label='Cancel'
onClick={ this.onClose }
/>
);
const confirmBtn = (
<Button
icon={ <SaveIcon /> }
label='Save'
disabled={ !!this.state.nameError }
onClick={ this.onSave }
/>
);
return [ cancelBtn, confirmBtn ];
}
onClose = () => {
this.props.onClose();
}
onSave = () => {
const { name } = this.state;
const { sourcecode } = this.props;
this.props.onSave({ name, sourcecode });
this.onClose();
}
onChangeName = (event, value) => {
const { name, nameError } = validateName(value);
this.setState({ name, nameError });
}
}

View File

@ -24,6 +24,8 @@ import FirstRun from './FirstRun';
import Shapeshift from './Shapeshift'; import Shapeshift from './Shapeshift';
import Transfer from './Transfer'; import Transfer from './Transfer';
import PasswordManager from './PasswordManager'; import PasswordManager from './PasswordManager';
import SaveContract from './SaveContract';
import LoadContract from './LoadContract';
export { export {
AddAddress, AddAddress,
@ -35,5 +37,7 @@ export {
FirstRun, FirstRun,
Shapeshift, Shapeshift,
Transfer, Transfer,
PasswordManager PasswordManager,
LoadContract,
SaveContract
}; };

View File

@ -0,0 +1,37 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import CompilerWorker from 'worker-loader!./compilerWorker.js';
export function setWorker (worker) {
return {
type: 'setWorker',
worker
};
}
export function setupWorker () {
return (dispatch, getState) => {
const state = getState();
if (state.compiler.worker) {
return;
}
const worker = new CompilerWorker();
dispatch(setWorker(worker));
};
}

View File

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

View File

@ -0,0 +1,177 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import solc from 'solc/browser-wrapper';
import { isWebUri } from 'valid-url';
self.solcVersions = {};
self.files = {};
self.lastCompile = {
sourcecode: '',
result: '',
version: ''
};
// eslint-disable-next-line no-undef
onmessage = (event) => {
const message = JSON.parse(event.data);
switch (message.action) {
case 'compile':
compile(message.data);
break;
case 'load':
load(message.data);
break;
case 'setFiles':
setFiles(message.data);
break;
case 'close':
close();
break;
}
};
function setFiles (files) {
const prevFiles = self.files;
const nextFiles = files.reduce((obj, file) => {
obj[file.name] = file.sourcecode;
return obj;
}, {});
self.files = {
...prevFiles,
...nextFiles
};
}
function findImports (path) {
if (self.files[path]) {
if (self.files[path].error) {
return { error: self.files[path].error };
}
return { contents: self.files[path] };
}
if (isWebUri(path)) {
console.log('[worker] fetching', path);
fetch(path)
.then((r) => r.text())
.then((c) => {
console.log('[worker]', 'got content at ' + path);
self.files[path] = c;
postMessage(JSON.stringify({
event: 'try-again'
}));
})
.catch((e) => {
console.error('[worker]', 'fetching', path, e);
self.files[path] = { error: e };
});
return { error: '__parity_tryAgain' };
}
console.log(`[worker] path ${path} not found...`);
return { error: 'File not found' };
}
function compile (data) {
const { sourcecode, build } = data;
const { longVersion } = build;
if (self.lastCompile.sourcecode === sourcecode && self.lastCompile.longVersion === longVersion) {
return postMessage(JSON.stringify({
event: 'compiled',
data: self.lastCompile.result
}));
}
fetchSolc(build)
.then((compiler) => {
const input = {
'': sourcecode
};
const compiled = compiler.compile({ sources: input }, 0, findImports);
self.lastCompile = {
version: longVersion, result: compiled,
sourcecode
};
postMessage(JSON.stringify({
event: 'compiled',
data: compiled
}));
});
}
function load (build) {
postMessage(JSON.stringify({
event: 'loading',
data: true
}));
fetchSolc(build)
.then(() => {
postMessage(JSON.stringify({
event: 'loading',
data: false
}));
})
.catch(() => {
postMessage(JSON.stringify({
event: 'loading',
data: false
}));
});
}
function fetchSolc (build) {
const { path, longVersion } = build;
if (self.solcVersions[path]) {
return Promise.resolve(self.solcVersions[path]);
}
const URL = `https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/bin/${path}`;
console.log(`[worker] fetching solc-bin ${longVersion} at ${URL}`);
return fetch(URL)
.then((r) => r.text())
.then((code) => {
const solcCode = code.replace(/^var Module;/, 'var Module=self.__solcModule;');
self.__solcModule = {};
console.log(`[worker] evaluating ${longVersion}`);
// eslint-disable-next-line no-eval
eval(solcCode);
console.log(`[worker] done evaluating ${longVersion}`);
const compiler = solc(self.__solcModule);
self.solcVersions[path] = compiler;
return compiler;
})
.catch((e) => {
console.error('fetching solc', e);
});
}

View File

@ -26,3 +26,4 @@ export personalReducer from './personalReducer';
export signerReducer from './signerReducer'; export signerReducer from './signerReducer';
export statusReducer from './statusReducer'; export statusReducer from './statusReducer';
export blockchainReducer from './blockchainReducer'; export blockchainReducer from './blockchainReducer';
export compilerReducer from './compilerReducer';

View File

@ -17,7 +17,7 @@
import { combineReducers } from 'redux'; import { combineReducers } from 'redux';
import { routerReducer } from 'react-router-redux'; import { routerReducer } from 'react-router-redux';
import { apiReducer, balancesReducer, blockchainReducer, imagesReducer, personalReducer, signerReducer, statusReducer as nodeStatusReducer } from './providers'; import { apiReducer, balancesReducer, blockchainReducer, compilerReducer, imagesReducer, personalReducer, signerReducer, statusReducer as nodeStatusReducer } from './providers';
import { errorReducer } from '../ui/Errors'; import { errorReducer } from '../ui/Errors';
import { settingsReducer } from '../views/Settings'; import { settingsReducer } from '../views/Settings';
@ -33,6 +33,7 @@ export default function () {
balances: balancesReducer, balances: balancesReducer,
blockchain: blockchainReducer, blockchain: blockchainReducer,
compiler: compilerReducer,
images: imagesReducer, images: imagesReducer,
nodeStatus: nodeStatusReducer, nodeStatus: nodeStatusReducer,
personal: personalReducer, personal: personalReducer,

45
js/src/ui/Actionbar/Import/import.css vendored Normal file
View File

@ -0,0 +1,45 @@
/* Copyright 2015, 2016 Ethcore (UK) Ltd.
/* This file is part of Parity.
/*
/* Parity is free software: you can redistribute it and/or modify
/* it under the terms of the GNU General Public License as published by
/* the Free Software Foundation, either version 3 of the License, or
/* (at your option) any later version.
/*
/* Parity is distributed in the hope that it will be useful,
/* but WITHOUT ANY WARRANTY; without even the implied warranty of
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
/* GNU General Public License for more details.
/*
/* You should have received a copy of the GNU General Public License
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/
.importZone {
width: 100%;
height: 200px;
border-width: 2px;
border-color: #666;
border-style: dashed;
border-radius: 10px;
background-color: rgba(50, 50, 50, 0.2);
display: flex;
align-items: center;
justify-content: center;
font-size: 1.2em;
transition: all 0.5s ease;
&:hover {
cursor: pointer;
border-radius: 0;
background-color: rgba(50, 50, 50, 0.5);
}
}
.desc {
margin-top: 0;
color: #ccc;
}

View File

@ -0,0 +1,198 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import Dropzone from 'react-dropzone';
import FileUploadIcon from 'material-ui/svg-icons/file/file-upload';
import ContentClear from 'material-ui/svg-icons/content/clear';
import ActionDoneAll from 'material-ui/svg-icons/action/done-all';
import { Modal, Button } from '../../';
import styles from './import.css';
const initialState = {
step: 0,
show: false,
validate: false,
validationBody: null,
content: ''
};
export default class ActionbarImport extends Component {
static propTypes = {
onConfirm: PropTypes.func.isRequired,
renderValidation: PropTypes.func,
className: PropTypes.string,
title: PropTypes.string
};
static defaultProps = {
title: 'Import from a file'
};
state = Object.assign({}, initialState);
render () {
const { className } = this.props;
return (
<div>
<Button
className={ className }
icon={ <FileUploadIcon /> }
label='import'
onClick={ this.onOpenModal }
/>
{ this.renderModal() }
</div>
);
}
renderModal () {
const { title, renderValidation } = this.props;
const { show, step } = this.state;
if (!show) {
return null;
}
const hasSteps = typeof renderValidation === 'function';
const steps = hasSteps ? [ 'select a file', 'validate' ] : null;
return (
<Modal
actions={ this.renderActions() }
title={ title }
steps={ steps }
current={ step }
visible
>
{ this.renderBody() }
</Modal>
);
}
renderActions () {
const { validate } = this.state;
const cancelBtn = (
<Button
key='cancel'
label='Cancel'
icon={ <ContentClear /> }
onClick={ this.onCloseModal }
/>
);
if (validate) {
const confirmBtn = (
<Button
key='confirm'
label='Confirm'
icon={ <ActionDoneAll /> }
onClick={ this.onConfirm }
/>
);
return [ cancelBtn, confirmBtn ];
}
return [ cancelBtn ];
}
renderBody () {
const { validate } = this.state;
if (validate) {
return this.renderValidation();
}
return this.renderFileSelect();
}
renderFileSelect () {
return (
<div>
<Dropzone
onDrop={ this.onDrop }
multiple={ false }
className={ styles.importZone }
>
<div>Drop a file here, or click to select a file to upload.</div>
</Dropzone>
</div>
);
}
renderValidation () {
const { validationBody } = this.state;
return (
<div>
<p className={ styles.desc }>
Confirm that this is what was intended to import.
</p>
<div>
{ validationBody }
</div>
</div>
);
}
onDrop = (files) => {
const { renderValidation } = this.props;
const file = files[0];
const reader = new FileReader();
reader.onload = (e) => {
const content = e.target.result;
if (typeof renderValidation !== 'function') {
this.props.onConfirm(content);
return this.onCloseModal();
}
this.setState({
step: 1,
validate: true,
validationBody: renderValidation(content),
content
});
};
reader.readAsText(file);
}
onConfirm = () => {
const { content } = this.state;
this.props.onConfirm(content);
return this.onCloseModal();
}
onOpenModal = () => {
this.setState({ show: true });
}
onCloseModal = () => {
this.setState(initialState);
}
}

View File

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

View File

@ -19,6 +19,7 @@ import { FlatButton } from 'material-ui';
export default class Button extends Component { export default class Button extends Component {
static propTypes = { static propTypes = {
backgroundColor: PropTypes.string,
className: PropTypes.string, className: PropTypes.string,
disabled: PropTypes.bool, disabled: PropTypes.bool,
icon: PropTypes.node, icon: PropTypes.node,
@ -26,19 +27,25 @@ export default class Button extends Component {
React.PropTypes.string, React.PropTypes.string,
React.PropTypes.object React.PropTypes.object
]), ]),
onClick: PropTypes.func onClick: PropTypes.func,
primary: PropTypes.bool
}
static defaultProps = {
primary: true
} }
render () { render () {
const { className, disabled, icon, label, onClick } = this.props; const { className, backgroundColor, disabled, icon, label, primary, onClick } = this.props;
return ( return (
<FlatButton <FlatButton
className={ className } className={ className }
backgroundColor={ backgroundColor }
disabled={ disabled } disabled={ disabled }
icon={ icon } icon={ icon }
label={ label } label={ label }
primary primary={ primary }
onTouchTap={ onClick } /> onTouchTap={ onClick } />
); );
} }

103
js/src/ui/Editor/editor.js Normal file
View File

@ -0,0 +1,103 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { PropTypes, Component } from 'react';
import 'brace';
import AceEditor from 'react-ace';
import { noop } from 'lodash';
import 'brace/theme/solarized_dark';
import 'brace/mode/json';
import './mode-solidity';
export default class Editor extends Component {
static propTypes = {
className: PropTypes.string,
value: PropTypes.string,
mode: PropTypes.string,
maxLines: PropTypes.number,
annotations: PropTypes.array,
onExecute: PropTypes.func,
onChange: PropTypes.func,
readOnly: PropTypes.bool
};
static defaultProps = {
className: '',
value: '',
mode: 'javascript',
annotations: [],
onExecute: noop,
onChange: noop,
readOnly: false
};
componentWillMount () {
this.name = `PARITY_EDITOR_${Math.round(Math.random() * 99999)}`;
}
render () {
const { className, annotations, value, readOnly, mode, maxLines } = this.props;
const commands = [
{
name: 'execut',
bindKey: { win: 'Ctrl-Enter', mac: 'Command-Enter' },
exec: this.handleExecute
}
];
const max = (maxLines !== undefined)
? maxLines
: (readOnly ? value.split('\n').length + 1 : null);
return (
<AceEditor
mode={ mode }
theme='solarized_dark'
width='100%'
ref='brace'
style={ { flex: 1 } }
onChange={ this.handleOnChange }
name={ this.name }
editorProps={ { $blockScrolling: Infinity } }
setOptions={ {
useWorker: false,
fontFamily: 'monospace',
fontSize: '0.9em'
} }
maxLines={ max }
enableBasicAutocompletion={ !readOnly }
showPrintMargin={ false }
annotations={ annotations }
value={ value }
commands={ commands }
readOnly={ readOnly }
className={ className }
/>
);
}
handleExecute = () => {
this.props.onExecute();
}
handleOnChange = (value) => {
this.props.onChange(value);
}
}

17
js/src/ui/Editor/index.js Normal file
View File

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

View File

@ -0,0 +1,994 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
/**
* This file has been taken from `ethereum/browser-solidity`
*
* @see: https://raw.githubusercontent.com/ethereum/browser-solidity/master/src/mode-solidity.js
*/
/* eslint-disable */
var ace = window.ace;
ace.define("ace/mode/doc_comment_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/text_highlight_rules"], function(acequire, exports, module) {
"use strict";
var oop = acequire("../lib/oop");
var TextHighlightRules = acequire("./text_highlight_rules").TextHighlightRules;
var DocCommentHighlightRules = function() {
this.$rules = {
"start" : [ {
token : "comment.doc.tag",
regex : "@[\\w\\d_]+" // TODO: fix email addresses
},
DocCommentHighlightRules.getTagRule(),
{
defaultToken : "comment.doc",
caseInsensitive: true
}]
};
};
oop.inherits(DocCommentHighlightRules, TextHighlightRules);
DocCommentHighlightRules.getTagRule = function(start) {
return {
token : "comment.doc.tag.storage.type",
regex : "\\b(?:TODO|FIXME|XXX|HACK)\\b"
};
};
DocCommentHighlightRules.getStartRule = function(start) {
return {
token : "comment.doc", // doc comment
regex : "\\/\\*(?=\\*)",
next : start
};
};
DocCommentHighlightRules.getEndRule = function (start) {
return {
token : "comment.doc", // closing comment
regex : "\\*\\/",
next : start
};
};
exports.DocCommentHighlightRules = DocCommentHighlightRules;
});
ace.define("ace/mode/javascript_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/doc_comment_highlight_rules","ace/mode/text_highlight_rules"], function(acequire, exports, module) {
"use strict";
var oop = acequire("../lib/oop");
var DocCommentHighlightRules = acequire("./doc_comment_highlight_rules").DocCommentHighlightRules;
var TextHighlightRules = acequire("./text_highlight_rules").TextHighlightRules;
var JavaScriptHighlightRules = function(options) {
var intTypes = 'bytes|int|uint';
for (var width = 8; width <= 256; width += 8)
intTypes += '|bytes' + (width / 8) + '|uint' + width + '|int' + width;
var keywordMapper = this.createKeywordMapper({
"variable.language":
"this|bool|string|byte|bytes|bytes0|address|" + intTypes,
"keyword":
"contract|library|constant|event|modifier|" +
"struct|mapping|enum|break|continue|delete|else|for|function|" +
"if|new|return|returns|var|while|using|" +
"private|public|external|internal|storage|memory",
"storage.type":
"constant|var|function",
"constant.language.boolean": "true|false"
}, "identifier");
var kwBeforeRe = "case|do|else|finally|in|instanceof|return|throw|try|typeof|yield|void";
var identifierRe = "[a-zA-Z\\$_\u00a1-\uffff][a-zA-Z\\d\\$_\u00a1-\uffff]*\\b";
var escapedRe = "\\\\(?:x[0-9a-fA-F]{2}|" + // hex
"u[0-9a-fA-F]{4}|" + // unicode
"[0-2][0-7]{0,2}|" + // oct
"3[0-6][0-7]?|" + // oct
"37[0-7]?|" + // oct
"[4-7][0-7]?|" + //oct
".)";
this.$rules = {
"no_regex" : [
{
token : "comment",
regex : "\\/\\/",
next : "line_comment"
},
DocCommentHighlightRules.getStartRule("doc-start"),
{
token : "comment", // multi line comment
regex : /\/\*/,
next : "comment"
}, {
token : "string",
regex : "'(?=.)",
next : "qstring"
}, {
token : "string",
regex : '"(?=.)',
next : "qqstring"
}, {
token : "constant.numeric", // hex
regex : /0[xX][0-9a-fA-F]+\b/
}, {
token : "constant.numeric", // float
regex : /[+-]?\d+(?:(?:\.\d*)?(?:[eE][+-]?\d+)?)?\b/
}, {
token : [
"storage.type", "punctuation.operator", "support.function",
"punctuation.operator", "entity.name.function", "text","keyword.operator"
],
regex : "(" + identifierRe + ")(\\.)(prototype)(\\.)(" + identifierRe +")(\\s*)(=)",
next: "function_arguments"
}, {
token : [
"storage.type", "punctuation.operator", "entity.name.function", "text",
"keyword.operator", "text", "storage.type", "text", "paren.lparen"
],
regex : "(" + identifierRe + ")(\\.)(" + identifierRe +")(\\s*)(=)(\\s*)(function)(\\s*)(\\()",
next: "function_arguments"
}, {
token : [
"entity.name.function", "text", "keyword.operator", "text", "storage.type",
"text", "paren.lparen"
],
regex : "(" + identifierRe +")(\\s*)(=)(\\s*)(function)(\\s*)(\\()",
next: "function_arguments"
}, {
token : [
"storage.type", "punctuation.operator", "entity.name.function", "text",
"keyword.operator", "text",
"storage.type", "text", "entity.name.function", "text", "paren.lparen"
],
regex : "(" + identifierRe + ")(\\.)(" + identifierRe +")(\\s*)(=)(\\s*)(function)(\\s+)(\\w+)(\\s*)(\\()",
next: "function_arguments"
}, {
token : [
"storage.type", "text", "entity.name.function", "text", "paren.lparen"
],
regex : "(function)(\\s+)(" + identifierRe + ")(\\s*)(\\()",
next: "function_arguments"
}, {
token : [
"entity.name.function", "text", "punctuation.operator",
"text", "storage.type", "text", "paren.lparen"
],
regex : "(" + identifierRe + ")(\\s*)(:)(\\s*)(function)(\\s*)(\\()",
next: "function_arguments"
}, {
token : [
"text", "text", "storage.type", "text", "paren.lparen"
],
regex : "(:)(\\s*)(function)(\\s*)(\\()",
next: "function_arguments"
}, {
token : "keyword",
regex : "(?:" + kwBeforeRe + ")\\b",
next : "start"
}, {
token : ["punctuation.operator", "support.function"],
regex : /(\.)(s(?:h(?:ift|ow(?:Mod(?:elessDialog|alDialog)|Help))|croll(?:X|By(?:Pages|Lines)?|Y|To)?|t(?:op|rike)|i(?:n|zeToContent|debar|gnText)|ort|u(?:p|b(?:str(?:ing)?)?)|pli(?:ce|t)|e(?:nd|t(?:Re(?:sizable|questHeader)|M(?:i(?:nutes|lliseconds)|onth)|Seconds|Ho(?:tKeys|urs)|Year|Cursor|Time(?:out)?|Interval|ZOptions|Date|UTC(?:M(?:i(?:nutes|lliseconds)|onth)|Seconds|Hours|Date|FullYear)|FullYear|Active)|arch)|qrt|lice|avePreferences|mall)|h(?:ome|andleEvent)|navigate|c(?:har(?:CodeAt|At)|o(?:s|n(?:cat|textual|firm)|mpile)|eil|lear(?:Timeout|Interval)?|a(?:ptureEvents|ll)|reate(?:StyleSheet|Popup|EventObject))|t(?:o(?:GMTString|S(?:tring|ource)|U(?:TCString|pperCase)|Lo(?:caleString|werCase))|est|a(?:n|int(?:Enabled)?))|i(?:s(?:NaN|Finite)|ndexOf|talics)|d(?:isableExternalCapture|ump|etachEvent)|u(?:n(?:shift|taint|escape|watch)|pdateCommands)|j(?:oin|avaEnabled)|p(?:o(?:p|w)|ush|lugins.refresh|a(?:ddings|rse(?:Int|Float)?)|r(?:int|ompt|eference))|e(?:scape|nableExternalCapture|val|lementFromPoint|x(?:p|ec(?:Script|Command)?))|valueOf|UTC|queryCommand(?:State|Indeterm|Enabled|Value)|f(?:i(?:nd|le(?:ModifiedDate|Size|CreatedDate|UpdatedDate)|xed)|o(?:nt(?:size|color)|rward)|loor|romCharCode)|watch|l(?:ink|o(?:ad|g)|astIndexOf)|a(?:sin|nchor|cos|t(?:tachEvent|ob|an(?:2)?)|pply|lert|b(?:s|ort))|r(?:ou(?:nd|teEvents)|e(?:size(?:By|To)|calc|turnValue|place|verse|l(?:oad|ease(?:Capture|Events)))|andom)|g(?:o|et(?:ResponseHeader|M(?:i(?:nutes|lliseconds)|onth)|Se(?:conds|lection)|Hours|Year|Time(?:zoneOffset)?|Da(?:y|te)|UTC(?:M(?:i(?:nutes|lliseconds)|onth)|Seconds|Hours|Da(?:y|te)|FullYear)|FullYear|A(?:ttention|llResponseHeaders)))|m(?:in|ove(?:B(?:y|elow)|To(?:Absolute)?|Above)|ergeAttributes|a(?:tch|rgins|x))|b(?:toa|ig|o(?:ld|rderWidths)|link|ack))\b(?=\()/
}, {
token : ["punctuation.operator", "support.function.dom"],
regex : /(\.)(s(?:ub(?:stringData|mit)|plitText|e(?:t(?:NamedItem|Attribute(?:Node)?)|lect))|has(?:ChildNodes|Feature)|namedItem|c(?:l(?:ick|o(?:se|neNode))|reate(?:C(?:omment|DATASection|aption)|T(?:Head|extNode|Foot)|DocumentFragment|ProcessingInstruction|E(?:ntityReference|lement)|Attribute))|tabIndex|i(?:nsert(?:Row|Before|Cell|Data)|tem)|open|delete(?:Row|C(?:ell|aption)|T(?:Head|Foot)|Data)|focus|write(?:ln)?|a(?:dd|ppend(?:Child|Data))|re(?:set|place(?:Child|Data)|move(?:NamedItem|Child|Attribute(?:Node)?)?)|get(?:NamedItem|Element(?:sBy(?:Name|TagName)|ById)|Attribute(?:Node)?)|blur)\b(?=\()/
}, {
token : ["punctuation.operator", "support.constant"],
regex : /(\.)(s(?:ystemLanguage|cr(?:ipts|ollbars|een(?:X|Y|Top|Left))|t(?:yle(?:Sheets)?|atus(?:Text|bar)?)|ibling(?:Below|Above)|ource|uffixes|e(?:curity(?:Policy)?|l(?:ection|f)))|h(?:istory|ost(?:name)?|as(?:h|Focus))|y|X(?:MLDocument|SLDocument)|n(?:ext|ame(?:space(?:s|URI)|Prop))|M(?:IN_VALUE|AX_VALUE)|c(?:haracterSet|o(?:n(?:structor|trollers)|okieEnabled|lorDepth|mp(?:onents|lete))|urrent|puClass|l(?:i(?:p(?:boardData)?|entInformation)|osed|asses)|alle(?:e|r)|rypto)|t(?:o(?:olbar|p)|ext(?:Transform|Indent|Decoration|Align)|ags)|SQRT(?:1_2|2)|i(?:n(?:ner(?:Height|Width)|put)|ds|gnoreCase)|zIndex|o(?:scpu|n(?:readystatechange|Line)|uter(?:Height|Width)|p(?:sProfile|ener)|ffscreenBuffering)|NEGATIVE_INFINITY|d(?:i(?:splay|alog(?:Height|Top|Width|Left|Arguments)|rectories)|e(?:scription|fault(?:Status|Ch(?:ecked|arset)|View)))|u(?:ser(?:Profile|Language|Agent)|n(?:iqueID|defined)|pdateInterval)|_content|p(?:ixelDepth|ort|ersonalbar|kcs11|l(?:ugins|atform)|a(?:thname|dding(?:Right|Bottom|Top|Left)|rent(?:Window|Layer)?|ge(?:X(?:Offset)?|Y(?:Offset)?))|r(?:o(?:to(?:col|type)|duct(?:Sub)?|mpter)|e(?:vious|fix)))|e(?:n(?:coding|abledPlugin)|x(?:ternal|pando)|mbeds)|v(?:isibility|endor(?:Sub)?|Linkcolor)|URLUnencoded|P(?:I|OSITIVE_INFINITY)|f(?:ilename|o(?:nt(?:Size|Family|Weight)|rmName)|rame(?:s|Element)|gColor)|E|whiteSpace|l(?:i(?:stStyleType|n(?:eHeight|kColor))|o(?:ca(?:tion(?:bar)?|lName)|wsrc)|e(?:ngth|ft(?:Context)?)|a(?:st(?:M(?:odified|atch)|Index|Paren)|yer(?:s|X)|nguage))|a(?:pp(?:MinorVersion|Name|Co(?:deName|re)|Version)|vail(?:Height|Top|Width|Left)|ll|r(?:ity|guments)|Linkcolor|bove)|r(?:ight(?:Context)?|e(?:sponse(?:XML|Text)|adyState))|global|x|m(?:imeTypes|ultiline|enubar|argin(?:Right|Bottom|Top|Left))|L(?:N(?:10|2)|OG(?:10E|2E))|b(?:o(?:ttom|rder(?:Width|RightWidth|BottomWidth|Style|Color|TopWidth|LeftWidth))|ufferDepth|elow|ackground(?:Color|Image)))\b/
}, {
token : ["support.constant"],
regex : /that\b/
}, {
token : ["storage.type", "punctuation.operator", "support.function.firebug"],
regex : /(console)(\.)(warn|info|log|error|time|trace|timeEnd|assert)\b/
}, {
token : keywordMapper,
regex : identifierRe
}, {
token : "keyword.operator",
regex : /--|\*\*|\+\+|===|==|=|!=|!==|=>|<=|>=|<<=|>>=|>>>=|<>|<|>|!|&&|\|\||\?\:|[!$%&*+\-~\/^]=?/,
next : "start"
}, {
token : "punctuation.operator",
regex : /[?:,;.]/,
next : "start"
}, {
token : "paren.lparen",
regex : /[\[({]/,
next : "start"
}, {
token : "paren.rparen",
regex : /[\])}]/
}, {
token: "comment",
regex: /^#!.*$/
}
],
"start": [
DocCommentHighlightRules.getStartRule("doc-start"),
{
token : "comment", // multi line comment
regex : "\\/\\*",
next : "comment_regex_allowed"
}, {
token : "comment",
regex : "\\/\\/",
next : "line_comment_regex_allowed"
}, {
token: "string.regexp",
regex: "\\/",
next: "regex"
}, {
token : "text",
regex : "\\s+|^$",
next : "start"
}, {
token: "empty",
regex: "",
next: "no_regex"
}
],
"regex": [
{
token: "regexp.keyword.operator",
regex: "\\\\(?:u[\\da-fA-F]{4}|x[\\da-fA-F]{2}|.)"
}, {
token: "string.regexp",
regex: "/[sxngimy]*",
next: "no_regex"
}, {
token : "invalid",
regex: /\{\d+\b,?\d*\}[+*]|[+*$^?][+*]|[$^][?]|\?{3,}/
}, {
token : "constant.language.escape",
regex: /\(\?[:=!]|\)|\{\d+\b,?\d*\}|[+*]\?|[()$^+*?.]/
}, {
token : "constant.language.delimiter",
regex: /\|/
}, {
token: "constant.language.escape",
regex: /\[\^?/,
next: "regex_character_class"
}, {
token: "empty",
regex: "$",
next: "no_regex"
}, {
defaultToken: "string.regexp"
}
],
"regex_character_class": [
{
token: "regexp.charclass.keyword.operator",
regex: "\\\\(?:u[\\da-fA-F]{4}|x[\\da-fA-F]{2}|.)"
}, {
token: "constant.language.escape",
regex: "]",
next: "regex"
}, {
token: "constant.language.escape",
regex: "-"
}, {
token: "empty",
regex: "$",
next: "no_regex"
}, {
defaultToken: "string.regexp.charachterclass"
}
],
"function_arguments": [
{
token: "variable.parameter",
regex: identifierRe
}, {
token: "punctuation.operator",
regex: "[, ]+"
}, {
token: "punctuation.operator",
regex: "$"
}, {
token: "empty",
regex: "",
next: "no_regex"
}
],
"comment_regex_allowed" : [
DocCommentHighlightRules.getTagRule(),
{token : "comment", regex : "\\*\\/", next : "start"},
{defaultToken : "comment", caseInsensitive: true}
],
"comment" : [
DocCommentHighlightRules.getTagRule(),
{token : "comment", regex : "\\*\\/", next : "no_regex"},
{defaultToken : "comment", caseInsensitive: true}
],
"line_comment_regex_allowed" : [
DocCommentHighlightRules.getTagRule(),
{token : "comment", regex : "$|^", next : "start"},
{defaultToken : "comment", caseInsensitive: true}
],
"line_comment" : [
DocCommentHighlightRules.getTagRule(),
{token : "comment", regex : "$|^", next : "no_regex"},
{defaultToken : "comment", caseInsensitive: true}
],
"qqstring" : [
{
token : "constant.language.escape",
regex : escapedRe
}, {
token : "string",
regex : "\\\\$",
next : "qqstring"
}, {
token : "string",
regex : '"|$',
next : "no_regex"
}, {
defaultToken: "string"
}
],
"qstring" : [
{
token : "constant.language.escape",
regex : escapedRe
}, {
token : "string",
regex : "\\\\$",
next : "qstring"
}, {
token : "string",
regex : "'|$",
next : "no_regex"
}, {
defaultToken: "string"
}
]
};
if (!options || !options.noES6) {
this.$rules.no_regex.unshift({
regex: "[{}]", onMatch: function(val, state, stack) {
this.next = val == "{" ? this.nextState : "";
if (val == "{" && stack.length) {
stack.unshift("start", state);
return "paren";
}
if (val == "}" && stack.length) {
stack.shift();
this.next = stack.shift();
if (this.next.indexOf("string") != -1)
return "paren.quasi.end";
}
return val == "{" ? "paren.lparen" : "paren.rparen";
},
nextState: "start"
}, {
token : "string.quasi.start",
regex : /`/,
push : [{
token : "constant.language.escape",
regex : escapedRe
}, {
token : "paren.quasi.start",
regex : /\${/,
push : "start"
}, {
token : "string.quasi.end",
regex : /`/,
next : "pop"
}, {
defaultToken: "string.quasi"
}]
});
}
this.embedRules(DocCommentHighlightRules, "doc-",
[ DocCommentHighlightRules.getEndRule("no_regex") ]);
this.normalizeRules();
};
oop.inherits(JavaScriptHighlightRules, TextHighlightRules);
exports.JavaScriptHighlightRules = JavaScriptHighlightRules;
});
ace.define("ace/mode/matching_brace_outdent",["require","exports","module","ace/range"], function(acequire, exports, module) {
"use strict";
var Range = acequire("../range").Range;
var MatchingBraceOutdent = function() {};
(function() {
this.checkOutdent = function(line, input) {
if (! /^\s+$/.test(line))
return false;
return /^\s*\}/.test(input);
};
this.autoOutdent = function(doc, row) {
var line = doc.getLine(row);
var match = line.match(/^(\s*\})/);
if (!match) return 0;
var column = match[1].length;
var openBracePos = doc.findMatchingBracket({row: row, column: column});
if (!openBracePos || openBracePos.row == row) return 0;
var indent = this.$getIndent(doc.getLine(openBracePos.row));
doc.replace(new Range(row, 0, row, column-1), indent);
};
this.$getIndent = function(line) {
return line.match(/^\s*/)[0];
};
}).call(MatchingBraceOutdent.prototype);
exports.MatchingBraceOutdent = MatchingBraceOutdent;
});
ace.define("ace/mode/behaviour/cstyle",["require","exports","module","ace/lib/oop","ace/mode/behaviour","ace/token_iterator","ace/lib/lang"], function(acequire, exports, module) {
"use strict";
var oop = acequire("../../lib/oop");
var Behaviour = acequire("../behaviour").Behaviour;
var TokenIterator = acequire("../../token_iterator").TokenIterator;
var lang = acequire("../../lib/lang");
var SAFE_INSERT_IN_TOKENS =
["text", "paren.rparen", "punctuation.operator"];
var SAFE_INSERT_BEFORE_TOKENS =
["text", "paren.rparen", "punctuation.operator", "comment"];
var context;
var contextCache = {};
var initContext = function(editor) {
var id = -1;
if (editor.multiSelect) {
id = editor.selection.index;
if (contextCache.rangeCount != editor.multiSelect.rangeCount)
contextCache = {rangeCount: editor.multiSelect.rangeCount};
}
if (contextCache[id])
return context = contextCache[id];
context = contextCache[id] = {
autoInsertedBrackets: 0,
autoInsertedRow: -1,
autoInsertedLineEnd: "",
maybeInsertedBrackets: 0,
maybeInsertedRow: -1,
maybeInsertedLineStart: "",
maybeInsertedLineEnd: ""
};
};
var CstyleBehaviour = function() {
this.add("braces", "insertion", function(state, action, editor, session, text) {
var cursor = editor.getCursorPosition();
var line = session.doc.getLine(cursor.row);
if (text == '{') {
initContext(editor);
var selection = editor.getSelectionRange();
var selected = session.doc.getTextRange(selection);
if (selected !== "" && selected !== "{" && editor.getWrapBehavioursEnabled()) {
return {
text: '{' + selected + '}',
selection: false
};
} else if (CstyleBehaviour.isSaneInsertion(editor, session)) {
if (/[\]\}\)]/.test(line[cursor.column]) || editor.inMultiSelectMode) {
CstyleBehaviour.recordAutoInsert(editor, session, "}");
return {
text: '{}',
selection: [1, 1]
};
} else {
CstyleBehaviour.recordMaybeInsert(editor, session, "{");
return {
text: '{',
selection: [1, 1]
};
}
}
} else if (text == '}') {
initContext(editor);
var rightChar = line.substring(cursor.column, cursor.column + 1);
if (rightChar == '}') {
var matching = session.$findOpeningBracket('}', {column: cursor.column + 1, row: cursor.row});
if (matching !== null && CstyleBehaviour.isAutoInsertedClosing(cursor, line, text)) {
CstyleBehaviour.popAutoInsertedClosing();
return {
text: '',
selection: [1, 1]
};
}
}
} else if (text == "\n" || text == "\r\n") {
initContext(editor);
var closing = "";
if (CstyleBehaviour.isMaybeInsertedClosing(cursor, line)) {
closing = lang.stringRepeat("}", context.maybeInsertedBrackets);
CstyleBehaviour.clearMaybeInsertedClosing();
}
var rightChar = line.substring(cursor.column, cursor.column + 1);
if (rightChar === '}') {
var openBracePos = session.findMatchingBracket({row: cursor.row, column: cursor.column+1}, '}');
if (!openBracePos)
return null;
var next_indent = this.$getIndent(session.getLine(openBracePos.row));
} else if (closing) {
var next_indent = this.$getIndent(line);
} else {
CstyleBehaviour.clearMaybeInsertedClosing();
return;
}
var indent = next_indent + session.getTabString();
return {
text: '\n' + indent + '\n' + next_indent + closing,
selection: [1, indent.length, 1, indent.length]
};
} else {
CstyleBehaviour.clearMaybeInsertedClosing();
}
});
this.add("braces", "deletion", function(state, action, editor, session, range) {
var selected = session.doc.getTextRange(range);
if (!range.isMultiLine() && selected == '{') {
initContext(editor);
var line = session.doc.getLine(range.start.row);
var rightChar = line.substring(range.end.column, range.end.column + 1);
if (rightChar == '}') {
range.end.column++;
return range;
} else {
context.maybeInsertedBrackets--;
}
}
});
this.add("parens", "insertion", function(state, action, editor, session, text) {
if (text == '(') {
initContext(editor);
var selection = editor.getSelectionRange();
var selected = session.doc.getTextRange(selection);
if (selected !== "" && editor.getWrapBehavioursEnabled()) {
return {
text: '(' + selected + ')',
selection: false
};
} else if (CstyleBehaviour.isSaneInsertion(editor, session)) {
CstyleBehaviour.recordAutoInsert(editor, session, ")");
return {
text: '()',
selection: [1, 1]
};
}
} else if (text == ')') {
initContext(editor);
var cursor = editor.getCursorPosition();
var line = session.doc.getLine(cursor.row);
var rightChar = line.substring(cursor.column, cursor.column + 1);
if (rightChar == ')') {
var matching = session.$findOpeningBracket(')', {column: cursor.column + 1, row: cursor.row});
if (matching !== null && CstyleBehaviour.isAutoInsertedClosing(cursor, line, text)) {
CstyleBehaviour.popAutoInsertedClosing();
return {
text: '',
selection: [1, 1]
};
}
}
}
});
this.add("parens", "deletion", function(state, action, editor, session, range) {
var selected = session.doc.getTextRange(range);
if (!range.isMultiLine() && selected == '(') {
initContext(editor);
var line = session.doc.getLine(range.start.row);
var rightChar = line.substring(range.start.column + 1, range.start.column + 2);
if (rightChar == ')') {
range.end.column++;
return range;
}
}
});
this.add("brackets", "insertion", function(state, action, editor, session, text) {
if (text == '[') {
initContext(editor);
var selection = editor.getSelectionRange();
var selected = session.doc.getTextRange(selection);
if (selected !== "" && editor.getWrapBehavioursEnabled()) {
return {
text: '[' + selected + ']',
selection: false
};
} else if (CstyleBehaviour.isSaneInsertion(editor, session)) {
CstyleBehaviour.recordAutoInsert(editor, session, "]");
return {
text: '[]',
selection: [1, 1]
};
}
} else if (text == ']') {
initContext(editor);
var cursor = editor.getCursorPosition();
var line = session.doc.getLine(cursor.row);
var rightChar = line.substring(cursor.column, cursor.column + 1);
if (rightChar == ']') {
var matching = session.$findOpeningBracket(']', {column: cursor.column + 1, row: cursor.row});
if (matching !== null && CstyleBehaviour.isAutoInsertedClosing(cursor, line, text)) {
CstyleBehaviour.popAutoInsertedClosing();
return {
text: '',
selection: [1, 1]
};
}
}
}
});
this.add("brackets", "deletion", function(state, action, editor, session, range) {
var selected = session.doc.getTextRange(range);
if (!range.isMultiLine() && selected == '[') {
initContext(editor);
var line = session.doc.getLine(range.start.row);
var rightChar = line.substring(range.start.column + 1, range.start.column + 2);
if (rightChar == ']') {
range.end.column++;
return range;
}
}
});
this.add("string_dquotes", "insertion", function(state, action, editor, session, text) {
if (text == '"' || text == "'") {
initContext(editor);
var quote = text;
var selection = editor.getSelectionRange();
var selected = session.doc.getTextRange(selection);
if (selected !== "" && selected !== "'" && selected != '"' && editor.getWrapBehavioursEnabled()) {
return {
text: quote + selected + quote,
selection: false
};
} else {
var cursor = editor.getCursorPosition();
var line = session.doc.getLine(cursor.row);
var leftChar = line.substring(cursor.column-1, cursor.column);
if (leftChar == '\\') {
return null;
}
var tokens = session.getTokens(selection.start.row);
var col = 0, token;
var quotepos = -1; // Track whether we're inside an open quote.
for (var x = 0; x < tokens.length; x++) {
token = tokens[x];
if (token.type == "string") {
quotepos = -1;
} else if (quotepos < 0) {
quotepos = token.value.indexOf(quote);
}
if ((token.value.length + col) > selection.start.column) {
break;
}
col += tokens[x].value.length;
}
if (!token || (quotepos < 0 && token.type !== "comment" && (token.type !== "string" || ((selection.start.column !== token.value.length+col-1) && token.value.lastIndexOf(quote) === token.value.length-1)))) {
if (!CstyleBehaviour.isSaneInsertion(editor, session))
return;
return {
text: quote + quote,
selection: [1,1]
};
} else if (token && token.type === "string") {
var rightChar = line.substring(cursor.column, cursor.column + 1);
if (rightChar == quote) {
return {
text: '',
selection: [1, 1]
};
}
}
}
}
});
this.add("string_dquotes", "deletion", function(state, action, editor, session, range) {
var selected = session.doc.getTextRange(range);
if (!range.isMultiLine() && (selected == '"' || selected == "'")) {
initContext(editor);
var line = session.doc.getLine(range.start.row);
var rightChar = line.substring(range.start.column + 1, range.start.column + 2);
if (rightChar == selected) {
range.end.column++;
return range;
}
}
});
};
CstyleBehaviour.isSaneInsertion = function(editor, session) {
var cursor = editor.getCursorPosition();
var iterator = new TokenIterator(session, cursor.row, cursor.column);
if (!this.$matchTokenType(iterator.getCurrentToken() || "text", SAFE_INSERT_IN_TOKENS)) {
var iterator2 = new TokenIterator(session, cursor.row, cursor.column + 1);
if (!this.$matchTokenType(iterator2.getCurrentToken() || "text", SAFE_INSERT_IN_TOKENS))
return false;
}
iterator.stepForward();
return iterator.getCurrentTokenRow() !== cursor.row ||
this.$matchTokenType(iterator.getCurrentToken() || "text", SAFE_INSERT_BEFORE_TOKENS);
};
CstyleBehaviour.$matchTokenType = function(token, types) {
return types.indexOf(token.type || token) > -1;
};
CstyleBehaviour.recordAutoInsert = function(editor, session, bracket) {
var cursor = editor.getCursorPosition();
var line = session.doc.getLine(cursor.row);
if (!this.isAutoInsertedClosing(cursor, line, context.autoInsertedLineEnd[0]))
context.autoInsertedBrackets = 0;
context.autoInsertedRow = cursor.row;
context.autoInsertedLineEnd = bracket + line.substr(cursor.column);
context.autoInsertedBrackets++;
};
CstyleBehaviour.recordMaybeInsert = function(editor, session, bracket) {
var cursor = editor.getCursorPosition();
var line = session.doc.getLine(cursor.row);
if (!this.isMaybeInsertedClosing(cursor, line))
context.maybeInsertedBrackets = 0;
context.maybeInsertedRow = cursor.row;
context.maybeInsertedLineStart = line.substr(0, cursor.column) + bracket;
context.maybeInsertedLineEnd = line.substr(cursor.column);
context.maybeInsertedBrackets++;
};
CstyleBehaviour.isAutoInsertedClosing = function(cursor, line, bracket) {
return context.autoInsertedBrackets > 0 &&
cursor.row === context.autoInsertedRow &&
bracket === context.autoInsertedLineEnd[0] &&
line.substr(cursor.column) === context.autoInsertedLineEnd;
};
CstyleBehaviour.isMaybeInsertedClosing = function(cursor, line) {
return context.maybeInsertedBrackets > 0 &&
cursor.row === context.maybeInsertedRow &&
line.substr(cursor.column) === context.maybeInsertedLineEnd &&
line.substr(0, cursor.column) == context.maybeInsertedLineStart;
};
CstyleBehaviour.popAutoInsertedClosing = function() {
context.autoInsertedLineEnd = context.autoInsertedLineEnd.substr(1);
context.autoInsertedBrackets--;
};
CstyleBehaviour.clearMaybeInsertedClosing = function() {
if (context) {
context.maybeInsertedBrackets = 0;
context.maybeInsertedRow = -1;
}
};
oop.inherits(CstyleBehaviour, Behaviour);
exports.CstyleBehaviour = CstyleBehaviour;
});
ace.define("ace/mode/folding/cstyle",["require","exports","module","ace/lib/oop","ace/range","ace/mode/folding/fold_mode"], function(acequire, exports, module) {
"use strict";
var oop = acequire("../../lib/oop");
var Range = acequire("../../range").Range;
var BaseFoldMode = acequire("./fold_mode").FoldMode;
var FoldMode = exports.FoldMode = function(commentRegex) {
if (commentRegex) {
this.foldingStartMarker = new RegExp(
this.foldingStartMarker.source.replace(/\|[^|]*?$/, "|" + commentRegex.start)
);
this.foldingStopMarker = new RegExp(
this.foldingStopMarker.source.replace(/\|[^|]*?$/, "|" + commentRegex.end)
);
}
};
oop.inherits(FoldMode, BaseFoldMode);
(function() {
this.foldingStartMarker = /(\{|\[)[^\}\]]*$|^\s*(\/\*)/;
this.foldingStopMarker = /^[^\[\{]*(\}|\])|^[\s\*]*(\*\/)/;
this.getFoldWidgetRange = function(session, foldStyle, row, forceMultiline) {
var line = session.getLine(row);
var match = line.match(this.foldingStartMarker);
if (match) {
var i = match.index;
if (match[1])
return this.openingBracketBlock(session, match[1], row, i);
var range = session.getCommentFoldRange(row, i + match[0].length, 1);
if (range && !range.isMultiLine()) {
if (forceMultiline) {
range = this.getSectionRange(session, row);
} else if (foldStyle != "all")
range = null;
}
return range;
}
if (foldStyle === "markbegin")
return;
var match = line.match(this.foldingStopMarker);
if (match) {
var i = match.index + match[0].length;
if (match[1])
return this.closingBracketBlock(session, match[1], row, i);
return session.getCommentFoldRange(row, i, -1);
}
};
this.getSectionRange = function(session, row) {
var line = session.getLine(row);
var startIndent = line.search(/\S/);
var startRow = row;
var startColumn = line.length;
row = row + 1;
var endRow = row;
var maxRow = session.getLength();
while (++row < maxRow) {
line = session.getLine(row);
var indent = line.search(/\S/);
if (indent === -1)
continue;
if (startIndent > indent)
break;
var subRange = this.getFoldWidgetRange(session, "all", row);
if (subRange) {
if (subRange.start.row <= startRow) {
break;
} else if (subRange.isMultiLine()) {
row = subRange.end.row;
} else if (startIndent == indent) {
break;
}
}
endRow = row;
}
return new Range(startRow, startColumn, endRow, session.getLine(endRow).length);
};
}).call(FoldMode.prototype);
});
ace.define("ace/mode/javascript",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/javascript_highlight_rules","ace/mode/matching_brace_outdent","ace/range","ace/worker/worker_client","ace/mode/behaviour/cstyle","ace/mode/folding/cstyle"], function(acequire, exports, module) {
"use strict";
var oop = acequire("../lib/oop");
var TextMode = acequire("./text").Mode;
var JavaScriptHighlightRules = acequire("./javascript_highlight_rules").JavaScriptHighlightRules;
var MatchingBraceOutdent = acequire("./matching_brace_outdent").MatchingBraceOutdent;
var Range = acequire("../range").Range;
var WorkerClient = acequire("../worker/worker_client").WorkerClient;
var CstyleBehaviour = acequire("./behaviour/cstyle").CstyleBehaviour;
var CStyleFoldMode = acequire("./folding/cstyle").FoldMode;
var Mode = function() {
this.HighlightRules = JavaScriptHighlightRules;
this.$outdent = new MatchingBraceOutdent();
this.$behaviour = new CstyleBehaviour();
this.foldingRules = new CStyleFoldMode();
};
oop.inherits(Mode, TextMode);
(function() {
this.lineCommentStart = "//";
this.blockComment = {start: "/*", end: "*/"};
this.getNextLineIndent = function(state, line, tab) {
var indent = this.$getIndent(line);
var tokenizedLine = this.getTokenizer().getLineTokens(line, state);
var tokens = tokenizedLine.tokens;
var endState = tokenizedLine.state;
if (tokens.length && tokens[tokens.length-1].type == "comment") {
return indent;
}
if (state == "start" || state == "no_regex") {
var match = line.match(/^.*(?:\bcase\b.*\:|[\{\(\[])\s*$/);
if (match) {
indent += tab;
}
} else if (state == "doc-start") {
if (endState == "start" || endState == "no_regex") {
return "";
}
var match = line.match(/^\s*(\/?)\*/);
if (match) {
if (match[1]) {
indent += " ";
}
indent += "* ";
}
}
return indent;
};
this.checkOutdent = function(state, line, input) {
return this.$outdent.checkOutdent(line, input);
};
this.autoOutdent = function(state, doc, row) {
this.$outdent.autoOutdent(doc, row);
};
// this.createWorker = function(session) {
// var worker = new WorkerClient(["ace"], "ace/mode/javascript_worker", "JavaScriptWorker");
// worker.attachToDocument(session.getDocument());
//
// worker.on("jslint", function(results) {
// session.setAnnotations(results.data);
// });
//
// worker.on("terminate", function() {
// session.clearAnnotations();
// });
//
// return worker;
// };
this.$id = "ace/mode/javascript";
}).call(Mode.prototype);
exports.Mode = Mode;
});

View File

@ -16,6 +16,7 @@
import Actionbar from './Actionbar'; import Actionbar from './Actionbar';
import ActionbarExport from './Actionbar/Export'; import ActionbarExport from './Actionbar/Export';
import ActionbarImport from './Actionbar/Import';
import ActionbarSearch from './Actionbar/Search'; import ActionbarSearch from './Actionbar/Search';
import ActionbarSort from './Actionbar/Sort'; import ActionbarSort from './Actionbar/Sort';
import Badge from './Badge'; import Badge from './Badge';
@ -26,6 +27,7 @@ import ConfirmDialog from './ConfirmDialog';
import Container, { Title as ContainerTitle } from './Container'; import Container, { Title as ContainerTitle } from './Container';
import ContextProvider from './ContextProvider'; import ContextProvider from './ContextProvider';
import CopyToClipboard from './CopyToClipboard'; import CopyToClipboard from './CopyToClipboard';
import Editor from './Editor';
import Errors from './Errors'; import Errors from './Errors';
import Form, { AddressSelect, FormWrap, Input, InputAddress, InputAddressSelect, InputChip, InputInline, Select } from './Form'; import Form, { AddressSelect, FormWrap, Input, InputAddress, InputAddressSelect, InputChip, InputInline, Select } from './Form';
import IdentityIcon from './IdentityIcon'; import IdentityIcon from './IdentityIcon';
@ -43,6 +45,7 @@ import TxHash from './TxHash';
export { export {
Actionbar, Actionbar,
ActionbarExport, ActionbarExport,
ActionbarImport,
ActionbarSearch, ActionbarSearch,
ActionbarSort, ActionbarSort,
AddressSelect, AddressSelect,
@ -55,6 +58,7 @@ export {
ContainerTitle, ContainerTitle,
ContextProvider, ContextProvider,
CopyToClipboard, CopyToClipboard,
Editor,
Errors, Errors,
Form, Form,
FormWrap, FormWrap,

View File

@ -145,6 +145,8 @@ export default class List extends Component {
return tagsA.localeCompare(tagsB); return tagsA.localeCompare(tagsB);
} }
const reverse = key === 'timestamp' ? -1 : 1;
const metaA = accountA.meta[key]; const metaA = accountA.meta[key];
const metaB = accountB.meta[key]; const metaB = accountB.meta[key];
@ -152,14 +154,22 @@ export default class List extends Component {
return 0; return 0;
} }
if ((metaA && !metaB) || (metaA < metaB)) { if (metaA && !metaB) {
return -1; return -1;
} }
if ((!metaA && metaB) || (metaA > metaB)) { if (metaA < metaB) {
return -1 * reverse;
}
if (!metaA && metaB) {
return 1; return 1;
} }
if (metaA > metaB) {
return 1 * reverse;
}
return 0; return 0;
} }

View File

@ -29,7 +29,8 @@ const TABMAP = {
accounts: 'account', accounts: 'account',
addresses: 'address', addresses: 'address',
apps: 'app', apps: 'app',
contracts: 'contract' contracts: 'contract',
deploy: 'contract'
}; };
class TabBar extends Component { class TabBar extends Component {

View File

@ -18,4 +18,6 @@
.outer, .outer,
.container { .container {
min-height: 100vh; min-height: 100vh;
display: flex;
flex-direction: column;
} }

View File

@ -90,3 +90,12 @@
margin-bottom: -10px; margin-bottom: -10px;
margin-right: 0.5em; margin-right: 0.5em;
} }
.details {
margin-top: -1.5em;
h4 {
text-transform: uppercase;
margin: 1.5em 0 1em;
}
}

View File

@ -20,10 +20,12 @@ import { bindActionCreators } from 'redux';
import ActionDelete from 'material-ui/svg-icons/action/delete'; import ActionDelete from 'material-ui/svg-icons/action/delete';
import AvPlayArrow from 'material-ui/svg-icons/av/play-arrow'; import AvPlayArrow from 'material-ui/svg-icons/av/play-arrow';
import ContentCreate from 'material-ui/svg-icons/content/create'; import ContentCreate from 'material-ui/svg-icons/content/create';
import EyeIcon from 'material-ui/svg-icons/image/remove-red-eye';
import ContentClear from 'material-ui/svg-icons/content/clear';
import { newError } from '../../redux/actions'; import { newError } from '../../redux/actions';
import { EditMeta, ExecuteContract } from '../../modals'; import { EditMeta, ExecuteContract } from '../../modals';
import { Actionbar, Button, Page } from '../../ui'; import { Actionbar, Button, Page, Modal, Editor } from '../../ui';
import Header from '../Account/Header'; import Header from '../Account/Header';
import Delete from '../Address/Delete'; import Delete from '../Address/Delete';
@ -52,6 +54,7 @@ class Contract extends Component {
showDeleteDialog: false, showDeleteDialog: false,
showEditDialog: false, showEditDialog: false,
showExecuteDialog: false, showExecuteDialog: false,
showDetailsDialog: false,
subscriptionId: -1, subscriptionId: -1,
blockSubscriptionId: -1, blockSubscriptionId: -1,
allEvents: [], allEvents: [],
@ -118,11 +121,69 @@ class Contract extends Component {
<Events <Events
isTest={ isTest } isTest={ isTest }
events={ allEvents } /> events={ allEvents } />
{ this.renderDetails(account) }
</Page> </Page>
</div> </div>
); );
} }
renderDetails (contract) {
const { showDetailsDialog } = this.state;
if (!showDetailsDialog) {
return null;
}
const cancelBtn = (
<Button
icon={ <ContentClear /> }
label='Close'
onClick={ this.closeDetailsDialog } />
);
return (
<Modal
actions={ [ cancelBtn ] }
title={ 'contract details' }
visible
scroll
>
<div className={ styles.details }>
{ this.renderSource(contract) }
<div>
<h4>Contract ABI</h4>
<Editor
value={ JSON.stringify(contract.meta.abi, null, 2) }
mode='json'
maxLines={ 20 }
readOnly
/>
</div>
</div>
</Modal>
);
}
renderSource (contract) {
const { source } = contract.meta;
if (!source) {
return null;
}
return (
<div>
<h4>Contract source code</h4>
<Editor
value={ source }
readOnly
/>
</div>
);
}
renderActionbar (account) { renderActionbar (account) {
const buttons = [ const buttons = [
<Button <Button
@ -139,7 +200,12 @@ class Contract extends Component {
key='delete' key='delete'
icon={ <ActionDelete /> } icon={ <ActionDelete /> }
label='delete contract' label='delete contract'
onClick={ this.showDeleteDialog } /> onClick={ this.showDeleteDialog } />,
<Button
key='viewDetails'
icon={ <EyeIcon /> }
label='view details'
onClick={ this.showDetailsDialog } />
]; ];
return ( return (
@ -235,6 +301,14 @@ class Contract extends Component {
this.setState({ showDeleteDialog: true }); this.setState({ showDeleteDialog: true });
} }
showDetailsDialog = () => {
this.setState({ showDetailsDialog: true });
}
closeDetailsDialog = () => {
this.setState({ showDetailsDialog: false });
}
closeExecuteDialog = () => { closeExecuteDialog = () => {
this.setState({ showExecuteDialog: false }); this.setState({ showExecuteDialog: false });
} }

View File

@ -15,9 +15,11 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import { Link } from 'react-router';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
import ContentAdd from 'material-ui/svg-icons/content/add'; import ContentAdd from 'material-ui/svg-icons/content/add';
import FileIcon from 'material-ui/svg-icons/action/description';
import { uniq } from 'lodash'; import { uniq } from 'lodash';
import { Actionbar, ActionbarSearch, ActionbarSort, Button, Page } from '../../ui'; import { Actionbar, ActionbarSearch, ActionbarSort, Button, Page } from '../../ui';
@ -114,6 +116,15 @@ class Contracts extends Component {
icon={ <ContentAdd /> } icon={ <ContentAdd /> }
label='deploy contract' label='deploy contract'
onClick={ this.onDeployContract } />, onClick={ this.onDeployContract } />,
<Link
to='/contracts/write'
key='writeContract'
>
<Button
icon={ <FileIcon /> }
label='write contract'
/>
</Link>,
this.renderSearchButton(), this.renderSearchButton(),
this.renderSortButton() this.renderSortButton()

View File

@ -0,0 +1,43 @@
[
{
"id": "0xf9f2d620c2e08f83e45555247146c62185e4ab7cf82a4b9002a265a0d020348f",
"url": "basiccoin",
"name": "Token Deployment",
"description": "Deploy new basic tokens that you are able to send around",
"author": "Parity Team <admin@ethcore.io>",
"version": "1.0.0"
},
{
"id": "0xd1adaede68d344519025e2ff574650cd99d3830fe6d274c7a7843cdc00e17938",
"url": "registry",
"name": "Registry",
"description": "A global registry of addresses on the network",
"author": "Parity Team <admin@ethcore.io>",
"version": "1.0.0"
},
{
"id": "0x0a8048117e51e964628d0f2d26342b3cd915248b59bcce2721e1d05f5cfa2208",
"url": "tokenreg",
"name": "Token Registry",
"description": "A registry of transactable tokens on the network",
"author": "Parity Team <admin@ethcore.io>",
"version": "1.0.0"
},
{
"id": "0xf49089046f53f5d2e5f3513c1c32f5ff57d986e46309a42d2b249070e4e72c46",
"url": "signaturereg",
"name": "Method Registry",
"description": "A registry of method signatures for lookups on transactions",
"author": "Parity Team <admin@ethcore.io>",
"version": "1.0.0"
},
{
"id": "0x058740ee9a5a3fb9f1cfa10752baec87e09cc45cd7027fd54708271aca300c75",
"url": "githubhint",
"name": "GitHub Hint",
"description": "A mapping of GitHub URLs to hashes for use in contracts as references",
"author": "Parity Team <admin@ethcore.io>",
"version": "1.0.0",
"secure": true
}
]

View File

@ -14,69 +14,39 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { action, computed, observable } from 'mobx'; import { action, computed, observable, transaction } from 'mobx';
import Contracts from '../../contracts'; import Contracts from '../../contracts';
import { hashToImageUrl } from '../../redux/util'; import { hashToImageUrl } from '../../redux/util';
const builtinApps = [ import builtinApps from './builtin.json';
{
id: '0xf9f2d620c2e08f83e45555247146c62185e4ab7cf82a4b9002a265a0d020348f', const LS_KEY_HIDDEN = 'hiddenApps';
url: 'basiccoin', const LS_KEY_EXTERNAL = 'externalApps';
name: 'Token Deployment',
description: 'Deploy new basic tokens that you are able to send around',
author: 'Parity Team <admin@ethcore.io>',
version: '1.0.0'
},
{
id: '0xd1adaede68d344519025e2ff574650cd99d3830fe6d274c7a7843cdc00e17938',
url: 'registry',
name: 'Registry',
description: 'A global registry of addresses on the network',
author: 'Parity Team <admin@ethcore.io>',
version: '1.0.0'
},
{
id: '0x0a8048117e51e964628d0f2d26342b3cd915248b59bcce2721e1d05f5cfa2208',
url: 'tokenreg',
name: 'Token Registry',
description: 'A registry of transactable tokens on the network',
author: 'Parity Team <admin@ethcore.io>',
version: '1.0.0'
},
{
id: '0xf49089046f53f5d2e5f3513c1c32f5ff57d986e46309a42d2b249070e4e72c46',
url: 'signaturereg',
name: 'Method Registry',
description: 'A registry of method signatures for lookups on transactions',
author: 'Parity Team <admin@ethcore.io>',
version: '1.0.0'
},
{
id: '0x058740ee9a5a3fb9f1cfa10752baec87e09cc45cd7027fd54708271aca300c75',
url: 'githubhint',
name: 'GitHub Hint',
description: 'A mapping of GitHub URLs to hashes for use in contracts as references',
author: 'Parity Team <admin@ethcore.io>',
version: '1.0.0',
secure: true
}
];
export default class DappsStore { export default class DappsStore {
@observable apps = []; @observable apps = [];
@observable hidden = []; @observable externalApps = [];
@observable hiddenApps = [];
@observable modalOpen = false; @observable modalOpen = false;
constructor (api) { constructor (api) {
this._api = api; this._api = api;
this._readHiddenApps(); this._readHiddenApps();
this._fetch(); this._readExternalApps();
this._fetchBuiltinApps();
this._fetchLocalApps();
this._fetchRegistryApps();
} }
@computed get visible () { @computed get visible () {
return this.apps.filter((app) => !this.hidden.includes(app.id)); return this.apps
.filter((app) => {
return this.externalApps.includes(app.id) || !this.hiddenApps.includes(app.id);
})
.sort((a, b) => a.name.localeCompare(b.name));
} }
@action openModal = () => { @action openModal = () => {
@ -88,12 +58,12 @@ export default class DappsStore {
} }
@action hideApp = (id) => { @action hideApp = (id) => {
this.hidden = this.hidden.concat(id); this.hiddenApps = this.hiddenApps.concat(id);
this._writeHiddenApps(); this._writeHiddenApps();
} }
@action showApp = (id) => { @action showApp = (id) => {
this.hidden = this.hidden.filter((_id) => _id !== id); this.hiddenApps = this.hiddenApps.filter((_id) => _id !== id);
this._writeHiddenApps(); this._writeHiddenApps();
} }
@ -103,25 +73,48 @@ export default class DappsStore {
: ''; : '';
} }
_fetch () { _fetchBuiltinApps () {
Promise const { dappReg } = Contracts.get();
.all([
this._fetchLocal(), return Promise
this._fetchRegistry() .all(builtinApps.map((app) => dappReg.getImage(app.id)))
]) .then((imageIds) => {
.then(([localApps, registryApps]) => { transaction(() => {
this.apps = [] builtinApps.forEach((app, index) => {
.concat(localApps) app.type = 'builtin';
.concat(registryApps) app.image = hashToImageUrl(imageIds[index]);
.filter((app) => app.id) this.apps.push(app);
.sort((a, b) => (a.name || '').localeCompare(b.name || '')); });
}) });
.catch((error) => {
console.warn('DappStore:fetch', error);
}); });
} }
_fetchRegistry () { _fetchLocalApps () {
return fetch(`${this._getHost()}/api/apps`)
.then((response) => {
return response.ok
? response.json()
: [];
})
.then((apps) => {
return apps
.map((app) => {
app.type = 'local';
return app;
})
.filter((app) => app.id && !['ui'].includes(app.id));
})
.then((apps) => {
transaction(() => {
(apps || []).forEach((app) => this.apps.push(app));
});
})
.catch((error) => {
console.warn('DappsStore:fetchLocal', error);
});
}
_fetchRegistryApps () {
const { dappReg } = Contracts.get(); const { dappReg } = Contracts.get();
return dappReg return dappReg
@ -137,9 +130,9 @@ export default class DappsStore {
return Promise.all(promises); return Promise.all(promises);
}) })
.then((appsInfo) => { .then((appsInfo) => {
const appIds = appsInfo.map(([appId, owner]) => { const appIds = appsInfo
return this._api.util.bytesToHex(appId); .map(([appId, owner]) => this._api.util.bytesToHex(appId))
}); .filter((appId) => !builtinApps.find((app) => app.id === appId));
return Promise return Promise
.all([ .all([
@ -149,27 +142,21 @@ export default class DappsStore {
]) ])
.then(([imageIds, contentIds, manifestIds]) => { .then(([imageIds, contentIds, manifestIds]) => {
return appIds.map((appId, index) => { return appIds.map((appId, index) => {
const app = builtinApps.find((ba) => ba.id === appId) || { const app = {
id: appId, id: appId,
image: hashToImageUrl(imageIds[index]),
contentHash: this._api.util.bytesToHex(contentIds[index]).substr(2), contentHash: this._api.util.bytesToHex(contentIds[index]).substr(2),
manifestHash: this._api.util.bytesToHex(manifestIds[index]).substr(2), manifestHash: this._api.util.bytesToHex(manifestIds[index]).substr(2),
type: 'network' type: 'network'
}; };
app.image = hashToImageUrl(imageIds[index]);
app.type = app.type || 'builtin';
return app; return app;
}); });
}); });
}) })
.then((apps) => { .then((apps) => {
return Promise return Promise
.all(apps.map((app) => { .all(apps.map((app) => this._fetchManifest(app.manifestHash)))
return app.manifestHash
? this._fetchManifest(app.manifestHash)
: null;
}))
.then((manifests) => { .then((manifests) => {
return apps.map((app, index) => { return apps.map((app, index) => {
const manifest = manifests[index]; const manifest = manifests[index];
@ -177,7 +164,7 @@ export default class DappsStore {
if (manifest) { if (manifest) {
app.manifestHash = null; app.manifestHash = null;
Object.keys(manifest) Object.keys(manifest)
.filter((key) => key !== 'id') .filter((key) => ['author', 'description', 'name', 'version'].includes(key))
.forEach((key) => { .forEach((key) => {
app[key] = manifest[key]; app[key] = manifest[key];
}); });
@ -192,6 +179,11 @@ export default class DappsStore {
}); });
}); });
}) })
.then((apps) => {
transaction(() => {
(apps || []).forEach((app) => this.apps.push(app));
});
})
.catch((error) => { .catch((error) => {
console.warn('DappsStore:fetchRegistry', error); console.warn('DappsStore:fetchRegistry', error);
}); });
@ -210,39 +202,43 @@ export default class DappsStore {
}); });
} }
_fetchLocal () {
return fetch(`${this._getHost()}/api/apps`)
.then((response) => {
return response.ok
? response.json()
: [];
})
.then((localApps) => {
return localApps
.filter((app) => app && app.id && !['ui'].includes(app.id))
.map((app) => {
app.type = 'local';
return app;
});
})
.catch((error) => {
console.warn('DappsStore:fetchLocal', error);
});
}
_readHiddenApps () { _readHiddenApps () {
const stored = localStorage.getItem('hiddenApps'); const stored = localStorage.getItem(LS_KEY_HIDDEN);
if (stored) { if (stored) {
try { try {
this.hidden = JSON.parse(stored); this.hiddenApps = JSON.parse(stored);
} catch (error) { } catch (error) {
console.warn('DappsStore:readHiddenApps', error); console.warn('DappsStore:readHiddenApps', error);
} }
} }
} }
_readExternalApps () {
const stored = localStorage.getItem(LS_KEY_EXTERNAL);
if (stored) {
try {
this.externalApps = JSON.parse(stored);
} catch (error) {
console.warn('DappsStore:readExternalApps', error);
}
}
}
_writeExternalApps () {
try {
localStorage.setItem(LS_KEY_EXTERNAL, JSON.stringify(this.externalApps));
} catch (error) {
console.error('DappsStore:writeExternalApps', error);
}
}
_writeHiddenApps () { _writeHiddenApps () {
localStorage.setItem('hiddenApps', JSON.stringify(this.hidden)); try {
localStorage.setItem(LS_KEY_HIDDEN, JSON.stringify(this.hiddenApps));
} catch (error) {
console.error('DappsStore:writeHiddenApps', error);
}
} }
} }

View File

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

View File

@ -0,0 +1,174 @@
/* Copyright 2015, 2016 Ethcore (UK) Ltd.
/* This file is part of Parity.
/*
/* Parity is free software: you can redistribute it and/or modify
/* it under the terms of the GNU General Public License as published by
/* the Free Software Foundation, either version 3 of the License, or
/* (at your option) any later version.
/*
/* Parity is distributed in the hope that it will be useful,
/* but WITHOUT ANY WARRANTY; without even the implied warranty of
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
/* GNU General Public License for more details.
/*
/* You should have received a copy of the GNU General Public License
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/
.outer, .page, .editor {
flex: 1;
display: flex;
flex-direction: column;
}
.timestamp {
font-size: 0.75em;
margin-left: 1em;
color: #ccc;
}
.container {
padding: 1em 0;
display: flex;
flex: 1;
flex-direction: row;
> * {
margin: 0;
> h2 {
margin-top: 0;
}
}
}
.mainEditor {
&:global(.ace-solarized-dark) {
background-color: rgba(0, 0, 0, 0.5);
:global(.ace_gutter) {
background-color: rgba(0, 0, 0, 0.7);
}
:global(.ace_content) {
background-color: transparent;
}
}
}
.big {
font-size: 1.2em;
}
.centeredMessage {
width: 100%;
height: 75%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.resizing * {
cursor: ew-resize !important;
user-select: none !important;
}
.editor {
width: 0;
margin-left: 0.5em;
}
.parameters {
width: 0;
display: flex;
flex-direction: column;
margin-right: 0.5em;
.panel {
background-color: rgba(0, 0, 0, 0.5);
padding: 1em;
flex: 1;
display: flex;
flex-direction: column;
}
.compilation {
flex: 1 0 0;
display: flex;
flex-direction: column;
}
.errors {
flex: 1 0 0;
overflow: auto;
margin-right: -0.5em;
margin-top: 0.5em;
}
}
.messageContainer {
padding: 0.5em 0;
margin-right: 0.5em;
&:first-child {
padding-top: 0;
}
&:last-child {
padding-bottom: 0;
}
.errorPosition {
background-color: rgba(0, 0, 0, 0.5);
padding: 0.25em 0.5em;
top: 0;
position: relative;
margin-bottom: 0.25em;
font-size: 0.75em;
}
}
.message {
font-family: monospace;
padding: 0.5em;
font-size: 0.9em;
white-space: pre;
overflow: auto;
&.error {
background-color: rgba(244, 67, 54, 0.5);
}
&.warning {
background-color: rgba(255, 235, 59, 0.5);
}
&.formal {
background-color: rgba(243, 156, 18, 0.5);
}
}
.messagesHeader {
margin-bottom: 0.25em;
text-transform: uppercase;
font-size: 0.9em;
}
.sliderContainer {
flex: 0 0 .8em;
display: flex;
align-items: center;
justify-content: center;
.slider {
width: 0.4em;
height: 3em;
border-radius: 0.75em;
background-color: rgba(0, 0, 0, 0.5);
content: ' ';
&:hover {
cursor: ew-resize;
}
}
}

View File

@ -0,0 +1,502 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { PropTypes, Component } from 'react';
import { observer } from 'mobx-react';
import { MenuItem } from 'material-ui';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import CircularProgress from 'material-ui/CircularProgress';
import moment from 'moment';
import ContentClear from 'material-ui/svg-icons/content/clear';
import SaveIcon from 'material-ui/svg-icons/content/save';
import ListIcon from 'material-ui/svg-icons/action/view-list';
import SettingsIcon from 'material-ui/svg-icons/action/settings';
import SendIcon from 'material-ui/svg-icons/content/send';
import { Actionbar, ActionbarExport, ActionbarImport, Button, Editor, Page, Select, Input } from '../../ui';
import { DeployContract, SaveContract, LoadContract } from '../../modals';
import { setupWorker } from '../../redux/providers/compilerActions';
import WriteContractStore from './writeContractStore';
import styles from './writeContract.css';
@observer
class WriteContract extends Component {
static propTypes = {
accounts: PropTypes.object.isRequired,
setupWorker: PropTypes.func.isRequired,
worker: PropTypes.object
};
store = new WriteContractStore();
state = {
resizing: false,
size: 65
};
componentWillMount () {
const { setupWorker, worker } = this.props;
setupWorker();
if (worker) {
this.store.setCompiler(worker);
}
}
componentDidMount () {
this.store.setEditor(this.refs.editor);
// Wait for editor to be loaded
window.setTimeout(() => {
this.store.resizeEditor();
}, 2000);
}
componentWillReceiveProps (nextProps) {
if (!this.props.worker && nextProps.worker) {
this.store.setCompiler(nextProps.worker);
}
}
render () {
const { sourcecode } = this.store;
const { size, resizing } = this.state;
const annotations = this.store.annotations
.slice()
.filter((a) => a.contract === '');
return (
<div className={ styles.outer }>
{ this.renderDeployModal() }
{ this.renderSaveModal() }
{ this.renderLoadModal() }
{ this.renderActionBar() }
<Page className={ styles.page }>
<div
className={ `${styles.container} ${resizing ? styles.resizing : ''}` }
onMouseMove={ this.handleResize }
onMouseUp={ this.handleStopResize }
onMouseLeave={ this.handleStopResize }
>
<div
className={ styles.editor }
style={ { flex: `${size}%` } }
>
<h2>{ this.renderTitle() }</h2>
<Editor
ref='editor'
onChange={ this.store.handleEditSourcecode }
onExecute={ this.store.handleCompile }
annotations={ annotations }
value={ sourcecode }
className={ styles.mainEditor }
/>
</div>
<div className={ styles.sliderContainer }>
<span
className={ styles.slider }
onMouseDown={ this.handleStartResize }
>
</span>
</div>
<div
className={ styles.parameters }
style={ { flex: `${100 - size}%` } }
>
<h2>Parameters</h2>
{ this.renderParameters() }
</div>
</div>
</Page>
</div>
);
}
renderTitle () {
const { selectedContract } = this.store;
if (!selectedContract || !selectedContract.name) {
return 'New Solidity Contract';
}
return (
<span>
{ selectedContract.name }
<span
className={ styles.timestamp }
title={ `saved @ ${(new Date(selectedContract.timestamp)).toISOString()}` }
>
(saved { moment(selectedContract.timestamp).fromNow() })
</span>
</span>
);
}
renderActionBar () {
const { sourcecode, selectedContract } = this.store;
const filename = selectedContract && selectedContract.name
? selectedContract.name
.replace(/[^a-z0-9]+/gi, '-')
.replace(/-$/, '')
.toLowerCase()
: 'contract.sol';
const extension = /\.sol$/.test(filename) ? '' : '.sol';
const buttons = [
<Button
icon={ <ContentClear /> }
label='New'
key='newContract'
onClick={ this.store.handleNewContract }
/>,
<Button
icon={ <ListIcon /> }
label='Load'
key='loadContract'
onClick={ this.store.handleOpenLoadModal }
/>,
<Button
icon={ <SaveIcon /> }
label='Save'
key='saveContract'
onClick={ this.store.handleSaveContract }
/>,
<ActionbarExport
key='exportSourcecode'
content={ sourcecode }
filename={ `${filename}${extension}` }
/>,
<ActionbarImport
key='importSourcecode'
title='Import Solidity code'
onConfirm={ this.store.handleImport }
renderValidation={ this.renderImportValidation }
/>
];
return (
<Actionbar
title='Write a Contract'
buttons={ buttons }
/>
);
}
renderImportValidation = (content) => {
return (
<Editor
readOnly
value={ content }
maxLines={ 20 }
/>
);
}
renderParameters () {
const { compiling, contract, selectedBuild, loading } = this.store;
if (selectedBuild < 0) {
return (
<div className={ `${styles.panel} ${styles.centeredMessage}` }>
<CircularProgress size={ 80 } thickness={ 5 } />
<p>Loading...</p>
</div>
);
}
if (loading) {
const { longVersion } = this.store.builds[selectedBuild];
return (
<div className={ styles.panel }>
<div className={ styles.centeredMessage }>
<CircularProgress size={ 80 } thickness={ 5 } />
<p>Loading Solidity { longVersion }</p>
</div>
</div>
);
}
return (
<div className={ styles.panel }>
<div>
<Button
icon={ <SettingsIcon /> }
label='Compile'
onClick={ this.store.handleCompile }
primary={ false }
disabled={ compiling }
/>
{
contract
? <Button
icon={ <SendIcon /> }
label='Deploy'
onClick={ this.store.handleOpenDeployModal }
primary={ false }
/>
: null
}
</div>
{ this.renderSolidityVersions() }
{ this.renderCompilation() }
</div>
);
}
renderSolidityVersions () {
const { builds, selectedBuild } = this.store;
const buildsList = builds.map((build, index) => (
<MenuItem
key={ index }
value={ index }
label={ build.release ? build.version : build.longVersion }
>
{
build.release
? (<span className={ styles.big }>{ build.version }</span>)
: build.longVersion
}
</MenuItem>
));
return (
<div>
<Select
label='Select a Solidity version'
value={ selectedBuild }
onChange={ this.store.handleSelectBuild }
>
{ buildsList }
</Select>
</div>
);
}
renderDeployModal () {
const { showDeployModal, contract, sourcecode } = this.store;
if (!showDeployModal) {
return null;
}
return (
<DeployContract
abi={ contract.interface }
code={ `0x${contract.bytecode}` }
source={ sourcecode }
accounts={ this.props.accounts }
onClose={ this.store.handleCloseDeployModal }
readOnly
/>
);
}
renderLoadModal () {
const { showLoadModal } = this.store;
if (!showLoadModal) {
return null;
}
return (
<LoadContract
onLoad={ this.store.handleLoadContract }
onDelete={ this.store.handleDeleteContract }
onClose={ this.store.handleCloseLoadModal }
contracts={ this.store.savedContracts }
snippets={ this.store.snippets }
/>
);
}
renderSaveModal () {
const { showSaveModal, sourcecode } = this.store;
if (!showSaveModal) {
return null;
}
return (
<SaveContract
sourcecode={ sourcecode }
onSave={ this.store.handleSaveNewContract }
onClose={ this.store.handleCloseSaveModal }
/>
);
}
renderCompilation () {
const { compiled, contracts, compiling, contractIndex, contract } = this.store;
if (compiling) {
return (
<div className={ styles.centeredMessage }>
<CircularProgress size={ 80 } thickness={ 5 } />
<p>Compiling...</p>
</div>
);
}
if (!compiled) {
return (
<div className={ styles.centeredMessage }>
<p>Please compile the source code.</p>
</div>
);
}
if (!contracts) {
return this.renderErrors();
}
const contractKeys = Object.keys(contracts);
if (contractKeys.length === 0) {
return (
<div className={ styles.centeredMessage }>
<p>No contract has been found.</p>
</div>
);
}
const contractsList = contractKeys.map((name, index) => (
<MenuItem
key={ index }
value={ index }
label={ name }
>
{ name }
</MenuItem>
));
return (
<div className={ styles.compilation }>
<Select
label='Select a contract'
value={ contractIndex }
onChange={ this.store.handleSelectContract }
>
{ contractsList }
</Select>
{ this.renderContract(contract) }
<h4 className={ styles.messagesHeader }>Compiler messages</h4>
{ this.renderErrors() }
</div>
);
}
renderContract (contract) {
const { bytecode } = contract;
const abi = contract.interface;
return (
<div>
<Input
readOnly
value={ abi }
label='ABI Interface'
/>
<Input
readOnly
value={ `0x${bytecode}` }
label='Bytecode'
/>
</div>
);
}
renderErrors () {
const { annotations } = this.store;
const body = annotations.map((annotation, index) => {
const { text, row, column, contract, type, formal } = annotation;
const classType = formal ? 'formal' : type;
const classes = [ styles.message, styles[classType] ];
return (
<div key={ index } className={ styles.messageContainer }>
<div className={ classes.join(' ') }>{ text }</div>
<span className={ styles.errorPosition }>
{ contract ? `[ ${contract} ] ` : '' }
{ row }: { column }
</span>
</div>
);
});
return (
<div className={ styles.errors }>
{ body }
</div>
);
}
handleStartResize = () => {
this.setState({ resizing: true });
}
handleStopResize = () => {
this.setState({ resizing: false });
}
handleResize = (event) => {
if (!this.state.resizing) {
return;
}
const { pageX, currentTarget } = event;
const { width, left } = currentTarget.getBoundingClientRect();
const x = pageX - left;
this.setState({ size: 100 * x / width });
event.stopPropagation();
}
}
function mapStateToProps (state) {
const { accounts } = state.personal;
const { worker } = state.compiler;
return { accounts, worker };
}
function mapDispatchToProps (dispatch) {
return bindActionCreators({
setupWorker
}, dispatch);
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(WriteContract);

View File

@ -0,0 +1,373 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { action, observable } from 'mobx';
import store from 'store';
import { debounce } from 'lodash';
const WRITE_CONTRACT_STORE_KEY = '_parity::writeContractStore';
const SNIPPETS = {
snippet0: {
name: 'Token.sol',
description: 'Standard ERP20 Token Contract',
id: 'snippet0', sourcecode: require('raw!../../contracts/snippets/token.sol')
},
snippet1: {
name: 'StandardToken.sol',
description: 'Implementation of ERP20 Token Contract',
id: 'snippet1', sourcecode: require('raw!../../contracts/snippets/standard-token.sol')
},
snippet2: {
name: 'HumanStandardToken.sol',
description: 'Implementation of the Human Token Contract',
id: 'snippet2', sourcecode: require('raw!../../contracts/snippets/human-standard-token.sol')
}
};
export default class WriteContractStore {
@observable sourcecode = '';
@observable compiled = false;
@observable compiling = false;
@observable loading = true;
@observable contractIndex = -1;
@observable contract = null;
@observable contracts = {};
@observable errors = [];
@observable annotations = [];
@observable builds = [];
@observable selectedBuild = -1;
@observable showDeployModal = false;
@observable showSaveModal = false;
@observable showLoadModal = false;
@observable savedContracts = {};
@observable selectedContract = {};
snippets = SNIPPETS;
constructor () {
this.reloadContracts();
this.fetchSolidityVersions();
this.debouncedCompile = debounce(this.handleCompile, 1000);
}
@action setEditor (editor) {
this.editor = editor;
}
@action setCompiler (compiler) {
this.compiler = compiler;
this.compiler.onmessage = (event) => {
const message = JSON.parse(event.data);
switch (message.event) {
case 'compiled':
this.parseCompiled(message.data);
break;
case 'loading':
this.parseLoading(message.data);
break;
case 'try-again':
this.handleCompile();
break;
}
};
}
fetchSolidityVersions () {
fetch('https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/bin/list.json')
.then((r) => r.json())
.then((data) => {
const { builds, releases, latestRelease } = data;
let latestIndex = -1;
this.builds = builds.reverse().map((build, index) => {
if (releases[build.version] === build.path) {
build.release = true;
if (build.version === latestRelease) {
build.latest = true;
this.loadSolidityVersion(build);
latestIndex = index;
}
}
return build;
});
this.selectedBuild = latestIndex;
});
}
@action closeWorker = () => {
this.compiler.postMessage(JSON.stringify({
action: 'close'
}));
}
@action handleImport = (sourcecode) => {
this.reloadContracts(-1, sourcecode);
}
@action handleSelectBuild = (_, index, value) => {
this.selectedBuild = value;
this.loadSolidityVersion(this.builds[value]);
}
@action loadSolidityVersion = (build) => {
this.compiler.postMessage(JSON.stringify({
action: 'load',
data: build
}));
}
@action handleOpenDeployModal = () => {
this.showDeployModal = true;
}
@action handleCloseDeployModal = () => {
this.showDeployModal = false;
}
@action handleOpenLoadModal = () => {
this.showLoadModal = true;
}
@action handleCloseLoadModal = () => {
this.showLoadModal = false;
}
@action handleOpenSaveModal = () => {
this.showSaveModal = true;
}
@action handleCloseSaveModal = () => {
this.showSaveModal = false;
}
@action handleSelectContract = (_, index, value) => {
this.contractIndex = value;
this.contract = this.contracts[Object.keys(this.contracts)[value]];
}
@action handleCompile = () => {
this.compiled = false;
this.compiling = true;
const build = this.builds[this.selectedBuild];
if (this.compiler && typeof this.compiler.postMessage === 'function') {
this.sendFilesToWorker();
this.compiler.postMessage(JSON.stringify({
action: 'compile',
data: {
sourcecode: this.sourcecode,
build: build
}
}));
}
}
parseErrors = (data, formal = false) => {
const regex = /^(.*):(\d+):(\d+):\s*([a-z]+):\s*((.|[\r\n])+)$/i;
return (data || [])
.filter((e) => regex.test(e))
.map((error, index) => {
const match = regex.exec(error);
const contract = match[1];
const row = parseInt(match[2]) - 1;
const column = parseInt(match[3]);
const type = formal ? 'warning' : match[4].toLowerCase();
const text = match[5];
return {
contract,
row, column,
type, text,
formal
};
});
}
@action parseCompiled = (data) => {
const { contracts } = data;
const { errors = [] } = data;
const errorAnnotations = this.parseErrors(errors);
const formalAnnotations = this.parseErrors(data.formal && data.formal.errors, true);
const annotations = [].concat(
errorAnnotations,
formalAnnotations
);
if (annotations.findIndex((a) => /__parity_tryAgain/.test(a.text)) > -1) {
return;
}
const contractKeys = Object.keys(contracts || {});
this.contract = contractKeys.length ? contracts[contractKeys[0]] : null;
this.contractIndex = contractKeys.length ? 0 : -1;
this.contracts = contracts;
this.errors = errors;
this.annotations = annotations;
this.compiled = true;
this.compiling = false;
}
@action parseLoading = (isLoading) => {
this.loading = isLoading;
if (!isLoading) {
this.handleCompile();
}
}
@action handleEditSourcecode = (value, compile = false) => {
this.sourcecode = value;
const localStore = store.get(WRITE_CONTRACT_STORE_KEY) || {};
store.set(WRITE_CONTRACT_STORE_KEY, {
...localStore,
current: value
});
if (compile) {
this.handleCompile();
} else {
this.debouncedCompile();
}
}
@action handleSaveContract = () => {
if (this.selectedContract && this.selectedContract.id !== undefined) {
return this.handleSaveNewContract({
...this.selectedContract,
sourcecode: this.sourcecode
});
}
return this.handleOpenSaveModal();
}
getId (contracts) {
return Object.values(contracts)
.map((c) => c.id)
.reduce((max, id) => Math.max(max, id), 0) + 1;
}
@action handleSaveNewContract = (data) => {
const { name, sourcecode, id } = data;
const localStore = store.get(WRITE_CONTRACT_STORE_KEY) || {};
const savedContracts = localStore.saved || {};
const cId = (id !== undefined)
? id
: this.getId(savedContracts);
store.set(WRITE_CONTRACT_STORE_KEY, {
...localStore,
saved: {
...savedContracts,
[ cId ]: { sourcecode, id: cId, name, timestamp: Date.now() }
}
});
this.reloadContracts(cId);
}
@action reloadContracts = (id, sourcecode) => {
const localStore = store.get(WRITE_CONTRACT_STORE_KEY) || {};
this.savedContracts = localStore.saved || {};
const cId = id !== undefined ? id : localStore.currentId;
this.selectedContract = this.savedContracts[cId] || {};
this.sourcecode = sourcecode !== undefined
? sourcecode
: this.selectedContract.sourcecode || localStore.current || '';
store.set(WRITE_CONTRACT_STORE_KEY, {
...localStore,
currentId: this.selectedContract ? cId : null,
current: this.sourcecode
});
this.handleCompile();
this.resizeEditor();
}
@action handleLoadContract = (contract) => {
const { sourcecode, id } = contract;
this.reloadContracts(id, sourcecode);
}
@action handleDeleteContract = (id) => {
const localStore = store.get(WRITE_CONTRACT_STORE_KEY) || {};
const savedContracts = Object.assign({}, localStore.saved || {});
if (savedContracts[id]) {
delete savedContracts[id];
}
store.set(WRITE_CONTRACT_STORE_KEY, {
...localStore,
saved: savedContracts
});
this.reloadContracts();
}
@action handleNewContract = () => {
this.reloadContracts(-1, '');
}
@action resizeEditor = () => {
try {
this.editor.refs.brace.editor.resize();
} catch (e) {}
}
sendFilesToWorker = () => {
const files = [].concat(
Object.values(this.snippets),
Object.values(this.savedContracts)
);
this.compiler.postMessage(JSON.stringify({
action: 'setFiles',
data: files
}));
}
}

View File

@ -21,6 +21,7 @@ import Addresses from './Addresses';
import Application from './Application'; import Application from './Application';
import Contract from './Contract'; import Contract from './Contract';
import Contracts from './Contracts'; import Contracts from './Contracts';
import WriteContract from './WriteContract';
import Dapp from './Dapp'; import Dapp from './Dapp';
import Dapps from './Dapps'; import Dapps from './Dapps';
import ParityBar from './ParityBar'; import ParityBar from './ParityBar';
@ -36,6 +37,7 @@ export {
Application, Application,
Contract, Contract,
Contracts, Contracts,
WriteContract,
Dapp, Dapp,
Dapps, Dapps,
ParityBar, ParityBar,

View File

@ -48,7 +48,7 @@ mod tests {
"0x01" : "0x9a10c2b5bb8f3c602e674006d9b21f09167df57c87a78a5ce96d4159ecb76520" "0x01" : "0x9a10c2b5bb8f3c602e674006d9b21f09167df57c87a78a5ce96d4159ecb76520"
} }
}"#; }"#;
let _deserialized: Account= serde_json::from_str(s).unwrap(); let _deserialized: Account = serde_json::from_str(s).unwrap();
// TODO: validate all fields // TODO: validate all fields
} }
} }

View File

@ -16,6 +16,7 @@
//! Spec account deserialization. //! Spec account deserialization.
use std::collections::BTreeMap;
use uint::Uint; use uint::Uint;
use bytes::Bytes; use bytes::Bytes;
use spec::builtin::Builtin; use spec::builtin::Builtin;
@ -30,18 +31,21 @@ pub struct Account {
/// Nonce. /// Nonce.
pub nonce: Option<Uint>, pub nonce: Option<Uint>,
/// Code. /// Code.
pub code: Option<Bytes> pub code: Option<Bytes>,
/// Storage
pub storage: Option<BTreeMap<Uint, Uint>>,
} }
impl Account { impl Account {
/// Returns true if account does not have nonce and balance. /// Returns true if account does not have nonce and balance.
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.balance.is_none() && self.nonce.is_none() self.balance.is_none() && self.nonce.is_none() && self.code.is_none() && self.storage.is_none()
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::collections::BTreeMap;
use serde_json; use serde_json;
use spec::account::Account; use spec::account::Account;
use util::U256; use util::U256;
@ -62,4 +66,21 @@ mod tests {
assert_eq!(deserialized.code.unwrap(), Bytes::new(vec![0x12, 0x34])); assert_eq!(deserialized.code.unwrap(), Bytes::new(vec![0x12, 0x34]));
assert!(deserialized.builtin.is_some()); // Further tested in builtin.rs assert!(deserialized.builtin.is_some()); // Further tested in builtin.rs
} }
#[test]
fn account_storage_deserialization() {
let s = r#"{
"balance": "1",
"nonce": "0",
"code": "1234",
"storage": { "0x7fffffffffffffff7fffffffffffffff": "0x1" }
}"#;
let deserialized: Account = serde_json::from_str(s).unwrap();
assert_eq!(deserialized.balance.unwrap(), Uint(U256::from(1)));
assert_eq!(deserialized.nonce.unwrap(), Uint(U256::from(0)));
assert_eq!(deserialized.code.unwrap(), Bytes::new(vec![0x12, 0x34]));
let mut storage = BTreeMap::new();
storage.insert(Uint(U256::from("7fffffffffffffff7fffffffffffffff")), Uint(U256::from(1)));
assert_eq!(deserialized.storage.unwrap(), storage);
}
} }

View File

@ -15,9 +15,8 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
use std::{io, env}; use std::{io, env};
use std::io::{Write, Read, BufReader, BufRead}; use std::io::{Write, BufReader, BufRead};
use std::time::Duration; use std::time::Duration;
use std::path::Path;
use std::fs::File; use std::fs::File;
use util::{clean_0x, U256, Uint, Address, path, CompactionProfile}; use util::{clean_0x, U256, Uint, Address, path, CompactionProfile};
use util::journaldb::Algorithm; use util::journaldb::Algorithm;
@ -279,17 +278,18 @@ pub fn execute_upgrades(
/// Prompts user asking for password. /// Prompts user asking for password.
pub fn password_prompt() -> Result<String, String> { pub fn password_prompt() -> Result<String, String> {
use rpassword::read_password; use rpassword::read_password;
const STDIN_ERROR: &'static str = "Unable to ask for password on non-interactive terminal.";
println!("Please note that password is NOT RECOVERABLE."); println!("Please note that password is NOT RECOVERABLE.");
print!("Type password: "); print!("Type password: ");
flush_stdout(); flush_stdout();
let password = read_password().unwrap(); let password = try!(read_password().map_err(|_| STDIN_ERROR.to_owned()));
print!("Repeat password: "); print!("Repeat password: ");
flush_stdout(); flush_stdout();
let password_repeat = read_password().unwrap(); let password_repeat = try!(read_password().map_err(|_| STDIN_ERROR.to_owned()));
if password != password_repeat { if password != password_repeat {
return Err("Passwords do not match!".into()); return Err("Passwords do not match!".into());
@ -299,13 +299,11 @@ pub fn password_prompt() -> Result<String, String> {
} }
/// Read a password from password file. /// Read a password from password file.
pub fn password_from_file<P>(path: P) -> Result<String, String> where P: AsRef<Path> { pub fn password_from_file(path: String) -> Result<String, String> {
let mut file = try!(File::open(path).map_err(|_| "Unable to open password file.")); let passwords = try!(passwords_from_files(vec![path]));
let mut file_content = String::new(); // use only first password from the file
match file.read_to_string(&mut file_content) { passwords.get(0).map(String::to_owned)
Ok(_) => Ok(file_content.trim().into()), .ok_or_else(|| "Password file seems to be empty.".to_owned())
Err(_) => Err("Unable to read password file.".into()),
}
} }
/// Reads passwords from files. Treats each line as a separate password. /// Reads passwords from files. Treats each line as a separate password.
@ -314,10 +312,11 @@ pub fn passwords_from_files(files: Vec<String>) -> Result<Vec<String>, String> {
let file = try!(File::open(filename).map_err(|_| format!("{} Unable to read password file. Ensure it exists and permissions are correct.", filename))); let file = try!(File::open(filename).map_err(|_| format!("{} Unable to read password file. Ensure it exists and permissions are correct.", filename)));
let reader = BufReader::new(&file); let reader = BufReader::new(&file);
let lines = reader.lines() let lines = reader.lines()
.map(|l| l.unwrap()) .filter_map(|l| l.ok())
.map(|pwd| pwd.trim().to_owned())
.collect::<Vec<String>>(); .collect::<Vec<String>>();
Ok(lines) Ok(lines)
}).collect::<Result<Vec<Vec<String>>, String>>(); }).collect::<Result<Vec<Vec<String>>, String>>();
Ok(try!(passwords).into_iter().flat_map(|x| x).collect()) Ok(try!(passwords).into_iter().flat_map(|x| x).collect())
} }
@ -418,7 +417,20 @@ mod tests {
let path = RandomTempPath::new(); let path = RandomTempPath::new();
let mut file = File::create(path.as_path()).unwrap(); let mut file = File::create(path.as_path()).unwrap();
file.write_all(b"a bc ").unwrap(); file.write_all(b"a bc ").unwrap();
assert_eq!(password_from_file(path).unwrap().as_bytes(), b"a bc"); assert_eq!(password_from_file(path.as_str().into()).unwrap().as_bytes(), b"a bc");
}
#[test]
fn test_password_multiline() {
let path = RandomTempPath::new();
let mut file = File::create(path.as_path()).unwrap();
file.write_all(br#" password with trailing whitespace
those passwords should be
ignored
but the first password is trimmed
"#).unwrap();
assert_eq!(&password_from_file(path.as_str().into()).unwrap(), "password with trailing whitespace");
} }
#[test] #[test]

View File

@ -15,7 +15,6 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use std::thread;
use std::time; use std::time;
use std::sync::Arc; use std::sync::Arc;
use devtools::{http_client, RandomTempPath}; use devtools::{http_client, RandomTempPath};

View File

@ -73,7 +73,7 @@ pub trait SyncProvider: Send + Sync {
/// Get peers information /// Get peers information
fn peers(&self) -> Vec<PeerInfo>; fn peers(&self) -> Vec<PeerInfo>;
/// Get the enode if available. /// Get the enode if available.
fn enode(&self) -> Option<String>; fn enode(&self) -> Option<String>;
} }
@ -231,6 +231,7 @@ impl ChainNotify for EthSync {
} }
fn stop(&self) { fn stop(&self) {
self.handler.snapshot_service.abort_restore();
self.network.stop().unwrap_or_else(|e| warn!("Error stopping network: {:?}", e)); self.network.stop().unwrap_or_else(|e| warn!("Error stopping network: {:?}", e));
} }
} }

View File

@ -24,7 +24,7 @@
#![feature(asm)] #![feature(asm)]
extern crate test; extern crate test;
extern crate bigint; extern crate ethcore_bigint as bigint;
use test::{Bencher, black_box}; use test::{Bencher, black_box};
use bigint::uint::{U256, U512, Uint, U128}; use bigint::uint::{U256, U512, Uint, U128};

View File

@ -23,12 +23,13 @@
#![feature(test)] #![feature(test)]
extern crate test; extern crate test;
extern crate ethcore_util; extern crate rlp;
extern crate ethcore_bigint as bigint;
use test::Bencher; use test::Bencher;
use std::str::FromStr; use std::str::FromStr;
use ethcore_util::rlp::*; use rlp::*;
use ethcore_util::U256; use bigint::uint::U256;
#[bench] #[bench]
fn bench_stream_u64_value(b: &mut Bencher) { fn bench_stream_u64_value(b: &mut Bencher) {

View File

@ -17,10 +17,10 @@
//! Database of byte-slices keyed to their Keccak hash. //! Database of byte-slices keyed to their Keccak hash.
use hash::*; use hash::*;
use std::collections::HashMap; use std::collections::HashMap;
use elastic_array::ElasticArray256; use elastic_array::ElasticArray128;
/// `HashDB` value type. /// `HashDB` value type.
pub type DBValue = ElasticArray256<u8>; pub type DBValue = ElasticArray128<u8>;
/// Trait modelling datastore keyed by a 32-byte Keccak hash. /// Trait modelling datastore keyed by a 32-byte Keccak hash.
pub trait HashDB: AsHashDB + Send + Sync { pub trait HashDB: AsHashDB + Send + Sync {

View File

@ -464,6 +464,7 @@ impl Database {
try!(db.write_opt(batch, &self.write_opts)); try!(db.write_opt(batch, &self.write_opts));
for column in self.flushing.write().iter_mut() { for column in self.flushing.write().iter_mut() {
column.clear(); column.clear();
column.shrink_to_fit();
} }
Ok(()) Ok(())
}, },