use enum_iterator::Sequence;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "codec")]
use {
parity_scale_codec::{Decode, Encode},
scale_decode::DecodeAsType,
scale_encode::EncodeAsType,
scale_info::TypeInfo,
};
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Sequence, thiserror::Error)]
#[cfg_attr(
feature = "codec",
derive(Encode, EncodeAsType, Decode, DecodeAsType, TypeInfo)
)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub enum ReplyCode {
#[error("Success reply sent due to {0}")]
Success(#[from] SuccessReplyReason) = 0,
#[error("Error reply sent due to {0}")]
Error(#[from] ErrorReplyReason) = 1,
#[error("<unsupported reply code>")]
Unsupported = 255,
}
impl ReplyCode {
fn discriminant(&self) -> u8 {
unsafe { *<*const _>::from(self).cast::<u8>() }
}
pub fn to_bytes(self) -> [u8; 4] {
let mut bytes = [self.discriminant(), 0, 0, 0];
match self {
Self::Success(reason) => bytes[1..].copy_from_slice(&reason.to_bytes()),
Self::Error(reason) => bytes[1..].copy_from_slice(&reason.to_bytes()),
Self::Unsupported => {}
}
bytes
}
pub fn from_bytes(bytes: [u8; 4]) -> Self {
match bytes[0] {
b if Self::Success(SuccessReplyReason::Unsupported).discriminant() == b => {
let reason_bytes = bytes[1..].try_into().unwrap_or_else(|_| unreachable!());
Self::Success(SuccessReplyReason::from_bytes(reason_bytes))
}
b if Self::Error(ErrorReplyReason::Unsupported).discriminant() == b => {
let reason_bytes = bytes[1..].try_into().unwrap_or_else(|_| unreachable!());
Self::Error(ErrorReplyReason::from_bytes(reason_bytes))
}
_ => Self::Unsupported,
}
}
pub fn error(reason: impl Into<ErrorReplyReason>) -> Self {
Self::Error(reason.into())
}
pub fn is_success(&self) -> bool {
matches!(self, Self::Success(_))
}
pub fn is_error(&self) -> bool {
matches!(self, Self::Error(_))
}
pub fn is_unsupported(&self) -> bool {
matches!(self, Self::Unsupported)
}
}
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Sequence, thiserror::Error)]
#[cfg_attr(
feature = "codec",
derive(Encode, EncodeAsType, Decode, DecodeAsType, TypeInfo)
)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub enum SuccessReplyReason {
#[error("automatic sending")]
Auto = 0,
#[error("manual sending")]
Manual = 1,
#[error("<unsupported reason>")]
Unsupported = 255,
}
impl SuccessReplyReason {
fn to_bytes(self) -> [u8; 3] {
[self as u8, 0, 0]
}
fn from_bytes(bytes: [u8; 3]) -> Self {
match bytes[0] {
b if Self::Auto as u8 == b => Self::Auto,
b if Self::Manual as u8 == b => Self::Manual,
_ => Self::Unsupported,
}
}
}
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Sequence, thiserror::Error)]
#[cfg_attr(
feature = "codec",
derive(Encode, EncodeAsType, Decode, DecodeAsType, TypeInfo)
)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub enum ErrorReplyReason {
#[error("execution error ({0})")]
Execution(#[from] SimpleExecutionError) = 0,
#[error("destination actor is unavailable ({0})")]
UnavailableActor(#[from] SimpleUnavailableActorError) = 2,
#[error("removal from waitlist")]
RemovedFromWaitlist = 3,
#[error("<unsupported reason>")]
Unsupported = 255,
}
impl ErrorReplyReason {
pub fn is_exited(&self) -> bool {
matches!(
self,
Self::UnavailableActor(SimpleUnavailableActorError::ProgramExited)
)
}
pub fn is_userspace_panic(&self) -> bool {
matches!(self, Self::Execution(SimpleExecutionError::UserspacePanic))
}
fn discriminant(&self) -> u8 {
unsafe { *<*const _>::from(self).cast::<u8>() }
}
fn to_bytes(self) -> [u8; 3] {
let mut bytes = [self.discriminant(), 0, 0];
match self {
Self::Execution(error) => bytes[1..].copy_from_slice(&error.to_bytes()),
Self::UnavailableActor(error) => bytes[1..].copy_from_slice(&error.to_bytes()),
Self::RemovedFromWaitlist | Self::Unsupported => {}
}
bytes
}
fn from_bytes(bytes: [u8; 3]) -> Self {
match bytes[0] {
1 |
4 => Self::Unsupported,
b if Self::Execution(SimpleExecutionError::Unsupported).discriminant() == b => {
let err_bytes = bytes[1..].try_into().unwrap_or_else(|_| unreachable!());
Self::Execution(SimpleExecutionError::from_bytes(err_bytes))
}
b if Self::UnavailableActor(SimpleUnavailableActorError::Unsupported).discriminant() == b => {
let err_bytes = bytes[1..].try_into().unwrap_or_else(|_| unreachable!());
Self::UnavailableActor(SimpleUnavailableActorError::from_bytes(err_bytes))
}
b if Self::RemovedFromWaitlist.discriminant() == b => Self::RemovedFromWaitlist,
_ => Self::Unsupported,
}
}
}
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Sequence, thiserror::Error)]
#[cfg_attr(
feature = "codec",
derive(Encode, EncodeAsType, Decode, DecodeAsType, TypeInfo)
)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub enum SimpleExecutionError {
#[error("Message ran out of gas")]
RanOutOfGas = 0,
#[error("Program reached memory limit")]
MemoryOverflow = 1,
#[error("Message ran into uncatchable error")]
BackendError = 2,
#[error("Message panicked")]
UserspacePanic = 3,
#[error("Program called WASM `unreachable` instruction")]
UnreachableInstruction = 4,
#[error("Program reached stack limit")]
StackLimitExceeded = 5,
#[error("<unsupported error>")]
Unsupported = 255,
}
impl SimpleExecutionError {
fn to_bytes(self) -> [u8; 2] {
[self as u8, 0]
}
fn from_bytes(bytes: [u8; 2]) -> Self {
match bytes[0] {
b if Self::RanOutOfGas as u8 == b => Self::RanOutOfGas,
b if Self::MemoryOverflow as u8 == b => Self::MemoryOverflow,
b if Self::BackendError as u8 == b => Self::BackendError,
b if Self::UserspacePanic as u8 == b => Self::UserspacePanic,
b if Self::UnreachableInstruction as u8 == b => Self::UnreachableInstruction,
b if Self::StackLimitExceeded as u8 == b => Self::StackLimitExceeded,
_ => Self::Unsupported,
}
}
}
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Sequence, thiserror::Error)]
#[cfg_attr(
feature = "codec",
derive(Encode, EncodeAsType, Decode, DecodeAsType, TypeInfo)
)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub enum SimpleUnavailableActorError {
#[error("Program exited")]
ProgramExited = 0,
#[error("Program was terminated due failed initialization")]
InitializationFailure = 1,
#[error("Program is not initialized yet")]
Uninitialized = 2,
#[error("Program was not created")]
ProgramNotCreated = 3,
#[error("Program re-instrumentation failed")]
ReinstrumentationFailure = 4,
#[error("<unsupported error>")]
Unsupported = 255,
}
impl SimpleUnavailableActorError {
fn to_bytes(self) -> [u8; 2] {
[self as u8, 0]
}
fn from_bytes(bytes: [u8; 2]) -> Self {
match bytes[0] {
b if Self::ProgramExited as u8 == b => Self::ProgramExited,
b if Self::InitializationFailure as u8 == b => Self::InitializationFailure,
b if Self::Uninitialized as u8 == b => Self::Uninitialized,
b if Self::ProgramNotCreated as u8 == b => Self::ProgramNotCreated,
b if Self::ReinstrumentationFailure as u8 == b => Self::ReinstrumentationFailure,
_ => Self::Unsupported,
}
}
}
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Sequence, thiserror::Error)]
#[cfg_attr(
feature = "codec",
derive(Encode, EncodeAsType, Decode, DecodeAsType, TypeInfo)
)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub enum SignalCode {
#[error("Signal message sent due to execution error ({0})")]
Execution(#[from] SimpleExecutionError),
#[error("Signal message sent due to removal from waitlist")]
RemovedFromWaitlist,
}
impl SignalCode {
pub const fn to_u32(self) -> u32 {
match self {
Self::Execution(SimpleExecutionError::UserspacePanic) => 100,
Self::Execution(SimpleExecutionError::RanOutOfGas) => 101,
Self::Execution(SimpleExecutionError::BackendError) => 102,
Self::Execution(SimpleExecutionError::MemoryOverflow) => 103,
Self::Execution(SimpleExecutionError::UnreachableInstruction) => 104,
Self::Execution(SimpleExecutionError::StackLimitExceeded) => 105,
Self::RemovedFromWaitlist => 200,
Self::Execution(SimpleExecutionError::Unsupported) => u32::MAX,
}
}
pub const fn from_u32(num: u32) -> Option<Self> {
let res = match num {
v if Self::Execution(SimpleExecutionError::UserspacePanic).to_u32() == v => {
Self::Execution(SimpleExecutionError::UserspacePanic)
}
v if Self::Execution(SimpleExecutionError::RanOutOfGas).to_u32() == v => {
Self::Execution(SimpleExecutionError::RanOutOfGas)
}
v if Self::Execution(SimpleExecutionError::BackendError).to_u32() == v => {
Self::Execution(SimpleExecutionError::BackendError)
}
v if Self::Execution(SimpleExecutionError::MemoryOverflow).to_u32() == v => {
Self::Execution(SimpleExecutionError::MemoryOverflow)
}
v if Self::Execution(SimpleExecutionError::UnreachableInstruction).to_u32() == v => {
Self::Execution(SimpleExecutionError::UnreachableInstruction)
}
v if Self::Execution(SimpleExecutionError::StackLimitExceeded).to_u32() == v => {
Self::Execution(SimpleExecutionError::StackLimitExceeded)
}
v if Self::Execution(SimpleExecutionError::Unsupported).to_u32() == v => {
Self::Execution(SimpleExecutionError::Unsupported)
}
v if Self::RemovedFromWaitlist.to_u32() == v => Self::RemovedFromWaitlist,
_ => return None,
};
Some(res)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_forbidden_codes() {
let codes = [
1, 4, ];
for code in codes {
let err = ErrorReplyReason::from_bytes([code, 0, 0]);
assert_eq!(err, ErrorReplyReason::Unsupported);
}
for code in enum_iterator::all::<ErrorReplyReason>() {
let bytes = code.to_bytes();
assert!(!codes.contains(&bytes[0]));
}
}
#[test]
fn test_reply_code_encode_decode() {
for code in enum_iterator::all::<ReplyCode>() {
let bytes = code.to_bytes();
assert_eq!(code, ReplyCode::from_bytes(bytes));
}
}
#[test]
fn test_signal_code_encode_decode() {
for signal in enum_iterator::all::<SignalCode>() {
let code = signal.to_u32();
assert_eq!(signal, SignalCode::from_u32(code).unwrap());
}
}
}