Skip to content

Commit

Permalink
feat: minimum chunk size setting (#5314)
Browse files Browse the repository at this point in the history
  • Loading branch information
dandanlen committed Oct 11, 2024
1 parent 0db92df commit 8ad9eda
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 61 deletions.
27 changes: 23 additions & 4 deletions state-chain/pallets/cf-swapping/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,7 @@ pub enum CcmFailReason {
#[scale_info(skip_type_params(T, I))]
pub enum PalletConfigUpdate<T: Config> {
/// Set the maximum amount allowed to be put into a swap. Excess amounts are confiscated.
MaximumSwapAmount { asset: Asset, amount: Option<AssetAmount> },
SetMinimumChunkSize { asset: Asset, amount: Option<AssetAmount> },
/// Set the delay in blocks before retrying a previously failed swap.
SwapRetryDelay { delay: BlockNumberFor<T> },
/// Set the interval at which we buy FLIP in order to burn it.
Expand Down Expand Up @@ -543,6 +543,14 @@ pub mod pallet {
ConstU32<DEFAULT_MAX_SWAP_REQUEST_DURATION_BLOCKS>,
>;

/// The minimum chunk size for DCA swaps. The number of chunks of a DCA swap will be reduced
/// so that the chunk size is greater than or equal to this value. Setting to zero will disable
/// the check for that asset.
#[pallet::storage]
#[pallet::getter(fn minimum_chunk_size)]
pub type MinimumChunkSize<T: Config> =
StorageMap<_, Twox64Concat, Asset, AssetAmount, ValueQuery>;

#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
Expand Down Expand Up @@ -999,9 +1007,8 @@ pub mod pallet {

for update in updates {
match update {
PalletConfigUpdate::MaximumSwapAmount { asset, amount } => {
MaximumSwapAmount::<T>::set(asset, amount);
Self::deposit_event(Event::<T>::MaximumSwapAmountSet { asset, amount });
PalletConfigUpdate::SetMinimumChunkSize { asset, amount } => {
MinimumChunkSize::<T>::set(asset, amount.unwrap_or_default());
},
PalletConfigUpdate::SwapRetryDelay { delay } => {
ensure!(
Expand Down Expand Up @@ -2111,6 +2118,18 @@ pub mod pallet {
swap_amount
};

// Restrict the number of chunks based on the minimum chunk size.
let dca_params = dca_params.map(|mut dca_params| {
let minimum_chunk_size = MinimumChunkSize::<T>::get(input_asset);
if minimum_chunk_size > 0 {
dca_params.number_of_chunks = core::cmp::min(
max((input_amount / minimum_chunk_size) as u32, 1),
dca_params.number_of_chunks,
);
}
dca_params
});

Self::deposit_event(Event::<T>::SwapRequested {
swap_request_id: request_id,
input_asset,
Expand Down
7 changes: 1 addition & 6 deletions state-chain/pallets/cf-swapping/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,7 @@ static EVM_OUTPUT_ADDRESS: LazyLock<ForeignChainAddress> =
LazyLock::new(|| ForeignChainAddress::Eth([1; 20].into()));

fn set_maximum_swap_amount(asset: Asset, amount: Option<AssetAmount>) {
assert_ok!(Swapping::update_pallet_config(
OriginTrait::root(),
vec![PalletConfigUpdate::MaximumSwapAmount { asset, amount }]
.try_into()
.unwrap()
));
MaximumSwapAmount::<Test>::set(asset, amount);
}

struct TestSwapParams {
Expand Down
59 changes: 8 additions & 51 deletions state-chain/pallets/cf-swapping/src/tests/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,61 +3,46 @@ use super::*;
#[test]
fn can_update_all_config_items() {
new_test_ext().execute_with(|| {
const NEW_MAX_SWAP_AMOUNT_BTC: Option<AssetAmount> = Some(100);
const NEW_MAX_SWAP_AMOUNT_DOT: Option<AssetAmount> = Some(69);
let new_swap_retry_delay = BlockNumberFor::<Test>::from(1234u32);
let new_flip_buy_interval = BlockNumberFor::<Test>::from(5678u32);
const NEW_MAX_SWAP_RETRY_DURATION: u32 = 69_u32;
const MAX_SWAP_REQUEST_DURATION: u32 = 420_u32;
const NEW_MINIMUM_CHUNK_SIZE: AssetAmount = 1;

// Check that the default values are different from the new ones
assert!(MaximumSwapAmount::<Test>::get(Asset::Btc).is_none());
assert!(MaximumSwapAmount::<Test>::get(Asset::Dot).is_none());
assert_ne!(SwapRetryDelay::<Test>::get(), new_swap_retry_delay);
assert_ne!(FlipBuyInterval::<Test>::get(), new_flip_buy_interval);
assert_ne!(MaxSwapRetryDurationBlocks::<Test>::get(), NEW_MAX_SWAP_RETRY_DURATION);
assert_ne!(MaxSwapRequestDurationBlocks::<Test>::get(), MAX_SWAP_REQUEST_DURATION);
assert_ne!(MinimumChunkSize::<Test>::get(Asset::Eth), NEW_MINIMUM_CHUNK_SIZE);

// Update all config items at the same time, and updates 2 separate max swap amounts.
assert_ok!(Swapping::update_pallet_config(
OriginTrait::root(),
vec![
PalletConfigUpdate::MaximumSwapAmount {
asset: Asset::Btc,
amount: NEW_MAX_SWAP_AMOUNT_BTC
},
PalletConfigUpdate::MaximumSwapAmount {
asset: Asset::Dot,
amount: NEW_MAX_SWAP_AMOUNT_DOT
},
PalletConfigUpdate::SwapRetryDelay { delay: new_swap_retry_delay },
PalletConfigUpdate::FlipBuyInterval { interval: new_flip_buy_interval },
PalletConfigUpdate::SetMaxSwapRetryDuration { blocks: NEW_MAX_SWAP_RETRY_DURATION },
PalletConfigUpdate::SetMaxSwapRequestDuration { blocks: MAX_SWAP_REQUEST_DURATION },
PalletConfigUpdate::SetMinimumChunkSize {
asset: Asset::Eth,
amount: Some(NEW_MINIMUM_CHUNK_SIZE)
},
]
.try_into()
.unwrap()
));

// Check that the new values were set
assert_eq!(MaximumSwapAmount::<Test>::get(Asset::Btc), NEW_MAX_SWAP_AMOUNT_BTC);
assert_eq!(MaximumSwapAmount::<Test>::get(Asset::Dot), NEW_MAX_SWAP_AMOUNT_DOT);
assert_eq!(SwapRetryDelay::<Test>::get(), new_swap_retry_delay);
assert_eq!(FlipBuyInterval::<Test>::get(), new_flip_buy_interval);
assert_eq!(MaxSwapRetryDurationBlocks::<Test>::get(), NEW_MAX_SWAP_RETRY_DURATION);
assert_eq!(MaxSwapRequestDurationBlocks::<Test>::get(), MAX_SWAP_REQUEST_DURATION);
assert_eq!(MinimumChunkSize::<Test>::get(Asset::Eth), NEW_MINIMUM_CHUNK_SIZE);

// Check that the events were emitted
assert_events_eq!(
Test,
RuntimeEvent::Swapping(crate::Event::MaximumSwapAmountSet {
asset: Asset::Btc,
amount: NEW_MAX_SWAP_AMOUNT_BTC,
}),
RuntimeEvent::Swapping(crate::Event::MaximumSwapAmountSet {
asset: Asset::Dot,
amount: NEW_MAX_SWAP_AMOUNT_DOT,
}),
RuntimeEvent::Swapping(crate::Event::SwapRetryDelaySet {
swap_retry_delay: new_swap_retry_delay
}),
Expand All @@ -69,7 +54,7 @@ fn can_update_all_config_items() {
}),
RuntimeEvent::Swapping(crate::Event::MaxSwapRequestDurationSet {
blocks: MAX_SWAP_REQUEST_DURATION
})
}),
);

// Make sure that only governance can update the config
Expand Down Expand Up @@ -132,34 +117,6 @@ fn max_swap_amount_can_be_removed() {
});
}

#[test]
fn can_set_maximum_swap_amount() {
new_test_ext().execute_with(|| {
let asset = Asset::Eth;
let amount = Some(1_000u128);
assert!(MaximumSwapAmount::<Test>::get(asset).is_none());

// Set the new maximum swap_amount
set_maximum_swap_amount(asset, amount);

assert_eq!(MaximumSwapAmount::<Test>::get(asset), amount);
assert_eq!(Swapping::maximum_swap_amount(asset), amount);

System::assert_last_event(RuntimeEvent::Swapping(Event::<Test>::MaximumSwapAmountSet {
asset,
amount,
}));

// Can remove maximum swap amount
set_maximum_swap_amount(asset, None);
assert!(MaximumSwapAmount::<Test>::get(asset).is_none());
System::assert_last_event(RuntimeEvent::Swapping(Event::<Test>::MaximumSwapAmountSet {
asset,
amount: None,
}));
});
}

#[test]
fn can_swap_below_max_amount() {
new_test_ext().execute_with(|| {
Expand Down
54 changes: 54 additions & 0 deletions state-chain/pallets/cf-swapping/src/tests/dca.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1001,3 +1001,57 @@ mod ccm_tests {
});
}
}

#[test]
fn test_minimum_chunk_size() {
#[track_caller]
fn set_and_test_chunk_size(
asset_amount: AssetAmount,
number_of_chunks: u32,
expected_number_of_chunks: u32,
minimum_chunk_size: AssetAmount,
) {
// Update the minimum chunk size
assert_ok!(Swapping::update_pallet_config(
OriginTrait::root(),
vec![PalletConfigUpdate::SetMinimumChunkSize {
asset: Asset::Eth,
amount: Some(minimum_chunk_size)
},]
.try_into()
.unwrap()
));

// Init a swap, this is where the minimum chunk size will kick in
let dca_params = DcaParameters { number_of_chunks, chunk_interval: CHUNK_INTERVAL };
let expected_swap_request_id = Swapping::init_swap_request(
Asset::Eth,
asset_amount,
Asset::Btc,
SwapRequestType::Regular { output_address: ForeignChainAddress::Eth([1; 20].into()) },
vec![].try_into().unwrap(),
None,
Some(dca_params),
SwapOrigin::Vault { tx_hash: Default::default() },
);

// Check that the swap was initiated with the updated number of chunks
let expected_dca_params = DcaParameters {
number_of_chunks: expected_number_of_chunks,
chunk_interval: CHUNK_INTERVAL,
};
assert_has_matching_event!(
Test,
RuntimeEvent::Swapping(Event::SwapRequested {swap_request_id, dca_parameters, .. })
if dca_parameters == &Some(expected_dca_params.clone()) && Ok(*swap_request_id) == expected_swap_request_id
);
}

new_test_ext().execute_with(|| {
set_and_test_chunk_size(100, 10, 10, 9);
set_and_test_chunk_size(100, 10, 10, 10);
set_and_test_chunk_size(100, 10, 9, 11);
set_and_test_chunk_size(1, 10, 1, 10);
set_and_test_chunk_size(1, 1000, 1000, 0);
});
}

0 comments on commit 8ad9eda

Please sign in to comment.