diff --git a/execution_engine_testing/tests/src/test/system_contracts/auction/distribute.rs b/execution_engine_testing/tests/src/test/system_contracts/auction/distribute.rs index 7d5da31892..356b85469b 100644 --- a/execution_engine_testing/tests/src/test/system_contracts/auction/distribute.rs +++ b/execution_engine_testing/tests/src/test/system_contracts/auction/distribute.rs @@ -974,8 +974,8 @@ fn should_distribute_rewards_after_restaking_delegated_funds() { delegator_1_stake = get_delegator_staked_amount(&mut builder, VALIDATOR_1.clone(), DELEGATOR_1.clone()); - let updelegate_amount = U512::from(1_000_000); - let updelegate_result = builder.bidding( + let undelegate_amount = U512::from(1_000_000); + let undelegate_result = builder.bidding( None, protocol_version, (*DELEGATOR_2_ADDR).into(), @@ -983,10 +983,10 @@ fn should_distribute_rewards_after_restaking_delegated_funds() { max_delegators_per_validator: u32::MAX, validator: VALIDATOR_1.clone(), delegator: DelegatorKind::PublicKey(DELEGATOR_2.clone()), - amount: updelegate_amount, + amount: undelegate_amount, }, ); - assert!(updelegate_result.is_success(), "{:?}", updelegate_result); + assert!(undelegate_result.is_success(), "{:?}", undelegate_result); builder.commit_transforms(builder.get_post_state_hash(), undelegate_result.effects()); delegator_2_stake = get_delegator_staked_amount(&mut builder, VALIDATOR_1.clone(), DELEGATOR_2.clone()); @@ -998,9 +998,10 @@ fn should_distribute_rewards_after_restaking_delegated_funds() { public_key: VALIDATOR_1.clone(), amount, delegation_rate: 0, - minimum_delegation_amount: updelegate_amount.as_u64(), - maximum_delegation_amount: updelegate_amount.as_u64(), + minimum_delegation_amount: undelegate_amount.as_u64(), + maximum_delegation_amount: undelegate_amount.as_u64(), minimum_bid_amount: DEFAULT_MINIMUM_BID_AMOUNT, + reserved_slots: 0, } } else { AuctionMethod::WithdrawBid { diff --git a/node/src/reactor/main_reactor/tests/transactions.rs b/node/src/reactor/main_reactor/tests/transactions.rs index a79fc7289d..b3f28589c2 100644 --- a/node/src/reactor/main_reactor/tests/transactions.rs +++ b/node/src/reactor/main_reactor/tests/transactions.rs @@ -2931,8 +2931,9 @@ async fn add_and_withdraw_bid_transaction() { PublicKey::from(&**BOB_SECRET_KEY), 0, bid_amount, - test.chainspec().core_config.minimum_delegation_amount, - test.chainspec().core_config.maximum_delegation_amount, + None, + None, + None, ) .unwrap() .with_chain_name(CHAIN_NAME) @@ -3089,18 +3090,12 @@ async fn insufficient_funds_add_bid() { let bid_amount = bob_initial_balance.total; let mut txn = Transaction::from( - TransactionV1Builder::new_add_bid( - BOB_PUBLIC_KEY.clone(), - 0, - bid_amount, - test.chainspec().core_config.minimum_delegation_amount, - test.chainspec().core_config.maximum_delegation_amount, - ) - .unwrap() - .with_chain_name(CHAIN_NAME) - .with_initiator_addr(PublicKey::from(&**BOB_SECRET_KEY)) - .build() - .unwrap(), + TransactionV1Builder::new_add_bid(BOB_PUBLIC_KEY.clone(), 0, bid_amount, None, None, None) + .unwrap() + .with_chain_name(CHAIN_NAME) + .with_initiator_addr(PublicKey::from(&**BOB_SECRET_KEY)) + .build() + .unwrap(), ); txn.sign(&BOB_SECRET_KEY); diff --git a/storage/src/data_access_layer/auction.rs b/storage/src/data_access_layer/auction.rs index 726ab0a3cb..723d670026 100644 --- a/storage/src/data_access_layer/auction.rs +++ b/storage/src/data_access_layer/auction.rs @@ -62,6 +62,8 @@ pub enum AuctionMethod { /// The minimum bid amount a validator must submit to have /// their bid considered as valid. minimum_bid_amount: u64, + /// Number of delegator slots which can be reserved for specific delegators + reserved_slots: u32, }, /// Withdraw bid. WithdrawBid { @@ -188,6 +190,8 @@ impl AuctionMethod { let maximum_delegation_amount = Self::get_named_argument(runtime_args, auction::ARG_MAXIMUM_DELEGATION_AMOUNT) .unwrap_or(global_maximum_delegation); + let reserved_slots = + Self::get_named_argument(runtime_args, auction::ARG_RESERVED_SLOTS).unwrap_or(0); Ok(Self::AddBid { public_key, @@ -196,6 +200,7 @@ impl AuctionMethod { minimum_delegation_amount, maximum_delegation_amount, minimum_bid_amount: global_minimum_bid_amount, + reserved_slots, }) } diff --git a/storage/src/global_state/state/mod.rs b/storage/src/global_state/state/mod.rs index 29a558d6c3..815561921d 100644 --- a/storage/src/global_state/state/mod.rs +++ b/storage/src/global_state/state/mod.rs @@ -1279,6 +1279,7 @@ pub trait StateProvider: Send + Sync + Sized { minimum_delegation_amount, maximum_delegation_amount, minimum_bid_amount, + reserved_slots, } => runtime .add_bid( public_key, @@ -1288,7 +1289,7 @@ pub trait StateProvider: Send + Sync + Sized { maximum_delegation_amount, minimum_bid_amount, max_delegators_per_validator, - 0, + reserved_slots, ) .map(AuctionMethodRet::UpdatedAmount) .map_err(TrackingCopyError::Api), diff --git a/types/Cargo.toml b/types/Cargo.toml index 23af9075e4..3d6c7c17a4 100644 --- a/types/Cargo.toml +++ b/types/Cargo.toml @@ -74,7 +74,7 @@ untrusted = "0.7.1" [features] json-schema = ["once_cell", "schemars", "serde-map-to-array/json-schema"] -testing = ["proptest", "proptest-derive", "rand/default", "rand_pcg", "strum", "bincode"] +testing = ["proptest", "proptest-derive", "rand/default", "rand_pcg", "strum", "bincode", "thiserror", "getrandom", "derp"] # Includes a restricted set of std lib functionality suitable for usage e.g. in a JS environment when compiled to Wasm. std = ["base16/std", "derp", "getrandom/std", "humantime", "itertools/use_std", "libc", "once_cell", "pem", "serde_json/preserve_order", "thiserror", "untrusted"] # Includes a complete set of std lib functionality, including filesystem I/O operations. diff --git a/types/src/crypto.rs b/types/src/crypto.rs index f42c741a04..1e64e1ca98 100644 --- a/types/src/crypto.rs +++ b/types/src/crypto.rs @@ -21,7 +21,7 @@ pub use asymmetric_key::{ SECP256K1_TAG, SYSTEM_ACCOUNT, SYSTEM_TAG, }; pub use error::Error; -#[cfg(any(feature = "std", test))] +#[cfg(any(feature = "std", feature = "testing", test))] pub use error::ErrorExt; pub(crate) fn blake2b>(data: T) -> [u8; BLAKE2B_DIGEST_LENGTH] { diff --git a/types/src/crypto/asymmetric_key.rs b/types/src/crypto/asymmetric_key.rs index 8edb388fa7..3a60de816b 100644 --- a/types/src/crypto/asymmetric_key.rs +++ b/types/src/crypto/asymmetric_key.rs @@ -13,6 +13,8 @@ use core::{ iter, marker::Copy, }; +#[cfg(any(feature = "testing", test))] +use rand::distributions::{Distribution, Standard}; #[cfg(any(feature = "std-fs-io", test))] use std::path::Path; @@ -45,7 +47,7 @@ use serde_json::json; #[cfg(any(feature = "std", test))] use untrusted::Input; -#[cfg(any(feature = "std", test))] +#[cfg(any(feature = "std", feature = "testing", test))] use crate::crypto::ErrorExt; #[cfg(any(feature = "std-fs-io", test))] use crate::file_utils::{read_file, write_file, write_private_file}; @@ -221,7 +223,7 @@ impl SecretKey { } /// Generates a new ed25519 variant using the system's secure random number generator. - #[cfg(any(feature = "std", test))] + #[cfg(any(feature = "std", feature = "testing", test))] pub fn generate_ed25519() -> Result { let mut bytes = [0u8; Self::ED25519_LENGTH]; getrandom::getrandom(&mut bytes[..])?; @@ -229,7 +231,7 @@ impl SecretKey { } /// Generates a new secp256k1 variant using the system's secure random number generator. - #[cfg(any(feature = "std", test))] + #[cfg(any(feature = "std", feature = "testing", test))] pub fn generate_secp256k1() -> Result { let mut bytes = [0u8; Self::SECP256K1_LENGTH]; getrandom::getrandom(&mut bytes[..])?; @@ -703,6 +705,18 @@ impl From<&SecretKey> for PublicKey { } } +#[cfg(any(feature = "testing", test))] +impl Distribution for Standard { + fn sample(&self, rng: &mut R) -> PublicKey { + let secret_key = if rng.gen() { + SecretKey::generate_ed25519().unwrap() + } else { + SecretKey::generate_secp256k1().unwrap() + }; + PublicKey::from(&secret_key) + } +} + #[cfg(any(feature = "testing", test))] impl PartialEq for SecretKey { fn eq(&self, other: &Self) -> bool { diff --git a/types/src/crypto/error.rs b/types/src/crypto/error.rs index b1b7f56fdb..01df0dab34 100644 --- a/types/src/crypto/error.rs +++ b/types/src/crypto/error.rs @@ -1,6 +1,6 @@ use alloc::string::String; use core::fmt::{self, Display, Formatter}; -#[cfg(any(feature = "std", test))] +#[cfg(any(feature = "std", feature = "testing", test))] use std::error::Error as StdError; #[cfg(feature = "datasize")] @@ -9,7 +9,7 @@ use ed25519_dalek::ed25519::Error as SignatureError; #[cfg(any(feature = "std", test))] use pem::PemError; use serde::Serialize; -#[cfg(any(feature = "std", test))] +#[cfg(any(feature = "std", feature = "testing", test))] use thiserror::Error; #[cfg(any(feature = "std-fs-io", test))] @@ -74,7 +74,7 @@ impl From for Error { } } -#[cfg(any(feature = "std", test))] +#[cfg(any(feature = "std", feature = "testing", test))] impl StdError for Error { fn source(&self) -> Option<&(dyn StdError + 'static)> { match self { @@ -86,7 +86,7 @@ impl StdError for Error { } /// Cryptographic errors extended with some additional variants. -#[cfg(any(feature = "std", test))] +#[cfg(any(feature = "std", feature = "testing", test))] #[derive(Debug, Error)] #[non_exhaustive] pub enum ErrorExt { diff --git a/types/src/system/auction/delegator_kind.rs b/types/src/system/auction/delegator_kind.rs index 47ef0915d0..d82caf5d09 100644 --- a/types/src/system/auction/delegator_kind.rs +++ b/types/src/system/auction/delegator_kind.rs @@ -10,6 +10,11 @@ use core::{ }; #[cfg(feature = "datasize")] use datasize::DataSize; +#[cfg(any(feature = "testing", test))] +use rand::{ + distributions::{Distribution, Standard}, + Rng, +}; #[cfg(feature = "json-schema")] use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -139,6 +144,17 @@ impl CLTyped for DelegatorKind { } } +#[cfg(any(feature = "testing", test))] +impl Distribution for Standard { + fn sample(&self, rng: &mut R) -> DelegatorKind { + if rng.gen() { + DelegatorKind::PublicKey(rng.gen()) + } else { + DelegatorKind::Purse(rng.gen()) + } + } +} + #[cfg(test)] mod tests { use crate::{bytesrepr, system::auction::delegator_kind::DelegatorKind, PublicKey, SecretKey}; diff --git a/types/src/system/auction/reservation.rs b/types/src/system/auction/reservation.rs index 19e1fac77b..01850aa139 100644 --- a/types/src/system/auction/reservation.rs +++ b/types/src/system/auction/reservation.rs @@ -1,5 +1,10 @@ use alloc::vec::Vec; use core::fmt::{self, Display, Formatter}; +#[cfg(any(feature = "testing", test))] +use rand::{ + distributions::{Distribution, Standard}, + Rng, +}; #[cfg(feature = "datasize")] use datasize::DataSize; @@ -113,6 +118,17 @@ impl Display for Reservation { } } +#[cfg(any(feature = "testing", test))] +impl Distribution for Standard { + fn sample(&self, rng: &mut R) -> Reservation { + Reservation { + delegator_kind: rng.gen(), + validator_public_key: rng.gen(), + delegation_rate: rng.gen(), + } + } +} + #[cfg(test)] mod tests { use crate::{bytesrepr, system::auction::Reservation, PublicKey, SecretKey}; diff --git a/types/src/transaction/transaction_entry_point.rs b/types/src/transaction/transaction_entry_point.rs index 5a25a27491..6598c55696 100644 --- a/types/src/transaction/transaction_entry_point.rs +++ b/types/src/transaction/transaction_entry_point.rs @@ -34,8 +34,10 @@ use serde::{Deserialize, Serialize}; pub enum TransactionEntryPoint { /// The default entry point name. Call, + /// A non-native, arbitrary entry point. Custom(String), + /// The `transfer` native entry point, used to transfer `Motes` from a source purse to a target /// purse. /// @@ -55,14 +57,16 @@ pub enum TransactionEntryPoint { ) )] Transfer, + /// The `add_bid` native entry point, used to create or top off a bid purse. /// /// Requires the following runtime args: /// * "public_key": `PublicKey` /// * "delegation_rate": `u8` /// * "amount": `U512` - /// * "minimum_delegation_amount": `u64` - /// * "maximum_delegation_amount": `u64` + /// * "minimum_delegation_amount": `Option` + /// * "maximum_delegation_amount": `Option` + /// * "reserved_slots": `Option` #[cfg_attr( feature = "json-schema", schemars( @@ -70,6 +74,7 @@ pub enum TransactionEntryPoint { ) )] AddBid, + /// The `withdraw_bid` native entry point, used to decrease a stake. /// /// Requires the following runtime args: @@ -157,12 +162,12 @@ pub enum TransactionEntryPoint { ) )] ChangeBidPublicKey, + /// The `add_reservations` native entry point, used to add delegators to validator's reserve /// list. /// /// Requires the following runtime args: - /// * "validator": `PublicKey` - /// * "delegators": `&[PublicKey]` + /// * "reservations": `Vec` #[cfg_attr( feature = "json-schema", schemars( @@ -171,12 +176,13 @@ pub enum TransactionEntryPoint { ) )] AddReservations, + /// The `cancel_reservations` native entry point, used to remove delegators from validator's /// reserve list. /// /// Requires the following runtime args: /// * "validator": `PublicKey` - /// * "delegators": `&[PublicKey]` + /// * "delegators": `Vec` #[cfg_attr( feature = "json-schema", schemars( diff --git a/types/src/transaction/transaction_v1/arg_handling.rs b/types/src/transaction/transaction_v1/arg_handling.rs index 2d9971307e..e393e9ab54 100644 --- a/types/src/transaction/transaction_v1/arg_handling.rs +++ b/types/src/transaction/transaction_v1/arg_handling.rs @@ -25,10 +25,11 @@ const TRANSFER_ARG_ID: OptionalArg = OptionalArg::new("id"); const ADD_BID_ARG_PUBLIC_KEY: RequiredArg = RequiredArg::new("public_key"); const ADD_BID_ARG_DELEGATION_RATE: RequiredArg = RequiredArg::new("delegation_rate"); const ADD_BID_ARG_AMOUNT: RequiredArg = RequiredArg::new("amount"); -const ADD_BID_ARG_MINIMUM_DELEGATION_AMOUNT: RequiredArg = - RequiredArg::new("minimum_delegation_amount"); -const ADD_BID_ARG_MAXIMUM_DELEGATION_AMOUNT: RequiredArg = - RequiredArg::new("maximum_delegation_amount"); +const ADD_BID_ARG_MINIMUM_DELEGATION_AMOUNT: OptionalArg = + OptionalArg::new("minimum_delegation_amount"); +const ADD_BID_ARG_MAXIMUM_DELEGATION_AMOUNT: OptionalArg = + OptionalArg::new("maximum_delegation_amount"); +const ADD_BID_ARG_RESERVED_SLOTS: OptionalArg = OptionalArg::new("reserved_slots"); const WITHDRAW_BID_ARG_PUBLIC_KEY: RequiredArg = RequiredArg::new("public_key"); const WITHDRAW_BID_ARG_AMOUNT: RequiredArg = RequiredArg::new("amount"); @@ -49,9 +50,7 @@ const REDELEGATE_ARG_NEW_VALIDATOR: RequiredArg = RequiredArg::new("n #[cfg(any(all(feature = "std", feature = "testing"), test))] const ACTIVATE_BID_ARG_VALIDATOR: RequiredArg = RequiredArg::new(ARG_VALIDATOR); -#[cfg(any(all(feature = "std", feature = "testing"), test))] const CHANGE_BID_PUBLIC_KEY_ARG_PUBLIC_KEY: RequiredArg = RequiredArg::new("public_key"); -#[cfg(any(all(feature = "std", feature = "testing"), test))] const CHANGE_BID_PUBLIC_KEY_ARG_NEW_PUBLIC_KEY: RequiredArg = RequiredArg::new("new_public_key"); @@ -247,15 +246,23 @@ pub fn new_add_bid_args>( public_key: PublicKey, delegation_rate: u8, amount: A, - minimum_delegation_amount: u64, - maximum_delegation_amount: u64, + maybe_minimum_delegation_amount: Option, + maybe_maximum_delegation_amount: Option, + maybe_reserved_slots: Option, ) -> Result { let mut args = RuntimeArgs::new(); ADD_BID_ARG_PUBLIC_KEY.insert(&mut args, public_key)?; ADD_BID_ARG_DELEGATION_RATE.insert(&mut args, delegation_rate)?; ADD_BID_ARG_AMOUNT.insert(&mut args, amount.into())?; - ADD_BID_ARG_MINIMUM_DELEGATION_AMOUNT.insert(&mut args, minimum_delegation_amount)?; - ADD_BID_ARG_MAXIMUM_DELEGATION_AMOUNT.insert(&mut args, maximum_delegation_amount)?; + if let Some(minimum_delegation_amount) = maybe_minimum_delegation_amount { + ADD_BID_ARG_MINIMUM_DELEGATION_AMOUNT.insert(&mut args, minimum_delegation_amount)?; + }; + if let Some(maximum_delegation_amount) = maybe_maximum_delegation_amount { + ADD_BID_ARG_MAXIMUM_DELEGATION_AMOUNT.insert(&mut args, maximum_delegation_amount)?; + }; + if let Some(reserved_slots) = maybe_reserved_slots { + ADD_BID_ARG_RESERVED_SLOTS.insert(&mut args, reserved_slots)?; + }; Ok(args) } @@ -268,6 +275,9 @@ pub fn has_valid_add_bid_args(args: &TransactionArgs) -> Result<(), InvalidTrans let _public_key = ADD_BID_ARG_PUBLIC_KEY.get(args)?; let _delegation_rate = ADD_BID_ARG_DELEGATION_RATE.get(args)?; let _amount = ADD_BID_ARG_AMOUNT.get(args)?; + let _minimum_delegation_amount = ADD_BID_ARG_MINIMUM_DELEGATION_AMOUNT.get(args)?; + let _maximum_delegation_amount = ADD_BID_ARG_MAXIMUM_DELEGATION_AMOUNT.get(args)?; + let _reserved_slots = ADD_BID_ARG_RESERVED_SLOTS.get(args)?; Ok(()) } @@ -381,6 +391,17 @@ pub fn has_valid_activate_bid_args(args: &TransactionArgs) -> Result<(), Invalid Ok(()) } +/// Creates a `RuntimeArgs` suitable for use in a change bid public key transaction. +pub fn new_change_bid_public_key_args( + public_key: PublicKey, + new_public_key: PublicKey, +) -> Result { + let mut args = RuntimeArgs::new(); + CHANGE_BID_PUBLIC_KEY_ARG_PUBLIC_KEY.insert(&mut args, public_key)?; + CHANGE_BID_PUBLIC_KEY_ARG_NEW_PUBLIC_KEY.insert(&mut args, new_public_key)?; + Ok(args) +} + /// Checks the given `RuntimeArgs` are suitable for use in a change bid public key transaction. #[cfg(any(all(feature = "std", feature = "testing"), test))] pub fn has_valid_change_bid_public_key_args( @@ -394,6 +415,16 @@ pub fn has_valid_change_bid_public_key_args( Ok(()) } +/// Creates a `RuntimeArgs` suitable for use in a add resrvations transaction. +#[cfg(any(all(feature = "std", feature = "testing"), test))] +pub fn new_add_reservations_args( + reservations: Vec, +) -> Result { + let mut args = RuntimeArgs::new(); + ADD_RESERVATIONS_ARG_RESERVATIONS.insert(&mut args, reservations)?; + Ok(args) +} + /// Checks the given `TransactionArgs` are suitable for use in a add reservations transaction. #[cfg(any(all(feature = "std", feature = "testing"), test))] pub fn has_valid_add_reservations_args(args: &TransactionArgs) -> Result<(), InvalidTransactionV1> { @@ -404,6 +435,18 @@ pub fn has_valid_add_reservations_args(args: &TransactionArgs) -> Result<(), Inv Ok(()) } +/// Creates a `RuntimeArgs` suitable for use in a cancel reservations transaction. +#[cfg(any(all(feature = "std", feature = "testing"), test))] +pub fn new_cancel_reservations_args( + validator: PublicKey, + delegators: Vec, +) -> Result { + let mut args = RuntimeArgs::new(); + CANCEL_RESERVATIONS_ARG_VALIDATOR.insert(&mut args, validator)?; + CANCEL_RESERVATIONS_ARG_DELEGATORS.insert(&mut args, delegators)?; + Ok(args) +} + /// Checks the given `TransactionArgs` are suitable for use in a add reservations transaction. #[cfg(any(all(feature = "std", feature = "testing"), test))] pub fn has_valid_cancel_reservations_args( @@ -419,6 +462,8 @@ pub fn has_valid_cancel_reservations_args( #[cfg(test)] mod tests { + use core::ops::Range; + use rand::Rng; use super::*; @@ -572,8 +617,10 @@ mod tests { fn should_validate_add_bid_args() { let rng = &mut TestRng::new(); - let minimum_delegation_amount = rng.gen::() as u64; - let maximum_delegation_amount = minimum_delegation_amount + rng.gen::() as u64; + let minimum_delegation_amount = rng.gen::().then(|| rng.gen()); + let maximum_delegation_amount = minimum_delegation_amount + .map(|minimum_delegation_amount| minimum_delegation_amount + rng.gen::() as u64); + let reserved_slots = rng.gen::().then(|| rng.gen::()); // Check random args. let mut args = new_add_bid_args( @@ -582,6 +629,7 @@ mod tests { rng.gen::(), minimum_delegation_amount, maximum_delegation_amount, + reserved_slots, ) .unwrap(); has_valid_add_bid_args(&TransactionArgs::Named(args.clone())).unwrap(); @@ -986,6 +1034,211 @@ mod tests { ); } + #[test] + fn should_validate_change_bid_public_key_args() { + let rng = &mut TestRng::new(); + + // Check random args. + let mut args = + new_change_bid_public_key_args(PublicKey::random(rng), PublicKey::random(rng)).unwrap(); + has_valid_change_bid_public_key_args(&TransactionArgs::Named(args.clone())).unwrap(); + + // Check with extra arg. + args.insert("a", 1).unwrap(); + has_valid_change_bid_public_key_args(&TransactionArgs::Named(args)).unwrap(); + } + + #[test] + fn change_bid_public_key_args_with_missing_required_should_be_invalid() { + let rng = &mut TestRng::new(); + + // Missing "public_key". + let args = runtime_args! { + CHANGE_BID_PUBLIC_KEY_ARG_NEW_PUBLIC_KEY.name => PublicKey::random(rng), + }; + let expected_error = InvalidTransactionV1::MissingArg { + arg_name: CHANGE_BID_PUBLIC_KEY_ARG_PUBLIC_KEY.name.to_string(), + }; + assert_eq!( + has_valid_change_bid_public_key_args(&TransactionArgs::Named(args)), + Err(expected_error) + ); + + // Missing "new_public_key". + let args = runtime_args! { + CHANGE_BID_PUBLIC_KEY_ARG_PUBLIC_KEY.name => PublicKey::random(rng), + }; + let expected_error = InvalidTransactionV1::MissingArg { + arg_name: CHANGE_BID_PUBLIC_KEY_ARG_NEW_PUBLIC_KEY.name.to_string(), + }; + assert_eq!( + has_valid_change_bid_public_key_args(&TransactionArgs::Named(args)), + Err(expected_error) + ); + } + + #[test] + fn change_bid_public_key_args_with_wrong_type_should_be_invalid() { + let rng = &mut TestRng::new(); + + // Wrong "public_key" type. + let args = runtime_args! { + CHANGE_BID_PUBLIC_KEY_ARG_PUBLIC_KEY.name => rng.gen::(), + CHANGE_BID_PUBLIC_KEY_ARG_NEW_PUBLIC_KEY.name => PublicKey::random(rng), + }; + let expected_error = InvalidTransactionV1::UnexpectedArgType { + arg_name: CHANGE_BID_PUBLIC_KEY_ARG_PUBLIC_KEY.name.to_string(), + expected: vec![CLType::PublicKey], + got: CLType::U8, + }; + assert_eq!( + has_valid_change_bid_public_key_args(&TransactionArgs::Named(args)), + Err(expected_error) + ); + + // Wrong "new_public_key" type. + let args = runtime_args! { + CHANGE_BID_PUBLIC_KEY_ARG_PUBLIC_KEY.name => PublicKey::random(rng), + CHANGE_BID_PUBLIC_KEY_ARG_NEW_PUBLIC_KEY.name => rng.gen::(), + }; + let expected_error = InvalidTransactionV1::UnexpectedArgType { + arg_name: CHANGE_BID_PUBLIC_KEY_ARG_NEW_PUBLIC_KEY.name.to_string(), + expected: vec![CLType::PublicKey], + got: CLType::U8, + }; + assert_eq!( + has_valid_change_bid_public_key_args(&TransactionArgs::Named(args)), + Err(expected_error) + ); + } + + #[test] + fn should_validate_add_reservations_args() { + let rng = &mut TestRng::new(); + + let reservations = rng.random_vec(1..100); + + // Check random args. + let mut args = new_add_reservations_args(reservations).unwrap(); + has_valid_add_reservations_args(&TransactionArgs::Named(args.clone())).unwrap(); + + // Check with extra arg. + args.insert("a", 1).unwrap(); + has_valid_add_reservations_args(&TransactionArgs::Named(args)).unwrap(); + } + + #[test] + fn add_reservations_args_with_missing_required_should_be_invalid() { + // Missing "reservations". + let args = runtime_args! {}; + let expected_error = InvalidTransactionV1::MissingArg { + arg_name: ADD_RESERVATIONS_ARG_RESERVATIONS.name.to_string(), + }; + assert_eq!( + has_valid_add_reservations_args(&TransactionArgs::Named(args)), + Err(expected_error) + ); + } + + #[test] + fn add_reservations_args_with_wrong_type_should_be_invalid() { + let rng = &mut TestRng::new(); + + // Wrong "reservations" type. + let args = runtime_args! { + ADD_RESERVATIONS_ARG_RESERVATIONS.name => PublicKey::random(rng), + }; + let expected_error = InvalidTransactionV1::UnexpectedArgType { + arg_name: ADD_RESERVATIONS_ARG_RESERVATIONS.name.to_string(), + expected: vec![CLType::List(Box::new(CLType::Any))], + got: CLType::PublicKey, + }; + assert_eq!( + has_valid_add_reservations_args(&TransactionArgs::Named(args)), + Err(expected_error) + ); + } + + #[test] + fn should_validate_cancel_reservations_args() { + let rng = &mut TestRng::new(); + + let validator = PublicKey::random(rng); + let delegators = rng.random_vec(0..100); + + // Check random args. + let mut args = new_cancel_reservations_args(validator, delegators).unwrap(); + has_valid_cancel_reservations_args(&TransactionArgs::Named(args.clone())).unwrap(); + + // Check with extra arg. + args.insert("a", 1).unwrap(); + has_valid_cancel_reservations_args(&TransactionArgs::Named(args)).unwrap(); + } + + #[test] + fn cancel_reservations_args_with_missing_required_should_be_invalid() { + let rng = &mut TestRng::new(); + + // Missing "validator". + let args = runtime_args! { + CANCEL_RESERVATIONS_ARG_DELEGATORS.name => rng.random_vec::, PublicKey>(0..100), + }; + let expected_error = InvalidTransactionV1::MissingArg { + arg_name: CANCEL_RESERVATIONS_ARG_VALIDATOR.name.to_string(), + }; + assert_eq!( + has_valid_cancel_reservations_args(&TransactionArgs::Named(args)), + Err(expected_error) + ); + + // Missing "delegators". + let args = runtime_args! { + CANCEL_RESERVATIONS_ARG_VALIDATOR.name => PublicKey::random(rng), + }; + let expected_error = InvalidTransactionV1::MissingArg { + arg_name: CANCEL_RESERVATIONS_ARG_DELEGATORS.name.to_string(), + }; + assert_eq!( + has_valid_cancel_reservations_args(&TransactionArgs::Named(args)), + Err(expected_error) + ); + } + + #[test] + fn cancel_reservations_args_with_wrong_type_should_be_invalid() { + let rng = &mut TestRng::new(); + + // Wrong "validator" type. + let args = runtime_args! { + CANCEL_RESERVATIONS_ARG_VALIDATOR.name => rng.random_vec::, PublicKey>(0..100), + CANCEL_RESERVATIONS_ARG_DELEGATORS.name => rng.random_vec::, PublicKey>(0..100), + }; + let expected_error = InvalidTransactionV1::UnexpectedArgType { + arg_name: CANCEL_RESERVATIONS_ARG_VALIDATOR.name.to_string(), + expected: vec![CLType::PublicKey], + got: CLType::List(Box::new(CLType::PublicKey)), + }; + assert_eq!( + has_valid_cancel_reservations_args(&TransactionArgs::Named(args)), + Err(expected_error) + ); + + // Wrong "delegators" type. + let args = runtime_args! { + CANCEL_RESERVATIONS_ARG_VALIDATOR.name => PublicKey::random(rng), + CANCEL_RESERVATIONS_ARG_DELEGATORS.name => rng.gen::(), + }; + let expected_error = InvalidTransactionV1::UnexpectedArgType { + arg_name: CANCEL_RESERVATIONS_ARG_DELEGATORS.name.to_string(), + expected: vec![CLType::List(Box::new(CLType::PublicKey))], + got: CLType::U8, + }; + assert_eq!( + has_valid_cancel_reservations_args(&TransactionArgs::Named(args)), + Err(expected_error) + ); + } + #[test] fn native_calls_require_named_args() { let args = TransactionArgs::Bytesrepr(vec![b'a'; 100].into()); diff --git a/types/src/transaction/transaction_v1/fields_container.rs b/types/src/transaction/transaction_v1/fields_container.rs index 78a4a6a54a..9a714d436a 100644 --- a/types/src/transaction/transaction_v1/fields_container.rs +++ b/types/src/transaction/transaction_v1/fields_container.rs @@ -114,14 +114,19 @@ impl FieldsContainer { let public_key = PublicKey::random(rng); let delegation_rate = rng.gen(); let amount = rng.gen::(); - let minimum_delegation_amount = rng.gen::() as u64; - let maximum_delegation_amount = minimum_delegation_amount + rng.gen::() as u64; + let minimum_delegation_amount = rng.gen::().then(|| rng.gen()); + let maximum_delegation_amount = + minimum_delegation_amount.map(|minimum_delegation_amount| { + minimum_delegation_amount + rng.gen::() as u64 + }); + let reserved_slots = rng.gen::().then(|| rng.gen::()); let args = arg_handling::new_add_bid_args( public_key, delegation_rate, amount, minimum_delegation_amount, maximum_delegation_amount, + reserved_slots, ) .unwrap(); FieldsContainer::new( @@ -252,14 +257,17 @@ impl FieldsContainer { let public_key = PublicKey::random(rng); let delegation_rate = rng.gen(); let amount = rng.gen::(); - let minimum_delegation_amount = rng.gen::() as u64; - let maximum_delegation_amount = minimum_delegation_amount + rng.gen::() as u64; + let minimum_delegation_amount = rng.gen::().then(|| rng.gen()); + let maximum_delegation_amount = minimum_delegation_amount + .map(|minimum_delegation_amount| minimum_delegation_amount + rng.gen::() as u64); + let reserved_slots = rng.gen::().then(|| rng.gen::()); let args = arg_handling::new_add_bid_args( public_key, delegation_rate, amount, minimum_delegation_amount, maximum_delegation_amount, + reserved_slots, ) .unwrap(); FieldsContainer::new( diff --git a/types/src/transaction/transaction_v1/transaction_v1_builder.rs b/types/src/transaction/transaction_v1/transaction_v1_builder.rs index 4641b2dfb2..aa05aedf46 100644 --- a/types/src/transaction/transaction_v1/transaction_v1_builder.rs +++ b/types/src/transaction/transaction_v1/transaction_v1_builder.rs @@ -8,6 +8,8 @@ use super::{ fields_container::FieldsContainerError, InitiatorAddrAndSecretKey, PricingMode, TransactionArgs, TransactionV1, }; +#[cfg(any(all(feature = "std", feature = "testing"), test))] +use crate::system::auction::Reservation; use crate::{ bytesrepr::Bytes, transaction::FieldsContainer, AddressableEntityHash, CLValue, CLValueError, EntityVersion, PackageHash, PublicKey, RuntimeArgs, SecretKey, TimeDiff, Timestamp, @@ -198,8 +200,9 @@ impl<'a> TransactionV1Builder<'a> { public_key: PublicKey, delegation_rate: u8, amount: A, - minimum_delegation_amount: u64, - maximum_delegation_amount: u64, + minimum_delegation_amount: Option, + maximum_delegation_amount: Option, + reserved_slots: Option, ) -> Result { let args = arg_handling::new_add_bid_args( public_key, @@ -207,6 +210,7 @@ impl<'a> TransactionV1Builder<'a> { amount, minimum_delegation_amount, maximum_delegation_amount, + reserved_slots, )?; let mut builder = TransactionV1Builder::new(); builder.args = TransactionArgs::Named(args); @@ -277,6 +281,50 @@ impl<'a> TransactionV1Builder<'a> { Ok(builder) } + /// Returns a new `TransactionV1Builder` suitable for building a native change_bid_public_key + /// transaction. + pub fn new_change_bid_public_key>( + public_key: PublicKey, + new_public_key: PublicKey, + ) -> Result { + let args = arg_handling::new_change_bid_public_key_args(public_key, new_public_key)?; + let mut builder = TransactionV1Builder::new(); + builder.args = TransactionArgs::Named(args); + builder.target = TransactionTarget::Native; + builder.entry_point = TransactionEntryPoint::ChangeBidPublicKey; + builder.scheduling = Self::DEFAULT_SCHEDULING; + Ok(builder) + } + + /// Returns a new `TransactionV1Builder` suitable for building a native add_reservations + /// transaction. + #[cfg(any(all(feature = "std", feature = "testing"), test))] + pub fn new_add_reservations(reservations: Vec) -> Result { + let args = arg_handling::new_add_reservations_args(reservations)?; + let mut builder = TransactionV1Builder::new(); + builder.args = TransactionArgs::Named(args); + builder.target = TransactionTarget::Native; + builder.entry_point = TransactionEntryPoint::AddReservations; + builder.scheduling = Self::DEFAULT_SCHEDULING; + Ok(builder) + } + + /// Returns a new `TransactionV1Builder` suitable for building a native cancel_reservations + /// transaction. + #[cfg(any(all(feature = "std", feature = "testing"), test))] + pub fn new_cancel_reservations( + validator: PublicKey, + delegators: Vec, + ) -> Result { + let args = arg_handling::new_cancel_reservations_args(validator, delegators)?; + let mut builder = TransactionV1Builder::new(); + builder.args = TransactionArgs::Named(args); + builder.target = TransactionTarget::Native; + builder.entry_point = TransactionEntryPoint::CancelReservations; + builder.scheduling = Self::DEFAULT_SCHEDULING; + Ok(builder) + } + fn new_targeting_stored>( id: TransactionInvocationTarget, entry_point: E,