Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add fuzz tests for combinatorial tokens, pool and futarchy #1392

Open
wants to merge 12 commits into
base: develop-combo-futarchy
Choose a base branch
from
47 changes: 47 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,15 @@ members = [
"runtime/zeitgeist",
"zrml/authorized",
"zrml/combinatorial-tokens",
"zrml/combinatorial-tokens/fuzz",
"zrml/court",
"zrml/futarchy",
"zrml/futarchy/fuzz",
"zrml/hybrid-router",
"zrml/global-disputes",
"zrml/market-commons",
"zrml/neo-swaps",
"zrml/neo-swaps/fuzz",
"zrml/orderbook",
"zrml/orderbook/fuzz",
"zrml/parimutuel",
Expand Down
2 changes: 1 addition & 1 deletion primitives/src/traits/market_commons_pallet_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ use sp_runtime::{

// Abstraction of the market type, which is not a part of `MarketCommonsPalletApi` because Rust
// doesn't support type aliases in traits.
type MarketOf<T> = Market<
pub type MarketOf<T> = Market<
<T as MarketCommonsPalletApi>::AccountId,
<T as MarketCommonsPalletApi>::Balance,
<T as MarketCommonsPalletApi>::BlockNumber,
Expand Down
10 changes: 10 additions & 0 deletions scripts/tests/fuzz.sh
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,13 @@ cargo fuzz run --release --fuzz-dir zrml/swaps/fuzz pool_exit -- -runs=$(($(($RU

# --- Orderbook-v1 Pallet fuzz tests ---
cargo fuzz run --release --fuzz-dir zrml/orderbook/fuzz orderbook_v1_full_workflow -- -runs=$RUNS

cargo fuzz run --release --fuzz-dir zrml/futarchy/fuzz submit_proposal -- -runs=$RUNS

cargo fuzz run --release --fuzz-dir zrml/combinatorial-tokens/fuzz split_position -- -runs=$RUNS
cargo fuzz run --release --fuzz-dir zrml/combinatorial-tokens/fuzz merge_position -- -runs=$RUNS
cargo fuzz run --release --fuzz-dir zrml/combinatorial-tokens/fuzz redeem_position -- -runs=$RUNS

cargo fuzz run --release --fuzz-dir zrml/neo-swaps/fuzz deploy_combinatorial_pool -- -runs=$RUNS
cargo fuzz run --release --fuzz-dir zrml/neo-swaps/fuzz combo_buy -- -runs=$RUNS
cargo fuzz run --release --fuzz-dir zrml/neo-swaps/fuzz combo_sell -- -runs=$RUNS
38 changes: 38 additions & 0 deletions zrml/combinatorial-tokens/fuzz/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
[[bin]]
doc = false
name = "split_position"
path = "split_position.rs"
test = false

[[bin]]
doc = false
name = "merge_position"
path = "merge_position.rs"
test = false

[[bin]]
doc = false
name = "redeem_position"
path = "redeem_position.rs"
test = false

[dependencies]
arbitrary = { workspace = true, features = ["derive"] }
frame-support = { workspace = true, features = ["default"] }
frame-system = { workspace = true }
libfuzzer-sys = { workspace = true }
orml-traits = { workspace = true, features = ["default"] }
rand = { workspace = true, features = ["default"] }
sp-runtime = { workspace = true, features = ["default"] }
zeitgeist-primitives = { workspace = true, features = ["default", "mock"] }
zrml-combinatorial-tokens = { workspace = true, features = ["default", "mock"] }

[package]
authors = ["Forecasting Technologies Ltd"]
edition.workspace = true
name = "zrml-combinatorial-tokens-fuzz"
publish = false
version = "0.5.5"

[package.metadata]
cargo-fuzz = true
35 changes: 35 additions & 0 deletions zrml/combinatorial-tokens/fuzz/common.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use zeitgeist_primitives::{
traits::MarketOf,
types::{Market, MarketCreation, MarketPeriod, MarketStatus, MarketType, ScoringRule},
};
use zrml_combinatorial_tokens::{AssetOf, Config, MarketIdOf};

pub(crate) fn market<T>(
market_id: MarketIdOf<T>,
base_asset: AssetOf<T>,
market_type: MarketType,
) -> MarketOf<<T as Config>::MarketCommons>
where
T: Config,
<T as frame_system::Config>::AccountId: Default,
{
Market {
market_id,
base_asset,
creator: Default::default(),
creation: MarketCreation::Permissionless,
creator_fee: Default::default(),
oracle: Default::default(),
metadata: Default::default(),
market_type,
period: MarketPeriod::Block(0u8.into()..10u8.into()),
deadlines: Default::default(),
scoring_rule: ScoringRule::AmmCdaHybrid,
status: MarketStatus::Active,
report: None,
resolved_outcome: None,
dispute_mechanism: None,
bonds: Default::default(),
early_close: None,
}
}
120 changes: 120 additions & 0 deletions zrml/combinatorial-tokens/fuzz/merge_position.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
#![no_main]

mod common;

use arbitrary::{Arbitrary, Result as ArbitraryResult, Unstructured};
use libfuzzer_sys::fuzz_target;
use orml_traits::currency::MultiCurrency;
use zeitgeist_primitives::{
traits::MarketCommonsPalletApi,
types::{Asset, MarketType},
};
use zrml_combinatorial_tokens::{
mock::{
ext_builder::ExtBuilder,
runtime::{CombinatorialTokens, Runtime, RuntimeOrigin},
},
AccountIdOf, BalanceOf, CombinatorialIdOf, Config, MarketIdOf,
};

#[derive(Debug)]
struct MergePositionFuzzParams {
account_id: AccountIdOf<Runtime>,
parent_collection_id: Option<CombinatorialIdOf<Runtime>>,
market_id: MarketIdOf<Runtime>,
partition: Vec<Vec<bool>>,
amount: BalanceOf<Runtime>,
force_max_work: bool,
}

impl<'a> Arbitrary<'a> for MergePositionFuzzParams {
fn arbitrary(u: &mut Unstructured<'a>) -> ArbitraryResult<Self> {
let account_id = u128::arbitrary(u)?;
let parent_collection_id = Arbitrary::arbitrary(u)?;
let market_id = 0u8.into();
let amount = Arbitrary::arbitrary(u)?;
let force_max_work = Arbitrary::arbitrary(u)?;

// Note: This might result in members of unequal length, but that's OK.
let min_len = 0;
let max_len = 10;
let len = u.int_in_range(0..=max_len)?;
let partition =
(min_len..len).map(|_| Arbitrary::arbitrary(u)).collect::<ArbitraryResult<Vec<_>>>()?;

let params = MergePositionFuzzParams {
account_id,
parent_collection_id,
market_id,
partition,
amount,
force_max_work,
};

Ok(params)
}
}

fuzz_target!(|params: MergePositionFuzzParams| {
let mut ext = ExtBuilder::build();

ext.execute_with(|| {
// We create a market and equip the user with the tokens they require to make the
// `merge_position` call meaningful, and deposit collateral in the pallet account.
let collateral = Asset::Ztg;
let asset_count = if let Some(member) = params.partition.first() {
member.len().max(2) as u16
} else {
2u16 // In this case the index set doesn't fit the market.
};
let market = common::market::<Runtime>(
params.market_id,
collateral,
MarketType::Categorical(asset_count),
);
<<Runtime as Config>::MarketCommons as MarketCommonsPalletApi>::push_market(market)
.unwrap();

let positions = params
.partition
.iter()
.cloned()
.map(|index_set| {
CombinatorialTokens::position_from_parent_collection(
params.parent_collection_id,
params.market_id,
index_set,
false,
)
})
.collect::<Result<Vec<_>, _>>()
.unwrap();
for &position in positions.iter() {
<<Runtime as Config>::MultiCurrency>::deposit(
position,
&params.account_id,
params.amount,
)
.unwrap();
}

// Is not required if `parent_collection_id.is_some()`, but we're doing it anyways.
<<Runtime as Config>::MultiCurrency>::deposit(
collateral,
&CombinatorialTokens::account_id(),
params.amount,
)
.unwrap();

let _ = CombinatorialTokens::merge_position(
RuntimeOrigin::signed(params.account_id),
params.parent_collection_id,
params.market_id,
params.partition,
params.amount,
params.force_max_work,
);
});

let _ = ext.commit_all();
});
Loading