Backport Beta 2.2.1 (#9905)

* Bump beta to version 2.2.1

* fix: Intermittent failing CI due to addr in use (#9885)

Allow OS to set port at runtime

* Use Weak reference in PubSubClient (#9886)

* Fix json tracer overflow (#9873)

* Fix json tracer overflow

* Replace trace_executed with a direct trace push

* Remove unused variable

* Add test for 5a51

* Remove duplicate json!

* Fix docker script (#9854)


* Dockerfile: change source path of the newly added check_sync.sh (#9869)

* Allow to seal work on latest block (#9876)

* Allow to seal work on latest block.

* Test from @todr to check sealing conditions.

* gitlab-ci: make android release build succeed (#9743)

* use docker cargo config file for android builds

* make android build succeed

* ethcore: use Machine::verify_transaction on parent block (#9900)

* ethcore: use Machine::verify_transaction on parent block

also fixes off-by-one activation of transaction permission contract

* ethcore: clarify call to verify_transaction

* foundation: #6692865, ropsten: #4417537, kovan: #9363457

* Remove rust-toolchain file (#9906)

* EIP-712 implementation (#9631)

* EIP-712 impl

* added more tests

* removed size parsing unwrap

* corrected TYPE_REGEX to disallow zero sized fixed length arrays, replaced LinkedHashSet with IndexSet, added API spec to docs, fixed Type::Byte encoding branch

* use Option<u64> instead of u64 for Type::Array::Length

* replace `.iter()` with  `.values()`

Co-Authored-By: seunlanlege <seunlanlege@gmail.com>

* tabify eip712.rs

* use proper comments for docs

* Cargo.lock: revert unrelated changes

* tabify encode.rs

* EIP 191 (#9701)

* added sign_191 rpc method

* fixed hash_structured_data return type

* added ConfirmationPayload::SignMessage for non-prefixed signatures, added tests for sign191

* renamed WithValidator -> PresignedTransaction

* rename applicationData to data in test

* adds docs for EIP191Version, renamed SignRequest to EIP191SignRequest

* light-fetch: Differentiate between out-of-gas/manual throw and use required gas from response on failure (#9824)

* fix start_gas, handle OOG exceptions & NotEnoughGas

* Change START_GAS: 50_000 -> 60_000
* When the `OutOfGas exception` is received then try to double the gas until it succeeds or block gas limit is reached
* When `NotEnoughBasGas error` is received then use the required gas provided in the response

* fix(light-fetch): ensure block_gas_limit is tried

Try the `block_gas_limit` before regard the execution as an error

* Update rpc/src/v1/helpers/light_fetch.rs

Co-Authored-By: niklasad1 <niklasadolfsson1@gmail.com>

* simplify cargo audit

* Use block header for building finality (#9914)

* ci: nuke the gitlab caches (#9855)
This commit is contained in:
Thibaut Sardan
2018-11-14 15:23:44 +01:00
committed by GitHub
parent 5a7e54fb9e
commit 5c56fc5023
42 changed files with 2146 additions and 117 deletions

23
util/EIP-712/Cargo.toml Normal file
View File

@@ -0,0 +1,23 @@
[package]
name = "eip712"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
[dependencies]
serde_derive = "1.0"
serde = "1.0"
serde_json = "1.0"
ethabi = "6.0"
keccak-hash = "0.1"
ethereum-types = "0.4"
failure = "0.1"
itertools = "0.7"
failure_derive = "0.1"
lazy_static = "1.1"
toolshed = "0.4"
regex = "1.0"
validator = "0.8"
validator_derive = "0.8"
lunarity-lexer = "0.1"
rustc-hex = "2.0"
indexmap = "1.0.2"

177
util/EIP-712/src/eip712.rs Normal file
View File

@@ -0,0 +1,177 @@
// Copyright 2015-2018 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU 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/>.
//! EIP712 structs
use serde_json::{Value};
use std::collections::HashMap;
use ethereum_types::{U256, H256, Address};
use regex::Regex;
use validator::Validate;
use validator::ValidationErrors;
pub(crate) type MessageTypes = HashMap<String, Vec<FieldType>>;
lazy_static! {
// match solidity identifier with the addition of '[(\d)*]*'
static ref TYPE_REGEX: Regex = Regex::new(r"^[a-zA-Z_$][a-zA-Z_$0-9]*(\[([1-9]\d*)*\])*$").unwrap();
static ref IDENT_REGEX: Regex = Regex::new(r"^[a-zA-Z_$][a-zA-Z_$0-9]*$").unwrap();
}
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
#[derive(Deserialize, Serialize, Validate, Debug, Clone)]
pub(crate) struct EIP712Domain {
pub(crate) name: String,
pub(crate) version: String,
pub(crate) chain_id: U256,
pub(crate) verifying_contract: Address,
#[serde(skip_serializing_if="Option::is_none")]
pub(crate) salt: Option<H256>,
}
/// EIP-712 struct
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
#[derive(Deserialize, Debug, Clone)]
pub struct EIP712 {
pub(crate) types: MessageTypes,
pub(crate) primary_type: String,
pub(crate) message: Value,
pub(crate) domain: EIP712Domain,
}
impl Validate for EIP712 {
fn validate(&self) -> Result<(), ValidationErrors> {
for field_types in self.types.values() {
for field_type in field_types {
field_type.validate()?;
}
}
Ok(())
}
}
#[derive(Serialize, Deserialize, Validate, Debug, Clone)]
pub(crate) struct FieldType {
#[validate(regex = "IDENT_REGEX")]
pub name: String,
#[serde(rename = "type")]
#[validate(regex = "TYPE_REGEX")]
pub type_: String,
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::from_str;
#[test]
fn test_regex() {
let test_cases = vec!["unint bytes32", "Seun\\[]", "byte[]uint", "byte[7[]uint][]", "Person[0]"];
for case in test_cases {
assert_eq!(TYPE_REGEX.is_match(case), false)
}
let test_cases = vec!["bytes32", "Foo[]", "bytes1", "bytes32[][]", "byte[9]", "contents"];
for case in test_cases {
assert_eq!(TYPE_REGEX.is_match(case), true)
}
}
#[test]
fn test_deserialization() {
let string = r#"{
"primaryType": "Mail",
"domain": {
"name": "Ether Mail",
"version": "1",
"chainId": "0x1",
"verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
},
"message": {
"from": {
"name": "Cow",
"wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"
},
"to": {
"name": "Bob",
"wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"
},
"contents": "Hello, Bob!"
},
"types": {
"EIP712Domain": [
{ "name": "name", "type": "string" },
{ "name": "version", "type": "string" },
{ "name": "chainId", "type": "uint256" },
{ "name": "verifyingContract", "type": "address" }
],
"Person": [
{ "name": "name", "type": "string" },
{ "name": "wallet", "type": "address" }
],
"Mail": [
{ "name": "from", "type": "Person" },
{ "name": "to", "type": "Person" },
{ "name": "contents", "type": "string" }
]
}
}"#;
let _ = from_str::<EIP712>(string).unwrap();
}
#[test]
fn test_failing_deserialization() {
let string = r#"{
"primaryType": "Mail",
"domain": {
"name": "Ether Mail",
"version": "1",
"chainId": "0x1",
"verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
},
"message": {
"from": {
"name": "Cow",
"wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"
},
"to": {
"name": "Bob",
"wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"
},
"contents": "Hello, Bob!"
},
"types": {
"EIP712Domain": [
{ "name": "name", "type": "string" },
{ "name": "version", "type": "string" },
{ "name": "chainId", "type": "7uint256[x] Seun" },
{ "name": "verifyingContract", "type": "address" }
],
"Person": [
{ "name": "name", "type": "string" },
{ "name": "wallet amen", "type": "address" }
],
"Mail": [
{ "name": "from", "type": "Person" },
{ "name": "to", "type": "Person" },
{ "name": "contents", "type": "string" }
]
}
}"#;
let data = from_str::<EIP712>(string).unwrap();
assert_eq!(data.validate().is_err(), true);
}
}

415
util/EIP-712/src/encode.rs Normal file
View File

@@ -0,0 +1,415 @@
// Copyright 2015-2018 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU 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/>.
//! EIP712 Encoder
use ethabi::{encode, Token as EthAbiToken};
use ethereum_types::{Address as EthAddress, U256, H256};
use keccak_hash::keccak;
use serde_json::Value;
use std::str::FromStr;
use itertools::Itertools;
use indexmap::IndexSet;
use serde_json::to_value;
use parser::{Parser, Type};
use error::{Result, ErrorKind, serde_error};
use eip712::{EIP712, MessageTypes};
use rustc_hex::FromHex;
use validator::Validate;
use std::collections::HashSet;
fn check_hex(string: &str) -> Result<()> {
if string.len() >= 2 && &string[..2] == "0x" {
return Ok(())
}
return Err(ErrorKind::HexParseError(
format!("Expected a 0x-prefixed string of even length, found {} length string", string.len()))
)?
}
/// given a type and HashMap<String, Vec<FieldType>>
/// returns a HashSet of dependent types of the given type
fn build_dependencies<'a>(message_type: &'a str, message_types: &'a MessageTypes) -> Option<(HashSet<&'a str>)>
{
if message_types.get(message_type).is_none() {
return None;
}
let mut types = IndexSet::new();
types.insert(message_type);
let mut deps = HashSet::new();
while let Some(item) = types.pop() {
if let Some(fields) = message_types.get(item) {
deps.insert(item);
for field in fields {
// seen this type before? or not a custom type skip
if deps.contains(&*field.type_) || !message_types.contains_key(&*field.type_) {
continue;
}
types.insert(&*field.type_);
}
}
};
return Some(deps)
}
fn encode_type(message_type: &str, message_types: &MessageTypes) -> Result<String> {
let deps = {
let mut temp = build_dependencies(message_type, message_types).ok_or_else(|| ErrorKind::NonExistentType)?;
temp.remove(message_type);
let mut temp = temp.into_iter().collect::<Vec<_>>();
(&mut temp[..]).sort_unstable();
temp.insert(0, message_type);
temp
};
let encoded = deps
.into_iter()
.filter_map(|dep| {
message_types.get(dep).map(|field_types| {
let types = field_types
.iter()
.map(|value| format!("{} {}", value.type_, value.name))
.join(",");
return format!("{}({})", dep, types);
})
})
.collect::<Vec<_>>()
.concat();
Ok(encoded)
}
fn type_hash(message_type: &str, typed_data: &MessageTypes) -> Result<H256> {
Ok(keccak(encode_type(message_type, typed_data)?))
}
fn encode_data(
parser: &Parser,
message_type: &Type,
message_types: &MessageTypes,
value: &Value,
field_name: Option<&str>
) -> Result<Vec<u8>>
{
let encoded = match message_type {
Type::Array {
inner,
length
} => {
let mut items = vec![];
let values = value.as_array().ok_or_else(|| serde_error("array", field_name))?;
// check if the type definition actually matches
// the length of items to be encoded
if length.is_some() && Some(values.len() as u64) != *length {
let array_type = format!("{}[{}]", *inner, length.unwrap());
return Err(ErrorKind::UnequalArrayItems(length.unwrap(), array_type, values.len() as u64))?
}
for item in values {
let mut encoded = encode_data(parser, &*inner, &message_types, item, field_name)?;
items.append(&mut encoded);
}
keccak(items).to_vec()
}
Type::Custom(ref ident) if message_types.get(&*ident).is_some() => {
let type_hash = (&type_hash(ident, &message_types)?).to_vec();
let mut tokens = encode(&[EthAbiToken::FixedBytes(type_hash)]);
for field in message_types.get(ident).expect("Already checked in match guard; qed") {
let value = &value[&field.name];
let type_ = parser.parse_type(&*field.type_)?;
let mut encoded = encode_data(parser, &type_, &message_types, &value, Some(&*field.name))?;
tokens.append(&mut encoded);
}
keccak(tokens).to_vec()
}
Type::Bytes => {
let string = value.as_str().ok_or_else(|| serde_error("string", field_name))?;
check_hex(&string)?;
let bytes = (&string[2..])
.from_hex::<Vec<u8>>()
.map_err(|err| ErrorKind::HexParseError(format!("{}", err)))?;
let bytes = keccak(&bytes).to_vec();
encode(&[EthAbiToken::FixedBytes(bytes)])
}
Type::Byte(_) => {
let string = value.as_str().ok_or_else(|| serde_error("string", field_name))?;
check_hex(&string)?;
let mut bytes = (&string[2..])
.from_hex::<Vec<u8>>()
.map_err(|err| ErrorKind::HexParseError(format!("{}", err)))?;
encode(&[EthAbiToken::FixedBytes(bytes)])
}
Type::String => {
let value = value.as_str().ok_or_else(|| serde_error("string", field_name))?;
let hash = keccak(value).to_vec();
encode(&[EthAbiToken::FixedBytes(hash)])
}
Type::Bool => encode(&[EthAbiToken::Bool(value.as_bool().ok_or_else(|| serde_error("bool", field_name))?)]),
Type::Address => {
let addr = value.as_str().ok_or_else(|| serde_error("string", field_name))?;
if addr.len() != 42 {
return Err(ErrorKind::InvalidAddressLength(addr.len()))?;
}
let address = EthAddress::from_str(&addr[2..]).map_err(|err| ErrorKind::HexParseError(format!("{}", err)))?;
encode(&[EthAbiToken::Address(address)])
}
Type::Uint | Type::Int => {
let string = value.as_str().ok_or_else(|| serde_error("int/uint", field_name))?;
check_hex(&string)?;
let uint = U256::from_str(&string[2..]).map_err(|err| ErrorKind::HexParseError(format!("{}", err)))?;
let token = if *message_type == Type::Uint {
EthAbiToken::Uint(uint)
} else {
EthAbiToken::Int(uint)
};
encode(&[token])
}
_ => return Err(ErrorKind::UnknownType(format!("{}", field_name.unwrap_or("")), format!("{}", *message_type)))?
};
Ok(encoded)
}
/// encodes and hashes the given EIP712 struct
pub fn hash_structured_data(typed_data: EIP712) -> Result<H256> {
// validate input
typed_data.validate()?;
// EIP-191 compliant
let prefix = (b"\x19\x01").to_vec();
let domain = to_value(&typed_data.domain).unwrap();
let parser = Parser::new();
let (domain_hash, data_hash) = (
encode_data(&parser, &Type::Custom("EIP712Domain".into()), &typed_data.types, &domain, None)?,
encode_data(&parser, &Type::Custom(typed_data.primary_type), &typed_data.types, &typed_data.message, None)?
);
let concat = [&prefix[..], &domain_hash[..], &data_hash[..]].concat();
Ok(keccak(concat))
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::from_str;
use rustc_hex::ToHex;
const JSON: &'static str = r#"{
"primaryType": "Mail",
"domain": {
"name": "Ether Mail",
"version": "1",
"chainId": "0x1",
"verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
},
"message": {
"from": {
"name": "Cow",
"wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"
},
"to": {
"name": "Bob",
"wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"
},
"contents": "Hello, Bob!"
},
"types": {
"EIP712Domain": [
{ "name": "name", "type": "string" },
{ "name": "version", "type": "string" },
{ "name": "chainId", "type": "uint256" },
{ "name": "verifyingContract", "type": "address" }
],
"Person": [
{ "name": "name", "type": "string" },
{ "name": "wallet", "type": "address" }
],
"Mail": [
{ "name": "from", "type": "Person" },
{ "name": "to", "type": "Person" },
{ "name": "contents", "type": "string" }
]
}
}"#;
#[test]
fn test_build_dependencies() {
let string = r#"{
"EIP712Domain": [
{ "name": "name", "type": "string" },
{ "name": "version", "type": "string" },
{ "name": "chainId", "type": "uint256" },
{ "name": "verifyingContract", "type": "address" }
],
"Person": [
{ "name": "name", "type": "string" },
{ "name": "wallet", "type": "address" }
],
"Mail": [
{ "name": "from", "type": "Person" },
{ "name": "to", "type": "Person" },
{ "name": "contents", "type": "string" }
]
}"#;
let value = from_str::<MessageTypes>(string).expect("alas error!");
let mail = "Mail";
let person = "Person";
let hashset = {
let mut temp = HashSet::new();
temp.insert(mail);
temp.insert(person);
temp
};
assert_eq!(build_dependencies(mail, &value), Some(hashset));
}
#[test]
fn test_encode_type() {
let string = r#"{
"EIP712Domain": [
{ "name": "name", "type": "string" },
{ "name": "version", "type": "string" },
{ "name": "chainId", "type": "uint256" },
{ "name": "verifyingContract", "type": "address" }
],
"Person": [
{ "name": "name", "type": "string" },
{ "name": "wallet", "type": "address" }
],
"Mail": [
{ "name": "from", "type": "Person" },
{ "name": "to", "type": "Person" },
{ "name": "contents", "type": "string" }
]
}"#;
let value = from_str::<MessageTypes>(string).expect("alas error!");
let mail = &String::from("Mail");
assert_eq!(
"Mail(Person from,Person to,string contents)Person(string name,address wallet)",
encode_type(&mail, &value).expect("alas error!")
)
}
#[test]
fn test_encode_type_hash() {
let string = r#"{
"EIP712Domain": [
{ "name": "name", "type": "string" },
{ "name": "version", "type": "string" },
{ "name": "chainId", "type": "uint256" },
{ "name": "verifyingContract", "type": "address" }
],
"Person": [
{ "name": "name", "type": "string" },
{ "name": "wallet", "type": "address" }
],
"Mail": [
{ "name": "from", "type": "Person" },
{ "name": "to", "type": "Person" },
{ "name": "contents", "type": "string" }
]
}"#;
let value = from_str::<MessageTypes>(string).expect("alas error!");
let mail = &String::from("Mail");
let hash = (type_hash(&mail, &value).expect("alas error!").0).to_hex::<String>();
assert_eq!(
hash,
"a0cedeb2dc280ba39b857546d74f5549c3a1d7bdc2dd96bf881f76108e23dac2"
);
}
#[test]
fn test_hash_data() {
let typed_data = from_str::<EIP712>(JSON).expect("alas error!");
assert_eq!(
hash_structured_data(typed_data).expect("alas error!").to_hex::<String>(),
"be609aee343fb3c4b28e1df9e632fca64fcfaede20f02e86244efddf30957bd2"
)
}
#[test]
fn test_unequal_array_lengths() {
const TEST: &'static str = r#"{
"primaryType": "Mail",
"domain": {
"name": "Ether Mail",
"version": "1",
"chainId": "0x1",
"verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
},
"message": {
"from": {
"name": "Cow",
"wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"
},
"to": [{
"name": "Bob",
"wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"
}],
"contents": "Hello, Bob!"
},
"types": {
"EIP712Domain": [
{ "name": "name", "type": "string" },
{ "name": "version", "type": "string" },
{ "name": "chainId", "type": "uint256" },
{ "name": "verifyingContract", "type": "address" }
],
"Person": [
{ "name": "name", "type": "string" },
{ "name": "wallet", "type": "address" }
],
"Mail": [
{ "name": "from", "type": "Person" },
{ "name": "to", "type": "Person[2]" },
{ "name": "contents", "type": "string" }
]
}
}"#;
let typed_data = from_str::<EIP712>(TEST).expect("alas error!");
assert_eq!(
hash_structured_data(typed_data).unwrap_err().kind(),
ErrorKind::UnequalArrayItems(2, "Person[2]".into(), 1)
)
}
}

125
util/EIP-712/src/error.rs Normal file
View File

@@ -0,0 +1,125 @@
// Copyright 2015-2018 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU 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/>.
use std::fmt::{self, Display};
use failure::{Fail, Context, Backtrace};
use validator::ValidationErrors;
use validator::ValidationErrorsKind;
pub(crate) type Result<T> = ::std::result::Result<T, Error>;
/// Error type
#[derive(Debug)]
pub struct Error {
inner: Context<ErrorKind>,
}
/// Possible errors encountered while hashing/encoding an EIP-712 compliant data structure
#[derive(Clone, Fail, Debug, PartialEq)]
pub enum ErrorKind {
/// if we fail to deserialize from a serde::Value as a type specified in message types
/// fail with this error.
#[fail(display = "Expected type '{}' for field '{}'", _0, _1)]
UnexpectedType(String, String),
/// the primary type supplied doesn't exist in the MessageTypes
#[fail(display = "The given primaryType wasn't found in the types field")]
NonExistentType,
/// an invalid address was encountered during encoding
#[fail(display = "Address string should be a 0x-prefixed 40 character string, got '{}'", _0)]
InvalidAddressLength(usize),
/// a hex parse error occured
#[fail(display = "Failed to parse hex '{}'", _0)]
HexParseError(String),
/// the field was declared with a unknown type
#[fail(display = "The field '{}' has an unknown type '{}'", _0, _1)]
UnknownType(String, String),
/// Unexpected token
#[fail(display = "Unexpected token '{}' while parsing typename '{}'", _0, _1)]
UnexpectedToken(String, String),
/// the user has attempted to define a typed array with a depth > 10
#[fail(display = "Maximum depth for nested arrays is 10")]
UnsupportedArrayDepth,
/// FieldType validation error
#[fail(display = "{}", _0)]
ValidationError(String),
/// the typed array defined in message types was declared with a fixed length
/// that is of unequal length with the items to be encoded
#[fail(display = "Expected {} items for array type {}, got {} items", _0, _1, _2)]
UnequalArrayItems(u64, String, u64),
/// Typed array length doesn't fit into a u64
#[fail(display = "Attempted to declare fixed size with length {}", _0)]
InvalidArraySize(String)
}
pub(crate) fn serde_error(expected: &str, field: Option<&str>) -> ErrorKind {
ErrorKind::UnexpectedType(expected.to_owned(), field.unwrap_or("").to_owned())
}
impl Fail for Error {
fn cause(&self) -> Option<&Fail> {
self.inner.cause()
}
fn backtrace(&self) -> Option<&Backtrace> {
self.inner.backtrace()
}
}
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Display::fmt(&self.inner, f)
}
}
impl Error {
/// extract the error kind
pub fn kind(&self) -> ErrorKind {
self.inner.get_context().clone()
}
}
impl From<ErrorKind> for Error {
fn from(kind: ErrorKind) -> Error {
Error { inner: Context::new(kind) }
}
}
impl From<Context<ErrorKind>> for Error {
fn from(inner: Context<ErrorKind>) -> Error {
Error { inner }
}
}
impl From<ValidationErrors> for Error {
fn from(error: ValidationErrors) -> Self {
let mut string: String = "".into();
for (field_name, error_kind) in error.errors() {
match error_kind {
ValidationErrorsKind::Field(validation_errors) => {
for error in validation_errors {
let str_error = format!(
"the field '{}', has an invalid value {}",
field_name,
error.params["value"]
);
string.push_str(&str_error);
}
},
_ => unreachable!("#[validate] is only used on fields for regex;\
its impossible to get any other ErrorKind; qed")
}
}
ErrorKind::ValidationError(string).into()
}
}

192
util/EIP-712/src/lib.rs Normal file
View File

@@ -0,0 +1,192 @@
// Copyright 2015-2018 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU 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/>.
//! EIP-712 encoding utilities
//!
//! # Specification
//!
//! `encode(domainSeparator : 𝔹²⁵⁶, message : 𝕊) = "\x19\x01" ‖ domainSeparator ‖ hashStruct(message)`
//! - data adheres to 𝕊, a structure defined in the rigorous eip-712
//! - `\x01` is needed to comply with EIP-191
//! - `domainSeparator` and `hashStruct` are defined below
//!
//! ## A) domainSeparator
//!
//! `domainSeparator = hashStruct(eip712Domain)`
//! <br/>
//! <br/>
//! Struct named `EIP712Domain` with the following fields
//!
//! - `name: String`
//! - `version: String`
//! - `chain_id: U256`,
//! - `verifying_contract: H160`
//! - `salt: Option<H256>`
//!
//! ## C) hashStruct
//!
//! `hashStruct(s : 𝕊) = keccak256(typeHash ‖ encodeData(s))`
//! <br/>
//! `typeHash = keccak256(encodeType(typeOf(s)))`
//!
//! ### i) encodeType
//!
//! - `name ‖ "(" ‖ member₁ ‖ "," ‖ member₂ ‖ "," ‖ … ‖ memberₙ ")"`
//! - each member is written as `type ‖ " " ‖ name`
//! - encodings cascade down and are sorted by name
//!
//! ### ii) encodeData
//!
//! - `enc(value₁) ‖ enc(value₂) ‖ … ‖ enc(valueₙ)`
//! - each encoded member is 32-byte long
//!
//! #### a) atomic
//!
//! - `boolean` => `U256`
//! - `address` => `H160`
//! - `uint` => sign-extended `U256` in big endian order
//! - `bytes1:31` => `H@256`
//!
//! #### b) dynamic
//!
//! - `bytes` => `keccak256(bytes)`
//! - `string` => `keccak256(string)`
//!
//! #### c) referenced
//!
//! - `array` => `keccak256(encodeData(array))`
//! - `struct` => `rec(keccak256(hashStruct(struct)))`
//!
//! ## D) Example
//! ### Query
//! ```json
//! {
//! "jsonrpc": "2.0",
//! "method": "eth_signTypedData",
//! "params": [
//! "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826",
//! {
//! "types": {
//! "EIP712Domain": [
//! {
//! "name": "name",
//! "type": "string"
//! },
//! {
//! "name": "version",
//! "type": "string"
//! },
//! {
//! "name": "chainId",
//! "type": "uint256"
//! },
//! {
//! "name": "verifyingContract",
//! "type": "address"
//! }
//! ],
//! "Person": [
//! {
//! "name": "name",
//! "type": "string"
//! },
//! {
//! "name": "wallet",
//! "type": "address"
//! }
//! ],
//! "Mail": [
//! {
//! "name": "from",
//! "type": "Person"
//! },
//! {
//! "name": "to",
//! "type": "Person"
//! },
//! {
//! "name": "contents",
//! "type": "string"
//! }
//! ]
//! },
//! "primaryType": "Mail",
//! "domain": {
//! "name": "Ether Mail",
//! "version": "1",
//! "chainId": 1,
//! "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
//! },
//! "message": {
//! "from": {
//! "name": "Cow",
//! "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"
//! },
//! "to": {
//! "name": "Bob",
//! "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"
//! },
//! "contents": "Hello, Bob!"
//! }
//! }
//! ],
//! "id": 1
//! }
//! ```
//
//! ### Response
//! ```json
//! {
//! "id":1,
//! "jsonrpc": "2.0",
//! "result": "0x4355c47d63924e8a72e509b65029052eb6c299d53a04e167c5775fd466751c9d07299936d304c153f6443dfa05f40ff007d72911b6f72307f996231605b915621c"
//! }
//! ```
#![warn(missing_docs, unused_extern_crates)]
extern crate serde_json;
extern crate ethabi;
extern crate ethereum_types;
extern crate keccak_hash;
extern crate itertools;
extern crate failure;
extern crate indexmap;
extern crate lunarity_lexer;
extern crate toolshed;
extern crate regex;
extern crate validator;
#[macro_use]
extern crate validator_derive;
#[macro_use]
extern crate failure_derive;
#[macro_use]
extern crate serde_derive;
#[macro_use]
extern crate lazy_static;
extern crate rustc_hex;
mod eip712;
mod error;
mod parser;
mod encode;
/// the EIP-712 encoding function
pub use encode::hash_structured_data;
/// encoding Error types
pub use error::{ErrorKind, Error};
/// EIP712 struct
pub use eip712::EIP712;

172
util/EIP-712/src/parser.rs Normal file
View File

@@ -0,0 +1,172 @@
// Copyright 2015-2018 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU 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/>.
//! Solidity type-name parsing
use lunarity_lexer::{Lexer, Token};
use error::*;
use toolshed::Arena;
use std::{fmt, result};
#[derive(Debug, Clone, PartialEq)]
pub enum Type {
Address,
Uint,
Int,
String,
Bool,
Bytes,
Byte(u8),
Custom(String),
Array {
length: Option<u64>,
inner: Box<Type>
}
}
impl From<Type> for String {
fn from(field_type: Type) -> String {
match field_type {
Type::Address => "address".into(),
Type::Uint => "uint".into(),
Type::Int => "int".into(),
Type::String => "string".into(),
Type::Bool => "bool".into(),
Type::Bytes => "bytes".into(),
Type::Byte(len) => format!("bytes{}", len),
Type::Custom(custom) => custom,
Type::Array {
inner,
length
} => {
let inner: String = (*inner).into();
match length {
None => format!("{}[]", inner),
Some(length) => format!("{}[{}]", inner, length)
}
}
}
}
}
impl fmt::Display for Type {
fn fmt(&self, f: &mut fmt::Formatter) -> result::Result<(), fmt::Error> {
let item: String = self.clone().into();
write!(f, "{}", item)
}
}
pub struct Parser {
arena: Arena,
}
impl Parser {
pub fn new() -> Self {
Parser {
arena: Arena::new()
}
}
/// the type string is being validated before it's parsed.
pub fn parse_type(&self, field_type: &str) -> Result<Type> {
#[derive(PartialEq)]
enum State { Open, Close }
let mut lexer = Lexer::new(&self.arena, field_type);
let mut token = None;
let mut state = State::Close;
let mut array_depth = 0;
let mut current_array_length: Option<u64> = None;
while lexer.token != Token::EndOfProgram {
let type_ = match lexer.token {
Token::Identifier => Type::Custom(lexer.token_as_str().to_owned()),
Token::TypeByte => Type::Byte(lexer.type_size.0),
Token::TypeBytes => Type::Bytes,
Token::TypeBool => Type::Bool,
Token::TypeUint => Type::Uint,
Token::TypeInt => Type::Int,
Token::TypeString => Type::String,
Token::TypeAddress => Type::Address,
Token::LiteralInteger => {
let length = lexer.token_as_str();
current_array_length = Some(length
.parse()
.map_err(|_|
ErrorKind::InvalidArraySize(length.into())
)?
);
lexer.consume();
continue;
},
Token::BracketOpen if token.is_some() && state == State::Close => {
state = State::Open;
lexer.consume();
continue
}
Token::BracketClose if array_depth < 10 => {
if state == State::Open && token.is_some() {
let length = current_array_length.take();
state = State::Close;
token = Some(Type::Array {
inner: Box::new(token.expect("if statement checks for some; qed")),
length
});
lexer.consume();
array_depth += 1;
continue
} else {
return Err(ErrorKind::UnexpectedToken(lexer.token_as_str().to_owned(), field_type.to_owned()))?
}
}
Token::BracketClose if array_depth == 10 => {
return Err(ErrorKind::UnsupportedArrayDepth)?
}
_ => return Err(ErrorKind::UnexpectedToken(lexer.token_as_str().to_owned(), field_type.to_owned()))?
};
token = Some(type_);
lexer.consume();
}
Ok(token.ok_or_else(|| ErrorKind::NonExistentType)?)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parser() {
let parser = Parser::new();
let source = "byte[][][7][][][][][][][]";
parser.parse_type(source).unwrap();
}
#[test]
fn test_nested_array() {
let parser = Parser::new();
let source = "byte[][][7][][][][][][][][]";
assert_eq!(parser.parse_type(source).is_err(), true);
}
#[test]
fn test_malformed_array_type() {
let parser = Parser::new();
let source = "byte[7[]uint][]";
assert_eq!(parser.parse_type(source).is_err(), true)
}
}

View File

@@ -3,7 +3,7 @@
[package]
name = "parity-version"
# NOTE: this value is used for Parity Ethereum version string (via env CARGO_PKG_VERSION)
version = "2.2.0"
version = "2.2.1"
authors = ["Parity Technologies <admin@parity.io>"]
build = "build.rs"