Skip to content

Commit

Permalink
test(covenant): improve test coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
sdbondi committed Apr 26, 2022
1 parent 50e2812 commit 41103cd
Show file tree
Hide file tree
Showing 14 changed files with 482 additions and 122 deletions.
32 changes: 16 additions & 16 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 15 additions & 10 deletions base_layer/core/src/covenants/arguments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ use crate::{
covenants::{
byte_codes,
covenant::Covenant,
decoder::{CovenantDecodeError, CovenentReadExt},
decoder::{CovenantDecodeError, CovenantReadExt},
encoder::CovenentWriteExt,
error::CovenantError,
fields::{OutputField, OutputFields},
Expand Down Expand Up @@ -238,7 +238,12 @@ impl Display for CovenantArg {

#[cfg(test)]
mod test {
use tari_common_types::types::Commitment;
use tari_script::script;
use tari_utilities::hex::from_hex;

use super::*;
use crate::{covenant, covenants::byte_codes::*};

mod require_x_impl {
use super::*;
Expand All @@ -260,18 +265,18 @@ mod test {
}
}

mod write_to {
use tari_common_types::types::Commitment;
use tari_script::script;
use tari_utilities::hex::from_hex;

mod write_to_and_read_from {
use super::*;
use crate::{covenant, covenants::byte_codes::*};

fn test_case(arg: CovenantArg, expected: &[u8]) {
fn test_case(argument: CovenantArg, mut data: &[u8]) {
let mut buf = Vec::new();
arg.write_to(&mut buf).unwrap();
assert_eq!(buf, expected);
argument.write_to(&mut buf).unwrap();
assert_eq!(buf, data);

let reader = &mut data;
let code = reader.read_next_byte_code().unwrap().unwrap();
let arg = CovenantArg::read_from(&mut data, code).unwrap();
assert_eq!(arg, argument);
}

#[test]
Expand Down
71 changes: 69 additions & 2 deletions base_layer/core/src/covenants/byte_codes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,21 @@

//---------------------------------- ARG byte codes --------------------------------------------//
pub(super) fn is_valid_arg_code(code: u8) -> bool {
(0x01..=0x09).contains(&code)
ALL_ARGS.contains(&code)
}

pub(super) const ALL_ARGS: [u8; 9] = [
ARG_HASH,
ARG_PUBLIC_KEY,
ARG_COMMITMENT,
ARG_TARI_SCRIPT,
ARG_COVENANT,
ARG_UINT,
ARG_OUTPUT_FIELD,
ARG_OUTPUT_FIELDS,
ARG_BYTES,
];

pub const ARG_HASH: u8 = 0x01;
pub const ARG_PUBLIC_KEY: u8 = 0x02;
pub const ARG_COMMITMENT: u8 = 0x03;
Expand All @@ -37,9 +50,22 @@ pub const ARG_BYTES: u8 = 0x09;
//---------------------------------- FILTER byte codes --------------------------------------------//

pub(super) fn is_valid_filter_code(code: u8) -> bool {
(0x20..=0x24).contains(&code) || (0x30..=0x34).contains(&code)
ALL_FILTERS.contains(&code)
}

pub(super) const ALL_FILTERS: [u8; 10] = [
FILTER_IDENTITY,
FILTER_AND,
FILTER_OR,
FILTER_XOR,
FILTER_NOT,
FILTER_OUTPUT_HASH_EQ,
FILTER_FIELDS_PRESERVED,
FILTER_FIELDS_HASHED_EQ,
FILTER_FIELD_EQ,
FILTER_ABSOLUTE_HEIGHT,
];

pub const FILTER_IDENTITY: u8 = 0x20;
pub const FILTER_AND: u8 = 0x21;
pub const FILTER_OR: u8 = 0x22;
Expand All @@ -63,3 +89,44 @@ pub const FIELD_FEATURES_MATURITY: u8 = 0x06;
pub const FIELD_FEATURES_UNIQUE_ID: u8 = 0x07;
pub const FIELD_FEATURES_PARENT_PUBLIC_KEY: u8 = 0x08;
pub const FIELD_FEATURES_METADATA: u8 = 0x09;

#[cfg(test)]
mod tests {
use super::*;

mod is_valid_filter_code {
use super::*;

#[test]
fn it_returns_true_for_all_filter_codes() {
ALL_FILTERS.iter().for_each(|code| {
assert!(is_valid_filter_code(*code));
});
}

#[test]
fn it_returns_false_for_all_arg_codes() {
ALL_ARGS.iter().for_each(|code| {
assert!(!is_valid_filter_code(*code));
});
}
}

mod is_valid_arg_code {
use super::*;

#[test]
fn it_returns_false_for_all_filter_codes() {
ALL_FILTERS.iter().for_each(|code| {
assert!(!is_valid_arg_code(*code));
});
}

#[test]
fn it_returns_true_for_all_arg_codes() {
ALL_ARGS.iter().for_each(|code| {
assert!(is_valid_arg_code(*code));
});
}
}
}
51 changes: 48 additions & 3 deletions base_layer/core/src/covenants/decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,12 @@ pub enum CovenantDecodeError {
Io(#[from] io::Error),
}

pub(super) trait CovenentReadExt: io::Read {
pub(super) trait CovenantReadExt: io::Read {
fn read_next_byte_code(&mut self) -> Result<Option<u8>, io::Error>;
fn read_variable_length_bytes(&mut self, size: usize) -> Result<Vec<u8>, io::Error>;
}

impl<R: io::Read> CovenentReadExt for R {
impl<R: io::Read> CovenantReadExt for R {
fn read_next_byte_code(&mut self) -> Result<Option<u8>, io::Error> {
let mut buf = [0u8; 1];
loop {
Expand Down Expand Up @@ -127,13 +127,41 @@ mod test {
use super::*;
use crate::{
covenant,
covenants::{arguments::CovenantArg, fields::OutputField, filters::CovenantFilter},
covenants::{
arguments::CovenantArg,
byte_codes::ARG_OUTPUT_FIELD,
fields::OutputField,
filters::CovenantFilter,
},
};

#[test]
fn it_immediately_ends_iterator_given_empty_bytes() {
let buf = &[] as &[u8; 0];
assert!(CovenantTokenDecoder::new(&mut &buf[..]).next().is_none());
assert!(CovenantTokenDecoder::new(&mut &buf[..]).next().is_none());
}

#[test]
fn it_ends_after_an_error() {
let buf = &[0xffu8];
let mut reader = &buf[..];
let mut decoder = CovenantTokenDecoder::new(&mut reader);
assert!(matches!(decoder.next(), Some(Err(_))));
assert!(decoder.next().is_none());
}

#[test]
fn it_returns_an_error_if_arg_expected() {
let buf = &[ARG_OUTPUT_FIELD];
let mut reader = &buf[..];
let mut decoder = CovenantTokenDecoder::new(&mut reader);

assert!(matches!(
decoder.next(),
Some(Err(CovenantDecodeError::UnexpectedEof { .. }))
));
assert!(decoder.next().is_none());
}

#[test]
Expand Down Expand Up @@ -171,4 +199,21 @@ mod test {

assert!(decoder.next().is_none());
}

mod covenant_read_ext {
use super::*;

#[test]
fn it_reads_bytes_with_length_prefix() {
let data = vec![0x03u8, 0x01, 0x02, 0x03];
let bytes = CovenantReadExt::read_variable_length_bytes(&mut data.as_slice(), 3).unwrap();
assert_eq!(bytes, [1u8, 2, 3]);
}

#[test]
fn it_errors_if_len_byte_exceeds_maximum() {
let data = vec![0x02, 0x01];
CovenantReadExt::read_variable_length_bytes(&mut data.as_slice(), 1).unwrap_err();
}
}
}
60 changes: 60 additions & 0 deletions base_layer/core/src/covenants/encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,63 @@ impl<W: io::Write> CovenentWriteExt for W {
Ok(1)
}
}

#[cfg(test)]
mod tests {

use super::*;
use crate::{
covenant,
covenants::{
byte_codes::{ARG_HASH, ARG_OUTPUT_FIELD, FILTER_AND, FILTER_FIELD_EQ, FILTER_IDENTITY, FILTER_OR},
OutputField,
},
};

#[test]
fn it_encodes_empty_tokens() {
let encoder = CovenantTokenEncoder::new(&[]);
let mut buf = Vec::<u8>::new();
let written = encoder.write_to(&mut buf).unwrap();
assert_eq!(buf, [] as [u8; 0]);
assert_eq!(written, 0);
}

#[test]
fn it_encodes_tokens_correctly() {
let covenant = covenant!(and(identity(), or(identity())));
let encoder = CovenantTokenEncoder::new(covenant.tokens());
let mut buf = Vec::<u8>::new();
let written = encoder.write_to(&mut buf).unwrap();
assert_eq!(buf, [FILTER_AND, FILTER_IDENTITY, FILTER_OR, FILTER_IDENTITY]);
assert_eq!(written, 4);
}

#[test]
fn it_encodes_args_correctly() {
let dummy = [0u8; 32];
let covenant = covenant!(field_eq(@field::features, @hash(dummy)));
let encoder = CovenantTokenEncoder::new(covenant.tokens());
let mut buf = Vec::<u8>::new();
let written = encoder.write_to(&mut buf).unwrap();
assert_eq!(buf[..4], [
FILTER_FIELD_EQ,
ARG_OUTPUT_FIELD,
OutputField::Features.as_byte(),
ARG_HASH
]);
assert_eq!(buf[4..], [0u8; 32]);
assert_eq!(written, 36);
}

mod covenant_write_ext {
use super::*;

#[test]
fn it_writes_a_single_byte() {
let mut buf = Vec::new();
buf.write_u8_fixed(123u8).unwrap();
assert_eq!(buf, vec![123u8]);
}
}
}
2 changes: 0 additions & 2 deletions base_layer/core/src/covenants/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,4 @@ pub enum CovenantError {
RemainingTokens,
#[error("Invalid argument for filter {filter}: {details}")]
InvalidArgument { filter: &'static str, details: String },
#[error("Unsupported argument {arg}: {details}")]
UnsupportedArgument { arg: &'static str, details: String },
}
Loading

0 comments on commit 41103cd

Please sign in to comment.