Make decl_error! errors usable (#4449)

* Make `decl_error!` errors usable

This pr implements support for returning errors of different pallets in
a pallet. These errors need to be declared with `decl_error!`.

The pr changes the following:

- Each dispatchable function now returns a `DispatchResult` which is an
alias for `Result<(), DispatchError>`.
- `DispatchError` is an enum that has 4 variants:
  - `Other`: For storing string error messages
  - `CannotLookup`: Variant that is returned when something returns a
  `sp_runtime::LookupError`
  - `BadOrigin`: Variant that is returned for any kind of bad origin
  - `Module`: The error of a specific module. Contains the `index`,
  `error` and the `message`. The index is the index of the module in
  `construct_runtime!`. `error` is the index of the error in the error
  enum declared by `decl_error!`. `message` is the message to the error
  variant (this will not be encoded).
- `construct_runtime!` now creates a new struct `ModuleToIndex`. This
struct implements the trait `ModuleToIndex`.
- `frame_system::Trait` has a new associated type: `ModuleToIndex` that
expects the `ModuleToIndex` generated by `construct_runtime!`.
- All error strings returned in any module are being converted now to `DispatchError`.
- `BadOrigin` is the default error returned by any type that implements `EnsureOrigin`.

* Fix frame system benchmarks
This commit is contained in:
Bastian Köcher
2019-12-19 14:01:52 +01:00
committed by Gavin Wood
parent 0aab5c659e
commit 8e393aa5a8
69 changed files with 868 additions and 611 deletions
+34 -20
View File
@@ -23,7 +23,7 @@ use crate::rent;
use sp_std::prelude::*;
use sp_runtime::traits::{Bounded, CheckedAdd, CheckedSub, Zero};
use frame_support::{
storage::unhashed,
storage::unhashed, dispatch::DispatchError,
traits::{WithdrawReason, Currency, Time, Randomness},
};
@@ -66,7 +66,7 @@ impl ExecReturnValue {
/// non-existent destination contract, etc.).
#[cfg_attr(test, derive(sp_runtime::RuntimeDebug))]
pub struct ExecError {
pub reason: &'static str,
pub reason: DispatchError,
/// This is an allocated buffer that may be reused. The buffer must be cleared explicitly
/// before reuse.
pub buffer: Vec<u8>,
@@ -83,7 +83,9 @@ macro_rules! try_or_exec_error {
($e:expr, $buffer:expr) => {
match $e {
Ok(val) => val,
Err(reason) => return Err($crate::exec::ExecError { reason, buffer: $buffer }),
Err(reason) => return Err(
$crate::exec::ExecError { reason: reason.into(), buffer: $buffer }
),
}
}
}
@@ -336,7 +338,7 @@ where
) -> ExecResult {
if self.depth == self.config.max_depth as usize {
return Err(ExecError {
reason: "reached maximum depth, cannot make a call",
reason: "reached maximum depth, cannot make a call".into(),
buffer: input_data,
});
}
@@ -346,7 +348,7 @@ where
.is_out_of_gas()
{
return Err(ExecError {
reason: "not enough gas to pay base call fee",
reason: "not enough gas to pay base call fee".into(),
buffer: input_data,
});
}
@@ -359,7 +361,7 @@ where
// Calls to dead contracts always fail.
if let Some(ContractInfo::Tombstone(_)) = contract_info {
return Err(ExecError {
reason: "contract has been evicted",
reason: "contract has been evicted".into(),
buffer: input_data,
});
};
@@ -404,7 +406,7 @@ where
.expect("a nested execution context must have a parent; qed");
if parent.is_live(&dest) {
return Err(ExecError {
reason: "contract cannot be destroyed during recursive execution",
reason: "contract cannot be destroyed during recursive execution".into(),
buffer: output.data,
});
}
@@ -428,7 +430,7 @@ where
) -> Result<(T::AccountId, ExecReturnValue), ExecError> {
if self.depth == self.config.max_depth as usize {
return Err(ExecError {
reason: "reached maximum depth, cannot instantiate",
reason: "reached maximum depth, cannot instantiate".into(),
buffer: input_data,
});
}
@@ -438,7 +440,7 @@ where
.is_out_of_gas()
{
return Err(ExecError {
reason: "not enough gas to pay base instantiate fee",
reason: "not enough gas to pay base instantiate fee".into(),
buffer: input_data,
});
}
@@ -488,7 +490,7 @@ where
// Error out if insufficient remaining balance.
if nested.overlay.get_balance(&dest) < nested.config.existential_deposit {
return Err(ExecError {
reason: "insufficient remaining balance",
reason: "insufficient remaining balance".into(),
buffer: output.data,
});
}
@@ -603,7 +605,7 @@ fn transfer<'a, T: Trait, V: Vm<T>, L: Loader<T>>(
dest: &T::AccountId,
value: BalanceOf<T>,
ctx: &mut ExecutionContext<'a, T, V, L>,
) -> Result<(), &'static str> {
) -> Result<(), DispatchError> {
use self::TransferCause::*;
use self::TransferFeeKind::*;
@@ -637,23 +639,28 @@ fn transfer<'a, T: Trait, V: Vm<T>, L: Loader<T>>(
};
if gas_meter.charge(ctx.config, token).is_out_of_gas() {
return Err("not enough gas to pay transfer fee");
Err("not enough gas to pay transfer fee")?
}
// We allow balance to go below the existential deposit here:
let from_balance = ctx.overlay.get_balance(transactor);
let new_from_balance = match from_balance.checked_sub(&value) {
Some(b) => b,
None => return Err("balance too low to send value"),
None => Err("balance too low to send value")?,
};
if would_create && value < ctx.config.existential_deposit {
return Err("value too low to create account");
Err("value too low to create account")?
}
T::Currency::ensure_can_withdraw(transactor, value, WithdrawReason::Transfer.into(), new_from_balance)?;
T::Currency::ensure_can_withdraw(
transactor,
value,
WithdrawReason::Transfer.into(),
new_from_balance,
)?;
let new_to_balance = match to_balance.checked_add(&value) {
Some(b) => b,
None => return Err("destination balance too high to receive value"),
None => Err("destination balance too high to receive value")?,
};
if transactor != dest {
@@ -821,6 +828,7 @@ mod tests {
};
use std::{cell::RefCell, rc::Rc, collections::HashMap, marker::PhantomData};
use assert_matches::assert_matches;
use sp_runtime::DispatchError;
const ALICE: u64 = 1;
const BOB: u64 = 2;
@@ -1176,7 +1184,10 @@ mod tests {
assert_matches!(
result,
Err(ExecError { reason: "balance too low to send value", buffer: _ })
Err(ExecError {
reason: DispatchError::Other("balance too low to send value"),
buffer: _,
})
);
assert_eq!(ctx.overlay.get_balance(&origin), 0);
assert_eq!(ctx.overlay.get_balance(&dest), 0);
@@ -1313,7 +1324,10 @@ mod tests {
// Verify that we've got proper error and set `reached_bottom`.
assert_matches!(
r,
Err(ExecError { reason: "reached maximum depth, cannot make a call", buffer: _ })
Err(ExecError {
reason: DispatchError::Other("reached maximum depth, cannot make a call"),
buffer: _,
})
);
*reached_bottom = true;
} else {
@@ -1583,7 +1597,7 @@ mod tests {
let mut loader = MockLoader::empty();
let dummy_ch = loader.insert(
|_| Err(ExecError { reason: "It's a trap!", buffer: Vec::new() })
|_| Err(ExecError { reason: "It's a trap!".into(), buffer: Vec::new() })
);
let instantiator_ch = loader.insert({
let dummy_ch = dummy_ch.clone();
@@ -1596,7 +1610,7 @@ mod tests {
ctx.gas_meter,
vec![]
),
Err(ExecError { reason: "It's a trap!", buffer: _ })
Err(ExecError { reason: DispatchError::Other("It's a trap!"), buffer: _ })
);
exec_success()