1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
//! Module with common error types.

mod ganache;
mod geth;
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};

/// Error that can occur while locating a deployed contract.
#[derive(Debug, Error)]
pub enum DeployError {
    /// An error occured while performing a web3 call.
    #[error("web3 error: {0}")]
    Web3(#[from] Web3Error),

    /// No previously deployed contract could be found on the network being used
    /// by the current `web3` provider.
    #[error("could not find deployed contract for network {0}")]
    NotFound(String),

    /// Error linking a contract with a deployed library.
    #[error("could not link library {0}")]
    Link(#[from] LinkError),

    /// Attempted to deploy a contract when empty bytecode. This can happen when
    /// attempting to deploy a contract that is actually an interface.
    #[error("can not deploy contract with empty bytecode")]
    EmptyBytecode,

    /// An error occured encoding deployment parameters with the contract ABI.
    #[error("error ABI ecoding deployment parameters: {0}")]
    Abi(#[from] AbiError),

    /// Error executing contract deployment transaction.
    #[error("error executing contract deployment transaction: {0}")]
    Tx(#[from] ExecutionError),

    /// Transaction was unable to confirm and is still pending. The contract
    /// address cannot be determined.
    #[error("contract deployment transaction pending: {0}")]
    Pending(H256),
}

/// Error that can occur while executing a contract call or transaction.
#[derive(Debug, Error)]
pub enum ExecutionError {
    /// An error occured while performing a web3 call.
    #[error("web3 error: {0}")]
    Web3(Web3Error),

    /// An error occured while ABI decoding the result of a contract method
    /// call.
    #[error("abi decode error: {0}")]
    AbiDecode(#[from] AbiError),

    /// An error occured while parsing chain ID received from a Web3 call.
    #[error("parse chain ID error: {0}")]
    Parse(#[from] ParseIntError),

    /// An error indicating that an attempt was made to build or send a locally
    /// signed transaction to a node without any local accounts.
    #[error("no local accounts")]
    NoLocalAccounts,

    /// A contract call reverted.
    #[error("contract call reverted with message: {0:?}")]
    Revert(Option<String>),

    /// A contract call executed an invalid opcode.
    #[error("contract call executed an invalid opcode")]
    InvalidOpcode,

    /// A contract transaction failed to confirm within the block timeout limit.
    #[error("transaction confirmation timed-out")]
    ConfirmTimeout(Box<TransactionResult>),

    /// Transaction failure (e.g. out of gas or revert).
    #[error("transaction failed: {:?}", .0.transaction_hash)]
    Failure(Box<TransactionReceipt>),

    /// Failed to find a transaction by hash.
    #[error("missing transaction {0:?}")]
    MissingTransaction(H256),

    /// Failed to get a block for a pending transaction that has not yet been
    /// mined.
    #[error("pending transaction {0:?}, not yet part of a block")]
    PendingTransaction(H256),

    /// A removed log was received when querying past logs.
    #[error("unexepected removed log when querying past logs")]
    RemovedLog(Box<Log>),

    /// A stream ended unexpectedly.
    #[error("log stream ended unexpectedly")]
    StreamEndedUnexpectedly,

    /// A tokenization related error.
    #[error("tokenization error: {0}")]
    Tokenization(#[from] crate::tokens::Error),
}

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;
            }
        }

        ExecutionError::Web3(err)
    }
}

/// Error that can occur while executing a contract call or transaction.
#[derive(Debug, Error)]
#[error("method '{signature}' failure: {inner}")]
pub struct MethodError {
    /// The signature of the failed method.
    pub signature: String,

    /// The inner execution error that for the method transaction that failed.
    #[source]
    pub inner: ExecutionError,
}

impl MethodError {
    /// Create a new `MethodError` from an ABI function specification and an
    /// inner `ExecutionError`.
    pub fn new<I: Into<ExecutionError>>(function: &Function, inner: I) -> Self {
        MethodError::from_parts(function.signature(), inner.into())
    }

    /// Create a `MethodError` from its signature and inner `ExecutionError`.
    pub fn from_parts(signature: String, inner: ExecutionError) -> Self {
        MethodError { signature, inner }
    }
}

/// Error that can occur while streaming contract events.
#[derive(Debug, Error)]
#[error("event '{signature}' failure: {inner}")]
pub struct EventError {
    /// The signature of the failed event.
    pub signature: String,

    /// The inner execution error that for the method transaction that failed.
    #[source]
    pub inner: ExecutionError,
}

impl EventError {
    /// Create a new `EventError` from an ABI function specification and an
    /// inner `ExecutionError`.
    pub fn new<I: Into<ExecutionError>>(event: &Event, inner: I) -> Self {
        EventError::from_parts(event.abi_signature(), inner.into())
    }

    /// Create a `EventError` from its signature and inner `ExecutionError`.
    pub fn from_parts(signature: String, inner: ExecutionError) -> Self {
        EventError { signature, inner }
    }
}

/// An error indicating an invalid private key. Private keys for secp256k1 must
/// be exactly 32 bytes and fall within the range `[1, n)` where `n` is the
/// order of the generator point of the curve.
#[derive(Debug, Error)]
#[error("invalid private key")]
pub struct InvalidPrivateKey;

impl From<Secp256k1Error> for InvalidPrivateKey {
    fn from(err: Secp256k1Error) -> Self {
        match err {
            Secp256k1Error::InvalidSecretKey => {}
            _ => {
                // NOTE: Assert that we never try to make this conversion with
                //   errors not related to `SecretKey`.
                debug_assert!(false, "invalid conversion to InvalidPrivateKey error");
            }
        }
        InvalidPrivateKey
    }
}

/// The error type that is returned when conversion to or from a 256-bit integer
/// fails.
#[derive(Clone, Copy, Debug, Error)]
#[error("output of range integer conversion attempted")]
pub struct TryFromBigIntError;

/// The error type that is returned when parsing a 256-bit signed integer.
#[derive(Clone, Copy, Debug, Error)]
pub enum ParseI256Error {
    /// Error that occurs when an invalid digit is encountered while parsing.
    #[error("invalid digit found in string")]
    InvalidDigit,

    /// Error that occurs when the number is too large or too small (negative)
    /// and does not fit in a 256-bit signed integer.
    #[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>();
    }
}