Skip to content

Commit

Permalink
Fix eth_estimateGas and eth_call to match with Ethereum impl (#168)
Browse files Browse the repository at this point in the history
* move estimate_gas method into frame_ethereum and handle exit_reason

* replace estimate_gas with generic ethereum call, so both  and  can use it

* cleanup

* revert some changes

* refactor

* add test case
  • Loading branch information
ermalkaleci committed Oct 19, 2020
1 parent 2147407 commit 6173a3c
Show file tree
Hide file tree
Showing 8 changed files with 263 additions and 149 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

227 changes: 138 additions & 89 deletions frame/ethereum/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

use frame_support::{
decl_module, decl_storage, decl_error, decl_event,
traits::Get, traits::FindAuthor
traits::Get, traits::FindAuthor,
};
use sp_std::prelude::*;
use frame_system::ensure_none;
Expand All @@ -49,6 +49,12 @@ mod tests;
#[cfg(all(feature = "std", test))]
mod mock;

#[derive(Eq, PartialEq, Clone, sp_runtime::RuntimeDebug)]
pub enum ReturnValue {
Bytes(Vec<u8>),
Hash(H160),
}

/// A type alias for the balance type from this pallet's point of view.
pub type BalanceOf<T> = <T as pallet_balances::Trait>::Balance;

Expand Down Expand Up @@ -172,7 +178,7 @@ decl_module! {

#[repr(u8)]
enum TransactionValidationError {
#[allow (dead_code)]
#[allow(dead_code)]
UnknownError,
InvalidChainId,
InvalidSignature,
Expand Down Expand Up @@ -219,7 +225,6 @@ impl<T: Trait> frame_support::unsigned::ValidateUnsigned for Module<T> {
}

impl<T: Trait> Module<T> {

fn recover_signer(transaction: &ethereum::Transaction) -> Option<H160> {
let mut sig = [0u8; 65];
let mut msg = [0u8; 32];
Expand Down Expand Up @@ -321,78 +326,52 @@ impl<T: Trait> Module<T> {
}

/// Execute an Ethereum transaction, ignoring transaction signatures.
pub fn execute(source: H160, transaction: ethereum::Transaction) -> DispatchResult {
pub fn execute(from: H160, transaction: ethereum::Transaction) -> DispatchResult {
let transaction_hash = H256::from_slice(
Keccak256::digest(&rlp::encode(&transaction)).as_slice()
);
let transaction_index = Pending::get().len() as u32;

let (status, gas_used) = match transaction.action {
ethereum::TransactionAction::Call(target) => {
let (_, _, gas_used, logs) = Self::handle_exec(
pallet_evm::Module::<T>::execute_call(
source,
target,
transaction.input.clone(),
transaction.value,
transaction.gas_limit.low_u32(),
transaction.gas_price,
Some(transaction.nonce),
true,
)?
)?;

(TransactionStatus {
transaction_hash,
transaction_index,
from: source,
to: Some(target),
contract_address: None,
logs: {
logs.into_iter()
.map(|log| {
Log {
address: log.address,
topics: log.topics,
data: log.data
}
}).collect()
},
logs_bloom: Bloom::default(), // TODO: feed in bloom.
}, gas_used)
},
ethereum::TransactionAction::Create => {
let (_, contract_address, gas_used, logs) = Self::handle_exec(
pallet_evm::Module::<T>::execute_create(
source,
transaction.input.clone(),
transaction.value,
transaction.gas_limit.low_u32(),
transaction.gas_price,
Some(transaction.nonce),
true,
)?
)?;

(TransactionStatus {
transaction_hash,
transaction_index,
from: source,
to: None,
contract_address: Some(contract_address),
logs: {
logs.into_iter()
.map(|log| {
Log {
address: log.address,
topics: log.topics,
data: log.data
}
}).collect()
},
logs_bloom: Bloom::default(), // TODO: feed in bloom.
}, gas_used)
let (exit_reason, returned_value, gas_used, logs) = Self::execute_evm(
from,
transaction.input.clone(),
transaction.value,
transaction.gas_limit.low_u32(),
transaction.gas_price,
Some(transaction.nonce),
transaction.action,
true,
)?;

Self::handle_exit_reason(exit_reason)?;

let to = match transaction.action {
TransactionAction::Create => None,
TransactionAction::Call(to) => Some(to)
};

let contract_address = match returned_value {
ReturnValue::Bytes(_) => None,
ReturnValue::Hash(addr) => Some(addr)
};

let status = TransactionStatus {
transaction_hash,
transaction_index,
from,
to,
contract_address,
logs: {
logs.into_iter()
.map(|log| {
Log {
address: log.address,
topics: log.topics,
data: log.data,
}
}).collect()
},
logs_bloom: Bloom::default(), // TODO: feed in bloom.
};

let receipt = ethereum::Receipt {
Expand All @@ -407,43 +386,113 @@ impl<T: Trait> Module<T> {
Ok(())
}

fn handle_exec<R>(res: (ExitReason, R, U256, Vec<pallet_evm::Log>))
-> Result<(ExitReason, R, U256, Vec<pallet_evm::Log>), Error<T>> {
match res.0 {
ExitReason::Succeed(_s) => Ok(res),
/// Execute an EVM call or create
pub fn call(
source: H160,
data: Vec<u8>,
value: U256,
gas_limit: u32,
gas_price: U256,
nonce: Option<U256>,
action: TransactionAction,
) -> Result<(Vec<u8>, U256), (sp_runtime::DispatchError, Vec<u8>)> {
let (exit_reason, returned_value, gas_used, _) = Self::execute_evm(
source,
data,
value,
gas_limit,
gas_price,
nonce,
action,
false,
).map_err(|err| (err.into(), Vec::new()))?;

let returned_value = match returned_value.clone() {
ReturnValue::Bytes(data) => data,
ReturnValue::Hash(_) => Vec::new()
};

Self::handle_exit_reason(exit_reason)
.map_err(|err| (err.into(), returned_value.clone()))?;

Ok((returned_value, gas_used))
}
}

impl<T: Trait> Module<T> {
fn execute_evm(
source: H160,
data: Vec<u8>,
value: U256,
gas_limit: u32,
gas_price: U256,
nonce: Option<U256>,
action: TransactionAction,
apply_state: bool,
) -> Result<(ExitReason, ReturnValue, U256, Vec<pallet_evm::Log>), pallet_evm::Error<T>> {
match action {
TransactionAction::Call(target) => pallet_evm::Module::<T>::execute_call(
source,
target,
data,
value,
gas_limit,
gas_price,
nonce,
apply_state,
).map(|(exit_reason, returned_value, gas_used, logs)| {
(exit_reason, ReturnValue::Bytes(returned_value), gas_used, logs)
}),
TransactionAction::Create => pallet_evm::Module::<T>::execute_create(
source,
data,
value,
gas_limit,
gas_price,
nonce,
apply_state,
).map(|(exit_reason, returned_value, gas_used, logs)| {
(exit_reason, ReturnValue::Hash(returned_value), gas_used, logs)
})
}
}

fn handle_exit_reason(exit_reason: ExitReason) -> Result<(), Error<T>> {
match exit_reason {
ExitReason::Succeed(_s) => Ok(()),
ExitReason::Error(e) => Err(Self::parse_exit_error(e)),
ExitReason::Revert(e) => {
match e {
ExitRevert::Reverted => Err(Error::<T>::Reverted),
}
},
}
ExitReason::Fatal(e) => {
match e {
ExitFatal::NotSupported => Err(Error::<T>::NotSupported),
ExitFatal::UnhandledInterrupt => Err(Error::<T>::UnhandledInterrupt),
ExitFatal::CallErrorAsFatal(e_error) => Err(Self::parse_exit_error(e_error)),
ExitFatal::Other(_s) => Err(Error::<T>::ExitFatalOther),
}
},
}
}
}

fn parse_exit_error(exit_error: ExitError) -> Error<T> {
match exit_error {
ExitError::StackUnderflow => return Error::<T>::StackUnderflow,
ExitError::StackOverflow => return Error::<T>::StackOverflow,
ExitError::InvalidJump => return Error::<T>::InvalidJump,
ExitError::InvalidRange => return Error::<T>::InvalidRange,
ExitError::DesignatedInvalid => return Error::<T>::DesignatedInvalid,
ExitError::CallTooDeep => return Error::<T>::CallTooDeep,
ExitError::CreateCollision => return Error::<T>::CreateCollision,
ExitError::CreateContractLimit => return Error::<T>::CreateContractLimit,
ExitError::OutOfOffset => return Error::<T>::OutOfOffset,
ExitError::OutOfGas => return Error::<T>::OutOfGas,
ExitError::OutOfFund => return Error::<T>::OutOfFund,
ExitError::PCUnderflow => return Error::<T>::PCUnderflow,
ExitError::CreateEmpty => return Error::<T>::CreateEmpty,
ExitError::Other(_s) => return Error::<T>::ExitErrorOther,
ExitError::StackUnderflow => Error::<T>::StackUnderflow,
ExitError::StackOverflow => Error::<T>::StackOverflow,
ExitError::InvalidJump => Error::<T>::InvalidJump,
ExitError::InvalidRange => Error::<T>::InvalidRange,
ExitError::DesignatedInvalid => Error::<T>::DesignatedInvalid,
ExitError::CallTooDeep => Error::<T>::CallTooDeep,
ExitError::CreateCollision => Error::<T>::CreateCollision,
ExitError::CreateContractLimit => Error::<T>::CreateContractLimit,
ExitError::OutOfOffset => Error::<T>::OutOfOffset,
ExitError::OutOfGas => Error::<T>::OutOfGas,
ExitError::OutOfFund => Error::<T>::OutOfFund,
ExitError::PCUnderflow => Error::<T>::PCUnderflow,
ExitError::CreateEmpty => Error::<T>::CreateEmpty,
ExitError::Other(_s) => Error::<T>::ExitErrorOther,
}
}
}
63 changes: 61 additions & 2 deletions frame/ethereum/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

use super::*;
use mock::*;
use rustc_hex::FromHex;
use rustc_hex::{FromHex, ToHex};
use std::str::FromStr;
use ethereum::TransactionSignature;
use frame_support::{
Expand Down Expand Up @@ -146,7 +146,6 @@ fn contract_constructor_should_get_executed() {
assert_eq!(Evm::account_storages(
erc20_address, alice_storage_address
), H256::from_str("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").unwrap())

});
}

Expand Down Expand Up @@ -218,3 +217,63 @@ fn transaction_should_generate_correct_gas_used() {
assert_eq!(Pending::get()[0].2.used_gas, expected_gas);
});
}

#[test]
fn call_should_handle_errors() {
// pragma solidity ^0.6.6;
// contract Test {
// function foo() external pure returns (bool) {
// return true;
// }
// function bar() external pure {
// require(false, "error_msg");
// }
// }
let contract: &str = "608060405234801561001057600080fd5b50610113806100206000396000f3fe6080604052348015600f57600080fd5b506004361060325760003560e01c8063c2985578146037578063febb0f7e146057575b600080fd5b603d605f565b604051808215151515815260200191505060405180910390f35b605d6068565b005b60006001905090565b600060db576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260098152602001807f6572726f725f6d7367000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b56fea2646970667358221220fde68a3968e0e99b16fabf9b2997a78218b32214031f8e07e2c502daf603a69e64736f6c63430006060033";

let (pairs, mut ext) = new_test_ext(1);
let alice = &pairs[0];

ext.execute_with(|| {
assert_ok!(Ethereum::execute(
alice.address,
UnsignedTransaction {
nonce: U256::zero(),
gas_price: U256::from(1),
gas_limit: U256::from(0x100000),
action: ethereum::TransactionAction::Create,
value: U256::zero(),
input: FromHex::from_hex(contract).unwrap(),
}.sign(&alice.private_key),
));

let contract_address: Vec<u8> = FromHex::from_hex("32dcab0ef3fb2de2fce1d2e0799d36239671f04a").unwrap();
let foo: Vec<u8> = FromHex::from_hex("c2985578").unwrap();
let bar: Vec<u8> = FromHex::from_hex("febb0f7e").unwrap();

// calling foo will succeed
let (returned, _) = Ethereum::call(
alice.address,
foo,
U256::zero(),
1048576,
U256::from(1),
None,
TransactionAction::Call(H160::from_slice(&contract_address)),
).unwrap();
assert_eq!(returned.to_hex::<String>(), "0000000000000000000000000000000000000000000000000000000000000001".to_owned());

// calling bar will revert
let (error, returned) = Ethereum::call(
alice.address,
bar,
U256::zero(),
1048576,
U256::from(1),
None,
TransactionAction::Call(H160::from_slice(&contract_address))
).err().unwrap();
assert_eq!(error, Error::<Test>::Reverted.into());
assert_eq!(returned.to_hex::<String>(), "08c379a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000096572726f725f6d73670000000000000000000000000000000000000000000000".to_owned());
});
}
1 change: 1 addition & 0 deletions rpc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@ rlp = "0.4"
pallet-ethereum = "0.1"
futures = { version = "0.3.1", features = ["compat"] }
sha3 = "0.8"
rustc-hex = { version = "2.1.0", default-features = false }
Loading

0 comments on commit 6173a3c

Please sign in to comment.