diff --git a/.gitignore b/.gitignore index eab4f8925..8a30b968d 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,7 @@ **/artifacts/ # NPM -**/node_modules/ \ No newline at end of file +**/node_modules/ + +# Visual Studio Code +.vscode \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 16047033b..f19f1ab59 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12849,7 +12849,11 @@ name = "zrml-swaps-fuzz" version = "0.0.0" dependencies = [ "arbitrary", + "frame-support", "libfuzzer-sys", + "orml-traits", + "rand 0.8.5", + "sp-runtime", "zeitgeist-primitives", "zrml-swaps", ] diff --git a/scripts/tests/fuzz.sh b/scripts/tests/fuzz.sh index 8884fcd98..2fb08c5e9 100755 --- a/scripts/tests/fuzz.sh +++ b/scripts/tests/fuzz.sh @@ -8,6 +8,17 @@ set -euxo pipefail # a hardware- and fuzz target specific run count. BASE=1000 RUNS="${RUNS:-50000}" + +CREATE_POOL_FACT=500 +POOL_JOIN_FACT=150 +POOL_JOIN_WITH_EXACT_POOL_AMOUNT_FACT=150 +POOL_JOIN_WITH_EXACT_ASSET_AMOUNT_FACT=150 +SWAP_EXACT_AMOUNT_IN_FACT=500 +SWAP_EXACT_AMOUNT_OUT_FACT=500 +POOL_EXIT_WITH_EXACT_ASSET_AMOUNT_FACT=150 +POOL_EXIT_WITH_EXACT_POOL_AMOUNT_FACT=150 +POOL_EXIT_FACT=150 + FEE_SIGMOID_FACT=50000 FIXEDI_TO_FIXEDU_FACT=100000 FIXEDU_TO_FIXEDI_FACT=100000 @@ -25,7 +36,15 @@ RIKIDDO_PALLET_FACT=1000 RUST_BACKTRACE=1 cargo fuzz run --fuzz-dir zrml/prediction-markets/fuzz pm_full_workflow -- -runs=$RUNS # --- Swaps Pallet fuzz tests --- -RUST_BACKTRACE=1 cargo fuzz run --fuzz-dir zrml/swaps/fuzz swaps_full_workflow -- -runs=$RUNS +RUST_BACKTRACE=1 cargo fuzz run --fuzz-dir zrml/swaps/fuzz create_pool -- -runs=$(($(($RUNS * $CREATE_POOL_FACT)) / $BASE)) +RUST_BACKTRACE=1 cargo fuzz run --fuzz-dir zrml/swaps/fuzz pool_join -- -runs=$(($(($RUNS * $POOL_JOIN_FACT)) / $BASE)) +RUST_BACKTRACE=1 cargo fuzz run --fuzz-dir zrml/swaps/fuzz pool_join_with_exact_pool_amount -- -runs=$(($(($RUNS * $POOL_JOIN_WITH_EXACT_POOL_AMOUNT_FACT)) / $BASE)) +RUST_BACKTRACE=1 cargo fuzz run --fuzz-dir zrml/swaps/fuzz pool_join_with_exact_asset_amount -- -runs=$(($(($RUNS * $POOL_JOIN_WITH_EXACT_ASSET_AMOUNT_FACT)) / $BASE)) +RUST_BACKTRACE=1 cargo fuzz run --fuzz-dir zrml/swaps/fuzz swap_exact_amount_in -- -runs=$(($(($RUNS * $SWAP_EXACT_AMOUNT_IN_FACT)) / $BASE)) +RUST_BACKTRACE=1 cargo fuzz run --fuzz-dir zrml/swaps/fuzz swap_exact_amount_out -- -runs=$(($(($RUNS * $SWAP_EXACT_AMOUNT_OUT_FACT)) / $BASE)) +RUST_BACKTRACE=1 cargo fuzz run --fuzz-dir zrml/swaps/fuzz pool_exit_with_exact_asset_amount -- -runs=$(($(($RUNS * $POOL_EXIT_WITH_EXACT_ASSET_AMOUNT_FACT)) / $BASE)) +RUST_BACKTRACE=1 cargo fuzz run --fuzz-dir zrml/swaps/fuzz pool_exit_with_exact_pool_amount -- -runs=$(($(($RUNS * $POOL_EXIT_WITH_EXACT_POOL_AMOUNT_FACT)) / $BASE)) +RUST_BACKTRACE=1 cargo fuzz run --fuzz-dir zrml/swaps/fuzz pool_exit -- -runs=$(($(($RUNS * $POOL_EXIT_FACT)) / $BASE)) # --- Orderbook-v1 Pallet fuzz tests --- RUST_BACKTRACE=1 cargo fuzz run --fuzz-dir zrml/orderbook-v1/fuzz orderbook_v1_full_workflow -- -runs=$RUNS diff --git a/zrml/swaps/fuzz/Cargo.toml b/zrml/swaps/fuzz/Cargo.toml index 2fd36bcf5..23cf2841a 100644 --- a/zrml/swaps/fuzz/Cargo.toml +++ b/zrml/swaps/fuzz/Cargo.toml @@ -1,7 +1,55 @@ [[bin]] doc = false -name = "swaps_full_workflow" -path = "swaps_full_workflow.rs" +name = "create_pool" +path = "create_pool.rs" +test = false + +[[bin]] +doc = false +name = "pool_join" +path = "pool_join.rs" +test = false + +[[bin]] +doc = false +name = "pool_join_with_exact_pool_amount" +path = "pool_join_with_exact_pool_amount.rs" +test = false + +[[bin]] +doc = false +name = "pool_join_with_exact_asset_amount" +path = "pool_join_with_exact_asset_amount.rs" +test = false + +[[bin]] +doc = false +name = "swap_exact_amount_in" +path = "swap_exact_amount_in.rs" +test = false + +[[bin]] +doc = false +name = "swap_exact_amount_out" +path = "swap_exact_amount_out.rs" +test = false + +[[bin]] +doc = false +name = "pool_exit_with_exact_asset_amount" +path = "pool_exit_with_exact_asset_amount.rs" +test = false + +[[bin]] +doc = false +name = "pool_exit_with_exact_pool_amount" +path = "pool_exit_with_exact_pool_amount.rs" +test = false + +[[bin]] +doc = false +name = "pool_exit" +path = "pool_exit.rs" test = false [dependencies] @@ -9,6 +57,10 @@ arbitrary = { features = ["derive"], version = "1.0" } libfuzzer-sys = "0.4" zrml-swaps = { features = ["mock"], path = ".." } zeitgeist-primitives = { path = "../../../primitives" } +sp-runtime = { branch = "moonbeam-polkadot-v0.9.19", default-features = false, git = "https://github.com/purestake/substrate" } +frame-support = { branch = "moonbeam-polkadot-v0.9.19", default-features = false, git = "https://github.com/purestake/substrate" } +orml-traits = { branch = "polkadot-0.9.19-latest", default-features = false, git = "https://github.com/zeitgeistpm/open-runtime-module-library" } +rand = "0.8.4" [package] authors = ["Automatically generated"] diff --git a/zrml/swaps/fuzz/create_pool.rs b/zrml/swaps/fuzz/create_pool.rs new file mode 100644 index 000000000..7e847bc37 --- /dev/null +++ b/zrml/swaps/fuzz/create_pool.rs @@ -0,0 +1,26 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; +use zeitgeist_primitives::{traits::Swaps as SwapsTrait, types::ScoringRule}; + +use zrml_swaps::mock::{ExtBuilder, Swaps}; + +mod utils; +use utils::{construct_asset, PoolCreationData}; + +fuzz_target!(|data: PoolCreationData| { + let mut ext = ExtBuilder::default().build(); + let _ = ext.execute_with(|| { + let _ = Swaps::create_pool( + data.origin, + data.assets.into_iter().map(construct_asset).collect(), + construct_asset(data.base_asset), + data.market_id, + ScoringRule::CPMM, + data.swap_fee, + data.amount, + data.weights, + ); + }); + let _ = ext.commit_all(); +}); diff --git a/zrml/swaps/fuzz/pool_exit.rs b/zrml/swaps/fuzz/pool_exit.rs new file mode 100644 index 000000000..3c6c483dc --- /dev/null +++ b/zrml/swaps/fuzz/pool_exit.rs @@ -0,0 +1,40 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; +use zrml_swaps::mock::{ExtBuilder, Origin, Swaps}; + +mod utils; +use orml_traits::MultiCurrency; +use utils::{construct_asset, GeneralPoolData}; +use zeitgeist_primitives::types::{Asset, SerdeWrapper}; +use zrml_swaps::mock::Shares; + +fuzz_target!(|data: GeneralPoolData| { + let mut ext = ExtBuilder::default().build(); + let _ = ext.execute_with(|| { + // ensure that the account origin has a sufficient balance + // use orml_traits::MultiCurrency; required for this + for a in &data.pool_creation.assets { + let _ = Shares::deposit( + construct_asset(*a), + &data.pool_creation.origin, + data.pool_creation.amount, + ); + } + let pool_creator = data.pool_creation.origin; + let pool_id = data.pool_creation.create_pool(); + // to exit a pool, origin also needs to have the pool tokens of the pool that they're exiting + let _ = Shares::deposit( + Asset::PoolShare(SerdeWrapper(pool_id)), + &pool_creator, + data.pool_amount, + ); + let _ = Swaps::pool_exit( + Origin::signed(data.origin), + pool_id, + data.pool_amount, + data.asset_bounds, + ); + }); + let _ = ext.commit_all(); +}); diff --git a/zrml/swaps/fuzz/pool_exit_with_exact_asset_amount.rs b/zrml/swaps/fuzz/pool_exit_with_exact_asset_amount.rs new file mode 100644 index 000000000..7167adecd --- /dev/null +++ b/zrml/swaps/fuzz/pool_exit_with_exact_asset_amount.rs @@ -0,0 +1,43 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; +use zrml_swaps::mock::{ExtBuilder, Origin, Swaps}; + +mod utils; +use orml_traits::MultiCurrency; +use utils::{construct_asset, ExactAssetAmountData}; +use zrml_swaps::mock::Shares; + +use zeitgeist_primitives::types::{Asset, SerdeWrapper}; + +fuzz_target!(|data: ExactAssetAmountData| { + let mut ext = ExtBuilder::default().build(); + let _ = ext.execute_with(|| { + // ensure that the account origin has a sufficient balance + // use orml_traits::MultiCurrency; required for this + for a in &data.pool_creation.assets { + let _ = Shares::deposit( + construct_asset(*a), + &data.pool_creation.origin, + data.pool_creation.amount, + ); + } + + let pool_creator = data.pool_creation.origin; + let pool_id = data.pool_creation.create_pool(); + // to exit a pool, origin also needs to have the pool tokens of the pool that they're exiting + let _ = Shares::deposit( + Asset::PoolShare(SerdeWrapper(pool_id)), + &pool_creator, + data.pool_amount, + ); + let _ = Swaps::pool_exit_with_exact_asset_amount( + Origin::signed(data.origin), + pool_id, + construct_asset(data.asset), + data.asset_amount, + data.pool_amount, + ); + }); + let _ = ext.commit_all(); +}); diff --git a/zrml/swaps/fuzz/pool_exit_with_exact_pool_amount.rs b/zrml/swaps/fuzz/pool_exit_with_exact_pool_amount.rs new file mode 100644 index 000000000..68466fc4b --- /dev/null +++ b/zrml/swaps/fuzz/pool_exit_with_exact_pool_amount.rs @@ -0,0 +1,42 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; +use zrml_swaps::mock::{ExtBuilder, Origin, Swaps}; + +mod utils; +use orml_traits::MultiCurrency; +use utils::{construct_asset, ExactAmountData}; +use zeitgeist_primitives::types::{Asset, SerdeWrapper}; +use zrml_swaps::mock::Shares; + +fuzz_target!(|data: ExactAmountData| { + let mut ext = ExtBuilder::default().build(); + let _ = ext.execute_with(|| { + // ensure that the account origin has a sufficient balance + // use orml_traits::MultiCurrency; required for this + for a in &data.pool_creation.assets { + let _ = Shares::deposit( + construct_asset(*a), + &data.pool_creation.origin, + data.pool_creation.amount, + ); + } + + let pool_creator = data.pool_creation.origin; + let pool_id = data.pool_creation.create_pool(); + // to exit a pool, origin also needs to have the pool tokens of the pool that they're exiting + let _ = Shares::deposit( + Asset::PoolShare(SerdeWrapper(pool_id)), + &pool_creator, + data.pool_amount, + ); + let _ = Swaps::pool_exit_with_exact_pool_amount( + Origin::signed(data.origin), + pool_id, + construct_asset(data.asset), + data.pool_amount, + data.asset_amount, + ); + }); + let _ = ext.commit_all(); +}); diff --git a/zrml/swaps/fuzz/pool_join.rs b/zrml/swaps/fuzz/pool_join.rs new file mode 100644 index 000000000..71f9ac49a --- /dev/null +++ b/zrml/swaps/fuzz/pool_join.rs @@ -0,0 +1,36 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; + +use utils::GeneralPoolData; +use zrml_swaps::mock::{ExtBuilder, Origin, Swaps}; +mod utils; +use utils::construct_asset; +use zrml_swaps::mock::Shares; + +use orml_traits::MultiCurrency; + +fuzz_target!(|data: GeneralPoolData| { + let mut ext = ExtBuilder::default().build(); + let _ = ext.execute_with(|| { + // ensure that the account origin has a sufficient balance + // use orml_traits::MultiCurrency; required for this + for a in &data.pool_creation.assets { + let _ = Shares::deposit( + construct_asset(*a), + &data.pool_creation.origin, + data.pool_creation.amount, + ); + } + let pool_id = data.pool_creation.create_pool(); + // join a pool with a valid pool id + let _ = Swaps::pool_join( + Origin::signed(data.origin), + pool_id, + data.pool_amount, + data.asset_bounds, + ); + }); + + let _ = ext.commit_all(); +}); diff --git a/zrml/swaps/fuzz/pool_join_with_exact_asset_amount.rs b/zrml/swaps/fuzz/pool_join_with_exact_asset_amount.rs new file mode 100644 index 000000000..046b045cf --- /dev/null +++ b/zrml/swaps/fuzz/pool_join_with_exact_asset_amount.rs @@ -0,0 +1,35 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; +use zrml_swaps::mock::{ExtBuilder, Origin, Swaps}; + +use utils::ExactAssetAmountData; +mod utils; +use orml_traits::MultiCurrency; +use utils::construct_asset; +use zrml_swaps::mock::Shares; + +fuzz_target!(|data: ExactAssetAmountData| { + let mut ext = ExtBuilder::default().build(); + let _ = ext.execute_with(|| { + // ensure that the account origin has a sufficient balance + // use orml_traits::MultiCurrency; required for this + for a in &data.pool_creation.assets { + let _ = Shares::deposit( + construct_asset(*a), + &data.pool_creation.origin, + // In order to successfully join the pool, data.asset_amount more tokens needed + data.pool_creation.amount.saturating_add(data.asset_amount), + ); + } + let pool_id = data.pool_creation.create_pool(); + let _ = Swaps::pool_join_with_exact_asset_amount( + Origin::signed(data.origin), + pool_id, + construct_asset(data.asset), + data.asset_amount, + data.pool_amount, + ); + }); + let _ = ext.commit_all(); +}); diff --git a/zrml/swaps/fuzz/pool_join_with_exact_pool_amount.rs b/zrml/swaps/fuzz/pool_join_with_exact_pool_amount.rs new file mode 100644 index 000000000..8f5b21b72 --- /dev/null +++ b/zrml/swaps/fuzz/pool_join_with_exact_pool_amount.rs @@ -0,0 +1,35 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; +use zrml_swaps::mock::{ExtBuilder, Origin, Swaps}; + +use utils::ExactAmountData; +mod utils; +use orml_traits::MultiCurrency; +use utils::construct_asset; +use zrml_swaps::mock::Shares; + +fuzz_target!(|data: ExactAmountData| { + let mut ext = ExtBuilder::default().build(); + let _ = ext.execute_with(|| { + // ensure that the account origin has a sufficient balance + // use orml_traits::MultiCurrency; required for this + for a in &data.pool_creation.assets { + let _ = Shares::deposit( + construct_asset(*a), + &data.pool_creation.origin, + // In order to successfully join the pool, data.asset_amount more tokens needed + data.pool_creation.amount.saturating_add(data.asset_amount), + ); + } + let pool_id = data.pool_creation.create_pool(); + let _ = Swaps::pool_join_with_exact_pool_amount( + Origin::signed(data.origin), + pool_id, + construct_asset(data.asset), + data.pool_amount, + data.asset_amount, + ); + }); + let _ = ext.commit_all(); +}); diff --git a/zrml/swaps/fuzz/swap_exact_amount_in.rs b/zrml/swaps/fuzz/swap_exact_amount_in.rs new file mode 100644 index 000000000..7c57fc985 --- /dev/null +++ b/zrml/swaps/fuzz/swap_exact_amount_in.rs @@ -0,0 +1,37 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; +use zrml_swaps::mock::{ExtBuilder, Origin, Swaps}; + +use utils::SwapExactAmountInData; +mod utils; +use orml_traits::MultiCurrency; +use utils::construct_asset; +use zrml_swaps::mock::Shares; + +fuzz_target!(|data: SwapExactAmountInData| { + let mut ext = ExtBuilder::default().build(); + let _ = ext.execute_with(|| { + // ensure that the account origin has a sufficient balance + // use orml_traits::MultiCurrency; required for this + for a in &data.pool_creation.assets { + let _ = Shares::deposit( + construct_asset(*a), + &data.pool_creation.origin, + data.pool_creation.amount, + ); + } + let pool_id = data.pool_creation.create_pool(); + let _ = Shares::deposit(construct_asset(data.asset_in), &data.origin, data.asset_amount_in); + let _ = Swaps::swap_exact_amount_in( + Origin::signed(data.origin), + pool_id, + construct_asset(data.asset_in), + data.asset_amount_in, + construct_asset(data.asset_out), + data.asset_amount_out, + data.max_price, + ); + }); + let _ = ext.commit_all(); +}); diff --git a/zrml/swaps/fuzz/swap_exact_amount_out.rs b/zrml/swaps/fuzz/swap_exact_amount_out.rs new file mode 100644 index 000000000..3844efb5d --- /dev/null +++ b/zrml/swaps/fuzz/swap_exact_amount_out.rs @@ -0,0 +1,40 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; +use zrml_swaps::mock::{ExtBuilder, Origin, Swaps}; + +mod utils; +use orml_traits::MultiCurrency; +use utils::{construct_asset, SwapExactAmountOutData}; +use zrml_swaps::mock::Shares; + +fuzz_target!(|data: SwapExactAmountOutData| { + let mut ext = ExtBuilder::default().build(); + let _ = ext.execute_with(|| { + // ensure that the account origin has a sufficient balance + // use orml_traits::MultiCurrency; required for this + for a in &data.pool_creation.assets { + let _ = Shares::deposit( + construct_asset(*a), + &data.pool_creation.origin, + data.pool_creation.amount, + ); + } + let pool_id = data.pool_creation.create_pool(); + + if let Some(amount) = data.asset_amount_in { + let _ = Shares::deposit(construct_asset(data.asset_in), &data.origin, amount); + } + + let _ = Swaps::swap_exact_amount_out( + Origin::signed(data.origin), + pool_id, + construct_asset(data.asset_in), + data.asset_amount_in, + construct_asset(data.asset_out), + data.asset_amount_out, + data.max_price, + ); + }); + let _ = ext.commit_all(); +}); diff --git a/zrml/swaps/fuzz/swaps_full_workflow.rs b/zrml/swaps/fuzz/swaps_full_workflow.rs deleted file mode 100644 index fbdfb57bc..000000000 --- a/zrml/swaps/fuzz/swaps_full_workflow.rs +++ /dev/null @@ -1,146 +0,0 @@ -#![no_main] - -use libfuzzer_sys::fuzz_target; -use zeitgeist_primitives::types::{Asset, ScalarPosition, SerdeWrapper}; -use zrml_swaps::mock::{ExtBuilder, Origin, Swaps}; - -fuzz_target!(|data: Data| { - let mut ext = ExtBuilder::default().build(); - let _ = ext.execute_with(|| { - let _ = Swaps::pool_join( - Origin::signed(data.pool_join_origin.into()), - data.pool_join_pool_id.into(), - data.pool_join_pool_amount, - data.pool_join_max_assets_in, - ); - - let _ = Swaps::pool_join_with_exact_asset_amount( - Origin::signed(data.pool_join_with_exact_asset_amount_origin.into()), - data.pool_join_with_exact_asset_amount_pool_id.into(), - asset(data.pool_join_with_exact_asset_amount_asset_in), - data.pool_join_with_exact_asset_amount_asset_amount, - data.pool_join_with_exact_asset_amount_min_pool_amount, - ); - - let _ = Swaps::pool_join_with_exact_pool_amount( - Origin::signed(data.pool_join_with_exact_pool_amount_origin.into()), - data.pool_join_with_exact_pool_amount_pool_id.into(), - asset(data.pool_join_with_exact_pool_amount_asset), - data.pool_join_with_exact_pool_amount_pool_amount, - data.pool_join_with_exact_pool_amount_max_asset_amount, - ); - - let _ = Swaps::swap_exact_amount_in( - Origin::signed(data.swap_exact_amount_in_origin.into()), - data.swap_exact_amount_in_pool_id.into(), - asset(data.swap_exact_amount_in_asset_in), - data.swap_exact_amount_in_asset_amount_in, - asset(data.swap_exact_amount_in_asset_out), - data.swap_exact_amount_in_min_asset_amount_out, - data.swap_exact_amount_in_max_price, - ); - - let _ = Swaps::swap_exact_amount_out( - Origin::signed(data.swap_exact_amount_out_origin.into()), - data.swap_exact_amount_out_pool_id.into(), - asset(data.swap_exact_amount_out_asset_in), - data.swap_exact_amount_out_max_asset_amount_in, - asset(data.swap_exact_amount_out_asset_out), - data.swap_exact_amount_out_asset_amount_out, - data.swap_exact_amount_out_max_price, - ); - - let _ = Swaps::pool_exit_with_exact_pool_amount( - Origin::signed(data.pool_exit_with_exact_pool_amount_origin.into()), - data.pool_exit_with_exact_pool_amount_pool_id.into(), - asset(data.pool_exit_with_exact_pool_amount_asset), - data.pool_exit_with_exact_pool_amount_pool_amount, - data.pool_exit_with_exact_pool_amount_min_asset_amount, - ); - - let _ = Swaps::pool_exit_with_exact_asset_amount( - Origin::signed(data.pool_exit_with_exact_asset_amount_origin.into()), - data.pool_exit_with_exact_asset_amount_pool_id.into(), - asset(data.pool_exit_with_exact_asset_amount_asset), - data.pool_exit_with_exact_asset_amount_asset_amount, - data.pool_exit_with_exact_asset_amount_max_pool_amount, - ); - - let _ = Swaps::pool_exit( - Origin::signed(data.pool_exit_origin.into()), - data.pool_exit_pool_id.into(), - data.pool_exit_pool_amount, - data.pool_exit_min_assets_out, - ); - }); - let _ = ext.commit_all(); -}); - -#[derive(Debug, arbitrary::Arbitrary)] -struct Data { - pool_join_origin: u8, - pool_join_pool_id: u8, - pool_join_pool_amount: u128, - pool_join_max_assets_in: Vec, - - pool_join_with_exact_asset_amount_origin: u8, - pool_join_with_exact_asset_amount_pool_id: u8, - pool_join_with_exact_asset_amount_asset_in: (u128, u16), - pool_join_with_exact_asset_amount_asset_amount: u128, - pool_join_with_exact_asset_amount_min_pool_amount: u128, - - pool_join_with_exact_pool_amount_origin: u8, - pool_join_with_exact_pool_amount_pool_id: u8, - pool_join_with_exact_pool_amount_asset: (u128, u16), - pool_join_with_exact_pool_amount_pool_amount: u128, - pool_join_with_exact_pool_amount_max_asset_amount: u128, - - swap_exact_amount_in_origin: u8, - swap_exact_amount_in_pool_id: u8, - swap_exact_amount_in_asset_in: (u128, u16), - swap_exact_amount_in_asset_amount_in: u128, - swap_exact_amount_in_asset_out: (u128, u16), - swap_exact_amount_in_min_asset_amount_out: Option, - swap_exact_amount_in_max_price: Option, - - swap_exact_amount_out_origin: u8, - swap_exact_amount_out_pool_id: u8, - swap_exact_amount_out_asset_in: (u128, u16), - swap_exact_amount_out_max_asset_amount_in: Option, - swap_exact_amount_out_asset_out: (u128, u16), - swap_exact_amount_out_asset_amount_out: u128, - swap_exact_amount_out_max_price: Option, - - pool_exit_with_exact_pool_amount_origin: u8, - pool_exit_with_exact_pool_amount_pool_id: u8, - pool_exit_with_exact_pool_amount_asset: (u128, u16), - pool_exit_with_exact_pool_amount_pool_amount: u128, - pool_exit_with_exact_pool_amount_min_asset_amount: u128, - - pool_exit_with_exact_asset_amount_origin: u8, - pool_exit_with_exact_asset_amount_pool_id: u8, - pool_exit_with_exact_asset_amount_asset: (u128, u16), - pool_exit_with_exact_asset_amount_asset_amount: u128, - pool_exit_with_exact_asset_amount_max_pool_amount: u128, - - pool_exit_origin: u8, - pool_exit_pool_id: u8, - pool_exit_pool_amount: u128, - pool_exit_min_assets_out: Vec, -} - -fn asset(seed: (u128, u16)) -> Asset { - let (seed0, seed1) = seed; - let module = seed0 % 4; - match module { - 0 => Asset::CategoricalOutcome(seed0, seed1), - 1 => { - let scalar_position = - if seed1 % 2 == 0 { ScalarPosition::Long } else { ScalarPosition::Short }; - Asset::ScalarOutcome(seed0, scalar_position) - } - 2 => Asset::CombinatorialOutcome, - 3 => Asset::PoolShare(SerdeWrapper(seed0)), - _ => Asset::Ztg, - } -} diff --git a/zrml/swaps/fuzz/utils.rs b/zrml/swaps/fuzz/utils.rs new file mode 100644 index 000000000..b3a827826 --- /dev/null +++ b/zrml/swaps/fuzz/utils.rs @@ -0,0 +1,197 @@ +#![allow( + // Mocks are only used for fuzzing and unit tests + clippy::integer_arithmetic +)] + +use zeitgeist_primitives::constants::{ + MaxAssets, MaxTotalWeight, MaxWeight, MinAssets, MinLiquidity, MinWeight, BASE, +}; + +use arbitrary::{Arbitrary, Result, Unstructured}; + +use rand::{rngs::ThreadRng, seq::SliceRandom, Rng}; + +use zeitgeist_primitives::types::{Asset, ScalarPosition, SerdeWrapper}; + +use zeitgeist_primitives::{ + traits::Swaps as SwapsTrait, + types::{PoolId, ScoringRule}, +}; +use zrml_swaps::mock::Swaps; + +pub fn construct_asset(seed: (u8, u128, u16)) -> Asset { + let (module, seed0, seed1) = seed; + match module % 5 { + 0 => Asset::CategoricalOutcome(seed0, seed1), + 1 => { + let scalar_position = + if seed1 % 2 == 0 { ScalarPosition::Long } else { ScalarPosition::Short }; + Asset::ScalarOutcome(seed0, scalar_position) + } + 2 => Asset::CombinatorialOutcome, + 3 => Asset::PoolShare(SerdeWrapper(seed0)), + _ => Asset::Ztg, + } +} + +#[derive(Debug)] +pub struct ValidPoolData { + pub origin: u128, + pub assets: Vec<(u8, u128, u16)>, + pub base_asset: (u8, u128, u16), + pub market_id: u128, + pub swap_fee: u128, + pub amount: u128, + pub weights: Vec, +} + +impl ValidPoolData { + // This function is called in the swap fuzz tests. + #[allow(dead_code)] + pub fn create_pool(self) -> PoolId { + match Swaps::create_pool( + self.origin, + self.assets.into_iter().map(construct_asset).collect(), + construct_asset(self.base_asset), + self.market_id, + ScoringRule::CPMM, + Some(self.swap_fee), + Some(self.amount), + Some(self.weights), + ) { + Ok(pool_id) => pool_id, + Err(e) => panic!("Pool creation failed unexpectedly. Error: {:?}", e), + } + } +} + +impl<'a> arbitrary::Arbitrary<'a> for ValidPoolData { + fn arbitrary(_: &mut Unstructured<'a>) -> Result { + let mut rng = rand::thread_rng(); + + // unstructured arbitrary_len function did always return zero, that's why using rand crate + let assets_len: u16 = rng.gen_range(MinAssets::get()..=MaxAssets::get()); + + let assets_len = assets_len as usize; + + // create assets and weights collections with assets length + let (assets, weights) = create_random_assets_and_weights(assets_len, &mut rng)?; + + // the base_assets needs to be in the assets + let base_asset = *assets + .get(rng.gen::() % assets_len) + .ok_or(::IncorrectFormat)?; + + let origin = rng.gen::(); + let market_id = rng.gen::(); + let swap_fee = rng.gen_range(0..BASE); + let amount = rng.gen_range(MinLiquidity::get()..u128::MAX); + + Ok(ValidPoolData { origin, assets, base_asset, market_id, swap_fee, amount, weights }) + } +} + +fn create_random_assets_and_weights( + assets_len: usize, + rng: &mut ThreadRng, +) -> Result<(Vec<(u8, u128, u16)>, Vec)> { + let mut assets: Vec<(u8, u128, u16)> = Vec::with_capacity(assets_len); + let mut weights: Vec = Vec::with_capacity(assets_len); + + assert!(MaxWeight::get() <= MaxTotalWeight::get()); + + let mut weight_sum = 0; + + let assets_len = assets_len as u128; + for i in 0..assets_len { + // first iteration: (asset_len - 1) assets left + let assets_left = assets_len - 1 - i; + // reservation of multiple MinWeight for the future iterations + let future_min_weight_reserve = assets_left * MinWeight::get(); + // take min_weight_reserve for future iterations in calculation + // maximum value of weight without looking at the previous weight_sum + let max_weight_limit = MaxTotalWeight::get() - future_min_weight_reserve; + // previous weight sum substraction limits to exceed the MaxTotalWeight (otherwise no pool creation) + let max_weight_limit = max_weight_limit - weight_sum; + // each individual weight is at most MaxWeight + let max = max_weight_limit.min(MaxWeight::get()); + let weight = rng.gen_range(MinWeight::get()..max); + + match weight_sum.checked_add(weight) { + Some(sum) => weight_sum = sum, + None => return Err(::IncorrectFormat), + }; + weights.push(weight); + + let mut asset = (rng.gen::(), rng.gen::(), rng.gen::()); + while assets.clone().into_iter().map(construct_asset).any(|a| a == construct_asset(asset)) { + // another try for finding a non-duplicated asset + asset = (rng.gen::(), rng.gen::(), rng.gen::()); + } + + assets.push(asset); + } + // Need to shuffle the vector, because earlier numbers have a higher probability of being + // large. + weights.shuffle(rng); + Ok((assets, weights)) +} + +#[derive(Debug, Arbitrary)] +pub struct ExactAmountData { + pub origin: u128, + pub pool_creation: ValidPoolData, + pub asset: (u8, u128, u16), + pub pool_amount: u128, + pub asset_amount: u128, +} + +#[derive(Debug, Arbitrary)] +pub struct ExactAssetAmountData { + pub origin: u128, + pub pool_creation: ValidPoolData, + pub asset: (u8, u128, u16), + pub asset_amount: u128, + pub pool_amount: u128, +} + +#[derive(Debug, Arbitrary)] +pub struct GeneralPoolData { + pub origin: u128, + pub pool_creation: ValidPoolData, + pub pool_amount: u128, + pub asset_bounds: Vec, +} + +#[derive(Debug, Arbitrary)] +pub struct SwapExactAmountInData { + pub origin: u128, + pub pool_creation: ValidPoolData, + pub asset_in: (u8, u128, u16), + pub asset_amount_in: u128, + pub asset_out: (u8, u128, u16), + pub asset_amount_out: Option, + pub max_price: Option, +} + +#[derive(Debug, Arbitrary)] +pub struct SwapExactAmountOutData { + pub origin: u128, + pub pool_creation: ValidPoolData, + pub asset_in: (u8, u128, u16), + pub asset_amount_in: Option, + pub asset_out: (u8, u128, u16), + pub asset_amount_out: u128, + pub max_price: Option, +} + +#[derive(Debug, Arbitrary)] +pub struct PoolCreationData { + pub origin: u128, + pub assets: Vec<(u8, u128, u16)>, + pub base_asset: (u8, u128, u16), + pub market_id: u128, + pub swap_fee: Option, + pub amount: Option, + pub weights: Option>, +} diff --git a/zrml/swaps/src/mock.rs b/zrml/swaps/src/mock.rs index eb3fd0294..ef1d02cd9 100644 --- a/zrml/swaps/src/mock.rs +++ b/zrml/swaps/src/mock.rs @@ -1,4 +1,8 @@ #![cfg(feature = "mock")] +#![allow( + // Mocks are only used for fuzzing and unit tests + clippy::integer_arithmetic +)] use crate as zrml_swaps; use frame_support::{construct_runtime, parameter_types, traits::Everything}; @@ -54,6 +58,8 @@ construct_runtime!( } ); +pub type Shares = Currencies; + impl crate::Config for Runtime { type Event = Event; type ExitFee = ExitFeeMock; @@ -74,7 +80,7 @@ impl crate::Config for Runtime { type MinWeight = MinWeight; type PalletId = SwapsPalletId; type RikiddoSigmoidFeeMarketEma = RikiddoSigmoidFeeMarketEma; - type Shares = Currencies; + type Shares = Shares; type WeightInfo = zrml_swaps::weights::WeightInfo; }