diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 591807e24..1d152114c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -326,7 +326,7 @@ windows: - set INCLUDE=C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Include;C:\vs2015\VC\include;C:\Program Files (x86)\Windows Kits\10\Include\10.0.10240.0\ucrt - set LIB=C:\vs2015\VC\lib;C:\Program Files (x86)\Windows Kits\10\Lib\10.0.10240.0\ucrt\x64 - set RUST_BACKTRACE=1 - - set RUSTFLAGS=%RUSTFLAGS% -Zorbit=off + - set RUSTFLAGS=%RUSTFLAGS% - rustup default stable-x86_64-pc-windows-msvc - cargo build --release %CARGOFLAGS% - curl -sL --url "https://github.com/ethcore/win-build/raw/master/SimpleFC.dll" -o nsis\SimpleFC.dll diff --git a/.travis.yml b/.travis.yml index 80bf9ba10..6f3fd9933 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,7 +17,7 @@ matrix: include: - rust: stable env: RUN_TESTS="true" TEST_OPTIONS="--no-release" - - rust: beta + - rust: stable env: RUN_COVERAGE="true" - rust: stable env: RUN_DOCS="true" diff --git a/Cargo.lock b/Cargo.lock index b8e14e5fa..036deaa64 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1249,7 +1249,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#7bf550e482ef274480d1fd7e1ceb2efddd747dbd" +source = "git+https://github.com/ethcore/js-precompiled.git#b4df3bd592651729716ec38577410f7c1e767007" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1926,7 +1926,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "ws" version = "0.5.3" -source = "git+https://github.com/ethcore/ws-rs.git?branch=mio-upstream-stable#0cd6c5e3e9d5e61a37d53eb8dcbad523dcc69314" +source = "git+https://github.com/ethcore/ws-rs.git?branch=mio-upstream-stable#f5c0b35d660244d1b7500693c8cc28277ce1d418" dependencies = [ "bytes 0.4.0-dev (git+https://github.com/carllerche/bytes)", "httparse 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/ethcore/res/instant_seal.json b/ethcore/res/instant_seal.json index b7c29a01f..2d5b38659 100644 --- a/ethcore/res/instant_seal.json +++ b/ethcore/res/instant_seal.json @@ -1,5 +1,5 @@ { - "name": "TestInstantSeal", + "name": "DevelopmentChain", "engine": { "InstantSeal": null }, @@ -28,6 +28,6 @@ "0000000000000000000000000000000000000002": { "balance": "1", "nonce": "1048576", "builtin": { "name": "sha256", "pricing": { "linear": { "base": 60, "word": 12 } } } }, "0000000000000000000000000000000000000003": { "balance": "1", "nonce": "1048576", "builtin": { "name": "ripemd160", "pricing": { "linear": { "base": 600, "word": 120 } } } }, "0000000000000000000000000000000000000004": { "balance": "1", "nonce": "1048576", "builtin": { "name": "identity", "pricing": { "linear": { "base": 15, "word": 3 } } } }, - "102e61f5d8f9bc71d0ad4a084df4e65e05ce0e1c": { "balance": "1606938044258990275541962092341162602522202993782792835301376", "nonce": "1048576" } + "0x00a329c0648769a73afac7f9381e08fb43dbea72": { "balance": "1606938044258990275541962092341162602522202993782792835301376", "nonce": "1048576" } } } diff --git a/ethcore/src/engines/instant_seal.rs b/ethcore/src/engines/instant_seal.rs index acead19b4..75fae4d81 100644 --- a/ethcore/src/engines/instant_seal.rs +++ b/ethcore/src/engines/instant_seal.rs @@ -55,7 +55,7 @@ impl Engine for InstantSeal { } fn schedule(&self, _env_info: &EnvInfo) -> Schedule { - Schedule::new_homestead() + Schedule::new_post_eip150(false, false, false) } fn is_sealer(&self, _author: &Address) -> Option { Some(true) } @@ -79,7 +79,7 @@ mod tests { let tap = AccountProvider::transient_provider(); let addr = tap.insert_account("".sha3(), "").unwrap(); - let spec = Spec::new_test_instant(); + let spec = Spec::new_instant(); let engine = &*spec.engine; let genesis_header = spec.genesis_header(); let mut db_result = get_temp_state_db(); @@ -95,7 +95,7 @@ mod tests { #[test] fn instant_cant_verify() { - let engine = Spec::new_test_instant().engine; + let engine = Spec::new_instant().engine; let mut header: Header = Header::default(); assert!(engine.verify_block_basic(&header, None).is_ok()); diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 19a2b9a10..af4677cf3 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -1251,7 +1251,7 @@ mod tests { #[test] fn internal_seals_without_work() { - let miner = Miner::with_spec(&Spec::new_test_instant()); + let miner = Miner::with_spec(&Spec::new_instant()); let c = generate_dummy_client(2); let client = c.reference().as_ref(); diff --git a/ethcore/src/snapshot/account.rs b/ethcore/src/snapshot/account.rs index 38a4028e1..327979ce3 100644 --- a/ethcore/src/snapshot/account.rs +++ b/ethcore/src/snapshot/account.rs @@ -19,11 +19,11 @@ use account_db::{AccountDB, AccountDBMut}; use snapshot::Error; -use util::{U256, FixedHash, H256, Bytes, HashDB, DBValue, SHA3_EMPTY, SHA3_NULL_RLP}; +use util::{U256, FixedHash, H256, Bytes, HashDB, SHA3_EMPTY, SHA3_NULL_RLP}; use util::trie::{TrieDB, Trie}; use rlp::{Rlp, RlpStream, Stream, UntrustedRlp, View}; -use std::collections::{HashMap, HashSet}; +use std::collections::HashSet; // An empty account -- these are replaced with RLP null data for a space optimization. const ACC_EMPTY: Account = Account { @@ -150,7 +150,6 @@ impl Account { pub fn from_fat_rlp( acct_db: &mut AccountDBMut, rlp: UntrustedRlp, - code_map: &HashMap, ) -> Result<(Self, Option), Error> { use util::{TrieDBMut, TrieMut}; @@ -177,9 +176,6 @@ impl Account { } CodeState::Hash => { let code_hash = try!(rlp.val_at(3)); - if let Some(code) = code_map.get(&code_hash) { - acct_db.emplace(code_hash.clone(), DBValue::from_slice(code)); - } (code_hash, None) } @@ -229,7 +225,7 @@ mod tests { use util::{Address, FixedHash, H256, HashDB, DBValue}; use rlp::{UntrustedRlp, View}; - use std::collections::{HashSet, HashMap}; + use std::collections::HashSet; use super::{ACC_EMPTY, Account}; @@ -250,7 +246,7 @@ mod tests { let fat_rlp = account.to_fat_rlp(&AccountDB::new(db.as_hashdb(), &addr), &mut Default::default()).unwrap(); let fat_rlp = UntrustedRlp::new(&fat_rlp); - assert_eq!(Account::from_fat_rlp(&mut AccountDBMut::new(db.as_hashdb_mut(), &addr), fat_rlp, &Default::default()).unwrap().0, account); + assert_eq!(Account::from_fat_rlp(&mut AccountDBMut::new(db.as_hashdb_mut(), &addr), fat_rlp).unwrap().0, account); } #[test] @@ -275,7 +271,7 @@ mod tests { let fat_rlp = account.to_fat_rlp(&AccountDB::new(db.as_hashdb(), &addr), &mut Default::default()).unwrap(); let fat_rlp = UntrustedRlp::new(&fat_rlp); - assert_eq!(Account::from_fat_rlp(&mut AccountDBMut::new(db.as_hashdb_mut(), &addr), fat_rlp, &Default::default()).unwrap().0, account); + assert_eq!(Account::from_fat_rlp(&mut AccountDBMut::new(db.as_hashdb_mut(), &addr), fat_rlp).unwrap().0, account); } #[test] @@ -318,12 +314,11 @@ mod tests { let fat_rlp1 = UntrustedRlp::new(&fat_rlp1); let fat_rlp2 = UntrustedRlp::new(&fat_rlp2); - let code_map = HashMap::new(); - let (acc, maybe_code) = Account::from_fat_rlp(&mut AccountDBMut::new(db.as_hashdb_mut(), &addr2), fat_rlp2, &code_map).unwrap(); + let (acc, maybe_code) = Account::from_fat_rlp(&mut AccountDBMut::new(db.as_hashdb_mut(), &addr2), fat_rlp2).unwrap(); assert!(maybe_code.is_none()); assert_eq!(acc, account2); - let (acc, maybe_code) = Account::from_fat_rlp(&mut AccountDBMut::new(db.as_hashdb_mut(), &addr1), fat_rlp1, &code_map).unwrap(); + let (acc, maybe_code) = Account::from_fat_rlp(&mut AccountDBMut::new(db.as_hashdb_mut(), &addr1), fat_rlp1).unwrap(); assert_eq!(maybe_code, Some(b"this is definitely code".to_vec())); assert_eq!(acc, account1); } @@ -332,9 +327,8 @@ mod tests { fn encoding_empty_acc() { let mut db = get_temp_state_db(); let mut used_code = HashSet::new(); - let code_map = HashMap::new(); assert_eq!(ACC_EMPTY.to_fat_rlp(&AccountDB::new(db.as_hashdb(), &Address::default()), &mut used_code).unwrap(), ::rlp::NULL_RLP.to_vec()); - assert_eq!(Account::from_fat_rlp(&mut AccountDBMut::new(db.as_hashdb_mut(), &Address::default()), UntrustedRlp::new(&::rlp::NULL_RLP), &code_map).unwrap(), (ACC_EMPTY, None)); + assert_eq!(Account::from_fat_rlp(&mut AccountDBMut::new(db.as_hashdb_mut(), &Address::default()), UntrustedRlp::new(&::rlp::NULL_RLP)).unwrap(), (ACC_EMPTY, None)); } } diff --git a/ethcore/src/snapshot/mod.rs b/ethcore/src/snapshot/mod.rs index f4d791593..3f63ac208 100644 --- a/ethcore/src/snapshot/mod.rs +++ b/ethcore/src/snapshot/mod.rs @@ -389,7 +389,7 @@ pub fn chunk_state<'a>(db: &HashDB, root: &H256, writer: &Mutex, state_root: H256, - code_map: HashMap, // maps code hashes to code itself. + known_code: HashMap, // code hashes mapped to first account with this code. missing_code: HashMap>, // maps code hashes to lists of accounts missing that code. bloom: Bloom, } @@ -400,7 +400,7 @@ impl StateRebuilder { StateRebuilder { db: journaldb::new(db.clone(), pruning, ::db::COL_STATE), state_root: SHA3_NULL_RLP, - code_map: HashMap::new(), + known_code: HashMap::new(), missing_code: HashMap::new(), bloom: StateDB::load_bloom(&*db), } @@ -419,7 +419,7 @@ impl StateRebuilder { self.db.as_hashdb_mut(), rlp, &mut pairs, - &self.code_map, + &self.known_code, flag )); @@ -428,13 +428,13 @@ impl StateRebuilder { } // patch up all missing code. must be done after collecting all new missing code entries. - for (code_hash, code) in status.new_code { + for (code_hash, code, first_with) in status.new_code { 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); db.emplace(code_hash, DBValue::from_slice(&code)); } - self.code_map.insert(code_hash, code); + self.known_code.insert(code_hash, first_with); } let backing = self.db.backing().clone(); @@ -482,7 +482,8 @@ impl StateRebuilder { #[derive(Default)] struct RebuiltStatus { - new_code: Vec<(H256, Bytes)>, // new code that's become available. + // new code that's become available. (code_hash, code, addr_hash) + new_code: Vec<(H256, Bytes, H256)>, missing_code: Vec<(H256, H256)>, // accounts that are missing code. } @@ -492,10 +493,9 @@ fn rebuild_accounts( db: &mut HashDB, account_fat_rlps: UntrustedRlp, out_chunk: &mut [(H256, Bytes)], - code_map: &HashMap, - abort_flag: &AtomicBool -) -> Result -{ + known_code: &HashMap, + abort_flag: &AtomicBool, +) -> Result { let mut status = RebuiltStatus::default(); for (account_rlp, out) in account_fat_rlps.into_iter().zip(out_chunk) { if !abort_flag.load(Ordering::SeqCst) { return Err(Error::RestorationAborted.into()) } @@ -504,17 +504,33 @@ fn rebuild_accounts( let fat_rlp = try!(account_rlp.at(1)); let thin_rlp = { - let mut acct_db = AccountDBMut::from_hash(db, hash); // fill out the storage trie and code while decoding. - let (acc, maybe_code) = try!(Account::from_fat_rlp(&mut acct_db, fat_rlp, code_map)); + let (acc, maybe_code) = { + let mut acct_db = AccountDBMut::from_hash(db, hash); + try!(Account::from_fat_rlp(&mut acct_db, fat_rlp)) + }; let code_hash = acc.code_hash().clone(); match maybe_code { - Some(code) => status.new_code.push((code_hash, code)), + // new inline code + Some(code) => status.new_code.push((code_hash, code, hash)), None => { - if code_hash != ::util::SHA3_EMPTY && !code_map.contains_key(&code_hash) { - status.missing_code.push((hash, code_hash)); + if code_hash != ::util::SHA3_EMPTY { + // see if this code has already been included inline + match known_code.get(&code_hash) { + Some(&first_with) => { + // if so, load it from the database. + let code = try!(AccountDB::from_hash(db, first_with) + .get(&code_hash) + .ok_or_else(|| Error::MissingCode(vec![first_with]))); + + // and write it again under a different mangled key + AccountDBMut::from_hash(db, hash).emplace(code_hash, code); + } + // if not, queue it up to be filled later + None => status.missing_code.push((hash, code_hash)), + } } } } diff --git a/ethcore/src/snapshot/tests/state.rs b/ethcore/src/snapshot/tests/state.rs index 05537fa96..36c268f73 100644 --- a/ethcore/src/snapshot/tests/state.rs +++ b/ethcore/src/snapshot/tests/state.rs @@ -17,6 +17,7 @@ //! State snapshotting tests. use snapshot::{chunk_state, Error as SnapshotError, Progress, StateRebuilder}; +use snapshot::account::Account; use snapshot::io::{PackedReader, PackedWriter, SnapshotReader, SnapshotWriter}; use super::helpers::{compare_dbs, StateProducer}; @@ -30,6 +31,8 @@ use util::memorydb::MemoryDB; use util::Mutex; use devtools::RandomTempPath; +use util::sha3::SHA3_NULL_RLP; + use std::sync::Arc; use std::sync::atomic::AtomicBool; @@ -88,6 +91,58 @@ fn snap_and_restore() { compare_dbs(&old_db, new_db.as_hashdb()); } +#[test] +fn get_code_from_prev_chunk() { + use std::collections::HashSet; + use rlp::{RlpStream, Stream}; + use util::{HashDB, H256, FixedHash, U256, Hashable}; + + use account_db::{AccountDBMut, AccountDB}; + + let code = b"this is definitely code"; + let mut used_code = HashSet::new(); + let mut acc_stream = RlpStream::new_list(4); + acc_stream.append(&U256::default()) + .append(&U256::default()) + .append(&SHA3_NULL_RLP) + .append(&code.sha3()); + + let (h1, h2) = (H256::random(), H256::random()); + + // two accounts with the same code, one per chunk. + // first one will have code inlined, + // second will just have its hash. + let thin_rlp = acc_stream.out(); + let acc1 = Account::from_thin_rlp(&thin_rlp); + let acc2 = Account::from_thin_rlp(&thin_rlp); + + let mut make_chunk = |acc: Account, hash| { + let mut db = MemoryDB::new(); + AccountDBMut::from_hash(&mut db, hash).insert(&code[..]); + + let fat_rlp = acc.to_fat_rlp(&AccountDB::from_hash(&db, hash), &mut used_code).unwrap(); + + let mut stream = RlpStream::new_list(1); + stream.begin_list(2).append(&hash).append_raw(&fat_rlp, 1); + stream.out() + }; + + let chunk1 = make_chunk(acc1, h1); + let chunk2 = make_chunk(acc2, h2); + + let db_path = RandomTempPath::create_dir(); + let db_cfg = DatabaseConfig::with_columns(::db::NUM_COLUMNS); + let new_db = Arc::new(Database::open(&db_cfg, &db_path.to_string_lossy()).unwrap()); + + let mut rebuilder = StateRebuilder::new(new_db, Algorithm::Archive); + let flag = AtomicBool::new(true); + + rebuilder.feed(&chunk1, &flag).unwrap(); + rebuilder.feed(&chunk2, &flag).unwrap(); + + rebuilder.check_missing().unwrap(); +} + #[test] fn checks_flag() { let mut producer = StateProducer::new(); diff --git a/ethcore/src/spec/spec.rs b/ethcore/src/spec/spec.rs index 6bfb1bcc1..de1b7db42 100644 --- a/ethcore/src/spec/spec.rs +++ b/ethcore/src/spec/spec.rs @@ -135,6 +135,12 @@ impl From for Spec { } } +macro_rules! load_bundled { + ($e:expr) => { + Spec::load(include_bytes!(concat!("../../res/", $e, ".json")) as &[u8]).expect(concat!("Chain spec ", $e, " is invalid.")) + }; +} + impl Spec { /// Convert engine spec into a arc'd Engine of the right underlying type. /// TODO avoid this hard-coded nastiness - use dynamic-linked plugin framework instead. @@ -267,19 +273,13 @@ impl Spec { } /// Create a new Spec which conforms to the Frontier-era Morden chain except that it's a NullEngine consensus. - pub fn new_test() -> Self { - Spec::load(include_bytes!("../../res/null_morden.json") as &[u8]).expect("null_morden.json is invalid") - } + pub fn new_test() -> Spec { load_bundled!("null_morden") } /// Create a new Spec which is a NullEngine consensus with a premine of address whose secret is sha3(''). - pub fn new_null() -> Self { - Spec::load(include_bytes!("../../res/null.json") as &[u8]).expect("null.json is invalid") - } + pub fn new_null() -> Spec { load_bundled!("null") } /// Create a new Spec with InstantSeal consensus which does internal sealing (not requiring work). - pub fn new_test_instant() -> Self { - Spec::load(include_bytes!("../../res/instant_seal.json") as &[u8]).expect("instant_seal.json is invalid") - } + pub fn new_instant() -> Spec { load_bundled!("instant_seal") } } #[cfg(test)] diff --git a/js/package.json b/js/package.json index 2556a5f80..770e99695 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.38", + "version": "0.2.46", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", diff --git a/js/src/abi/util/pad.js b/js/src/abi/util/pad.js index a7d940431..5a6751a4f 100644 --- a/js/src/abi/util/pad.js +++ b/js/src/abi/util/pad.js @@ -46,7 +46,8 @@ function stringToBytes (input) { if (isArray(input)) { return input; } else if (input.substr(0, 2) === '0x') { - return input.substr(2).toLowerCase().match(/.{1,2}/g).map((value) => parseInt(value, 16)); + const matches = input.substr(2).toLowerCase().match(/.{1,2}/g) || []; + return matches.map((value) => parseInt(value, 16)); } else { return input.split('').map((char) => char.charCodeAt(0)); } diff --git a/js/src/api/contract/contract.js b/js/src/api/contract/contract.js index 8d556a118..06afb0d9d 100644 --- a/js/src/api/contract/contract.js +++ b/js/src/api/contract/contract.js @@ -40,9 +40,12 @@ export default class Contract { this._events.forEach((evt) => { this._instance[evt.name] = evt; + this._instance[evt.signature] = evt; }); + this._functions.forEach((fn) => { this._instance[fn.name] = fn; + this._instance[fn.signature] = fn; }); this._sendSubscriptionChanges(); diff --git a/js/src/api/contract/contract.spec.js b/js/src/api/contract/contract.spec.js index 3d57c2afa..9c08024a9 100644 --- a/js/src/api/contract/contract.spec.js +++ b/js/src/api/contract/contract.spec.js @@ -20,6 +20,7 @@ import sinon from 'sinon'; import { TEST_HTTP_URL, mockHttp } from '../../../test/mockRpc'; import Abi from '../../abi'; +import { sha3 } from '../util/sha3'; import Api from '../api'; import Contract from './contract'; @@ -113,7 +114,13 @@ describe('api/contract/Contract', () => { ]); contract.at('6789'); - expect(Object.keys(contract.instance)).to.deep.equal(['Drained', 'balanceOf', 'address']); + expect(Object.keys(contract.instance)).to.deep.equal([ + 'Drained', + /^(?:0x)(.+)$/.exec(sha3('Drained(uint256)'))[1], + 'balanceOf', + /^(?:0x)(.+)$/.exec(sha3('balanceOf(address)'))[1].substr(0, 8), + 'address' + ]); expect(contract.address).to.equal('6789'); }); }); diff --git a/js/src/dapps/basiccoin/AddressSelect/addressSelect.css b/js/src/dapps/basiccoin/AddressSelect/addressSelect.css index ddfd334e8..818906708 100644 --- a/js/src/dapps/basiccoin/AddressSelect/addressSelect.css +++ b/js/src/dapps/basiccoin/AddressSelect/addressSelect.css @@ -21,3 +21,7 @@ .iconMenu option { padding-left: 30px; } + +.menu { + display: none; +} diff --git a/js/src/dapps/tokenreg/Actions/Register/register.js b/js/src/dapps/tokenreg/Actions/Register/register.js index 8ae042494..a008ce84f 100644 --- a/js/src/dapps/tokenreg/Actions/Register/register.js +++ b/js/src/dapps/tokenreg/Actions/Register/register.js @@ -21,7 +21,7 @@ import { Dialog, FlatButton } from 'material-ui'; import AccountSelector from '../../Accounts/AccountSelector'; import InputText from '../../Inputs/Text'; -import { TOKEN_ADDRESS_TYPE, TLA_TYPE, UINT_TYPE, STRING_TYPE } from '../../Inputs/validation'; +import { TOKEN_ADDRESS_TYPE, TLA_TYPE, DECIMAL_TYPE, STRING_TYPE } from '../../Inputs/validation'; import styles from '../actions.css'; @@ -41,11 +41,11 @@ const initState = { floatingLabelText: 'Token TLA', hintText: 'The token short name (3 characters)' }, - base: { + decimals: { ...defaultField, - type: UINT_TYPE, - floatingLabelText: 'Token Base', - hintText: 'The token precision' + type: DECIMAL_TYPE, + floatingLabelText: 'Token Decimals', + hintText: 'The number of decimals (0-18)' }, name: { ...defaultField, diff --git a/js/src/dapps/tokenreg/Actions/actions.js b/js/src/dapps/tokenreg/Actions/actions.js index 0f3390ea4..1c6703f77 100644 --- a/js/src/dapps/tokenreg/Actions/actions.js +++ b/js/src/dapps/tokenreg/Actions/actions.js @@ -47,7 +47,8 @@ export const registerToken = (tokenData) => (dispatch, getState) => { const contractInstance = state.status.contract.instance; const fee = state.status.contract.fee; - const { address, base, name, tla } = tokenData; + const { address, decimals, name, tla } = tokenData; + const base = Math.pow(10, decimals); dispatch(setRegisterSending(true)); diff --git a/js/src/dapps/tokenreg/Inputs/validation.js b/js/src/dapps/tokenreg/Inputs/validation.js index 38eba5ef1..fd394b4ec 100644 --- a/js/src/dapps/tokenreg/Inputs/validation.js +++ b/js/src/dapps/tokenreg/Inputs/validation.js @@ -32,6 +32,7 @@ export const SIMPLE_TOKEN_ADDRESS_TYPE = 'SIMPLE_TOKEN_ADDRESS_TYPE'; export const TLA_TYPE = 'TLA_TYPE'; export const SIMPLE_TLA_TYPE = 'SIMPLE_TLA_TYPE'; export const UINT_TYPE = 'UINT_TYPE'; +export const DECIMAL_TYPE = 'DECIMAL_TYPE'; export const STRING_TYPE = 'STRING_TYPE'; export const HEX_TYPE = 'HEX_TYPE'; export const URL_TYPE = 'URL_TYPE'; @@ -39,6 +40,7 @@ export const URL_TYPE = 'URL_TYPE'; export const ERRORS = { invalidTLA: 'The TLA should be 3 characters long', invalidUint: 'Please enter a non-negative integer', + invalidDecimal: 'Please enter a value between 0 and 18', invalidString: 'Please enter at least a character', invalidAccount: 'Please select an account to transact with', invalidRecipient: 'Please select an account to send to', @@ -152,6 +154,21 @@ const validateUint = (uint) => { }; }; +const validateDecimal = (decimal) => { + if (!/^\d+$/.test(decimal) || parseInt(decimal) < 0 || parseInt(decimal) > 18) { + return { + error: ERRORS.invalidDecimal, + valid: false + }; + } + + return { + value: parseInt(decimal), + error: null, + valid: true + }; +}; + const validateString = (string) => { if (string.toString().length === 0) { return { @@ -204,6 +221,7 @@ export const validate = (value, type, contract) => { if (type === TLA_TYPE) return validateTLA(value, contract); if (type === SIMPLE_TLA_TYPE) return validateTLA(value, contract, true); if (type === UINT_TYPE) return validateUint(value); + if (type === DECIMAL_TYPE) return validateDecimal(value); if (type === STRING_TYPE) return validateString(value); if (type === HEX_TYPE) return validateHex(value); if (type === URL_TYPE) return validateURL(value); diff --git a/js/src/dapps/tokenreg/Status/reducer.js b/js/src/dapps/tokenreg/Status/reducer.js index 7b018bd85..357cb2244 100644 --- a/js/src/dapps/tokenreg/Status/reducer.js +++ b/js/src/dapps/tokenreg/Status/reducer.js @@ -25,7 +25,7 @@ const initialState = { isLoading: true, subscriptionId: null, contract: { - addres: null, + address: null, instance: null, raw: null, owner: null, diff --git a/js/src/dapps/tokenreg/Tokens/Token/token.js b/js/src/dapps/tokenreg/Tokens/Token/token.js index 34550ef48..660b21b97 100644 --- a/js/src/dapps/tokenreg/Tokens/Token/token.js +++ b/js/src/dapps/tokenreg/Tokens/Token/token.js @@ -152,8 +152,8 @@ export default class Token extends Component { if (!base || base < 0) return null; return ( + value={ Math.log10(base).toString() } + label='Decimals' /> ); } diff --git a/js/src/dapps/tokenreg/Tokens/actions.js b/js/src/dapps/tokenreg/Tokens/actions.js index 7953bb241..bd4413163 100644 --- a/js/src/dapps/tokenreg/Tokens/actions.js +++ b/js/src/dapps/tokenreg/Tokens/actions.js @@ -239,22 +239,22 @@ export const addGithubhintURL = (from, key, url) => (dispatch, getState) => { export const unregisterToken = (index) => (dispatch, getState) => { console.log('unregistering token', index); - const state = getState(); - const contractInstance = state.status.contract.instance; + const { contract } = getState().status; + const { instance, owner } = contract; const values = [ index ]; const options = { - from: state.accounts.selected.address + from: owner }; - contractInstance + instance .unregister .estimateGas(options, values) .then((gasEstimate) => { options.gas = gasEstimate.mul(1.2).toFixed(0); console.log(`transfer: gas estimated as ${gasEstimate.toFixed(0)} setting to ${options.gas}`); - return contractInstance.unregister.postTransaction(options, values); + return instance.unregister.postTransaction(options, values); }) .catch((e) => { console.error(`unregisterToken #${index} error`, e); diff --git a/js/src/modals/DeployContract/DetailsStep/detailsStep.js b/js/src/modals/DeployContract/DetailsStep/detailsStep.js index 854715396..6e23f79c9 100644 --- a/js/src/modals/DeployContract/DetailsStep/detailsStep.js +++ b/js/src/modals/DeployContract/DetailsStep/detailsStep.js @@ -15,10 +15,10 @@ // along with Parity. If not, see . import React, { Component, PropTypes } from 'react'; -import { MenuItem } from 'material-ui'; -import { AddressSelect, Form, Input, InputAddressSelect, Select } from '../../../ui'; +import { AddressSelect, Form, Input, TypedInput } from '../../../ui'; import { validateAbi } from '../../../util/validation'; +import { parseAbiType } from '../../../util/abi'; import styles from '../deployContract.css'; @@ -103,6 +103,7 @@ export default class DetailsStep extends Component { value={ code } onSubmit={ this.onCodeChange } readOnly={ readOnly } /> + { this.renderConstructorInputs() } ); @@ -117,59 +118,23 @@ export default class DetailsStep extends Component { } return inputs.map((input, index) => { - const onChange = (event, value) => this.onParamChange(index, value); - const onChangeBool = (event, _index, value) => this.onParamChange(index, value === 'true'); - const onSubmit = (value) => this.onParamChange(index, value); - const label = `${input.name}: ${input.type}`; - let inputBox = null; + const onChange = (value) => this.onParamChange(index, value); - switch (input.type) { - case 'address': - inputBox = ( - - ); - break; - - case 'bool': - const boolitems = ['false', 'true'].map((bool) => { - return ( - { bool } - ); - }); - inputBox = ( - - ); - break; - - default: - inputBox = ( - - ); - break; - } + const label = `${input.name ? `${input.name}: ` : ''}${input.type}`; + const value = params[index]; + const error = paramsError[index]; + const param = parseAbiType(input.type); return (
- { inputBox } +
); }); @@ -200,35 +165,14 @@ export default class DetailsStep extends Component { const { abiError, abiParsed } = validateAbi(abi, api); if (!abiError) { - const { inputs } = abiParsed.find((method) => method.type === 'constructor') || { inputs: [] }; + const { inputs } = abiParsed + .find((method) => method.type === 'constructor') || { inputs: [] }; + const params = []; inputs.forEach((input) => { - switch (input.type) { - case 'address': - params.push('0x'); - break; - - case 'bool': - params.push(false); - break; - - case 'bytes': - params.push('0x'); - break; - - case 'uint': - params.push('0'); - break; - - case 'string': - params.push(''); - break; - - default: - params.push('0'); - break; - } + const param = parseAbiType(input.type); + params.push(param.default); }); onParamsChange(params); diff --git a/js/src/modals/DeployContract/deployContract.js b/js/src/modals/DeployContract/deployContract.js index 768723d1f..a99b49412 100644 --- a/js/src/modals/DeployContract/deployContract.js +++ b/js/src/modals/DeployContract/deployContract.js @@ -101,7 +101,8 @@ export default class DeployContract extends Component { steps={ deployError ? null : steps } title={ deployError ? 'deployment failed' : null } waiting={ [1] } - visible> + visible + scroll> { this.renderStep() } ); @@ -118,8 +119,22 @@ export default class DeployContract extends Component { onClick={ this.onClose } /> ); + const closeBtn = ( +