diff --git a/ethcore/src/json_tests/transaction.rs b/ethcore/src/json_tests/transaction.rs index 51888413d..f6d6291e6 100644 --- a/ethcore/src/json_tests/transaction.rs +++ b/ethcore/src/json_tests/transaction.rs @@ -16,75 +16,54 @@ use super::test_common::*; use evm; +use ethjson; fn do_json_test(json_data: &[u8]) -> Vec { - let json = Json::from_str(::std::str::from_utf8(json_data).unwrap()).expect("Json is invalid"); + let tests = ethjson::transaction::Test::load(json_data).unwrap(); let mut failed = Vec::new(); let old_schedule = evm::Schedule::new_frontier(); let new_schedule = evm::Schedule::new_homestead(); - let ot = RefCell::new(None); - for (name, test) in json.as_object().unwrap() { + for (name, test) in tests.into_iter() { let mut fail = false; - let mut fail_unless = |cond: bool| if !cond && !fail { failed.push(name.clone()); println!("Transaction: {:?}", ot.borrow()); fail = true }; - let schedule = match test.find("blocknumber") - .and_then(|j| j.as_string()) - .and_then(|s| BlockNumber::from_str(s).ok()) - .unwrap_or(0) { x if x < 1_000_000 => &old_schedule, _ => &new_schedule }; - let rlp = Bytes::from_json(&test["rlp"]); - let res = UntrustedRlp::new(&rlp).as_val().map_err(From::from).and_then(|t: SignedTransaction| t.validate(schedule, schedule.have_delegate_call)); - fail_unless(test.find("transaction").is_none() == res.is_err()); - if let (Some(&Json::Object(ref tx)), Some(&Json::String(ref expect_sender))) = (test.find("transaction"), test.find("sender")) { + let mut fail_unless = |cond: bool| if !cond && !fail { failed.push(name.clone()); println!("Transaction failed: {:?}", name); fail = true }; + + let number: Option = test.block_number.map(Into::into); + let schedule = match number { + None => &old_schedule, + Some(x) if x < 1_000_000 => &old_schedule, + Some(_) => &new_schedule + }; + + let rlp: Vec = test.rlp.into(); + let res = UntrustedRlp::new(&rlp) + .as_val() + .map_err(From::from) + .and_then(|t: SignedTransaction| t.validate(schedule, schedule.have_delegate_call)); + + fail_unless(test.transaction.is_none() == res.is_err()); + if let (Some(tx), Some(sender)) = (test.transaction, test.sender) { let t = res.unwrap(); - fail_unless(t.sender().unwrap() == address_from_hex(clean(expect_sender))); - fail_unless(t.data == Bytes::from_json(&tx["data"])); - fail_unless(t.gas == xjson!(&tx["gasLimit"])); - fail_unless(t.gas_price == xjson!(&tx["gasPrice"])); - fail_unless(t.nonce == xjson!(&tx["nonce"])); - fail_unless(t.value == xjson!(&tx["value"])); - if let Action::Call(ref to) = t.action { - *ot.borrow_mut() = Some(t.clone()); - fail_unless(to == &xjson!(&tx["to"])); - } else { - *ot.borrow_mut() = Some(t.clone()); - fail_unless(Bytes::from_json(&tx["to"]).is_empty()); + fail_unless(t.sender().unwrap() == sender.into()); + let data: Vec = tx.data.into(); + fail_unless(t.data == data); + fail_unless(t.gas_price == tx.gas_price.into()); + fail_unless(t.nonce == tx.nonce.into()); + fail_unless(t.value == tx.value.into()); + let to: Option<_> = tx.to.into(); + let to: Option
= to.map(Into::into); + match t.action { + Action::Call(dest) => fail_unless(Some(dest) == to), + Action::Create => fail_unless(None == to), } } } + for f in &failed { println!("FAILED: {:?}", f); } failed } -// Once we have interpolate idents. -/*macro_rules! declare_test { - ($test_set_name: ident / $name: ident) => { - #[test] - #[allow(non_snake_case)] - fn $name() { - assert!(do_json_test(include_bytes!(concat!("../res/ethereum/tests/", stringify!($test_set_name), "/", stringify!($name), ".json"))).len() == 0); - } - }; - ($test_set_name: ident / $prename: ident / $name: ident) => { - #[test] - #[allow(non_snake_case)] - interpolate_idents! { fn [$prename _ $name]() - { - let json = include_bytes!(concat!("../res/ethereum/tests/", stringify!($test_set_name), "/", stringify!($prename), "/", stringify!($name), ".json")); - assert!(do_json_test(json).len() == 0); - } - } - }; -} - -declare_test!{TransactionTests/ttTransactionTest} -declare_test!{TransactionTests/tt10mbDataField} -declare_test!{TransactionTests/ttWrongRLPTransaction} -declare_test!{TransactionTests/Homestead/ttTransactionTest} -declare_test!{heavy => TransactionTests/Homestead/tt10mbDataField} -declare_test!{TransactionTests/Homestead/ttWrongRLPTransaction} -declare_test!{TransactionTests/RandomTests/tr201506052141PYTHON}*/ - declare_test!{TransactionTests_ttTransactionTest, "TransactionTests/ttTransactionTest"} declare_test!{heavy => TransactionTests_tt10mbDataField, "TransactionTests/tt10mbDataField"} declare_test!{TransactionTests_ttWrongRLPTransaction, "TransactionTests/ttWrongRLPTransaction"} diff --git a/ethcore/src/transaction.rs b/ethcore/src/transaction.rs index db110f8c8..ab43743d3 100644 --- a/ethcore/src/transaction.rs +++ b/ethcore/src/transaction.rs @@ -97,37 +97,61 @@ impl From for SignedTransaction { } } -impl FromJson for SignedTransaction { - #[cfg_attr(feature="dev", allow(single_char_pattern))] - fn from_json(json: &Json) -> SignedTransaction { - let t = Transaction { - nonce: xjson!(&json["nonce"]), - gas_price: xjson!(&json["gasPrice"]), - gas: xjson!(&json["gasLimit"]), - action: match Bytes::from_json(&json["to"]) { - ref x if x.is_empty() => Action::Create, - ref x => Action::Call(Address::from_slice(x)), +impl From for SignedTransaction { + fn from(t: ethjson::transaction::Transaction) -> Self { + let to: Option<_> = t.to.into(); + SignedTransaction { + unsigned: Transaction { + nonce: t.nonce.into(), + gas_price: t.gas_price.into(), + gas: t.gas_limit.into(), + action: match to { + Some(to) => Action::Call(to.into()), + None => Action::Create + }, + value: t.value.into(), + data: t.data.into(), }, - value: xjson!(&json["value"]), - data: xjson!(&json["data"]), - }; - match json.find("secretKey") { - Some(&Json::String(ref secret_key)) => t.sign(&h256_from_hex(clean(secret_key))), - _ => SignedTransaction { - unsigned: t, - v: match json.find("v") { Some(ref j) => u16::from_json(j) as u8, None => 0 }, - r: match json.find("r") { Some(j) => xjson!(j), None => x!(0) }, - s: match json.find("s") { Some(j) => xjson!(j), None => x!(0) }, - hash: Cell::new(None), - sender: match json.find("sender") { - Some(&Json::String(ref sender)) => Cell::new(Some(address_from_hex(clean(sender)))), - _ => Cell::new(None), - } - } + r: t.r.into(), + s: t.s.into(), + v: t.v.into(), + sender: Cell::new(None), + hash: Cell::new(None) } } } +//impl FromJson for SignedTransaction { + //#[cfg_attr(feature="dev", allow(single_char_pattern))] + //fn from_json(json: &Json) -> SignedTransaction { + //let t = Transaction { + //nonce: xjson!(&json["nonce"]), + //gas_price: xjson!(&json["gasPrice"]), + //gas: xjson!(&json["gasLimit"]), + //action: match Bytes::from_json(&json["to"]) { + //ref x if x.is_empty() => Action::Create, + //ref x => Action::Call(Address::from_slice(x)), + //}, + //value: xjson!(&json["value"]), + //data: xjson!(&json["data"]), + //}; + //match json.find("secretKey") { + //Some(&Json::String(ref secret_key)) => t.sign(&h256_from_hex(clean(secret_key))), + //_ => SignedTransaction { + //unsigned: t, + //v: match json.find("v") { Some(ref j) => u16::from_json(j) as u8, None => 0 }, + //r: match json.find("r") { Some(j) => xjson!(j), None => x!(0) }, + //s: match json.find("s") { Some(j) => xjson!(j), None => x!(0) }, + //hash: Cell::new(None), + //sender: match json.find("sender") { + //Some(&Json::String(ref sender)) => Cell::new(Some(address_from_hex(clean(sender)))), + //_ => Cell::new(None), + //} + //} + //} + //} +//} + impl Transaction { /// The message hash of the transaction. pub fn hash(&self) -> H256 { diff --git a/json/src/bytes.rs b/json/src/bytes.rs index 6ccae51d7..812b109f7 100644 --- a/json/src/bytes.rs +++ b/json/src/bytes.rs @@ -46,6 +46,10 @@ impl Visitor for BytesVisitor { let v = match value.len() { 0 => vec![], 2 if value.starts_with("0x") => vec![], + _ if value.starts_with("0x") && value.len() % 2 == 1 => { + let v = "0".to_owned() + &value[2..]; + FromHex::from_hex(v.as_ref() as &str).unwrap_or(vec![]), + }, _ if value.starts_with("0x") => FromHex::from_hex(&value[2..]).unwrap_or(vec![]), _ => FromHex::from_hex(value).unwrap_or(vec![]), }; @@ -64,13 +68,14 @@ mod test { #[test] fn bytes_deserialization() { - let s = r#"["", "0x", "0x12", "1234"]"#; + let s = r#"["", "0x", "0x12", "1234", "0x001"]"#; let deserialized: Vec = serde_json::from_str(s).unwrap(); assert_eq!(deserialized, vec![ Bytes(vec![]), Bytes(vec![]), Bytes(vec![0x12]), - Bytes(vec![0x12, 0x34]) + Bytes(vec![0x12, 0x34]), + Bytes(vec![0, 1]) ]); } diff --git a/json/src/lib.rs.in b/json/src/lib.rs.in index 61f556e7c..73adaf07a 100644 --- a/json/src/lib.rs.in +++ b/json/src/lib.rs.in @@ -27,3 +27,4 @@ pub mod spec; pub mod vm; pub mod maybe; pub mod state; +pub mod transaction; diff --git a/json/src/transaction/mod.rs b/json/src/transaction/mod.rs new file mode 100644 index 000000000..cec6734b3 --- /dev/null +++ b/json/src/transaction/mod.rs @@ -0,0 +1,25 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +//! Transaction test deserialization. + +mod transaction; +mod txtest; +mod test; + +pub use self::transaction::Transaction; +pub use self::txtest::TransactionTest; +pub use self::test::Test; diff --git a/json/src/transaction/test.rs b/json/src/transaction/test.rs new file mode 100644 index 000000000..1ea54ff04 --- /dev/null +++ b/json/src/transaction/test.rs @@ -0,0 +1,43 @@ +// 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 . + +//! TransactionTest test deserializer. + +use std::collections::BTreeMap; +use std::io::Read; +use serde_json; +use serde_json::Error; +use transaction::TransactionTest; + +/// TransactionTest test deserializer. +#[derive(Debug, PartialEq, Deserialize)] +pub struct Test(BTreeMap); + +impl IntoIterator for Test { + type Item = as IntoIterator>::Item; + type IntoIter = as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl Test { + /// Loads test from json. + pub fn load(reader: R) -> Result where R: Read { + serde_json::from_reader(reader) + } +} diff --git a/json/src/transaction/transaction.rs b/json/src/transaction/transaction.rs new file mode 100644 index 000000000..8898f723e --- /dev/null +++ b/json/src/transaction/transaction.rs @@ -0,0 +1,70 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +//! Transaction test transaction deserialization. + +use uint::Uint; +use bytes::Bytes; +use hash::Address; +use maybe::MaybeEmpty; + +/// Transaction test transaction deserialization. +#[derive(Debug, PartialEq, Deserialize)] +pub struct Transaction { + /// Transaction data. + pub data: Bytes, + /// Gas limit. + #[serde(rename="gasLimit")] + pub gas_limit: Uint, + /// Gas price. + #[serde(rename="gasPrice")] + pub gas_price: Uint, + /// Nonce. + pub nonce: Uint, + /// To. + pub to: MaybeEmpty
, + /// Value. + pub value: Uint, + /// R. + pub r: Uint, + /// S. + pub s: Uint, + /// V. + pub v: Uint, +} + +#[cfg(test)] +mod tests { + use serde_json; + use transaction::Transaction; + + #[test] + fn transaction_deserialization() { + let s = r#"{ + "data" : "0x", + "gasLimit" : "0xf388", + "gasPrice" : "0x09184e72a000", + "nonce" : "0x00", + "r" : "0x2c", + "s" : "0x04", + "to" : "", + "v" : "0x1b", + "value" : "0x00" + }"#; + let _deserialized: Transaction = serde_json::from_str(s).unwrap(); + // TODO: validate all fields + } +} diff --git a/json/src/transaction/txtest.rs b/json/src/transaction/txtest.rs new file mode 100644 index 000000000..059203453 --- /dev/null +++ b/json/src/transaction/txtest.rs @@ -0,0 +1,64 @@ +// 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 . + +//! Transaction test deserialization. + +use uint::Uint; +use bytes::Bytes; +use hash::Address; +use transaction::Transaction; + +/// Transaction test deserialization. +#[derive(Debug, PartialEq, Deserialize)] +pub struct TransactionTest { + /// Block number. + #[serde(rename="blocknumber")] + pub block_number: Option, + /// Transaction rlp. + pub rlp: Bytes, + /// Transaction sender. + pub sender: Option
, + /// Transaction + pub transaction: Option, +} + +#[cfg(test)] +mod tests { + use serde_json; + use transaction::TransactionTest; + + #[test] + fn transaction_deserialization() { + let s = r#"{ + "blocknumber" : "0", + "rlp" : "0xf83f800182520894095e7baea6a6c7c4c2dfeb977efac326af552d870b801ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a3664935301", + "sender" : "e115cf6bb5656786569dd273705242ca72d84bc0", + "transaction" : { + "data" : "", + "gasLimit" : "0x5208", + "gasPrice" : "0x01", + "nonce" : "0x00", + "r" : "0x48b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353", + "s" : "0x01", + "to" : "095e7baea6a6c7c4c2dfeb977efac326af552d87", + "v" : "0x1b", + "value" : "0x0b" + } + }"#; + let _deserialized: TransactionTest = serde_json::from_str(s).unwrap(); + // TODO: validate all fields + } +} diff --git a/json/src/uint.rs b/json/src/uint.rs index f6eae80e4..2ebdca910 100644 --- a/json/src/uint.rs +++ b/json/src/uint.rs @@ -37,6 +37,12 @@ impl Into for Uint { } } +impl Into for Uint { + fn into(self) -> u8 { + >::into(self) as u8 + } +} + impl Deserialize for Uint { fn deserialize(deserializer: &mut D) -> Result where D: Deserializer {