mod ganache;
mod geth;
mod nethermind;
mod parity;
pub(crate) mod revert;
use crate::transaction::TransactionResult;
use ethcontract_common::abi::{Error as AbiError, Event, Function};
use ethcontract_common::abiext::EventExt;
pub use ethcontract_common::errors::*;
use secp256k1::Error as Secp256k1Error;
use std::num::ParseIntError;
use thiserror::Error;
use uint::FromDecStrErr;
use web3::error::Error as Web3Error;
use web3::types::{Log, TransactionReceipt, H256};
#[derive(Debug, Error)]
pub enum DeployError {
#[error("web3 error: {0}")]
Web3(#[from] Web3Error),
#[error("could not find deployed contract for network {0}")]
NotFound(String),
#[error("could not link library {0}")]
Link(#[from] LinkError),
#[error("can not deploy contract with empty bytecode")]
EmptyBytecode,
#[error("error ABI ecoding deployment parameters: {0}")]
Abi(#[from] AbiError),
#[error("error executing contract deployment transaction: {0}")]
Tx(#[from] ExecutionError),
#[error("contract deployment transaction pending: {0}")]
Pending(H256),
}
#[derive(Debug, Error)]
pub enum ExecutionError {
#[error("web3 error: {0}")]
Web3(Web3Error),
#[error("abi decode error: {0}")]
AbiDecode(#[from] AbiError),
#[error("parse chain ID error: {0}")]
Parse(#[from] ParseIntError),
#[error("no local accounts")]
NoLocalAccounts,
#[error("contract call reverted with message: {0:?}")]
Revert(Option<String>),
#[error("contract call executed an invalid opcode")]
InvalidOpcode,
#[error("transaction confirmation timed-out")]
ConfirmTimeout(Box<TransactionResult>),
#[error("transaction failed: {:?}", .0.transaction_hash)]
Failure(Box<TransactionReceipt>),
#[error("missing transaction {0:?}")]
MissingTransaction(H256),
#[error("pending transaction {0:?}, not yet part of a block")]
PendingTransaction(H256),
#[error("unexepected removed log when querying past logs")]
RemovedLog(Box<Log>),
#[error("log stream ended unexpectedly")]
StreamEndedUnexpectedly,
#[error("tokenization error: {0}")]
Tokenization(#[from] crate::tokens::Error),
#[error("transaction hash returned from node when sending raw transaction does not match expected hash")]
UnexpectedTransactionHash,
}
impl From<Web3Error> for ExecutionError {
fn from(err: Web3Error) -> Self {
if let Web3Error::Rpc(jsonrpc_err) = &err {
if let Some(err) = ganache::get_encoded_error(jsonrpc_err) {
return err;
}
if let Some(err) = parity::get_encoded_error(jsonrpc_err) {
return err;
}
if let Some(err) = geth::get_encoded_error(jsonrpc_err) {
return err;
}
if let Some(err) = nethermind::get_encoded_error(jsonrpc_err) {
return err;
}
}
ExecutionError::Web3(err)
}
}
#[derive(Debug, Error)]
#[error("method '{signature}' failure: {inner}")]
pub struct MethodError {
pub signature: String,
#[source]
pub inner: ExecutionError,
}
impl MethodError {
pub fn new<I: Into<ExecutionError>>(function: &Function, inner: I) -> Self {
MethodError::from_parts(function.signature(), inner.into())
}
pub fn from_parts(signature: String, inner: ExecutionError) -> Self {
MethodError { signature, inner }
}
}
#[derive(Debug, Error)]
#[error("event '{signature}' failure: {inner}")]
pub struct EventError {
pub signature: String,
#[source]
pub inner: ExecutionError,
}
impl EventError {
pub fn new<I: Into<ExecutionError>>(event: &Event, inner: I) -> Self {
EventError::from_parts(event.abi_signature(), inner.into())
}
pub fn from_parts(signature: String, inner: ExecutionError) -> Self {
EventError { signature, inner }
}
}
#[derive(Debug, Error)]
#[error("invalid private key")]
pub struct InvalidPrivateKey;
impl From<Secp256k1Error> for InvalidPrivateKey {
fn from(err: Secp256k1Error) -> Self {
match err {
Secp256k1Error::InvalidSecretKey => {}
_ => {
debug_assert!(false, "invalid conversion to InvalidPrivateKey error");
}
}
InvalidPrivateKey
}
}
#[derive(Clone, Copy, Debug, Error)]
#[error("output of range integer conversion attempted")]
pub struct TryFromBigIntError;
#[derive(Clone, Copy, Debug, Error)]
pub enum ParseI256Error {
#[error("invalid digit found in string")]
InvalidDigit,
#[error("number does not fit in 256-bit integer")]
IntegerOverflow,
}
impl From<FromDecStrErr> for ParseI256Error {
fn from(err: FromDecStrErr) -> Self {
match err {
FromDecStrErr::InvalidCharacter => ParseI256Error::InvalidDigit,
FromDecStrErr::InvalidLength => ParseI256Error::IntegerOverflow,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::error::Error;
#[test]
fn from_ganache_encoded_error() {
let web3_err = Web3Error::Rpc(ganache::rpc_error("invalid opcode", None));
let err = ExecutionError::from(web3_err);
assert!(
matches!(err, ExecutionError::InvalidOpcode),
"bad error conversion {:?}",
err
);
}
#[test]
fn from_parity_encoded_error() {
let web3_err = Web3Error::Rpc(parity::rpc_error("Bad instruction fd"));
let err = ExecutionError::from(web3_err);
assert!(
matches!(err, ExecutionError::InvalidOpcode),
"bad error conversion {:?}",
err
);
}
#[test]
fn all_errors_are_boxable_errors() {
fn assert_boxable_error<T: Error + Send + Sync + 'static>() {}
assert_boxable_error::<DeployError>();
assert_boxable_error::<ExecutionError>();
assert_boxable_error::<MethodError>();
assert_boxable_error::<InvalidPrivateKey>();
}
}