Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: covenants audit #5526

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion base_layer/core/src/covenants/byte_codes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,9 @@ pub const FIELD_COVENANT: u8 = 0x03;
pub const FIELD_FEATURES: u8 = 0x04;
pub const FIELD_FEATURES_OUTPUT_TYPE: u8 = 0x05;
pub const FIELD_FEATURES_MATURITY: u8 = 0x06;
pub const FIELD_FEATURES_SIDE_CHAIN_FEATURES: u8 = 0x08;
pub const FIELD_FEATURES_SIDE_CHAIN_FEATURES: u8 = 0x07;
pub const FIELD_FEATURES_RANGE_PROOF_TYPE: u8 = 0x08;
pub const MINIMUM_VALUE_PROMISE: u8 = 0x09;

#[cfg(test)]
mod tests {
Expand Down
111 changes: 90 additions & 21 deletions base_layer/core/src/covenants/fields.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ pub enum OutputField {
FeaturesOutputType = byte_codes::FIELD_FEATURES_OUTPUT_TYPE,
FeaturesMaturity = byte_codes::FIELD_FEATURES_MATURITY,
FeaturesSideChainFeatures = byte_codes::FIELD_FEATURES_SIDE_CHAIN_FEATURES,
FeaturesRangeProofType = byte_codes::FIELD_FEATURES_RANGE_PROOF_TYPE,
MinimumValuePromise = byte_codes::MINIMUM_VALUE_PROMISE,
}

impl OutputField {
Expand All @@ -70,6 +72,8 @@ impl OutputField {
FIELD_FEATURES_OUTPUT_TYPE => Ok(FeaturesOutputType),
FIELD_FEATURES_MATURITY => Ok(FeaturesMaturity),
FIELD_FEATURES_SIDE_CHAIN_FEATURES => Ok(FeaturesSideChainFeatures),
FIELD_FEATURES_RANGE_PROOF_TYPE => Ok(FeaturesRangeProofType),
MINIMUM_VALUE_PROMISE => Ok(MinimumValuePromise),

_ => Err(CovenantDecodeError::UnknownByteCode { code: byte }),
}
Expand All @@ -91,6 +95,8 @@ impl OutputField {
FeaturesOutputType => &output.features.output_type as &dyn Any,
FeaturesMaturity => &output.features.maturity as &dyn Any,
FeaturesSideChainFeatures => &output.features.sidechain_feature as &dyn Any,
FeaturesRangeProofType => &output.features.range_proof_type as &dyn Any,
MinimumValuePromise => &output.minimum_value_promise as &dyn Any,
};
val.downcast_ref::<T>()
}
Expand All @@ -109,6 +115,8 @@ impl OutputField {
FeaturesOutputType => BorshSerialize::serialize(&output.features.output_type, &mut writer),
FeaturesMaturity => BorshSerialize::serialize(&output.features.maturity, &mut writer),
FeaturesSideChainFeatures => BorshSerialize::serialize(&output.features.sidechain_feature, &mut writer),
FeaturesRangeProofType => BorshSerialize::serialize(&output.features.range_proof_type, &mut writer),
MinimumValuePromise => BorshSerialize::serialize(&output.minimum_value_promise, &mut writer),
}
.unwrap();
writer
Expand Down Expand Up @@ -147,6 +155,14 @@ impl OutputField {
.features()
.map(|features| features.sidechain_feature == output.features.sidechain_feature)
.unwrap_or(false),
FeaturesRangeProofType => input
.features()
.map(|features| features.range_proof_type == output.features.range_proof_type)
.unwrap_or(false),
MinimumValuePromise => input
.minimum_value_promise()
.map(|minimum_value_promise| *minimum_value_promise == output.minimum_value_promise)
.unwrap_or(false),
}
}

Expand Down Expand Up @@ -222,11 +238,20 @@ impl OutputField {
OutputField::FeaturesMaturity
}

#[allow(dead_code)]
#[allow(dead_code)]
pub fn features_sidechain_feature() -> Self {
OutputField::FeaturesSideChainFeatures
}

#[allow(dead_code)]
pub fn features_range_proof_type() -> Self {
OutputField::FeaturesRangeProofType
}

#[allow(dead_code)]
pub fn minimum_value_promise() -> Self {
OutputField::MinimumValuePromise
}
}

impl Display for OutputField {
Expand All @@ -242,6 +267,8 @@ impl Display for OutputField {
FeaturesOutputType => write!(f, "field::features_flags"),
FeaturesSideChainFeatures => write!(f, "field::features_sidechain_feature"),
FeaturesMaturity => write!(f, "field::features_maturity"),
FeaturesRangeProofType => write!(f, "field::features_range_proof_type"),
MinimumValuePromise => write!(f, "field::minimum_value_promise"),
}
}
}
Expand Down Expand Up @@ -344,7 +371,11 @@ mod test {

mod is_eq {
use super::*;
use crate::transactions::test_helpers::create_test_core_key_manager_with_memory_db;
use crate::transactions::{
tari_amount::MicroTari,
test_helpers::create_test_core_key_manager_with_memory_db,
transaction_components::RangeProofType,
};

#[tokio::test]
async fn it_returns_true_if_eq() {
Expand All @@ -366,20 +397,26 @@ mod test {
.remove(0);

assert!(OutputField::Commitment.is_eq(&output, &output.commitment).unwrap());
assert!(OutputField::Features.is_eq(&output, &output.features).unwrap());
assert!(OutputField::Script.is_eq(&output, &output.script).unwrap());
assert!(OutputField::Covenant.is_eq(&output, &output.covenant).unwrap());
assert!(OutputField::FeaturesMaturity
.is_eq(&output, &output.features.maturity)
assert!(OutputField::SenderOffsetPublicKey
.is_eq(&output, &output.sender_offset_public_key)
.unwrap());
assert!(OutputField::Covenant.is_eq(&output, &output.covenant).unwrap());
assert!(OutputField::Features.is_eq(&output, &output.features).unwrap());
assert!(OutputField::FeaturesOutputType
.is_eq(&output, &output.features.output_type)
.unwrap());
assert!(OutputField::FeaturesMaturity
.is_eq(&output, &output.features.maturity)
.unwrap());
assert!(OutputField::FeaturesSideChainFeatures
.is_eq(&output, output.features.sidechain_feature.as_ref().unwrap())
.unwrap());
assert!(OutputField::SenderOffsetPublicKey
.is_eq(&output, &output.sender_offset_public_key)
assert!(OutputField::FeaturesRangeProofType
.is_eq(&output, &output.features.range_proof_type)
.unwrap());
assert!(OutputField::MinimumValuePromise
.is_eq(&output, &output.minimum_value_promise)
.unwrap());
}

Expand All @@ -392,9 +429,13 @@ mod test {
UtxoTestParams {
features: OutputFeatures {
sidechain_feature: Some(side_chain_features),
range_proof_type: RangeProofType::RevealedValue,
output_type: OutputType::Burn,
..Default::default()
},
script: script![Drop Nop],
minimum_value_promise: MicroTari(123456),
value: MicroTari(123456),
..Default::default()
},
&key_manager,
Expand All @@ -403,19 +444,25 @@ mod test {
.remove(0);

assert!(!OutputField::Commitment.is_eq(&output, &Commitment::default()).unwrap());
assert!(!OutputField::Features
.is_eq(&output, &OutputFeatures::default())
.unwrap());
assert!(!OutputField::Script.is_eq(&output, &script![Nop Drop]).unwrap());
assert!(!OutputField::SenderOffsetPublicKey
.is_eq(&output, &PublicKey::default())
.unwrap());
assert!(!OutputField::Covenant
.is_eq(&output, &covenant!(and(identity(), identity())))
.unwrap());
assert!(!OutputField::Features
.is_eq(&output, &OutputFeatures::default())
.unwrap());
assert!(!OutputField::FeaturesMaturity.is_eq(&output, &123u64).unwrap());
assert!(!OutputField::FeaturesOutputType
.is_eq(&output, &OutputType::Coinbase)
.unwrap());
assert!(!OutputField::SenderOffsetPublicKey
.is_eq(&output, &PublicKey::default())
assert!(!OutputField::FeaturesRangeProofType
.is_eq(&output, &RangeProofType::BulletProofPlus)
.unwrap());
assert!(!OutputField::MinimumValuePromise
.is_eq(&output, &MicroTari::default())
.unwrap());
}
}
Expand Down Expand Up @@ -459,27 +506,31 @@ mod test {
}

assert!(OutputField::Commitment.is_eq_input(&input, &output));
assert!(OutputField::Features.is_eq_input(&input, &output));
assert!(OutputField::Script.is_eq_input(&input, &output));
assert!(OutputField::SenderOffsetPublicKey.is_eq_input(&input, &output));
assert!(OutputField::Covenant.is_eq_input(&input, &output));
assert!(OutputField::Features.is_eq_input(&input, &output));
assert!(OutputField::FeaturesMaturity.is_eq_input(&input, &output));
assert!(OutputField::FeaturesOutputType.is_eq_input(&input, &output));
assert!(OutputField::FeaturesSideChainFeatures.is_eq_input(&input, &output));
assert!(OutputField::SenderOffsetPublicKey.is_eq_input(&input, &output));
assert!(OutputField::FeaturesRangeProofType.is_eq_input(&input, &output));
assert!(OutputField::MinimumValuePromise.is_eq_input(&input, &output));
}
}

#[test]
fn display() {
let output_fields = [
OutputField::Commitment,
OutputField::Script,
OutputField::SenderOffsetPublicKey,
OutputField::Covenant,
OutputField::Features,
OutputField::FeaturesMaturity,
OutputField::FeaturesOutputType,
OutputField::FeaturesSideChainFeatures,
OutputField::FeaturesMaturity,
OutputField::SenderOffsetPublicKey,
OutputField::Script,
OutputField::Covenant,
OutputField::FeaturesRangeProofType,
OutputField::MinimumValuePromise,
];
output_fields.iter().for_each(|f| {
assert!(f.to_string().starts_with("field::"));
Expand All @@ -495,21 +546,28 @@ mod test {
use tari_crypto::hashing::DomainSeparation;

use super::*;
use crate::transactions::test_helpers::create_test_core_key_manager_with_memory_db;
use crate::transactions::{
tari_amount::MicroTari,
test_helpers::create_test_core_key_manager_with_memory_db,
transaction_components::RangeProofType,
};

#[tokio::test]
async fn it_constructs_challenge_using_consensus_encoding() {
let key_manager = create_test_core_key_manager_with_memory_db();
let features = OutputFeatures {
maturity: 42,
output_type: OutputType::Coinbase,
range_proof_type: RangeProofType::RevealedValue,
..Default::default()
};
let output = create_outputs(
1,
UtxoTestParams {
features,
script: script![Drop Nop],
minimum_value_promise: MicroTari(123456),
value: MicroTari(123456),
..Default::default()
},
&key_manager,
Expand All @@ -521,6 +579,7 @@ mod test {
fields.push(OutputField::Features);
fields.push(OutputField::Commitment);
fields.push(OutputField::Script);
fields.push(OutputField::MinimumValuePromise);
let hash = fields.construct_challenge_from(&output).finalize();
let hash = hash.to_vec();

Expand All @@ -530,6 +589,7 @@ mod test {
.chain(output.features.try_to_vec().unwrap())
.chain(output.commitment.try_to_vec().unwrap())
.chain(output.script.try_to_vec().unwrap())
.chain(output.minimum_value_promise.try_to_vec().unwrap())
.finalize()
.to_vec();
assert_eq!(hash, expected_hash);
Expand All @@ -538,19 +598,26 @@ mod test {

mod get_field_value_ref {
use super::*;
use crate::transactions::test_helpers::create_test_core_key_manager_with_memory_db;
use crate::transactions::{
tari_amount::MicroTari,
test_helpers::create_test_core_key_manager_with_memory_db,
transaction_components::RangeProofType,
};

#[tokio::test]
async fn it_retrieves_the_value_as_ref() {
let key_manager = create_test_core_key_manager_with_memory_db();
let features = OutputFeatures {
maturity: 42,
range_proof_type: RangeProofType::RevealedValue,
..Default::default()
};
let output = create_outputs(
1,
UtxoTestParams {
features: features.clone(),
minimum_value_promise: MicroTari(123456),
value: MicroTari(123456),
..Default::default()
},
&key_manager,
Expand All @@ -560,6 +627,8 @@ mod test {
.unwrap();
let r = OutputField::Features.get_field_value_ref::<OutputFeatures>(&output);
assert_eq!(*r.unwrap(), features);
let r = OutputField::MinimumValuePromise.get_field_value_ref::<MicroTari>(&output);
assert_eq!(*r.unwrap(), MicroTari(123456));
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions base_layer/core/src/covenants/filters/absolute_height.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,13 @@

use crate::covenants::{context::CovenantContext, error::CovenantError, filters::Filter, output_set::OutputSet};

/// Holding struct for the "absolute height" filter
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AbsoluteHeightFilter;

impl Filter for AbsoluteHeightFilter {
// The absolute height filter removes all outputs in the mutable output set if the current block height is less than
// the absolute height provided in the covenant context.
fn filter(&self, context: &mut CovenantContext<'_>, output_set: &mut OutputSet<'_>) -> Result<(), CovenantError> {
let abs_height = context.next_arg()?.require_uint()?;
let current_height = context.block_height();
Expand Down
18 changes: 14 additions & 4 deletions base_layer/core/src/covenants/filters/and.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,13 @@

use crate::covenants::{context::CovenantContext, error::CovenantError, filters::Filter, output_set::OutputSet};

/// Holding struct for the "and" filter
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AndFilter;

impl Filter for AndFilter {
// The and filter removes all outputs in the mutable output set based on the next two filters in the covenant
// context - the filters are applied in order.
fn filter(&self, context: &mut CovenantContext<'_>, output_set: &mut OutputSet<'_>) -> Result<(), CovenantError> {
let a = context.require_next_filter()?;
a.filter(context, output_set)?;
Expand All @@ -51,19 +54,26 @@ mod test {
#[tokio::test]
async fn it_filters_outputset_using_intersection() {
let key_manager = create_test_core_key_manager_with_memory_db();
let script = script!(Nop);
let covenant =
covenant!(and(field_eq(@field::features_maturity, @uint(42),), field_eq(@field::script, @script(script))));
let script = script!(CheckHeight(101));
let covenant = covenant!(and(field_eq(@field::features_maturity, @uint(42),), field_eq(@field::script, @script(script.clone()))));
let input = create_input(&key_manager).await;
let (mut context, outputs) = setup_filter_test(
&covenant,
&input,
0,
|outputs| {
// output satisfying maturity only
outputs[2].features.maturity = 42;
outputs[2].script = script!(CheckHeight(102));
// output satisfying maturity and script
outputs[5].features.maturity = 42;
outputs[5].script = script.clone();
// output satisfying maturity and script
outputs[7].features.maturity = 42;
// Does not have maturity = 42
outputs[7].script = script.clone();
// output satisfying script only
outputs[8].features.maturity = 123;
outputs[8].script = script.clone();
},
&key_manager,
)
Expand Down
3 changes: 3 additions & 0 deletions base_layer/core/src/covenants/filters/field_eq.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,13 @@ use crate::covenants::{
output_set::OutputSet,
};

/// Holding struct for the "fields equal" filter
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FieldEqFilter;

impl Filter for FieldEqFilter {
// Filters out all outputs that do not have the specified output field equal to the specified value based on the
// next two arguments in the covenant context.
fn filter(&self, context: &mut CovenantContext<'_>, output_set: &mut OutputSet<'_>) -> Result<(), CovenantError> {
let field = context.next_arg()?.require_outputfield()?;
let arg = context.next_arg()?;
Expand Down
Loading