use core::fmt;
use core::str::FromStr;
use units::parse_int;
use crate::opcodes::all::{OP_1, OP_16};
use crate::opcodes::{Opcode, OP_PUSHBYTES_0};
#[rustfmt::skip] #[doc(no_inline)]
pub use self::error::{FromStrError, TryFromError};
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
#[repr(u8)]
pub enum WitnessVersion {
V0 = 0,
V1 = 1,
V2 = 2,
V3 = 3,
V4 = 4,
V5 = 5,
V6 = 6,
V7 = 7,
V8 = 8,
V9 = 9,
V10 = 10,
V11 = 11,
V12 = 12,
V13 = 13,
V14 = 14,
V15 = 15,
V16 = 16,
}
impl WitnessVersion {
pub fn to_num(self) -> u8 { self as u8 }
}
impl fmt::Display for WitnessVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", *self as u8) }
}
impl FromStr for WitnessVersion {
type Err = FromStrError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let version: u8 = parse_int::int_from_str(s).map_err(FromStrError::Unparsable)?;
Self::try_from(version).map_err(FromStrError::Invalid)
}
}
impl TryFrom<u8> for WitnessVersion {
type Error = TryFromError;
fn try_from(no: u8) -> Result<Self, Self::Error> {
Ok(match no {
0 => Self::V0,
1 => Self::V1,
2 => Self::V2,
3 => Self::V3,
4 => Self::V4,
5 => Self::V5,
6 => Self::V6,
7 => Self::V7,
8 => Self::V8,
9 => Self::V9,
10 => Self::V10,
11 => Self::V11,
12 => Self::V12,
13 => Self::V13,
14 => Self::V14,
15 => Self::V15,
16 => Self::V16,
invalid => return Err(TryFromError { invalid }),
})
}
}
impl TryFrom<Opcode> for WitnessVersion {
type Error = TryFromError;
fn try_from(opcode: Opcode) -> Result<Self, Self::Error> {
match opcode.to_u8() {
0 => Ok(Self::V0),
version if version >= OP_1.to_u8() && version <= OP_16.to_u8() =>
Self::try_from(version - OP_1.to_u8() + 1),
invalid => Err(TryFromError { invalid }),
}
}
}
impl From<WitnessVersion> for Opcode {
fn from(version: WitnessVersion) -> Self {
match version {
WitnessVersion::V0 => OP_PUSHBYTES_0,
no => Self::from(OP_1.to_u8() + no.to_num() - 1),
}
}
}
pub mod error {
use core::convert::Infallible;
use core::fmt;
use internals::write_err;
use units::parse_int::ParseIntError;
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum FromStrError {
Unparsable(ParseIntError),
Invalid(TryFromError),
}
impl From<Infallible> for FromStrError {
fn from(never: Infallible) -> Self { match never {} }
}
impl fmt::Display for FromStrError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Self::Unparsable(ref e) => write_err!(f, "integer parse error"; e),
Self::Invalid(ref e) => write_err!(f, "invalid version number"; e),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for FromStrError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match *self {
Self::Unparsable(ref e) => Some(e),
Self::Invalid(ref e) => Some(e),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TryFromError {
pub(super) invalid: u8,
}
impl TryFromError {
pub fn invalid_version(&self) -> u8 { self.invalid }
}
impl fmt::Display for TryFromError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "invalid witness script version: {}", self.invalid)
}
}
#[cfg(feature = "std")]
impl std::error::Error for TryFromError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
let Self { invalid: _ } = self;
None
}
}
}
#[cfg(test)]
mod tests {
#[cfg(feature = "alloc")]
use alloc::string::ToString;
use super::*;
use crate::opcodes::OP_PUSHDATA4;
#[test]
fn witness_version_to_num() {
assert_eq!(WitnessVersion::V0.to_num(), 0);
assert_eq!(WitnessVersion::V1.to_num(), 1);
assert_eq!(WitnessVersion::V2.to_num(), 2);
assert_eq!(WitnessVersion::V16.to_num(), 16);
}
#[test]
#[cfg(feature = "alloc")]
fn witness_version_display() {
assert_eq!(WitnessVersion::V0.to_string(), "0");
assert_eq!(WitnessVersion::V1.to_string(), "1");
assert_eq!(WitnessVersion::V10.to_string(), "10");
assert_eq!(WitnessVersion::V16.to_string(), "16");
}
#[test]
fn witness_version_try_from_opcode() {
assert_eq!(WitnessVersion::try_from(OP_PUSHBYTES_0).unwrap(), WitnessVersion::V0);
assert_eq!(WitnessVersion::try_from(OP_1).unwrap(), WitnessVersion::V1);
assert_eq!(WitnessVersion::try_from(OP_16).unwrap(), WitnessVersion::V16);
let op = Opcode::from(OP_1.to_u8() - 1);
assert_eq!(WitnessVersion::try_from(op).unwrap_err().invalid_version(), OP_1.to_u8() - 1);
let op = Opcode::from(0xff);
assert_eq!(WitnessVersion::try_from(op).unwrap_err().invalid_version(), 0xff);
assert_eq!(
WitnessVersion::try_from(Opcode::from(OP_PUSHDATA4)).unwrap_err().invalid_version(),
OP_PUSHDATA4
);
}
#[test]
fn witness_version_into_opcode() {
assert_eq!(Opcode::from(WitnessVersion::V0), OP_PUSHBYTES_0);
assert_eq!(Opcode::from(WitnessVersion::V1), OP_1);
assert_eq!(Opcode::from(WitnessVersion::V16), OP_16);
}
#[test]
fn witness_version_opcode_round_trip() {
for version in 0u8..=16 {
let wv = WitnessVersion::try_from(version).unwrap();
let opcode = Opcode::from(wv);
assert_eq!(WitnessVersion::try_from(opcode).unwrap(), wv);
}
}
}