Skip to content

Commit c35a436

Browse files
goldoneengavofyork
andcommitted
pallet-evm: return Ok(()) when EVM execution fails (#6493)
* pallet-evm: return Ok(()) when EVM execution fails * Bump spec version * Init test module * Add fail_call_return_ok test * Fix tests and use full match pattern Co-authored-by: Gav Wood <gavin@parity.io>
1 parent fe6b033 commit c35a436

File tree

2 files changed

+213
-29
lines changed

2 files changed

+213
-29
lines changed

src/lib.rs

Lines changed: 44 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#![cfg_attr(not(feature = "std"), no_std)]
2222

2323
mod backend;
24+
mod tests;
2425

2526
pub use crate::backend::{Account, Log, Vicinity, Backend};
2627

@@ -144,7 +145,7 @@ pub trait Trait: frame_system::Trait + pallet_timestamp::Trait {
144145
/// Precompiles associated with this EVM engine.
145146
type Precompiles: Precompiles;
146147
/// Chain ID of EVM.
147-
type ChainId: Get<U256>;
148+
type ChainId: Get<u64>;
148149

149150
/// EVM config used in the module.
150151
fn config() -> &'static Config {
@@ -201,6 +202,12 @@ decl_event! {
201202
Log(Log),
202203
/// A contract has been created at given address.
203204
Created(H160),
205+
/// A contract was attempted to be created, but the execution failed.
206+
CreatedFailed(H160),
207+
/// A contract has been executed successfully with states applied.
208+
Executed(H160),
209+
/// A contract has been executed with errors. States are reverted with only gas fees applied.
210+
ExecutedFailed(H160),
204211
/// A deposit has been made at a given address.
205212
BalanceDeposit(AccountId, H160, U256),
206213
/// A withdrawal has been made from a given address.
@@ -220,12 +227,6 @@ decl_error! {
220227
WithdrawFailed,
221228
/// Gas price is too low.
222229
GasPriceTooLow,
223-
/// Call failed
224-
ExitReasonFailed,
225-
/// Call reverted
226-
ExitReasonRevert,
227-
/// Call returned VM fatal error
228-
ExitReasonFatal,
229230
/// Nonce is invalid
230231
InvalidNonce,
231232
}
@@ -300,15 +301,24 @@ decl_module! {
300301
let sender = ensure_signed(origin)?;
301302
let source = T::ConvertAccountId::convert_account_id(&sender);
302303

303-
Self::execute_call(
304+
match Self::execute_call(
304305
source,
305306
target,
306307
input,
307308
value,
308309
gas_limit,
309310
gas_price,
310311
nonce,
311-
).map_err(Into::into)
312+
)? {
313+
ExitReason::Succeed(_) => {
314+
Module::<T>::deposit_event(Event::<T>::Executed(target));
315+
},
316+
ExitReason::Error(_) | ExitReason::Revert(_) | ExitReason::Fatal(_) => {
317+
Module::<T>::deposit_event(Event::<T>::ExecutedFailed(target));
318+
},
319+
}
320+
321+
Ok(())
312322
}
313323

314324
/// Issue an EVM create operation. This is similar to a contract creation transaction in
@@ -327,16 +337,22 @@ decl_module! {
327337
let sender = ensure_signed(origin)?;
328338
let source = T::ConvertAccountId::convert_account_id(&sender);
329339

330-
let create_address = Self::execute_create(
340+
match Self::execute_create(
331341
source,
332342
init,
333343
value,
334344
gas_limit,
335345
gas_price,
336346
nonce
337-
)?;
347+
)? {
348+
(create_address, ExitReason::Succeed(_)) => {
349+
Module::<T>::deposit_event(Event::<T>::Created(create_address));
350+
},
351+
(create_address, _) => {
352+
Module::<T>::deposit_event(Event::<T>::CreatedFailed(create_address));
353+
},
354+
}
338355

339-
Module::<T>::deposit_event(Event::<T>::Created(create_address));
340356
Ok(())
341357
}
342358

@@ -356,17 +372,23 @@ decl_module! {
356372
let sender = ensure_signed(origin)?;
357373
let source = T::ConvertAccountId::convert_account_id(&sender);
358374

359-
let create_address = Self::execute_create2(
375+
match Self::execute_create2(
360376
source,
361377
init,
362378
salt,
363379
value,
364380
gas_limit,
365381
gas_price,
366382
nonce
367-
)?;
383+
)? {
384+
(create_address, ExitReason::Succeed(_)) => {
385+
Module::<T>::deposit_event(Event::<T>::Created(create_address));
386+
},
387+
(create_address, _) => {
388+
Module::<T>::deposit_event(Event::<T>::CreatedFailed(create_address));
389+
},
390+
}
368391

369-
Module::<T>::deposit_event(Event::<T>::Created(create_address));
370392
Ok(())
371393
}
372394
}
@@ -413,7 +435,7 @@ impl<T: Trait> Module<T> {
413435
gas_limit: u32,
414436
gas_price: U256,
415437
nonce: Option<U256>
416-
) -> Result<H160, Error<T>> {
438+
) -> Result<(H160, ExitReason), Error<T>> {
417439
Self::execute_evm(
418440
source,
419441
value,
@@ -442,7 +464,7 @@ impl<T: Trait> Module<T> {
442464
gas_limit: u32,
443465
gas_price: U256,
444466
nonce: Option<U256>
445-
) -> Result<H160, Error<T>> {
467+
) -> Result<(H160, ExitReason), Error<T>> {
446468
let code_hash = H256::from_slice(Keccak256::digest(&init).as_slice());
447469
Self::execute_evm(
448470
source,
@@ -473,8 +495,8 @@ impl<T: Trait> Module<T> {
473495
gas_limit: u32,
474496
gas_price: U256,
475497
nonce: Option<U256>,
476-
) -> Result<(), Error<T>> {
477-
Self::execute_evm(
498+
) -> Result<ExitReason, Error<T>> {
499+
Ok(Self::execute_evm(
478500
source,
479501
value,
480502
gas_limit,
@@ -487,7 +509,7 @@ impl<T: Trait> Module<T> {
487509
input,
488510
gas_limit as usize,
489511
)),
490-
)
512+
)?.1)
491513
}
492514

493515
/// Execute an EVM operation.
@@ -498,7 +520,7 @@ impl<T: Trait> Module<T> {
498520
gas_price: U256,
499521
nonce: Option<U256>,
500522
f: F,
501-
) -> Result<R, Error<T>> where
523+
) -> Result<(R, ExitReason), Error<T>> where
502524
F: FnOnce(&mut StackExecutor<Backend<T>>) -> (R, ExitReason),
503525
{
504526
let vicinity = Vicinity {
@@ -527,19 +549,12 @@ impl<T: Trait> Module<T> {
527549

528550
let (retv, reason) = f(&mut executor);
529551

530-
let ret = match reason {
531-
ExitReason::Succeed(_) => Ok(retv),
532-
ExitReason::Error(_) => Err(Error::<T>::ExitReasonFailed),
533-
ExitReason::Revert(_) => Err(Error::<T>::ExitReasonRevert),
534-
ExitReason::Fatal(_) => Err(Error::<T>::ExitReasonFatal),
535-
};
536-
537552
let actual_fee = executor.fee(gas_price);
538553
executor.deposit(source, total_fee.saturating_sub(actual_fee));
539554

540555
let (values, logs) = executor.deconstruct();
541556
backend.apply(values, logs, true);
542557

543-
ret
558+
Ok((retv, reason))
544559
}
545560
}

src/tests.rs

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
#![cfg(test)]
2+
3+
use super::*;
4+
5+
use std::{str::FromStr, collections::BTreeMap};
6+
use frame_support::{
7+
assert_ok, impl_outer_origin, parameter_types, impl_outer_dispatch,
8+
};
9+
use sp_core::H256;
10+
// The testing primitives are very useful for avoiding having to work with signatures
11+
// or public keys. `u64` is used as the `AccountId` and no `Signature`s are required.
12+
use sp_runtime::{
13+
Perbill,
14+
testing::Header,
15+
traits::{BlakeTwo256, IdentityLookup},
16+
};
17+
18+
impl_outer_origin! {
19+
pub enum Origin for Test where system = frame_system {}
20+
}
21+
22+
impl_outer_dispatch! {
23+
pub enum OuterCall for Test where origin: Origin {
24+
self::EVM,
25+
}
26+
}
27+
28+
// For testing the pallet, we construct most of a mock runtime. This means
29+
// first constructing a configuration type (`Test`) which `impl`s each of the
30+
// configuration traits of pallets we want to use.
31+
#[derive(Clone, Eq, PartialEq)]
32+
pub struct Test;
33+
parameter_types! {
34+
pub const BlockHashCount: u64 = 250;
35+
pub const MaximumBlockWeight: Weight = 1024;
36+
pub const MaximumBlockLength: u32 = 2 * 1024;
37+
pub const AvailableBlockRatio: Perbill = Perbill::one();
38+
}
39+
impl frame_system::Trait for Test {
40+
type BaseCallFilter = ();
41+
type Origin = Origin;
42+
type Index = u64;
43+
type BlockNumber = u64;
44+
type Hash = H256;
45+
type Call = OuterCall;
46+
type Hashing = BlakeTwo256;
47+
type AccountId = H256;
48+
type Lookup = IdentityLookup<Self::AccountId>;
49+
type Header = Header;
50+
type Event = ();
51+
type BlockHashCount = BlockHashCount;
52+
type MaximumBlockWeight = MaximumBlockWeight;
53+
type DbWeight = ();
54+
type BlockExecutionWeight = ();
55+
type ExtrinsicBaseWeight = ();
56+
type MaximumExtrinsicWeight = MaximumBlockWeight;
57+
type MaximumBlockLength = MaximumBlockLength;
58+
type AvailableBlockRatio = AvailableBlockRatio;
59+
type Version = ();
60+
type ModuleToIndex = ();
61+
type AccountData = pallet_balances::AccountData<u64>;
62+
type OnNewAccount = ();
63+
type OnKilledAccount = ();
64+
}
65+
66+
parameter_types! {
67+
pub const ExistentialDeposit: u64 = 1;
68+
}
69+
impl pallet_balances::Trait for Test {
70+
type Balance = u64;
71+
type DustRemoval = ();
72+
type Event = ();
73+
type ExistentialDeposit = ExistentialDeposit;
74+
type AccountStore = System;
75+
}
76+
77+
parameter_types! {
78+
pub const MinimumPeriod: u64 = 1000;
79+
}
80+
impl pallet_timestamp::Trait for Test {
81+
type Moment = u64;
82+
type OnTimestampSet = ();
83+
type MinimumPeriod = MinimumPeriod;
84+
}
85+
86+
/// Fixed gas price of `0`.
87+
pub struct FixedGasPrice;
88+
impl FeeCalculator for FixedGasPrice {
89+
fn min_gas_price() -> U256 {
90+
// Gas price is always one token per gas.
91+
0.into()
92+
}
93+
}
94+
parameter_types! {
95+
pub const EVMModuleId: ModuleId = ModuleId(*b"py/evmpa");
96+
}
97+
impl Trait for Test {
98+
type ChainId = SystemChainId;
99+
type ModuleId = EVMModuleId;
100+
type FeeCalculator = FixedGasPrice;
101+
type ConvertAccountId = HashTruncateConvertAccountId<BlakeTwo256>;
102+
type Currency = Balances;
103+
type Event = Event<Test>;
104+
type Precompiles = ();
105+
}
106+
107+
type System = frame_system::Module<Test>;
108+
type Balances = pallet_balances::Module<Test>;
109+
type EVM = Module<Test>;
110+
111+
// This function basically just builds a genesis storage key/value store according to
112+
// our desired mockup.
113+
pub fn new_test_ext() -> sp_io::TestExternalities {
114+
let mut t = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();
115+
116+
let mut accounts = BTreeMap::new();
117+
accounts.insert(
118+
H160::from_str("1000000000000000000000000000000000000001").unwrap(),
119+
GenesisAccount {
120+
nonce: U256::from(1),
121+
balance: U256::from(1000000),
122+
storage: Default::default(),
123+
code: vec![
124+
0x00, // STOP
125+
],
126+
}
127+
);
128+
accounts.insert(
129+
H160::from_str("1000000000000000000000000000000000000002").unwrap(),
130+
GenesisAccount {
131+
nonce: U256::from(1),
132+
balance: U256::from(1000000),
133+
storage: Default::default(),
134+
code: vec![
135+
0xff, // INVALID
136+
],
137+
}
138+
);
139+
140+
// We use default for brevity, but you can configure as desired if needed.
141+
pallet_balances::GenesisConfig::<Test>::default().assimilate_storage(&mut t).unwrap();
142+
GenesisConfig { accounts }.assimilate_storage(&mut t).unwrap();
143+
t.into()
144+
}
145+
146+
#[test]
147+
fn fail_call_return_ok() {
148+
new_test_ext().execute_with(|| {
149+
assert_ok!(EVM::call(
150+
Origin::signed(H256::default()),
151+
H160::from_str("1000000000000000000000000000000000000001").unwrap(),
152+
Vec::new(),
153+
U256::default(),
154+
1000000,
155+
U256::default(),
156+
None,
157+
));
158+
159+
assert_ok!(EVM::call(
160+
Origin::signed(H256::default()),
161+
H160::from_str("1000000000000000000000000000000000000002").unwrap(),
162+
Vec::new(),
163+
U256::default(),
164+
1000000,
165+
U256::default(),
166+
None,
167+
));
168+
});
169+
}

0 commit comments

Comments
 (0)