diff --git a/tuxedo-template-runtime/Cargo.toml b/tuxedo-template-runtime/Cargo.toml index 4949fb6cb..de5c5c49c 100644 --- a/tuxedo-template-runtime/Cargo.toml +++ b/tuxedo-template-runtime/Cargo.toml @@ -40,6 +40,7 @@ money = { default-features = false, path = "../wardrobe/money" } poe = { default-features = false, path = "../wardrobe/poe" } runtime-upgrade = { default-features = false, path = "../wardrobe/runtime_upgrade" } timestamp = { default-features = false, path = "../wardrobe/timestamp" } +tradable-kitties = { default-features = false, path = "../wardrobe/tradable_kitties" } tuxedo-core = { default-features = false, path = "../tuxedo-core" } # Parachain related ones diff --git a/tuxedo-template-runtime/src/lib.rs b/tuxedo-template-runtime/src/lib.rs index 6a2c8a389..dd9c4e59b 100644 --- a/tuxedo-template-runtime/src/lib.rs +++ b/tuxedo-template-runtime/src/lib.rs @@ -45,6 +45,7 @@ pub use money; pub use poe; pub use runtime_upgrade; pub use timestamp; +pub use tradable_kitties; /// Opaque types. These are used by the CLI to instantiate machinery that don't need to know /// the specifics of the runtime. They can then be made to be agnostic over specific formats @@ -169,7 +170,9 @@ pub enum OuterConstraintChecker { /// Checks monetary transactions in a basic fungible cryptocurrency Money(money::MoneyConstraintChecker<0>), /// Checks Free Kitty transactions - FreeKittyConstraintChecker(kitties::FreeKittyConstraintChecker), + FreeKitty(kitties::FreeKittyConstraintChecker), + /// Checks tradable Kitty transactions + TradableKitty(tradable_kitties::TradableKittyConstraintChecker<0>), /// Checks that an amoeba can split into two new amoebas AmoebaMitosis(amoeba::AmoebaMitosis), /// Checks that a single amoeba is simply removed from the state @@ -204,7 +207,9 @@ pub enum OuterConstraintChecker { /// Checks monetary transactions in a basic fungible cryptocurrency Money(money::MoneyConstraintChecker<0>), /// Checks Free Kitty transactions - FreeKittyConstraintChecker(kitties::FreeKittyConstraintChecker), + FreeKitty(kitties::FreeKittyConstraintChecker), + /// Checks Paid Kitty transactions + TradableKitty(tradable_kitties::TradableKittyConstraintChecker<0>), /// Checks that an amoeba can split into two new amoebas AmoebaMitosis(amoeba::AmoebaMitosis), /// Checks that a single amoeba is simply removed from the state diff --git a/wardrobe/kitties/src/lib.rs b/wardrobe/kitties/src/lib.rs index 49d7d4162..cd3f4d38c 100644 --- a/wardrobe/kitties/src/lib.rs +++ b/wardrobe/kitties/src/lib.rs @@ -1,18 +1,48 @@ -//! An NFT game inspired by cryptokitties. -//! This is a game which allows for kitties to be bred based on a few factors -//! 1.) Mom and Tired have to be in a state where they are ready to breed -//! 2.) Each Mom and Dad have some DNA and the child will have unique DNA combined from the both of them -//! Linkable back to the Mom and Dad -//! 3.) The game also allows Kitties to have a cooling off period inbetween breeding before they can be bred again. -//! 4.) A rest operation allows for a Mom Kitty and a Dad Kitty to be cooled off +//! An NFT game inspired by Cryptokitties. +//! In this game, Kitties can be created, bred or renamed. +//! In this game, Kitties can be created, bred or renamed. //! -//! In order to submit a valid transaction you must strutucture it as follows: -//! 1.) Input must contain 1 mom and 1 dad -//! 2.) Output must contain Mom, Dad, and newly created Child -//! 3.) A child's DNA is calculated by: +//! ## Features +//! +//! - **Create:** Generate new kitties from scratch. +//! To submit a valid transaction for creating kitties, adhere to the following structure: +//! - **Create:** Generate new kitties from scratch. +//! To submit a valid transaction for creating kitties, adhere to the following structure: +//! 1. The input must be empty. +//! 2. The output must contain only the newly created kitties. +//! 2. The output must contain only the newly created kitties. +//! +//! **Note 1:** Multiple kitties can be created at the same time in the same transaction. +//! +//! - **Update Name:** Modify the name of one or more kitties. +//! To submit a valid transaction for updating some kitties' names, adhere to the following structure: +//! 1. The input must be the kitties to update. +//! 2. The output must contain the kitties with the updated names. +//! - **Update Name:** Modify the name of one or more kitties. +//! To submit a valid transaction for updating some kitties' names, adhere to the following structure: +//! 1. The input must be the kitties to update. +//! 2. The output must contain the kitties with the updated names. +//! +//! **Note 1:** All other properties, such as DNA, parents, free breedings, etc., must remain unaltered in the output. +//! **Note 2:** The input and output kitties must follow the same order. +//! +//! - **Breed:** Breed a new kitty using Mom and Dad based on the factors below: +//! - **Breed:** Breed a new kitty using Mom and Dad based on the factors below: +//! 1. Mom and Dad have to be in a state where they are ready to breed. +//! 2. The child's unique DNA combined from Mom's and Dad's, linkable back to them. +//! 2. The child's unique DNA combined from Mom's and Dad's, linkable back to them. +//! 3. The game also allows kitties to have a cooling-off period in between breeding before they can be bred again. +//! 4. A rest operation allows for a Mom Kitty and a Dad Kitty to cool off. +//! +//! In order to submit a valid breed transaction, you must structure it as follows: +//! 1. The input must contain 1 Mom and 1 Dad, in a `RearinToGo` state. +//! 2. The output must contain Mom, Dad, and the newly created Child. Mom and Dad's state must be updated to `HadBirthRecently` and `Tired`. +//! 1. The input must contain 1 Mom and 1 Dad, in a `RearinToGo` state. +//! 2. The output must contain Mom, Dad, and the newly created Child. Mom and Dad's state must be updated to `HadBirthRecently` and `Tired`. +//! 3. A child's DNA is calculated by: //! BlakeTwo256::hash_of(MomDna, DadDna, MomCurrNumBreedings, DadCurrNumberBreedings) //! -//! There are a only a finite amount of free breedings available before it starts to cost money +//! There are only a finite amount of free breedings available before it starts to cost money //! to breed kitties. #![cfg_attr(not(feature = "std"), no_std)] @@ -36,6 +66,10 @@ use tuxedo_core::{ #[cfg(test)] mod tests; +/// The main constraint checker for the kitty piece. Allows the following: +/// Create: Allows the creation of a kitty without parents. Multiple kitties can be created in the same transaction. +/// UpdateKittiesName: Allows updating the names of the kitties. Multiple kitty names can be updated in the same transaction. +/// Breed: Allows the breeding of kitties. #[derive( Serialize, Deserialize, @@ -50,8 +84,16 @@ mod tests; Debug, TypeInfo, )] -pub struct FreeKittyConstraintChecker; +pub enum FreeKittyConstraintChecker { + /// Transaction that creates a kitty without parents. Multiple kitties can be created at the same time + Create, + /// Transaction that updates kitty names. Multiple kitty names can be updated. Input and output must follow the same order + UpdateKittiesName, + /// Transaction where kitties are consumed, and a new family (parents: mom, dad, and child) is created. + Breed, +} +/// Dad Kitty's breeding status. #[derive( Serialize, Deserialize, @@ -69,10 +111,14 @@ pub struct FreeKittyConstraintChecker; )] pub enum DadKittyStatus { #[default] + /// Can breed. RearinToGo, + /// Can't breed due to tiredness. + /// Can't breed due to tiredness. Tired, } +/// Mom Kitty's breeding status. #[derive( Serialize, Deserialize, @@ -90,10 +136,14 @@ pub enum DadKittyStatus { )] pub enum MomKittyStatus { #[default] + /// Can breed. RearinToGo, + /// Can't breed due to a recent delivery of kittens. HadBirthRecently, } +/// The parent structure contains 1 Mom Kitty and 1 Dad Kitty. +/// The parent structure contains 1 Mom Kitty and 1 Dad Kitty. #[derive( Serialize, Deserialize, @@ -146,6 +196,12 @@ impl Default for Parent { )] pub struct KittyDNA(pub H256); +/// Kitty data contains basic information such as below: +/// parent: 1 mom kitty and 1 dad kitty. +/// free_breedings: Maximum free breeding allowed for a kitty. +/// dna: It's unique per kitty. +/// num_breedings: Current count of remaining free breedings. +/// name: Name of kitty. #[derive( Serialize, Deserialize, @@ -165,6 +221,7 @@ pub struct KittyData { pub free_breedings: u64, // Ignore in breed for money case pub dna: KittyDNA, pub num_breedings: u128, + pub name: [u8; 4], } impl KittyData { @@ -187,7 +244,7 @@ impl KittyData { v, ) .into()], - checker: FreeKittyConstraintChecker.into(), + checker: FreeKittyConstraintChecker::Create.into(), } } } @@ -199,6 +256,7 @@ impl Default for KittyData { free_breedings: 2, dna: KittyDNA(H256::from_slice(b"mom_kitty_1asdfasdfasdfasdfasdfa")), num_breedings: 3, + name: *b"kity", } } } @@ -207,6 +265,7 @@ impl UtxoData for KittyData { const TYPE_ID: [u8; 4] = *b"Kitt"; } +/// Reasons that kitty opertaion may go wrong. #[derive( Serialize, Deserialize, @@ -261,9 +320,25 @@ pub enum ConstraintCheckerError { TooManyBreedingsForKitty, /// Not enough free breedings available for these parents. NotEnoughFreeBreedings, + /// The transaction attempts to create no Kitty. + CreatingNothing, + /// Inputs (Parents) are not required for kitty creation. + CreatingWithInputs, + /// The number of inputs does not match the number of outputs for a transaction. + NumberOfInputOutputMismatch, + /// DNA mismatch between input and output. + DnaMismatchBetweenInputAndOutput, + /// Name is not updated + KittyNameUnAltered, + /// Kitty FreeBreeding cannot be updated. + FreeBreedingCannotBeUpdated, + /// Kitty NumOfBreeding cannot be updated. + NumOfBreedingCannotBeUpdated, + /// Gender cannot be updated. + KittyGenderCannotBeUpdated, } -trait Breed { +pub trait Breed { /// The Cost to breed a kitty if it is not free. const COST: u128; /// Number of free breedings a kitty will have. @@ -500,28 +575,105 @@ impl TryFrom<&DynamicallyTypedData> for KittyData { impl SimpleConstraintChecker for FreeKittyConstraintChecker { type Error = ConstraintCheckerError; - /// Checks: - /// - `input_data` is of length 2 - /// - `output_data` is of length 3 - /// + fn check( &self, input_data: &[DynamicallyTypedData], _peeks: &[DynamicallyTypedData], output_data: &[DynamicallyTypedData], ) -> Result { - // Input must be a Mom and a Dad - ensure!(input_data.len() == 2, Self::Error::TwoParentsDoNotExist); - - let mom = KittyData::try_from(&input_data[0])?; - let dad = KittyData::try_from(&input_data[1])?; - KittyHelpers::can_breed(&mom, &dad)?; - - // Output must be Mom, Dad, Child - ensure!(output_data.len() == 3, Self::Error::NotEnoughFamilyMembers); - - KittyHelpers::check_new_family(&mom, &dad, output_data)?; + match &self { + Self::Create => { + // Ensure that no inputs are being consumed. + ensure!( + input_data.is_empty(), + ConstraintCheckerError::CreatingWithInputs + ); + + // Ensure that at least one kitty is being created. + ensure!( + !output_data.is_empty(), + ConstraintCheckerError::CreatingNothing + ); + + // Ensure the outputs are the right type. + for utxo in output_data { + let _utxo_kitty = utxo + .extract::() + .map_err(|_| ConstraintCheckerError::BadlyTyped)?; + } + Ok(0) + } + Self::Breed => { + // Check that we are consuming at least one input. + ensure!(input_data.len() == 2, Self::Error::TwoParentsDoNotExist); + + let mom = KittyData::try_from(&input_data[0])?; + let dad = KittyData::try_from(&input_data[1])?; + KittyHelpers::can_breed(&mom, &dad)?; + // Output must be Mom, Dad, and Child. + ensure!(output_data.len() == 3, Self::Error::NotEnoughFamilyMembers); + KittyHelpers::check_new_family(&mom, &dad, output_data)?; + Ok(0) + } + Self::UpdateKittiesName => { + can_kitties_name_be_updated(input_data, output_data)?; + Ok(0) + } + } + } +} - Ok(0) +/// Checks if input and output contain a list of KittyData in the same order. +/// The KittyData in the output list can have different names from the inputs, but other properties must be unmodified. +pub fn can_kitties_name_be_updated( + input_data: &[DynamicallyTypedData], + output_data: &[DynamicallyTypedData], +) -> Result { + ensure!( + input_data.len() == output_data.len() && !input_data.is_empty(), + { ConstraintCheckerError::NumberOfInputOutputMismatch } + ); + + for (input, output) in input_data.iter().zip(output_data.iter()) { + let utxo_input_kitty = input + .extract::() + .map_err(|_| ConstraintCheckerError::BadlyTyped)?; + + let utxo_output_kitty = output + .extract::() + .map_err(|_| ConstraintCheckerError::BadlyTyped)?; + + check_kitty_name_update(&utxo_input_kitty, &utxo_output_kitty)?; } + Ok(0) +} + +/// Checks if only the name is updated, and other basic properties remain the same. +/// Checks if only the name is updated, and other basic properties remain the same. +fn check_kitty_name_update( + original_kitty: &KittyData, + updated_kitty: &KittyData, +) -> Result { + ensure!( + original_kitty.dna == updated_kitty.dna, + ConstraintCheckerError::DnaMismatchBetweenInputAndOutput + ); + ensure!( + original_kitty != updated_kitty, + ConstraintCheckerError::KittyNameUnAltered + ); + ensure!( + original_kitty.free_breedings == updated_kitty.free_breedings, + ConstraintCheckerError::FreeBreedingCannotBeUpdated + ); + ensure!( + original_kitty.num_breedings == updated_kitty.num_breedings, + ConstraintCheckerError::NumOfBreedingCannotBeUpdated + ); + ensure!( + original_kitty.parent == updated_kitty.parent, + ConstraintCheckerError::KittyGenderCannotBeUpdated + ); + Ok(0) } diff --git a/wardrobe/kitties/src/tests.rs b/wardrobe/kitties/src/tests.rs index 0b9c7fdfe..531310bcb 100644 --- a/wardrobe/kitties/src/tests.rs +++ b/wardrobe/kitties/src/tests.rs @@ -1,13 +1,7 @@ //! Tests for the Crypto Kitties Piece use super::*; -/// A bogus data type used in tests for type validation -#[derive(Encode, Decode)] -struct Bogus; - -impl UtxoData for Bogus { - const TYPE_ID: [u8; 4] = *b"bogs"; -} +use tuxedo_core::dynamic_typing::testing::Bogus; impl KittyData { pub fn default_dad() -> Self { @@ -24,6 +18,7 @@ impl KittyData { KittyData { parent: Parent::Mom(MomKittyStatus::RearinToGo), free_breedings: 2, + name: *b"bkty", dna: KittyDNA(BlakeTwo256::hash_of(&( mom.dna, dad.dna, @@ -51,13 +46,55 @@ impl KittyData { } } +#[test] +fn create_happy_path_works() { + let result = FreeKittyConstraintChecker::check( + &FreeKittyConstraintChecker::Create, + &[], + &[], + &[KittyData::default().into(), KittyData::default_dad().into()], + ); + assert!(result.is_ok()); +} + +#[test] +fn create_with_input_fails() { + let result = FreeKittyConstraintChecker::check( + &FreeKittyConstraintChecker::Create, + &[KittyData::default().into()], + &[], + &[], + ); + assert_eq!(result, Err(ConstraintCheckerError::CreatingWithInputs)); +} +#[test] +fn create_without_output_fails() { + let result = + FreeKittyConstraintChecker::check(&FreeKittyConstraintChecker::Create, &[], &[], &[]); + assert_eq!(result, Err(ConstraintCheckerError::CreatingNothing)); +} +#[test] +fn create_with_wrong_output_type_fails() { + let result = FreeKittyConstraintChecker::check( + &FreeKittyConstraintChecker::Create, + &[], + &[], + &[ + Bogus.into(), + KittyData::default().into(), + KittyData::default_dad().into(), + ], + ); + assert_eq!(result, Err(ConstraintCheckerError::BadlyTyped)); +} + #[test] fn breed_happy_path_works() { let new_family = KittyData::default_family(); let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker, + &FreeKittyConstraintChecker::Breed, &[KittyData::default().into(), KittyData::default_dad().into()], - &[], // no peeks + &[], &[ new_family[0].clone().into(), new_family[1].clone().into(), @@ -70,9 +107,9 @@ fn breed_happy_path_works() { #[test] fn breed_wrong_input_type_fails() { let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker, + &FreeKittyConstraintChecker::Breed, &[Bogus.into(), Bogus.into()], - &[], // no peeks + &[], &[], ); assert_eq!(result, Err(ConstraintCheckerError::BadlyTyped)); @@ -81,9 +118,9 @@ fn breed_wrong_input_type_fails() { #[test] fn breed_wrong_output_type_fails() { let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker, + &FreeKittyConstraintChecker::Breed, &[KittyData::default().into(), KittyData::default_dad().into()], - &[], // no peeks + &[], &[Bogus.into(), Bogus.into(), Bogus.into()], ); assert_eq!(result, Err(ConstraintCheckerError::BadlyTyped)); @@ -92,9 +129,9 @@ fn breed_wrong_output_type_fails() { #[test] fn inputs_dont_contain_two_parents_fails() { let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker, + &FreeKittyConstraintChecker::Breed, &[KittyData::default().into()], - &[], // no peeks + &[], &[], ); assert_eq!(result, Err(ConstraintCheckerError::TwoParentsDoNotExist)); @@ -103,9 +140,9 @@ fn inputs_dont_contain_two_parents_fails() { #[test] fn outputs_dont_contain_all_family_members_fails() { let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker, + &FreeKittyConstraintChecker::Breed, &[KittyData::default().into(), KittyData::default_dad().into()], - &[], // no peeks + &[], &[KittyData::default().into()], ); assert_eq!(result, Err(ConstraintCheckerError::NotEnoughFamilyMembers)); @@ -114,12 +151,12 @@ fn outputs_dont_contain_all_family_members_fails() { #[test] fn breed_two_dads_fails() { let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker, + &FreeKittyConstraintChecker::Breed, &[ KittyData::default_dad().into(), KittyData::default_dad().into(), ], - &[], // no peeks + &[], &[KittyData::default().into()], ); assert_eq!(result, Err(ConstraintCheckerError::TwoDadsNotValid)); @@ -128,9 +165,9 @@ fn breed_two_dads_fails() { #[test] fn breed_two_moms_fails() { let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker, + &FreeKittyConstraintChecker::Breed, &[KittyData::default().into(), KittyData::default().into()], - &[], // no peeks + &[], &[KittyData::default().into()], ); assert_eq!(result, Err(ConstraintCheckerError::TwoMomsNotValid)); @@ -139,9 +176,9 @@ fn breed_two_moms_fails() { #[test] fn first_input_not_mom_fails() { let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker, + &FreeKittyConstraintChecker::Breed, &[KittyData::default_dad().into(), KittyData::default().into()], - &[], // no peeks + &[], &[], ); assert_eq!(result, Err(ConstraintCheckerError::TwoDadsNotValid)) @@ -150,9 +187,9 @@ fn first_input_not_mom_fails() { #[test] fn first_output_not_mom_fails() { let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker, + &FreeKittyConstraintChecker::Breed, &[KittyData::default().into(), KittyData::default_dad().into()], - &[], // no peeks + &[], &[ KittyData::default_dad().into(), KittyData::default().into(), @@ -168,9 +205,9 @@ fn breed_mom_when_she_gave_birth_recently_fails() { new_momma.parent = Parent::Mom(MomKittyStatus::HadBirthRecently); let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker, + &FreeKittyConstraintChecker::Breed, &[new_momma.into(), KittyData::default_dad().into()], - &[], // no peeks + &[], &[], ); assert_eq!(result, Err(ConstraintCheckerError::MomNotReadyYet)); @@ -182,9 +219,9 @@ fn breed_dad_when_he_is_tired_fails() { tired_dadda.parent = Parent::Dad(DadKittyStatus::Tired); let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker, + &FreeKittyConstraintChecker::Breed, &[KittyData::default().into(), tired_dadda.into()], - &[], // no peeks + &[], &[], ); assert_eq!(result, Err(ConstraintCheckerError::DadTooTired)); @@ -196,9 +233,9 @@ fn check_mom_breedings_overflow_fails() { test_mom.num_breedings = u128::MAX; let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker, + &FreeKittyConstraintChecker::Breed, &[test_mom.into(), KittyData::default_dad().into()], - &[], // no peeks + &[], &[], ); assert_eq!( @@ -213,9 +250,9 @@ fn check_dad_breedings_overflow_fails() { test_dad.num_breedings = u128::MAX; let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker, + &FreeKittyConstraintChecker::Breed, &[KittyData::default().into(), test_dad.into()], - &[], // no peeks + &[], &[], ); assert_eq!( @@ -230,9 +267,9 @@ fn check_mom_free_breedings_zero_fails() { test_mom.free_breedings = 0; let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker, + &FreeKittyConstraintChecker::Breed, &[test_mom.into(), KittyData::default_dad().into()], - &[], // no peeks + &[], &[], ); assert_eq!(result, Err(ConstraintCheckerError::NotEnoughFreeBreedings)); @@ -244,9 +281,9 @@ fn check_dad_free_breedings_zero_fails() { test_dad.free_breedings = 0; let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker, + &FreeKittyConstraintChecker::Breed, &[KittyData::default().into(), test_dad.into()], - &[], // no peeks + &[], &[], ); assert_eq!(result, Err(ConstraintCheckerError::NotEnoughFreeBreedings)); @@ -259,9 +296,9 @@ fn check_new_mom_free_breedings_incorrect_fails() { new_mom.free_breedings = 2; let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker, + &FreeKittyConstraintChecker::Breed, &[KittyData::default().into(), KittyData::default_dad().into()], - &[], // no peeks + &[], &[ new_mom.into(), new_family[1].clone().into(), @@ -281,9 +318,9 @@ fn check_new_dad_free_breedings_incorrect_fails() { new_dad.free_breedings = 2; let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker, + &FreeKittyConstraintChecker::Breed, &[KittyData::default().into(), KittyData::default_dad().into()], - &[], // no peeks + &[], &[ new_family[0].clone().into(), new_dad.into(), @@ -303,9 +340,9 @@ fn check_new_mom_num_breedings_incorrect_fails() { new_mom.num_breedings = 0; let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker, + &FreeKittyConstraintChecker::Breed, &[KittyData::default().into(), KittyData::default_dad().into()], - &[], // no peeks + &[], &[ new_mom.into(), new_family[1].clone().into(), @@ -325,9 +362,9 @@ fn check_new_dad_num_breedings_incorrect_fails() { new_dad.num_breedings = 0; let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker, + &FreeKittyConstraintChecker::Breed, &[KittyData::default().into(), KittyData::default_dad().into()], - &[], // no peeks + &[], &[ new_family[0].clone().into(), new_dad.into(), @@ -347,9 +384,9 @@ fn check_new_mom_dna_doesnt_match_old_fails() { new_mom.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoci")); let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker, + &FreeKittyConstraintChecker::Breed, &[KittyData::default().into(), KittyData::default_dad().into()], - &[], // no peeks + &[], &[ new_mom.into(), new_family[1].clone().into(), @@ -369,9 +406,9 @@ fn check_new_dad_dna_doesnt_match_old_fails() { new_dad.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoci")); let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker, + &FreeKittyConstraintChecker::Breed, &[KittyData::default().into(), KittyData::default_dad().into()], - &[], // no peeks + &[], &[ new_family[0].clone().into(), new_dad.into(), @@ -391,9 +428,9 @@ fn check_child_dna_incorrect_fails() { new_child.dna = KittyDNA(H256::zero()); let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker, + &FreeKittyConstraintChecker::Breed, &[KittyData::default().into(), KittyData::default_dad().into()], - &[], // no peeks + &[], &[ new_family[0].clone().into(), new_family[1].clone().into(), @@ -410,9 +447,9 @@ fn check_child_dad_parent_tired_fails() { new_child.parent = Parent::Dad(DadKittyStatus::Tired); let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker, + &FreeKittyConstraintChecker::Breed, &[KittyData::default().into(), KittyData::default_dad().into()], - &[], // no peeks + &[], &[ new_family[0].clone().into(), new_family[1].clone().into(), @@ -432,9 +469,9 @@ fn check_child_mom_parent_recently_gave_birth_fails() { new_child.parent = Parent::Mom(MomKittyStatus::HadBirthRecently); let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker, + &FreeKittyConstraintChecker::Breed, &[KittyData::default().into(), KittyData::default_dad().into()], - &[], // no peeks + &[], &[ new_family[0].clone().into(), new_family[1].clone().into(), @@ -454,9 +491,9 @@ fn check_child_free_breedings_incorrect_fails() { new_child.free_breedings = KittyHelpers::NUM_FREE_BREEDINGS + 1; let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker, + &FreeKittyConstraintChecker::Breed, &[KittyData::default().into(), KittyData::default_dad().into()], - &[], // no peeks + &[], &[ new_family[0].clone().into(), new_family[1].clone().into(), @@ -476,9 +513,9 @@ fn check_child_num_breedings_non_zero_fails() { new_child.num_breedings = 42; let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker, + &FreeKittyConstraintChecker::Breed, &[KittyData::default().into(), KittyData::default_dad().into()], - &[], // no peeks + &[], &[ new_family[0].clone().into(), new_family[1].clone().into(), @@ -490,3 +527,199 @@ fn check_child_num_breedings_non_zero_fails() { Err(ConstraintCheckerError::NewChildHasNonZeroBreedings) ); } + +#[test] +fn update_name_happy_path_works() { + let input = KittyData::default_dad(); + let mut output = KittyData::default_dad(); + output.name = *b"kty1"; + + let result = FreeKittyConstraintChecker::check( + &FreeKittyConstraintChecker::UpdateKittiesName, + &[input.into()], + &[], + &[output.into()], + ); + assert!(result.is_ok()); +} + +#[test] +fn update_name_happy_path_with_multiple_input_sworks() { + let input1 = KittyData::default_dad(); + let mut input2 = KittyData::default(); + input2.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialaroci")); + let mut output1 = input1.clone(); + let mut output2 = input2.clone(); + + output1.name = *b"kty1"; + output2.name = *b"kty2"; + + let result = FreeKittyConstraintChecker::check( + &FreeKittyConstraintChecker::UpdateKittiesName, + &[input1.into(), input2.into()], + &[], + &[output1.into(), output2.into()], + ); + assert!(result.is_ok()); +} + +#[test] +fn update_name_inputs_and_outputs_number_mismatch_fails() { + let input1 = KittyData::default_dad(); + let input2 = KittyData::default_dad(); + let mut output1 = input1.clone(); + let mut output2 = input2.clone(); + + output1.name = *b"kty1"; + output2.name = *b"kty2"; + + let result = FreeKittyConstraintChecker::check( + &FreeKittyConstraintChecker::UpdateKittiesName, + &[input1.into(), input2.into()], + &[], + &[output1.into()], + ); + assert_eq!( + result, + Err(ConstraintCheckerError::NumberOfInputOutputMismatch) + ); +} + +#[test] +fn update_name_no_inputs_fails() { + let output = KittyData::default_dad(); + + let result = FreeKittyConstraintChecker::check( + &FreeKittyConstraintChecker::UpdateKittiesName, + &[], + &[], + &[output.into()], + ); + assert_eq!( + result, + Err(ConstraintCheckerError::NumberOfInputOutputMismatch) + ); +} + +#[test] +fn update_name_no_output_fails() { + let input = KittyData::default_dad(); + + let result = FreeKittyConstraintChecker::check( + &FreeKittyConstraintChecker::UpdateKittiesName, + &[input.into()], + &[], + &[], + ); + assert_eq!( + result, + Err(ConstraintCheckerError::NumberOfInputOutputMismatch) + ); +} + +#[test] +fn update_name_dna_update_fails() { + let input = KittyData::default_dad(); + let mut output = input.clone(); + output.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoca")); + output.name = *b"kty1"; + + let result = FreeKittyConstraintChecker::check( + &FreeKittyConstraintChecker::UpdateKittiesName, + &[input.into()], + &[], + &[output.into()], + ); + assert_eq!( + result, + Err(ConstraintCheckerError::DnaMismatchBetweenInputAndOutput) + ); +} + +#[test] +fn update_name_out_of_order_input_and_output_fails() { + let input = KittyData::default_dad(); + let mut output = input.clone(); + output.name = *b"kty1"; + + let mut input1 = KittyData::default_dad(); + input1.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoca")); + let mut output1 = input1.clone(); + output1.name = *b"kty2"; + + let result = FreeKittyConstraintChecker::check( + &FreeKittyConstraintChecker::UpdateKittiesName, + &[input.clone().into(), input1.into()], + &[], + &[output1.clone().into(), output.into()], + ); + assert_eq!( + result, + Err(ConstraintCheckerError::DnaMismatchBetweenInputAndOutput) + ); +} + +#[test] +fn update_name_name_unupdated_path_fails() { + let result = FreeKittyConstraintChecker::check( + &FreeKittyConstraintChecker::UpdateKittiesName, + &[KittyData::default_dad().into()], + &[], + &[KittyData::default_dad().into()], + ); + assert_eq!(result, Err(ConstraintCheckerError::KittyNameUnAltered)); +} + +#[test] +fn update_name_free_breeding_updated_path_fails() { + let mut output = KittyData::default_dad(); + output.name = *b"kty1"; + output.free_breedings += 1; + + let result = FreeKittyConstraintChecker::check( + &FreeKittyConstraintChecker::UpdateKittiesName, + &[KittyData::default().into()], + &[], + &[output.into()], + ); + assert_eq!( + result, + Err(ConstraintCheckerError::FreeBreedingCannotBeUpdated) + ); +} + +#[test] +fn update_name_num_of_breeding_updated_path_fails() { + let mut output = KittyData::default_dad(); + output.name = *b"kty1"; + output.num_breedings += 1; + + let result = FreeKittyConstraintChecker::check( + &FreeKittyConstraintChecker::UpdateKittiesName, + &[KittyData::default().into()], + &[], + &[output.into()], + ); + assert_eq!( + result, + Err(ConstraintCheckerError::NumOfBreedingCannotBeUpdated) + ); +} + +#[test] +fn update_name_gender_updated_path_fails() { + let input = KittyData::default(); + let mut output = KittyData::default_dad(); + output.name = *b"kty1"; + + let result = FreeKittyConstraintChecker::check( + &FreeKittyConstraintChecker::UpdateKittiesName, + &[input.into()], + &[], + &[output.into()], + ); + assert_eq!( + result, + Err(ConstraintCheckerError::KittyGenderCannotBeUpdated) + ); +} diff --git a/wardrobe/tradable_kitties/Cargo.toml b/wardrobe/tradable_kitties/Cargo.toml new file mode 100644 index 000000000..a88820538 --- /dev/null +++ b/wardrobe/tradable_kitties/Cargo.toml @@ -0,0 +1,31 @@ +[package] +description = "A Tuxedo piece that provides an NFT game loosely inspired by crypto kitties which can be traded" +edition = "2021" +name = "tradable-kitties" +version = "0.1.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +kitties = { default-features = false, path = "../kitties/" } +money = { default-features = false, path = "../money/" } +parity-scale-codec = { features = [ "derive" ], workspace = true } +scale-info = { features = [ "derive" ], workspace = true } +serde = { features = [ "derive" ], workspace = true } +sp-core = { default_features = false, workspace = true } +sp-runtime = { default_features = false, workspace = true } +sp-std = { default_features = false, workspace = true } +tuxedo-core = { default-features = false, path = "../../tuxedo-core" } + +[features] +default = [ "std" ] +std = [ + "tuxedo-core/std", + "parity-scale-codec/std", + "sp-runtime/std", + "sp-std/std", + "sp-core/std", + "serde/std", + "money/std", + "kitties/std", +] diff --git a/wardrobe/tradable_kitties/src/lib.rs b/wardrobe/tradable_kitties/src/lib.rs new file mode 100644 index 000000000..21e53324a --- /dev/null +++ b/wardrobe/tradable_kitties/src/lib.rs @@ -0,0 +1,403 @@ +//! # TradableKitty Module +//! +//! This Tuxedo piece codifies additional features that work with the Kitties piece. +//! This piece should not and cannot be used without the Kitties and Money pieces. +//! The introduced features are: +//! This piece should not and cannot be used without the Kitties and Money pieces. +//! The introduced features are: +//! +//! - **ListKittiesForSale:** Convert basic kitties into tradable kitties, adding a `price` field. +//! - **DelistKittiesFromSale:** Transform tradable kitties back into regular kitties when owners decide not to sell. +//! - **UpdateKittiesPrice:** Allow owners to modify the `price` of TradableKitties. +//! - **UpdateKittiesName:** Permit owners to update the `name` of TradableKitties. +//! +//! - **Buy:** Enable users to securely purchase TradableKitty from others, ensuring fair exchanges. +//! Make sure to place the kitty first and then coins in the inputs and outputs. +//! +//! *Note: Only one kitty can be bought at a time.* +//! + +#![cfg_attr(not(feature = "std"), no_std)] + +use kitties::KittyData; +use money::{Coin, ConstraintCheckerError as MoneyError, MoneyConstraintChecker}; +use parity_scale_codec::{Decode, Encode}; +use scale_info::TypeInfo; +use serde::{Deserialize, Serialize}; +use sp_runtime::transaction_validity::TransactionPriority; +use sp_std::prelude::*; +use tuxedo_core::{ + dynamic_typing::{DynamicallyTypedData, UtxoData}, + ensure, SimpleConstraintChecker, +}; + +#[cfg(test)] +mod tests; + +/// The default price of a kitten is 10. +const DEFAULT_KITTY_PRICE: u128 = 10; + +/// A `TradableKittyData` is required for trading the Kitty. +/// It includes the `price` of the Kitty, in addition to the basic `KittyData` provided by the Kitties piece. +/// A `TradableKittyData` is required for trading the Kitty. +/// It includes the `price` of the Kitty, in addition to the basic `KittyData` provided by the Kitties piece. +#[derive( + Serialize, + Deserialize, + PartialEq, + Eq, + PartialOrd, + Ord, + Clone, + Encode, + Decode, + Hash, + Debug, + TypeInfo, +)] +pub struct TradableKittyData { + /// Basic `KittyData` composed from the `kitties` piece. + pub kitty_basic_data: KittyData, + /// Price of the `TradableKitty` + pub price: u128, +} + +impl Default for TradableKittyData { + fn default() -> Self { + Self { + kitty_basic_data: KittyData::default(), + price: DEFAULT_KITTY_PRICE, + } + } +} + +impl TryFrom<&DynamicallyTypedData> for TradableKittyData { + type Error = TradeableKittyError; + fn try_from(a: &DynamicallyTypedData) -> Result { + a.extract::() + .map_err(|_| TradeableKittyError::BadlyTyped) + } +} + +impl UtxoData for TradableKittyData { + const TYPE_ID: [u8; 4] = *b"tdkt"; +} + +/// Reasons that tradable kitty opertaion may go wrong. +#[derive( + Serialize, + Deserialize, + PartialEq, + Eq, + PartialOrd, + Ord, + Clone, + Encode, + Decode, + Hash, + Debug, + TypeInfo, +)] +pub enum TradeableKittyError { + /// Error in the underlying `money` piece. + MoneyError(money::ConstraintCheckerError), + /// Error in the underlying `kitties` piece. + KittyError(kitties::ConstraintCheckerError), + /// Dynamic typing issue. + /// This error doesn't discriminate between badly typed inputs and outputs. + BadlyTyped, + /// Input missing for the transaction. + InputMissingError, + /// Output missing for the transaction. + OutputMissingError, + /// Not enough amount to buy a `kitty`. + InsufficientCollateralToBuyKitty, + /// The number of input vs number of output doesn't match for a transaction. + NumberOfInputOutputMismatch, + /// Kitty basic properties such as `DNA`, `free breeding`, and the `number of breedings`, are altered error. + KittyBasicPropertiesAltered, + /// Kitty `price` can't be zero when it is available for sale. + KittyPriceCantBeZero, + /// Kitty `price` is unaltered and is not allowed for kitty price update transactions. + KittyPriceUnaltered, +} + +impl From for TradeableKittyError { + fn from(error: money::ConstraintCheckerError) -> Self { + TradeableKittyError::MoneyError(error) + } +} + +impl From for TradeableKittyError { + fn from(error: kitties::ConstraintCheckerError) -> Self { + TradeableKittyError::KittyError(error) + } +} + +/// The main constraint checker for the tradable kitty piece. Allows the following: +/// Listing kitty for sale: Multiple kitties are allowed, provided input and output are in the same order. +/// Delisting kitty from sale: Multiple tradable kitties are allowed, provided input and output are in the same order. +/// Update kitty price: Multiple tradable kitties are allowed, provided input and output are in the same order. +/// Update kitty name: Multiple tradable kitties are allowed, provided input and output are in the same order. +/// Buy tradable kitty: Multiple tradable kitties are not allowed. Only a single kitty operation is allowed. +/// For buying a kitty, you need to send the kitty first, and then coins in both input and output of the transaction. + +#[derive( + Serialize, + Deserialize, + PartialEq, + Eq, + PartialOrd, + Ord, + Clone, + Encode, + Decode, + Hash, + Debug, + TypeInfo, +)] +pub enum TradableKittyConstraintChecker { + /// List the kitties for sale. This means the kitties will be converted to a tradable kitties once the transaction is executed. + ListKittiesForSale, + /// Delist the kitties from sale, This means tradable kitties will converted back to kitties. + DelistKittiesFromSale, + /// Update the `price` of tradable kitties. + UpdateKittiesPrice, + // Update the name of the kitties. + UpdateKittiesName, + /// For buying a new kitty from other owners. + Buy, +} + +/// Checks if buying the kitty is possible or not. It depends on the Money variable to validate the spending of coins. +/// Make sure to place the kitty first and then the coins in the transaction. +fn check_can_buy( + input_data: &[DynamicallyTypedData], + output_data: &[DynamicallyTypedData], +) -> Result { + ensure!(!input_data.is_empty(), { + TradeableKittyError::InputMissingError + }); + + ensure!(!output_data.is_empty(), { + TradeableKittyError::OutputMissingError + }); + + let mut input_coin_data: Vec = Vec::new(); + let mut output_coin_data: Vec = Vec::new(); + let input_kitty_to_be_traded: Option; + + let mut total_input_amount: u128 = 0; + let total_price_of_kitty: u128; + + if let Ok(td_input_kitty) = input_data[0].extract::() { + ensure!( + td_input_kitty.price != 0, + TradeableKittyError::KittyPriceCantBeZero + ); + input_kitty_to_be_traded = Some(td_input_kitty.clone()); + total_price_of_kitty = td_input_kitty.price; + } else { + return Err(TradeableKittyError::BadlyTyped); + } + + if let Ok(td_output_kitty) = output_data[0].extract::() { + ensure!( + input_kitty_to_be_traded.clone().unwrap().kitty_basic_data + == td_output_kitty.kitty_basic_data, + TradeableKittyError::KittyBasicPropertiesAltered + ); + } else { + return Err(TradeableKittyError::BadlyTyped); + } + + for coin_data in input_data.iter().skip(1) { + let coin = coin_data + .clone() + .extract::>() + .map_err(|_| TradeableKittyError::BadlyTyped)?; + + let utxo_value = coin.0; + ensure!( + utxo_value > 0, + TradeableKittyError::MoneyError(MoneyError::ZeroValueCoin) + ); + input_coin_data.push(coin_data.clone()); + total_input_amount = total_input_amount + .checked_add(utxo_value) + .ok_or(TradeableKittyError::MoneyError(MoneyError::ValueOverflow))?; + } + + for coin_data in output_data.iter().skip(1) { + let coin = coin_data + .clone() + .extract::>() + .map_err(|_| TradeableKittyError::BadlyTyped)?; + + let utxo_value = coin.0; + ensure!( + utxo_value > 0, + TradeableKittyError::MoneyError(MoneyError::ZeroValueCoin) + ); + ensure!( + utxo_value > 0, + TradeableKittyError::MoneyError(MoneyError::ZeroValueCoin) + ); + output_coin_data.push(coin_data.clone()); + } + ensure!( + total_price_of_kitty <= total_input_amount, + TradeableKittyError::InsufficientCollateralToBuyKitty + ); + + // Filtered coins are sent to MoneyConstraintChecker for money validation. + Ok(MoneyConstraintChecker::<0>::Spend.check(&input_coin_data, &[], &output_coin_data)?) +} + +/// Checks if updates to the prices of tradable kitties are possible or not. +/// Prices of multiple tradable kitties can be updated in the same transaction. +fn check_kitties_price_update( + input_data: &[DynamicallyTypedData], + output_data: &[DynamicallyTypedData], +) -> Result { + ensure!(input_data.len() == output_data.len(), { + TradeableKittyError::NumberOfInputOutputMismatch + }); + for i in 0..input_data.len() { + let utxo_input_tradable_kitty = input_data[i] + .extract::() + .map_err(|_| TradeableKittyError::BadlyTyped)?; + + let utxo_output_tradable_kitty = output_data[i] + .extract::() + .map_err(|_| TradeableKittyError::BadlyTyped)?; + + ensure!( + utxo_input_tradable_kitty.kitty_basic_data + == utxo_output_tradable_kitty.kitty_basic_data, + TradeableKittyError::KittyBasicPropertiesAltered + ); + ensure!( + utxo_output_tradable_kitty.price != 0, + TradeableKittyError::KittyPriceCantBeZero + ); + ensure!( + utxo_input_tradable_kitty.price != utxo_output_tradable_kitty.price, + TradeableKittyError::KittyPriceUnaltered + ); + } + + Ok(0) +} + +/// Wrapper function for verifying the conversion from basic kitties to tradable kitties. +/// Multiple kitties can be converted in a single transaction. +fn check_can_list_kitties_for_sale( + input_data: &[DynamicallyTypedData], + output_data: &[DynamicallyTypedData], +) -> Result { + check_kitties_tdkitties_interconversion(input_data, output_data)?; + Ok(0) +} + +/// Wrapper function for verifying the conversion from tradable kitties to basic kitties. +/// Multiple tradable kitties can be converted in a single transaction. +fn check_can_delist_kitties_from_sale( + input_data: &[DynamicallyTypedData], + output_data: &[DynamicallyTypedData], +) -> Result { + // Below is the conversion from tradable kitty to regular kitty, the reverse of the ListKittiesForSale. + // Hence, input parameters are reversed. + check_kitties_tdkitties_interconversion(output_data, input_data)?; + Ok(0) +} + +/// Validates inter-conversion between both regular kitties and tradable kitties, as used by the `listForSale` and `delistFromSale` functions. +fn check_kitties_tdkitties_interconversion( + kitty_data: &[DynamicallyTypedData], + tradable_kitty_data: &[DynamicallyTypedData], +) -> Result { + ensure!(kitty_data.len() == tradable_kitty_data.len(), { + TradeableKittyError::NumberOfInputOutputMismatch + }); + + for i in 0..kitty_data.len() { + let utxo_kitty = kitty_data[i] + .extract::() + .map_err(|_| TradeableKittyError::BadlyTyped)?; + + let utxo_tradable_kitty = tradable_kitty_data[i] + .extract::() + .map_err(|_| TradeableKittyError::BadlyTyped)?; + + ensure!( + utxo_kitty == utxo_tradable_kitty.kitty_basic_data, + TradeableKittyError::KittyBasicPropertiesAltered + ); + ensure!( + utxo_tradable_kitty.price != 0, + TradeableKittyError::KittyPriceCantBeZero + ); + } + + Ok(0) +} + +impl SimpleConstraintChecker for TradableKittyConstraintChecker { + type Error = TradeableKittyError; + + fn check( + &self, + input_data: &[DynamicallyTypedData], + _peeks: &[DynamicallyTypedData], + output_data: &[DynamicallyTypedData], + ) -> Result { + match &self { + Self::ListKittiesForSale => { + check_can_list_kitties_for_sale(input_data, output_data)?; + } + Self::DelistKittiesFromSale => { + check_can_delist_kitties_from_sale(input_data, output_data)?; + } + Self::UpdateKittiesPrice => { + check_kitties_price_update(input_data, output_data)?; + } + Self::UpdateKittiesName => { + let result: Result, _> = input_data + .iter() + .map(|utxo| utxo.extract::()) + .collect::, _>>() + .map(|vec| { + vec.into_iter() + .map(|tdkitty| tdkitty.kitty_basic_data.clone().into()) + .collect() + }); + + let input_basic_kitty_data = result.map_err(|_| TradeableKittyError::BadlyTyped)?; + + let result: Result, _> = output_data + .iter() + .map(|utxo| utxo.extract::()) + .collect::, _>>() + .map(|vec| { + vec.into_iter() + .map(|tdkitty| tdkitty.kitty_basic_data.clone().into()) + .collect() + }); + + let output_basic_kitty_data = + result.map_err(|_| TradeableKittyError::BadlyTyped)?; + + kitties::can_kitties_name_be_updated( + &input_basic_kitty_data, + &output_basic_kitty_data, + )?; + } + Self::Buy => { + let priority = check_can_buy::(input_data, output_data)?; + return Ok(priority); + } + } + Ok(0) + } +} diff --git a/wardrobe/tradable_kitties/src/tests.rs b/wardrobe/tradable_kitties/src/tests.rs new file mode 100644 index 000000000..f84fe193d --- /dev/null +++ b/wardrobe/tradable_kitties/src/tests.rs @@ -0,0 +1,891 @@ +//! Tests for the Tradable Kitty Piece + +use super::*; +use kitties::DadKittyStatus; +use kitties::KittyDNA; +use kitties::MomKittyStatus; +use kitties::Parent; +use sp_runtime::testing::H256; +use tuxedo_core::dynamic_typing::testing::Bogus; + +impl TradableKittyData { + pub fn default_kitty() -> KittyData { + KittyData { + parent: Parent::Dad(DadKittyStatus::RearinToGo), + ..Default::default() + } + } + + pub fn default_tradable_kitty() -> Self { + let kitty_basic = KittyData { + parent: Parent::Dad(DadKittyStatus::RearinToGo), + ..Default::default() + }; + TradableKittyData { + kitty_basic_data: kitty_basic, + price: 100, + ..Default::default() + } + } +} +// ListKittiesForSale UT startes from here. +#[test] +fn list_kitty_for_sale_happy_path_works() { + let result = TradableKittyConstraintChecker::<0>::ListKittiesForSale.check( + &[TradableKittyData::default_kitty().into()], + &[], + &[TradableKittyData::default_tradable_kitty().into()], + ); + assert!(result.is_ok()); +} + +#[test] +fn list_kitty_for_sale_multiple_input_happy_path_works() { + let input1 = TradableKittyData::default_kitty(); + let mut input2 = TradableKittyData::default_kitty(); + input2.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadola")); + let mut output1 = TradableKittyData::default_tradable_kitty(); + let mut output2 = TradableKittyData::default_tradable_kitty(); + output1.kitty_basic_data = input1.clone(); + output2.kitty_basic_data = input2.clone(); + + let result = TradableKittyConstraintChecker::<0>::ListKittiesForSale.check( + &[input1.into(), input2.into()], + &[], + &[output1.into(), output2.into()], + ); + assert!(result.is_ok()); +} + +#[test] +fn list_kitty_for_sale_multiple_out_of_order_input_fails() { + let input1 = TradableKittyData::default_kitty(); + let mut input2 = TradableKittyData::default_kitty(); + input2.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadola")); + let mut output1 = TradableKittyData::default_tradable_kitty(); + let mut output2 = TradableKittyData::default_tradable_kitty(); + output1.kitty_basic_data = input1.clone(); + output2.kitty_basic_data = input2.clone(); + + let result = TradableKittyConstraintChecker::<0>::ListKittiesForSale.check( + &[input1.into(), input2.into()], + &[], + &[output2.into(), output1.into()], + ); + assert_eq!( + result, + Err(TradeableKittyError::KittyBasicPropertiesAltered) + ); +} + +#[test] +fn list_kitty_for_sale_different_num_of_input_output_path_fails() { + let mut input1 = TradableKittyData::default_kitty(); + let input2 = TradableKittyData::default_kitty(); + input1.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoca")); + let mut output1 = TradableKittyData::default_tradable_kitty(); + output1.kitty_basic_data = input2.clone(); + + let result = TradableKittyConstraintChecker::<0>::ListKittiesForSale.check( + &[input1.into(), input2.into()], + &[], + &[output1.into()], + ); + assert_eq!( + result, + Err(TradeableKittyError::NumberOfInputOutputMismatch) + ); +} + +#[test] +fn list_kitty_for_sale_input_missing_path_fails() { + let output = TradableKittyData::default_tradable_kitty(); + + let result = + TradableKittyConstraintChecker::<0>::ListKittiesForSale.check(&[], &[], &[output.into()]); + assert_eq!( + result, + Err(TradeableKittyError::NumberOfInputOutputMismatch) + ); +} + +#[test] +fn list_kitty_for_sale_out_put_missing_path_fails() { + let input1 = TradableKittyData::default_kitty(); + let result = + TradableKittyConstraintChecker::<0>::ListKittiesForSale.check(&[input1.into()], &[], &[]); + assert_eq!( + result, + Err(TradeableKittyError::NumberOfInputOutputMismatch) + ); +} + +#[test] +fn list_kitty_for_sale_with_input_missing_fails() { + let input1 = TradableKittyData::default_kitty(); + + let mut output1 = TradableKittyData::default_tradable_kitty(); + output1.kitty_basic_data = input1.clone(); + let mut output2 = TradableKittyData::default_tradable_kitty(); + output2.kitty_basic_data.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoca")); + let result = TradableKittyConstraintChecker::<0>::ListKittiesForSale.check( + &[], + &[], + &[output1.into(), output2.into()], + ); + assert_eq!( + result, + Err(TradeableKittyError::NumberOfInputOutputMismatch) + ); +} + +#[test] +fn list_for_sale_with_wrong_output_type_amoung_valid_output_fails() { + let input1 = TradableKittyData::default_kitty(); + let mut output1 = TradableKittyData::default_tradable_kitty(); + output1.kitty_basic_data = input1.clone(); + let result = TradableKittyConstraintChecker::<0>::ListKittiesForSale.check( + &[input1.into()], + &[], + &[Bogus.into()], + ); + assert_eq!(result, Err(TradeableKittyError::BadlyTyped)); +} + +#[test] +fn list_kitty_for_sale_with_wrong_input_type_fails() { + let result = TradableKittyConstraintChecker::<0>::ListKittiesForSale.check( + &[Bogus.into()], + &[], + &[TradableKittyData::default_tradable_kitty().into()], + ); + assert_eq!(result, Err(TradeableKittyError::BadlyTyped)); +} + +#[test] +fn list_for_sale_multiple_inputs_with_basic_property_changed_fails() { + let input1 = TradableKittyData::default_kitty(); + let input2 = TradableKittyData::default_kitty(); + + let mut output1 = TradableKittyData::default_tradable_kitty(); + output1.kitty_basic_data = input1.clone(); + let mut output2 = TradableKittyData::default_tradable_kitty(); + output2.kitty_basic_data.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoca")); + let result = TradableKittyConstraintChecker::<0>::ListKittiesForSale.check( + &[input1.into(), input2.into()], + &[], + &[output1.clone().into(), output2.into()], + ); + assert_eq!( + result, + Err(TradeableKittyError::KittyBasicPropertiesAltered) + ); +} + +#[test] +fn list_for_sale_with_basic_property_changed_fails() { + let input = TradableKittyData::default_kitty(); + let mut output = TradableKittyData::default_tradable_kitty(); + output.kitty_basic_data = input.clone(); + output.kitty_basic_data.free_breedings += 1; + let result = TradableKittyConstraintChecker::<0>::ListKittiesForSale.check( + &[input.into()], + &[], + &[output.into()], + ); + assert_eq!( + result, + Err(TradeableKittyError::KittyBasicPropertiesAltered) + ); +} + +#[test] +fn list_for_sale_with_price_zero_fails() { + let input1 = TradableKittyData::default_kitty(); + let mut output1 = TradableKittyData::default_tradable_kitty(); + output1.kitty_basic_data = input1.clone(); + output1.price = 0; + let result = TradableKittyConstraintChecker::<0>::ListKittiesForSale.check( + &[input1.into()], + &[], + &[output1.into()], + ); + assert_eq!(result, Err(TradeableKittyError::KittyPriceCantBeZero)); +} + +// DelistKittiesFromSale UT starts from here. +#[test] +fn delist_kitty_from_sale_happy_path_works() { + let result = TradableKittyConstraintChecker::<0>::DelistKittiesFromSale.check( + &[TradableKittyData::default_tradable_kitty().into()], + &[], + &[TradableKittyData::default_kitty().into()], + ); + assert!(result.is_ok()); +} + +#[test] +fn delist_kitty_from_sale_multiple_input_happy_path_works() { + let output1 = TradableKittyData::default_kitty(); + let mut output2 = TradableKittyData::default_kitty(); + output2.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoca")); + let input1 = TradableKittyData::default_tradable_kitty(); + let mut input2 = TradableKittyData::default_tradable_kitty(); + input2.kitty_basic_data = output2.clone(); + + let result = TradableKittyConstraintChecker::<0>::DelistKittiesFromSale.check( + &[input1.into(), input2.into()], + &[], + &[output1.into(), output2.into()], + ); + assert!(result.is_ok()); +} + +#[test] +fn delist_kitty_from_sale_multiple_input_out_of_order_path_fails() { + let output1 = TradableKittyData::default_kitty(); + let mut output2 = TradableKittyData::default_kitty(); + output2.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoca")); + let input1 = TradableKittyData::default_tradable_kitty(); + let mut input2 = TradableKittyData::default_tradable_kitty(); + input2.kitty_basic_data = output2.clone(); + + let result = TradableKittyConstraintChecker::<0>::DelistKittiesFromSale.check( + &[input1.into(), input2.into()], + &[], + &[output2.into(), output1.into()], + ); + assert_eq!( + result, + Err(TradeableKittyError::KittyBasicPropertiesAltered) + ); +} + +#[test] +fn delist_kitty_from_sale_different_num_of_input_output_fails() { + let output1 = TradableKittyData::default_kitty(); + let mut output2 = TradableKittyData::default_kitty(); + output2.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoca")); + let input1 = TradableKittyData::default_tradable_kitty(); + let mut input2 = TradableKittyData::default_tradable_kitty(); + input2.kitty_basic_data = output2; + + let result = TradableKittyConstraintChecker::<0>::DelistKittiesFromSale.check( + &[input1.into(), input2.into()], + &[], + &[output1.into()], + ); + assert_eq!( + result, + Err(TradeableKittyError::NumberOfInputOutputMismatch) + ); +} + +#[test] +fn delist_kitty_from_sale_input_missing_fails() { + let output = TradableKittyData::default_kitty(); + let result = TradableKittyConstraintChecker::<0>::DelistKittiesFromSale.check( + &[], + &[], + &[output.into()], + ); + assert_eq!( + result, + Err(TradeableKittyError::NumberOfInputOutputMismatch) + ); +} +#[test] +fn delist_kitty_from_sale_out_put_missing_path_fails() { + let result = TradableKittyConstraintChecker::<0>::DelistKittiesFromSale.check( + &[TradableKittyData::default_tradable_kitty().into()], + &[], + &[], + ); + assert_eq!( + result, + Err(TradeableKittyError::NumberOfInputOutputMismatch) + ); +} + +#[test] +fn delist_kitty_from_sale_with_wrong_output_type_ampoung_valid_output_fails() { + let output1 = TradableKittyData::default_kitty(); + let mut output2 = TradableKittyData::default_kitty(); + output2.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoca")); + let mut input1 = TradableKittyData::default_tradable_kitty(); + let mut input2 = TradableKittyData::default_tradable_kitty(); + input1.kitty_basic_data = output1.clone(); + input2.kitty_basic_data = output2.clone(); + + let result = TradableKittyConstraintChecker::<0>::DelistKittiesFromSale.check( + &[input1.into(), input2.into()], + &[], + &[output1.into(), Bogus.into()], + ); + assert_eq!(result, Err(TradeableKittyError::BadlyTyped)); +} + +#[test] +fn delist_from_sale_with_wrong_input_type_fails() { + let result = TradableKittyConstraintChecker::<0>::DelistKittiesFromSale.check( + &[Bogus.into()], + &[], + &[TradableKittyData::default_kitty().into()], + ); + assert_eq!(result, Err(TradeableKittyError::BadlyTyped)); +} + +#[test] +fn delist_from_sale_with_basic_property_update_fails() { + let output1 = TradableKittyData::default_kitty(); + let mut output2 = TradableKittyData::default_kitty(); + output2.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadocz")); + let mut input1 = TradableKittyData::default_tradable_kitty(); + let mut input2 = TradableKittyData::default_tradable_kitty(); + input1.kitty_basic_data = output1.clone(); + input2.kitty_basic_data = output2.clone(); + let result = TradableKittyConstraintChecker::<0>::DelistKittiesFromSale.check( + &[input1.clone().into(), input2.into()], + &[], + &[output1.clone().into(), output1.into()], + ); + assert_eq!( + result, + Err(TradeableKittyError::KittyBasicPropertiesAltered) + ); +} + +// From below update tradable kitty name test cases starts +#[test] +fn update_name_happy_path_works() { + let input = TradableKittyData::default_tradable_kitty(); + let mut output = input.clone(); + output.kitty_basic_data.name = *b"tdkt"; + + let result = TradableKittyConstraintChecker::<0>::UpdateKittiesName.check( + &[input.into()], + &[], + &[output.into()], + ); + assert!(result.is_ok()); +} + +#[test] +fn update_name_invalid_type_in_input_fails() { + let input = TradableKittyData::default_tradable_kitty(); + let mut output = input.clone(); + output.kitty_basic_data.name = *b"tdkt"; + + let result = TradableKittyConstraintChecker::<0>::UpdateKittiesName.check( + &[input.into(), Bogus.into()], + &[], + &[output.clone().into(), output.into()], + ); + assert_eq!(result, Err(TradeableKittyError::BadlyTyped)); +} + +#[test] +fn update_name_invalid_type_in_output_fails() { + let input = TradableKittyData::default_tradable_kitty(); + let mut output = input.clone(); + output.kitty_basic_data.name = *b"tdkt"; + + let result = TradableKittyConstraintChecker::<0>::UpdateKittiesName.check( + &[input.clone().into(), input.into()], + &[], + &[output.into(), Bogus.into()], + ); + assert_eq!(result, Err(TradeableKittyError::BadlyTyped)); +} + +#[test] +fn update_name_dna_update_fails() { + let input = TradableKittyData::default_tradable_kitty(); + let mut output = input.clone(); + output.kitty_basic_data.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoca")); + output.kitty_basic_data.name = *b"tdkt"; + + let result = TradableKittyConstraintChecker::<0>::UpdateKittiesName.check( + &[input.into()], + &[], + &[output.into()], + ); + assert_eq!( + result, + Err(TradeableKittyError::KittyError( + kitties::ConstraintCheckerError::DnaMismatchBetweenInputAndOutput + )) + ); +} + +#[test] +fn update_name_free_breeding_update_fails() { + let input = TradableKittyData::default_tradable_kitty(); + let mut output = input.clone(); + output.kitty_basic_data.name = *b"kty1"; + output.kitty_basic_data.free_breedings += 1; + + let result = TradableKittyConstraintChecker::<0>::UpdateKittiesName.check( + &[input.into()], + &[], + &[output.into()], + ); + assert_eq!( + result, + Err(TradeableKittyError::KittyError( + kitties::ConstraintCheckerError::FreeBreedingCannotBeUpdated + )) + ); +} + +#[test] +fn update_name_num_of_breeding_updated_path_fails() { + let input = TradableKittyData::default_tradable_kitty(); + let mut output = input.clone(); + output.kitty_basic_data.name = *b"kty1"; + output.kitty_basic_data.num_breedings += 1; + + let result = TradableKittyConstraintChecker::<0>::UpdateKittiesName.check( + &[input.into()], + &[], + &[output.into()], + ); + assert_eq!( + result, + Err(TradeableKittyError::KittyError( + kitties::ConstraintCheckerError::NumOfBreedingCannotBeUpdated + )) + ); +} + +#[test] +fn update_name_gender_updated_path_fails() { + let input = TradableKittyData::default_tradable_kitty(); + let mut output = TradableKittyData::default_tradable_kitty(); + output.kitty_basic_data.name = *b"tdk1"; + output.kitty_basic_data.parent = Parent::Mom(MomKittyStatus::RearinToGo); + + let result = TradableKittyConstraintChecker::<0>::UpdateKittiesName.check( + &[input.into()], + &[], + &[output.into()], + ); + assert_eq!( + result, + Err(TradeableKittyError::KittyError( + kitties::ConstraintCheckerError::KittyGenderCannotBeUpdated + )) + ); +} + +//// Price update UT starts from here. +#[test] +fn update_price_happy_path_works() { + let input = TradableKittyData::default_tradable_kitty(); + let mut output = input.clone(); + output.price = 500; + + let result = TradableKittyConstraintChecker::<0>::UpdateKittiesPrice.check( + &[input.into()], + &[], + &[output.into()], + ); + assert!(result.is_ok()); +} + +#[test] +fn update_price_multiple_input_happy_path_works() { + let input = TradableKittyData::default_tradable_kitty(); + let mut output = input.clone(); + output.price = 500; + let mut input1 = TradableKittyData::default_tradable_kitty(); + input1.kitty_basic_data.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoai")); + let mut output1 = input1.clone(); + output1.price = 700; + + let result = TradableKittyConstraintChecker::<0>::UpdateKittiesPrice.check( + &[input.into(), input1.into()], + &[], + &[output.into(), output1.into()], + ); + assert!(result.is_ok()); +} + +#[test] +fn update_price_multiple_input_out_of_order_fails() { + let input = TradableKittyData::default_tradable_kitty(); + let mut output = input.clone(); + output.price = 500; + let mut input1 = TradableKittyData::default_tradable_kitty(); + input1.kitty_basic_data.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoai")); + let mut output1 = input1.clone(); + output1.price = 700; + + let result = TradableKittyConstraintChecker::<0>::UpdateKittiesPrice.check( + &[input.into(), input1.into()], + &[], + &[output1.into(), output.into()], + ); + assert_eq!( + result, + Err(TradeableKittyError::KittyBasicPropertiesAltered) + ); +} + +#[test] +fn update_price_output_missing_path_fails() { + let input = TradableKittyData::default_tradable_kitty(); + let mut output = input.clone(); + output.price = 500; + let mut input1 = TradableKittyData::default_tradable_kitty(); + input1.kitty_basic_data.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoai")); + + let result = TradableKittyConstraintChecker::<0>::UpdateKittiesPrice.check( + &[input.into(), input1.into()], + &[], + &[output.into()], + ); + assert_eq!( + result, + Err(TradeableKittyError::NumberOfInputOutputMismatch) + ); +} + +#[test] +fn update_price_input_missing_path_fails() { + let input = TradableKittyData::default_tradable_kitty(); + let mut output = input.clone(); + output.price = 500; + let mut input1 = TradableKittyData::default_tradable_kitty(); + input1.kitty_basic_data.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoai")); + let mut output1 = input1.clone(); + output1.price = 700; + + let result = TradableKittyConstraintChecker::<0>::UpdateKittiesPrice.check( + &[input.into()], + &[], + &[output.into(), output1.into()], + ); + assert_eq!( + result, + Err(TradeableKittyError::NumberOfInputOutputMismatch) + ); +} + +#[test] +fn update_price_bad_input_path_fails() { + let input = TradableKittyData::default_tradable_kitty(); + let mut output = input.clone(); + output.price = 500; + + let result = TradableKittyConstraintChecker::<0>::UpdateKittiesPrice.check( + &[Bogus.into()], + &[], + &[output.into()], + ); + assert_eq!(result, Err(TradeableKittyError::BadlyTyped)); +} + +#[test] +fn update_price_bad_output_path_fails() { + let input = TradableKittyData::default_tradable_kitty(); + let mut output = input.clone(); + output.price = 500; + let mut input1 = TradableKittyData::default_tradable_kitty(); + input1.kitty_basic_data.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoai")); + + let result = TradableKittyConstraintChecker::<0>::UpdateKittiesPrice.check( + &[input.into(), input1.into()], + &[], + &[output.into(), Bogus.into()], + ); + assert_eq!(result, Err(TradeableKittyError::BadlyTyped)); +} + +#[test] +fn update_price_different_dna_path_fails() { + let input = TradableKittyData::default_tradable_kitty(); + let mut output = input.clone(); + output.price = 500; + + output.kitty_basic_data.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoai")); + + let result = TradableKittyConstraintChecker::<0>::UpdateKittiesPrice.check( + &[input.clone().into()], + &[], + &[output.into()], + ); + assert_eq!( + result, + Err(TradeableKittyError::KittyBasicPropertiesAltered) + ); +} + +#[test] +fn update_price_basic_properties_updated_path_fails() { + let input = TradableKittyData::default_tradable_kitty(); + let mut output = input.clone(); + output.price = 500; + output.kitty_basic_data.free_breedings += 1; + + let result = TradableKittyConstraintChecker::<0>::UpdateKittiesPrice.check( + &[input.into()], + &[], + &[output.into()], + ); + assert_eq!( + result, + Err(TradeableKittyError::KittyBasicPropertiesAltered) + ); +} + +#[test] +fn update_price_not_updated_path_fails() { + let input = TradableKittyData::default_tradable_kitty(); + let output = input.clone(); + + let result = TradableKittyConstraintChecker::<0>::UpdateKittiesPrice.check( + &[input.into()], + &[], + &[output.into()], + ); + assert_eq!(result, Err(TradeableKittyError::KittyPriceUnaltered)); +} + +#[test] +fn update_price_to_zero_updated_path_fails() { + let input = TradableKittyData::default_tradable_kitty(); + let mut output = input.clone(); + output.price = 0; + + let result = TradableKittyConstraintChecker::<0>::UpdateKittiesPrice.check( + &[input.into()], + &[], + &[output.into()], + ); + assert_eq!(result, Err(TradeableKittyError::KittyPriceCantBeZero)); +} + +// Buy tradable Kitty UT starts from here. + +#[test] +fn buy_happy_path_single_input_coinworks() { + let mut input_kitty = TradableKittyData::default_tradable_kitty(); + input_kitty.price = 100; + let output_kitty = input_kitty.clone(); + + let input_coin = Coin::<0>(100); + let output_coin = Coin::<0>(100); + + let result = TradableKittyConstraintChecker::<0>::Buy.check( + &[input_kitty.into(), input_coin.into()], + &[], + &[output_kitty.into(), output_coin.into()], + ); + assert!(result.is_ok()); +} + +#[test] +fn buy_happy_path_multiple_input_coinworks() { + let mut input_kitty = TradableKittyData::default_tradable_kitty(); + input_kitty.price = 100; + let output_kitty = input_kitty.clone(); + + let input_coin1 = Coin::<0>(10); + let input_coin2 = Coin::<0>(90); + let output_coin = Coin::<0>(100); + + let result = TradableKittyConstraintChecker::<0>::Buy.check( + &[input_kitty.into(), input_coin1.into(), input_coin2.into()], + &[], + &[output_kitty.into(), output_coin.into()], + ); + assert!(result.is_ok()); +} + +#[test] +fn buy_path_multiple_kitty_fails() { + let mut input_kitty = TradableKittyData::default_tradable_kitty(); + input_kitty.price = 100; + let output_kitty = input_kitty.clone(); + + let mut input_kitty1 = TradableKittyData::default_tradable_kitty(); + input_kitty1.kitty_basic_data.dna = + KittyDNA(H256::from_slice(b"superkalifragislisticexpialadolx")); + input_kitty1.price = 1; + let output_kitty1 = input_kitty1.clone(); + + let input_coin1 = Coin::<0>(10); + let input_coin2 = Coin::<0>(90); + let output_coin = Coin::<0>(100); + + let result = TradableKittyConstraintChecker::<0>::Buy.check( + &[ + input_kitty.into(), + input_kitty1.into(), + input_coin1.into(), + input_coin2.into(), + ], + &[], + &[ + output_kitty.into(), + output_kitty1.into(), + output_coin.into(), + ], + ); + assert_eq!(result, Err(TradeableKittyError::BadlyTyped)); +} + +#[test] +fn buy_kityy_with_price_none_fails() { + let mut input_kitty = TradableKittyData::default_tradable_kitty(); + input_kitty.price = 0; + let output_kitty = input_kitty.clone(); + + let input_coin = Coin::<0>(100); + let output_coin = Coin::<0>(100); + + let result = TradableKittyConstraintChecker::<0>::Buy.check( + &[input_kitty.into(), input_coin.into()], + &[], + &[output_kitty.into(), output_coin.into()], + ); + assert_eq!(result, Err(TradeableKittyError::KittyPriceCantBeZero)); +} + +#[test] +fn buy_kityy_wrong_input_type_fails() { + let mut input_kitty = TradableKittyData::default_tradable_kitty(); + + input_kitty.price = 101; + let output_kitty = input_kitty.clone(); + + let input_coin = Coin::<0>(100); + let output_coin = Coin::<0>(100); + + let result = TradableKittyConstraintChecker::<0>::Buy.check( + &[input_kitty.into(), input_coin.into(), Bogus.into()], + &[], + &[output_kitty.into(), output_coin.into()], + ); + assert_eq!(result, Err(TradeableKittyError::BadlyTyped)); +} + +#[test] +fn buy_kityy_wrong_output_type_fails() { + let mut input_kitty = TradableKittyData::default_tradable_kitty(); + input_kitty.price = 101; + let output_kitty = input_kitty.clone(); + + let input_coin = Coin::<0>(100); + let output_coin = Coin::<0>(100); + + let result = TradableKittyConstraintChecker::<0>::Buy.check( + &[input_kitty.into(), input_coin.into()], + &[], + &[output_kitty.into(), output_coin.into(), Bogus.into()], + ); + assert_eq!(result, Err(TradeableKittyError::BadlyTyped)); +} + +#[test] +fn buy_kitty_less_money_than_price_of_kityy_fails() { + let mut input_kitty = TradableKittyData::default_tradable_kitty(); + input_kitty.price = 101; + let output_kitty = input_kitty.clone(); + + let input_coin1 = Coin::<0>(100); + let output_coin = Coin::<0>(100); + + let result = TradableKittyConstraintChecker::<0>::Buy.check( + &[input_kitty.into(), input_coin1.into()], + &[], + &[output_kitty.into(), output_coin.into()], + ); + assert_eq!( + result, + Err(TradeableKittyError::InsufficientCollateralToBuyKitty) + ); +} + +#[test] +fn buy_kitty_coin_output_value_exceeds_input_coin_value_fails() { + let mut input_kitty = TradableKittyData::default_tradable_kitty(); + input_kitty.price = 101; + let output_kitty = input_kitty.clone(); + + let input_coin1 = Coin::<0>(100); + let input_coin2 = Coin::<0>(90); + let output_coin = Coin::<0>(300); + + let result = TradableKittyConstraintChecker::<0>::Buy.check( + &[input_kitty.into(), input_coin1.into(), input_coin2.into()], + &[], + &[output_kitty.into(), output_coin.into()], + ); + assert_eq!( + result, + Err(TradeableKittyError::MoneyError( + MoneyError::OutputsExceedInputs + )) + ) +} + +#[test] +fn buy_kitty_input_zero_coin_value_fails() { + let mut input_kitty = TradableKittyData::default_tradable_kitty(); + input_kitty.price = 101; + let output_kitty = input_kitty.clone(); + + let input_coin1 = Coin::<0>(0); + let input_coin2 = Coin::<0>(90); + let output_coin = Coin::<0>(300); + + let result = TradableKittyConstraintChecker::<0>::Buy.check( + &[input_kitty.into(), input_coin1.into(), input_coin2.into()], + &[], + &[output_kitty.into(), output_coin.into()], + ); + assert_eq!( + result, + Err(TradeableKittyError::MoneyError(MoneyError::ZeroValueCoin)) + ) +} + +#[test] +fn buy_kitty_output_zero_coin_value_fails() { + let mut input_kitty = TradableKittyData::default_tradable_kitty(); + input_kitty.price = 101; + let output_kitty = input_kitty.clone(); + + let input_coin1 = Coin::<0>(100); + let input_coin2 = Coin::<0>(90); + let output_coin = Coin::<0>(0); + + let result = TradableKittyConstraintChecker::<0>::Buy.check( + &[input_kitty.into(), input_coin1.into(), input_coin2.into()], + &[], + &[output_kitty.into(), output_coin.into()], + ); + assert_eq!( + result, + Err(TradeableKittyError::MoneyError(MoneyError::ZeroValueCoin)) + ) +} + +#[test] +fn buy_kitty_basic_kitty_fails() { + let input_kitty = TradableKittyData::default_kitty(); + let output_kitty = input_kitty.clone(); + + let input_coin1 = Coin::<0>(100); + let input_coin2 = Coin::<0>(90); + let output_coin = Coin::<0>(0); + + let result = TradableKittyConstraintChecker::<0>::Buy.check( + &[input_kitty.into(), input_coin1.into(), input_coin2.into()], + &[], + &[output_kitty.into(), output_coin.into()], + ); + assert_eq!(result, Err(TradeableKittyError::BadlyTyped)) +}