backwards compatible call_type creation_method (#11450)

* rlp_derive: update syn & co

* rlp_derive: remove dummy_const

* rlp_derive: remove unused attirubutes

* rlp-derive: change authors

* rlp_derive: add rlp(default) attribute

* Revert "Revert "[Trace] Distinguish between `create` and `create2` (#11311)" (#11427)"

This reverts commit 5d4993b0f8.

* trace: backwards compatible call_type and creation_method

* trace: add rlp backward compatibility tests

* cleanup

* i know, i hate backwards compatibility too

* address review grumbles
This commit is contained in:
Andronik Ordian
2020-02-05 15:30:45 +01:00
committed by GitHub
parent 7108b3f048
commit 626543326d
27 changed files with 582 additions and 166 deletions

View File

@@ -374,13 +374,12 @@ mod tests {
use ethcore::test_helpers::new_db;
use ethereum_types::{H256, U256, Address};
use evm::CallType;
use kvdb::DBTransaction;
use crate::{
BlockNumber, Config, TraceDB, Database as TraceDatabase, ImportRequest, DatabaseExtras,
Filter, LocalizedTrace, AddressesFilter, TraceError,
trace::{Call, Action, Res},
trace::{Call, CallType, Action, Res},
flat::{FlatTrace, FlatBlockTraces, FlatTransactionTraces}
};
@@ -465,7 +464,7 @@ mod tests {
value: 3.into(),
gas: 4.into(),
input: vec![],
call_type: CallType::Call,
call_type: Some(CallType::Call).into(),
}),
result: Res::FailedCall(TraceError::OutOfGas),
}])]),
@@ -487,7 +486,7 @@ mod tests {
value: 3.into(),
gas: 4.into(),
input: vec![],
call_type: CallType::Call,
call_type: Some(CallType::Call).into(),
}),
result: Res::FailedCall(TraceError::OutOfGas),
}])]),
@@ -506,7 +505,7 @@ mod tests {
value: U256::from(3),
gas: U256::from(4),
input: vec![],
call_type: CallType::Call,
call_type: Some(CallType::Call).into(),
}),
result: Res::FailedCall(TraceError::OutOfGas),
trace_address: vec![],

View File

@@ -125,10 +125,9 @@ impl Filter {
#[cfg(test)]
mod tests {
use ethereum_types::{Address, Bloom, BloomInput};
use evm::CallType;
use crate::{
Filter, AddressesFilter, TraceError, RewardType,
trace::{Action, Call, Res, Create, CreateResult, Suicide, Reward},
trace::{Action, Call, CallType, Res, Create, CreationMethod, CreateResult, Suicide, Reward},
flat::FlatTrace,
};
@@ -273,7 +272,7 @@ mod tests {
value: 3.into(),
gas: 4.into(),
input: vec![0x5],
call_type: CallType::Call,
call_type: Some(CallType::Call).into(),
}),
result: Res::FailedCall(TraceError::OutOfGas),
trace_address: vec![0].into_iter().collect(),
@@ -294,6 +293,7 @@ mod tests {
value: 3.into(),
gas: 4.into(),
init: vec![0x5],
creation_method: Some(CreationMethod::Create),
}),
result: Res::Create(CreateResult {
gas_used: 10.into(),
@@ -413,6 +413,7 @@ mod tests {
gas: 4.into(),
init: vec![0x5],
value: 3.into(),
creation_method: Some(CreationMethod::Create),
}),
result: Res::FailedCall(TraceError::BadInstruction),
trace_address: vec![].into_iter().collect(),

View File

@@ -123,9 +123,8 @@ mod tests {
use rlp::*;
use crate::{
FlatBlockTraces, FlatTransactionTraces, FlatTrace,
trace::{Action, Res, CallResult, Call, Suicide, Reward, RewardType}
trace::{Action, Res, CallResult, Call, CallType, Suicide, Reward, RewardType}
};
use evm::CallType;
#[test]
fn encode_flat_transaction_traces() {
@@ -162,7 +161,7 @@ mod tests {
value: "3627e8f712373c0000".parse().unwrap(),
gas: 0x03e8.into(),
input: vec![],
call_type: CallType::Call,
call_type: Some(CallType::Call).into(),
}),
result: Res::Call(CallResult {
gas_used: 0.into(),
@@ -179,7 +178,7 @@ mod tests {
value: 0.into(),
gas: 0x010c78.into(),
input: vec![0x41, 0xc0, 0xe1, 0xb5],
call_type: CallType::Call,
call_type: Some(CallType::Call).into(),
}),
result: Res::Call(CallResult {
gas_used: 0x0127.into(),

View File

@@ -16,12 +16,19 @@
//! Tracing data types.
// ================== NOTE ========================
// IF YOU'RE ADDING A FIELD TO A STRUCT WITH
// RLP ENCODING, MAKE SURE IT'S DONE IN A BACKWARDS
// COMPATIBLE WAY!
// ================== NOTE ========================
use std::convert::TryFrom;
use ethereum_types::{U256, Address, Bloom, BloomInput};
use parity_bytes::Bytes;
use rlp::{Rlp, RlpStream, Encodable, DecoderError, Decodable};
use rlp_derive::{RlpEncodable, RlpDecodable};
use vm::ActionParams;
use evm::CallType;
use evm::ActionType;
use super::error::Error;
/// `Call` result.
@@ -33,6 +40,57 @@ pub struct CallResult {
pub output: Bytes,
}
/// `Call` type. Distinguish between different types of contract interactions.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum CallType {
/// Call
Call,
/// Call code
CallCode,
/// Delegate call
DelegateCall,
/// Static call
StaticCall,
}
impl TryFrom<ActionType> for CallType {
type Error = &'static str;
fn try_from(action_type: ActionType) -> Result<Self, Self::Error> {
match action_type {
ActionType::Call => Ok(CallType::Call),
ActionType::CallCode => Ok(CallType::CallCode),
ActionType::DelegateCall => Ok(CallType::DelegateCall),
ActionType::StaticCall => Ok(CallType::StaticCall),
ActionType::Create => Err("Create cannot be converted to CallType"),
ActionType::Create2 => Err("Create2 cannot be converted to CallType"),
}
}
}
impl Encodable for CallType {
fn rlp_append(&self, s: &mut RlpStream) {
let v = match *self {
CallType::Call => 0u32,
CallType::CallCode => 1,
CallType::DelegateCall => 2,
CallType::StaticCall => 3,
};
Encodable::rlp_append(&v, s);
}
}
impl Decodable for CallType {
fn decode(rlp: &Rlp) -> Result<Self, DecoderError> {
rlp.as_val().and_then(|v| Ok(match v {
1u32 => CallType::Call,
2 => CallType::CallCode,
3 => CallType::DelegateCall,
4 => CallType::StaticCall,
_ => return Err(DecoderError::Custom("Invalid value of CallType item")),
}))
}
}
/// `Create` result.
#[derive(Debug, Clone, PartialEq, RlpEncodable, RlpDecodable)]
pub struct CreateResult {
@@ -51,6 +109,49 @@ impl CreateResult {
}
}
/// `Create` method. Distinguish between use of `CREATE` and `CREATE2` opcodes in an action.
#[derive(Debug, Clone, PartialEq)]
pub enum CreationMethod {
/// Create
Create,
/// Create2
Create2,
}
impl TryFrom<ActionType> for CreationMethod {
type Error = &'static str;
fn try_from(action_type: ActionType) -> Result<Self, Self::Error> {
match action_type {
ActionType::Call => Err("Call cannot be converted to CreationMethod"),
ActionType::CallCode => Err("CallCode cannot be converted to CreationMethod"),
ActionType::DelegateCall => Err("DelegateCall cannot be converted to CreationMethod"),
ActionType::StaticCall => Err("StaticCall cannot be converted to CreationMethod"),
ActionType::Create => Ok(CreationMethod::Create),
ActionType::Create2 => Ok(CreationMethod::Create2),
}
}
}
impl Encodable for CreationMethod {
fn rlp_append(&self, s: &mut RlpStream) {
let v = match *self {
CreationMethod::Create => 0u32,
CreationMethod::Create2 => 1,
};
Encodable::rlp_append(&v, s);
}
}
impl Decodable for CreationMethod {
fn decode(rlp: &Rlp) -> Result<Self, DecoderError> {
rlp.as_val().and_then(|v| Ok(match v {
0u32 => CreationMethod::Create,
1 => CreationMethod::Create2,
_ => return Err(DecoderError::Custom("Invalid value of CreationMethod item")),
}))
}
}
/// Description of a _call_ action, either a `CALL` operation or a message transaction.
#[derive(Debug, Clone, PartialEq, RlpEncodable, RlpDecodable)]
pub struct Call {
@@ -65,19 +166,96 @@ pub struct Call {
/// The input data provided to the call.
pub input: Bytes,
/// The type of the call.
pub call_type: CallType,
pub call_type: BackwardsCompatibleCallType,
}
/// This is essentially an `Option<CallType>`, but with a custom
/// `rlp` en/de-coding which preserves backwards compatibility with
/// the older encodings used in parity-ethereum versions < 2.7 and 2.7.0.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct BackwardsCompatibleCallType(pub Option<CallType>);
impl From<Option<CallType>> for BackwardsCompatibleCallType {
fn from(option: Option<CallType>) -> Self {
BackwardsCompatibleCallType(option)
}
}
// Encoding is the same as `CallType_v2_6_x`.
impl Encodable for BackwardsCompatibleCallType {
fn rlp_append(&self, s: &mut RlpStream) {
let v = match self.0 {
None => 0u32,
Some(CallType::Call) => 1,
Some(CallType::CallCode) => 2,
Some(CallType::DelegateCall) => 3,
Some(CallType::StaticCall) => 4,
};
Encodable::rlp_append(&v, s);
}
}
// Try to decode it as `CallType_v2_6_x` first, and then as `Option<CallType_v2_7_0>`.
impl Decodable for BackwardsCompatibleCallType {
fn decode(rlp: &Rlp) -> Result<Self, DecoderError> {
if rlp.is_data() {
rlp.as_val().and_then(|v| Ok(match v {
0u32 => None,
1 => Some(CallType::Call),
2 => Some(CallType::CallCode),
3 => Some(CallType::DelegateCall),
4 => Some(CallType::StaticCall),
_ => return Err(DecoderError::Custom("Invalid value of CallType item")),
}.into()))
} else {
#[allow(non_camel_case_types)]
#[derive(Debug, Clone, Copy, PartialEq)]
enum CallType_v2_7_0 {
Call,
CallCode,
DelegateCall,
StaticCall,
}
impl Decodable for CallType_v2_7_0 {
fn decode(rlp: &Rlp) -> Result<Self, DecoderError> {
rlp.as_val().and_then(|v| Ok(match v {
0u32 => CallType_v2_7_0::Call,
1 => CallType_v2_7_0::CallCode,
2 => CallType_v2_7_0::DelegateCall,
3 => CallType_v2_7_0::StaticCall,
_ => return Err(DecoderError::Custom("Invalid value of CallType item")),
}))
}
}
impl From<CallType_v2_7_0> for CallType {
fn from(old_call_type: CallType_v2_7_0) -> Self {
match old_call_type {
CallType_v2_7_0::Call => Self::Call,
CallType_v2_7_0::CallCode => Self::CallCode,
CallType_v2_7_0::DelegateCall => Self::DelegateCall,
CallType_v2_7_0::StaticCall => Self::StaticCall,
}
}
}
let optional: Option<CallType_v2_7_0> = Decodable::decode(rlp)?;
Ok(optional.map(Into::into).into())
}
}
}
impl From<ActionParams> for Call {
fn from(p: ActionParams) -> Self {
match p.call_type {
CallType::DelegateCall | CallType::CallCode => Call {
match p.action_type {
ActionType::DelegateCall | ActionType::CallCode => Call {
from: p.address,
to: p.code_address,
value: p.value.value(),
gas: p.gas,
input: p.data.unwrap_or_else(Vec::new),
call_type: p.call_type,
call_type: CallType::try_from(p.action_type).ok().into(),
},
_ => Call {
from: p.sender,
@@ -85,7 +263,7 @@ impl From<ActionParams> for Call {
value: p.value.value(),
gas: p.gas,
input: p.data.unwrap_or_else(Vec::new),
call_type: p.call_type,
call_type: CallType::try_from(p.action_type).ok().into(),
},
}
}
@@ -113,6 +291,9 @@ pub struct Create {
pub gas: U256,
/// The init code.
pub init: Bytes,
/// Creation method (CREATE vs CREATE2).
#[rlp(default)]
pub creation_method: Option<CreationMethod>,
}
impl From<ActionParams> for Create {
@@ -122,6 +303,7 @@ impl From<ActionParams> for Create {
value: p.value.value(),
gas: p.gas,
init: p.code.map_or_else(Vec::new, |c| (*c).clone()),
creation_method: CreationMethod::try_from(p.action_type).ok().into(),
}
}
}
@@ -429,3 +611,132 @@ pub struct VMTrace {
/// Thre is a 1:1 correspondance between these and a CALL/CREATE/CALLCODE/DELEGATECALL instruction.
pub subs: Vec<VMTrace>,
}
#[cfg(test)]
mod tests {
use rlp::{RlpStream, Encodable};
use rlp_derive::{RlpEncodable, RlpDecodable};
use super::{Address, Bytes, Call, CallType, Create, CreationMethod, U256};
#[test]
fn test_call_type_backwards_compatibility() {
// Call type in version < 2.7.
#[derive(Debug, Clone, PartialEq, RlpEncodable)]
struct OldCall {
from: Address,
to: Address,
value: U256,
gas: U256,
input: Bytes,
call_type: OldCallType,
}
// CallType type in version < 2.7.
#[allow(dead_code)]
#[derive(Debug, PartialEq, Clone)]
enum OldCallType {
None,
Call,
CallCode,
DelegateCall,
StaticCall,
}
// CallType rlp encoding in version < 2.7.
impl Encodable for OldCallType {
fn rlp_append(&self, s: &mut RlpStream) {
let v = match *self {
OldCallType::None => 0u32,
OldCallType::Call => 1,
OldCallType::CallCode => 2,
OldCallType::DelegateCall => 3,
OldCallType::StaticCall => 4,
};
Encodable::rlp_append(&v, s);
}
}
let old_call = OldCall {
from: Address::from_low_u64_be(1),
to: Address::from_low_u64_be(2),
value: U256::from(3),
gas: U256::from(4),
input: vec![5],
call_type: OldCallType::DelegateCall,
};
let old_encoded = rlp::encode(&old_call);
let new_call = Call {
from: Address::from_low_u64_be(1),
to: Address::from_low_u64_be(2),
value: U256::from(3),
gas: U256::from(4),
input: vec![5],
call_type: Some(CallType::DelegateCall).into(),
};
// `old_call` should be deserialized successfully into `new_call`
assert_eq!(rlp::decode(&old_encoded), Ok(new_call.clone()));
// test a roundtrip with `Some` `call_type`
let new_encoded = rlp::encode(&new_call);
assert_eq!(rlp::decode(&new_encoded), Ok(new_call));
// test a roundtrip with `None` `call_type`
let none_call = Call {
from: Address::from_low_u64_be(1),
to: Address::from_low_u64_be(2),
value: U256::from(3),
gas: U256::from(4),
input: vec![5],
call_type: None.into(),
};
let none_encoded = rlp::encode(&none_call);
assert_eq!(rlp::decode(&none_encoded), Ok(none_call));
}
#[test]
fn test_creation_method_backwards_compatibility() {
// Create type in version < 2.7.
#[derive(Debug, Clone, PartialEq, RlpEncodable, RlpDecodable)]
struct OldCreate {
from: Address,
value: U256,
gas: U256,
init: Bytes,
}
let old_create = OldCreate {
from: Address::from_low_u64_be(1),
value: U256::from(3),
gas: U256::from(4),
init: vec![5],
};
let old_encoded = rlp::encode(&old_create);
let new_create = Create {
from: Address::from_low_u64_be(1),
value: U256::from(3),
gas: U256::from(4),
init: vec![5],
creation_method: None,
};
// `old_create` should be deserialized successfully into `new_create`
assert_eq!(rlp::decode(&old_encoded), Ok(new_create.clone()));
// test a roundtrip with `None` `creation_method`
let new_encoded = rlp::encode(&new_create);
assert_eq!(rlp::decode(&new_encoded), Ok(new_create));
// test a roundtrip with `Some` `creation_method`
let some_create = Create {
from: Address::from_low_u64_be(1),
value: U256::from(3),
gas: U256::from(4),
init: vec![5],
creation_method: Some(CreationMethod::Create2),
};
let some_encoded = rlp::encode(&some_create);
assert_eq!(rlp::decode(&some_encoded), Ok(some_create));
}
}