From ff21a047fb016a01450c158e3fe678d5a1799a01 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Fri, 13 Sep 2024 15:15:58 +0200 Subject: [PATCH 01/73] Implement Combinatorial Betting (#1354) * Revert "New Asset System (#1295)" (#1338) * Revert "New Asset System (#1295)" This reverts commit a956877d596ac41d8bda5c341b35472ef915fa18. * Fix formatting * Update copyright * Remove pallet-assets dependency * Fix fuzz tests * Merge `main` into `develop` (#1345) * Update dependencies to Polkadot v1.1.0 (#1331) * Update dependencies (#1319) * Update dependencies to polkadot-v1.1.0 * Format code * Remove duplicate dependencies * Update zrml-asset-router (#1321) * Update zrml-primitives * Partially update asset-router * Finalize logic adjustments in asset-router * Make asset-router tests compilable * Correct Inspect routing for market assets in Currencies * Directly invoke Inspect API for Currencies * Add tests for remaining Unbalances functions * Update remaining Zeitgeist pallets (#1322) * Update zrml-asset-router (#1321) * Upgrade zrml-market-commons * Upgrade zrml-authorized && use MockBlock instead of MockBlockU32 * Upgrade zrml-court * Upgrade zrml-global-disputes * Upgrade liquidity mining * Upgrade zrml-rikiddo * Upgrade zrml-simple-disputes * Upgrade zrml-styx * Upgrade zrml-orderbook * Upgrade zrml-parimutuel * Upgrade zrml-swaps * Upgrade zrml-prediction-markets * Upgrade zrml-neo-swaps * Upgrade zrml-hybrid-router * Update license headers * Update runtime (#1323) * Update weight files & Runtime enum * Use workspace metadata * Always use serde serialization for asset types * Make battery station standalone runtime compilable * Make benchmark and try-runtime feature compilable * Make BS build with all features * Make parachain tests compile * Partially fix xcm tests * Use safe xcm version 2 * Update Zeitgeist runtime (except xcm tests) * Format code * Remove deprecated comment * Integrate new xcm-emulator (#1324) * Integrate new xcm-emulator environment * Utilize new xcm-emulator interfaces * Spawn relay-para network using patched xcm-emulator * Use proper collator genesis config * Fix Rococo tests * Finalize Battery Station XCM tests * Finalize Zeitgeist XCM tests * Update client (#1327) * Fix rpc and work on client update * Finalize standalone client * Update parachain client * Use same try-runtime subcommand in every case * Update node/src/cli.rs Co-authored-by: Malte Kliemann * Update try-runtime* Makefile targets --------- Co-authored-by: Malte Kliemann * Make CI succeed and add migrations (#1329) * Fix rpc and work on client update * Finalize standalone client * Update parachain client * Use same try-runtime subcommand in every case * Satisfy Clippy * Fix benchmarks * Add migrations * Satisfy Clippy * Update moonkit depedencies * Free disk space more aggressively --------- Co-authored-by: Malte Kliemann * Update spec version, try-runtime Makefile * Fix copyright notices * Fix broken chain state (#1336) * Add `StorageVersion` fix and contrats fix migrations * Don't set pallet-balances' storage version * Remove migrations from pallet-contracts config * Clear storage tries of contracts * Fix migration and info logs in try-runtime * Fix licenses and comments * Fix formatting --------- Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --------- Co-authored-by: Harald Heckmann Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> * Merge * Fix benchmark * Fix compiler error * Fix tests and imports * Fix imports (again...) * Fix orderbook benchmarks * Fix fuzz tests * Fix formatting * Fix orderbook fuzz --------- Co-authored-by: Harald Heckmann Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> * Update versions to v0.5.3 * Restructure math module * More scaffolding * Implement combinatorial buy math * Implement price calculation for combo * Remove `println!` * Implement equalization * Implement selling * Add tests for combinatorial buying * Add more tests for combinatorial buying * Add tests for equalization * Add more tests/corner cases * Implement full testing, fix critical bug --------- Co-authored-by: Harald Heckmann Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- zrml/neo-swaps/src/lib.rs | 2 +- zrml/neo-swaps/src/math/mod.rs | 20 + .../src/math/traits/combo_math_ops.rs | 54 ++ zrml/neo-swaps/src/math/traits/math_ops.rs | 64 ++ zrml/neo-swaps/src/math/traits/mod.rs | 22 + zrml/neo-swaps/src/math/transcendental.rs | 120 +++ zrml/neo-swaps/src/math/types/combo_math.rs | 768 ++++++++++++++++++ zrml/neo-swaps/src/{ => math/types}/math.rs | 159 +--- zrml/neo-swaps/src/math/types/mod.rs | 5 + zrml/neo-swaps/src/types/pool.rs | 2 +- 10 files changed, 1063 insertions(+), 153 deletions(-) create mode 100644 zrml/neo-swaps/src/math/mod.rs create mode 100644 zrml/neo-swaps/src/math/traits/combo_math_ops.rs create mode 100644 zrml/neo-swaps/src/math/traits/math_ops.rs create mode 100644 zrml/neo-swaps/src/math/traits/mod.rs create mode 100644 zrml/neo-swaps/src/math/transcendental.rs create mode 100644 zrml/neo-swaps/src/math/types/combo_math.rs rename zrml/neo-swaps/src/{ => math/types}/math.rs (78%) create mode 100644 zrml/neo-swaps/src/math/types/mod.rs diff --git a/zrml/neo-swaps/src/lib.rs b/zrml/neo-swaps/src/lib.rs index 07d3d3566..03346909a 100644 --- a/zrml/neo-swaps/src/lib.rs +++ b/zrml/neo-swaps/src/lib.rs @@ -40,7 +40,7 @@ mod pallet { use crate::{ consts::LN_NUMERICAL_LIMIT, liquidity_tree::types::{BenchmarkInfo, LiquidityTree, LiquidityTreeError}, - math::{Math, MathOps}, + math::{traits::MathOps, types::Math}, traits::{pool_operations::PoolOperations, LiquiditySharesManager}, types::{FeeDistribution, MaxAssets, Pool}, weights::*, diff --git a/zrml/neo-swaps/src/math/mod.rs b/zrml/neo-swaps/src/math/mod.rs new file mode 100644 index 000000000..0c9c3a0cc --- /dev/null +++ b/zrml/neo-swaps/src/math/mod.rs @@ -0,0 +1,20 @@ +// Copyright 2023-2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +pub(crate) mod traits; +mod transcendental; +pub(crate) mod types; diff --git a/zrml/neo-swaps/src/math/traits/combo_math_ops.rs b/zrml/neo-swaps/src/math/traits/combo_math_ops.rs new file mode 100644 index 000000000..bdf78ebe4 --- /dev/null +++ b/zrml/neo-swaps/src/math/traits/combo_math_ops.rs @@ -0,0 +1,54 @@ +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use crate::{BalanceOf, Config}; +use sp_runtime::DispatchError; + +pub(crate) trait ComboMathOps +where + T: Config, +{ + fn calculate_swap_amount_out_for_buy( + buy: Vec>, + sell: Vec>, + amount_in: BalanceOf, + liquidity: BalanceOf, + ) -> Result, DispatchError>; + + fn calculate_equalize_amount( + buy: Vec>, + sell: Vec>, + amount_buy: BalanceOf, + amount_sell: BalanceOf, + liquidity: BalanceOf, + ) -> Result, DispatchError>; + + fn calculate_swap_amount_out_for_sell( + buy: Vec>, + keep: Vec>, + sell: Vec>, + amount_buy: BalanceOf, + amount_keep: BalanceOf, + liquidity: BalanceOf, + ) -> Result, DispatchError>; + + fn calculate_spot_price( + buy: Vec>, + sell: Vec>, + liquidity: BalanceOf, + ) -> Result, DispatchError>; +} diff --git a/zrml/neo-swaps/src/math/traits/math_ops.rs b/zrml/neo-swaps/src/math/traits/math_ops.rs new file mode 100644 index 000000000..45f699247 --- /dev/null +++ b/zrml/neo-swaps/src/math/traits/math_ops.rs @@ -0,0 +1,64 @@ +// Copyright 2023-2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use crate::{BalanceOf, Config}; +use sp_runtime::DispatchError; + +pub(crate) trait MathOps +where + T: Config, +{ + fn calculate_swap_amount_out_for_buy( + reserve: BalanceOf, + amount_in: BalanceOf, + liquidity: BalanceOf, + ) -> Result, DispatchError>; + + fn calculate_swap_amount_out_for_sell( + reserve: BalanceOf, + amount_in: BalanceOf, + liquidity: BalanceOf, + ) -> Result, DispatchError>; + + fn calculate_spot_price( + reserve: BalanceOf, + liquidity: BalanceOf, + ) -> Result, DispatchError>; + + fn calculate_reserves_from_spot_prices( + amount: BalanceOf, + spot_prices: Vec>, + ) -> Result<(BalanceOf, Vec>), DispatchError>; + + fn calculate_buy_ln_argument( + reserve: BalanceOf, + amount: BalanceOf, + liquidity: BalanceOf, + ) -> Result, DispatchError>; + + fn calculate_buy_amount_until( + until: BalanceOf, + liquidity: BalanceOf, + spot_price: BalanceOf, + ) -> Result, DispatchError>; + + fn calculate_sell_amount_until( + until: BalanceOf, + liquidity: BalanceOf, + spot_price: BalanceOf, + ) -> Result, DispatchError>; +} diff --git a/zrml/neo-swaps/src/math/traits/mod.rs b/zrml/neo-swaps/src/math/traits/mod.rs new file mode 100644 index 000000000..b8a86228d --- /dev/null +++ b/zrml/neo-swaps/src/math/traits/mod.rs @@ -0,0 +1,22 @@ +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +mod combo_math_ops; +mod math_ops; + +pub(crate) use combo_math_ops::ComboMathOps; +pub(crate) use math_ops::MathOps; diff --git a/zrml/neo-swaps/src/math/transcendental.rs b/zrml/neo-swaps/src/math/transcendental.rs new file mode 100644 index 000000000..1d3d97122 --- /dev/null +++ b/zrml/neo-swaps/src/math/transcendental.rs @@ -0,0 +1,120 @@ +// Copyright 2023-2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . +// +// This file incorporates work covered by the following copyright and +// permission notice: +// +// Copyright (c) 2019 Alain Brenzikofer, modified by GalacticCouncil(2021) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Original source: https://github.com/encointer/substrate-fixed +// +// The changes applied are: Re-used and extended tests for `exp` and other +// functions. + +pub(crate) use hydra_dx_math::transcendental::{exp, ln}; + +#[cfg(test)] +mod tests { + use super::*; + use alloc::str::FromStr; + use fixed::types::U64F64; + use test_case::test_case; + + type S = U64F64; + type D = U64F64; + + #[test_case("0", false, "1")] + #[test_case("0", true, "1")] + #[test_case("1", false, "2.7182818284590452353")] + #[test_case("1", true, "0.367879441171442321595523770161460867445")] + #[test_case("2", false, "7.3890560989306502265")] + #[test_case("2", true, "0.13533528323661269186")] + #[test_case("0.1", false, "1.1051709180756476246")] + #[test_case("0.1", true, "0.9048374180359595733")] + #[test_case("0.9", false, "2.4596031111569496633")] + #[test_case("0.9", true, "0.40656965974059911195")] + #[test_case("1.5", false, "4.481689070338064822")] + #[test_case("1.5", true, "0.22313016014842982894")] + #[test_case("3.3", false, "27.1126389206578874259")] + #[test_case("3.3", true, "0.03688316740124000543")] + #[test_case("7.3456", false, "1549.3643050275008503592")] + #[test_case("7.3456", true, "0.00064542599616831253")] + #[test_case("12.3456789", false, "229964.194569082134542849")] + #[test_case("12.3456789", true, "0.00000434850304358833")] + #[test_case("13", false, "442413.39200892050332603603")] + #[test_case("13", true, "0.0000022603294069810542")] + fn exp_works(operand: &str, neg: bool, expected: &str) { + let o = U64F64::from_str(operand).unwrap(); + let e = U64F64::from_str(expected).unwrap(); + assert_eq!(exp::(o, neg).unwrap(), e); + } + + #[test_case("1", "0", false)] + #[test_case("2", "0.69314718055994530943", false)] + #[test_case("3", "1.09861228866810969136", false)] + #[test_case("2.718281828459045235360287471352662497757", "1", false)] + #[test_case("1.1051709180756476246", "0.09999999999999999975", false)] + #[test_case("2.4596031111569496633", "0.89999999999999999976", false)] + #[test_case("4.481689070338064822", "1.49999999999999999984", false)] + #[test_case("27.1126389206578874261", "3.3", false)] + #[test_case("1549.3643050275008503592", "7.34560000000000000003", false)] + #[test_case("229964.194569082134542849", "12.3456789000000000002", false)] + #[test_case("442413.39200892050332603603", "13.0000000000000000002", false)] + #[test_case("0.9048374180359595733", "0.09999999999999999975", true)] + #[test_case("0.40656965974059911195", "0.8999999999999999998", true)] + #[test_case("0.22313016014842982894", "1.4999999999999999999", true)] + #[test_case("0.03688316740124000543", "3.3000000000000000005", true)] + #[test_case("0.00064542599616831253", "7.34560000000000002453", true)] + #[test_case("0.00000434850304358833", "12.34567890000000711117", true)] + #[test_case("0.0000022603294069810542", "13.0000000000000045352", true)] + #[test_case("1.0001", "0.00009999500033330827", false)] + #[test_case("1.00000001", "0.0000000099999999499", false)] + #[test_case("0.9999", "0.00010000500033335825", true)] + #[test_case("0.99999999", "0.00000001000000004987", true)] + // Powers of 2 (since we're using squares when calculating the fractional part of log2. + #[test_case("3.999999999", "1.38629436086989061877", false)] + #[test_case("4", "1.38629436111989061886", false)] + #[test_case("4.000000001", "1.3862943613698906188", false)] + #[test_case("7.999999999", "2.07944154155483592824", false)] + #[test_case("8", "2.0794415416798359283", false)] + #[test_case("8.000000001", "2.0794415418048359282", false)] + #[test_case("0.499999999", "0.69314718255994531136", true)] + #[test_case("0.5", "0.69314718055994530943", true)] + #[test_case("0.500000001", "0.69314717855994531135", true)] + #[test_case("0.249999999", "1.38629436511989062684", true)] + #[test_case("0.25", "1.38629436111989061886", true)] + #[test_case("0.250000001", "1.38629435711989062676", true)] + fn ln_works(operand: &str, expected_abs: &str, expected_neg: bool) { + let o = U64F64::from_str(operand).unwrap(); + let e = U64F64::from_str(expected_abs).unwrap(); + let (a, n) = ln::(o).unwrap(); + assert_eq!(a, e); + assert_eq!(n, expected_neg); + } +} diff --git a/zrml/neo-swaps/src/math/types/combo_math.rs b/zrml/neo-swaps/src/math/types/combo_math.rs new file mode 100644 index 000000000..d6ef12897 --- /dev/null +++ b/zrml/neo-swaps/src/math/types/combo_math.rs @@ -0,0 +1,768 @@ +// Copyright 2023-2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use crate::{ + math::{ + traits::ComboMathOps, + transcendental::{exp, ln}, + }, + BalanceOf, Config, Error, +}; +use alloc::vec::Vec; +use core::marker::PhantomData; +use fixed::FixedU128; +use sp_runtime::{ + traits::{One, Zero}, + DispatchError, SaturatedConversion, +}; +use typenum::U80; + +type Fractional = U80; +type FixedType = FixedU128; + +/// The point at which 32.44892769177272 +const EXP_OVERFLOW_THRESHOLD: FixedType = FixedType::from_bits(0x20_72EC_ECDA_6EBE_EACC_40C7); + +pub(crate) struct ComboMath(PhantomData); + +impl ComboMathOps for ComboMath +where + T: Config, +{ + fn calculate_swap_amount_out_for_buy( + buy: Vec>, + sell: Vec>, + amount_in: BalanceOf, + liquidity: BalanceOf, + ) -> Result, DispatchError> { + detail::calculate_swap_amount_out_for_buy( + buy.into_iter().map(|x| x.saturated_into()).collect(), + sell.into_iter().map(|x| x.saturated_into()).collect(), + amount_in.saturated_into(), + liquidity.saturated_into(), + ) + .map(|result| result.saturated_into()) + .ok_or_else(|| Error::::MathError.into()) + } + + fn calculate_equalize_amount( + buy: Vec>, + sell: Vec>, + amount_buy: BalanceOf, + amount_sell: BalanceOf, + liquidity: BalanceOf, + ) -> Result, DispatchError> { + detail::calculate_equalize_amount( + buy.into_iter().map(|x| x.saturated_into()).collect(), + sell.into_iter().map(|x| x.saturated_into()).collect(), + amount_buy.saturated_into(), + amount_sell.saturated_into(), + liquidity.saturated_into(), + ) + .map(|result| result.saturated_into()) + .ok_or_else(|| Error::::MathError.into()) + } + + fn calculate_swap_amount_out_for_sell( + buy: Vec>, + keep: Vec>, + sell: Vec>, + amount_buy: BalanceOf, + amount_keep: BalanceOf, + liquidity: BalanceOf, + ) -> Result, DispatchError> { + detail::calculate_swap_amount_out_for_sell( + buy.into_iter().map(|x| x.saturated_into()).collect(), + keep.into_iter().map(|x| x.saturated_into()).collect(), + sell.into_iter().map(|x| x.saturated_into()).collect(), + amount_buy.saturated_into(), + amount_keep.saturated_into(), + liquidity.saturated_into(), + ) + .map(|result| result.saturated_into()) + .ok_or_else(|| Error::::MathError.into()) + } + + fn calculate_spot_price( + buy: Vec>, + sell: Vec>, + liquidity: BalanceOf, + ) -> Result, DispatchError> { + detail::calculate_spot_price( + buy.into_iter().map(|x| x.saturated_into()).collect(), + sell.into_iter().map(|x| x.saturated_into()).collect(), + liquidity.saturated_into(), + ) + .map(|result| result.saturated_into()) + .ok_or_else(|| Error::::MathError.into()) + } +} + +mod detail { + use super::*; + use zeitgeist_primitives::{ + constants::DECIMALS, + math::fixed::{IntoFixedDecimal, IntoFixedFromDecimal}, + }; + + fn to_fixed(value: u128) -> Option { + value.to_fixed_from_fixed_decimal(DECIMALS).ok() + } + + /// Converts `Vec` of fixed decimal numbers to a `Vec` of fixed point numbers; + /// returns `None` if any of them fail. + fn vec_to_fixed(vec: Vec) -> Option> { + vec.into_iter().map(to_fixed).collect() + } + + fn from_fixed(value: FixedType) -> Option + where + B: Into + From, + { + value.to_fixed_decimal(DECIMALS).ok() + } + + /// Returns `\sum_{r \in R} e^{-r/b}`, where `R` denotes `reserves` and `b` denotes `liquidity`. + /// The result is `None` if and only if one of the `exp` calculations has failed. + fn exp_sum(reserves: Vec, liquidity: FixedType) -> Option { + reserves + .iter() + .map(|r| exp(r.checked_div(liquidity)?, true).ok()) + .collect::>>()? + .iter() + .try_fold(FixedType::zero(), |acc, &val| acc.checked_add(val)) + } + + pub(super) fn calculate_swap_amount_out_for_buy( + buy: Vec, + sell: Vec, + amount_in: u128, + liquidity: u128, + ) -> Option { + let result_fixed = calculate_swap_amount_out_for_buy_fixed( + vec_to_fixed(buy)?, + vec_to_fixed(sell)?, + to_fixed(amount_in)?, + to_fixed(liquidity)?, + )?; + from_fixed(result_fixed) + } + + pub(super) fn calculate_equalize_amount( + buy: Vec, + sell: Vec, + amount_buy: u128, + amount_sell: u128, + liquidity: u128, + ) -> Option { + let result_fixed = calculate_equalize_amount_fixed( + vec_to_fixed(buy)?, + vec_to_fixed(sell)?, + to_fixed(amount_buy)?, + to_fixed(amount_sell)?, + to_fixed(liquidity)?, + )?; + from_fixed(result_fixed) + } + + pub(super) fn calculate_swap_amount_out_for_sell( + buy: Vec, + keep: Vec, + sell: Vec, + amount_buy: u128, + amount_keep: u128, + liquidity: u128, + ) -> Option { + let result_fixed = calculate_swap_amount_out_for_sell_fixed( + vec_to_fixed(buy)?, + vec_to_fixed(keep)?, + vec_to_fixed(sell)?, + to_fixed(amount_buy)?, + to_fixed(amount_keep)?, + to_fixed(liquidity)?, + )?; + from_fixed(result_fixed) + } + + pub(super) fn calculate_spot_price( + buy: Vec, + sell: Vec, + liquidity: u128, + ) -> Option { + let result_fixed = calculate_spot_price_fixed( + vec_to_fixed(buy)?, + vec_to_fixed(sell)?, + to_fixed(liquidity)?, + )?; + from_fixed(result_fixed) + } + + fn calculate_swap_amount_out_for_buy_fixed( + buy: Vec, + sell: Vec, + amount_in: FixedType, + liquidity: FixedType, + ) -> Option { + if buy.is_empty() || sell.is_empty() || amount_in.is_zero() { + return None; + } + + let exp_sum_buy = exp_sum(buy, liquidity)?; + let exp_sum_sell = exp_sum(sell, liquidity)?; + let amount_in_div_liquidity = amount_in.checked_div(liquidity)?; + let exp_of_minus_amount_in: FixedType = exp(amount_in_div_liquidity, true).ok()?; + let exp_of_minus_amount_in_times_exp_sum_sell = + exp_of_minus_amount_in.checked_mul(exp_sum_sell)?; + let numerator = exp_sum_buy + .checked_add(exp_sum_sell)? + .checked_sub(exp_of_minus_amount_in_times_exp_sum_sell)?; + let ln_arg = numerator.checked_div(exp_sum_buy)?; + let (ln_val, _): (FixedType, _) = ln(ln_arg).ok()?; + ln_val.checked_mul(liquidity) + } + + fn calculate_equalize_amount_fixed( + buy: Vec, + sell: Vec, + amount_buy: FixedType, + amount_sell: FixedType, + liquidity: FixedType, + ) -> Option { + if buy.is_empty() || sell.is_empty() || amount_buy.is_zero() { + return None; + } + + let exp_sum_buy = exp_sum(buy, liquidity)?; + let exp_sum_sell = exp_sum(sell, liquidity)?; + let numerator = exp_sum_buy.checked_add(exp_sum_sell)?; + let delta = amount_buy.checked_sub(amount_sell)?; + let delta_div_liquidity = delta.checked_div(liquidity)?; + let exp_delta: FixedType = exp(delta_div_liquidity, false).ok()?; + let exp_delta_times_exp_sum_sell = exp_delta.checked_mul(exp_sum_sell)?; + let denominator = exp_sum_buy.checked_add(exp_delta_times_exp_sum_sell)?; + let ln_arg = numerator.checked_div(denominator)?; + let (ln_val, _): (FixedType, _) = ln(ln_arg).ok()?; + ln_val.checked_mul(liquidity) + } + + fn calculate_swap_amount_out_for_sell_fixed( + buy: Vec, + keep: Vec, + sell: Vec, + amount_buy: FixedType, + amount_keep: FixedType, + liquidity: FixedType, + ) -> Option { + // Ensure that either `keep` is empty and `amount_keep` is zero, or `keep` is non-empty and + // `amount_keep` is non-zero. + if keep.is_empty() && !amount_keep.is_zero() || !keep.is_empty() && amount_keep.is_zero() { + return None; + } + + // Reserves change after the first equalization. Since we do two equalization calculations + // in one, we need to determine the intermediate reserves for the second calculation. + let (amount_buy_keep, buy_keep) = if keep.is_empty() { + (amount_buy, buy) + } else { + let delta_buy = calculate_equalize_amount_fixed( + buy.clone(), + keep.clone(), + amount_buy, + amount_keep, + liquidity, + )?; + + let delta_keep = amount_buy.checked_sub(delta_buy)?.checked_sub(amount_keep)?; + + let buy_intermediate = + buy.into_iter().map(|x| x.checked_add(delta_buy)).collect::>>()?; + let keep_intermediate = + keep.into_iter().map(|x| x.checked_sub(delta_keep)).collect::>>()?; + let buy_keep = + buy_intermediate.into_iter().chain(keep_intermediate.into_iter()).collect(); + + (amount_buy.checked_sub(delta_buy)?, buy_keep) + }; + + let delta_buy_keep = calculate_equalize_amount_fixed( + buy_keep, + sell, + amount_buy_keep, + FixedType::zero(), + liquidity, + )?; + + amount_buy_keep.checked_sub(delta_buy_keep) + } + + fn calculate_spot_price_fixed( + buy: Vec, + sell: Vec, + liquidity: FixedType, + ) -> Option { + let exp_sum_buy = exp_sum(buy, liquidity)?; + let exp_sum_sell = exp_sum(sell, liquidity)?; + let denominator = exp_sum_buy.checked_add(exp_sum_sell)?; + exp_sum_buy.checked_div(denominator) + } +} + +#[cfg(test)] +mod tests { + // TODO(#1328): Remove after rustc nightly-2024-04-22 + #![allow(clippy::duplicated_attributes)] + + use super::*; + use crate::{mock::Runtime as MockRuntime, MAX_SPOT_PRICE, MIN_SPOT_PRICE}; + use alloc::str::FromStr; + use frame_support::assert_err; + use test_case::test_case; + use zeitgeist_primitives::constants::base_multiples::*; + + type MockBalance = BalanceOf; + type MockMath = ComboMath; + + // Example taken from + // https://docs.gnosis.io/conditionaltokens/docs/introduction3/#an-example-with-lmsr + #[test_case(vec![_10], vec![_10], _10, 144_269_504_088, 58_496_250_072)] + #[test_case(vec![_1], vec![4_586_751_453], _1, _1, 7_353_256_641)] + #[test_case(vec![_2], vec![9_173_502_907], _2, _2, 14_706_513_281; "positive ln")] + #[test_case(vec![_1], vec![37_819_608_145], _1_10, _3, 386_589_943; "negative ln")] + // Tests generated with Python. + #[test_case(vec![_100, _100], vec![_100], _10, 721_347_520_444, 45_240_236_913)] + #[test_case(vec![_100, _100, _100], vec![_100], _10, 721_347_520_444, 30_473_182_882)] + #[test_case(vec![_100, _100], vec![_100, _100], _10, 721_347_520_444, 87_809_842_736)] + #[test_case(vec![_100], vec![_100, _100, _100], _10, 721_347_520_444, 236_684_778_998)] + #[test_case( + vec![848_358_525_162, 482_990_395_533], + vec![730_736_259_258], + _10, + 527_114_788_714, + 36_648_762_089 + )] + #[test_case( + vec![848_358_525_162, _100, 482_990_395_533], + vec![730_736_259_258], + _10, + 527_114_788_714, + 29_520_025_573 + )] + #[test_case( + vec![848_358_525_162, 482_990_395_533, _100], + vec![730_736_259_258], + _10, + 527_114_788_714, + 29_520_025_573 + )] + #[test_case( + vec![848_358_525_162, 482_990_395_533], + vec![730_736_259_258, _100], + _10, + 527_114_788_714, + 57_474_148_073 + )] + #[test_case( + vec![482_990_395_533], + vec![730_736_259_258, _100, 848_358_525_162], + _10, + 527_114_788_714, + 121_489_297_813 + )] + #[test_case( + vec![848_358_525_162, 482_990_395_533], + vec![730_736_259_258, _100], + 1_00, + 527_114_788_714, + 67 + )] + #[test_case( + vec![848_358_525_162, 482_990_395_533], + vec![730_736_259_258, _100], + 1, + 527_114_788_714, + 1 + )] + fn calculate_swap_amount_out_for_buy_works( + buy: Vec, + sell: Vec, + amount_in: MockBalance, + liquidity: MockBalance, + expected: MockBalance, + ) { + assert_eq!( + MockMath::calculate_swap_amount_out_for_buy(buy, sell, amount_in, liquidity).unwrap(), + expected + ); + } + + #[test_case(vec![_1], vec![_1], _1, 0)] // Division by zero + #[test_case(vec![_1], vec![_1], 1_000 * _1, _1)] // Overflow + #[test_case(vec![u128::MAX], vec![_1], _1, _1)] // to_fixed error + #[test_case(vec![_1], vec![u128::MAX], _1, _1)] // to_fixed error + #[test_case(vec![_1], vec![_1], u128::MAX, _1)] // to_fixed error + #[test_case(vec![_1], vec![_1], _1, u128::MAX)] // to_fixed error + #[test_case(vec![], vec![_1], _1, _1)] // empty vector + #[test_case(vec![_1], vec![], _1, _1)] // empty vector + #[test_case(vec![_1], vec![_1], 0, _1)] // zero value + fn calculate_swap_amount_out_for_buy_throws_math_error( + buy: Vec, + sell: Vec, + amount_in: MockBalance, + liquidity: MockBalance, + ) { + assert_err!( + MockMath::calculate_swap_amount_out_for_buy(buy, sell, amount_in, liquidity), + Error::::MathError + ); + } + + // "Reversing" the tests for `calculate_swap_amount_for_buy`. + #[test_case(vec![_11], vec![_12], _10, _10, 144_269_504_088, 0)] + #[test_case( + vec![_10 - 58_496_250_072], + vec![_20], + _10 + 58_496_250_072, + 0, + 144_269_504_088, + 58_496_250_072 + )] + #[test_case( + vec![_1 - 7_353_256_641], + vec![14_586_751_453], + 17_353_256_641, + 0, + _1, + 7_353_256_641 + )] + #[test_case( + vec![_2 - 14_706_513_281], + vec![_2 + 9_173_502_907], + _2 + 14_706_513_281, + 0, + _2, + 14_706_513_281; + "positive ln" + )] + #[test_case( + vec![_1 - 386_589_943], + vec![37_819_608_145 + _1_10], + _1_10 + 386_589_943, + 0, + _3, + 386_589_943; + "negative ln" + )] + // Tests generated with Python + #[test_case( + vec![537_243_573_680, 305_865_360_520], + vec![462_756_426_319], + 76_500_000_000, + 43_200_000_000, + 333_808_200_695, + 10_143_603_301 + )] + #[test_case( + vec![537_243_573_680, 305_865_360_520, 768_621_786_840], + vec![462_756_426_319], + 232_000_000_000, + 112_300_000_000, + 333_808_200_695, + 35_887_802_365 + )] + #[test_case( + vec![537_243_573_680, 305_865_360_520], + vec![462_756_426_319, _100], + _10, + _5, + 333_808_200_695, + 17_512_119_761 + )] + #[test_case( + vec![537_243_573_680, 305_865_360_520], + vec![_100, 462_756_426_319], + _10, + _5, + 333_808_200_695, + 17_512_119_761 + )] + #[test_case( + vec![305_865_360_520, 537_243_573_680], + vec![462_756_426_319, _100], + _10, + _5, + 333_808_200_695, + 17_512_119_761 + )] + #[test_case( + vec![305_865_360_520, 537_243_573_680], + vec![_100, 462_756_426_319], + _10, + _5, + 333_808_200_695, + 17_512_119_761 + )] + #[test_case( + vec![305_865_360_520, 537_243_573_680], + vec![_100, 462_756_426_319], + _10, + 100, + 333_808_200_695, + 36_763_618_626 + )] + #[test_case( + vec![305_865_360_520, 537_243_573_680], + vec![_100, 462_756_426_319], + _10, + 1, + 333_808_200_695, + 36_763_618_666 + )] + #[test_case( + vec![305_865_360_520, 537_243_573_680], + vec![_100, 462_756_426_319], + 2, + 1, + 333_808_200_695, + 0 + )] + #[test_case( + vec![305_865_360_520, 537_243_573_680], + vec![_100, 462_756_426_319], + 1, + 0, + 333_808_200_695, + 0 + )] + fn calculate_equalize_amount_works( + buy: Vec, + sell: Vec, + amount_buy: MockBalance, + amount_sell: MockBalance, + liquidity: MockBalance, + expected: MockBalance, + ) { + assert_eq!( + MockMath::calculate_equalize_amount(buy, sell, amount_buy, amount_sell, liquidity) + .unwrap(), + expected + ); + } + + #[test_case(vec![_1], vec![_1], _1, _1, 0)] // Division by zero + #[test_case(vec![_1], vec![_1], 1_000 * _1, _1, _1)] // Overflow + #[test_case(vec![_1], vec![_1], _1, 1_000 * _1, _1)] // Overflow + #[test_case(vec![u128::MAX], vec![_1], _1, _1, _1)] // to_fixed error + #[test_case(vec![_1], vec![u128::MAX], _1, _1, _1)] // to_fixed error + #[test_case(vec![_1], vec![_1], u128::MAX, _1, _1)] // to_fixed error + #[test_case(vec![_1], vec![_1], _1, u128::MAX, _1)] // to_fixed error + #[test_case(vec![_1], vec![_1], _1, _1, u128::MAX)] // to_fixed error + #[test_case(vec![], vec![_1], _1, _1, _1)] // empty vector + #[test_case(vec![_1], vec![], _1, _1, _1)] // empty vector + #[test_case(vec![_1], vec![_1], 0, _1, _1)] // zero value + fn calculate_equalize_amount_throws_error( + buy: Vec, + sell: Vec, + amount_buy: MockBalance, + amount_sell: MockBalance, + liquidity: MockBalance, + ) { + assert_err!( + MockMath::calculate_equalize_amount(buy, sell, amount_buy, amount_sell, liquidity), + Error::::MathError + ); + } + + // Tests for `calculate_equalize`. + #[test_case( + vec![_10 - 58_496_250_072], + vec![], + vec![_20], + _10 + 58_496_250_072, + 0, + 144_269_504_088, + _10 + )] + #[test_case( + vec![_1 - 7_353_256_641], + vec![], + vec![14_586_751_453], + 17_353_256_641, + 0, + _1, + _1 + )] + #[test_case( + vec![_2 - 14_706_513_281], + vec![], + vec![_2 + 9_173_502_907], + _2 + 14_706_513_281, + 0, + _2, + _2; + "positive ln" + )] + #[test_case( + vec![_1 - 386_589_943], + vec![], + vec![37_819_608_145 + _1_10], + _1_10 + 386_589_943, + 0, + _3, + _1_10; + "negative ln" + )] + // Tests generated by Python. + #[test_case( + vec![_100, 305_865_360_520], + vec![768_621_786_840, _100, 768_621_786_840, _100], + vec![462_756_426_319], + 76_500_000_000, + 43_200_000_000, + 333_808_200_695, + 45_943_057_520 + )] + #[test_case( + vec![_100, 305_865_360_520], + vec![768_621_786_840, _100, 768_621_786_840, _100], + vec![462_756_426_319], + _2, + _1, + 333_808_200_695, + 11_900_842_524 + )] + #[test_case( + vec![_100, 305_865_360_520, 768_621_786_840], + vec![_100, 768_621_786_840], + vec![462_756_426_319, _100], + 123_400_000_000, + _1, + 333_808_200_695, + 63_972_215_306 + )] + #[test_case( + vec![_100, 305_865_360_520, 768_621_786_840], + vec![_100], + vec![462_756_426_319, _100, 768_621_786_840], + 123_400_000_000, + 1, + 333_808_200_695, + 62_187_083_257 + )] + #[test_case( + vec![_100, 305_865_360_520, 768_621_786_840], + vec![_100], + vec![462_756_426_319, _100, 768_621_786_840], + 2, + 1, + 333_808_200_695, + 1 + )] + #[test_case( + vec![_100, 305_865_360_520, 768_621_786_840], + vec![], + vec![462_756_426_319, _100, 768_621_786_840, _100], + 123_400_000_000, + 0, + 333_808_200_695, + 62_187_083_257 + )] + fn calculate_swap_amount_out_for_sell_works( + buy: Vec, + keep: Vec, + sell: Vec, + amount_buy: MockBalance, + amount_sell: MockBalance, + liquidity: MockBalance, + expected: MockBalance, + ) { + assert_eq!( + MockMath::calculate_swap_amount_out_for_sell( + buy, + keep, + sell, + amount_buy, + amount_sell, + liquidity + ) + .unwrap(), + expected + ); + } + + #[test_case(vec![_1], vec![_1], vec![_1], _1, _1, 0)] // Division by zero + #[test_case(vec![_1], vec![_1], vec![_1], 1_000 * _1, _1, _1)] // Overflow + #[test_case(vec![_1], vec![_1], vec![_1], _1, 1_000 * _1, _1)] // Overflow + #[test_case(vec![u128::MAX], vec![_1], vec![_1], _1, _1, _1)] // to_fixed error + #[test_case(vec![_1], vec![u128::MAX], vec![_1], _1, _1, _1)] // to_fixed error + #[test_case(vec![_1], vec![_1], vec![u128::MAX], u128::MAX, _1, _1)] // to_fixed error + #[test_case(vec![_1], vec![_1], vec![_1], _1, u128::MAX, _1)] // to_fixed error + #[test_case(vec![_1], vec![_1], vec![_1], _1, _1, u128::MAX)] // to_fixed error + #[test_case(vec![], vec![_1], vec![_1], _1, _1, _1)] // empty vector + #[test_case(vec![_1], vec![_1], vec![], _1, _1, _1)] // empty vector + #[test_case(vec![_1], vec![], vec![_1], _1, _1, _1)] // empty vector + #[test_case(vec![_1], vec![_1], vec![_1], 0, _1, _1)] // zero value + #[test_case(vec![_1], vec![_1], vec![_1], _1, 0, _1)] // zero value + fn calculate_swap_amount_out_for_sell_throws_error( + buy: Vec, + keep: Vec, + sell: Vec, + amount_buy: MockBalance, + amount_keep: MockBalance, + liquidity: MockBalance, + ) { + assert_err!( + MockMath::calculate_swap_amount_out_for_sell( + buy, + keep, + sell, + amount_buy, + amount_keep, + liquidity + ), + Error::::MathError + ); + } + + #[test_case(vec![_10], vec![_10], 144_269_504_088, _1_2)] + #[test_case(vec![_10 - 58_496_250_072], vec![_20], 144_269_504_088, _3_4)] + #[test_case(vec![_20], vec![_10 - 58_496_250_072], 144_269_504_088, _1_4)] + fn calcuate_spot_price_works( + buy: Vec, + sell: Vec, + liquidity: MockBalance, + expected: MockBalance, + ) { + assert_eq!(MockMath::calculate_spot_price(buy, sell, liquidity).unwrap(), expected); + } + + #[test_case(vec![_1], vec![_1], 0)] // Division by zero + #[test_case(vec![1_000 * _1], vec![_1], _1)] // Overflow + #[test_case(vec![_1], vec![1_000 * _1], _1)] // Overflow + #[test_case(vec![u128::MAX], vec![_1], _1)] // to_fixed error + #[test_case(vec![_1], vec![u128::MAX], _1)] // to_fixed error + #[test_case(vec![_1], vec![_1], u128::MAX)] // to_fixed error + fn calculate_spot_price_throws_math_error( + buy: Vec, + sell: Vec, + liquidity: MockBalance, + ) { + assert_err!( + MockMath::calculate_spot_price(buy, sell, liquidity), + Error::::MathError + ); + } +} diff --git a/zrml/neo-swaps/src/math.rs b/zrml/neo-swaps/src/math/types/math.rs similarity index 78% rename from zrml/neo-swaps/src/math.rs rename to zrml/neo-swaps/src/math/types/math.rs index 401dfb8be..8b311e28f 100644 --- a/zrml/neo-swaps/src/math.rs +++ b/zrml/neo-swaps/src/math/types/math.rs @@ -14,31 +14,12 @@ // // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . -// -// This file incorporates work covered by the following copyright and -// permission notice: -// -// Copyright (c) 2019 Alain Brenzikofer, modified by GalacticCouncil(2021) -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// Original source: https://github.com/encointer/substrate-fixed -// -// The changes applied are: Re-used and extended tests for `exp` and other -// functions. use crate::{ - math::transcendental::{exp, ln}, + math::{ + traits::MathOps, + transcendental::{exp, ln}, + }, BalanceOf, Config, Error, }; use alloc::vec::Vec; @@ -56,51 +37,12 @@ type FixedType = FixedU128; // 32.44892769177272 const EXP_OVERFLOW_THRESHOLD: FixedType = FixedType::from_bits(0x20_72EC_ECDA_6EBE_EACC_40C7); -pub(crate) trait MathOps { - fn calculate_swap_amount_out_for_buy( - reserve: BalanceOf, - amount_in: BalanceOf, - liquidity: BalanceOf, - ) -> Result, DispatchError>; - - fn calculate_swap_amount_out_for_sell( - reserve: BalanceOf, - amount_in: BalanceOf, - liquidity: BalanceOf, - ) -> Result, DispatchError>; - - fn calculate_spot_price( - reserve: BalanceOf, - liquidity: BalanceOf, - ) -> Result, DispatchError>; - - fn calculate_reserves_from_spot_prices( - amount: BalanceOf, - spot_prices: Vec>, - ) -> Result<(BalanceOf, Vec>), DispatchError>; - - fn calculate_buy_ln_argument( - reserve: BalanceOf, - amount: BalanceOf, - liquidity: BalanceOf, - ) -> Result, DispatchError>; - - fn calculate_buy_amount_until( - until: BalanceOf, - liquidity: BalanceOf, - spot_price: BalanceOf, - ) -> Result, DispatchError>; - - fn calculate_sell_amount_until( - until: BalanceOf, - liquidity: BalanceOf, - spot_price: BalanceOf, - ) -> Result, DispatchError>; -} - pub(crate) struct Math(PhantomData); -impl MathOps for Math { +impl MathOps for Math +where + T: Config, +{ fn calculate_swap_amount_out_for_buy( reserve: BalanceOf, amount_in: BalanceOf, @@ -418,91 +360,6 @@ mod detail { } } -mod transcendental { - pub(crate) use hydra_dx_math::transcendental::{exp, ln}; - - #[cfg(test)] - mod tests { - - use super::*; - use alloc::str::FromStr; - use fixed::types::U64F64; - use test_case::test_case; - - type S = U64F64; - type D = U64F64; - - #[test_case("0", false, "1")] - #[test_case("0", true, "1")] - #[test_case("1", false, "2.7182818284590452353")] - #[test_case("1", true, "0.367879441171442321595523770161460867445")] - #[test_case("2", false, "7.3890560989306502265")] - #[test_case("2", true, "0.13533528323661269186")] - #[test_case("0.1", false, "1.1051709180756476246")] - #[test_case("0.1", true, "0.9048374180359595733")] - #[test_case("0.9", false, "2.4596031111569496633")] - #[test_case("0.9", true, "0.40656965974059911195")] - #[test_case("1.5", false, "4.481689070338064822")] - #[test_case("1.5", true, "0.22313016014842982894")] - #[test_case("3.3", false, "27.1126389206578874259")] - #[test_case("3.3", true, "0.03688316740124000543")] - #[test_case("7.3456", false, "1549.3643050275008503592")] - #[test_case("7.3456", true, "0.00064542599616831253")] - #[test_case("12.3456789", false, "229964.194569082134542849")] - #[test_case("12.3456789", true, "0.00000434850304358833")] - #[test_case("13", false, "442413.39200892050332603603")] - #[test_case("13", true, "0.0000022603294069810542")] - fn exp_works(operand: &str, neg: bool, expected: &str) { - let o = U64F64::from_str(operand).unwrap(); - let e = U64F64::from_str(expected).unwrap(); - assert_eq!(exp::(o, neg).unwrap(), e); - } - - #[test_case("1", "0", false)] - #[test_case("2", "0.69314718055994530943", false)] - #[test_case("3", "1.09861228866810969136", false)] - #[test_case("2.718281828459045235360287471352662497757", "1", false)] - #[test_case("1.1051709180756476246", "0.09999999999999999975", false)] - #[test_case("2.4596031111569496633", "0.89999999999999999976", false)] - #[test_case("4.481689070338064822", "1.49999999999999999984", false)] - #[test_case("27.1126389206578874261", "3.3", false)] - #[test_case("1549.3643050275008503592", "7.34560000000000000003", false)] - #[test_case("229964.194569082134542849", "12.3456789000000000002", false)] - #[test_case("442413.39200892050332603603", "13.0000000000000000002", false)] - #[test_case("0.9048374180359595733", "0.09999999999999999975", true)] - #[test_case("0.40656965974059911195", "0.8999999999999999998", true)] - #[test_case("0.22313016014842982894", "1.4999999999999999999", true)] - #[test_case("0.03688316740124000543", "3.3000000000000000005", true)] - #[test_case("0.00064542599616831253", "7.34560000000000002453", true)] - #[test_case("0.00000434850304358833", "12.34567890000000711117", true)] - #[test_case("0.0000022603294069810542", "13.0000000000000045352", true)] - #[test_case("1.0001", "0.00009999500033330827", false)] - #[test_case("1.00000001", "0.0000000099999999499", false)] - #[test_case("0.9999", "0.00010000500033335825", true)] - #[test_case("0.99999999", "0.00000001000000004987", true)] - // Powers of 2 (since we're using squares when calculating the fractional part of log2. - #[test_case("3.999999999", "1.38629436086989061877", false)] - #[test_case("4", "1.38629436111989061886", false)] - #[test_case("4.000000001", "1.3862943613698906188", false)] - #[test_case("7.999999999", "2.07944154155483592824", false)] - #[test_case("8", "2.0794415416798359283", false)] - #[test_case("8.000000001", "2.0794415418048359282", false)] - #[test_case("0.499999999", "0.69314718255994531136", true)] - #[test_case("0.5", "0.69314718055994530943", true)] - #[test_case("0.500000001", "0.69314717855994531135", true)] - #[test_case("0.249999999", "1.38629436511989062684", true)] - #[test_case("0.25", "1.38629436111989061886", true)] - #[test_case("0.250000001", "1.38629435711989062676", true)] - fn ln_works(operand: &str, expected_abs: &str, expected_neg: bool) { - let o = U64F64::from_str(operand).unwrap(); - let e = U64F64::from_str(expected_abs).unwrap(); - let (a, n) = ln::(o).unwrap(); - assert_eq!(a, e); - assert_eq!(n, expected_neg); - } - } -} - #[cfg(test)] mod tests { // TODO(#1328): Remove after rustc nightly-2024-04-22 diff --git a/zrml/neo-swaps/src/math/types/mod.rs b/zrml/neo-swaps/src/math/types/mod.rs new file mode 100644 index 000000000..43275d80f --- /dev/null +++ b/zrml/neo-swaps/src/math/types/mod.rs @@ -0,0 +1,5 @@ +mod combo_math; +mod math; + +pub(crate) use combo_math::ComboMath; +pub(crate) use math::Math; diff --git a/zrml/neo-swaps/src/types/pool.rs b/zrml/neo-swaps/src/types/pool.rs index 401a51d39..61e490662 100644 --- a/zrml/neo-swaps/src/types/pool.rs +++ b/zrml/neo-swaps/src/types/pool.rs @@ -17,7 +17,7 @@ use crate::{ consts::EXP_NUMERICAL_LIMIT, - math::{Math, MathOps}, + math::{traits::MathOps, types::Math}, pallet::{AssetOf, BalanceOf, Config}, traits::{LiquiditySharesManager, PoolOperations}, Error, From c0a0598b925a063110763e72e928980f7c19db15 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Tue, 17 Sep 2024 13:41:12 +0200 Subject: [PATCH 02/73] Implement combinatorial betting extrinsics (#1365) * Implement classical buying using combinatorial buys * Calculate classical sells with combinatorial math * Implement `combo_buy` * Implement `combo_sell` --- zrml/neo-swaps/src/lib.rs | 263 +++++++++++++++- zrml/neo-swaps/src/tests/buy_and_sell.rs | 8 +- zrml/neo-swaps/src/tests/combo_buy.rs | 295 ++++++++++++++++++ zrml/neo-swaps/src/tests/combo_sell.rs | 309 +++++++++++++++++++ zrml/neo-swaps/src/tests/mod.rs | 2 + zrml/neo-swaps/src/traits/pool_operations.rs | 38 ++- zrml/neo-swaps/src/types/pool.rs | 48 ++- 7 files changed, 929 insertions(+), 34 deletions(-) create mode 100644 zrml/neo-swaps/src/tests/combo_buy.rs create mode 100644 zrml/neo-swaps/src/tests/combo_sell.rs diff --git a/zrml/neo-swaps/src/lib.rs b/zrml/neo-swaps/src/lib.rs index 03346909a..51cdb0f57 100644 --- a/zrml/neo-swaps/src/lib.rs +++ b/zrml/neo-swaps/src/lib.rs @@ -213,6 +213,30 @@ mod pallet { market_id: MarketIdOf, amounts_out: Vec>, }, + /// A combinatorial position was opened. + ComboBuyExecuted { + who: AccountIdOf, + market_id: MarketIdOf, + buy: Vec>, + sell: Vec>, + amount_in: BalanceOf, + amount_out: BalanceOf, + swap_fee_amount: BalanceOf, + external_fee_amount: BalanceOf, + }, + /// A combinatorial position was closed. + ComboSellExecuted { + who: AccountIdOf, + market_id: MarketIdOf, + buy: Vec>, + keep: Vec>, + sell: Vec>, + amount_buy: BalanceOf, + amount_keep: BalanceOf, + amount_out: BalanceOf, + swap_fee_amount: BalanceOf, + external_fee_amount: BalanceOf, + }, } #[pallet::error] @@ -540,6 +564,55 @@ mod pallet { Self::do_deploy_pool(who, market_id, amount, spot_prices, swap_fee)?; Ok(Some(T::WeightInfo::deploy_pool(spot_prices_len)).into()) } + + #[pallet::call_index(6)] + #[pallet::weight(T::WeightInfo::buy((*asset_count).saturated_into()))] // TODO + #[transactional] + pub fn combo_buy( + origin: OriginFor, + #[pallet::compact] market_id: MarketIdOf, + asset_count: AssetIndexType, + buy: Vec>, + sell: Vec>, + #[pallet::compact] amount_in: BalanceOf, + #[pallet::compact] min_amount_out: BalanceOf, + ) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + let asset_count_real = T::MarketCommons::market(&market_id)?.outcomes(); + ensure!(asset_count == asset_count_real, Error::::IncorrectAssetCount); + Self::do_combo_buy(who, market_id, buy, sell, amount_in, min_amount_out)?; + Ok(Some(T::WeightInfo::buy(asset_count.into())).into()) // TODO + } + + #[pallet::call_index(7)] + #[pallet::weight(T::WeightInfo::buy((*asset_count).saturated_into()))] // TODO + #[transactional] + pub fn combo_sell( + origin: OriginFor, + #[pallet::compact] market_id: MarketIdOf, + asset_count: AssetIndexType, + buy: Vec>, + keep: Vec>, + sell: Vec>, + #[pallet::compact] amount_buy: BalanceOf, + #[pallet::compact] amount_keep: BalanceOf, + #[pallet::compact] min_amount_out: BalanceOf, + ) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + let asset_count_real = T::MarketCommons::market(&market_id)?.outcomes(); + ensure!(asset_count == asset_count_real, Error::::IncorrectAssetCount); + Self::do_combo_sell( + who, + market_id, + buy, + keep, + sell, + amount_buy, + amount_keep, + min_amount_out, + )?; + Ok(Some(T::WeightInfo::buy(asset_count.into())).into()) // TODO + } } impl Pallet { @@ -561,7 +634,7 @@ mod pallet { remaining: amount_in_minus_fees, swap_fees: swap_fee_amount, external_fees: external_fee_amount, - } = Self::distribute_fees(market_id, pool, amount_in)?; + } = Self::distribute_fees(market_id, pool, &pool.account_id.clone(), amount_in)?; ensure!( amount_in_minus_fees <= pool.calculate_numerical_threshold(), Error::::NumericalLimits(NumericalLimitsError::MaxAmountExceeded), @@ -571,8 +644,10 @@ mod pallet { >= LN_NUMERICAL_LIMIT.saturated_into(), Error::::NumericalLimits(NumericalLimitsError::MinAmountNotMet), ); + let buy = vec![asset_out]; + let sell = pool.assets_complement(&buy); let swap_amount_out = - pool.calculate_swap_amount_out_for_buy(asset_out, amount_in_minus_fees)?; + pool.calculate_swap_amount_out_for_buy(buy, sell, amount_in_minus_fees)?; let amount_out = swap_amount_out.checked_add_res(&amount_in_minus_fees)?; ensure!(amount_out >= min_amount_out, Error::::AmountOutBelowMin); // Instead of letting `who` buy the complete sets and then transfer almost all of @@ -626,14 +701,24 @@ mod pallet { amount_in <= pool.calculate_numerical_threshold(), Error::::NumericalLimits(NumericalLimitsError::MaxAmountExceeded), ); + + let buy = vec![asset_in]; + let keep = vec![]; + let sell = pool.assets_complement(&buy); + let amount_out = pool.calculate_swap_amount_out_for_sell( + buy, + keep, + sell, + amount_in, + Zero::zero(), + )?; + // Instead of first executing a swap with `(n-1)` transfers from the pool account to // `who` and then selling complete sets, we prevent `(n-1)` storage reads: 1) // Transfer `amount_in` units of `asset_in` to the pool account, 2) sell // `amount_out` complete sets using the pool account, 3) transfer // `amount_out_minus_fees` units of collateral to `who`. The fees automatically end // up in the pool. - let amount_out = pool.calculate_swap_amount_out_for_sell(asset_in, amount_in)?; - // Beware! This transfer **must** happen _after_ calculating `amount_out`: T::MultiCurrency::transfer(asset_in, &who, &pool.account_id, amount_in)?; T::CompleteSetOperations::sell_complete_set( pool.account_id.clone(), @@ -644,7 +729,7 @@ mod pallet { remaining: amount_out_minus_fees, swap_fees: swap_fee_amount, external_fees: external_fee_amount, - } = Self::distribute_fees(market_id, pool, amount_out)?; + } = Self::distribute_fees(market_id, pool, &pool.account_id.clone(), amount_out)?; ensure!(amount_out_minus_fees >= min_amount_out, Error::::AmountOutBelowMin); T::MultiCurrency::transfer( pool.collateral, @@ -924,6 +1009,169 @@ mod pallet { Ok(()) } + #[require_transactional] + fn do_combo_buy( + who: T::AccountId, + market_id: MarketIdOf, + // TODO Replace `buy`/`keep`/`sell` with a struct. + buy: Vec>, + sell: Vec>, + amount_in: BalanceOf, + min_amount_out: BalanceOf, + ) -> DispatchResult { + ensure!(amount_in != Zero::zero(), Error::::ZeroAmount); + let market = T::MarketCommons::market(&market_id)?; + ensure!(market.status == MarketStatus::Active, Error::::MarketNotActive); + Self::try_mutate_pool(&market_id, |pool| { + for asset in buy.iter().chain(sell.iter()) { + ensure!(pool.contains(&asset), Error::::AssetNotFound); + } + + // TODO Ensure that buy, sell partition the assets! + + // TODO Ensure that numerical limits are observed. + + let FeeDistribution { + remaining: amount_in_minus_fees, + swap_fees: swap_fee_amount, + external_fees: external_fee_amount, + } = Self::distribute_fees(market_id, pool, &who, amount_in)?; + let swap_amount_out = pool.calculate_swap_amount_out_for_buy( + buy.clone(), + sell.clone(), + amount_in_minus_fees, + )?; + let amount_out = swap_amount_out.checked_add_res(&amount_in_minus_fees)?; + ensure!(amount_out >= min_amount_out, Error::::AmountOutBelowMin); + + T::CompleteSetOperations::buy_complete_set( + who.clone(), + market_id, + amount_in_minus_fees, + )?; + + for &asset in buy.iter() { + T::MultiCurrency::transfer(asset, &pool.account_id, &who, swap_amount_out)?; + pool.decrease_reserve(&asset, &swap_amount_out)?; + } + for &asset in sell.iter() { + T::MultiCurrency::transfer( + asset, + &who, + &pool.account_id, + amount_in_minus_fees, + )?; + pool.increase_reserve(&asset, &amount_in_minus_fees)?; + } + + Self::deposit_event(Event::::ComboBuyExecuted { + who: who.clone(), + market_id, + buy: buy.clone(), + sell: sell.clone(), + amount_in, + amount_out, + swap_fee_amount, + external_fee_amount, + }); + + Ok(()) + }) + } + + #[require_transactional] + fn do_combo_sell( + who: T::AccountId, + market_id: MarketIdOf, + // TODO Replace `buy`/`keep`/`sell` with a struct. + buy: Vec>, + keep: Vec>, + sell: Vec>, + amount_buy: BalanceOf, + amount_keep: BalanceOf, + min_amount_out: BalanceOf, + ) -> DispatchResult { + ensure!(amount_buy != Zero::zero(), Error::::ZeroAmount); + let market = T::MarketCommons::market(&market_id)?; + ensure!(market.status == MarketStatus::Active, Error::::MarketNotActive); + Self::try_mutate_pool(&market_id, |pool| { + for asset in buy.iter().chain(sell.iter()).chain(keep.iter()) { + ensure!(pool.contains(&asset), Error::::AssetNotFound); + } + + // TODO Ensure that buy, sell partition the assets! + + // TODO Ensure that numerical limits are observed. + + // This is the amount of collateral the user will receive in the end, or, + // equivalently, the amount of each asset in `sell` that the user intermittently + // receives from the pool (before selling complete sets). + let amount_out = pool.calculate_swap_amount_out_for_sell( + buy.clone(), + keep.clone(), + sell.clone(), + amount_buy, + amount_keep, + )?; + ensure!(amount_out >= min_amount_out, Error::::AmountOutBelowMin); + + // The deal is that the user gives up all of the assets specified in the function + // parameters and receives `amount_out` (minus fees) units of collateral. To create + // the collateral, the pool has to call `sell_complete_set`. This approach is more + // stable than letting the user call `sell_complete_set` after equalizing their + // assets, as doing so may lead to `sell_complete_set` failing due to rounding + // errors. + + for &asset in buy.iter() { + T::MultiCurrency::transfer(asset, &who, &pool.account_id, amount_buy)?; + pool.increase_reserve(&asset, &amount_buy)?; + } + + for &asset in keep.iter() { + T::MultiCurrency::transfer(asset, &pool.account_id, &who, amount_keep)?; + pool.increase_reserve(&asset, &amount_keep)?; + } + + T::CompleteSetOperations::sell_complete_set( + pool.account_id.clone(), + market_id, + amount_out, + )?; + + for &asset in pool.assets().iter() { + pool.decrease_reserve(&asset, &amount_out)?; + } + + let FeeDistribution { + remaining: amount_out_minus_fees, + swap_fees: swap_fee_amount, + external_fees: external_fee_amount, + } = Self::distribute_fees(market_id, pool, &pool.account_id.clone(), amount_out)?; + + T::MultiCurrency::transfer( + pool.collateral, + &pool.account_id, + &who, + amount_out_minus_fees, + ); + + Self::deposit_event(Event::::ComboSellExecuted { + who: who.clone(), + market_id, + buy: buy.clone(), + keep: keep.clone(), + sell: sell.clone(), + amount_buy, + amount_keep, + amount_out: amount_out_minus_fees, + swap_fee_amount, + external_fee_amount, + }); + + Ok(()) + }) + } + #[inline] pub(crate) fn pool_account_id(market_id: &MarketIdOf) -> T::AccountId { T::PalletId::get().into_sub_account_truncating((*market_id).saturated_into::()) @@ -935,6 +1183,7 @@ mod pallet { /// /// - `market_id`: The ID of the market to which the pool belongs. /// - `pool`: The pool on which the trade was executed. + /// - `account`: The account that the fee is deducted from. /// - `amount`: The gross amount from which the fee is deduced. /// /// Will fail if the total amount of fees is more than the gross amount. In particular, the @@ -943,12 +1192,14 @@ mod pallet { fn distribute_fees( market_id: MarketIdOf, pool: &mut PoolOf, + account: &AccountIdOf, amount: BalanceOf, ) -> Result, DispatchError> { let swap_fees = pool.swap_fee.bmul(amount)?; + T::MultiCurrency::transfer(pool.collateral, &account, &pool.account_id, swap_fees)?; pool.liquidity_shares_manager.deposit_fees(swap_fees)?; // Should only error unexpectedly! let external_fees = - T::ExternalFees::distribute(market_id, pool.collateral, &pool.account_id, amount); + T::ExternalFees::distribute(market_id, pool.collateral, account, amount); let total_fees = external_fees.saturating_add(swap_fees); let remaining = amount.checked_sub(&total_fees).ok_or(Error::::Unexpected)?; Ok(FeeDistribution { remaining, swap_fees, external_fees }) diff --git a/zrml/neo-swaps/src/tests/buy_and_sell.rs b/zrml/neo-swaps/src/tests/buy_and_sell.rs index cce3d02a7..3d29969a3 100644 --- a/zrml/neo-swaps/src/tests/buy_and_sell.rs +++ b/zrml/neo-swaps/src/tests/buy_and_sell.rs @@ -61,7 +61,7 @@ fn buy_and_sell() { )); assert_pool_state!( market_id, - vec![1_807_876_540_789, 113_931_597_104, 1_976_969_097_720], + vec![1_807_876_540_789, 113_931_597_105, 1_976_969_097_720], [815_736_444, 8_538_986_828, 645_276_728], 721_347_520_444, create_b_tree_map!({ ALICE => _100 }), @@ -78,7 +78,7 @@ fn buy_and_sell() { )); assert_pool_state!( market_id, - vec![76_875_275, 6_650_531_597_104, 8_513_569_097_720], + vec![76_875_276, 6_650_531_597_105, 8_513_569_097_720], [9_998_934_339, 990_789, 74_872], 721_347_520_444, create_b_tree_map!({ ALICE => _100 }), @@ -108,7 +108,7 @@ fn buy_and_sell() { )); assert_pool_state!( market_id, - vec![77_948_356, 6_640_532_670_185, 8_503_570_170_801], + vec![77_948_357, 6_640_532_670_186, 8_503_570_170_801], [9_998_919_465, 1_004_618, 75_917], 721_347_520_444, create_b_tree_map!({ ALICE => _100 }), @@ -165,7 +165,7 @@ fn buy_and_sell() { )); assert_pool_state!( market_id, - vec![980_077_948_356, 7_620_532_670_185, 214_308_675_476], + vec![980_077_948_357, 7_620_532_670_186, 214_308_675_477], [2_570_006_838, 258_215, 7_429_734_946], 721_347_520_444, create_b_tree_map!({ ALICE => _100 }), diff --git a/zrml/neo-swaps/src/tests/combo_buy.rs b/zrml/neo-swaps/src/tests/combo_buy.rs new file mode 100644 index 000000000..c589a6aff --- /dev/null +++ b/zrml/neo-swaps/src/tests/combo_buy.rs @@ -0,0 +1,295 @@ +// Copyright 2023-2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use super::*; +#[cfg(not(feature = "parachain"))] +use sp_runtime::{DispatchError, TokenError}; +use test_case::test_case; + +// Example taken from +// https://docs.gnosis.io/conditionaltokens/docs/introduction3/#an-example-with-lmsr +#[test] +fn combo_buy_works() { + ExtBuilder::default().build().execute_with(|| { + let liquidity = _10; + let spot_prices = vec![_1_2, _1_2]; + let swap_fee = CENT; + let market_id = create_market_and_deploy_pool( + ALICE, + BASE_ASSET, + MarketType::Categorical(2), + liquidity, + spot_prices.clone(), + swap_fee, + ); + let pool = Pools::::get(market_id).unwrap(); + let total_fee_percentage = swap_fee + EXTERNAL_FEES; + let amount_in_minus_fees = _10; + let amount_in = amount_in_minus_fees.bdiv(_1 - total_fee_percentage).unwrap(); // This is exactly _10 after deducting fees. + let expected_fees = amount_in - amount_in_minus_fees; + let expected_swap_fee_amount = expected_fees / 2; + let expected_external_fee_amount = expected_fees / 2; + let pool_outcomes_before: Vec<_> = + pool.assets().iter().map(|a| pool.reserve_of(a).unwrap()).collect(); + let liquidity_parameter_before = pool.liquidity_parameter; + let buy = vec![pool.assets()[0]]; + let sell = pool.assets_complement(&buy); + assert_ok!(AssetManager::deposit(BASE_ASSET, &BOB, amount_in)); + println!("{}", AssetManager::free_balance(BASE_ASSET, &BOB)); + // Deposit some stuff in the pool account to check that the pools `reserves` fields tracks + // the reserve correctly. + assert_ok!(AssetManager::deposit(sell[0], &pool.account_id, _100)); + assert_ok!(NeoSwaps::combo_buy( + RuntimeOrigin::signed(BOB), + market_id, + 2, + buy.clone(), + sell.clone(), + amount_in, + 0, + )); + let pool = Pools::::get(market_id).unwrap(); + let expected_swap_amount_out = 58496250072; + let expected_amount_in_minus_fees = _10 + 1; // Note: This is 1 Pennock off of the correct result. + let expected_reserves = vec![ + pool_outcomes_before[0] - expected_swap_amount_out, + pool_outcomes_before[0] + expected_amount_in_minus_fees, + ]; + assert_pool_state!( + market_id, + expected_reserves, + vec![_3_4, _1_4], + liquidity_parameter_before, + create_b_tree_map!({ ALICE => liquidity }), + expected_swap_fee_amount, + ); + let expected_amount_out = expected_swap_amount_out + expected_amount_in_minus_fees; + assert_balance!(BOB, BASE_ASSET, 0); + assert_balance!(BOB, buy[0], expected_amount_out); + assert_balance!( + pool.account_id, + BASE_ASSET, + expected_swap_fee_amount + AssetManager::minimum_balance(pool.collateral) + ); + assert_balance!(FEE_ACCOUNT, BASE_ASSET, expected_external_fee_amount); + System::assert_last_event( + Event::ComboBuyExecuted { + who: BOB, + market_id, + buy, + sell, + amount_in, + amount_out: expected_amount_out, + swap_fee_amount: expected_swap_fee_amount, + external_fee_amount: expected_external_fee_amount, + } + .into(), + ); + }); +} + +#[test] +fn combo_buy_fails_on_incorrect_asset_count() { + ExtBuilder::default().build().execute_with(|| { + let market_id = create_market_and_deploy_pool( + ALICE, + BASE_ASSET, + MarketType::Scalar(0..=1), + _10, + vec![_1_2, _1_2], + CENT, + ); + assert_noop!( + NeoSwaps::combo_buy( + RuntimeOrigin::signed(BOB), + market_id, + 1, + vec![Asset::ScalarOutcome(market_id, ScalarPosition::Long)], + vec![Asset::ScalarOutcome(market_id, ScalarPosition::Short)], + _1, + 0 + ), + Error::::IncorrectAssetCount + ); + }); +} + +#[test] +fn combo_buy_fails_on_market_not_found() { + ExtBuilder::default().build().execute_with(|| { + let market_id = create_market_and_deploy_pool( + ALICE, + BASE_ASSET, + MarketType::Scalar(0..=1), + _10, + vec![_1_2, _1_2], + CENT, + ); + Markets::::remove(market_id); + assert_noop!( + NeoSwaps::combo_buy( + RuntimeOrigin::signed(BOB), + market_id, + 2, + vec![Asset::ScalarOutcome(market_id, ScalarPosition::Long)], + vec![Asset::ScalarOutcome(market_id, ScalarPosition::Short)], + _1, + 0 + ), + zrml_market_commons::Error::::MarketDoesNotExist, + ); + }); +} + +#[test_case(MarketStatus::Proposed)] +#[test_case(MarketStatus::Closed)] +#[test_case(MarketStatus::Reported)] +#[test_case(MarketStatus::Disputed)] +#[test_case(MarketStatus::Resolved)] +fn combo_buy_fails_on_inactive_market(market_status: MarketStatus) { + ExtBuilder::default().build().execute_with(|| { + let market_id = create_market_and_deploy_pool( + ALICE, + BASE_ASSET, + MarketType::Scalar(0..=1), + _10, + vec![_1_2, _1_2], + CENT, + ); + MarketCommons::mutate_market(&market_id, |market| { + market.status = market_status; + Ok(()) + }) + .unwrap(); + assert_noop!( + NeoSwaps::combo_buy( + RuntimeOrigin::signed(BOB), + market_id, + 2, + vec![Asset::ScalarOutcome(market_id, ScalarPosition::Long)], + vec![Asset::ScalarOutcome(market_id, ScalarPosition::Short)], + _1, + 0 + ), + Error::::MarketNotActive, + ); + }); +} + +#[test] +fn combo_buy_fails_on_pool_not_found() { + ExtBuilder::default().build().execute_with(|| { + let market_id = + create_market(ALICE, BASE_ASSET, MarketType::Scalar(0..=1), ScoringRule::AmmCdaHybrid); + assert_noop!( + NeoSwaps::combo_buy( + RuntimeOrigin::signed(BOB), + market_id, + 2, + vec![Asset::ScalarOutcome(market_id, ScalarPosition::Long)], + vec![Asset::ScalarOutcome(market_id, ScalarPosition::Short)], + _1, + 0 + ), + Error::::PoolNotFound, + ); + }); +} + +#[test_case(MarketType::Categorical(2))] +#[test_case(MarketType::Scalar(0..=1))] +fn combo_buy_fails_on_asset_not_found(market_type: MarketType) { + ExtBuilder::default().build().execute_with(|| { + let market_id = create_market_and_deploy_pool( + ALICE, + BASE_ASSET, + market_type, + _10, + vec![_1_2, _1_2], + CENT, + ); + assert_noop!( + NeoSwaps::combo_buy( + RuntimeOrigin::signed(BOB), + market_id, + 2, + vec![Asset::CategoricalOutcome(market_id, 2)], + vec![Asset::CategoricalOutcome(market_id, 1)], + _1, + 0 + ), + Error::::AssetNotFound, + ); + }); +} + +#[test] +fn combo_buy_fails_on_insufficient_funds() { + ExtBuilder::default().build().execute_with(|| { + let market_id = create_market_and_deploy_pool( + ALICE, + BASE_ASSET, + MarketType::Scalar(0..=1), + _10, + vec![_1_2, _1_2], + CENT, + ); + let amount_in = _10; + assert_ok!(AssetManager::deposit(BASE_ASSET, &BOB, amount_in - 1)); + assert_noop!( + NeoSwaps::combo_buy( + RuntimeOrigin::signed(BOB), + market_id, + 2, + vec![Asset::ScalarOutcome(market_id, ScalarPosition::Long)], + vec![Asset::ScalarOutcome(market_id, ScalarPosition::Short)], + amount_in, + 0, + ), + zrml_prediction_markets::Error::::NotEnoughBalance, + ); + }); +} + +#[test] +fn combo_buy_fails_on_amount_out_below_min() { + ExtBuilder::default().build().execute_with(|| { + let market_id = create_market_and_deploy_pool( + ALICE, + BASE_ASSET, + MarketType::Scalar(0..=1), + _10, + vec![_1_2, _1_2], + CENT, + ); + let amount_in = _1; + assert_ok!(AssetManager::deposit(BASE_ASSET, &BOB, amount_in)); + // Buying 1 at price of .5 will return less than 2 outcomes due to slippage. + assert_noop!( + NeoSwaps::combo_buy( + RuntimeOrigin::signed(BOB), + market_id, + 2, + vec![Asset::ScalarOutcome(market_id, ScalarPosition::Long)], + vec![Asset::ScalarOutcome(market_id, ScalarPosition::Short)], + amount_in, + _2, + ), + Error::::AmountOutBelowMin, + ); + }); +} diff --git a/zrml/neo-swaps/src/tests/combo_sell.rs b/zrml/neo-swaps/src/tests/combo_sell.rs new file mode 100644 index 000000000..4e9a92ad1 --- /dev/null +++ b/zrml/neo-swaps/src/tests/combo_sell.rs @@ -0,0 +1,309 @@ +// Copyright 2023-2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use super::*; +use test_case::test_case; + +#[test] +fn combo_sell_works() { + ExtBuilder::default().build().execute_with(|| { + let liquidity = _10; + let spot_prices = vec![_1_4, _3_4]; + let swap_fee = CENT; + let market_id = create_market_and_deploy_pool( + ALICE, + BASE_ASSET, + MarketType::Scalar(0..=1), + liquidity, + spot_prices.clone(), + swap_fee, + ); + let pool = Pools::::get(market_id).unwrap(); + let amount_buy = _10; + let amount_keep = 0; + let liquidity_parameter_before = pool.liquidity_parameter; + deposit_complete_set(market_id, BOB, amount_buy); + let buy = vec![pool.assets()[1]]; + let keep = vec![]; + let sell = vec![pool.assets()[0]]; + assert_ok!(NeoSwaps::combo_sell( + RuntimeOrigin::signed(BOB), + market_id, + 2, + buy.clone(), + keep.clone(), + sell.clone(), + amount_buy, + amount_keep, + 0, + )); + let total_fee_percentage = swap_fee + EXTERNAL_FEES; + let expected_amount_out = 59632253897; + let expected_fees = total_fee_percentage.bmul(expected_amount_out).unwrap(); + let expected_swap_fee_amount = expected_fees / 2; + let expected_external_fee_amount = expected_fees - expected_swap_fee_amount; + let expected_amount_out_minus_fees = expected_amount_out - expected_fees; + assert_balance!(BOB, BASE_ASSET, expected_amount_out_minus_fees); + assert_balance!(BOB, buy[0], 0); + assert_pool_state!( + market_id, + vec![40367746103, 61119621067], + [5_714_285_714, 4_285_714_286], + liquidity_parameter_before, + create_b_tree_map!({ ALICE => liquidity }), + expected_swap_fee_amount, + ); + assert_balance!( + pool.account_id, + BASE_ASSET, + expected_swap_fee_amount + AssetManager::minimum_balance(pool.collateral) + ); + assert_balance!(FEE_ACCOUNT, BASE_ASSET, expected_external_fee_amount); + assert_eq!( + AssetManager::total_issuance(pool.assets()[0]), + liquidity + amount_buy - expected_amount_out + ); + assert_eq!( + AssetManager::total_issuance(pool.assets()[1]), + liquidity + amount_buy - expected_amount_out + ); + System::assert_last_event( + Event::ComboSellExecuted { + who: BOB, + market_id, + buy, + keep, + sell, + amount_buy, + amount_keep, + amount_out: expected_amount_out_minus_fees, + swap_fee_amount: expected_swap_fee_amount, + external_fee_amount: expected_external_fee_amount, + } + .into(), + ); + }); +} + +#[test] +fn combo_sell_fails_on_incorrect_asset_count() { + ExtBuilder::default().build().execute_with(|| { + let market_id = create_market_and_deploy_pool( + ALICE, + BASE_ASSET, + MarketType::Scalar(0..=1), + _10, + vec![_1_2, _1_2], + CENT, + ); + assert_noop!( + NeoSwaps::combo_sell( + RuntimeOrigin::signed(BOB), + market_id, + 1, + vec![Asset::ScalarOutcome(market_id, ScalarPosition::Long)], + vec![], + vec![Asset::ScalarOutcome(market_id, ScalarPosition::Short)], + _1, + 0, + 0 + ), + Error::::IncorrectAssetCount + ); + }); +} + +#[test] +fn combo_sell_fails_on_market_not_found() { + ExtBuilder::default().build().execute_with(|| { + let market_id = create_market_and_deploy_pool( + ALICE, + BASE_ASSET, + MarketType::Scalar(0..=1), + _10, + vec![_1_2, _1_2], + CENT, + ); + Markets::::remove(market_id); + assert_noop!( + NeoSwaps::combo_sell( + RuntimeOrigin::signed(BOB), + market_id, + 2, + vec![Asset::ScalarOutcome(market_id, ScalarPosition::Long)], + vec![], + vec![Asset::ScalarOutcome(market_id, ScalarPosition::Short)], + _1, + 0, + 0 + ), + zrml_market_commons::Error::::MarketDoesNotExist, + ); + }); +} + +#[test_case(MarketStatus::Proposed)] +#[test_case(MarketStatus::Closed)] +#[test_case(MarketStatus::Reported)] +#[test_case(MarketStatus::Disputed)] +#[test_case(MarketStatus::Resolved)] +fn combo_sell_fails_on_inactive_market(market_status: MarketStatus) { + ExtBuilder::default().build().execute_with(|| { + let market_id = create_market_and_deploy_pool( + ALICE, + BASE_ASSET, + MarketType::Scalar(0..=1), + _10, + vec![_1_2, _1_2], + CENT, + ); + MarketCommons::mutate_market(&market_id, |market| { + market.status = market_status; + Ok(()) + }) + .unwrap(); + assert_noop!( + NeoSwaps::combo_sell( + RuntimeOrigin::signed(BOB), + market_id, + 2, + vec![Asset::ScalarOutcome(market_id, ScalarPosition::Long)], + vec![], + vec![Asset::ScalarOutcome(market_id, ScalarPosition::Short)], + _1, + 0, + 0 + ), + Error::::MarketNotActive, + ); + }); +} + +#[test] +fn combo_sell_fails_on_pool_not_found() { + ExtBuilder::default().build().execute_with(|| { + let market_id = + create_market(ALICE, BASE_ASSET, MarketType::Scalar(0..=1), ScoringRule::AmmCdaHybrid); + assert_noop!( + NeoSwaps::combo_sell( + RuntimeOrigin::signed(BOB), + market_id, + 2, + vec![Asset::ScalarOutcome(market_id, ScalarPosition::Long)], + vec![], + vec![Asset::ScalarOutcome(market_id, ScalarPosition::Short)], + _1, + 0, + 0 + ), + Error::::PoolNotFound, + ); + }); +} + +// TODO Needs to be expanded. +#[test_case(MarketType::Categorical(2))] +#[test_case(MarketType::Scalar(0..=1))] +fn combo_sell_fails_on_asset_not_found(market_type: MarketType) { + ExtBuilder::default().build().execute_with(|| { + let market_id = create_market_and_deploy_pool( + ALICE, + BASE_ASSET, + market_type, + _10, + vec![_1_2, _1_2], + CENT, + ); + assert_noop!( + NeoSwaps::combo_sell( + RuntimeOrigin::signed(BOB), + market_id, + 2, + vec![Asset::CategoricalOutcome(market_id, 3)], + vec![Asset::CategoricalOutcome(market_id, 5)], + vec![Asset::CategoricalOutcome(market_id, 4)], + _1, + 0, + u128::MAX, + ), + Error::::AssetNotFound, + ); + }); +} + +#[test] +fn combo_sell_fails_on_insufficient_funds() { + ExtBuilder::default().build().execute_with(|| { + let market_id = create_market_and_deploy_pool( + ALICE, + BASE_ASSET, + MarketType::Scalar(0..=1), + _10, + vec![_1_2, _1_2], + CENT, + ); + let amount_in = _10; + let asset_in = Asset::ScalarOutcome(market_id, ScalarPosition::Long); + assert_ok!(AssetManager::deposit(asset_in, &BOB, amount_in - 1)); + assert_noop!( + NeoSwaps::combo_sell( + RuntimeOrigin::signed(BOB), + market_id, + 2, + vec![Asset::ScalarOutcome(market_id, ScalarPosition::Long)], + vec![], + vec![Asset::ScalarOutcome(market_id, ScalarPosition::Short)], + amount_in, + 0, + 0, + ), + orml_tokens::Error::::BalanceTooLow, + ); + }); +} + +#[test] +fn combo_sell_fails_on_amount_out_below_min() { + ExtBuilder::default().build().execute_with(|| { + let market_id = create_market_and_deploy_pool( + ALICE, + BASE_ASSET, + MarketType::Scalar(0..=1), + _100, + vec![_1_2, _1_2], + CENT, + ); + let amount_in = _20; + let asset_in = Asset::ScalarOutcome(market_id, ScalarPosition::Long); + assert_ok!(AssetManager::deposit(asset_in, &BOB, amount_in)); + // Selling 20 at price of .5 will return less than 10 dollars due to slippage. + assert_noop!( + NeoSwaps::combo_sell( + RuntimeOrigin::signed(BOB), + market_id, + 2, + vec![Asset::ScalarOutcome(market_id, ScalarPosition::Long)], + vec![], + vec![Asset::ScalarOutcome(market_id, ScalarPosition::Short)], + amount_in, + 0, + _10 + ), + Error::::AmountOutBelowMin, + ); + }); +} diff --git a/zrml/neo-swaps/src/tests/mod.rs b/zrml/neo-swaps/src/tests/mod.rs index 8dc8eb78d..42fdbe5f2 100644 --- a/zrml/neo-swaps/src/tests/mod.rs +++ b/zrml/neo-swaps/src/tests/mod.rs @@ -19,6 +19,8 @@ mod buy; mod buy_and_sell; +mod combo_buy; +mod combo_sell; mod deploy_pool; mod exit; mod join; diff --git a/zrml/neo-swaps/src/traits/pool_operations.rs b/zrml/neo-swaps/src/traits/pool_operations.rs index 57c352661..26efd4bb8 100644 --- a/zrml/neo-swaps/src/traits/pool_operations.rs +++ b/zrml/neo-swaps/src/traits/pool_operations.rs @@ -32,6 +32,11 @@ pub(crate) trait PoolOperations { /// Beware! The reserve need not coincide with the balance in the pool account. fn reserve_of(&self, asset: &AssetOf) -> Result, DispatchError>; + /// Return the reserves of the specified `assets`, in the same order. + /// + /// Beware! The reserve need not coincide with the balance in the pool account. + fn reserves_of(&self, assets: &Vec>) -> Result>, DispatchError>; + /// Perform a checked addition to the balance of `asset`. fn increase_reserve( &mut self, @@ -46,32 +51,30 @@ pub(crate) trait PoolOperations { decrease_amount: &BalanceOf, ) -> DispatchResult; - /// Calculate the amount received from the swap that is executed when buying (the function - /// `y(x)` from the documentation). - /// - /// Note that `y(x)` does not include the amount of `asset_out` received from buying complete - /// sets and is therefore _not_ the total amount received from the buy. - /// - /// # Parameters - /// - /// - `asset_out`: The outcome being bought. - /// - `amount_in`: The amount of collateral paid. + /// Calculate the amount received when opening the specified combinatorial position. fn calculate_swap_amount_out_for_buy( &self, - asset_out: AssetOf, + buy: Vec>, + sell: Vec>, amount_in: BalanceOf, ) -> Result, DispatchError>; - /// Calculate the amount receives from selling an outcome to the pool. + /// Calculate the amount receives from closing the specified combinatorial bet. /// /// # Parameters /// - /// - `asset_in`: The outcome being sold. - /// - `amount_in`: The amount of `asset_in` sold. + /// - `buy`: The buy of the combinatorial bet to close. + /// - `keep`: The keep of the combinatorial bet to close. + /// - `sell`: The sell of the combinatorial bet to close. + /// - `amount_buy`: The amount of the buy held in the combinatorial position. + /// - `amount_sell`: The amount of the sell held in the combinatorial position. fn calculate_swap_amount_out_for_sell( &self, - asset_in: AssetOf, - amount_in: BalanceOf, + buy: Vec>, + keep: Vec>, + sell: Vec>, + amount_buy: BalanceOf, + amount_sell: BalanceOf, ) -> Result, DispatchError>; /// Calculate the spot price of `asset`. @@ -120,4 +123,7 @@ pub(crate) trait PoolOperations { asset: AssetOf, until: BalanceOf, ) -> Result, DispatchError>; + + /// Calculates the complement of `assets` in the set of assets contained in the pool. + fn assets_complement(&self, assets: &Vec>) -> Vec>; } diff --git a/zrml/neo-swaps/src/types/pool.rs b/zrml/neo-swaps/src/types/pool.rs index 61e490662..0a5f2afb6 100644 --- a/zrml/neo-swaps/src/types/pool.rs +++ b/zrml/neo-swaps/src/types/pool.rs @@ -17,7 +17,10 @@ use crate::{ consts::EXP_NUMERICAL_LIMIT, - math::{traits::MathOps, types::Math}, + math::{ + traits::{ComboMathOps, MathOps}, + types::{ComboMath, Math}, + }, pallet::{AssetOf, BalanceOf, Config}, traits::{LiquiditySharesManager, PoolOperations}, Error, @@ -71,6 +74,10 @@ where Ok(*self.reserves.get(asset).ok_or(Error::::AssetNotFound)?) } + fn reserves_of(&self, assets: &Vec>) -> Result>, DispatchError> { + assets.iter().map(|a| self.reserve_of(a)).collect() + } + fn increase_reserve( &mut self, asset: &AssetOf, @@ -93,20 +100,41 @@ where fn calculate_swap_amount_out_for_buy( &self, - asset_out: AssetOf, + buy: Vec>, + sell: Vec>, amount_in: BalanceOf, ) -> Result, DispatchError> { - let reserve = self.reserve_of(&asset_out)?; - Math::::calculate_swap_amount_out_for_buy(reserve, amount_in, self.liquidity_parameter) + let reserves_buy = self.reserves_of(&buy)?; + let reserves_sell = self.reserves_of(&sell)?; + + ComboMath::::calculate_swap_amount_out_for_buy( + reserves_buy, + reserves_sell, + amount_in, + self.liquidity_parameter, + ) } fn calculate_swap_amount_out_for_sell( &self, - asset_in: AssetOf, - amount_in: BalanceOf, + buy: Vec>, + keep: Vec>, + sell: Vec>, + amount_buy: BalanceOf, + amount_sell: BalanceOf, ) -> Result, DispatchError> { - let reserve = self.reserve_of(&asset_in)?; - Math::::calculate_swap_amount_out_for_sell(reserve, amount_in, self.liquidity_parameter) + let reserves_buy = self.reserves_of(&buy)?; + let reserves_keep = self.reserves_of(&keep)?; + let reserves_sell = self.reserves_of(&sell)?; + + ComboMath::::calculate_swap_amount_out_for_sell( + reserves_buy, + reserves_keep, + reserves_sell, + amount_buy, + amount_sell, + self.liquidity_parameter, + ) } fn calculate_spot_price(&self, asset: AssetOf) -> Result, DispatchError> { @@ -147,4 +175,8 @@ where let spot_price = Math::::calculate_spot_price(reserve, self.liquidity_parameter)?; Math::::calculate_sell_amount_until(until, self.liquidity_parameter, spot_price) } + + fn assets_complement(&self, assets: &Vec>) -> Vec> { + self.reserves.keys().filter(|a| !assets.contains(a)).cloned().collect() + } } From d45ecf45ec0f8463ae61960e16cca383a6b4968f Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Mon, 23 Sep 2024 19:54:53 +0200 Subject: [PATCH 03/73] Fix compiler and clippy issues (#1366) * Fix compiler and clippy issues * Fix formatting * Fix tests --- zrml/hybrid-router/src/tests/buy.rs | 2 +- zrml/neo-swaps/src/lib.rs | 15 ++++++++++----- zrml/neo-swaps/src/math/traits/combo_math_ops.rs | 3 +++ zrml/neo-swaps/src/math/traits/math_ops.rs | 3 +++ zrml/neo-swaps/src/math/types/combo_math.rs | 12 ++++-------- zrml/neo-swaps/src/traits/pool_operations.rs | 4 ++-- zrml/neo-swaps/src/types/pool.rs | 4 ++-- 7 files changed, 25 insertions(+), 18 deletions(-) diff --git a/zrml/hybrid-router/src/tests/buy.rs b/zrml/hybrid-router/src/tests/buy.rs index dec84799d..b22ca3212 100644 --- a/zrml/hybrid-router/src/tests/buy.rs +++ b/zrml/hybrid-router/src/tests/buy.rs @@ -758,7 +758,7 @@ fn buy_emits_event() { asset_in: BASE_ASSET, amount_in, asset_out: asset, - amount_out: 2301256894490, + amount_out: 2301256894491, external_fee_amount: 3423314400, swap_fee_amount: 2273314407, } diff --git a/zrml/neo-swaps/src/lib.rs b/zrml/neo-swaps/src/lib.rs index 51cdb0f57..afb42d490 100644 --- a/zrml/neo-swaps/src/lib.rs +++ b/zrml/neo-swaps/src/lib.rs @@ -17,6 +17,7 @@ #![doc = include_str!("../README.md")] #![cfg_attr(not(feature = "std"), no_std)] +#![allow(clippy::too_many_arguments)] // TODO Try to remove this later! extern crate alloc; @@ -565,6 +566,7 @@ mod pallet { Ok(Some(T::WeightInfo::deploy_pool(spot_prices_len)).into()) } + #[allow(clippy::too_many_arguments)] // TODO Bundle `buy`/`keep`/`sell` into one arg. #[pallet::call_index(6)] #[pallet::weight(T::WeightInfo::buy((*asset_count).saturated_into()))] // TODO #[transactional] @@ -584,6 +586,7 @@ mod pallet { Ok(Some(T::WeightInfo::buy(asset_count.into())).into()) // TODO } + #[allow(clippy::too_many_arguments)] // TODO Bundle `buy`/`keep`/`sell` into one arg. #[pallet::call_index(7)] #[pallet::weight(T::WeightInfo::buy((*asset_count).saturated_into()))] // TODO #[transactional] @@ -1009,6 +1012,7 @@ mod pallet { Ok(()) } + #[allow(clippy::too_many_arguments)] // TODO Bundle `buy`/`keep`/`sell` into one arg. #[require_transactional] fn do_combo_buy( who: T::AccountId, @@ -1024,7 +1028,7 @@ mod pallet { ensure!(market.status == MarketStatus::Active, Error::::MarketNotActive); Self::try_mutate_pool(&market_id, |pool| { for asset in buy.iter().chain(sell.iter()) { - ensure!(pool.contains(&asset), Error::::AssetNotFound); + ensure!(pool.contains(asset), Error::::AssetNotFound); } // TODO Ensure that buy, sell partition the assets! @@ -1079,11 +1083,12 @@ mod pallet { }) } + // TODO Replace `buy`/`keep`/`sell` with a struct. + #[allow(clippy::too_many_arguments)] #[require_transactional] fn do_combo_sell( who: T::AccountId, market_id: MarketIdOf, - // TODO Replace `buy`/`keep`/`sell` with a struct. buy: Vec>, keep: Vec>, sell: Vec>, @@ -1096,7 +1101,7 @@ mod pallet { ensure!(market.status == MarketStatus::Active, Error::::MarketNotActive); Self::try_mutate_pool(&market_id, |pool| { for asset in buy.iter().chain(sell.iter()).chain(keep.iter()) { - ensure!(pool.contains(&asset), Error::::AssetNotFound); + ensure!(pool.contains(asset), Error::::AssetNotFound); } // TODO Ensure that buy, sell partition the assets! @@ -1153,7 +1158,7 @@ mod pallet { &pool.account_id, &who, amount_out_minus_fees, - ); + )?; Self::deposit_event(Event::::ComboSellExecuted { who: who.clone(), @@ -1196,7 +1201,7 @@ mod pallet { amount: BalanceOf, ) -> Result, DispatchError> { let swap_fees = pool.swap_fee.bmul(amount)?; - T::MultiCurrency::transfer(pool.collateral, &account, &pool.account_id, swap_fees)?; + T::MultiCurrency::transfer(pool.collateral, account, &pool.account_id, swap_fees)?; pool.liquidity_shares_manager.deposit_fees(swap_fees)?; // Should only error unexpectedly! let external_fees = T::ExternalFees::distribute(market_id, pool.collateral, account, amount); diff --git a/zrml/neo-swaps/src/math/traits/combo_math_ops.rs b/zrml/neo-swaps/src/math/traits/combo_math_ops.rs index bdf78ebe4..69148c51d 100644 --- a/zrml/neo-swaps/src/math/traits/combo_math_ops.rs +++ b/zrml/neo-swaps/src/math/traits/combo_math_ops.rs @@ -16,6 +16,7 @@ // along with Zeitgeist. If not, see . use crate::{BalanceOf, Config}; +use alloc::vec::Vec; use sp_runtime::DispatchError; pub(crate) trait ComboMathOps @@ -29,6 +30,7 @@ where liquidity: BalanceOf, ) -> Result, DispatchError>; + #[allow(dead_code)] fn calculate_equalize_amount( buy: Vec>, sell: Vec>, @@ -46,6 +48,7 @@ where liquidity: BalanceOf, ) -> Result, DispatchError>; + #[allow(dead_code)] fn calculate_spot_price( buy: Vec>, sell: Vec>, diff --git a/zrml/neo-swaps/src/math/traits/math_ops.rs b/zrml/neo-swaps/src/math/traits/math_ops.rs index 45f699247..a11922468 100644 --- a/zrml/neo-swaps/src/math/traits/math_ops.rs +++ b/zrml/neo-swaps/src/math/traits/math_ops.rs @@ -16,18 +16,21 @@ // along with Zeitgeist. If not, see . use crate::{BalanceOf, Config}; +use alloc::vec::Vec; use sp_runtime::DispatchError; pub(crate) trait MathOps where T: Config, { + #[allow(dead_code)] fn calculate_swap_amount_out_for_buy( reserve: BalanceOf, amount_in: BalanceOf, liquidity: BalanceOf, ) -> Result, DispatchError>; + #[allow(dead_code)] fn calculate_swap_amount_out_for_sell( reserve: BalanceOf, amount_in: BalanceOf, diff --git a/zrml/neo-swaps/src/math/types/combo_math.rs b/zrml/neo-swaps/src/math/types/combo_math.rs index d6ef12897..3e4afe59a 100644 --- a/zrml/neo-swaps/src/math/types/combo_math.rs +++ b/zrml/neo-swaps/src/math/types/combo_math.rs @@ -25,16 +25,14 @@ use crate::{ use alloc::vec::Vec; use core::marker::PhantomData; use fixed::FixedU128; -use sp_runtime::{ - traits::{One, Zero}, - DispatchError, SaturatedConversion, -}; +use sp_runtime::{traits::Zero, DispatchError, SaturatedConversion}; use typenum::U80; type Fractional = U80; type FixedType = FixedU128; /// The point at which 32.44892769177272 +#[allow(dead_code)] // TODO Block calls that go outside of these bounds. const EXP_OVERFLOW_THRESHOLD: FixedType = FixedType::from_bits(0x20_72EC_ECDA_6EBE_EACC_40C7); pub(crate) struct ComboMath(PhantomData); @@ -292,8 +290,7 @@ mod detail { buy.into_iter().map(|x| x.checked_add(delta_buy)).collect::>>()?; let keep_intermediate = keep.into_iter().map(|x| x.checked_sub(delta_keep)).collect::>>()?; - let buy_keep = - buy_intermediate.into_iter().chain(keep_intermediate.into_iter()).collect(); + let buy_keep = buy_intermediate.into_iter().chain(keep_intermediate).collect(); (amount_buy.checked_sub(delta_buy)?, buy_keep) }; @@ -327,8 +324,7 @@ mod tests { #![allow(clippy::duplicated_attributes)] use super::*; - use crate::{mock::Runtime as MockRuntime, MAX_SPOT_PRICE, MIN_SPOT_PRICE}; - use alloc::str::FromStr; + use crate::mock::Runtime as MockRuntime; use frame_support::assert_err; use test_case::test_case; use zeitgeist_primitives::constants::base_multiples::*; diff --git a/zrml/neo-swaps/src/traits/pool_operations.rs b/zrml/neo-swaps/src/traits/pool_operations.rs index 26efd4bb8..341ffb779 100644 --- a/zrml/neo-swaps/src/traits/pool_operations.rs +++ b/zrml/neo-swaps/src/traits/pool_operations.rs @@ -35,7 +35,7 @@ pub(crate) trait PoolOperations { /// Return the reserves of the specified `assets`, in the same order. /// /// Beware! The reserve need not coincide with the balance in the pool account. - fn reserves_of(&self, assets: &Vec>) -> Result>, DispatchError>; + fn reserves_of(&self, assets: &[AssetOf]) -> Result>, DispatchError>; /// Perform a checked addition to the balance of `asset`. fn increase_reserve( @@ -125,5 +125,5 @@ pub(crate) trait PoolOperations { ) -> Result, DispatchError>; /// Calculates the complement of `assets` in the set of assets contained in the pool. - fn assets_complement(&self, assets: &Vec>) -> Vec>; + fn assets_complement(&self, assets: &[AssetOf]) -> Vec>; } diff --git a/zrml/neo-swaps/src/types/pool.rs b/zrml/neo-swaps/src/types/pool.rs index 0a5f2afb6..d6586514f 100644 --- a/zrml/neo-swaps/src/types/pool.rs +++ b/zrml/neo-swaps/src/types/pool.rs @@ -74,7 +74,7 @@ where Ok(*self.reserves.get(asset).ok_or(Error::::AssetNotFound)?) } - fn reserves_of(&self, assets: &Vec>) -> Result>, DispatchError> { + fn reserves_of(&self, assets: &[AssetOf]) -> Result>, DispatchError> { assets.iter().map(|a| self.reserve_of(a)).collect() } @@ -176,7 +176,7 @@ where Math::::calculate_sell_amount_until(until, self.liquidity_parameter, spot_price) } - fn assets_complement(&self, assets: &Vec>) -> Vec> { + fn assets_complement(&self, assets: &[AssetOf]) -> Vec> { self.reserves.keys().filter(|a| !assets.contains(a)).cloned().collect() } } From 2bd5e6c2adc88e0b89ba66a624c15857f75d7921 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Tue, 24 Sep 2024 16:10:40 +0200 Subject: [PATCH 04/73] Scaffold combo pallet (#1367) * Scaffold combo pallet * Fix dependencies/features --- Cargo.lock | 14 +++++++++ Cargo.toml | 3 ++ runtime/battery-station/Cargo.toml | 4 +++ runtime/common/src/lib.rs | 3 ++ runtime/zeitgeist/Cargo.toml | 4 +++ zrml/combo/Cargo.toml | 30 ++++++++++++++++++ zrml/combo/README.md | 3 ++ zrml/combo/src/lib.rs | 50 ++++++++++++++++++++++++++++++ 8 files changed, 111 insertions(+) create mode 100644 zrml/combo/Cargo.toml create mode 100644 zrml/combo/README.md create mode 100644 zrml/combo/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 5328917c8..3c41b94ef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -881,6 +881,7 @@ dependencies = [ "xcm-emulator", "zeitgeist-primitives", "zrml-authorized", + "zrml-combo", "zrml-court", "zrml-global-disputes", "zrml-hybrid-router", @@ -15039,6 +15040,7 @@ dependencies = [ "xcm-emulator", "zeitgeist-primitives", "zrml-authorized", + "zrml-combo", "zrml-court", "zrml-global-disputes", "zrml-hybrid-router", @@ -15110,6 +15112,18 @@ dependencies = [ "zrml-market-commons", ] +[[package]] +name = "zrml-combo" +version = "0.5.5" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-runtime", +] + [[package]] name = "zrml-court" version = "0.5.5" diff --git a/Cargo.toml b/Cargo.toml index d04e185ec..3985992ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ default-members = [ "runtime/battery-station", "runtime/zeitgeist", "zrml/authorized", + "zrml/combo", "zrml/court", "zrml/hybrid-router", "zrml/global-disputes", @@ -36,6 +37,7 @@ members = [ "runtime/battery-station", "runtime/zeitgeist", "zrml/authorized", + "zrml/combo", "zrml/court", "zrml/hybrid-router", "zrml/global-disputes", @@ -244,6 +246,7 @@ common-runtime = { path = "runtime/common", default-features = false } zeitgeist-macros = { path = "macros", default-features = false } zeitgeist-primitives = { path = "primitives", default-features = false } zrml-authorized = { path = "zrml/authorized", default-features = false } +zrml-combo = { path = "zrml/combo", default-features = false } zrml-court = { path = "zrml/court", default-features = false } zrml-global-disputes = { path = "zrml/global-disputes", default-features = false } zrml-hybrid-router = { path = "zrml/hybrid-router", default-features = false } diff --git a/runtime/battery-station/Cargo.toml b/runtime/battery-station/Cargo.toml index 3e1bf7035..035b69562 100644 --- a/runtime/battery-station/Cargo.toml +++ b/runtime/battery-station/Cargo.toml @@ -109,6 +109,7 @@ xcm-executor = { workspace = true, optional = true } common-runtime = { workspace = true } zeitgeist-primitives = { workspace = true } zrml-authorized = { workspace = true } +zrml-combo = { workspace = true } zrml-court = { workspace = true } zrml-global-disputes = { workspace = true, optional = true } zrml-hybrid-router = { workspace = true } @@ -214,6 +215,7 @@ runtime-benchmarks = [ "sp-runtime/runtime-benchmarks", "xcm-builder?/runtime-benchmarks", "zrml-authorized/runtime-benchmarks", + "zrml-combo/runtime-benchmarks", "zrml-court/runtime-benchmarks", "zrml-hybrid-router/runtime-benchmarks", "zrml-neo-swaps/runtime-benchmarks", @@ -327,6 +329,7 @@ std = [ "zeitgeist-primitives/std", "zrml-authorized/std", + "zrml-combo/std", "zrml-court/std", "zrml-hybrid-router/std", "zrml-market-commons/std", @@ -381,6 +384,7 @@ try-runtime = [ # Zeitgeist runtime pallets "zrml-authorized/try-runtime", + "zrml-combo/try-runtime", "zrml-court/try-runtime", "zrml-hybrid-router/try-runtime", "zrml-market-commons/try-runtime", diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs index 9b82785b6..083821a64 100644 --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -358,6 +358,7 @@ macro_rules! create_runtime { Orderbook: zrml_orderbook::{Call, Event, Pallet, Storage} = 61, Parimutuel: zrml_parimutuel::{Call, Event, Pallet, Storage} = 62, HybridRouter: zrml_hybrid_router::{Call, Event, Pallet, Storage} = 64, + Combo: zrml_combo::{Pallet, Storage} = 65, $($additional_pallets)* } @@ -1168,6 +1169,8 @@ macro_rules! impl_config_traits { type WeightInfo = zrml_authorized::weights::WeightInfo; } + impl zrml_combo::Config for Runtime {} + impl zrml_court::Config for Runtime { type AppealBond = AppealBond; type BlocksPerYear = BlocksPerYear; diff --git a/runtime/zeitgeist/Cargo.toml b/runtime/zeitgeist/Cargo.toml index a8a6ccc02..7d7aa5360 100644 --- a/runtime/zeitgeist/Cargo.toml +++ b/runtime/zeitgeist/Cargo.toml @@ -108,6 +108,7 @@ xcm-executor = { workspace = true, optional = true } common-runtime = { workspace = true } zeitgeist-primitives = { workspace = true } zrml-authorized = { workspace = true } +zrml-combo = { workspace = true } zrml-court = { workspace = true } zrml-global-disputes = { workspace = true, optional = true } zrml-hybrid-router = { workspace = true } @@ -211,6 +212,7 @@ runtime-benchmarks = [ "sp-runtime/runtime-benchmarks", "xcm-builder?/runtime-benchmarks", "zrml-authorized/runtime-benchmarks", + "zrml-combo/runtime-benchmarks", "zrml-court/runtime-benchmarks", "zrml-hybrid-router/runtime-benchmarks", "zrml-neo-swaps/runtime-benchmarks", @@ -316,6 +318,7 @@ std = [ "zeitgeist-primitives/std", "zrml-authorized/std", + "zrml-combo/std", "zrml-court/std", "zrml-hybrid-router/std", "zrml-market-commons/std", @@ -369,6 +372,7 @@ try-runtime = [ # Zeitgeist runtime pallets "zrml-authorized/try-runtime", + "zrml-combo/try-runtime", "zrml-court/try-runtime", "zrml-hybrid-router/try-runtime", "zrml-market-commons/try-runtime", diff --git a/zrml/combo/Cargo.toml b/zrml/combo/Cargo.toml new file mode 100644 index 000000000..5a182a122 --- /dev/null +++ b/zrml/combo/Cargo.toml @@ -0,0 +1,30 @@ +[dependencies] +frame-benchmarking = { workspace = true, optional = true } +frame-support = { workspace = true } +frame-system = { workspace = true } +parity-scale-codec = { workspace = true, features = ["derive", "max-encoded-len"] } +scale-info = { workspace = true, features = ["derive"] } +sp-runtime = { workspace = true } + +[features] +default = ["std"] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", +] +std = [ + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "sp-runtime/std", +] +try-runtime = [ + "frame-support/try-runtime", +] + +[package] +authors = ["Zeitgeist PM "] +edition.workspace = true +name = "zrml-combo" +version = "0.5.5" diff --git a/zrml/combo/README.md b/zrml/combo/README.md new file mode 100644 index 000000000..1e0c36716 --- /dev/null +++ b/zrml/combo/README.md @@ -0,0 +1,3 @@ +# Combo Module + +The Combo module implements combinatorial tokens in substrate. diff --git a/zrml/combo/src/lib.rs b/zrml/combo/src/lib.rs new file mode 100644 index 000000000..5ef80e751 --- /dev/null +++ b/zrml/combo/src/lib.rs @@ -0,0 +1,50 @@ +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +#![doc = include_str!("../README.md")] +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; + +// TODO Modules + +pub use pallet::*; + +#[frame_support::pallet] +mod pallet { + use core::marker::PhantomData; + use frame_support::pallet_prelude::StorageVersion; + + // TODO Config + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + #[pallet::storage_version(STORAGE_VERSION)] + pub struct Pallet(PhantomData); + + // TODO Types + pub(crate) const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); + + // TODO Storage Items + + // TODO `Event` enum + + // TODO `Error` enum + + // TODO Dispatchables +} From dcd492129cb24094420b0d01c92d7cb548753ad7 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Thu, 3 Oct 2024 10:35:01 +0200 Subject: [PATCH 05/73] Implement combinatorial tokens (#1368) * More scaffolding * Add `IdManager` trait * Scaffold ID manager * Use generics * WIP * Rough outline of `CryptographicIdManager` implementation * Improve `MaybeToBytes` implementation for `bool` * Implement pseudo-root * Add more tests * Implement `quadratic_residue` and add tests * Partial implementation of decompression algorithm * Loads of cleaning up * Refactor * Clean up `hash_pair` * Simplify interface * Simplify serialization * More cleanup * Clean up and tests * Better `ToBytes` implementation * Abstract `decompressor` tests * Reorganize tests * Minor clean up * More clean up * Test `get_collection_id`, fix bugs * Clean up * Expose `force_max_work` parameter and test it properly * Properly forget dummies * Add more tests * Fix some error handling, docs, and add missing tests * Prettify --- Cargo.lock | 352 ++++-- Cargo.toml | 3 + zrml/combo/Cargo.toml | 8 + zrml/combo/src/lib.rs | 57 +- zrml/combo/src/traits/id_manager.rs | 20 + zrml/combo/src/traits/mod.rs | 3 + .../decompressor/mod.rs | 582 +++++++++ .../tests/decompress_collection_id.rs | 561 +++++++++ .../decompressor/tests/decompress_hash.rs | 756 ++++++++++++ .../decompressor/tests/field_modulus.rs | 10 + .../decompressor/tests/get_collection_id.rs | 73 ++ .../tests/matching_y_coordinate.rs | 265 ++++ .../decompressor/tests/mod.rs | 34 + .../decompressor/tests/pow_magic_number.rs | 1081 +++++++++++++++++ .../cryptographic_id_manager/hash_tuple.rs | 115 ++ .../src/types/cryptographic_id_manager/mod.rs | 41 + .../cryptographic_id_manager/typedefs.rs | 1 + zrml/combo/src/types/mod.rs | 3 + 18 files changed, 3859 insertions(+), 106 deletions(-) create mode 100644 zrml/combo/src/traits/id_manager.rs create mode 100644 zrml/combo/src/traits/mod.rs create mode 100644 zrml/combo/src/types/cryptographic_id_manager/decompressor/mod.rs create mode 100644 zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/decompress_collection_id.rs create mode 100644 zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/decompress_hash.rs create mode 100644 zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/field_modulus.rs create mode 100644 zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/get_collection_id.rs create mode 100644 zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/matching_y_coordinate.rs create mode 100644 zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/mod.rs create mode 100644 zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/pow_magic_number.rs create mode 100644 zrml/combo/src/types/cryptographic_id_manager/hash_tuple.rs create mode 100644 zrml/combo/src/types/cryptographic_id_manager/mod.rs create mode 100644 zrml/combo/src/types/cryptographic_id_manager/typedefs.rs create mode 100644 zrml/combo/src/types/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 3c41b94ef..71c880a47 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -283,7 +283,7 @@ dependencies = [ "num-bigint", "num-traits", "paste", - "rustc_version 0.4.0", + "rustc_version 0.4.1", "zeroize", ] @@ -633,7 +633,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.79", ] [[package]] @@ -668,7 +668,7 @@ checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.79", ] [[package]] @@ -940,7 +940,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.60", + "syn 2.0.79", ] [[package]] @@ -1377,7 +1377,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.79", ] [[package]] @@ -1782,9 +1782,9 @@ dependencies = [ [[package]] name = "crypto-mac" -version = "0.11.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +checksum = "25fab6889090c8133f3deb8f73ba3c65a7f456f66436fc012a1b1e272b1e103e" dependencies = [ "generic-array 0.14.7", "subtle", @@ -2027,7 +2027,7 @@ dependencies = [ "proc-macro-crate 1.3.1", "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.79", ] [[package]] @@ -2305,7 +2305,7 @@ dependencies = [ "digest 0.10.7", "fiat-crypto", "platforms", - "rustc_version 0.4.0", + "rustc_version 0.4.1", "subtle", "zeroize", ] @@ -2318,7 +2318,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.79", ] [[package]] @@ -2358,7 +2358,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn 2.0.60", + "syn 2.0.79", ] [[package]] @@ -2375,7 +2375,7 @@ checksum = "ad08a837629ad949b73d032c637653d069e909cffe4ee7870b02301939ce39cc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.79", ] [[package]] @@ -2467,7 +2467,7 @@ checksum = "d65d7ce8132b7c0e54497a4d9a55a1c2a0912a0d786cf894472ba818fba45762" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.79", ] [[package]] @@ -2478,7 +2478,7 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.79", ] [[package]] @@ -2490,7 +2490,7 @@ dependencies = [ "convert_case", "proc-macro2", "quote", - "rustc_version 0.4.0", + "rustc_version 0.4.1", "syn 1.0.109", ] @@ -2579,7 +2579,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.79", ] [[package]] @@ -2620,7 +2620,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.60", + "syn 2.0.79", "termcolor", "toml 0.8.2", "walkdir", @@ -2799,7 +2799,7 @@ checksum = "5c785274071b1b420972453b306eeca06acf4633829db4223b58a2a8c5953bc4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.79", ] [[package]] @@ -2810,7 +2810,7 @@ checksum = "6fd000fd6988e73bbe993ea3db9b1aa64906ab88766d654973924340c8cddb42" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.79", ] [[package]] @@ -2861,6 +2861,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "ethnum" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b90ca2580b73ab6a1f724b76ca11ab632df820fd6040c336200d2c1df7b3c82c" + [[package]] name = "event-listener" version = "2.5.3" @@ -2964,7 +2970,7 @@ dependencies = [ "prettier-please", "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.79", ] [[package]] @@ -3034,6 +3040,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ + "bitvec", "rand_core 0.6.4", "subtle", ] @@ -3256,7 +3263,7 @@ dependencies = [ "proc-macro-crate 1.3.1", "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.79", ] [[package]] @@ -3383,7 +3390,7 @@ dependencies = [ "proc-macro-warning", "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.79", ] [[package]] @@ -3395,7 +3402,7 @@ dependencies = [ "proc-macro-crate 1.3.1", "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.79", ] [[package]] @@ -3405,7 +3412,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk?branch=release-polkadot dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.79", ] [[package]] @@ -3589,7 +3596,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.79", ] [[package]] @@ -3752,8 +3759,8 @@ dependencies = [ "aho-corasick", "bstr", "log", - "regex-automata 0.4.6", - "regex-syntax 0.8.3", + "regex-automata 0.4.8", + "regex-syntax 0.8.5", ] [[package]] @@ -3779,7 +3786,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap 2.2.6", + "indexmap 2.5.0", "slab", "tokio", "tokio-util", @@ -3792,6 +3799,47 @@ version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403" +[[package]] +name = "halo2curves" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d380afeef3f1d4d3245b76895172018cfb087d9976a7cabcd5597775b2933e07" +dependencies = [ + "blake2", + "digest 0.10.7", + "ff", + "group", + "halo2derive", + "lazy_static", + "num-bigint", + "num-integer", + "num-traits", + "pairing", + "pasta_curves", + "paste", + "rand 0.8.5", + "rand_core 0.6.4", + "rayon", + "sha2 0.10.8", + "static_assertions", + "subtle", + "unroll", +] + +[[package]] +name = "halo2derive" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdb99e7492b4f5ff469d238db464131b86c2eaac814a78715acba369f64d2c76" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "handlebars" version = "4.5.0" @@ -3914,7 +3962,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" dependencies = [ - "crypto-mac 0.11.1", + "crypto-mac 0.11.0", "digest 0.9.0", ] @@ -4194,9 +4242,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.6" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" dependencies = [ "equivalent", "hashbrown 0.14.5", @@ -4580,6 +4628,9 @@ name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin 0.5.2", +] [[package]] name = "lazycell" @@ -5226,7 +5277,7 @@ dependencies = [ "macro_magic_core", "macro_magic_macros", "quote", - "syn 2.0.60", + "syn 2.0.79", ] [[package]] @@ -5240,7 +5291,7 @@ dependencies = [ "macro_magic_core_macros", "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.79", ] [[package]] @@ -5251,7 +5302,7 @@ checksum = "d710e1214dffbab3b5dacb21475dde7d6ed84c69ff722b3a47a782668d44fbac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.79", ] [[package]] @@ -5262,7 +5313,7 @@ checksum = "b8fb85ec1620619edf2984a7693497d4ec88a9665d8b87e942856884c92dbf2a" dependencies = [ "macro_magic_core", "quote", - "syn 2.0.60", + "syn 2.0.79", ] [[package]] @@ -6110,6 +6161,15 @@ dependencies = [ "staging-xcm-executor", ] +[[package]] +name = "pairing" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fec4625e73cf41ef4bb6846cafa6d44736525f442ba45e407c4a000a13996f" +dependencies = [ + "group", +] + [[package]] name = "pallet-asset-tx-payment" version = "4.0.0-dev" @@ -6475,7 +6535,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk?branch=release-polkadot dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.79", ] [[package]] @@ -7063,7 +7123,7 @@ dependencies = [ "proc-macro-crate 1.3.1", "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.79", ] [[package]] @@ -7393,7 +7453,7 @@ version = "3.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be30eaf4b0a9fba5336683b38de57bb86d179a35862ba6bfcf57625d006bde5b" dependencies = [ - "proc-macro-crate 2.0.2", + "proc-macro-crate 2.0.0", "proc-macro2", "quote", "syn 1.0.109", @@ -7471,6 +7531,21 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7924d1d0ad836f665c9065e26d016c673ece3993f30d340068b16f282afc1156" +[[package]] +name = "pasta_curves" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e57598f73cc7e1b2ac63c79c517b31a0877cd7c402cdcaa311b5208de7a095" +dependencies = [ + "blake2b_simd", + "ff", + "group", + "lazy_static", + "rand 0.8.5", + "static_assertions", + "subtle", +] + [[package]] name = "paste" version = "1.0.14" @@ -7483,7 +7558,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d95f5254224e617595d2cc3cc73ff0a5eaf2637519e25f03388154e9378b6ffa" dependencies = [ - "crypto-mac 0.11.1", + "crypto-mac 0.11.0", ] [[package]] @@ -7556,7 +7631,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.79", ] [[package]] @@ -7577,7 +7652,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" dependencies = [ "fixedbitset", - "indexmap 2.2.6", + "indexmap 2.5.0", ] [[package]] @@ -7597,7 +7672,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.79", ] [[package]] @@ -9060,7 +9135,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22020dfcf177fcc7bf5deaf7440af371400c67c0de14c399938d8ed4fb4645d3" dependencies = [ "proc-macro2", - "syn 2.0.60", + "syn 2.0.79", ] [[package]] @@ -9080,7 +9155,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ac2cf0f2e4f42b49f5ffd07dae8d746508ef7526c13940e5f524012ae6c6550" dependencies = [ "proc-macro2", - "syn 2.0.60", + "syn 2.0.79", ] [[package]] @@ -9124,14 +9199,22 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "2.0.2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" +checksum = "7e8366a6159044a37876a2b9817124296703c586a5c92e2c53751fa06d8d43e8" dependencies = [ - "toml_datetime", "toml_edit 0.20.2", ] +[[package]] +name = "proc-macro-crate" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" +dependencies = [ + "toml_edit 0.22.22", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -9164,14 +9247,14 @@ checksum = "3d1eaa7fa0aa1929ffdf7eeb6eac234dde6268914a14ad44d23521ab6a9b258e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.79", ] [[package]] name = "proc-macro2" -version = "1.0.81" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] @@ -9210,7 +9293,7 @@ checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.79", ] [[package]] @@ -9335,9 +9418,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -9534,7 +9617,7 @@ checksum = "5fddb4f8d99b0a2ebafc65a87a69a7b9875e4b1ae1f00db265d300ef7f28bccc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.79", ] [[package]] @@ -9551,14 +9634,14 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.4" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.6", - "regex-syntax 0.8.3", + "regex-automata 0.4.8", + "regex-syntax 0.8.5", ] [[package]] @@ -9572,13 +9655,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.6" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.3", + "regex-syntax 0.8.5", ] [[package]] @@ -9589,9 +9672,15 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.3" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "relative-path" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" [[package]] name = "resolv-conf" @@ -9781,6 +9870,36 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "rstest" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a2c585be59b6b5dd66a9d2084aa1d8bd52fbdb806eafdeffb52791147862035" +dependencies = [ + "futures 0.3.30", + "futures-timer", + "rstest_macros", + "rustc_version 0.4.1", +] + +[[package]] +name = "rstest_macros" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "825ea780781b15345a146be27eaefb05085e337e869bff01b4306a4fd4a9ad5a" +dependencies = [ + "cfg-if", + "glob", + "proc-macro-crate 3.2.0", + "proc-macro2", + "quote", + "regex", + "relative-path", + "rustc_version 0.4.1", + "syn 2.0.79", + "unicode-ident", +] + [[package]] name = "rtnetlink" version = "0.10.1" @@ -9835,9 +9954,9 @@ dependencies = [ [[package]] name = "rustc_version" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver 1.0.22", ] @@ -10112,7 +10231,7 @@ dependencies = [ "proc-macro-crate 1.3.1", "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.79", ] [[package]] @@ -11090,7 +11209,7 @@ dependencies = [ "proc-macro-crate 1.3.1", "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.79", ] [[package]] @@ -11383,7 +11502,7 @@ checksum = "11bd257a6541e141e42ca6d24ae26f7714887b47e89aa739099104c7e4d3b7fc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.79", ] [[package]] @@ -11705,7 +11824,7 @@ dependencies = [ "curve25519-dalek 4.1.2", "rand_core 0.6.4", "ring 0.17.8", - "rustc_version 0.4.0", + "rustc_version 0.4.1", "sha2 0.10.8", "subtle", ] @@ -11779,7 +11898,7 @@ dependencies = [ "proc-macro-crate 1.3.1", "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.79", ] [[package]] @@ -12031,7 +12150,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk?branch=release-polkadot dependencies = [ "quote", "sp-core-hashing", - "syn 2.0.60", + "syn 2.0.79", ] [[package]] @@ -12050,7 +12169,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk?branch=release-polkadot dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.79", ] [[package]] @@ -12267,7 +12386,7 @@ dependencies = [ "proc-macro-crate 1.3.1", "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.79", ] [[package]] @@ -12464,7 +12583,7 @@ dependencies = [ "parity-scale-codec", "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.79", ] [[package]] @@ -12792,7 +12911,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.60", + "syn 2.0.79", ] [[package]] @@ -12940,9 +13059,9 @@ dependencies = [ [[package]] name = "subtle" -version = "2.4.1" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "subtle-ng" @@ -12963,9 +13082,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.60" +version = "2.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" dependencies = [ "proc-macro2", "quote", @@ -13062,7 +13181,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.79", ] [[package]] @@ -13073,7 +13192,7 @@ checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.79", "test-case-core", ] @@ -13117,7 +13236,7 @@ checksum = "e4c60d69f36615a077cc7663b9cb8e42275722d23e58a7fa3d2c7f2915d09d04" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.79", ] [[package]] @@ -13128,7 +13247,7 @@ checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.79", ] [[package]] @@ -13291,7 +13410,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.79", ] [[package]] @@ -13377,9 +13496,9 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.3" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" dependencies = [ "serde", ] @@ -13390,11 +13509,11 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.2.6", + "indexmap 2.5.0", "serde", "serde_spanned", "toml_datetime", - "winnow", + "winnow 0.5.40", ] [[package]] @@ -13403,11 +13522,22 @@ version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" dependencies = [ - "indexmap 2.2.6", + "indexmap 2.5.0", "serde", "serde_spanned", "toml_datetime", - "winnow", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.22.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +dependencies = [ + "indexmap 2.5.0", + "toml_datetime", + "winnow 0.6.20", ] [[package]] @@ -13471,7 +13601,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.79", ] [[package]] @@ -13515,7 +13645,7 @@ dependencies = [ "proc-macro-crate 1.3.1", "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.79", ] [[package]] @@ -13722,9 +13852,9 @@ checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-normalization" @@ -13757,6 +13887,16 @@ dependencies = [ "subtle", ] +[[package]] +name = "unroll" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ad948c1cb799b1a70f836077721a92a35ac177d4daddf4c20a633786d4cf618" +dependencies = [ + "quote", + "syn 1.0.109", +] + [[package]] name = "unsigned-varint" version = "0.7.2" @@ -13889,7 +14029,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.79", "wasm-bindgen-shared", ] @@ -13923,7 +14063,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.79", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -14701,6 +14841,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "winnow" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +dependencies = [ + "memchr", +] + [[package]] name = "winreg" version = "0.50.0" @@ -14801,7 +14950,7 @@ dependencies = [ "Inflector", "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.79", ] [[package]] @@ -15071,7 +15220,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.79", ] [[package]] @@ -15091,7 +15240,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.79", ] [[package]] @@ -15116,12 +15265,17 @@ dependencies = [ name = "zrml-combo" version = "0.5.5" dependencies = [ + "ethnum", "frame-benchmarking", "frame-support", "frame-system", + "halo2curves", "parity-scale-codec", + "rstest", "scale-info", "sp-runtime", + "test-case", + "zeitgeist-primitives", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 3985992ba..a746467dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -267,13 +267,16 @@ futures = "0.3.30" jsonrpsee = "0.16.3" libfuzzer-sys = "0.4.7" more-asserts = "0.3.1" +rstest = "0.23.0" test-case = "3.3.1" url = "2.5.0" # Other (wasm) arbitrary = { version = "1.3.2", default-features = false } arrayvec = { version = "0.7.4", default-features = false } +halo2curves = { version = "0.7.0" } cfg-if = { version = "1.0.0" } +ethnum = { version = "1.4.0" } fixed = { version = "=1.15.0", default-features = false, features = ["num-traits"] } # Hashbrown works in no_std by default and default features are used in Rikiddo hashbrown = { version = "0.14.3", default-features = true } diff --git a/zrml/combo/Cargo.toml b/zrml/combo/Cargo.toml index 5a182a122..cee14a362 100644 --- a/zrml/combo/Cargo.toml +++ b/zrml/combo/Cargo.toml @@ -1,10 +1,17 @@ [dependencies] +ethnum = { workspace = true } frame-benchmarking = { workspace = true, optional = true } frame-support = { workspace = true } frame-system = { workspace = true } +halo2curves = { workspace = true } parity-scale-codec = { workspace = true, features = ["derive", "max-encoded-len"] } scale-info = { workspace = true, features = ["derive"] } sp-runtime = { workspace = true } +zeitgeist-primitives = { workspace = true } + +[dev-dependencies] +test-case = { workspace = true } +rstest = { workspace = true } [features] default = ["std"] @@ -18,6 +25,7 @@ std = [ "frame-support/std", "frame-system/std", "sp-runtime/std", + "zeitgeist-primitives/std", ] try-runtime = [ "frame-support/try-runtime", diff --git a/zrml/combo/src/lib.rs b/zrml/combo/src/lib.rs index 5ef80e751..2c8826368 100644 --- a/zrml/combo/src/lib.rs +++ b/zrml/combo/src/lib.rs @@ -15,23 +15,32 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . +// TODO Refactor so that collection IDs are their own type with an `Fq` field and an `odd` field? + #![doc = include_str!("../README.md")] #![cfg_attr(not(feature = "std"), no_std)] extern crate alloc; -// TODO Modules +mod traits; +mod types; pub use pallet::*; #[frame_support::pallet] mod pallet { use core::marker::PhantomData; - use frame_support::pallet_prelude::StorageVersion; + use frame_support::{ + pallet_prelude::{IsType, StorageVersion}, + require_transactional, transactional, + }; + use frame_system::{ensure_signed, pallet_prelude::OriginFor}; + use sp_runtime::DispatchResult; - // TODO Config #[pallet::config] - pub trait Config: frame_system::Config {} + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + } #[pallet::pallet] #[pallet::storage_version(STORAGE_VERSION)] @@ -42,9 +51,43 @@ mod pallet { // TODO Storage Items - // TODO `Event` enum + #[pallet::event] + #[pallet::generate_deposit(fn deposit_event)] + pub enum Event + where + T: Config, {} + + #[pallet::error] + pub enum Error {} + + #[pallet::call] + impl Pallet { + #[pallet::call_index(0)] + #[pallet::weight(0)] // TODO + #[transactional] + pub fn split_position(origin: OriginFor) -> DispatchResult { + let _ = ensure_signed(origin)?; + Self::do_split_position() + } + + #[pallet::call_index(1)] + #[pallet::weight(0)] // TODO + #[transactional] + pub fn merge_position(origin: OriginFor) -> DispatchResult { + let _ = ensure_signed(origin)?; + Self::do_merge_position() + } + } - // TODO `Error` enum + impl Pallet { + #[require_transactional] + fn do_split_position() -> DispatchResult { + Ok(()) + } - // TODO Dispatchables + #[require_transactional] + fn do_merge_position() -> DispatchResult { + Ok(()) + } + } } diff --git a/zrml/combo/src/traits/id_manager.rs b/zrml/combo/src/traits/id_manager.rs new file mode 100644 index 000000000..04c83ddc9 --- /dev/null +++ b/zrml/combo/src/traits/id_manager.rs @@ -0,0 +1,20 @@ +use sp_runtime::DispatchError; + +pub(crate) trait IdManager { + type Asset; + type MarketId; + type Id; + + // TODO Replace `Vec` with a more effective bit mask type. + fn get_collection_id( + parent_collection_id: Option, + market_id: Self::MarketId, + index_set: Vec, + force_max_work: bool, + ) -> Option; + + fn get_position_id( + collateral: Self::Asset, + collection_id: Self::Id, + ) -> Option; +} diff --git a/zrml/combo/src/traits/mod.rs b/zrml/combo/src/traits/mod.rs new file mode 100644 index 000000000..2685f5d14 --- /dev/null +++ b/zrml/combo/src/traits/mod.rs @@ -0,0 +1,3 @@ +mod id_manager; + +pub(crate) use id_manager::IdManager; diff --git a/zrml/combo/src/types/cryptographic_id_manager/decompressor/mod.rs b/zrml/combo/src/types/cryptographic_id_manager/decompressor/mod.rs new file mode 100644 index 000000000..f7d46f50d --- /dev/null +++ b/zrml/combo/src/types/cryptographic_id_manager/decompressor/mod.rs @@ -0,0 +1,582 @@ +/// Highest/lowest bit always refers to the big endian representation of each bit sequence. +mod tests; + +use super::typedefs::Hash; +use core::num::ParseIntError; +use ethnum::U256; +use halo2curves::{ + bn256::{Fq, G1Affine}, + ff::PrimeField, + CurveAffine, +}; + +/// Will return `None` if and only if `parent_collection_id` is not a valid collection ID. +pub(crate) fn get_collection_id( + hash: Hash, + parent_collection_id: Option, + force_max_work: bool, +) -> Option { + let mut u = decompress_hash(hash, force_max_work)?; + + if let Some(pci) = parent_collection_id { + let v = decompress_collection_id(pci)?; + let w = u + v; // Projective coordinates. + u = w.into(); // Affine coordinates. + } + + // Convert back to bytes _before_ flipping, as flipping will sometimes result in numbers larger + // than the base field modulus. + let mut bytes = u.x.to_bytes(); + bytes.reverse(); // Little-endian to big-endian. + + if u.y.is_odd().into() { + flip_second_highest_bit(&mut bytes); + } + + Some(bytes) +} + +const DECOMPRESS_HASH_MAX_ITERS: usize = 1_000; + +/// Decompresses a collection ID `hash` to a point of `alt_bn128`. The amount of work done can be +/// forced to be independent of the input by setting the `force_max_work` flag. +/// +/// We don't have mathematical proof that the points of `alt_bn128` are distributed so that the +/// required number of iterations is below the specified limit of iterations, but there's good +/// evidence that input hash requires more than `log_2(P) = 507.19338271000436` iterations. We +/// will use `1_000` iterations as maximum for now. +/// +/// Provided the assumption above is correct, this function cannot return `None`. +fn decompress_hash(hash: Hash, force_max_work: bool) -> Option { + // Calculate `odd` first, then get congruent point `x` in `Fq`. As `hash` might represent a + // larger big endian number than `field_modulus()`, the MSB of `x` might be different from the + // MSB of `x_u256`. + let odd = is_msb_set(&hash); + + // `Fq` won't let us create an element of the Galois field if the number `x` represented by + // `hash` does not satisfy `x < P`, so we need to use `U256` to calculate the remainder of `x` + // when dividing by `P`. That's the whole reason we need ethnum. + let x_u256 = U256::from_be_bytes(hash); + let mut x = Fq::from_u256(x_u256.checked_rem(field_modulus())?)?; // Infallible. + + let mut y_opt = None; + let mut dummy_x = Fq::zero(); // Used to prevent rustc from optimizing dummy work away. + let mut dummy_y = None; + for _ in 0..DECOMPRESS_HASH_MAX_ITERS { + // If `y_opt.is_some()` and we're still in the loop, then `force_max_work` is set and we're + // jus here to spin our wheels for the benchmarks. + if y_opt.is_some() { + // Perform the same calculations as below, but store them in the dummy variables to + // avoid setting off rustc optimizations. + let dummy_x = x + Fq::one(); + + let matching_y = matching_y_coordinate(dummy_x); + + if matching_y.is_some() { + dummy_y = matching_y; + } + } else { + x = x + Fq::one(); + + let matching_y = matching_y_coordinate(x); + + if matching_y.is_some() { + y_opt = matching_y; + + if !force_max_work { + break; + } + } + } + } + std::mem::forget(dummy_x); // Ensure that the dummies are considered "read" by rustc. + std::mem::forget(dummy_y); + let mut y = y_opt?; // This **should** be infallible. + + // We have two options for the y-coordinate of the corresponding point: `y` and `P - y`. If + // `odd` is set but `y` isn't odd, we switch to the other option. + if (odd && y.is_even().into()) || (!odd && y.is_odd().into()) { + y = y.neg(); + } + + G1Affine::from_xy(x, y).into() +} + +fn decompress_collection_id(mut collection_id: Hash) -> Option { + let odd = is_second_msb_set(&collection_id); + chop_off_two_highest_bits(&mut collection_id); + collection_id.reverse(); // Big-endian to little-endian. + let x_opt: Option<_> = Fq::from_bytes(&collection_id).into(); + let x = x_opt?; // Fails if `collection_id` is not a collection ID. + + let mut y = matching_y_coordinate(x)?; // Fails if `collection_id` is not a collection ID. + + // We have two options for the y-coordinate of the corresponding point: `y` and `P - y`. If + // `odd` is set but `y` isn't odd, we switch to the other option. + if (odd && y.is_even().into()) || (!odd && y.is_odd().into()) { + y = y.neg(); + } + + G1Affine::from_xy(x, y).into() +} + +fn field_modulus() -> U256 { + U256::from_be_bytes([ + 0x30, 0x64, 0x4e, 0x72, 0xe1, 0x31, 0xa0, 0x29, 0xb8, 0x50, 0x45, 0xb6, 0x81, 0x81, 0x58, + 0x5d, 0x97, 0x81, 0x6a, 0x91, 0x68, 0x71, 0xca, 0x8d, 0x3c, 0x20, 0x8c, 0x16, 0xd8, 0x7c, + 0xfd, 0x47, + ]) +} + +/// Flips the second highests bit of big-endian `bytes`. +fn flip_second_highest_bit(bytes: &mut Hash) { + bytes[0] ^= 0b01000000; +} + +/// Checks if the most significant bit of the big-endian `bytes` is set. +fn is_msb_set(bytes: &Hash) -> bool { + (bytes[0] & 0b10000000) != 0 +} + +/// Checks if the second most significant bit of the big-endian `bytes` is set. +fn is_second_msb_set(bytes: &Hash) -> bool { + (bytes[0] & 0b01000000) != 0 +} + +/// Zeroes out the two most significant bits off the big-endian `bytes`. +fn chop_off_two_highest_bits(bytes: &mut Hash) { + bytes[0] &= 0b00111111; +} + +/// Returns a value `y` of `Fq` so that `(x, y)` is a point on `alt_bn128` or `None` if there is no +/// such value. +fn matching_y_coordinate(x: Fq) -> Option { + let xx = x * x; + let xxx = x * xx; + let yy = xxx + Fq::from(3); + let y = pow_magic_number(yy); + + if y * y == yy { Some(y) } else { None } +} + +/// Returns `x` to the power of `(P + 1) / 4` where `P` is the base field modulus of `alt_bn128`. +fn pow_magic_number(mut x: Fq) -> Fq { + let x_1 = x; + x = x * x; + let x_2 = x; + x = x * x; + x = x * x; + x = x * x_2; + let x_10 = x; + x = x * x_1; + let x_11 = x; + x = x * x_10; + let x_21 = x; + x = x * x; + let x_42 = x; + x = x * x; + x = x * x_42; + x = x * x; + x = x * x; + x = x * x_42; + x = x * x_11; + let x_557 = x; + x = x * x; + x = x * x; + x = x * x_21; + let x_2249 = x; + x = x * x; + x = x * x; + x = x * x; + x = x * x_2249; + x = x * x_557; + let x_20798 = x; + x = x * x; + x = x * x; + x = x * x; + x = x * x_20798; + x = x * x_2249; + let x_189431 = x; + x = x * x_20798; + let x_210229 = x; + x = x * x; + x = x * x; + x = x * x_189431; + let x_1030347 = x; + x = x * x; + let x_2060694 = x; + x = x * x; + x = x * x; + x = x * x; + x = x * x_2060694; + x = x * x_210229; + let x_18756475 = x; + x = x * x_1030347; + let x_19786822 = x; + x = x * x; + x = x * x; + x = x * x; + x = x * x_18756475; + let x_177051051 = x; + x = x * x; + x = x * x; + x = x * x_177051051; + x = x * x; + x = x * x; + x = x * x_177051051; + x = x * x_19786822; + let x_3737858893 = x; + x = x * x; + let x_7475717786 = x; + x = x * x; + x = x * x; + x = x * x_7475717786; + x = x * x_3737858893; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x_7475717786; + x = x * x_177051051; + let x_665515934005 = x; + x = x * x; + x = x * x_665515934005; + x = x * x_3737858893; + let x_2000285660908 = x; + x = x * x; + x = x * x_2000285660908; + x = x * x; + let x_12001713965448 = x; + x = x * x; + x = x * x_12001713965448; + let x_36005141896344 = x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x_36005141896344; + x = x * x_12001713965448; + x = x * x_665515934005; + let x_1200836912478805 = x; + x = x * x_2000285660908; + let x_1202837198139713 = x; + x = x * x; + x = x * x_1200836912478805; + let x_3606511308758231 = x; + x = x * x_1202837198139713; + let x_4809348506897944 = x; + x = x * x_3606511308758231; + let x_8415859815656175 = x; + x = x * x_4809348506897944; + let x_13225208322554119 = x; + x = x * x_8415859815656175; + let x_21641068138210294 = x; + x = x * x; + x = x * x_21641068138210294; + x = x * x; + x = x * x_13225208322554119; + let x_143071617151815883 = x; + x = x * x; + x = x * x; + x = x * x_21641068138210294; + let x_593927536745473826 = x; + x = x * x_143071617151815883; + let x_736999153897289709 = x; + x = x * x; + x = x * x_736999153897289709; + x = x * x_593927536745473826; + let x_2804924998437342953 = x; + x = x * x_736999153897289709; + let x_3541924152334632662 = x; + x = x * x_2804924998437342953; + let x_6346849150771975615 = x; + x = x * x_3541924152334632662; + let x_9888773303106608277 = x; + x = x * x; + x = x * x; + x = x * x_9888773303106608277; + x = x * x_6346849150771975615; + let x_55790715666305017000 = x; + x = x * x; + x = x * x_55790715666305017000; + x = x * x_9888773303106608277; + let x_177260920302021659277 = x; + x = x * x_55790715666305017000; + let x_233051635968326676277 = x; + x = x * x_177260920302021659277; + let x_410312556270348335554 = x; + x = x * x_233051635968326676277; + let x_643364192238675011831 = x; + x = x * x_410312556270348335554; + let x_1053676748509023347385 = x; + x = x * x; + x = x * x_1053676748509023347385; + x = x * x; + x = x * x_643364192238675011831; + let x_6965424683292815096141 = x; + x = x * x_1053676748509023347385; + let x_8019101431801838443526 = x; + x = x * x; + x = x * x_8019101431801838443526; + x = x * x; + x = x * x_6965424683292815096141; + let x_55080033274103845757297 = x; + x = x * x; + let x_110160066548207691514594 = x; + x = x * x; + x = x * x; + x = x * x_110160066548207691514594; + x = x * x_55080033274103845757297; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x_110160066548207691514594; + x = x * x_8019101431801838443526; + let x_9812265024222286383242392 = x; + x = x * x_55080033274103845757297; + let x_9867345057496390228999689 = x; + x = x * x_9812265024222286383242392; + let x_19679610081718676612242081 = x; + x = x * x_9867345057496390228999689; + let x_29546955139215066841241770 = x; + x = x * x; + x = x * x_29546955139215066841241770; + x = x * x; + x = x * x; + x = x * x; + x = x * x_29546955139215066841241770; + x = x * x_19679610081718676612242081; + let x_758353488562095347643286331 = x; + x = x * x; + x = x * x_758353488562095347643286331; + x = x * x; + x = x * x_29546955139215066841241770; + let x_4579667886511787152700959756 = x; + x = x * x; + x = x * x_4579667886511787152700959756; + x = x * x_758353488562095347643286331; + let x_14497357148097456805746165599 = x; + x = x * x_4579667886511787152700959756; + let x_19077025034609243958447125355 = x; + x = x * x; + x = x * x; + x = x * x_14497357148097456805746165599; + let x_90805457286534432639534667019 = x; + x = x * x_19077025034609243958447125355; + let x_109882482321143676597981792374 = x; + x = x * x; + x = x * x_90805457286534432639534667019; + let x_310570421928821785835498251767 = x; + x = x * x_109882482321143676597981792374; + let x_420452904249965462433480044141 = x; + x = x * x_310570421928821785835498251767; + let x_731023326178787248268978295908 = x; + x = x * x; + x = x * x_731023326178787248268978295908; + x = x * x_420452904249965462433480044141; + let x_2613522882786327207240414931865 = x; + x = x * x_731023326178787248268978295908; + let x_3344546208965114455509393227773 = x; + x = x * x; + x = x * x_3344546208965114455509393227773; + x = x * x; + x = x * x; + x = x * x_2613522882786327207240414931865; + let x_42748077390367700673353133665141 = x; + x = x * x; + x = x * x; + x = x * x; + x = x * x_42748077390367700673353133665141; + x = x * x_3344546208965114455509393227773; + let x_388077242722274420515687596214042 = x; + x = x * x_42748077390367700673353133665141; + let x_430825320112642121189040729879183 = x; + x = x * x; + let x_861650640225284242378081459758366 = x; + x = x * x_430825320112642121189040729879183; + x = x * x; + x = x * x; + x = x * x_861650640225284242378081459758366; + x = x * x_388077242722274420515687596214042; + let x_6419631724299264117162257814522604 = x; + x = x * x; + x = x * x_430825320112642121189040729879183; + let x_13270088768711170355513556358924391 = x; + x = x * x_6419631724299264117162257814522604; + let x_19689720493010434472675814173446995 = x; + x = x * x_13270088768711170355513556358924391; + let x_32959809261721604828189370532371386 = x; + x = x * x_19689720493010434472675814173446995; + let x_52649529754732039300865184705818381 = x; + x = x * x_32959809261721604828189370532371386; + let x_85609339016453644129054555238189767 = x; + x = x * x_52649529754732039300865184705818381; + let x_138258868771185683429919739944008148 = x; + x = x * x; + x = x * x_138258868771185683429919739944008148; + let x_414776606313557050289759219832024444 = x; + x = x * x_138258868771185683429919739944008148; + x = x * x; + x = x * x; + x = x * x_414776606313557050289759219832024444; + x = x * x_85609339016453644129054555238189767; + let x_2712527845668981629297529614174344579 = x; + x = x * x_138258868771185683429919739944008148; + let x_2850786714440167312727449354118352727 = x; + x = x * x_2712527845668981629297529614174344579; + let x_5563314560109148942024978968292697306 = x; + x = x * x_2850786714440167312727449354118352727; + let x_8414101274549316254752428322411050033 = x; + x = x * x_5563314560109148942024978968292697306; + let x_13977415834658465196777407290703747339 = x; + x = x * x; + x = x * x_13977415834658465196777407290703747339; + x = x * x_8414101274549316254752428322411050033; + let x_50346348778524711845084650194522292050 = x; + x = x * x_13977415834658465196777407290703747339; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x = x * x; + x * x_50346348778524711845084650194522292050 +} + +trait FromU256 +where + Self: Sized, +{ + fn from_u256(x: U256) -> Option; +} + +impl FromU256 for Fq { + fn from_u256(x: U256) -> Option { + let le_bytes = x.to_le_bytes(); + let ct_opt = Fq::from_bytes(&le_bytes); + + ct_opt.into() + } +} diff --git a/zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/decompress_collection_id.rs b/zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/decompress_collection_id.rs new file mode 100644 index 000000000..2308e8d2b --- /dev/null +++ b/zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/decompress_collection_id.rs @@ -0,0 +1,561 @@ +use super::*; +use test_case::test_case; + +#[test_case( + [0x16, 0x74, 0xab, 0x10, 0xed, 0xf8, 0xc4, 0xe2, 0x25, 0x72, 0x9e, 0x20, 0x9a, 0x58, 0x75, 0xa1, 0x9f, 0x14, 0x46, 0xba, 0xec, 0x3b, 0x30, 0xdf, 0x9b, 0xa8, 0x65, 0x75, 0xd5, 0x2d, 0xe3, 0xd3], + ( + "0x1674ab10edf8c4e225729e209a5875a19f1446baec3b30df9ba86575d52de3d3", + "0x1919edaf92ff08c3c5a2a5dafef1a0c01376dab9681be7fbe3895a18b96af98e", + ) +)] +#[test_case( + [0x02, 0xfd, 0xc0, 0xbc, 0xde, 0x3b, 0x3d, 0xa1, 0xb4, 0xd6, 0x0d, 0x2f, 0x3f, 0x2c, 0xe7, 0x51, 0xd5, 0x20, 0xce, 0x53, 0xe0, 0x10, 0xb1, 0x16, 0x85, 0x9a, 0x8a, 0x9d, 0xe4, 0x6d, 0x45, 0x5e], + ( + "0x2fdc0bcde3b3da1b4d60d2f3f2ce751d520ce53e010b116859a8a9de46d455e", + "0x23a3ae9baa8ed04165a7ebfdb1ffb683d494fc3bf6e402c23b7de5b8ca3b41f6", + ) +)] +#[test_case( + [0x18, 0x0a, 0x0e, 0x6e, 0x26, 0x13, 0xbc, 0x6e, 0x78, 0x1b, 0x9d, 0x8d, 0x9f, 0xc4, 0x16, 0xe0, 0x51, 0xbb, 0xa5, 0xe3, 0x83, 0x63, 0x93, 0xb2, 0x3e, 0x4f, 0x1d, 0x37, 0x7e, 0x2d, 0x52, 0x2d], + ( + "0x180a0e6e2613bc6e781b9d8d9fc416e051bba5e3836393b23e4f1d377e2d522d", + "0x1bc28847ccf82c7345687caba07a45b130e26e3f4489ceb47524e08a37d28d26", + ) +)] +#[test_case( + [0x1d, 0x9b, 0x46, 0x27, 0xaa, 0x60, 0x6d, 0x7d, 0xda, 0xd6, 0xfe, 0xe6, 0x5d, 0xd9, 0x52, 0x5d, 0x75, 0xac, 0x9b, 0x00, 0x14, 0x42, 0xfa, 0xe6, 0x5a, 0x6e, 0xfd, 0x8f, 0xd4, 0x36, 0x9e, 0xb7], + ( + "0x1d9b4627aa606d7ddad6fee65dd9525d75ac9b001442fae65a6efd8fd4369eb7", + "0x20181bfe3b6cfce9bebb4b3870ddbd9f0cc1bdfff9f15f9bc85debc254ab4b9c", + ) +)] +#[test_case( + [0x1d, 0xbf, 0x33, 0x21, 0x4f, 0x0a, 0xbe, 0xab, 0x8e, 0x39, 0x97, 0xf7, 0x6c, 0x79, 0x62, 0x90, 0x79, 0x5a, 0xc5, 0xc5, 0x1f, 0x51, 0xa7, 0xfb, 0x66, 0x25, 0x8c, 0x5a, 0x72, 0x07, 0x78, 0x9d], + ( + "0x1dbf33214f0abeab8e3997f76c796290795ac5c51f51a7fb66258c5a7207789d", + "0x241860400ae39bd169011fc88731985403051a72222cc82db0f443cbd2f9b886", + ) +)] +#[test_case( + [0x22, 0x3c, 0x15, 0x71, 0x4d, 0x71, 0x7d, 0x70, 0x08, 0x31, 0x72, 0xa2, 0x60, 0xa9, 0x6e, 0xb9, 0xe0, 0x13, 0x40, 0x1b, 0xdd, 0x3e, 0xbe, 0x00, 0xd4, 0x71, 0x49, 0x8c, 0xac, 0x52, 0x45, 0x1a], + ( + "0x223c15714d717d70083172a260a96eb9e013401bdd3ebe00d471498cac52451a", + "0xe05de1bd5c32a55bbf45ebbb59bd406ea680b54f81bcba4a0ba2ec481d1d78e", + ) +)] +#[test_case( + [0x23, 0xb5, 0x47, 0xd2, 0x96, 0xbe, 0x38, 0x65, 0x86, 0x60, 0x33, 0xfe, 0xbb, 0xe9, 0x4a, 0x12, 0x49, 0x2c, 0x81, 0x94, 0x38, 0xfa, 0x7b, 0x5a, 0xd6, 0xc2, 0x0c, 0xbf, 0xb2, 0x82, 0x43, 0x0d], + ( + "0x23b547d296be3865866033febbe94a12492c819438fa7b5ad6c20cbfb282430d", + "0x12cdd3c3f897dbc6237d317b1cc607916fafe3775d2971ee40c5248ee18e2b2a", + ) +)] +#[test_case( + [0x0c, 0x05, 0x3e, 0xb3, 0x09, 0xe1, 0x48, 0xe1, 0xe9, 0xde, 0xb2, 0x46, 0xe4, 0xee, 0x89, 0x74, 0x90, 0xed, 0xd5, 0x4e, 0x26, 0xae, 0x27, 0x56, 0xf1, 0xc4, 0x94, 0x0f, 0x28, 0x84, 0x16, 0xe1], + ( + "0xc053eb309e148e1e9deb246e4ee897490edd54e26ae2756f1c4940f288416e1", + "0x23d95d4f591b78e62a38e3d9c4cd40642303f54ad08e94139be77316ea58a16a", + ) +)] +#[test_case( + [0x0b, 0x59, 0x17, 0x6a, 0xba, 0xb9, 0x17, 0xe7, 0x72, 0xfe, 0x94, 0x50, 0xa8, 0x69, 0xcf, 0x62, 0xc8, 0x32, 0x88, 0x4a, 0x0f, 0xfd, 0xb6, 0x06, 0xbb, 0x6b, 0x3f, 0xa7, 0x0c, 0x1d, 0xb9, 0x1a], + ( + "0xb59176abab917e772fe9450a869cf62c832884a0ffdb606bb6b3fa70c1db91a", + "0x1420a6cb7d026a2c9cb2c05f7ff86afc73a11a6dfd90ce540cfe02d19372524e", + ) +)] +#[test_case( + [0x1e, 0x08, 0x14, 0xc5, 0xe1, 0x64, 0x5b, 0x62, 0x5b, 0x9d, 0xfc, 0xff, 0xe9, 0x7e, 0x49, 0x77, 0x32, 0xf5, 0xfc, 0x65, 0x06, 0x1c, 0x75, 0xf0, 0x06, 0x06, 0x92, 0xb7, 0xa2, 0xea, 0x39, 0x83], + ( + "0x1e0814c5e1645b625b9dfcffe97e497732f5fc65061c75f0060692b7a2ea3983", + "0x1506fac00eed4e27b88c2d07811b0e1586a42494e9f5acf95dac46d25ab93b00", + ) +)] +#[test_case( + [0x07, 0x50, 0x54, 0x78, 0x15, 0x5f, 0xcf, 0x43, 0x5d, 0x96, 0x77, 0xcc, 0x58, 0x7c, 0x85, 0x1e, 0x47, 0x02, 0xb7, 0x3d, 0xc2, 0xd8, 0xc6, 0xf5, 0x16, 0x7d, 0xbb, 0x84, 0x6c, 0x72, 0xbd, 0xb3], + ( + "0x7505478155fcf435d9677cc587c851e4702b73dc2d8c6f5167dbb846c72bdb3", + "0xce98d3afa473f8a47d734aa55dd74f3f6d18faf46689346e9930dfd2689877a", + ) +)] +#[test_case( + [0x28, 0x0a, 0x7e, 0xbf, 0xaf, 0xce, 0x97, 0x43, 0x55, 0x0f, 0x42, 0x8f, 0xc2, 0xd4, 0xdd, 0x28, 0x8f, 0xa8, 0x13, 0x24, 0xcb, 0x6e, 0x10, 0xa6, 0x7b, 0x42, 0x34, 0x5f, 0x1b, 0x76, 0xc6, 0x5d], + ( + "0x280a7ebfafce9743550f428fc2d4dd288fa81324cb6e10a67b42345f1b76c65d", + "0x26ca96ef76f1c517684cfeb53b22157e340990317c020fcf8b631e7157e4ce92", + ) +)] +#[test_case( + [0x08, 0x46, 0xe2, 0x53, 0x97, 0x46, 0xca, 0x06, 0xad, 0xa1, 0x8b, 0x22, 0xba, 0x2f, 0x66, 0xda, 0xcc, 0xaf, 0x0e, 0x9a, 0x99, 0x5c, 0x29, 0x35, 0xce, 0x8d, 0xbc, 0x55, 0x20, 0x8d, 0xcc, 0xbb], + ( + "0x846e2539746ca06ada18b22ba2f66daccaf0e9a995c2935ce8dbc55208dccbb", + "0x1336db44313546d7e740d3b1ed924eb8dbbd73dbb4399b7e37549562ad789930", + ) +)] +#[test_case( + [0x1b, 0xe5, 0xd3, 0x49, 0x51, 0x06, 0x3a, 0x47, 0x6a, 0x3c, 0x78, 0xa7, 0xdb, 0x40, 0x85, 0x5c, 0x49, 0xf2, 0xc5, 0x70, 0xc5, 0x06, 0xb8, 0x5e, 0x3b, 0xef, 0x44, 0x05, 0x68, 0xab, 0x02, 0xab], + ( + "0x1be5d34951063a476a3c78a7db40855c49f2c570c506b85e3bef440568ab02ab", + "0x5d5ebaef6981b87cde5ed99565a36d4eafbc6fbacab1f0cd15e509fba71d8c2", + ) +)] +#[test_case( + [0x01, 0x37, 0xe1, 0x21, 0xfc, 0x1b, 0xc8, 0x0c, 0x5b, 0x30, 0xf2, 0xad, 0x1a, 0x08, 0xe9, 0x26, 0x53, 0x30, 0xfb, 0x33, 0x07, 0x84, 0xa5, 0x63, 0x43, 0xc2, 0x9a, 0xc0, 0x46, 0x20, 0x1d, 0x1b], + ( + "0x137e121fc1bc80c5b30f2ad1a08e9265330fb330784a56343c29ac046201d1b", + "0x18d3facbbf735827083ff4e12a625ba2218edffef5c8815afdefea6528801fec", + ) +)] +#[test_case( + [0x2d, 0xf7, 0x8d, 0xdd, 0x64, 0xbc, 0xb7, 0x53, 0x60, 0xa7, 0x88, 0x16, 0x35, 0x29, 0xe2, 0x84, 0x95, 0x04, 0x08, 0x4a, 0x4b, 0x79, 0x91, 0x17, 0x28, 0xee, 0x33, 0x03, 0x4c, 0x7f, 0x6c, 0xd3], + ( + "0x2df78ddd64bcb75360a788163529e2849504084a4b79911728ee33034c7f6cd3", + "0x1703b907563d006f1df3fe65c0ffff73d469b58b831827d2b68e12278bf9f0e", + ) +)] +#[test_case( + [0x11, 0x05, 0x42, 0x04, 0x50, 0x3c, 0x67, 0x3f, 0x29, 0x4d, 0xf5, 0x82, 0xe7, 0x19, 0xe9, 0x5c, 0x40, 0x50, 0x2b, 0x65, 0xda, 0x36, 0xae, 0x0b, 0x05, 0x1c, 0xea, 0x5b, 0xa3, 0x80, 0x24, 0x7f], + ( + "0x11054204503c673f294df582e719e95c40502b65da36ae0b051cea5ba380247f", + "0xe9c934bcf52dfdefbcd8b9b1d669e41924ae89fb758d1bfacdc33c6ab98b6fc", + ) +)] +#[test_case( + [0x09, 0x1d, 0x74, 0x16, 0xbb, 0xf0, 0x48, 0x03, 0x92, 0x0a, 0x1f, 0x84, 0xd1, 0xd5, 0x63, 0x28, 0x0d, 0xbb, 0x5a, 0x6e, 0x3b, 0x03, 0x3f, 0xce, 0x74, 0xaf, 0x4a, 0x6d, 0x63, 0xf9, 0x02, 0xe3], + ( + "0x91d7416bbf04803920a1f84d1d563280dbb5a6e3b033fce74af4a6d63f902e3", + "0x2976e2bde1f1b94ba5a82a3323351f48148a1e518ac2ed0aa0070fcbd12a9784", + ) +)] +#[test_case( + [0x1b, 0x09, 0x08, 0xb9, 0xc1, 0xf2, 0x25, 0xa8, 0xfe, 0xf8, 0xf1, 0xff, 0x1d, 0x89, 0xf8, 0x65, 0x44, 0x06, 0x5a, 0xb2, 0xf5, 0x28, 0xed, 0x8a, 0x88, 0x47, 0x39, 0x04, 0x4b, 0x9b, 0x5d, 0x0d], + ( + "0x1b0908b9c1f225a8fef8f1ff1d89f86544065ab2f528ed8a884739044b9b5d0d", + "0x13bd194272a6615dfbfae1f888ace71eb2c035973b841c47cabbd5f617b55e5c", + ) +)] +#[test_case( + [0x1e, 0xb7, 0xf8, 0x6e, 0x71, 0x58, 0x7f, 0x50, 0x91, 0x19, 0xec, 0xe1, 0xb1, 0x90, 0x01, 0xfa, 0xc7, 0xbd, 0x45, 0x79, 0xa1, 0xe1, 0xae, 0xdf, 0xca, 0x4e, 0x11, 0x9a, 0x77, 0x78, 0xc0, 0xe0], + ( + "0x1eb7f86e71587f509119ece1b19001fac7bd4579a1e1aedfca4e119a7778c0e0", + "0x14a7107c10f3b98153f4750d35b7dc5713defeab87d7ce4712ed84b7657efb84", + ) +)] +#[test_case( + [0x18, 0x2f, 0x02, 0x1a, 0xcc, 0x92, 0x56, 0x45, 0x5e, 0x36, 0xf8, 0x2a, 0xca, 0xec, 0x16, 0xbd, 0xd4, 0x53, 0x6d, 0x1d, 0xca, 0xa1, 0xcd, 0x4b, 0xa0, 0x66, 0xb7, 0xba, 0xb4, 0x06, 0x7d, 0x72], + ( + "0x182f021acc9256455e36f82acaec16bdd4536d1dcaa1cd4ba066b7bab4067d72", + "0x1f326b433282b1e92c7012bf026405427d2490d9e28e6c01273f988255b9983c", + ) +)] +#[test_case( + [0x13, 0xbc, 0xaa, 0xb8, 0x01, 0xe0, 0x94, 0x26, 0xf8, 0xda, 0xa9, 0x2b, 0xf2, 0xca, 0x83, 0x28, 0x2a, 0xe3, 0xed, 0x70, 0xcc, 0x8c, 0x27, 0x7a, 0xa3, 0x44, 0xb8, 0xfe, 0xb9, 0x72, 0x81, 0x8c], + ( + "0x13bcaab801e09426f8daa92bf2ca83282ae3ed70cc8c277aa344b8feb972818c", + "0xc1abae2e25a39dcc9054e7122a7403439b3a0f0ebbddfe3c1ac26c8023f2cc", + ) +)] +#[test_case( + [0x1d, 0x87, 0x67, 0x17, 0x6e, 0xc6, 0xd9, 0x75, 0x96, 0xd0, 0x4e, 0x6b, 0xd7, 0x02, 0x4a, 0xa1, 0xcf, 0x32, 0x59, 0x50, 0x89, 0xb6, 0x45, 0x17, 0xa4, 0x3c, 0xd1, 0x0c, 0x1f, 0x99, 0x01, 0xbf], + ( + "0x1d8767176ec6d97596d04e6bd7024aa1cf32595089b64517a43cd10c1f9901bf", + "0x2302a48e833e3c702ed1eb82eebf8edb8b2cd48e03516061723ee87430a8d06a", + ) +)] +#[test_case( + [0x06, 0xfd, 0x22, 0x53, 0x3c, 0xcd, 0x75, 0x7c, 0xb6, 0xdb, 0xfd, 0x1d, 0x32, 0xbe, 0xbb, 0x29, 0x30, 0x3d, 0xa7, 0x1b, 0xc3, 0x34, 0x6b, 0x96, 0xf8, 0x76, 0x6e, 0x7e, 0xbd, 0xbf, 0x04, 0x61], + ( + "0x6fd22533ccd757cb6dbfd1d32bebb29303da71bc3346b96f8766e7ebdbf0461", + "0x246972fcae26d8b93dc62ffc2462c50cdfe22af01ca802f4072349ea4823b34e", + ) +)] +#[test_case( + [0x2d, 0x8f, 0x77, 0x8c, 0xfe, 0xbd, 0x03, 0x80, 0x95, 0xf3, 0x03, 0x8d, 0xdf, 0x86, 0x25, 0x44, 0xf8, 0x79, 0x8a, 0x64, 0xfe, 0x42, 0x33, 0x04, 0x7a, 0x2d, 0x29, 0x0e, 0xef, 0x4f, 0xf5, 0xd6], + ( + "0x2d8f778cfebd038095f3038ddf862544f8798a64fe4233047a2d290eef4ff5d6", + "0x27d0fab4ecf2e55a22af1f3f4d44e79ab133a2b9baecc4a59ae044d1c4ca4d52", + ) +)] +#[test_case( + [0x01, 0x55, 0x8a, 0xd8, 0xdd, 0xf9, 0x24, 0xe5, 0x75, 0x8f, 0x4c, 0x29, 0x4b, 0x56, 0xfc, 0x27, 0x1f, 0xb2, 0xa8, 0x38, 0x2a, 0xaa, 0x86, 0x0d, 0x94, 0x3f, 0x52, 0x49, 0xa5, 0xe7, 0x63, 0x34], + ( + "0x1558ad8ddf924e5758f4c294b56fc271fb2a8382aaa860d943f5249a5e76334", + "0x155645307359f5a7a6c304a353715fd8023809355a255daf4854f026d92693dc", + ) +)] +#[test_case( + [0x1e, 0x3c, 0xd8, 0xb4, 0xa8, 0x9e, 0x71, 0x63, 0x8a, 0xbf, 0xb0, 0x10, 0xe7, 0xfc, 0x77, 0x9c, 0xe9, 0x59, 0xe6, 0x39, 0x66, 0x73, 0x26, 0x37, 0x12, 0x83, 0x7c, 0xf0, 0xed, 0x76, 0x63, 0x91], + ( + "0x1e3cd8b4a89e71638abfb010e7fc779ce959e6396673263712837cf0ed766391", + "0x11d80dd5dd67cc67fcfe9bed95cfc2c4101526932088c041239885d723f0bc86", + ) +)] +#[test_case( + [0x22, 0x60, 0x1d, 0x97, 0x77, 0x53, 0x1c, 0x8c, 0x68, 0x75, 0x89, 0x8e, 0x47, 0xff, 0xd8, 0x04, 0xb2, 0x21, 0x4e, 0xc2, 0x41, 0x1f, 0x40, 0x43, 0x25, 0x9c, 0x45, 0x76, 0xc7, 0x4e, 0x75, 0xfb], + ( + "0x22601d9777531c8c6875898e47ffd804b2214ec2411f4043259c4576c74e75fb", + "0xe61684d4e5ffd436cc8cc5cf7de02d8a3c373b396b72f9692e6694b998164c2", + ) +)] +#[test_case( + [0x27, 0x01, 0xa9, 0xee, 0x7c, 0x63, 0x65, 0xce, 0x47, 0x75, 0x21, 0x31, 0xe9, 0xb9, 0xdd, 0x46, 0xb4, 0xc0, 0x48, 0xa9, 0x0d, 0x92, 0xf2, 0xe2, 0xf6, 0xae, 0xbb, 0x22, 0x6e, 0x58, 0xf0, 0x40], + ( + "0x2701a9ee7c6365ce47752131e9b9dd46b4c048a90d92f2e2f6aebb226e58f040", + "0x1875a82988dfe307569b5171ba09cc1707e1b03ecd3084f3251395a7894f0c22", + ) +)] +#[test_case( + [0x08, 0xbb, 0x8c, 0x18, 0x75, 0x35, 0x03, 0x40, 0x36, 0x3e, 0xe4, 0x35, 0x02, 0xba, 0x73, 0xf6, 0x77, 0x73, 0x3f, 0x29, 0xb2, 0x25, 0x2d, 0xf1, 0x89, 0x72, 0xcc, 0x96, 0x4b, 0xd4, 0x62, 0xc5], + ( + "0x8bb8c1875350340363ee43502ba73f677733f29b2252df18972cc964bd462c5", + "0x299bdb13d2514ee0677704821b2d2748def74762a22fd2bf649ceb17b91f9996", + ) +)] +#[test_case( + [0x01, 0xd7, 0x14, 0x30, 0x44, 0xba, 0x51, 0x3f, 0x92, 0x9f, 0xe7, 0x38, 0xd8, 0x0b, 0xd8, 0x4a, 0x45, 0x5e, 0x2b, 0xa9, 0x93, 0x59, 0x87, 0xaa, 0x7f, 0x8c, 0xf7, 0x7e, 0xc1, 0x8c, 0xf2, 0x0b], + ( + "0x1d7143044ba513f929fe738d80bd84a455e2ba9935987aa7f8cf77ec18cf20b", + "0x1a4e9dedf0f06f7c20e2c324c84e74f58b2c4845c3642ec20b3eb683e75b6edc", + ) +)] +#[test_case( + [0x07, 0x8a, 0x2a, 0x4c, 0x2e, 0xea, 0x7a, 0x70, 0x4a, 0x12, 0x05, 0xd4, 0x96, 0xdb, 0x94, 0x62, 0xe1, 0xee, 0xb1, 0xcb, 0x30, 0xcd, 0xd8, 0xf9, 0xc3, 0xc6, 0xca, 0x42, 0x3a, 0xdc, 0x61, 0xca], + ( + "0x78a2a4c2eea7a704a1205d496db9462e1eeb1cb30cdd8f9c3c6ca423adc61ca", + "0x5e7dd7f679c96cdf9509fb3d4db66af20f28a8dcc2622f1c90419110b465828", + ) +)] +#[test_case( + [0x1d, 0xbe, 0x17, 0xe6, 0x50, 0x79, 0x12, 0x6c, 0xaf, 0x00, 0x09, 0x7b, 0xdf, 0x54, 0x7c, 0x44, 0x57, 0xc6, 0x15, 0x1f, 0x4c, 0x6b, 0x90, 0xf4, 0xdc, 0x54, 0x24, 0xf5, 0x66, 0xdf, 0x0e, 0xe7], + ( + "0x1dbe17e65079126caf00097bdf547c4457c6151f4c6b90f4dc5424f566df0ee7", + "0x1f2fcbc14e2148084446caa954a2e0765dcf8af48e69dc186de83efa94fc806e", + ) +)] +#[test_case( + [0x2d, 0xec, 0xa9, 0x23, 0x55, 0x5c, 0x5c, 0xfc, 0xa7, 0x97, 0x2d, 0xb2, 0xb8, 0x38, 0xb5, 0x68, 0xef, 0xef, 0x51, 0xfa, 0x44, 0x72, 0x4c, 0x66, 0x4c, 0xc0, 0x45, 0x2a, 0xb9, 0xff, 0x7d, 0x63], + ( + "0x2deca923555c5cfca7972db2b838b568efef51fa44724c664cc0452ab9ff7d63", + "0x2d47b4ffdebf3532efc6490bab958d393fd785dd02bbc2b03fc24a9678ee304", + ) +)] +#[test_case( + [0x01, 0x98, 0x31, 0xeb, 0xf6, 0xa1, 0x58, 0x81, 0x45, 0x57, 0xfe, 0x02, 0x9a, 0x45, 0x37, 0xd5, 0xbf, 0x0f, 0xa3, 0xee, 0x84, 0xba, 0x43, 0x56, 0xe0, 0xe5, 0x98, 0x5f, 0x11, 0x29, 0x4a, 0xa4], + ( + "0x19831ebf6a158814557fe029a4537d5bf0fa3ee84ba4356e0e5985f11294aa4", + "0x2c8cad155eebb55bfd8492efade248ccee03202f5efb361904f74bbba5a17410", + ) +)] +#[test_case( + [0x2c, 0x72, 0xbe, 0xfc, 0x77, 0xa2, 0x88, 0xdb, 0xc8, 0x9f, 0xd6, 0x11, 0xcf, 0x22, 0x73, 0x5c, 0x64, 0x4a, 0x34, 0xad, 0x2b, 0xb0, 0x49, 0x20, 0xd1, 0x62, 0x96, 0xc8, 0x77, 0x0e, 0x81, 0x7b], + ( + "0x2c72befc77a288dbc89fd611cf22735c644a34ad2bb04920d16296c8770e817b", + "0x68230ca4c35321ef952a1336e48b5b99f74baeb5b4ff2ad6481e652274eb984", + ) +)] +#[test_case( + [0x28, 0x26, 0x3b, 0xc4, 0xaa, 0xed, 0xbc, 0xcc, 0xd2, 0xf6, 0x87, 0xa2, 0x05, 0x60, 0x48, 0x3c, 0x6a, 0x0a, 0xee, 0x2c, 0x89, 0x49, 0x74, 0x45, 0x75, 0xad, 0xc1, 0xf8, 0x1d, 0x4f, 0x72, 0xda], + ( + "0x28263bc4aaedbcccd2f687a20560483c6a0aee2c8949744575adc1f81d4f72da", + "0x1bfd7ddceed64b57a5b069ae92b2e7d56706c7a9dc56f4bbd82d986ee95603c6", + ) +)] +#[test_case( + [0x26, 0x38, 0x46, 0x0e, 0x75, 0x6c, 0x86, 0x2b, 0x10, 0xbe, 0x8b, 0xda, 0x35, 0x13, 0x9a, 0xa7, 0xa6, 0x80, 0xcb, 0xf8, 0xab, 0x74, 0x0a, 0x1a, 0xdc, 0xa9, 0x6e, 0xd2, 0x2e, 0xf0, 0xb1, 0x6f], + ( + "0x2638460e756c862b10be8bda35139aa7a680cbf8ab740a1adca96ed22ef0b16f", + "0xde2e2c86e45de9a933cc052dd17707b8b5309e39914a0f9c79c134d76147b04", + ) +)] +#[test_case( + [0x05, 0x52, 0x6b, 0xc9, 0xa0, 0xd7, 0x16, 0xe6, 0x66, 0x70, 0xd2, 0x31, 0x9e, 0x04, 0x1e, 0x46, 0xc9, 0x41, 0x64, 0xc4, 0x1c, 0x0c, 0xa7, 0x12, 0xe8, 0x11, 0x7b, 0x1a, 0xf6, 0x46, 0x76, 0xf8], + ( + "0x5526bc9a0d716e66670d2319e041e46c94164c41c0ca712e8117b1af64676f8", + "0x25e29f0a37ed937d2c226650fc78037790ea9c6d80baef29fa4539438bf853d8", + ) +)] +#[test_case( + [0x2b, 0x70, 0xd4, 0xe9, 0x4d, 0x8e, 0x35, 0x49, 0xd6, 0x09, 0x53, 0xf5, 0x18, 0x65, 0x9b, 0xb8, 0x54, 0xf2, 0x22, 0x7c, 0x5a, 0x88, 0xde, 0x27, 0xdb, 0x77, 0x70, 0x51, 0x8e, 0xd3, 0xe9, 0x31], + ( + "0x2b70d4e94d8e3549d60953f518659bb854f2227c5a88de27db7770518ed3e931", + "0x1c844b23b907c8244677b9bd1b3c64edaa47ac04537c19762d7b38d43cd1f148", + ) +)] +#[test_case( + [0x2e, 0x53, 0xd1, 0xcd, 0xd2, 0x8c, 0x7f, 0x6c, 0x2e, 0xa0, 0xc3, 0xc3, 0x2e, 0xea, 0x02, 0x28, 0x51, 0x10, 0xeb, 0xb9, 0xcc, 0x50, 0x7a, 0xa0, 0xc1, 0x2f, 0x1e, 0x33, 0xf7, 0x6e, 0xdf, 0x74], + ( + "0x2e53d1cdd28c7f6c2ea0c3c32eea02285110ebb9cc507aa0c12f1e33f76edf74", + "0x1a87019d19b0108728fa9e79c9083ad6e8688989f09920354380ac2721827282", + ) +)] +#[test_case( + [0x15, 0x69, 0x8b, 0x00, 0x9f, 0x37, 0xf8, 0xa4, 0x64, 0x62, 0xdb, 0xc2, 0x68, 0x8f, 0xff, 0xee, 0xb6, 0x78, 0x71, 0x30, 0xd8, 0xc1, 0xd6, 0xb2, 0x6d, 0x5b, 0xf8, 0xa1, 0x8d, 0x56, 0x27, 0xbf], + ( + "0x15698b009f37f8a46462dbc2688fffeeb6787130d8c1d6b26d5bf8a18d5627bf", + "0x189c4535196083e9a50d66efef26e4d599879d0244ddc08978315df04b41533a", + ) +)] +#[test_case( + [0x23, 0xa4, 0x90, 0x60, 0xff, 0xf2, 0xef, 0x43, 0x29, 0xbc, 0xe4, 0x7d, 0x12, 0x7a, 0x0c, 0x11, 0x3d, 0x66, 0xf4, 0xf8, 0xc1, 0x4f, 0x23, 0x94, 0xc7, 0x97, 0x6c, 0xff, 0x59, 0x5b, 0xd5, 0xaa], + ( + "0x23a49060fff2ef4329bce47d127a0c113d66f4f8c14f2394c7976cff595bd5aa", + "0x11cce022cb4450c53e53839a3940567784be6fb67d6f561966b9dee65f987a3e", + ) +)] +#[test_case( + [0x23, 0x32, 0x9b, 0x82, 0x1e, 0x5f, 0xbb, 0xa3, 0x02, 0x01, 0x27, 0xe3, 0x39, 0x28, 0xe4, 0xf2, 0x50, 0x48, 0x06, 0x17, 0xf9, 0x17, 0x39, 0x96, 0x60, 0xbd, 0x7b, 0x08, 0xb9, 0x28, 0x8b, 0x48], + ( + "0x23329b821e5fbba3020127e33928e4f250480617f917399660bd7b08b9288b48", + "0x197578bd1194ee1d207e9b0d2e4f2e55e4512a1e818dcf3556176ff017192dc2", + ) +)] +#[test_case( + [0x1f, 0x15, 0xd8, 0xcc, 0x8d, 0x7e, 0x53, 0x64, 0xd5, 0xac, 0x6e, 0xa0, 0xd3, 0x23, 0x12, 0x12, 0x76, 0xb1, 0x45, 0xbd, 0xdf, 0x05, 0x68, 0x1d, 0x2f, 0xcc, 0x3d, 0x2f, 0xe5, 0x77, 0x23, 0xf4], + ( + "0x1f15d8cc8d7e5364d5ac6ea0d323121276b145bddf05681d2fcc3d2fe57723f4", + "0x17ddaa99c0dbdfcc568d7b05a25a7f16a64f16eb2e86b6c8f7ec2b9998887792", + ) +)] +#[test_case( + [0x07, 0x76, 0x7c, 0x7e, 0x57, 0x22, 0xba, 0x85, 0x87, 0x91, 0x20, 0x15, 0x4f, 0x58, 0xaa, 0x16, 0xe2, 0xdb, 0x21, 0x75, 0x79, 0xea, 0x1d, 0x3d, 0xf7, 0x66, 0xbc, 0x4c, 0xad, 0xea, 0xc5, 0x4c], + ( + "0x7767c7e5722ba85879120154f58aa16e2db217579ea1d3df766bc4cadeac54c", + "0x871640dd8539208c68524db2071d814214b0c5c6e7de73aaacd2afcddb6ffc8", + ) +)] +#[test_case( + [0x29, 0x88, 0x22, 0x5b, 0x7c, 0x44, 0xf9, 0xe5, 0x06, 0xbb, 0xfe, 0x85, 0x39, 0xcd, 0x26, 0xc8, 0xb9, 0xb8, 0xec, 0xf3, 0xec, 0xab, 0x33, 0x1d, 0x86, 0x95, 0xad, 0xf3, 0x5e, 0x3f, 0xda, 0x07], + ( + "0x2988225b7c44f9e506bbfe8539cd26c8b9b8ecf3ecab331d8695adf35e3fda07", + "0x2f44627a25fb04aa6b44300e7421bf3ee3196d0538e5ae2b210546f783f2cd32", + ) +)] +#[test_case( + [0x2b, 0x5e, 0x49, 0x45, 0xc0, 0x74, 0x9b, 0xf9, 0xe1, 0x7e, 0x4d, 0x1d, 0xea, 0xc5, 0xe4, 0x89, 0x21, 0xb4, 0xc2, 0x82, 0xee, 0x45, 0x08, 0x8a, 0x7b, 0xf9, 0x6a, 0x1b, 0xc5, 0x42, 0xb5, 0x71], + ( + "0x2b5e4945c0749bf9e17e4d1deac5e48921b4c282ee45088a7bf96a1bc542b571", + "0x28b3a31a05ada890146c5e0227de13e27baa96bc781d5e5bcd6109b713d5f758", + ) +)] +#[test_case( + [0x23, 0x55, 0x02, 0x2c, 0x52, 0x57, 0x66, 0x1b, 0xfd, 0xce, 0xbe, 0xd1, 0xc7, 0xad, 0x0e, 0x22, 0x96, 0xa3, 0x3d, 0x4b, 0xe3, 0x05, 0x4d, 0x73, 0x85, 0x3c, 0xf6, 0x3d, 0x60, 0xec, 0x45, 0x70], + ( + "0x2355022c5257661bfdcebed1c7ad0e2296a33d4be3054d73853cf63d60ec4570", + "0xfcfaf1ef9839f6f5598a916ae7c28fc28bc0c987c5c3f9e170159e322c0ec58", + ) +)] +#[test_case( + [0x26, 0x05, 0x25, 0x5c, 0x41, 0x1a, 0x24, 0x88, 0x26, 0x14, 0xa9, 0x47, 0x8e, 0xd2, 0x66, 0x76, 0x22, 0xed, 0xa5, 0xd8, 0xb1, 0xc8, 0x12, 0xc8, 0x2b, 0xd3, 0x8a, 0xac, 0xf9, 0x7b, 0x46, 0xff], + ( + "0x2605255c411a24882614a9478ed2667622eda5d8b1c812c82bd38aacf97b46ff", + "0x20df728f992c0dbd084c479155b1b8cc4b0ace3bc7b7d31d671ed49d568b2d1a", + ) +)] +#[test_case( + [0x15, 0x84, 0x15, 0x4f, 0xf4, 0xec, 0xd3, 0x96, 0x7f, 0x84, 0x94, 0xfa, 0x33, 0xe9, 0x4f, 0x0e, 0x69, 0xb6, 0x9e, 0xb7, 0x50, 0xec, 0xe2, 0x14, 0x83, 0x78, 0xda, 0xbc, 0xba, 0xd3, 0x8c, 0x05], + ( + "0x1584154ff4ecd3967f8494fa33e94f0e69b69eb750ece2148378dabcbad38c05", + "0x51ad519b5257340edff33212434e68c5b43486ceed9211469506621abd5ece", + ) +)] +#[test_case( + [0x1b, 0x73, 0x05, 0x71, 0x5a, 0xdb, 0xc0, 0x99, 0xeb, 0xeb, 0xf9, 0x2d, 0x7d, 0xa0, 0x9e, 0x04, 0xfd, 0x2f, 0x85, 0x9f, 0xd7, 0x61, 0x3d, 0xc2, 0x60, 0xb0, 0x8d, 0x76, 0x73, 0x7c, 0x65, 0x4d], + ( + "0x1b7305715adbc099ebebf92d7da09e04fd2f859fd7613dc260b08d76737c654d", + "0x14a4f76f9fbcc89c48a1f88f7603d551d1fb5e46c539d17cf11b18a73265a4ec", + ) +)] +#[test_case( + [0x15, 0xa6, 0xf1, 0x77, 0xcb, 0xa6, 0x73, 0x5b, 0x75, 0x5b, 0xdd, 0x33, 0xd4, 0x93, 0xe8, 0xa2, 0xe1, 0xd7, 0xe4, 0x16, 0x05, 0x40, 0xb1, 0x57, 0xca, 0x70, 0x37, 0x82, 0xeb, 0x72, 0x16, 0xf5], + ( + "0x15a6f177cba6735b755bdd33d493e8a2e1d7e4160540b157ca703782eb7216f5", + "0x143b33d4f6e8b2ec4b4438e76b334e959a2094ba76693e10fb6e0bbbe1b56a52", + ) +)] +#[test_case( + [0x1c, 0x79, 0x7f, 0xe1, 0x35, 0x4c, 0x09, 0x27, 0xcf, 0x6b, 0x44, 0xe7, 0xa0, 0x3e, 0xe9, 0x51, 0xb5, 0x89, 0xf7, 0x3d, 0x53, 0x7f, 0xa7, 0x93, 0xcb, 0xf6, 0xc4, 0xdd, 0x36, 0x6a, 0x1c, 0x9a], + ( + "0x1c797fe1354c0927cf6b44e7a03ee951b589f73d537fa793cbf6c4dd366a1c9a", + "0x2ac21244ff5ef8ce3981c171cad186d69533485c3e58e5dec28814a6a528491c", + ) +)] +#[test_case( + [0x2c, 0x97, 0x85, 0xc9, 0x60, 0x78, 0xde, 0x3b, 0xdc, 0x3d, 0x95, 0x34, 0x75, 0x81, 0x32, 0x94, 0x45, 0x57, 0x0e, 0x46, 0xc9, 0x7f, 0xc5, 0x42, 0xad, 0x8b, 0xcd, 0xf1, 0xd7, 0x27, 0x42, 0xd8], + ( + "0x2c9785c96078de3bdc3d95347581329445570e46c97fc542ad8bcdf1d72742d8", + "0x14fc6cf9920b68bca02cd2223108a03fb4c8aa5fbb9335de3d7b74be069700e6", + ) +)] +#[test_case( + [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01], + ("0x1", "0x2") +)] +fn decompress_collection_id_works(collection_id: Hash, expected: (&str, &str)) { + let x = Fq::from_str_prefixed(expected.0).unwrap(); + let y = Fq::from_str_prefixed(expected.1).unwrap(); + let expected = G1Affine::from_xy(x, y).unwrap(); + + let actual = decompress_collection_id(collection_id).unwrap(); + assert_eq!(actual, expected); +} + +#[test_case( + [0x64, 0xf3, 0x41, 0x72, 0x9b, 0xea, 0x43, 0x90, 0x10, 0x1d, 0x0b, 0x1a, 0xcc, 0x67, 0x47, 0xbc, 0x0d, 0x8d, 0x1a, 0xc5, 0x9f, 0xf0, 0xb3, 0x2f, 0xe9, 0x91, 0x94, 0x93, 0x8b, 0x70, 0xa4, 0xda] +)] +#[test_case( + [0x71, 0xfb, 0x5a, 0x00, 0x74, 0xc4, 0xfd, 0xf8, 0xff, 0x2a, 0x59, 0x75, 0x0c, 0xb7, 0x25, 0x7c, 0x60, 0x6b, 0x5d, 0x09, 0x93, 0xf0, 0xe7, 0x9c, 0x33, 0x08, 0x84, 0x72, 0xbc, 0x98, 0xb0, 0xf2] +)] +#[test_case( + [0xed, 0x84, 0xb8, 0xdd, 0xca, 0x0c, 0x1b, 0x21, 0x42, 0x48, 0x4f, 0x42, 0x0e, 0x05, 0xba, 0x7b, 0x19, 0xa7, 0x91, 0xc4, 0xd9, 0x17, 0x5f, 0x4f, 0x49, 0xf7, 0x83, 0x6f, 0xf1, 0xfa, 0xba, 0xae] +)] +#[test_case( + [0x93, 0x0c, 0xf8, 0x88, 0x63, 0xc7, 0x0c, 0x66, 0x28, 0x5f, 0x4b, 0x96, 0x17, 0x10, 0x32, 0x65, 0x3d, 0x91, 0xc5, 0x0e, 0xcf, 0xda, 0x23, 0xf1, 0x82, 0x7e, 0x6a, 0x9b, 0x16, 0xb1, 0x50, 0x95] +)] +#[test_case( + [0x72, 0x7a, 0xf5, 0x5e, 0x17, 0x46, 0xa7, 0x00, 0xd1, 0xde, 0x3e, 0x03, 0x99, 0x92, 0x91, 0x20, 0xdd, 0xf7, 0xae, 0xff, 0xb3, 0x2d, 0xd9, 0x53, 0x18, 0xdc, 0xf5, 0x4d, 0x39, 0x44, 0xa3, 0xd8] +)] +#[test_case( + [0xd5, 0x7f, 0xa0, 0x9a, 0x3f, 0xa7, 0xaf, 0xb2, 0x1c, 0x94, 0xb0, 0x3b, 0x06, 0x65, 0xd3, 0x59, 0x5f, 0xa8, 0x48, 0x18, 0x7e, 0x68, 0xe2, 0xbc, 0x01, 0x0b, 0xfc, 0x16, 0xb1, 0x65, 0x55, 0x63] +)] +#[test_case( + [0x1a, 0x04, 0x7d, 0x43, 0x00, 0xd3, 0x6f, 0xb3, 0xea, 0xef, 0x0b, 0x27, 0x71, 0xf4, 0x54, 0x02, 0xf4, 0x05, 0xd9, 0x90, 0x84, 0x08, 0x7a, 0xd3, 0xd9, 0x59, 0xfb, 0x0d, 0x3f, 0x4d, 0x7d, 0xf4] +)] +#[test_case( + [0xbc, 0x15, 0xb3, 0x40, 0x0d, 0xe4, 0x0a, 0xd4, 0x96, 0x68, 0x98, 0x6a, 0xca, 0xb4, 0xf2, 0xa6, 0x2b, 0x5c, 0x8e, 0x18, 0x3e, 0x22, 0xd1, 0xa1, 0xe3, 0x52, 0xa8, 0x86, 0xc6, 0x56, 0xc2, 0xa9] +)] +#[test_case( + [0x59, 0x87, 0x2c, 0xc4, 0x34, 0x24, 0x80, 0x20, 0x47, 0xf5, 0xc6, 0xda, 0x00, 0x9d, 0xad, 0xc6, 0x48, 0x74, 0x74, 0x10, 0xf0, 0xc7, 0x70, 0x92, 0x7b, 0xe3, 0x9a, 0x1e, 0x47, 0x29, 0x76, 0xe1] +)] +#[test_case( + [0xa6, 0xf3, 0x83, 0x53, 0x08, 0x5f, 0x48, 0xaa, 0x67, 0x65, 0x24, 0xdc, 0x50, 0x50, 0x20, 0x76, 0x2c, 0x14, 0xc6, 0x11, 0x2e, 0xd2, 0x94, 0x87, 0xcf, 0x0e, 0x23, 0x3b, 0x32, 0xc5, 0xc2, 0x88] +)] +#[test_case( + [0x66, 0x61, 0x64, 0x78, 0xd5, 0xa0, 0xad, 0xeb, 0x87, 0x0a, 0x9e, 0x88, 0xb9, 0x1e, 0xe4, 0x77, 0xb1, 0x76, 0x81, 0x63, 0xd8, 0xea, 0x8d, 0x4c, 0x7e, 0x54, 0x33, 0xd4, 0x07, 0xf8, 0x78, 0x50] +)] +#[test_case( + [0x70, 0x8e, 0x06, 0xc5, 0xdf, 0xbf, 0x31, 0x86, 0xf1, 0x25, 0xa4, 0xb2, 0x78, 0x8a, 0x96, 0x61, 0x6f, 0x76, 0xa6, 0x1f, 0xa7, 0x92, 0x5b, 0xec, 0xd0, 0xab, 0xa7, 0xd1, 0xde, 0x77, 0xe0, 0xd7] +)] +#[test_case( + [0x63, 0x76, 0x07, 0xf0, 0xe1, 0x22, 0xde, 0xca, 0x26, 0x3d, 0x6a, 0xba, 0x24, 0xd2, 0x5d, 0x72, 0xc0, 0x1c, 0x52, 0x1b, 0x52, 0x2c, 0x2b, 0xfb, 0x38, 0x9a, 0x7c, 0xac, 0xd6, 0x47, 0xcd, 0x30] +)] +#[test_case( + [0xb3, 0xd6, 0x11, 0x5a, 0x46, 0xcd, 0x0b, 0x52, 0xd8, 0xac, 0xe6, 0xb4, 0x21, 0x3f, 0x1a, 0x1b, 0x52, 0x38, 0xfd, 0x51, 0x01, 0x0a, 0x11, 0x84, 0x5e, 0xb2, 0x03, 0xdb, 0xf4, 0xad, 0x2c, 0x47] +)] +#[test_case( + [0xcd, 0x46, 0xba, 0x18, 0x6b, 0x98, 0xe0, 0x47, 0xff, 0x70, 0x8c, 0xf3, 0xf4, 0x3e, 0x55, 0x25, 0x63, 0x2b, 0x62, 0x47, 0x76, 0xd5, 0xdb, 0xe6, 0xf2, 0xa0, 0x01, 0x13, 0x15, 0x5e, 0x3b, 0xb3] +)] +#[test_case( + [0x98, 0xa9, 0x27, 0x46, 0xc5, 0x6e, 0x65, 0x9b, 0x12, 0x0b, 0x0a, 0x23, 0xed, 0x39, 0x59, 0x33, 0x70, 0x8e, 0x12, 0xd5, 0x89, 0x8f, 0x10, 0x25, 0xb3, 0x8e, 0xb5, 0xfb, 0x03, 0xf2, 0x2d, 0x8e] +)] +#[test_case( + [0x03, 0x9c, 0xe1, 0x34, 0x24, 0x43, 0x6f, 0xd6, 0xf1, 0xe5, 0xb8, 0x98, 0x2a, 0x8a, 0xea, 0xb0, 0x74, 0xf4, 0xeb, 0x5e, 0xfa, 0x05, 0x5f, 0x8c, 0x1f, 0x1b, 0xf9, 0xef, 0x20, 0xe9, 0x90, 0xbd] +)] +#[test_case( + [0x64, 0xd3, 0x87, 0xc1, 0x6e, 0x52, 0x10, 0xe3, 0xe4, 0x8a, 0x7f, 0x07, 0x5d, 0x70, 0xd9, 0x2d, 0x19, 0xe9, 0xcc, 0x94, 0x66, 0x7a, 0x7f, 0x6a, 0x95, 0x36, 0xd0, 0xd9, 0x4c, 0x5b, 0xc4, 0xd7] +)] +#[test_case( + [0x96, 0x74, 0x61, 0x31, 0xcf, 0xcc, 0x2d, 0xcc, 0x27, 0xd0, 0x46, 0xc2, 0x46, 0x2d, 0x10, 0xa7, 0xa4, 0xb8, 0x1f, 0x5b, 0xe6, 0xc9, 0xd5, 0xc7, 0x69, 0x1f, 0xad, 0x1f, 0x34, 0x89, 0x05, 0xee] +)] +#[test_case( + [0x7f, 0x23, 0x8a, 0x24, 0x2f, 0xf8, 0xbe, 0x73, 0xfb, 0xd4, 0x68, 0x5e, 0x36, 0xe7, 0x64, 0xd4, 0xf0, 0x25, 0x7a, 0xb8, 0x47, 0x6e, 0x51, 0x13, 0x18, 0xa5, 0x07, 0xc9, 0x21, 0x2c, 0xb1, 0x73] +)] +#[test_case( + [0x34, 0x67, 0x08, 0xf1, 0x00, 0x8c, 0xe1, 0x71, 0x7f, 0x00, 0x88, 0x08, 0xb8, 0xd3, 0xff, 0xb2, 0x15, 0x1d, 0xf3, 0xc2, 0xbb, 0x45, 0x2d, 0x63, 0x34, 0xda, 0x21, 0x90, 0xdd, 0xd6, 0xfb, 0x91] +)] +#[test_case( + [0xe2, 0x70, 0xb9, 0x4f, 0xfc, 0xda, 0x54, 0x73, 0xd3, 0x9b, 0xf7, 0x23, 0xb1, 0xc3, 0x83, 0xf1, 0xe8, 0x01, 0xe8, 0xf7, 0x57, 0xb0, 0x9d, 0xf3, 0x27, 0xb5, 0x8b, 0xb6, 0x95, 0x3d, 0x78, 0xa8] +)] +#[test_case( + [0x25, 0x79, 0x24, 0x89, 0x2e, 0x15, 0x34, 0x5c, 0xe7, 0xfa, 0x78, 0x15, 0x68, 0xf8, 0x23, 0x3d, 0x1d, 0x4e, 0xb8, 0x7c, 0xaf, 0xa8, 0x75, 0x04, 0x49, 0xaf, 0xd0, 0x39, 0x77, 0x7b, 0xbe, 0xac] +)] +#[test_case( + [0x6b, 0x3b, 0x0a, 0x09, 0x43, 0x4d, 0x23, 0x0d, 0x4c, 0x6f, 0x93, 0xba, 0xe3, 0x02, 0xd7, 0x1b, 0xcc, 0xa5, 0x9e, 0xbb, 0x27, 0xb6, 0xa9, 0x66, 0xb3, 0x8f, 0x49, 0x06, 0x73, 0xbe, 0x79, 0xf1] +)] +#[test_case( + [0x49, 0xaf, 0x83, 0x00, 0x60, 0x19, 0x13, 0x24, 0xea, 0x98, 0x1b, 0x1a, 0xf5, 0x84, 0x72, 0x02, 0xd3, 0x0f, 0x28, 0x80, 0xbd, 0xa0, 0x9d, 0x33, 0xc4, 0x49, 0xa2, 0xf5, 0x7b, 0xca, 0xe1, 0xfe] +)] +#[test_case( + [0xd0, 0x38, 0x1e, 0xd6, 0xae, 0xd8, 0x85, 0xe2, 0x2d, 0x22, 0xdc, 0x10, 0x5e, 0x89, 0xc9, 0xc7, 0xc7, 0xba, 0x91, 0x7f, 0x98, 0xfe, 0x05, 0x59, 0xf0, 0xb6, 0x2e, 0xed, 0x24, 0xc7, 0xf5, 0x58] +)] +#[test_case( + [0x6b, 0xe4, 0xe5, 0x7d, 0x54, 0xf0, 0x48, 0xe0, 0x3f, 0x7e, 0xe5, 0x16, 0x91, 0x5d, 0x1c, 0xa2, 0x04, 0x4c, 0x08, 0x85, 0xf3, 0xe2, 0x50, 0x02, 0x73, 0x85, 0x65, 0x79, 0xde, 0x86, 0x5c, 0x75] +)] +#[test_case( + [0x20, 0x24, 0x92, 0x76, 0xe9, 0x41, 0x79, 0x08, 0x75, 0x82, 0xcd, 0xe9, 0x15, 0x76, 0xa0, 0xba, 0x2a, 0x8d, 0x69, 0x9f, 0xca, 0xa3, 0xc5, 0xa6, 0x8a, 0xf6, 0xcd, 0xdb, 0xbe, 0x90, 0x6b, 0x17] +)] +#[test_case( + [0x98, 0x1f, 0x5b, 0x9d, 0x34, 0x7e, 0x79, 0xe6, 0x71, 0xe6, 0x25, 0xe8, 0xb1, 0xe2, 0xdc, 0x27, 0xa3, 0x90, 0x43, 0x14, 0xe3, 0xe5, 0x5e, 0x58, 0x7b, 0x8f, 0xab, 0x9f, 0x9c, 0x94, 0x03, 0x1f] +)] +#[test_case( + [0x8e, 0x63, 0x3a, 0x31, 0xc5, 0x6d, 0x22, 0x8b, 0x4d, 0x55, 0xda, 0xbd, 0x4e, 0x2a, 0x9b, 0xae, 0xf6, 0x12, 0x4c, 0xf6, 0x56, 0x3d, 0xc8, 0x76, 0xb6, 0x33, 0x72, 0x48, 0x9a, 0x30, 0xfc, 0x3f] +)] +#[test_case( + [0x41, 0xe0, 0x5f, 0x70, 0xa2, 0x15, 0x83, 0x7a, 0x69, 0x2a, 0x8e, 0x18, 0x5f, 0x7a, 0x99, 0xe5, 0x86, 0x21, 0x51, 0xbd, 0xe7, 0xe4, 0xf4, 0x72, 0xfa, 0x8b, 0xf8, 0x54, 0x5e, 0xf5, 0x85, 0xd7] +)] +#[test_case( + [0x76, 0x67, 0x10, 0xb5, 0x92, 0xe8, 0x2f, 0xd1, 0xa8, 0x96, 0x8b, 0xb9, 0x13, 0x0f, 0x50, 0xe3, 0xda, 0xfa, 0xeb, 0x12, 0xce, 0xa4, 0x13, 0xe4, 0x5e, 0x31, 0xcd, 0x0c, 0x55, 0x08, 0xd4, 0x4e] +)] +#[test_case( + [0x94, 0xb4, 0xbd, 0xbb, 0xcd, 0x3d, 0x9d, 0x7e, 0x3b, 0x90, 0x2c, 0x9d, 0x02, 0x73, 0xf8, 0x7a, 0x84, 0x51, 0x0e, 0x52, 0xa5, 0x8c, 0x75, 0xfe, 0xce, 0xf5, 0x00, 0x0d, 0xf5, 0x4c, 0x91, 0x85] +)] +#[test_case( + [0x45, 0xe8, 0xf2, 0x5a, 0xfe, 0xf6, 0xfd, 0x7a, 0x2f, 0xf7, 0xcf, 0x6b, 0x05, 0x8b, 0x2d, 0xf9, 0x03, 0x5c, 0x76, 0x7a, 0x16, 0x1b, 0x55, 0x06, 0x39, 0x22, 0xdd, 0xc7, 0xa9, 0x55, 0xf7, 0x24] +)] +#[test_case( + [0xa2, 0xc0, 0xdd, 0xe2, 0x1a, 0x63, 0xd8, 0xe7, 0x57, 0xa9, 0x98, 0x51, 0xd8, 0x79, 0xf6, 0xe2, 0xe5, 0x82, 0x60, 0x7b, 0xd2, 0x08, 0x80, 0xef, 0x64, 0xc8, 0x31, 0xc9, 0xa7, 0xce, 0x88, 0x00] +)] +#[test_case( + [0xaa, 0x0a, 0x4e, 0xa0, 0xab, 0x4c, 0x0e, 0xbf, 0x66, 0x39, 0x9b, 0x36, 0xdc, 0xc1, 0x75, 0x9c, 0x0f, 0x00, 0x31, 0xb5, 0x45, 0xc5, 0x1d, 0xdc, 0x38, 0x45, 0x76, 0x53, 0x31, 0x07, 0x99, 0xa4] +)] +#[test_case( + [0x4d, 0x0f, 0x2c, 0xf2, 0xd1, 0xfc, 0x52, 0x49, 0xc5, 0x21, 0xaa, 0xbf, 0xbf, 0x91, 0xb9, 0x13, 0xd1, 0xfb, 0x42, 0x19, 0x86, 0x0a, 0x35, 0x5e, 0x3a, 0x3a, 0xee, 0x76, 0xd9, 0x2d, 0x6d, 0xf9] +)] +#[test_case( + [0xa2, 0xc3, 0xb5, 0xac, 0x07, 0xbd, 0x2f, 0x74, 0xf7, 0x98, 0x7e, 0x00, 0xe2, 0xaf, 0x52, 0x4f, 0x6a, 0x95, 0x07, 0xd4, 0x14, 0x93, 0x16, 0x87, 0xf6, 0xca, 0x42, 0x34, 0xe3, 0x7d, 0xf3, 0x2c] +)] +#[test_case( + [0x99, 0x32, 0x74, 0x19, 0x77, 0x0f, 0x9b, 0x3d, 0x5d, 0x19, 0xce, 0xad, 0xcc, 0x06, 0xa5, 0x1d, 0x08, 0xe2, 0x86, 0x30, 0x4b, 0x61, 0xd5, 0x08, 0xcc, 0x36, 0xbc, 0x2e, 0x23, 0x5b, 0xf3, 0x05] +)] +#[test_case( + [0x34, 0x7b, 0x86, 0xec, 0xe0, 0x00, 0x89, 0x2a, 0x2d, 0x84, 0x5b, 0x2b, 0x36, 0x82, 0x21, 0x63, 0x8a, 0x2a, 0x04, 0x82, 0x1d, 0x03, 0x2c, 0xe3, 0xef, 0xbc, 0xf7, 0xbe, 0x57, 0x44, 0x79, 0x59] +)] +#[test_case( + [0x3a, 0xb4, 0x53, 0xb4, 0xf9, 0x53, 0xe1, 0x50, 0xaa, 0xb1, 0x57, 0xdd, 0x64, 0xd7, 0x85, 0x77, 0x9e, 0xeb, 0xe6, 0x00, 0xb8, 0x7f, 0xb6, 0xf8, 0xe4, 0x62, 0x1f, 0x41, 0x94, 0x41, 0x73, 0x57] +)] +#[test_case( + [0xfc, 0x0c, 0xe9, 0xb2, 0xec, 0xb2, 0x50, 0xfb, 0xb9, 0x34, 0xbc, 0x5a, 0x74, 0x98, 0xe4, 0xc7, 0x62, 0x8d, 0x6f, 0x1f, 0x3a, 0x56, 0x69, 0x45, 0x47, 0x96, 0xbe, 0x15, 0xf8, 0x56, 0x31, 0x4e] +)] +#[test_case( + [0xab, 0x5e, 0x49, 0x8f, 0xd0, 0xf7, 0x2c, 0xd8, 0x54, 0xed, 0xe6, 0x27, 0x20, 0x4c, 0x23, 0xd6, 0x39, 0xa1, 0x4a, 0x71, 0x4b, 0x35, 0xe7, 0xa8, 0x09, 0x1e, 0x0f, 0x04, 0xa4, 0xd7, 0x49, 0xb2] +)] +#[test_case( + [0x62, 0x52, 0xa3, 0xde, 0xa6, 0x05, 0x54, 0x85, 0x65, 0xb6, 0x83, 0x8f, 0x85, 0x38, 0xee, 0xab, 0x9c, 0x8b, 0x66, 0x64, 0x90, 0x05, 0xc0, 0x17, 0x95, 0x9d, 0x0d, 0x2d, 0x20, 0xec, 0x2a, 0xa0] +)] +#[test_case( + [0xbd, 0xd9, 0x27, 0xb4, 0x6b, 0x96, 0x7b, 0xc9, 0x3a, 0xc4, 0x61, 0x41, 0x8d, 0x5e, 0x66, 0xad, 0xf2, 0xde, 0x77, 0x58, 0x95, 0x42, 0x57, 0x45, 0x5b, 0x16, 0x5e, 0x40, 0x5f, 0x40, 0x25, 0xc9] +)] +#[test_case( + [0x7f, 0x6f, 0x8b, 0xde, 0xf8, 0x24, 0x5a, 0x28, 0x10, 0x50, 0x4d, 0xa9, 0x8c, 0xe0, 0x59, 0x64, 0x5c, 0xa3, 0xa6, 0x27, 0x17, 0x79, 0x8e, 0x5c, 0x13, 0x5f, 0xbb, 0x5c, 0x13, 0x9a, 0x55, 0x59] +)] +#[test_case( + [0x30, 0x65, 0x95, 0xba, 0xf3, 0xbc, 0x3a, 0x34, 0x1a, 0xb9, 0x42, 0xf6, 0x00, 0x94, 0xd9, 0x1e, 0xc5, 0x51, 0x4b, 0x1c, 0x53, 0x5a, 0x33, 0xca, 0x77, 0x03, 0x93, 0x12, 0x39, 0x99, 0x3c, 0x45] +)] +#[test_case( + [0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff] +)] +#[test_case( + [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] +)] +#[test_case( + [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] +)] +#[test_case( + [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff] +)] +#[test_case( + [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe] +)] +fn decompress_collection_id_fails_on_invalid_collection_id(collection_id: Hash) { + let actual = decompress_collection_id(collection_id); + assert_eq!(actual, None); +} diff --git a/zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/decompress_hash.rs b/zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/decompress_hash.rs new file mode 100644 index 000000000..1ba11da48 --- /dev/null +++ b/zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/decompress_hash.rs @@ -0,0 +1,756 @@ +use super::*; +use rstest::rstest; + +#[rstest] +#[case( + [0x83, 0x15, 0x48, 0x88, 0xe8, 0x2c, 0xe4, 0xfc, 0x32, 0xc2, 0xd5, 0xcd, 0x76, 0x6f, 0xfd, 0xc1, 0x8a, 0x8b, 0x00, 0xd9, 0xb7, 0x18, 0x15, 0xc7, 0x2c, 0x52, 0x38, 0x91, 0x11, 0x4e, 0x19, 0xca], + ( + "0x224caba325c9a4a8c2224a60736d4d065b882bb6e63480acb411206360541f3f", + "0x2f69f0116b9c27402783c23a83890dd2e8d11598c875b4562ac61f8716674497", + ) +)] +#[case( + [0x64, 0xf3, 0x41, 0x72, 0x9b, 0xea, 0x43, 0x90, 0x10, 0x1d, 0x0b, 0x1a, 0xcc, 0x67, 0x47, 0xbc, 0x0d, 0x8d, 0x1a, 0xc5, 0x9f, 0xf0, 0xb3, 0x2f, 0xe9, 0x91, 0x94, 0x93, 0x8b, 0x70, 0xa4, 0xda], + ( + "0x42aa48cd987033c9f7c7fadc9649700de8a45a2cf0d1e1571507c65da76aa4d", + "0x17b8beb8dff7a68a74f5d5a59be03a403b01e1b52181bc317b2f85ecf288c160", + ) +)] +#[case( + [0x1f, 0x79, 0x5c, 0xdd, 0x77, 0xab, 0x5e, 0xe4, 0x48, 0x50, 0x24, 0x5e, 0x72, 0xbc, 0x94, 0x80, 0xe0, 0x0c, 0xca, 0x47, 0x6f, 0x83, 0xd4, 0x2b, 0x0f, 0xf0, 0xce, 0x60, 0x28, 0xdf, 0x4b, 0x73], + ( + "0x1f795cdd77ab5ee44850245e72bc9480e00cca476f83d42b0ff0ce6028df4b74", + "0x1611f17dcc316d337dbadfdd9ff708a1574dcafa7464d8192cb83f7dc345f630", + ) +)] +#[case( + [0x11, 0x7b, 0x7c, 0x6c, 0xab, 0x0d, 0x78, 0x37, 0xfe, 0x68, 0x5c, 0x3d, 0x1c, 0x83, 0x83, 0x31, 0x26, 0xb8, 0xe3, 0xa5, 0xcf, 0x2f, 0x10, 0x2d, 0x4f, 0x63, 0xf0, 0x2c, 0x34, 0x68, 0x9d, 0xa4], + ( + "0x117b7c6cab0d7837fe685c3d1c83833126b8e3a5cf2f102d4f63f02c34689da5", + "0x1d20ad42a133eb646a7785bf576b2fdc687c04c63a20de5e70c29207e27dd6e0", + ) +)] +#[case( + [0x71, 0xfb, 0x5a, 0x00, 0x74, 0xc4, 0xfd, 0xf8, 0xff, 0x2a, 0x59, 0x75, 0x0c, 0xb7, 0x25, 0x7c, 0x60, 0x6b, 0x5d, 0x09, 0x93, 0xf0, 0xe7, 0x9c, 0x33, 0x08, 0x84, 0x72, 0xbc, 0x98, 0xb0, 0xf2], + ( + "0x1132bd1ab261bda58e89ce0809b474c1316887e6c30d5281bac76c450b9eb666", + "0x678e222f0d626c3e4298406b8bfbc8fd3f41d6afe467704eb1ac07664fe262", + ) +)] +#[case( + [0x81, 0x8d, 0xe3, 0xde, 0x50, 0x1f, 0xff, 0xa2, 0xe2, 0x1c, 0x26, 0xcf, 0xc1, 0xb1, 0x02, 0x1d, 0x79, 0x13, 0xf7, 0x02, 0x91, 0x0e, 0xcf, 0xfb, 0xcd, 0x5d, 0x2b, 0x06, 0x55, 0xff, 0x62, 0x45], + ( + "0x20c546f88dbcbf4f717b9b62beae51624a1121dfc02b3ae1551c12d8a50567b9", + "0xcdda89c6b78b99bfba909bf8862cde968465cef38fda4e7df6ab617680adfaf", + ) +)] +#[case( + [0xed, 0x84, 0xb8, 0xdd, 0xca, 0x0c, 0x1b, 0x21, 0x42, 0x48, 0x4f, 0x42, 0x0e, 0x05, 0xba, 0x7b, 0x19, 0xa7, 0x91, 0xc4, 0xd9, 0x17, 0x5f, 0x4f, 0x49, 0xf7, 0x83, 0x6f, 0xf1, 0xfa, 0xba, 0xae], + ( + "0x2bf37f1245459a7a6107386808005904bba1e77f3750351a597553149006c593", + "0x19386875ee1078b2ede3be6a02f6482507bed0058b655bce614bc1abb408ddf1", + ) +)] +#[case( + [0xef, 0xa9, 0x42, 0xf4, 0xd0, 0x1f, 0x3a, 0x4d, 0xdd, 0x1e, 0x5c, 0xb6, 0xe8, 0x2a, 0xd8, 0xe7, 0x04, 0xa8, 0x59, 0x2c, 0xf0, 0xe0, 0xb8, 0x46, 0xc3, 0x6d, 0xc9, 0xbd, 0x05, 0x36, 0x9c, 0x46], + ( + "0x2e1809294b58b9a6fbdd45dce2257770a6a2aee74f198e11d2eb9961a342a72e", + "0xb5f465aedf5ad33337e665db38c12018472fa89c7e112bfe874d25f9fb9c13d", + ) +)] +#[case( + [0x19, 0x89, 0xc5, 0xce, 0xac, 0x1a, 0x65, 0xfa, 0x79, 0x22, 0x96, 0xed, 0x44, 0x9a, 0xe7, 0x9e, 0x54, 0x0b, 0x00, 0xd6, 0xa0, 0x0a, 0xd4, 0x46, 0x03, 0x5c, 0x87, 0xc7, 0x5e, 0xa6, 0x63, 0x36], + ( + "0x1989c5ceac1a65fa792296ed449ae79e540b00d6a00ad446035c87c75ea6633a", + "0x6589b2b453dbaed452e90d4a5ec85fbd3641e707f42e0ac3c8d5d88ffa62bea", + ) +)] +#[case( + [0xa5, 0xd8, 0x66, 0xaf, 0xcc, 0xbb, 0x43, 0xcc, 0xe8, 0xe0, 0x18, 0x76, 0xf3, 0x76, 0x2e, 0x4b, 0x32, 0xee, 0xf0, 0xee, 0xf1, 0xf0, 0x85, 0xaf, 0xed, 0xbc, 0xf8, 0x75, 0x88, 0x06, 0x12, 0x93], + ( + "0x14ab7b572926634fbfef47536ef225326c6ab13ab89b2608395b5430fe8f1ac0", + "0x2d760276c0c075b65b46f092b3ee5f0ea63a44bdda2d606acd903d62f4cd3379", + ) +)] +#[case( + [0x7b, 0xa3, 0x02, 0xd4, 0xc7, 0x01, 0xaa, 0xb6, 0xb4, 0x85, 0x28, 0x0b, 0x25, 0x03, 0x73, 0x55, 0x1a, 0xf2, 0xeb, 0x83, 0xa9, 0x8b, 0xe0, 0x04, 0x8f, 0xae, 0xb2, 0xdc, 0x20, 0x23, 0x50, 0xe4], + ( + "0x1ada65ef049e6a6343e49c9e2200c299ebf01660d8a84aea176d9aae6f295657", + "0x2d4d79805f35ec44f628b7cc6030017d6b3f238eae07612b074e42e32369ed34", + ) +)] +#[case( + [0xfd, 0xc6, 0x8c, 0xe3, 0x40, 0xf3, 0x04, 0x4b, 0x52, 0x6b, 0x8b, 0x6b, 0x1d, 0xff, 0x6f, 0x14, 0x3d, 0x4c, 0x04, 0x68, 0x65, 0x53, 0x3a, 0x59, 0x59, 0xbc, 0xb8, 0x28, 0xe9, 0x66, 0x07, 0xad], + ( + "0xbd104a4dafae37ab8da2eda9678b54047c4ef915b1a45972d19fbb6aef5154c", + "0xdffe17159cc7adba911c9aa514b86792832f6fa69d062371154ead6b152375b", + ) +)] +#[case( + [0x93, 0x0c, 0xf8, 0x88, 0x63, 0xc7, 0x0c, 0x66, 0x28, 0x5f, 0x4b, 0x96, 0x17, 0x10, 0x32, 0x65, 0x3d, 0x91, 0xc5, 0x0e, 0xcf, 0xda, 0x23, 0xf1, 0x82, 0x7e, 0x6a, 0x9b, 0x16, 0xb1, 0x50, 0x95], + ( + "0x1e00d2fc0322be8ff6e7a72928c294c770d855a9684c449ce1cc6568d3a58c1", + "0x241d3fe18e6b4bea53f4c1dd19254f35c2591d5e4a076305ccd1327289745b7b", + ) +)] +#[case( + [0x72, 0x7a, 0xf5, 0x5e, 0x17, 0x46, 0xa7, 0x00, 0xd1, 0xde, 0x3e, 0x03, 0x99, 0x92, 0x91, 0x20, 0xdd, 0xf7, 0xae, 0xff, 0xb3, 0x2d, 0xd9, 0x53, 0x18, 0xdc, 0xf5, 0x4d, 0x39, 0x44, 0xa3, 0xd8], + ( + "0x11b2587854e366ad613db296968fe065aef4d9dce24a4438a09bdd1f884aa94b", + "0x89d696be6968733b6012365a3f1823dc24ee9c391b2ec04cea60fa8f30be578", + ) +)] +#[case( + [0xd5, 0x7f, 0xa0, 0x9a, 0x3f, 0xa7, 0xaf, 0xb2, 0x1c, 0x94, 0xb0, 0x3b, 0x06, 0x65, 0xd3, 0x59, 0x5f, 0xa8, 0x48, 0x18, 0x7e, 0x68, 0xe2, 0xbc, 0x01, 0x0b, 0xfc, 0x16, 0xb1, 0x65, 0x55, 0x63], + ( + "0x13ee66cebae12f0b3b539961006071e301a29dd2dca1b8871089cbbb4f716048", + "0x84cb34653fc5497967aedb393171a776bc6ddbd1b92a36c08ff190841b3bc97", + ) +)] +#[case( + [0x1a, 0x04, 0x7d, 0x43, 0x00, 0xd3, 0x6f, 0xb3, 0xea, 0xef, 0x0b, 0x27, 0x71, 0xf4, 0x54, 0x02, 0xf4, 0x05, 0xd9, 0x90, 0x84, 0x08, 0x7a, 0xd3, 0xd9, 0x59, 0xfb, 0x0d, 0x3f, 0x4d, 0x7d, 0xf4], + ( + "0x1a047d4300d36fb3eaef0b2771f45402f405d99084087ad3d959fb0d3f4d7df5", + "0x11b24a741b48731ad5dbda58c09d662b1fc1335952c5b72b9c70f14e26399980", + ) +)] +#[case( + [0xbc, 0x15, 0xb3, 0x40, 0x0d, 0xe4, 0x0a, 0xd4, 0x96, 0x68, 0x98, 0x6a, 0xca, 0xb4, 0xf2, 0xa6, 0x2b, 0x5c, 0x8e, 0x18, 0x3e, 0x22, 0xd1, 0xa1, 0xe3, 0x52, 0xa8, 0x86, 0xc6, 0x56, 0xc2, 0xa9], + ( + "0x2ae8c7e76a4f2a576d77c7474630e98d64d84e6404cd71fa2ef104423cdfcad6", + "0x46d69a8b490311d5970dfeea57c56280cfa9d1c01cc65e309d5d7818e895a2d", + ) +)] +#[case( + [0xaf, 0xff, 0x49, 0xfa, 0xa4, 0xfb, 0x00, 0x56, 0x31, 0xab, 0x38, 0xf3, 0x9d, 0x57, 0x07, 0xd2, 0xba, 0xe3, 0x5c, 0x2a, 0xb9, 0xb5, 0xa5, 0x06, 0x51, 0x32, 0x08, 0x73, 0xf7, 0x99, 0xa4, 0xa9], + ( + "0x1ed25ea201661fd908ba67d018d2feb9f45f1c768060455e9cd0642f6e22acd5", + "0x2cfa7234f94faf07f1369e27ebda4de11e4ea091e6a8f7773fb0ad205394bcb5", + ) +)] +#[case( + [0xf8, 0x01, 0x51, 0x24, 0xd3, 0x4f, 0x6f, 0xc4, 0x7f, 0xbb, 0x2b, 0xd4, 0x48, 0x8b, 0x20, 0x16, 0x48, 0xdd, 0xbf, 0x0a, 0x75, 0xa2, 0x4e, 0x38, 0xa3, 0x26, 0xba, 0x74, 0xff, 0x76, 0x45, 0xab], + ( + "0x60bc8e66d574ef3e629cf43c10466425356aa336b6959767683fe02c5055349", + "0x279d88dc4e3488f950e3d12ed82d45a585d1ab9310a23549d3a9525b78494813", + ) +)] +#[case( + [0xc3, 0x2f, 0xfb, 0xd6, 0x6a, 0xa2, 0x28, 0x56, 0x30, 0x80, 0x73, 0x41, 0x2e, 0xc5, 0xd1, 0x54, 0xa8, 0xfa, 0x86, 0x82, 0x5d, 0x77, 0x71, 0xd2, 0xbf, 0xad, 0x37, 0x37, 0x27, 0x75, 0xa6, 0x97], + ( + "0x19ec20ae5dba7af4f3f5c6728c06fde4af4dc3cbbb0479dcf2b06dbc581b17c", + "0x1d42f564a2450a7e3616618d3550f0b00262a2d3b28187f27c8ef55e9dda5f7d", + ) +)] +#[case( + [0x2d, 0x45, 0xd3, 0x9e, 0x93, 0x38, 0xb1, 0xfb, 0xc4, 0x49, 0x03, 0xa5, 0x2b, 0x3a, 0x17, 0x7a, 0x2a, 0xad, 0x6b, 0xac, 0x0b, 0x68, 0x2c, 0x8f, 0x64, 0xf3, 0x2e, 0xba, 0xd4, 0x04, 0x4d, 0x0b], + ( + "0x2d45d39e9338b1fbc44903a52b3a177a2aad6bac0b682c8f64f32ebad4044d0c", + "0x27c51c1b8f126d3b987027f2a04d8cb26f73db03c8cabe314e3c11c5091945da", + ) +)] +#[case( + [0x59, 0x87, 0x2c, 0xc4, 0x34, 0x24, 0x80, 0x20, 0x47, 0xf5, 0xc6, 0xda, 0x00, 0x9d, 0xad, 0xc6, 0x48, 0x74, 0x74, 0x10, 0xf0, 0xc7, 0x70, 0x92, 0x7b, 0xe3, 0x9a, 0x1e, 0x47, 0x29, 0x76, 0xe1], + ( + "0x2922de5152f2dff68fa581237f1c5568b0f3097f8855a6053fc30e076eac799b", + "0x2aad24a3280997c33422d9081f12ebde0440122f17060ae655336c52c13228e8", + ) +)] +#[case( + [0xa6, 0xf3, 0x83, 0x53, 0x08, 0x5f, 0x48, 0xaa, 0x67, 0x65, 0x24, 0xdc, 0x50, 0x50, 0x20, 0x76, 0x2c, 0x14, 0xc6, 0x11, 0x2e, 0xd2, 0x94, 0x87, 0xcf, 0x0e, 0x23, 0x3b, 0x32, 0xc5, 0xc2, 0x88], + ( + "0x15c697fa64ca682d3e7453b8cbcc175d6590865cf57d34e01aac7ef6a94ecab5", + "0xece650efa6538a1b3d8c1e2a6ccc7e220934121758861c8d62bb48bdf5733e3", + ) +)] +#[case( + [0x66, 0x61, 0x64, 0x78, 0xd5, 0xa0, 0xad, 0xeb, 0x87, 0x0a, 0x9e, 0x88, 0xb9, 0x1e, 0xe4, 0x77, 0xb1, 0x76, 0x81, 0x63, 0xd8, 0xea, 0x8d, 0x4c, 0x7e, 0x54, 0x33, 0xd4, 0x07, 0xf8, 0x78, 0x50], + ( + "0x598c793133d6d98166a131bb61c33bc8273ac410806f83206131ba656fe7dcb", + "0x1790e75e16e2a035609d5344f412fad57aa07013913a5dc784c83ea863f3178", + ) +)] +#[case( + [0x70, 0x8e, 0x06, 0xc5, 0xdf, 0xbf, 0x31, 0x86, 0xf1, 0x25, 0xa4, 0xb2, 0x78, 0x8a, 0x96, 0x61, 0x6f, 0x76, 0xa6, 0x1f, 0xa7, 0x92, 0x5b, 0xec, 0xd0, 0xab, 0xa7, 0xd1, 0xde, 0x77, 0xe0, 0xd7], + ( + "0xfc569e01d5bf133808519457587e5a64073d0fcd6aec6d2586a8fa42d7de64c", + "0x139ed7620d16f3eced5b0b19edfdc8a77d1978caf2a208013b0f5dc0066b8c8e", + ) +)] +#[case( + [0x63, 0x76, 0x07, 0xf0, 0xe1, 0x22, 0xde, 0xca, 0x26, 0x3d, 0x6a, 0xba, 0x24, 0xd2, 0x5d, 0x72, 0xc0, 0x1c, 0x52, 0x1b, 0x52, 0x2c, 0x2b, 0xfb, 0x38, 0x9a, 0x7c, 0xac, 0xd6, 0x47, 0xcd, 0x30], + ( + "0x2ad6b0b1ebf9e76b59cdf4d21cfacb791197cf8814896e0c059647f254dd2a6", + "0xdc8061c2098b8c7f33477d0f9d79689ced5cdd55bd4c3bb55e90541bbcc0b48", + ) +)] +#[case( + [0x9f, 0x49, 0x14, 0x31, 0xc7, 0xdf, 0x9a, 0xbc, 0x2a, 0x1a, 0xf6, 0xd1, 0x22, 0x5f, 0x48, 0x78, 0x91, 0xd0, 0xad, 0xd2, 0x93, 0xf7, 0x2f, 0xb5, 0x3c, 0xc5, 0x89, 0x92, 0xb6, 0xb6, 0x85, 0x9f], + ( + "0xe1c28d9244aba3f012a25ad9ddb3f5fcb4c6e1e5aa1d00d8863e54e2d3f8dcb", + "0x243a9cce52bfaa26c0ff6861e3476c69e0c8eae62510c17e66b6090ac515b1e3", + ) +)] +#[case( + [0xb3, 0xd6, 0x11, 0x5a, 0x46, 0xcd, 0x0b, 0x52, 0xd8, 0xac, 0xe6, 0xb4, 0x21, 0x3f, 0x1a, 0x1b, 0x52, 0x38, 0xfd, 0x51, 0x01, 0x0a, 0x11, 0x84, 0x5e, 0xb2, 0x03, 0xdb, 0xf4, 0xad, 0x2c, 0x47], + ( + "0x22a92601a3382ad5afbc15909cbb11028bb4bd9cc7b4b1dcaa505f976b363473", + "0x2468cc28326c4102230355ae75b7ad9b51a8ef8ae897caf77ebe0629c18492b", + ) +)] +#[case( + [0xe8, 0x66, 0x29, 0x25, 0x84, 0x95, 0x25, 0x7c, 0xaa, 0x29, 0x57, 0x87, 0xf2, 0x50, 0xff, 0x30, 0x14, 0xf9, 0xa8, 0x61, 0x91, 0xd2, 0xfa, 0xe7, 0x4f, 0x57, 0x03, 0xd9, 0xaf, 0x25, 0xb8, 0x87], + ( + "0x26d4ef59ffcea4d5c8e840adec4b9db9b6f3fe1bf00bd0b25ed4d37e4d31c36d", + "0x4da2d54b9dc4f07956051d20389062d5e9b25ed56bd3a68b9066f9543dec88d", + ) +)] +#[case( + [0x63, 0xc1, 0x52, 0x80, 0x1c, 0xf9, 0x6e, 0x49, 0x82, 0x91, 0xb5, 0x86, 0x7e, 0x10, 0xe2, 0x98, 0x2a, 0xe8, 0x40, 0x83, 0x19, 0xfe, 0xee, 0xbe, 0x94, 0x26, 0x64, 0x8f, 0x36, 0xf1, 0xd5, 0x07], + ( + "0x2f8b59a5a962df611f12a197b0e31dcfbe56b60491b59a41be54c6185f7da7b", + "0x22d0275a80ca95052995a482a0c80b5e75b648f4d2db98360e06ede66ae8668e", + ) +)] +#[case( + [0xcd, 0x46, 0xba, 0x18, 0x6b, 0x98, 0xe0, 0x47, 0xff, 0x70, 0x8c, 0xf3, 0xf4, 0x3e, 0x55, 0x25, 0x63, 0x2b, 0x62, 0x47, 0x76, 0xd5, 0xdb, 0xe6, 0xf2, 0xa0, 0x01, 0x13, 0x15, 0x5e, 0x3b, 0xb3], + ( + "0xbb5804ce6d25fa11e2f7619ee38f3af0525b801d50eb1b2021dd0b7b36a4699", + "0x1d2ea0c75485040860d39d5014e8619dd802bd217bb32e16c175dad3800c930f", + ) +)] +#[case( + [0xb9, 0xc0, 0x7c, 0xd7, 0x80, 0xdd, 0xb5, 0x70, 0xcb, 0x2c, 0xb9, 0xe7, 0xa0, 0x6f, 0x50, 0xcf, 0xe3, 0x65, 0x17, 0xd9, 0xb6, 0x9c, 0xa1, 0xa1, 0xba, 0x7c, 0x5a, 0x90, 0x9b, 0xc7, 0x2c, 0x39], + ( + "0x2893917edd48d4f3a23be8c41beb47b71ce0d8257d4741fa061ab64c12503468", + "0x1c4760ed802d7b6a007dab8a489042056a83965f508333bfc3638fad27b9dad", + ) +)] +#[case( + [0x98, 0xa9, 0x27, 0x46, 0xc5, 0x6e, 0x65, 0x9b, 0x12, 0x0b, 0x0a, 0x23, 0xed, 0x39, 0x59, 0x33, 0x70, 0x8e, 0x12, 0xd5, 0x89, 0x8f, 0x10, 0x25, 0xb3, 0x8e, 0xb5, 0xfb, 0x03, 0xf2, 0x2d, 0x8e], + ( + "0x77c3bee21d9851de91a390068b5501aaa09d3215039b07dff2d11b67a7b35bb", + "0x2ea33101a896b978333948a111bfc23fa25f9a128c070f81299f411917192e6d", + ) +)] +#[case( + [0x03, 0x9c, 0xe1, 0x34, 0x24, 0x43, 0x6f, 0xd6, 0xf1, 0xe5, 0xb8, 0x98, 0x2a, 0x8a, 0xea, 0xb0, 0x74, 0xf4, 0xeb, 0x5e, 0xfa, 0x05, 0x5f, 0x8c, 0x1f, 0x1b, 0xf9, 0xef, 0x20, 0xe9, 0x90, 0xbd], + ( + "0x39ce13424436fd6f1e5b8982a8aeab074f4eb5efa055f8c1f1bf9ef20e990be", + "0x1880f40315bb2b5b7663b4591c3d29fc30c8f5201204537c70ecda7171a802dc", + ) +)] +#[case( + [0xde, 0x68, 0xcb, 0xb1, 0x48, 0xd7, 0x00, 0xb3, 0x08, 0xf4, 0x46, 0xb7, 0x26, 0xe9, 0x5b, 0xcf, 0x47, 0xa2, 0x95, 0x02, 0xa6, 0x0b, 0xdf, 0x66, 0x1a, 0x61, 0x99, 0xf0, 0x4b, 0x99, 0x30, 0xaf], + ( + "0x1cd791e5c410800c27b32fdd20e3fa58e99ceabd0444b53129df6994e9a53b94", + "0x812622ba3a01ce0f2d9eff7c20cb39b3f07582bf99101b3a2451ab19c30a7f1", + ) +)] +#[case( + [0x68, 0x70, 0x19, 0x94, 0x4d, 0x49, 0x22, 0x32, 0x2b, 0x04, 0xeb, 0xea, 0x55, 0xc5, 0x18, 0xcf, 0x11, 0x87, 0xfc, 0x5c, 0x41, 0x46, 0x2b, 0x4a, 0xee, 0x57, 0x81, 0x7a, 0x5f, 0xd7, 0xdb, 0x33], + ( + "0x7a77cae8ae5e1deba64607d52c26813e2852739706296307616694caedde0a7", + "0x189aa09a36b8c758731136f5d8f4d528e28aa071563d0e7ef5f6f868381e25d4", + ) +)] +#[case( + [0x81, 0x4d, 0x86, 0x37, 0xc7, 0xd2, 0xcb, 0x96, 0x7b, 0x8f, 0x65, 0x68, 0xbe, 0xcd, 0xb1, 0x42, 0x1e, 0x39, 0xfc, 0x2d, 0xff, 0x24, 0xd2, 0x69, 0xab, 0x86, 0x31, 0x59, 0x5a, 0x08, 0x9c, 0x97], + ( + "0x2084e952056f8b430aeed9fbbbcb0086ef37270b2e413d4f3345192ba90ea20a", + "0x273934c401ac2014771d9cf5e12d344a300c26d6e9a5d82a32c0b10ac3fe245d", + ) +)] +#[case( + [0x12, 0x49, 0x82, 0xde, 0x03, 0x4f, 0x41, 0x29, 0xc5, 0x3c, 0x4a, 0x52, 0xa0, 0x0c, 0xb0, 0xd2, 0x92, 0x3e, 0xc7, 0x4f, 0x0c, 0x9d, 0xf7, 0x78, 0xaf, 0x7e, 0x34, 0xf7, 0x8f, 0x9e, 0xb8, 0x46], + ( + "0x124982de034f4129c53c4a52a00cb0d2923ec74f0c9df778af7e34f78f9eb848", + "0x1c51c87febe5042e553631880fd35de6a395cf4f39521df7d66adfdcd597876a", + ) +)] +#[case( + [0x64, 0xd3, 0x87, 0xc1, 0x6e, 0x52, 0x10, 0xe3, 0xe4, 0x8a, 0x7f, 0x07, 0x5d, 0x70, 0xd9, 0x2d, 0x19, 0xe9, 0xcc, 0x94, 0x66, 0x7a, 0x7f, 0x6a, 0x95, 0x36, 0xd0, 0xd9, 0x4c, 0x5b, 0xc4, 0xd7], + ( + "0x40aeadbabeed09073e9f39a5a6e2871eae6f7719596ea501cf5b8ab9b61ca4a", + "0x1a874ccf79f771a2caae57d317055589b74215479657d1e4dd505a2c062e878c", + ) +)] +#[case( + [0xf8, 0xac, 0x13, 0xc4, 0xc2, 0xc5, 0x50, 0x3d, 0x7f, 0x7c, 0xa8, 0xe3, 0x8a, 0x53, 0x8f, 0x05, 0xa6, 0x06, 0x04, 0x6a, 0xb7, 0x55, 0xd2, 0x3c, 0x86, 0x26, 0x69, 0x72, 0xaf, 0xbf, 0x74, 0x91], + ( + "0x6b68b865ccd2f6ce5eb4c5302ccd531b07eef93ad1cdd7a5983ad00754e822f", + "0x3527ef056760fc1644805149e0697a75dbd7d309d5017a6ed5484277dbe8dcd", + ) +)] +#[case( + [0x96, 0x74, 0x61, 0x31, 0xcf, 0xcc, 0x2d, 0xcc, 0x27, 0xd0, 0x46, 0xc2, 0x46, 0x2d, 0x10, 0xa7, 0xa4, 0xb8, 0x1f, 0x5b, 0xe6, 0xc9, 0xd5, 0xc7, 0x69, 0x1f, 0xad, 0x1f, 0x34, 0x89, 0x05, 0xee], + ( + "0x54775d92c374d4efedf759ec1a9078ede33dfa7ad74761fb4be08daab120e1a", + "0x3e2c1cab56f549baf1a09d07e5951bda28671c9aa0f1ddb676cb5ba831cb4b", + ) +)] +#[case( + [0x7f, 0x23, 0x8a, 0x24, 0x2f, 0xf8, 0xbe, 0x73, 0xfb, 0xd4, 0x68, 0x5e, 0x36, 0xe7, 0x64, 0xd4, 0xf0, 0x25, 0x7a, 0xb8, 0x47, 0x6e, 0x51, 0x13, 0x18, 0xa5, 0x07, 0xc9, 0x21, 0x2c, 0xb1, 0x73], + ( + "0x1e5aed3e6d957e208b33dcf133e4b419c122a595768abbf8a063ef9b7032b6e6", + "0x1ad8d5bb5ae0e4e7f0d30e9016d20767c050e4f1a496a4c0c1cf081234c197d2", + ) +)] +#[case( + [0x1a, 0xa1, 0x15, 0xdf, 0x35, 0x31, 0xf7, 0x8d, 0x8d, 0x42, 0x26, 0xf3, 0xcd, 0xbd, 0x05, 0x02, 0x74, 0x61, 0xbb, 0xd6, 0xbc, 0x69, 0x1a, 0x5d, 0x17, 0x34, 0x3d, 0xb8, 0x2d, 0x29, 0xee, 0x80], + ( + "0x1aa115df3531f78d8d4226f3cdbd05027461bbd6bc691a5d17343db82d29ee81", + "0x7e0e7f3eb35691c2b36cccf35cf944127a51a4fd8dfc4056078ee9690808bc", + ) +)] +#[case( + [0x1e, 0xb4, 0xe8, 0x6d, 0x29, 0x4b, 0x86, 0x7c, 0x59, 0x03, 0x8b, 0x3f, 0x1e, 0x3d, 0x79, 0xc8, 0x5c, 0xdd, 0x59, 0x3a, 0xe2, 0x8c, 0x16, 0x76, 0x3c, 0x5e, 0x68, 0x15, 0x8f, 0x43, 0xbc, 0xd2], + ( + "0x1eb4e86d294b867c59038b3f1e3d79c85cdd593ae28c16763c5e68158f43bcd3", + "0x3f431fae97d3e02471fb81169367862ce60e92728c08e8b5d89c7099567e31a", + ) +)] +#[case( + [0x34, 0x67, 0x08, 0xf1, 0x00, 0x8c, 0xe1, 0x71, 0x7f, 0x00, 0x88, 0x08, 0xb8, 0xd3, 0xff, 0xb2, 0x15, 0x1d, 0xf3, 0xc2, 0xbb, 0x45, 0x2d, 0x63, 0x34, 0xda, 0x21, 0x90, 0xdd, 0xd6, 0xfb, 0x91], + ( + "0x402ba7e1f5b4147c6b042523752a7547d9c893152d362d5f8b9957a0559fe4c", + "0x19aa41a5f2c38b1002eb3bef7e17d62e63b1ebc0554a395b00730f53b8524eda", + ) +)] +#[case( + [0xe2, 0x70, 0xb9, 0x4f, 0xfc, 0xda, 0x54, 0x73, 0xd3, 0x9b, 0xf7, 0x23, 0xb1, 0xc3, 0x83, 0xf1, 0xe8, 0x01, 0xe8, 0xf7, 0x57, 0xb0, 0x9d, 0xf3, 0x27, 0xb5, 0x8b, 0xb6, 0x95, 0x3d, 0x78, 0xa8], + ( + "0x20df7f847813d3ccf25ae049abbe227b89fc3eb1b5e973be37335b5b3349838e", + "0xe8fc480f055cc91887432500a8ab752fa470ec49288701b52d8c658e32f3d3f", + ) +)] +#[case( + [0x25, 0x79, 0x24, 0x89, 0x2e, 0x15, 0x34, 0x5c, 0xe7, 0xfa, 0x78, 0x15, 0x68, 0xf8, 0x23, 0x3d, 0x1d, 0x4e, 0xb8, 0x7c, 0xaf, 0xa8, 0x75, 0x04, 0x49, 0xaf, 0xd0, 0x39, 0x77, 0x7b, 0xbe, 0xac], + ( + "0x257924892e15345ce7fa781568f8233d1d4eb87cafa8750449afd039777bbeae", + "0x1f989e2033b6f96ecae6143bacfa926c432075f6b10afdbde7f0aef1bd516eee", + ) +)] +#[case( + [0xc1, 0x4c, 0xcb, 0x92, 0xa0, 0xfd, 0x6e, 0x44, 0xaf, 0x59, 0xf6, 0x4e, 0x71, 0xfd, 0x3b, 0x85, 0xd6, 0x45, 0xe4, 0x68, 0xb3, 0xa3, 0x7a, 0x88, 0xab, 0x02, 0xd7, 0x7f, 0x3b, 0x9b, 0x17, 0x29], + ( + "0x301fe039fd688dc78669252aed79326d0fc1a4b47a4e1ae0f6a1333ab2241f56", + "0x2435ae0acce98fc60bce565d1b6139d82e7871cc5bf7456b39ff63c057f85473", + ) +)] +#[case( + [0x6b, 0x3b, 0x0a, 0x09, 0x43, 0x4d, 0x23, 0x0d, 0x4c, 0x6f, 0x93, 0xba, 0xe3, 0x02, 0xd7, 0x1b, 0xcc, 0xa5, 0x9e, 0xbb, 0x27, 0xb6, 0xa9, 0x66, 0xb3, 0x8f, 0x49, 0x06, 0x73, 0xbe, 0x79, 0xf1], + ( + "0xa726d2380e9e2b9dbcf084de00026609da2c99856d3144c3b4e30d8c2c47f66", + "0x46ab7fdff5fef556240462c54f44e9529a58881530501d36c32648320e24e56", + ) +)] +#[case( + [0xef, 0xc1, 0x7b, 0x7d, 0xc5, 0xf6, 0x09, 0x60, 0xc7, 0x7e, 0x9a, 0xda, 0x78, 0xee, 0xa6, 0x86, 0x55, 0xf4, 0xd4, 0x66, 0xdc, 0xbe, 0x89, 0x86, 0x71, 0x46, 0xd3, 0x47, 0xcf, 0xb3, 0xe0, 0x78], + ( + "0x2e3041b2412f88b9e63d840072e9450ff7ef2a213af75f5180c4a2ec6dbfeb5d", + "0x18504023c8e5785ed9fac7ed426576a4069e3607c0503328efca5a7abaa2aaa5", + ) +)] +#[case( + [0xc2, 0x40, 0x70, 0xce, 0x86, 0x15, 0xe5, 0x11, 0x95, 0x51, 0xac, 0xe6, 0xb4, 0xcc, 0xc8, 0xf5, 0xb8, 0x8a, 0xf6, 0x8e, 0x02, 0x36, 0x7f, 0xce, 0x8f, 0x74, 0xde, 0xac, 0xa8, 0x9d, 0xf8, 0xaa], + ( + "0xaf3703014f646ab410960caec7677f5a854c48606f55999ef2ae5146aa038f", + "0x2ef525f1ce2b9687b56a4d655374dd2c46332aaad6f0ee71db0af12a962ff371", + ) +)] +#[case( + [0x3a, 0x10, 0xb2, 0x1b, 0xc8, 0x59, 0xf0, 0x86, 0xeb, 0xd2, 0x64, 0x2f, 0xb5, 0x22, 0xfc, 0xea, 0xf7, 0x07, 0xdc, 0x5e, 0xeb, 0x46, 0x0c, 0x12, 0x27, 0xfa, 0xf3, 0xf6, 0x36, 0x74, 0xf1, 0xb1], + ( + "0x9ac63a8e728505d33821e7933a1a48d5f8671cd82d44184ebda67df5df7f46f", + "0xb0804c0f989a16041f75b8b8728838f365f476aabc19b145d0620b53386451c", + ) +)] +#[case( + [0xcf, 0x3d, 0xc4, 0xaf, 0x86, 0x79, 0xba, 0x0c, 0x0e, 0x55, 0x72, 0x1f, 0x28, 0x0e, 0x5d, 0x6e, 0xd7, 0x0c, 0xc3, 0x98, 0x64, 0xd1, 0x31, 0x1c, 0x05, 0x15, 0xa2, 0x5f, 0x4f, 0xa0, 0xc2, 0xed], + ( + "0xdac8ae401b339652d145b452208fbf879071952c30a06e714937203edaccdd4", + "0x2f178c75c7f3681cc8417a2efa8a5cfe9e9505c6346e8cf77c7c97108d1707ff", + ) +)] +#[case( + [0x49, 0xaf, 0x83, 0x00, 0x60, 0x19, 0x13, 0x24, 0xea, 0x98, 0x1b, 0x1a, 0xf5, 0x84, 0x72, 0x02, 0xd3, 0x0f, 0x28, 0x80, 0xbd, 0xa0, 0x9d, 0x33, 0xc4, 0x49, 0xa2, 0xf5, 0x7b, 0xca, 0xe1, 0xfe], + ( + "0x194b348d7ee772fb3247d564740319a53b8dbdef552ed2a6882916dea34de4b8", + "0x2792fb2742e19963369b48f358a321f35138bd5cec49cb47181f9b315bfbd9c8", + ) +)] +#[case( + [0xe7, 0xb2, 0x22, 0xb4, 0xae, 0xea, 0xfe, 0xc6, 0x2b, 0xa4, 0xb2, 0x04, 0xe0, 0x8a, 0xa2, 0x85, 0xca, 0x74, 0x0a, 0x75, 0xa3, 0x12, 0x4d, 0xc7, 0xf3, 0x22, 0xc2, 0x9f, 0x18, 0x95, 0x56, 0x13], + ( + "0x2620e8e92a247e1f4a639b2ada85410f6c6e6030014b239302a09243b6a160f9", + "0x30229ec3bc668ca9965bd8c24c64af0026f4c8c7359d6c566775e1bd55514219", + ) +)] +#[case( + [0xd0, 0x38, 0x1e, 0xd6, 0xae, 0xd8, 0x85, 0xe2, 0x2d, 0x22, 0xdc, 0x10, 0x5e, 0x89, 0xc9, 0xc7, 0xc7, 0xba, 0x91, 0x7f, 0x98, 0xfe, 0x05, 0x59, 0xf0, 0xb6, 0x2e, 0xed, 0x24, 0xc7, 0xf5, 0x58], + ( + "0xea6e50b2a12053b4be1c5365884685169b4e739f736db250033fe91c2d4003f", + "0x12cec8eef83574d643c0602fcba9e51cbb4cb1315dfd36ddc442c46df948301d", + ) +)] +#[case( + [0x6b, 0xe4, 0xe5, 0x7d, 0x54, 0xf0, 0x48, 0xe0, 0x3f, 0x7e, 0xe5, 0x16, 0x91, 0x5d, 0x1c, 0xa2, 0x04, 0x4c, 0x08, 0x85, 0xf3, 0xe2, 0x50, 0x02, 0x73, 0x85, 0x65, 0x79, 0xde, 0x86, 0x5c, 0x75], + ( + "0xb1c4897928d088ccede59a98e5a6be6d549336322febae7fb444d4c2d8c61e8", + "0x2e7ed32a4ac6444abee30ba89c4e9e5937bde6898c52bc0e8cac29a4b2c53a78", + ) +)] +#[case( + [0x20, 0x24, 0x92, 0x76, 0xe9, 0x41, 0x79, 0x08, 0x75, 0x82, 0xcd, 0xe9, 0x15, 0x76, 0xa0, 0xba, 0x2a, 0x8d, 0x69, 0x9f, 0xca, 0xa3, 0xc5, 0xa6, 0x8a, 0xf6, 0xcd, 0xdb, 0xbe, 0x90, 0x6b, 0x17], + ( + "0x20249276e94179087582cde91576a0ba2a8d699fcaa3c5a68af6cddbbe906b1a", + "0x7bf62a4abe2e61ca5c6fbfa714b557425f9dbf4c334a36ea96f46a1e74b4364", + ) +)] +#[case( + [0x98, 0x1f, 0x5b, 0x9d, 0x34, 0x7e, 0x79, 0xe6, 0x71, 0xe6, 0x25, 0xe8, 0xb1, 0xe2, 0xdc, 0x27, 0xa3, 0x90, 0x43, 0x14, 0xe3, 0xe5, 0x5e, 0x58, 0x7b, 0x8f, 0xab, 0x9f, 0x9c, 0x94, 0x03, 0x1f], + ( + "0x6f2704490e9996948f554c52d5ed30edd0c0360aa8ffeb0c72e075b131d0b4b", + "0x2d5bf423778fedc032bc999d5662e0e17873b38ce38acb17ac534dde1c52496d", + ) +)] +#[case( + [0x8e, 0x63, 0x3a, 0x31, 0xc5, 0x6d, 0x22, 0x8b, 0x4d, 0x55, 0xda, 0xbd, 0x4e, 0x2a, 0x9b, 0xae, 0xf6, 0x12, 0x4c, 0xf6, 0x56, 0x3d, 0xc8, 0x76, 0xb6, 0x33, 0x72, 0x48, 0x9a, 0x30, 0xfc, 0x3f], + ( + "0x2d9a9d4c0309e237dcb54f504b27eaf3c70f77d3855a335c3df25a1ae93701b2", + "0x1a1c7b0840b30bdaef6016acdd9396772e2da3639cc2b7c1c79ed30fdf94bce3", + ) +)] +#[case( + [0x41, 0xe0, 0x5f, 0x70, 0xa2, 0x15, 0x83, 0x7a, 0x69, 0x2a, 0x8e, 0x18, 0x5f, 0x7a, 0x99, 0xe5, 0x86, 0x21, 0x51, 0xbd, 0xe7, 0xe4, 0xf4, 0x72, 0xfa, 0x8b, 0xf8, 0x54, 0x5e, 0xf5, 0x85, 0xd7], + ( + "0x117c10fdc0e3e350b0da4861ddf94187ee9fe72c7f7329e5be6b6c3d86788891", + "0x106b5ce44a3c2c05625ab0f76a6857a10db19b30554817a261dea85e384266d8", + ) +)] +#[case( + [0x76, 0x67, 0x10, 0xb5, 0x92, 0xe8, 0x2f, 0xd1, 0xa8, 0x96, 0x8b, 0xb9, 0x13, 0x0f, 0x50, 0xe3, 0xda, 0xfa, 0xeb, 0x12, 0xce, 0xa4, 0x13, 0xe4, 0x5e, 0x31, 0xcd, 0x0c, 0x55, 0x08, 0xd4, 0x4e], + ( + "0x159e73cfd084ef7e37f6004c100ca028abf815effdc07ec9e5f0b4dea40ed9c1", + "0x13f78fde0e9596fad99a998b345d5269a58053753f3a7ec60c9fcd07f0e3b9a4", + ) +)] +#[case( + [0x6a, 0x8f, 0xdb, 0x6f, 0xdf, 0xa0, 0xf2, 0xd3, 0x5a, 0xba, 0x0e, 0x9d, 0x7e, 0xfe, 0x47, 0x36, 0x45, 0x9c, 0xe1, 0xa0, 0x5c, 0x78, 0x25, 0x25, 0x60, 0x0f, 0x48, 0x82, 0x92, 0xfc, 0x1a, 0x66], + ( + "0x9c73e8a1d3db27fea1983307bfb967b169a0c7d8b94900ae7ce3054e2021fd9", + "0x1835feae059b1bbdf10a9b8146d3c9e533dcd3c0f47ade837fa46f16906080b6", + ) +)] +#[case( + [0x45, 0x52, 0xa1, 0x43, 0x6a, 0xe7, 0xa1, 0x69, 0x61, 0x91, 0x3d, 0xfb, 0x71, 0x48, 0xe6, 0x18, 0x30, 0x11, 0x13, 0x09, 0xaf, 0xcc, 0xe3, 0x8a, 0xfa, 0x82, 0x33, 0x6a, 0x7c, 0x14, 0xda, 0x07], + ( + "0x14ee52d089b6013fa940f844efc78dba988fa878475b18fdbe61a753a397dcc2", + "0x166ed0d935faa57333f3d7b5563bf82573b89fd7874573d8df0984cc05cbd752", + ) +)] +#[case( + [0x94, 0xb4, 0xbd, 0xbb, 0xcd, 0x3d, 0x9d, 0x7e, 0x3b, 0x90, 0x2c, 0x9d, 0x02, 0x73, 0xf8, 0x7a, 0x84, 0x51, 0x0e, 0x52, 0xa5, 0x8c, 0x75, 0xfe, 0xce, 0xf5, 0x00, 0x0d, 0xf5, 0x4c, 0x91, 0x85], + ( + "0x387d26329a8bd01129f5b797defef61bdccce9e6c3716571a935bc96bd599b3", + "0x269d410ba9ea2bc9883b21071c96ce684d2d3f8751ec5b202d2108351f1e6e91", + ) +)] +#[case( + [0x45, 0xe8, 0xf2, 0x5a, 0xfe, 0xf6, 0xfd, 0x7a, 0x2f, 0xf7, 0xcf, 0x6b, 0x05, 0x8b, 0x2d, 0xf9, 0x03, 0x5c, 0x76, 0x7a, 0x16, 0x1b, 0x55, 0x06, 0x39, 0x22, 0xdd, 0xc7, 0xa9, 0x55, 0xf7, 0x24], + ( + "0x1584a3e81dc55d5077a789b48409d59b6bdb0be8ada98a78fd0251b0d0d8f9de", + "0x1a9e98c09961eeb7ecda08b384f55b4bee996f5c5d3447979575bbe91b8e68b2", + ) +)] +#[case( + [0xa2, 0xc0, 0xdd, 0xe2, 0x1a, 0x63, 0xd8, 0xe7, 0x57, 0xa9, 0x98, 0x51, 0xd8, 0x79, 0xf6, 0xe2, 0xe5, 0x82, 0x60, 0x7b, 0xd2, 0x08, 0x80, 0xef, 0x64, 0xc8, 0x31, 0xc9, 0xa7, 0xce, 0x88, 0x00], + ( + "0x1193f28976cef86a2eb8c72e53f5edca1efe20c798b32147b0668d851e57902d", + "0x241199f07ac58ba64709d646771084f30197bb59ff56a608d6e3dc3e726fdebb", + ) +)] +#[case( + [0xf7, 0x03, 0x68, 0x24, 0x29, 0x60, 0x72, 0x91, 0x63, 0x35, 0xa4, 0x35, 0x19, 0xa7, 0x09, 0xd4, 0x1e, 0x8e, 0x24, 0x4a, 0x34, 0xd0, 0x76, 0x71, 0x61, 0xe3, 0x76, 0xea, 0x41, 0x96, 0x5d, 0x8e], + ( + "0x50ddfe5c36851c0c9a447a49220500029070f732a9781af3540ba7807256b31", + "0xbc3e5ca0c6840889a0dda586ae1134ae54d0a55b9455559b9eb05fee189ce79", + ) +)] +#[case( + [0x62, 0xc1, 0xaf, 0x24, 0xad, 0x33, 0xc5, 0x62, 0x35, 0x66, 0x76, 0x40, 0x1c, 0x86, 0x66, 0xad, 0x22, 0x05, 0x21, 0x82, 0xc9, 0xa2, 0xaf, 0xdd, 0x7a, 0xbc, 0x13, 0x8f, 0xaa, 0x0d, 0x15, 0x50], + ( + "0x1f9123eead0850ec4c5ead31983b5f1f3024c5ff8bf1ac3027afb61f9131ac3", + "0x7293610b80e44120b6d7760348f8fce52682cb22937352d5b3027fbe6c25b54", + ) +)] +#[case( + [0xaa, 0x0a, 0x4e, 0xa0, 0xab, 0x4c, 0x0e, 0xbf, 0x66, 0x39, 0x9b, 0x36, 0xdc, 0xc1, 0x75, 0x9c, 0x0f, 0x00, 0x31, 0xb5, 0x45, 0xc5, 0x1d, 0xdc, 0x38, 0x45, 0x76, 0x53, 0x31, 0x07, 0x99, 0xa4], + ( + "0x18dd634807b72e423d48ca13583d6c83487bf2010c6fbe3483e3d20ea790a1d1", + "0x17c137a4ae3a4b273db4d0a58881f63fb785eb13200df81571f135eb7872b539", + ) +)] +#[case( + [0xa9, 0x4f, 0xd0, 0x74, 0x47, 0x80, 0x07, 0xc2, 0xe9, 0x54, 0x55, 0x14, 0x6a, 0x76, 0xe4, 0xff, 0x6c, 0x33, 0xa8, 0x54, 0xd1, 0x51, 0xf9, 0x92, 0xc0, 0x54, 0xbd, 0x62, 0xc3, 0xab, 0xc4, 0xd7], + ( + "0x1822e51ba3eb2745c06383f0e5f2dbe6a5af68a097fc99eb0bf3191e3a34cd04", + "0x255727a6ad0bf34a99c7ea8d7c824f6076521640f7e6ad459e67e1e2d290412b", + ) +)] +#[case( + [0xa3, 0x25, 0xbc, 0x68, 0xe7, 0x21, 0x01, 0xcd, 0xfd, 0x27, 0x23, 0xba, 0xb8, 0x2c, 0x44, 0xec, 0x83, 0x71, 0x00, 0x6f, 0xb1, 0xf3, 0x7a, 0x7d, 0x3b, 0xcd, 0x38, 0x72, 0xe2, 0x32, 0x77, 0x41], + ( + "0x11f8d110438c2150d436529733a83bd3bcecc0bb789e1ad5876b942e58bb7f6d", + "0x1efe429d05856da71c87b24c67834e96758d9a56fed30bf0ecedc6e23fc6e86f", + ) +)] +#[case( + [0x79, 0x3d, 0xd1, 0xab, 0xa5, 0x46, 0x98, 0xfd, 0x37, 0x56, 0x61, 0x3b, 0x80, 0x53, 0x18, 0xd9, 0xce, 0x22, 0xe0, 0x57, 0x92, 0xca, 0x62, 0x56, 0x11, 0xac, 0x6d, 0xa2, 0xee, 0xf0, 0x6b, 0xcf], + ( + "0x187534c5e2e358a9c6b5d5ce7d50681e9f200b34c1e6cd3b996b55753df67142", + "0x238bf89fc4013d62d7b3b59d62fd7e80f5579a230cbf259b9f2afd52fc0d05d8", + ) +)] +#[case( + [0x48, 0xa2, 0xf5, 0x9a, 0x3d, 0x15, 0x48, 0x70, 0xf2, 0x1b, 0x98, 0x8d, 0x2e, 0x61, 0x75, 0x4c, 0xa2, 0x19, 0x4f, 0xc9, 0xcd, 0x9e, 0xad, 0x8f, 0xff, 0x14, 0x30, 0xc1, 0x1a, 0xf5, 0x0c, 0xfe], + ( + "0x183ea7275be3a84739cb52d6ace01cef0a97e538652ce302c2f3a4aa42780fb9", + "0x107f79839eb4ef78cd3bf14ea4f31af765d750fcec55a05bdea0e1988799d770", + ) +)] +#[case( + [0x4d, 0x0f, 0x2c, 0xf2, 0xd1, 0xfc, 0x52, 0x49, 0xc5, 0x21, 0xaa, 0xbf, 0xbf, 0x91, 0xb9, 0x13, 0xd1, 0xfb, 0x42, 0x19, 0x86, 0x0a, 0x35, 0x5e, 0x3a, 0x3a, 0xee, 0x76, 0xd9, 0x2d, 0x6d, 0xf9], + ( + "0x1caade7ff0cab2200cd165093e1060b63a79d7881d986ad0fe1a626000b070b3", + "0x1af1ed981c6638f540e8213d8dd86effc94aae6a246033e6763fe22da4b52f8", + ) +)] +#[case( + [0xa2, 0xc3, 0xb5, 0xac, 0x07, 0xbd, 0x2f, 0x74, 0xf7, 0x98, 0x7e, 0x00, 0xe2, 0xaf, 0x52, 0x4f, 0x6a, 0x95, 0x07, 0xd4, 0x14, 0x93, 0x16, 0x87, 0xf6, 0xca, 0x42, 0x34, 0xe3, 0x7d, 0xf3, 0x2c], + ( + "0x1196ca5364284ef7cea7acdd5e2b4936a410c81fdb3db6e042689df05a06fb58", + "0x1c650bdd7a09178066e43e0ffe80de22481d67f45eafbb322b91b7527a5bf5ef", + ) +)] +#[case( + [0x9f, 0x07, 0xc9, 0x80, 0xf0, 0xb8, 0xb1, 0x26, 0x03, 0xd8, 0x8e, 0xfe, 0xd7, 0x7f, 0x1a, 0x75, 0x01, 0xbc, 0x3b, 0xa1, 0xe4, 0x1e, 0x10, 0x2c, 0xf3, 0xf1, 0x62, 0xf1, 0xf8, 0x1b, 0x74, 0xef], + ( + "0xddade284d23d0a8dae7bddb52fb115c3b37fbedaac8b0853f8fbead6ea47d1d", + "0x1747a74235fb00662a67bc573471a5f7c6514b4e82f06388b938ed62f1900fbf", + ) +)] +#[case( + [0x43, 0xef, 0x47, 0xbe, 0x77, 0xec, 0xba, 0x40, 0xa5, 0x5f, 0x78, 0x5b, 0x05, 0x26, 0x21, 0x73, 0x32, 0x6d, 0x9c, 0x10, 0xae, 0x1a, 0xf1, 0xaa, 0xba, 0xde, 0x95, 0x61, 0x71, 0x86, 0x00, 0x08], + ( + "0x138af94b96bb1a16ed0f32a483a4c9159aec317f45a9271d7ebe094a990902c3", + "0x27800587109ddaa3d2398457e8eff9d08594f2ef80ff6c168be9c17bfc67bb86", + ) +)] +#[case( + [0x99, 0x32, 0x74, 0x19, 0x77, 0x0f, 0x9b, 0x3d, 0x5d, 0x19, 0xce, 0xad, 0xcc, 0x06, 0xa5, 0x1d, 0x08, 0xe2, 0x86, 0x30, 0x4b, 0x61, 0xd5, 0x08, 0xcc, 0x36, 0xbc, 0x2e, 0x23, 0x5b, 0xf3, 0x05], + ( + "0x80588c0d37abac03428fd8a47829c04425e467c120c756117d517e999e4fb32", + "0x2b2b597df75fa810ed717dfe13439b741757b38c9334b122f4be3c506af6256d", + ) +)] +#[case( + [0x34, 0x7b, 0x86, 0xec, 0xe0, 0x00, 0x89, 0x2a, 0x2d, 0x84, 0x5b, 0x2b, 0x36, 0x82, 0x21, 0x63, 0x8a, 0x2a, 0x04, 0x82, 0x1d, 0x03, 0x2c, 0xe3, 0xef, 0xbc, 0xf7, 0xbe, 0x57, 0x44, 0x79, 0x59], + ( + "0x4173879fecee90075341574b500c905f2a899f0b4916256b39c6ba77ec77c13", + "0x245945b21c5f4c904ca92ae4841b7e3cf68b478b4b57188067544a70cf641e92", + ) +)] +#[case( + [0xb2, 0x32, 0xe7, 0x37, 0x69, 0xe5, 0xec, 0x75, 0xd8, 0x50, 0xeb, 0xac, 0xa4, 0x05, 0xd5, 0x7b, 0x59, 0x8d, 0x3b, 0xe7, 0x74, 0x7f, 0x00, 0xb6, 0x28, 0x11, 0x65, 0xe9, 0x27, 0xa8, 0x36, 0xb8], + ( + "0x2105fbdec6510bf8af601a891f81cc629308fc333b29a10e73afc1a49e313ee8", + "0x252caf4b3112785e5f912c17d052c4c14fa676800fcd37876dadde3a28d7dd81", + ) +)] +#[case( + [0x66, 0xa8, 0xd3, 0xe8, 0x9d, 0xc0, 0x41, 0x16, 0xe4, 0x64, 0xc4, 0xfe, 0x87, 0xcf, 0x7c, 0x83, 0x93, 0x12, 0x86, 0xef, 0xf4, 0x6a, 0xa2, 0x5a, 0x17, 0xf2, 0x62, 0xcd, 0x83, 0x1e, 0xbd, 0x1e], + ( + "0x5e03702db5d00c373c4399184cccbc8640fb1cd23870d3f9fb14a9fd224c292", + "0x26fdc724c7abf7481d3156c1d3d11b477c75099b1ae5aa683e8db7cfb8e94c9e", + ) +)] +#[case( + [0xb6, 0x95, 0xf0, 0x38, 0xf9, 0x32, 0x4f, 0x62, 0x8c, 0x2d, 0x39, 0x60, 0x83, 0xbe, 0x9f, 0xa5, 0xa9, 0xe6, 0xf7, 0xbf, 0xe7, 0x1f, 0x64, 0xc9, 0x5b, 0x8d, 0xc7, 0x23, 0xde, 0x94, 0x0e, 0xa9], + ( + "0x256904e0559d6ee5633c683cff3a968ce362b80badca0521a72c22df551d16d5", + "0x8a31cf2520f6f457641e85200ce2926b6869746fe11b7c3371d3b99658a4b67", + ) +)] +#[case( + [0x3a, 0xb4, 0x53, 0xb4, 0xf9, 0x53, 0xe1, 0x50, 0xaa, 0xb1, 0x57, 0xdd, 0x64, 0xd7, 0x85, 0x77, 0x9e, 0xeb, 0xe6, 0x00, 0xb8, 0x7f, 0xb6, 0xf8, 0xe4, 0x62, 0x1f, 0x41, 0x94, 0x41, 0x73, 0x57], + ( + "0xa50054218224126f2611226e3562d1a076a7b6f500dec6ba841932abbc47611", + "0x6077211dfc9be65367a46b06d7e680e80be6ea6b89b37942739bedbef074a42", + ) +)] +#[case( + [0xfc, 0x0c, 0xe9, 0xb2, 0xec, 0xb2, 0x50, 0xfb, 0xb9, 0x34, 0xbc, 0x5a, 0x74, 0x98, 0xe4, 0xc7, 0x62, 0x8d, 0x6f, 0x1f, 0x3a, 0x56, 0x69, 0x45, 0x47, 0x96, 0xbe, 0x15, 0xf8, 0x56, 0x31, 0x4e], + ( + "0xa17617486ba302b1fa35fc9ed122af36d065a48301d74831af401a3bde53eec", + "0x963de747d8d2b579ae26cf9112dd73fb1698112221245375a8915df8cd5c68f", + ) +)] +#[case( + [0x7c, 0x79, 0x95, 0x2e, 0x47, 0x38, 0xd5, 0x5f, 0xd8, 0xe3, 0x8f, 0x00, 0x89, 0x21, 0xb9, 0xba, 0x3a, 0xda, 0x1b, 0x74, 0xc2, 0xd1, 0x7c, 0x80, 0xe4, 0x47, 0x50, 0x0a, 0xb5, 0x40, 0xcf, 0xd7], + ( + "0x1bb0f84884d5950c68430393861f08ff0bd74651f1ede7666c0637dd0446d54b", + "0x1f6ba94c51523e4ea8e86c22cc91d2b41800cb6b82f55dff5c0e29eb7af447a4", + ) +)] +#[case( + [0xab, 0x5e, 0x49, 0x8f, 0xd0, 0xf7, 0x2c, 0xd8, 0x54, 0xed, 0xe6, 0x27, 0x20, 0x4c, 0x23, 0xd6, 0x39, 0xa1, 0x4a, 0x71, 0x4b, 0x35, 0xe7, 0xa8, 0x09, 0x1e, 0x0f, 0x04, 0xa4, 0xd7, 0x49, 0xb2], + ( + "0x1a315e372d624c5b2bfd15039bc81abd731d0abd11e0880054bc6ac01b6051de", + "0x2f3da23bb87b5172ca0a1fda1eca300280f31aa27bd7fb97880cc68cf44dd8e7", + ) +)] +#[case( + [0xaf, 0x4c, 0x6f, 0xf5, 0xb4, 0x84, 0x7e, 0x3c, 0x08, 0x7c, 0xf6, 0x1f, 0x23, 0x14, 0x1c, 0x60, 0xd4, 0x61, 0xf2, 0x52, 0x3a, 0x5f, 0x11, 0xa5, 0x5c, 0x26, 0x24, 0xdf, 0x02, 0xaf, 0x85, 0x46], + ( + "0x1e1f849d10ef9dbedf8c24fb9e9013480dddb29e0109b1fda7c4809a79388d72", + "0x1769bb9eef351384b387d37efa6c5e350f5f5aa92828fb66a7ce1e19a1160c07", + ) +)] +#[case( + [0x99, 0xd5, 0x5e, 0xd5, 0x2a, 0xf3, 0xed, 0x12, 0xb0, 0x4d, 0x7f, 0x6d, 0x19, 0xcc, 0xaf, 0x69, 0x38, 0x85, 0xdd, 0xdb, 0xcd, 0x8c, 0xbe, 0xd9, 0xd9, 0x01, 0x20, 0x28, 0x34, 0x18, 0x4f, 0xf0], + ( + "0x8a8737c875f0c95875cae499548a65072019e2794375f32249f7be3aaa1581c", + "0x18291fd161cc0167ccdc7ace93209769f4c2b598e5d4e18471bbb9dd030ec411", + ) +)] +#[case( + [0xe2, 0x7d, 0x5b, 0xf5, 0x9a, 0x10, 0x33, 0x4b, 0x41, 0x18, 0x88, 0x70, 0x78, 0x86, 0x2b, 0xbd, 0xd1, 0x62, 0x21, 0xfb, 0xd0, 0xaa, 0xb9, 0x99, 0x50, 0x10, 0xaf, 0x19, 0xdf, 0x1c, 0x2b, 0x3a], + ( + "0x20ec222a1549b2a45fd771967280ca47735c77b62ee38f645f8e7ebe7d28361f", + "0x17e26eba42b8a51b5364e0d117e68d46ea8d7c52f3885ed075bc441d62daf4e9", + ) +)] +#[case( + [0x62, 0x52, 0xa3, 0xde, 0xa6, 0x05, 0x54, 0x85, 0x65, 0xb6, 0x83, 0x8f, 0x85, 0x38, 0xee, 0xab, 0x9c, 0x8b, 0x66, 0x64, 0x90, 0x05, 0xc0, 0x17, 0x95, 0x9d, 0x0d, 0x2d, 0x20, 0xec, 0x2a, 0xa0], + ( + "0x18a06f8e3a21431f515f82282363df06d889141bf222afd1d5bf4ff6ff23019", + "0x1b7100007cbb0527c1fac9c535384306437fee51c246f5aad7398f6b147135e", + ) +)] +#[case( + [0x63, 0x13, 0x43, 0x12, 0x88, 0x35, 0x21, 0x36, 0x56, 0xaa, 0x9f, 0x4d, 0xfd, 0x7d, 0x6e, 0xca, 0xa5, 0xa8, 0x25, 0xd0, 0x9c, 0xb4, 0xa6, 0xa4, 0x24, 0xcf, 0x53, 0x90, 0x92, 0x17, 0xb8, 0xdd], + ( + "0x24aa62cc5d1e0e2e60a13e0fa7abe0f76a550adcbd11189ac8e3b62e11dbe50", + "0x2b9d2ba81761e8cbdef99433b49216b1d0a7dbcb19b5b000de82bd497febce90", + ) +)] +#[case( + [0xbd, 0xd9, 0x27, 0xb4, 0x6b, 0x96, 0x7b, 0xc9, 0x3a, 0xc4, 0x61, 0x41, 0x8d, 0x5e, 0x66, 0xad, 0xf2, 0xde, 0x77, 0x58, 0x95, 0x42, 0x57, 0x45, 0x5b, 0x16, 0x5e, 0x40, 0x5f, 0x40, 0x25, 0xc9], + ( + "0x2cac3c5bc8019b4c11d3901e08da5d952c5a37a45becf79da6b4b9fbd5c92df6", + "0xed67a945faf7b28eea82c4367889b22772a45a5b3552afcf47499a1f776b749", + ) +)] +#[case( + [0x7f, 0x6f, 0x8b, 0xde, 0xf8, 0x24, 0x5a, 0x28, 0x10, 0x50, 0x4d, 0xa9, 0x8c, 0xe0, 0x59, 0x64, 0x5c, 0xa3, 0xa6, 0x27, 0x17, 0x79, 0x8e, 0x5c, 0x13, 0x5f, 0xbb, 0x5c, 0x13, 0x9a, 0x55, 0x59], + ( + "0x1ea6eef935c119d49fafc23c89dda8a92da0d1044695f9419b1ea32e62a05acc", + "0x5998789b8df4a10b776915ba84d5a2bd92eacbe8839fd9467e58df7deb54918", + ) +)] +#[case( + [0x36, 0xa5, 0x2b, 0x01, 0xb7, 0x76, 0x93, 0x9b, 0x6a, 0x6c, 0xec, 0xca, 0x0e, 0x11, 0x54, 0xc8, 0xb7, 0x91, 0x88, 0xa0, 0x9d, 0x0e, 0xf4, 0x67, 0x9b, 0x84, 0xa3, 0xe9, 0x05, 0x38, 0xf1, 0x99], + ( + "0x640dc8ed644f371b21ca7138c8ffc6b20101e0f349d29da5f6417d22cbbf455", + "0xcd695eb60268449ff5bc196b963104ece0e0a25ea40fe70cccbf7f513b25e32", + ) +)] +#[case( + [0x0b, 0xac, 0x65, 0xce, 0x37, 0x67, 0x55, 0x9a, 0x34, 0x61, 0xbc, 0xc0, 0x78, 0x39, 0xfb, 0x41, 0x93, 0xf0, 0xb3, 0x5d, 0xad, 0x91, 0x90, 0xbe, 0xe5, 0xa4, 0x5c, 0xce, 0x12, 0x9f, 0x49, 0x2f], + ( + "0xbac65ce3767559a3461bcc07839fb4193f0b35dad9190bee5a45cce129f4931", + "0x16f8bfc17896276daad9fc8042bd1cc23102691f1bf6d02d8a98af3506479b42", + ) +)] +#[case( + [0x4b, 0x73, 0x62, 0x43, 0x62, 0x23, 0xef, 0xbd, 0xda, 0xe8, 0x27, 0xc7, 0x47, 0x05, 0x88, 0x51, 0x94, 0xdc, 0x53, 0x8e, 0x82, 0x7e, 0xb1, 0x9e, 0xc0, 0x63, 0xe2, 0x61, 0x2b, 0x54, 0x37, 0x58], + ( + "0x1b0f13d080f24f942297e210c5842ff3fd5ae8fd1a0ce7118443564a52d73a13", + "0x2a63fcc846be71e41180eb5555af10c3d800c4f2182bcfa70ccb38c1ca031b3e", + ) +)] +#[case( + [0x30, 0x65, 0x95, 0xba, 0xf3, 0xbc, 0x3a, 0x34, 0x1a, 0xb9, 0x42, 0xf6, 0x00, 0x94, 0xd9, 0x1e, 0xc5, 0x51, 0x4b, 0x1c, 0x53, 0x5a, 0x33, 0xca, 0x77, 0x03, 0x93, 0x12, 0x39, 0x99, 0x3c, 0x45], + ( + "0x14748128a9a0a6268fd3f7f1380c12dcfe08aeae8693d3ae306fb611c3f00", + "0x1b97709f6f73cc1de91f093c39f1676b3f9df710e918d44f413c577b75f6a2d4", + ) +)] +#[case( + [0xa4, 0x60, 0x9a, 0x18, 0xf4, 0x91, 0x1a, 0x4c, 0x2e, 0xcf, 0x7d, 0xe1, 0x21, 0x17, 0x13, 0xe3, 0x51, 0xe6, 0x4d, 0xcf, 0xa2, 0x67, 0xe3, 0xe1, 0x63, 0x5a, 0x05, 0x07, 0xb7, 0xd9, 0xf6, 0x4c], + ( + "0x1333aec050fc39cf05deacbd9c930aca8b620e1b69128439aef860c32e62fe78", + "0x7d90bb5159c95cb06870125aa576afa08c51d1e036af08c62ab5f6bff57ac95", + ) +)] +#[case( + [0x7a, 0x19, 0xf1, 0xbc, 0xc8, 0xc0, 0x82, 0xc3, 0x6f, 0x15, 0x7a, 0x6a, 0x53, 0x8a, 0xd7, 0x57, 0x89, 0xe6, 0xb5, 0x86, 0xe3, 0x97, 0x7e, 0xdd, 0x99, 0xc4, 0xad, 0x9b, 0xde, 0xc9, 0x98, 0x32], + ( + "0x195154d7065d426ffe74eefd5088269c5ae3e06412b3e9c32183956e2dcf9da7", + "0x1afabeb6cc7b74080ace244b6b464335b4b068f661f72b969e9badc0f4d2508e", + ) +)] +#[case( + [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], + ( + "0xe0a77c19a07df2f666ea36f7879462c0a78eb28f5c70b3dd35d438dc58f0d9d", + "0x7e1e236cdff80b192b235c513456f009e6c4be16a4bd0cba8b1c036c07d676f", + ) +)] +#[case( + [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe], + ( + "0xe0a77c19a07df2f666ea36f7879462c0a78eb28f5c70b3dd35d438dc58f0d9c", + "0x1ba60ab9532bc4edca3bebc05b5b1895dab411a9ecf0485a9c95c4d19bea3c7d", + ) +)] +#[case( + [0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], + ( + "0x2e6ec6347b397f591ebee925f9fa9e89a1fa55ba5e38d5cb0f7dcfa49e0c0ae5", + "0x195e851647e4861db215653fd73aaa4c4238f70ad4132e1d83af907f3b50e3a7", + ) +)] +#[case( + [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01], + ( + "0x2", + "0xce2c194b86251806451ec04be095d60517130cff61fcb49c4ef4e708dac7f34", + ) +)] +#[case( + [0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], + ( + "0x1000000000000000000000000000000000000000000000000000000000000003", + "0x818b9939a932ca6f6f95b35223bfd401a66e8bfad7101b8d1cc6df85c4f5fdc", + ) +)] +#[case( + [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], + ( + "0x1", + "0x2", + ) +)] +fn decompress_hash_works( + #[case] hash: Hash, + #[values(false, true)] force_max_work: bool, + #[case] expected: (&str, &str), +) { + let x = Fq::from_str_prefixed(expected.0).unwrap(); + let y = Fq::from_str_prefixed(expected.1).unwrap(); + let expected: Option<_> = G1Affine::from_xy(x, y).into(); + assert_eq!(decompress_hash(hash, force_max_work), expected); +} diff --git a/zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/field_modulus.rs b/zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/field_modulus.rs new file mode 100644 index 000000000..4cc4a554e --- /dev/null +++ b/zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/field_modulus.rs @@ -0,0 +1,10 @@ +use super::*; + +fn field_modulus_works() { + let expected = U256::from_str_prefixed( + "0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47", + ) + .unwrap(); + assert_eq!(field_modulus(), expected); +} + diff --git a/zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/get_collection_id.rs b/zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/get_collection_id.rs new file mode 100644 index 000000000..7f4a2f03c --- /dev/null +++ b/zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/get_collection_id.rs @@ -0,0 +1,73 @@ +use super::*; +use rstest::rstest; + +// Gnosis test cases using mocked keccak256 results, found here: https://docs.gnosis.io/conditionaltokens/docs/devguide05 +#[rstest] +#[case( + [ + 0x52, 0xFF, 0x54, 0xF0, 0xF5, 0x61, 0x6E, 0x34, 0xA2, 0xD4, 0xF5, 0x6F, 0xB6, 0x8A, 0xB4, + 0xCC, 0x63, 0x6B, 0xF0, 0xD9, 0x21, 0x11, 0xDE, 0x74, 0xD1, 0xEC, 0x99, 0x04, 0x0A, 0x8D, + 0xA1, 0x18, + ], + None, + Some([ + 0x22, 0x9B, 0x06, 0x7E, 0x14, 0x2F, 0xCE, 0x0A, 0xEA, 0x84, 0xAF, 0xB9, 0x35, 0x09, 0x5C, + 0x6E, 0xCB, 0xEA, 0x86, 0x47, 0xB8, 0xA0, 0x13, 0xE7, 0x95, 0xCC, 0x0C, 0xED, 0x32, 0x10, + 0xA3, 0xD5, + ]) +)] +#[case( + [ + 0xD7, 0x9C, 0x1D, 0x3F, 0x71, 0xF6, 0xC9, 0xD9, 0x98, 0x35, 0x3B, 0xA2, 0xA8, 0x48, 0xE5, + 0x96, 0xF0, 0xC6, 0xC1, 0xA9, 0xF6, 0xFA, 0x63, 0x3F, 0x2C, 0x9E, 0xC6, 0x5A, 0xAA, 0x09, + 0x7C, 0xDC, + ], + None, + Some([ + 0x56, 0x0A, 0xE3, 0x73, 0xED, 0x30, 0x49, 0x32, 0xB6, 0xF4, 0x24, 0xC8, 0xA2, 0x43, 0x84, + 0x20, 0x92, 0xC1, 0x17, 0x64, 0x55, 0x33, 0x39, 0x0A, 0x3C, 0x1C, 0x95, 0xFF, 0x48, 0x15, + 0x87, 0xC2, + ]) +)] +#[case( + [ + 0xD7, 0x9C, 0x1D, 0x3F, 0x71, 0xF6, 0xC9, 0xD9, 0x98, 0x35, 0x3B, 0xA2, 0xA8, 0x48, 0xE5, + 0x96, 0xF0, 0xC6, 0xC1, 0xA9, 0xF6, 0xFA, 0x63, 0x3F, 0x2C, 0x9E, 0xC6, 0x5A, 0xAA, 0x09, + 0x7C, 0xDC, + ], + Some([ + 0x22, 0x9B, 0x06, 0x7E, 0x14, 0x2F, 0xCE, 0x0A, 0xEA, 0x84, 0xAF, 0xB9, 0x35, 0x09, 0x5C, + 0x6E, 0xCB, 0xEA, 0x86, 0x47, 0xB8, 0xA0, 0x13, 0xE7, 0x95, 0xCC, 0x0C, 0xED, 0x32, 0x10, + 0xA3, 0xD5, + ]), + Some([ + 0x6F, 0x72, 0x2A, 0xA2, 0x50, 0x22, 0x1A, 0xF2, 0xEB, 0xA9, 0x86, 0x8F, 0xC9, 0xD7, 0xD4, + 0x39, 0x94, 0x79, 0x41, 0x77, 0xDD, 0x6F, 0xA7, 0x76, 0x6E, 0x3E, 0x72, 0xBA, 0x3C, 0x11, + 0x19, 0x09, + ]) +)] +#[case( + [ + 0x52, 0xFF, 0x54, 0xF0, 0xF5, 0x61, 0x6E, 0x34, 0xA2, 0xD4, 0xF5, 0x6F, 0xB6, 0x8A, 0xB4, + 0xCC, 0x63, 0x6B, 0xF0, 0xD9, 0x21, 0x11, 0xDE, 0x74, 0xD1, 0xEC, 0x99, 0x04, 0x0A, 0x8D, + 0xA1, 0x18, + ], + Some([ + 0x56, 0x0A, 0xE3, 0x73, 0xED, 0x30, 0x49, 0x32, 0xB6, 0xF4, 0x24, 0xC8, 0xA2, 0x43, 0x84, + 0x20, 0x92, 0xC1, 0x17, 0x64, 0x55, 0x33, 0x39, 0x0A, 0x3C, 0x1C, 0x95, 0xFF, 0x48, 0x15, + 0x87, 0xC2, + ]), + Some([ + 0x6F, 0x72, 0x2A, 0xA2, 0x50, 0x22, 0x1A, 0xF2, 0xEB, 0xA9, 0x86, 0x8F, 0xC9, 0xD7, 0xD4, + 0x39, 0x94, 0x79, 0x41, 0x77, 0xDD, 0x6F, 0xA7, 0x76, 0x6E, 0x3E, 0x72, 0xBA, 0x3C, 0x11, + 0x19, 0x09, + ]) +)] +fn get_collection_id_works( + #[case] hash: Hash, + #[case] parent_collection_id: Option, + #[values(false, true)] force_max_work: bool, + #[case] expected: Option, +) { + assert_eq!(get_collection_id(hash, parent_collection_id, force_max_work), expected); +} diff --git a/zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/matching_y_coordinate.rs b/zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/matching_y_coordinate.rs new file mode 100644 index 000000000..e36652770 --- /dev/null +++ b/zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/matching_y_coordinate.rs @@ -0,0 +1,265 @@ +use super::*; +use test_case::test_case; + +// Empty string in the `expected` argument signals `None`. +#[test_case("0", "")] +#[test_case("1", "2")] +// Python-generated: +#[test_case( + "0x20e949416f9b53d227472744dcc6e807311aa8cf1f3de6e23d9f146759d5afe2", + "0x14142aae4ac3081fc594fde12028d9b329e610472146bdfe7ec3ed4492894b90" +)] +#[test_case( + "0x26df0c83801d57dab45b0e36bbf322a7fecf072e2542b77de0b8eb450165bd1b", + "0x2596d10178999c2ed646acafa8a43cdd4926f9fb8a2ab3abfb75fcc010291440" +)] +#[test_case("0x21d8695d6abc0fcb4f070a45ebab7ce86ca8f82d948222b5ba0d572819c967f6", "")] +#[test_case("0x11c65bcf21136b93e15a23ce980383e30670e0d7106aff3c38b0558237421ce3", "")] +#[test_case("0x21c35530c4705937da4166a4f4e0c29b3a5ae1803610cd658638b33637261728", "")] +#[test_case( + "0x9a16530e10c85acbab040e9486fe1741e0a7c0d2249421a731d5c6403b4bcf8", + "0xd7117c72ace2d2cec54fa56ecbd5aa8760896d31308f57b9e56d79d7f7f1b34" +)] +#[test_case( + "0x42b9cfd6e74a002ee2ac03e230b74228359d49c0917784e0d2471867b593354", + "0x1e3a6b435798e6c845b77845991d6645771d7f6387fdca2f70b733749b432659" +)] +#[test_case("0x20ca4d81498b973879ae13e6e84532a80f770d11b2358d195ea2829ba3767afa", "")] +#[test_case("0x56eca00b1f4332c4ccc29256c0cde908b12f27f52767f223e47d276df7edaa2", "")] +#[test_case( + "0x2346bbe95aedd97d4e656c34e26428338e35c7557c401146378e26e325d9ccb1", + "0xe6529e3bbfe35be3a2f50fdfa139dd7b90d56817b091534e7b1328542a84c9b" +)] +#[test_case( + "0x2c18c2f96d049417a46388f4b33e9c027cda40949fc5ace55c0434ab14043389", + "0x1bdf570be306f3affe252fa1506ce317e034c073953a5459445ac5a9b9cde10e" +)] +#[test_case( + "0x1cb864f4de5a8449ffe9ffc9d24aae3893226c7fff60a9291729db5cbb6deae3", + "0x22b4d4c27ff59f4a87cd116fa68a2d4d708c06923d954e5b33a4a777fed7fc65" +)] +#[test_case("0x13920c77970b63884e6e9d7129740e8203d5bd427b784a020f204aebe10b9c8b", "")] +#[test_case("0x9098168314bd49d87562f2f67fe0bfc0d85f1dff08b42280804c25062bcfb56", "")] +#[test_case("0xd46cf808af0ec6a8986bae5904e937a3c40e542785b57b7705f4c2063ba636f", "")] +#[test_case( + "0x304f7642f50320bfd0fecd785aab9e2cb9aa4d78eea9d3db8c5e533ab1753f2c", + "0xd3a5d7fbc69cb057f7a36ed30c6b8bc1dee54bbea45a50a8459663f09aefc52" +)] +#[test_case("0xf37a8f1c56394ff52f1255a26d53f3fe24c7fb0765a3af1e58fc5d3fed84367", "")] +#[test_case("0x22edcd91537cba89f0a550b266ab6368fba606e7ded18042d0b33a11fa6d3362", "")] +#[test_case("0x2e1cf89cd4ee4b35ec15d0f9d475c52105f53ff6315629f7df36a9f02ed35646", "")] +#[test_case("0x1209ec936ef1e7e27bc51787f8be69646a87bffd72a7b1e52e7fcaf9932f74bf", "")] +#[test_case("0x162d48238d431d770a86ad6a013201cb0436d77ef7ddd2adcd39d6faa92b26d8", "")] +#[test_case("0xba0fa54d12fb286c4e0b9718c3e1410ea825dd4c2b5929001e064ee7f6361b6", "")] +#[test_case("0x1ebb509c2ecad8e01a0d86f0035ec68629aed3d5878300e044982292126783df", "")] +#[test_case("0x139c6113eee4057f6304b4e6472300b73c8c8a6d570b913d4d23528cfa661428", "")] +#[test_case( + "0xb177c6f82daed853261bc68080e17a4db51bc68d2c7e052dd483ddecca3d539", + "0x131902ccbeb6b5d5fcfd2902ff324b5ca03d26284f09932bbfb11c0cf25cbe04" +)] +#[test_case( + "0x11c2e51e2564719627780fa81f817ecdce8d34d3c4bce40a4164aa68a331753c", + "0x8ff562cbcbb36c6efc63b7d681820039bc4e8dff8ba94f26078a60a89312789" +)] +#[test_case( + "0x101d78729b63303abee4f033c2bc62de1a6aa85abd38c8b1ce5f61ff312e0b7", + "0xd2c903227d0b4da36a01788f84ca0abd481f75cd255907e99cfc90290af84c" +)] +#[test_case("0xb4aaee9e08f72a75fd4163a04ef9c2cef8467f388906a9dba01cc0a2707b31d", "")] +#[test_case("0xbb0249aa04e94c409b4edafe3b0b5473b85fc3a73db9e7105dc18ff567b665b", "")] +#[test_case("0x303dfe6b7f04759adb9c16d9a5f5e5e97ea4e059d7e4a3bc3d2bd466359c9d4b", "")] +#[test_case("0x1cf614533dfc15bcc167f4ffc7853ae15ce0364b51eb9b44df0ce3a081b55fc6", "")] +#[test_case( + "0x10954baebe08defd59e4a9c21c2506a16b41c7529d23674471c9a05371070abf", + "0x1732f7503ba205a5c39430040b2fd3f7f590eee539e01b1d645ca4de7f7ea5af" +)] +#[test_case("0x10f58303b4a4e4484ba1ce4c4a9e7e2df534ed15af0cfb0f283f9175af9fb6e5", "")] +#[test_case("0x7025c6378827384463d968e6bacfec443ec08a194104fd28bf6b247930c9bdd", "")] +#[test_case("0x1057d4959c007fa74d0e30dd6d15fbdaadd9c90279ca639c96d4f10d3af8a231", "")] +#[test_case( + "0xc2b2dcb2d9fe4efacc93808d9eb008d73712dacc9d4e5c2bc7a4a9fd1ff1ac0", + "0xa3ea41649a906bcb181557dc75e7409961e372b32b22901257763b83f96e409" +)] +#[test_case( + "0x28b8345b48442d41b77865f9fadb2e6e738f2e7d7d751d6b28d9e993fe361fd6", + "0x203e0b4abb8bda20274a279082157b465e7ac24a307c31d5470d6423a3d66d59" +)] +#[test_case( + "0x1f21a85e740f062dc35e7b38b7971c9224f17148f61035964d0741e8fa7ef795", + "0xcc4ba641136ca82b8318f2a223be701a30de95619badb5866c37d6349e8f3ab" +)] +#[test_case("0x851d8e29b71af39041cca092d1186d6aaf0e29571707b706f7bea1f6536dea5", "")] +#[test_case("0x22583cc7c94461fe8c221c39d3e3f9c0cf845c46b941f0685eb9d92b948d57b4", "")] +#[test_case( + "0x3677deec6a292db9413192996bef7e54cf765765e76f0442b48d03d78c1b4ca", + "0x14a8e165c8d161b9da152a082e891aac336deb6ef0a107e1372bcfc14a25bf1e" +)] +#[test_case( + "0x2b35222e0028f08e2cbe1b5b57880a5269d10f98d92e4c7ff173cbca5acc3c25", + "0x1254ccfdd7ed6582a08f6f14e202b8426b8401154f201b49c6554439f475d053" +)] +#[test_case( + "0x1a0559b8639f6ca09fbe7de8e573c75b52fcf271f59bb8d42de27e5b1dac5a51", + "0x2b5fe06267f5728b6b07066a6371f717b7a881e06ac81f94e671e13c116f5ba2" +)] +#[test_case("0xbb47c6119cf19d7adde393c20995152d0a4602634d2438998be00e7f8948b06", "")] +#[test_case( + "0x2bdd9b5112703affe898f3dc4467c54029a5e765a2932eae1911cfe9cd8a01fd", + "0x101458bcc903d98b9da338deebb97fa332a3b18eebeeeffca42fc360e8de37a7" +)] +#[test_case( + "0x2fed5842e08f871e084de333470ad5ff7f863cad37927a8e8efd0428a9a532c0", + "0x9ebd892a4e342dd6f4ebb4dd3a1f170192472e9a1f6f8de949f358fc36390ac" +)] +#[test_case( + "0x28af3fa7a519c2b8fc659dae65d3b2b81218da021dad2a1234b3a26b058ef32b", + "0x28a7177a3426b76cca4c9330911dd0c6d03fdc9458bc6f33b0b705aa642bc7a7" +)] +#[test_case("0x26e60dc63ec623b6b26972e6027d684925e7f482cd181eb73aabbf040f3b7ff6", "")] +#[test_case("0x20d6c202cbe710e1086135b2aeae79c613f87fab0ad907224f37e65512401e51", "")] +#[test_case("0x1b8a83401cce664812a7fd33096367451bc4d7a63178c84bc0fcfe7f5e7a866f", "")] +#[test_case( + "0x2420da207121c4043e1d956963a5bf85162ea695b29bfdd3fc6c62c5edf74ec0", + "0x15643f4438f6a5ebd13ee69527a3c2d74e2f6e17f37a107d74787e11c28e04b" +)] +#[test_case("0x7fc07f648a39c0ec90b3d67229e4476cf67f7b9e53e765bd4d42d0cfbb7457e", "")] +#[test_case( + "0x42c25def65eb895a1b0180670625743ac57607f1c405ad9d574474382f35d62", + "0x1d9d4fccdc146e3a621bfb55dbf8abcc07f98b2033e63a92416fdf705c12f7b7" +)] +#[test_case( + "0x101d3fbe5e5b208434fe6cd8b059fb313207f50871135ee6d218b0e2474828fb", + "0x113cf809c4e19b12bccb04341f807d6bd7ac373ef10f42d19788f6cef2a6685a" +)] +#[test_case("0x7947c8aa8cbdabafc8a2850e235cea4975b5a5579998ac2296c9d1e89bbb9de", "")] +#[test_case("0x995594be605fbf8da0939f1141cc5a34200e5e3081b0f0b240684c95e36b4ce", "")] +#[test_case( + "0x285c71f8a0fdf24ffd992a7d052270d1a091d5af3e7adc468b3f5eb2e888f2d4", + "0x276b94e9be42dac2f1311a5983897f69eaca89823559f3017dddff990f67a793" +)] +#[test_case( + "0x21dffeca30717d80192b3aa7a4e783a4eca4505f82d035d784dac171e277a3bf", + "0x2cb0757b9380194e04cd5963fff757de50d03831e4c283c46f7b0aa3d3e65641" +)] +#[test_case( + "0x2b8e383d30dba6b63255e01cea467a612b61fde33259dd090d222d28c0c1d77d", + "0x280980898b96035f9bf42af16aff52612b5e6016109e1aeb1ac525ca2477fce2" +)] +#[test_case( + "0x150625fe25855faaacd55f741003c4a372eaf7abbb3c01552bfd24339a365ea4", + "0x1826aead148f59af79633af3b978acbae66a3f1d4a49c1f1644a7507036d9a77" +)] +#[test_case("0xe98804dee68836ff4b4e42958efe51f4f1f669a9b8a1563f77f4e5805c42f51", "")] +#[test_case("0x84ed2d57e8a1c8ca2bd33b931ac29379667203438af460553519fec8175541a", "")] +#[test_case( + "0x191ec6a5b4fa90143c450666337adae84179dd007da85721b9c9dec27d89e46f", + "0x21b6ca804747058e4494e37f8013d244a343bb7b87f7f98f5577ab72c14b61c3" +)] +#[test_case("0x1ebf595517ff0e4c47793858604b0246b9c1a7677f372e1b7e46fa68dcf02a4d", "")] +#[test_case( + "0x17354a528bd9b24d83e741600566c8ff636dcfd04a8cb14b23eecc6c94857102", + "0x189ca63421d9c8c324d36169775e37d5ead71eb04fce35a1e5cea3bf581ae621" +)] +#[test_case("0x2a0bb756e81294701bc9d286914aeb9dcb5c803f73b04f91245e46be5120ae92", "")] +#[test_case("0x24dc380cd16479bb29e7651c506f23563f8a913b153d7c3a893981e8687bfb43", "")] +#[test_case( + "0x216db64e966475e96833d188725301fa0c04bb6b1d522fa93d161301b719c0bf", + "0x61b0bf3ab67041a1b606340242661a6e33f04f471ec172cf4b573d4129242f5" +)] +#[test_case( + "0x9f3a0b8ae4c51bd58da91095af52e6bef308ae8549b3a31d0358cdac7964976", + "0x1425a6c7ff8a1c54c63724d804a38b5084ab88fc6f01e162b8c78e8f32c5ead2" +)] +#[test_case( + "0x496a014cbfea4384fec7451a84b976ab0c28f76dd3ab3dc210be9b3bc4a7a94", + "0x1922462c88634ed2177c19df71fb80b6e5a5fca6ada05647ee00829a0a9cc482" +)] +#[test_case( + "0x2c751346419501eb3163f0e980a692ac5ff7ae880c23631737aba070d96a5068", + "0x10ec84d1aec1064a6168858d16d61742edb9b343c62af7749c853a13e319cb56" +)] +#[test_case( + "0x5aa5e9173ba1b12d79893accb863af98034dc10bee04a72745627805a96f432", + "0xa44fbe2c2f13907d2d26568156442f252f1499c67d5e89224ec9516efbeec9a" +)] +#[test_case("0xf11b967d4fd453f72ea17c192057163a4b86faeba58ae230cb93bdef0243a2d", "")] +#[test_case("0x17650d85a167378d8ecaed35d6c33f90011b5c1763b3a67edf99374e048e539", "")] +#[test_case("0x4ce9c9935a2727683a3b19bc28c2b1a9e3f629899539e3a3bdf1e438e06af27", "")] +#[test_case( + "0x29f83eef57bb73a548854860b466f8960265aef86ce96b03d3a694c8d1bd432a", + "0x2b6bf3ca49338fba98c6d0568093e7b350dfcf8eec2296d4dd514834a1f325d6" +)] +#[test_case("0x10f39b3b6e56245bcea1c0eaac5dde6f3dd4c245dc723dda85f28f7e8f059af1", "")] +#[test_case("0x802c3e67cb4052e844722a8cdffbc32929468ee86a395e7a381706b47be3ac9", "")] +#[test_case( + "0x1867b07712a6519c3c32c8f6571812ba523d3bce9b20eced5b926ce452b359b6", + "0x2c06e3a418433f6e8a72841772eb8968fba3eff8b6346b4d2d31730f6bda6254" +)] +#[test_case("0x127305af097d76b7ba80afb3b4d648ea1ef18137a6ed87193f79433a135dfcf1", "")] +#[test_case( + "0xf21ccbb974fd751baf632cc4d283d41ef3bf3692831d63c5f127c75b8568373", + "0x13c3f7f4f167ac914f3f1ef9cfe0b0825939eda1612fbdc1e72c30fd16c2cbb0" +)] +#[test_case( + "0x29c06ca72b9dd0e167319520dc58b330b0589fec9940e46b36e7e6d89f2d5f51", + "0x2b863a476d7134bae804560ce9deb21507009fcb38ed7e891da5330e6cebf843" +)] +#[test_case( + "0x24593c874871b77055cd668994aed955b3fd217707fc1e1aa548c46cce8b097", + "0x1334a18f5cee20d4424129d25910fcf6f6df0a110ed190fca6790ca75f0f2a34" +)] +#[test_case("0x1d5f05febf7f601d031491c42106903a5092f00c5768b95d0fea08a5fda111d0", "")] +#[test_case("0x28670112f911193a4fc626adce6ce40c8a4edf239379f69955b38d7e591a12ae", "")] +#[test_case( + "0x24e8ecef3e8905546882f8f34aaaa6936470006ed6d344ea9d9026ab3df5c224", + "0x1f19c628f201c1132c459133f21a2084e11d5f102e0fcf0662093193bffcea86" +)] +#[test_case( + "0x5d1d993e9dee8cd8065df381781ae2a452b54443dc4bd7b25aedf7e3c93b903", + "0x1af0c09e4e5fa57be5d4a00a3f3a1a4ca7a4cc98f02e1d75d7353a4822071b58" +)] +#[test_case("0x195235e8cf1639843590e2c504b32d98a1e6f6c238088a9602f2e01080303c90", "")] +#[test_case("0x19ed8c7dbc98179279c78eb4934e284f65bd8ad2840225e9c7cabe116620430b", "")] +#[test_case( + "0x17f87b57ea3ea75b10a58a5f4f14e77b7a3dc37980f750a1acbdc9f3eb7161a4", + "0x18d3efc9587da0fad2549b9435b3a8dea192a296f00951fa204cae6177e70b99" +)] +#[test_case("0x235bee2c7fb649ce30cd804d98133ab5dfcc793528f7a776f1768a28ce24371", "")] +#[test_case("0x2ad3dfe16d847f078d0ee7a573a41ff13fcd57a30f0c15ab22da5698c572facd", "")] +#[test_case("0x2efa31393facd1c3916012ef31a5d854e6c1d4a9ad2960fc962c804b90d98e59", "")] +#[test_case("0xede9f77d62bb62fbe81b267435fcf72fd0251b2814d10df320f6ea215eed56", "")] +#[test_case("0x1db1fd22a8fc9dbc46a5d4cefe21ec711cfadae76414c4f6ab3cb6cd95738764", "")] +#[test_case( + "0xcf6c703c780e6f72140faa60768188edcaaf7bfc75b68757083888e477a3f28", + "0x2bdea6633e0f2780a80366d9bc91d10a75904c9cd4555d462ef6166747fb629e" +)] +#[test_case( + "0x23f6e9a2d6f32a5255f33846b7611cc5043a7d965a17996fa4c1f3ad0ed56ebc", + "0x2f869f25efedaa551614153f9c706b3da92eeaab22137386e591865d25482306" +)] +#[test_case( + "0x29949070e2f5f3055c3afdeb38955a6d407b30b260b256e151df6208d90c12eb", + "0x1ec27fcc641f0d36c77d4ee49c905a562a5c25fb72f7cd1174b09b3537c0542e" +)] +#[test_case("0x1b8e001f30a427f88bebdb44d5ad768e417f79b6a087f135b26a1c872437fc99", "")] +#[test_case( + "0x16828439e722906f70b29c4837cdff27680ba81e01c7b1f210e558318090eb4d", + "0x2f3a7f5710ff2bc13fd4b2cd3a84a61f6c7bc7ea3a2e125ae89fcfabfd9e737d" +)] +fn matching_y_coordinate_works(x: &str, expected: &str) { + let x = Fq::from_str_prefixed(x).unwrap(); + let expected = + if expected.is_empty() { None } else { Some(Fq::from_str_prefixed(expected).unwrap()) }; + + let result = matching_y_coordinate(x); + assert_eq!(result, expected); + + // Ensure that the result is actually a point on `alt_bn128`. + if let Some(y) = result { + let xx = x * x; + let xxx = x * xx; + let xxx_plus_3 = xxx + Fq::from(3); + let yy = y * y; + assert_eq!(yy, xxx_plus_3); + } +} diff --git a/zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/mod.rs b/zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/mod.rs new file mode 100644 index 000000000..18ff08ec0 --- /dev/null +++ b/zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/mod.rs @@ -0,0 +1,34 @@ +#![cfg(test)] + +use super::*; + +mod decompress_collection_id; +mod decompress_hash; +mod field_modulus; +mod get_collection_id; +mod matching_y_coordinate; +mod pow_magic_number; + +#[derive(Debug)] +enum FromStrPrefixedError { + /// Failed to convert bytes to scalar. + FromBytesError, + + /// Failed to convert prefixed string to U256. + ParseIntError(core::num::ParseIntError), +} + +trait FromStrPrefixed +where + Self: Sized, +{ + fn from_str_prefixed(x: &str) -> Result; +} + +impl FromStrPrefixed for Fq { + fn from_str_prefixed(x: &str) -> Result { + let x_u256 = + U256::from_str_prefixed(x).map_err(|e| FromStrPrefixedError::ParseIntError(e))?; + Fq::from_u256(x_u256).ok_or(FromStrPrefixedError::FromBytesError) + } +} diff --git a/zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/pow_magic_number.rs b/zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/pow_magic_number.rs new file mode 100644 index 000000000..14707a3ba --- /dev/null +++ b/zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/pow_magic_number.rs @@ -0,0 +1,1081 @@ +use super::*; +use test_case::test_case; + +#[test_case("0x0", "0x0")] +#[test_case("0x1", "0x1")] +#[test_case( + "0x0000000000000000000000000000000000000000000000000000000000000002", + "0x279d7bc4e184e3a57f5fa684690c6df6b484a7f1daa1de608d266a2a4be6593f" +)] +#[test_case( + "0x0000000000000000000000000000000000000000000000000000000000000003", + "0x0000000000000000b3c4d79d41a91759a9e4c7e359b6b89eaec68e62effffffd" +)] +#[test_case( + "0x0000000000000000000000000000000000000000000000000000000000000004", + "0x0000000000000000000000000000000000000000000000000000000000000002" +)] +#[test_case( + "0x0000000000000000000000000000000000000000000000000000000000000005", + "0x2bbffb7b85b84d517b91517bcc7429cfc7fb18a7d88cfd7641df308c6d1a3517" +)] +#[test_case( + "0x0000000000000000000000000000000000000000000000000000000000000006", + "0x357d8998da08d51735597f5035c7a6c3cc58cca2a1e008b29c700e675c609ab" +)] +#[test_case( + "0x0000000000000000000000000000000000000000000000000000000000000007", + "0x1dada9100531c64cbe18cee1c3fabfe5082f0ce8505483dc5b9d6fd2be57cae1" +)] +#[test_case( + "0x0000000000000000000000000000000000000000000000000000000000000008", + "0x1ed6a916e1d82721466f07525097838fd187e5524cd1f233de2c483dbf4fb537" +)] +#[test_case( + "0x0000000000000000000000000000000000000000000000000000000000000009", + "0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd44" +)] +#[test_case( + "0x00000000000000000000000000000000ffffffffffffffffffffffffffffffff", + "0x1c078130f1d71a2b85a61f5a04d5b4ff2ee5f874e9bad4d7e5bb79b1be5e892e" +)] +#[test_case( + "0x0000000000000000000000000000000100000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000010000000000000000" +)] +#[test_case( + "0x00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "0x05f01ae4b9556bff71e988a7268d8faeddb82ac1f0f472e74f5e05f3c524cbb2" +)] +#[test_case( + "0x0000000000000000000000000000000000000000000000000002bacf38d0ee9d", + "0x2bb83c2e6a71464d3072fb62139c9e13a419cf13d5b31b17f3438cdcc2ab5a79" +)] +#[test_case( + "0x0000000000000000000000000000000000000000000000269fa8c16b8066ea69", + "0x0c250f940031ee8f6e14788aaec7bb1bb3cf23fe6ea76db7ee8dea724681c57c" +)] +#[test_case( + "0x000000000000000000000000000000000000000801241bb1f6295e704fba336b", + "0x0228ae1957bfe58548d834b28463d1d98fe69e2de54b873fcf78cd3e9d0fa195" +)] +#[test_case( + "0x00000000000000000000000000000000000000e132f566fa6d16bf5486f5bf6a", + "0x2c0dbf7f0f4afe4421700aa8ed8788757b0b12b1197931b7be00281772c0dc27" +)] +#[test_case( + "0x123456789abcde00000000000000000fffffffffffffffffffffffffffffffff", + "0x21b8fe191fb7d5a2ebf018c8c52f4317a41dddcb1a1ffc4ec9141bbbc97bcc62" +)] +#[test_case( + "0x00fedcbafedcbafedcbafedcbaffffffffffffffffffffffffffffffffffffff", + "0x00b2577cf5861468ac05eb9334b380f22bb78c575cb69061fc10f2358e539a31" +)] +// Generated using Gnosis implementation: +#[test_case( + "0x2354c122221ffc8680c57566ebd1f3970afe06d53facd4117a43aa1def82b557", + "0x1de6fa86b13267005611590911a66f775c303ae545a85e6f66356063836709bb" +)] +#[test_case( + "0x24fedbc02622ac674c98380f0d80ce10c43abe50b2264d16dcd12a0af6c2609d", + "0x1ab93efca782cd854bb5372470201592c1cd2b5f25a471abb2c7175c07792baa" +)] +#[test_case( + "0x75a840f2ef2c84472c42df4f53e095d26e5b4be57078cb000f3eb90667b05a9", + "0x29665bde39f038131074909fce09c0b794b74b1ff9ab189e8c5a4592f23be3d6" +)] +#[test_case( + "0xfacd5657c56f55eed66b72b74980fcc198a2cb23043e8a725001aa0995e5188", + "0x1d5e6d113cecf9a9c0a1896289f5c3483e1f0b5b547cbe7486b2c6b843274da5" +)] +#[test_case( + "0x5fb0c7c63f31999e68e2a2bd01bc7a95d05d558acedf49ffc054d25f78c0cf8", + "0x2bcc1855af3ab7e82dc70e97284736aecba06de8613e9e328a79ae12934ead01" +)] +#[test_case( + "0x1788074e4f196b2f08fd0d2259cc15e85c378a7468002991d018e8962d23af56", + "0x2c531e481bf64ed74c551f2a421c600d49894af8d408f136cc90418d2cc21277" +)] +#[test_case( + "0x9193bffd27dc61c61c79180e65dc7774bfb8282333fe039e864042f175e8afe", + "0x12b2834cf20f27203b810f287db287caa03c9259538a8ba65874866122373e06" +)] +#[test_case( + "0x112bd6c30c4438ae381f0a2a9fad2492b87270c63ff9fb8920937611d3b55cb4", + "0x2b0f81356115396a5a677d12ad18f13b62c626221963c4a1d8b1a93dafe478dc" +)] +#[test_case( + "0x13a26ba3b2d697fc55bbecd3510200468bc0ba0e7637ce13fbf54167121f5f13", + "0x1b409f6679274b016de55448f8ba69b77636cf75581e3cbce2841e7318e178e1" +)] +#[test_case( + "0x2bce28974dcfe586fb6eea5dde6d9efb91ee7ac94d3bab482d0e73136976b7a3", + "0x25539ea969433a9a46f52805de3243572d53a19a6d082e6908c393aa0429ac37" +)] +#[test_case( + "0x21b1257812346ebb0dfcf0a3c212e4619b789713557d76ee5310240be590cf27", + "0x18192eea11af08727c73131ea95b6885e118e687c8aeceb2f8d2b803227c5aee" +)] +#[test_case( + "0x1b0ce6c54d89b9e577c5ac893d12d3cdaf71609a4bae66d06bd95353d7722306", + "0x1b056f18f53791a773f2eec525fc09a3ad3b87963c9b2738809140f8c627f1d4" +)] +#[test_case( + "0x2e2f05a7a5952517e92834acc01cccea2d45e932d9e62433e841bc5574d9cb52", + "0x27c6cbc51000cc53e42e1200873ff5f3079eef6108afb351deb1207844e6a0f8" +)] +#[test_case( + "0x2b483511c5d2445dc876806f56d96c99ac7858013059c7ccea0821bc587b4945", + "0x13bfd6ec70991017d8d7e2ac37a08d505f0b51314509904817c2d7e7b2615938" +)] +#[test_case( + "0xb09f7171c618f1f3debe2cceccb452c5f1785173fa021b1eca6b9598519636a", + "0x9bbefdddae55c42cd4740a8103d8388dafa93cfcc68c1b1a8afdd4d22e5f411" +)] +#[test_case( + "0x684aba84d1f6436011195074e326ca68feae4ef4f4b505211ca13425d3c1b7", + "0x180ee0f41d5cb3e37a1e0d123cb605120c23a98da00739245dd29da73b7bf715" +)] +#[test_case( + "0x2a0dcbb881a771aa81d622ccd5c4cc3e6e2b18b5611e460f48add75df7ffbda2", + "0x6c9d6721042e1ccc0b3840b0c9a16a6373973aeab6ac474c0ef31896291b466" +)] +#[test_case( + "0x22a235b1ffd6a6f26fbbdab352088ad04ac3a90a06a309429b90f619fe47ad63", + "0x2cda39f4076bf3bbe07be17e4d41a2a430d0528ec17e1cb9b5d39930ea477261" +)] +#[test_case( + "0x141dbc7b4620096b85b492fac2df889353e1f0ae021b26b9d3b7c19171e3983c", + "0xe9e2f62e2ee75ee90d3ba561dc6f931b0a836f6a7ecfba9015cc41f91185a6f" +)] +#[test_case( + "0x17e0b43fcba8929d9c4f275c449c2c92195e1f6a364a51a6b5ce80fb3539d6d7", + "0x14f6886d669d73d6cc4ac31b787f55889e64eca1acfa89e3e4cde3c13f620c7f" +)] +#[test_case( + "0x4753eee5c232a1b78698e289da515d5540150f9fc9d5fda0dcc5b5113cffa69", + "0x1e1445559bffe23b8dbec67292850ebfa9135ac3027ea5de29929456083520a3" +)] +#[test_case( + "0x195b5985dc2ae928e1a0d098bb04e0d0750bd17ba249a6fad642b29ef0299ed9", + "0x1afc14672b9df4c141a9b20643434c197fe610bcd95855c8d7b9f193615ce807" +)] +#[test_case( + "0x15510b2b2d41c4d41b9bf7de8ce7453ba6d101ab5b298c83f6b8d21a3ab5fe5d", + "0x30216b11e53f919936fa4d5fca5bec88891f8c59407e7e043f156e7cfaf98ad" +)] +#[test_case( + "0x12358a54316e0412177026f53af2025e7ae28a10f574a1fb2834e1e449f79447", + "0x19f6b4c86f02e3fe5ac1c3f7356ca32e1f45c620a4b649501413018dffcf3cb1" +)] +#[test_case( + "0xcd2b6fbe475e734bbf0e7be0e7b64954b9dd33a571d5d03a4d9f6906afacef1", + "0x2cb10e299812d74d02c8c5f010fd787280f4b131f1233cba91bf9c25bf55a73f" +)] +#[test_case( + "0xd77c37d1367bbefc9a5955344a0e8c38ad03808f327b0ade3fdab5438199578", + "0x1c7f29a1a2d8fa2deeed36b97735e8483c3451da9dc7ffcb1004cad8b49f1861" +)] +#[test_case( + "0x23f0a43fba6dca923fa3081b857e4dd4c4055fc9a9a0c6a876049e2af998159f", + "0x4b4763b6047cf4930311921461ced618942c083cb81d198d5d3c6169d7e22fd" +)] +#[test_case( + "0x21b2b431ff068e1e30a05f9a2570a89469f334e4d5dc09bd41fe156c4f40df6c", + "0x1b61353cc37c1db2ff1cf5c0079e4648060ca8e5077bfb3123d2d7418624b046" +)] +#[test_case( + "0x6de94f99eb1da34ead03e09a0b72bb6e2881bd51dc96dc92b0891bc41b4b109", + "0xe294983fadc22f4e10612fc9d2d8c9b9b22ffef2986fc008764ba44c4d2ad18" +)] +#[test_case( + "0x2f17bf634f48c738caba4d42e4cc2ec3334ae98b8a2c82524f2dfd17a70620c8", + "0x299c9930426810ef78894f2ee8a47440463f3db583a745bc9a4e941b9207dd31" +)] +#[test_case( + "0x241b59c2e4d782f3afa89b28b336bfe2b0848d570d300b1c41fd0ef4d3054214", + "0x97eb9b64afe8299382fc790ed2d253ceb19463474bd01ca5b50095fcd33eab7" +)] +#[test_case( + "0x2ba18996e25bc6066756c7f9a388c1a746193b12fd56e39231395e5d96d06816", + "0xc7aee951b9b0a5c887aef547ffaec5a4e272ba627f44781c7173b1f89ef1ed7" +)] +#[test_case( + "0x2e2f2bcb14958b66d51d0ad8e30d83245f08972dbe72f8d759645113150a5d43", + "0x12136e3b37eade27d694e8bb0e31b6a563f074844375b273d70e823d990c4e63" +)] +#[test_case( + "0x1e51edbf2f604da89d2ba200d6a3872f73cc828dc8b642a61e0397990f086fc9", + "0x2bd93682d1122c1fcedbfe4403dd6f0b3488fd746b3231359f86e897c55a58d1" +)] +#[test_case( + "0x1608a8e8353015ca9afd7b4f4671db355ab64b56dbee919968d4d99c8da8dc59", + "0x112127e6f781c2abd9ff0e1b62712b16566d9b90506a5e5c68df494c552a599c" +)] +#[test_case( + "0x20fe1cb977b943d0541fdc5a5bf136f3db7011c7bbf36fe6e2f2c68d1dc50e67", + "0x23103fcadbcde4feeab37808c6c9c548e4a4320d25d7c3e14129e2d03b9a8fc9" +)] +#[test_case( + "0x1055689b55c0874a419dd4118b6adada3c93ab1609c7aa902e437840e798743e", + "0x1e52df64c4d282d9c7bbd7e6822bb3beb62776d58b317ad30137eec0a3febbb9" +)] +#[test_case( + "0x233e59a9b7b15e97edd03cb365fbd860df1bf9c2204f79b75298c43022605a37", + "0x14c976744a666aef10ef39e1dd873910789fe651f42a8398104a513dd37e3269" +)] +#[test_case( + "0x1cfa85789a2c8e93bd34d5f387e0ecaf3108c8a121cc986b361155ce2ac11cb0", + "0x277374b1c47704a063aa891969b41ac40241aac52ee9c4cf6b41770a7e2c77c9" +)] +#[test_case( + "0x196851b066fdbb555b5b81175f712805dede8449be697726f325a6fad8d5fddd", + "0x2b131a525f9630b24c2f9884b98ba541fbfecaec2499c9c57987b543543f0c21" +)] +#[test_case( + "0x2b07f1d0b3be425b6e54085c2a36170cd4692a68d30f071f32cc7a73234f0545", + "0x255085e9274e407b70d0c7fa163f2ab38b6a4cd55ec56ceb7286ea63c8991de3" +)] +#[test_case( + "0x2ab94649a62976a445688157861373a3774d144203362decc26feb65286541fa", + "0xc7cd7f76e45f7126649d448d70c7bd1db92d64dd431db6c333e89abdec4ae13" +)] +#[test_case( + "0x239136e344ae444cee3dac47937e8c275ef0d97d2659d17f4fbeb86c89ea4b75", + "0x158fd2a72745452819cf3b28ae52b3e17b8a4a24ed6d148fd04ae8ce424a0ea1" +)] +#[test_case( + "0x241e0cd8d249155b10c485b5dc0b07ac6c4b96af18ff8b88c005088d8f8f764c", + "0x23df5bcf5e7414159bf6397b93d5567ec2ae6d8245681e110527d531340f4322" +)] +#[test_case( + "0x19f25f19ce5e03e807cb86dc5166e9bb6ad3cde094402a2e03f18bb15793b421", + "0x1b1c24ee4dbdb2eca6cc36eb7aa8abd92ad4abd5ebb5d09ce543c492b435af04" +)] +#[test_case( + "0x139628f946e8e6ae364c76444ece6c5903c2a79d7b647a1e4563cad636f5b793", + "0x2b452dc3f48526d93f79364ed1fac59bb6908ddd149a51b40aff6aae2b55849" +)] +#[test_case( + "0x2b4405636d4ba139c8fb6f2ea9625282ed0731d7cb7de9a6019216251d96dd78", + "0x153224e84078d2805ac8acc14147a0670913c6bf9cfa5033bce10270f89feea8" +)] +#[test_case( + "0x209f53a57dbe9c5e7771737a6bea2f390df00ee9fdc60730b419139f9ffa3d04", + "0x15ed703c5e68539f26c73c28d98e14b87539f3ca5196b98cde2ab5fde6640639" +)] +#[test_case( + "0xd491ca9d59671992cbb519471940cc5d3bbeed8a2110a611649882589c4c774", + "0x1085ea5b92511a510a20c03a7974c08d822e9c42282dc5b024c12c0e8dd77d5f" +)] +#[test_case( + "0x15f0d0433d24a54402fb8cb84a23afb09501c829c59ce3a5660a57f175bdec34", + "0x1bf1b7bf37e25c8e1608bf5a51788a8883ada2d13d856507126b820ba7faf7a1" +)] +#[test_case( + "0x1616cc74f6732135f096bb3152c31f1fd255ebb2cec28aa5dce33a3089d6fbc8", + "0xc78bb843de8b7f3bfb5d2371c5b7a209392f9122406b2c4ca843062e5b9a840" +)] +#[test_case( + "0x185d82402cc3600275d4606f9aa0bc9b35845ade09f104c5c39382550b58eb24", + "0xc423b24d395eced3de2148bafe2f7898f3647ec17ff677ba0cef7aa6520a050" +)] +#[test_case( + "0x387ef556dccfbca8df28cf43e3b59bfe2b9e2935ee8b406f2cc075dd631eee6", + "0x152af2790bc93c155ac2598a57c8813bc28a05a29e5aee1247d7acdba73c862c" +)] +#[test_case( + "0x2bd06d520de42ec02fba49e235dca1778f512dfca5922de360a88673b21493b6", + "0x1ae0c86b7b3702566e356ac4d689bf8bb0e1afa9970961a0d6b2ef63eebb7574" +)] +#[test_case( + "0x176966499fd973f0d3d0366467e6e488730930fb43890aa8b177702f3a0b05f2", + "0x2cae575e00b58a74f3cb81e4223b175e09a7a66dfebae6d1be2a145d5eca4d21" +)] +#[test_case( + "0xe0a1b86632a7a010880754e792a1b655badadfded7971485e359c1157ee3565", + "0x11fe1c510d7a6144419b9c81b4d7039df681b97fcacb1cf4eaf4bd2e1e03198f" +)] +#[test_case( + "0x18a751dbd45c179b53f48bec1f3680ec4edc4c8fefd9b1934129724c25ef00f3", + "0x4cccf662fe8f4a260f178cfa8119870b93279ba323ee2f2dbf004376f3113a0" +)] +#[test_case( + "0x294cc553274f4c1147b277763e14ded5ccf337c6438fde9e69c2c767a51b10e7", + "0x27c095c3b1fca003c634c8933718911c0440d41565ded28ad6d38c6a8daa268" +)] +#[test_case( + "0x6ef20946ebada23d09c7167b95edd86ed8eee029927ce0f581dabd9b7645859", + "0x61b0c49ddfd7f064f4729131a4dbdfd4568283bc4fef8acde2721991c4e3f3f" +)] +#[test_case( + "0x9b205a3a97f2c638aa2d2ec9b0db6626a795d091e129a37c196fbce1ca7ad1f", + "0x9b7815c258d4b8035772e030b841ac131f32012c934488a5ea8d906152d4c50" +)] +#[test_case( + "0x502f9865bf63391ae24b8e9f3916c907a6b602d53be9c7ffbcd5d6fac35bfe3", + "0xd39cf062200d8b13abce798151a48e90b97267cf9d82f0a215160d2f2f960f0" +)] +#[test_case( + "0xccf4f0a11f6fe5ed3ffa674a2ccbd2caa733ed990b6dea5a6cf9aedcfe6215b", + "0x29d076ac9f5e6602c1ec3211b2beb0c31afb903b42e505ccba90ea255ec13a86" +)] +#[test_case( + "0x147f26d61f84fd5b9788fb08e6ab806c14405687a10539651f4901cf5043b69d", + "0x2e0f2541f0450db6f69006c3b44287941338800eaf087e4ce89ade713fbb8707" +)] +#[test_case( + "0x2483d51b769934debb3a943d85e547359727728ae0347cbf787e7af2448fb5aa", + "0x2541be86692f6efd2ebbf5a958959aaa0cb71f854405ca74d84b1fd8d4b439cf" +)] +#[test_case( + "0x1e33f31e367e4e7c4dc712985e0a54c4b7de742dd4e3cd316a09c71023f9d597", + "0x257b621ee15812e62bd9b8b0b85e3aa62b377e3871840312718fe1f3c90dcaf" +)] +#[test_case( + "0x1e359f812824489acb1cba316dbfcef3d2b7751710adf91179b086b9cea631fe", + "0x2245438b1cb85c8e0bd9612aacd2e80145333885819ac342ef5b938e75394bf5" +)] +#[test_case( + "0x13f408afe13c0224c8b147363cd8536d65cf475f7f6c437061e71418543e8cc3", + "0x68c85f58bd05d929e83fb9046fdfdbeaf87b507400df5fae0a569fb78c2c90d" +)] +#[test_case( + "0x1fa5471cba8562ba144f3d5be81aa7382c45c1de82c1ef93d1b4a8f557b2810d", + "0x2ddce2470c756ab1633f3a676e2a5145d5e262b075396c48be3c354243a10544" +)] +#[test_case( + "0x2bacd1057e935b7536d34b9f068dbd77266192f8854f92575fbf2f5a394d741c", + "0x1d4b3a1aebfcbd89f70acf083e1e267226038076614bb5bfa0641a8fb8a7ff50" +)] +#[test_case( + "0x53c791f801f297d5c03099659a5e4716a3dda9157d230c82a65eab1322c8992", + "0x15400ea5393513ac718c0a6c0fa615a4b862bc601aab1a79f17c25dd7628f072" +)] +#[test_case( + "0x13c9d53ffb115abb64a72f82f56127f7700eb52cdc7a5b83e9f5e5b848a56059", + "0x2fbe129483b47b0d60109830ba641b7c62841f898bf1b6f1ca2a8e64fc61c041" +)] +#[test_case( + "0x227e0f3e939bce0986024c953faebcaf144e5269d7bf9217351a429b5f688b1e", + "0x2939f37529d6282e8ffdb72874645618d13c2b61962570a4a59748a33eb52544" +)] +#[test_case( + "0x2d15888ecfa89595f82f096ceb01bd7196022f0e8c5d00b60f630300836fef37", + "0x2c2b04c58c6bf23752caebf5cf023c9ff2fb56e185ffeb005b48f9aba74b42f2" +)] +#[test_case( + "0x21441a377ae74dfbe448db5a3daf0f0a8d0f6897646b1c07869083fa4e6671c7", + "0x1ce46c42247a833720505d4138b198cafda1e624bd34a105445373451884c6f6" +)] +#[test_case( + "0x67965676a05d25adc2956f33859e87b1e244f7e3204125125f1ffb8efbd03c0", + "0x3f3fa9ba0f6a2bbb83ed096683279c6fc70182c6470bb1e5f5645ebeded5cfd" +)] +#[test_case( + "0x83f16198f036f472d6750ef862b5ac34ac4473bd056415e3568da9d730114e4", + "0x11e53e97c5ff41c6c6f981e5f59a7335799f83d7a71a5fb746ac0d4b579c7f5b" +)] +#[test_case( + "0x1cc9fdde2b75006a9abf8457a189c5f79afdab1c6ce63137575e1b74a0508995", + "0x3bc70435799feecdc5e7b5c2d1dd46ae076e626955b95802f69454e029429d7" +)] +#[test_case( + "0x2d246f374692631611eb9a02ee4907070d28887837845257e170a030c716bd13", + "0x193d617dfcd6392625da7bae5f313a5910a8b577e1abb9338c18614ef938931a" +)] +#[test_case( + "0x262b94eb71256dd7ec1e4b2b97ee562d4b977a7a7e5ebfe7cb9c41a3947efcb8", + "0x2f97463b4f05c3ab7f93cc98a2135530ad3a55beb5d6ee8e2846c44ebb182812" +)] +#[test_case( + "0x1491001ad8736a24a82bb64075c042023cb7ba9d2d709a84147dc4ef0bef7a20", + "0xb26973f748d8372ae5185a7a76ac9ea488dbb1366892aa1bd716260258540a9" +)] +#[test_case( + "0x2665f121fa2b054d5a5f4c0929cbffd4f8c008f88910d41a6e418166e7ed882f", + "0x25f3c1bae6c0def1650c8e8647cd768486100a513aab42b5ec90895aa660d1d" +)] +#[test_case( + "0x266f2a8e026378f15e398d030b4f556e00c0479b2b13a056ccdaa068111aa718", + "0x2339aeaf432f0a2e82848f69db7800014e68864ca9f13ad6ab1c26a634885304" +)] +#[test_case( + "0x14b78c618d1fd090d251b7abd7f7acfdecd668c48ef5e64d9c59d74ebedee960", + "0x18dc3dc2626c7bbeb733ef7a87149aa1143afd9cbc5ecbcda6ea4914beb9586d" +)] +#[test_case( + "0x233776dbd1b0ae61b72abb3cdf438c041dca957eb6dc25f7ce568f4f5acac14d", + "0x1cea4fd035651a82a4c3353d538b4310452949fbfbbf1057cdea4aa74661e4a9" +)] +#[test_case( + "0x277c5b62ead9931e1fa5f2cfeaaa77281451f89912df125d239ada32aa11f63e", + "0x16feb53a2ae546c9a663cff07e2b5ccd5c69a164df39b9620f10f4353279cd5c" +)] +#[test_case( + "0x16e9a9ec7ace195477fb7a08376a4f586811dd5ed1415f2926e4f3c78ebbe731", + "0x2669afac1014727ca473e0d88a0e1b1ea6ccb357f4f3949048ba7f22c81c14a4" +)] +#[test_case( + "0x2519f5da57a3f30043801ebe62115213cb6cdcef84d747ebb93628f2698d50fb", + "0x2e8ad65f5845ce324803a66e4f2110a60b0dfc014272a5097b61ca58ccf519ea" +)] +#[test_case( + "0x1aa1c72105370ecb28eebc32eca71a19104765eeb6f711cf5235eb87692de0d4", + "0x191acf349969702acdb8e963beb15653d7371ec34a02590f65d4c635487540" +)] +#[test_case( + "0x2a2bc990b5bf71485a4a8dee0683d123f5f71e77623ca07a30ce1e2c2676b167", + "0xf4f32804774aef11a3749dac93e5f5141531bb30b9d8b8d3912cb05b09dbbe9" +)] +#[test_case( + "0xe3c9c824a566e3432c02c284fb2f8363db4676cefae2e6355b24996908d2b8d", + "0x1b3fe8d1eab6c222c54d8ff4b958ba008de1eea7e319c3249fe55ca068228270" +)] +#[test_case( + "0x20c2d4a3019f45085e549daebad566d9ee4a4a429d1a928a244aebfa4233d5da", + "0x132582146ed242a554750ee95788acbc2c3172594f3e834b033b8e3c17540c05" +)] +#[test_case( + "0x2fdbf1efee35c9c7d94962251ccb9604b7ce53d89b144f0c5729ce618642ca94", + "0x10ba762fe7f91fa7e1476a22e56771e22c9320c5b21ed1591db78a630604e9f4" +)] +#[test_case( + "0x189352877c36efc33ae7c8f2ea974cd280a5cab22d5b14fc976d4115babcc09a", + "0x7af3cdfd3925232d2298f65706cb4e362d8ca5ea8b7f860b6bcd04d9373d938" +)] +#[test_case( + "0x206ba61317fb7ecc200706e5c5ef40d5422258e13728c0405ebe59137359ab3a", + "0x1338285e79deddc57e25bb8b69f0c71a993e81690fc25505a2a4957fbb1c62d5" +)] +#[test_case( + "0xbf70e0026bd8c03411aff5786b34da4079bddc3da0e957a32f27422ef3bed53", + "0x140d99cb25901d245ab6182665981db23f87140b578af38a4805ee43571ab347" +)] +#[test_case( + "0x15b3a542d0cc7cef48ed98178f2bb9540b663afaa3863e1258e3b9c3900bdbef", + "0x1a3e34051be5649640f1d6658c78f2ad219d5b82155b185da0d71663858e4f59" +)] +#[test_case( + "0x84b12f410c77939d561102b2b25decac4bd6b4c1caa0d93a212ffb7b26ca4d1", + "0x28d08e65568558d8d0edc3750ff4d8bebed2f022a286251ed87902744eb1455e" +)] +#[test_case( + "0x186384bd9f5ba026ac6fae1bf15ee4bd88268751941f9dfeefab8890080da767", + "0x101f9e607da2d440390faab2e58ecaa35497c61c410d44a6b3cd9292f912a7c5" +)] +#[test_case( + "0x362c451cb3b9d33292eb061e801c5a98d50613a67eb5b3b273742d81dad9273", + "0x1f115bd90d72204281efaac2afc5bfd4c5efe572267b13adc7e495640dc61c20" +)] +#[test_case( + "0x1349e6cc590e4954fc551e76d93ec5d6f342b5d7a9195db47ee63069dbf74b2d", + "0xdb453e6389b7e43027d0bf4c45b114bab8615d74860f97bb05271f5d0b5a2f7" +)] +#[test_case( + "0x10e2c61d7e369db368a05f02a2cda93c9c5e9a330f084a628cb1fb53c6d255db", + "0xfb1b229496d73280c1c156ccad9c10ebfeaf1d62253b01d58fd60bb2f68caad" +)] +#[test_case( + "0x1500111ac021fd81246012e138b9eda8aa616939892df222a7e2cfa9826ccd9a", + "0x1778361bc74efeb1525c906ab0fe8a1e3389b9334a38364ac88fbe8ce2df14f2" +)] +#[test_case( + "0x2501fcc7cf66f7855b80a99f61a1aaf4da79f15e733b5c162aae779cd899f7fe", + "0x1ee83d1fd40c18ca194aad652d0ccc646c8fdba07f96a8a3dc8811c60f5856d" +)] +#[test_case( + "0x22f86b046a3061ec416739cd769d1ee88528134d7efff80b380c3c75d965cd7b", + "0x2ede9051c70b409f9b161331c78de40f976a76e60b7ff762b6f0aa874af71979" +)] +#[test_case( + "0x257b9a3b85164b570a3acf910ee5ed3a42723605c7a3a20440f6ede770e19633", + "0x1d9b106156c3279a618931fbddc913e391e8caf2d818c4aa914b35bd88bd19aa" +)] +#[test_case( + "0x300bf0e4501d17fbc102d3c3e70fed3234d048f7648616e49b4b23ef3cb4d51c", + "0xd498e81ca2577112e6f6828ea8cdc3ca354e282224827349b76f1dd1bbefe64" +)] +#[test_case( + "0x14cfaf7e94abf742481f345124713140d4d1342ae8a962baa3b7f65547ec6924", + "0x2889f002b35b78413c8441379aecefc0c3ff55905bc2d719cb3518a079bbe4ff" +)] +#[test_case( + "0x16a851fa9cb2a27bb2b5bf453ca0becce53162bbfc263677f8376e8c2518b225", + "0x14d2a53d0871577ae830d08d38b66209b72891becef5e1ed23b016916eca5ad6" +)] +#[test_case( + "0x21de9280f09d946498268ae02b8d8d81bee8903b591b92cc3def80aedf90db09", + "0x132c4d2fa3356956754f4f1247c856bfe76857329a317f663217565a7bd90334" +)] +#[test_case( + "0x16a09bad60267721122fe1a9d729d9baa7706c9f61fbef5a4d6f5ee36ab4ef13", + "0xcfe2414cef63b161d8139fa85f26a86cb2a00db95415bdfcab29a0211b5356e" +)] +#[test_case( + "0x7b4de2295293811d707f2190c63f72391200355d62dbd59d426b627842d8cfb", + "0x3f0380d3101465ff3b5143a9d3b93481a74751e737a0a79e1275fa2f6695591" +)] +#[test_case( + "0x13bf8783d8204af58937db2e397c5ddb61e6caa7b84420fffeb7e759e4467323", + "0x7057404a9219fbd71ef1b308731c0269e601eae01652305c0f3e7aa9efecc2c" +)] +#[test_case( + "0x993e08d75da08185411f995c20e16745ea379dfd4185f73caf434cf650f5727", + "0x1f7559dd91a5c9b45a4dc73240815e2b4ee6e6fd824dfd67b6d743c81fe1e27d" +)] +#[test_case( + "0x2535ee3ca4609c3f34c03e65976dde76028ebda7d2a1c9f3c7bf7703e03ec958", + "0xf78b186166bc33c2d1123138474ad59dc6ff68d3af40c06eddd3a0664a983df" +)] +#[test_case( + "0x136554371983ef155879777e38b94fa8d1760dd98cfd7107fd2c85edcb2cd405", + "0x72cce4cb9def52526b490d338d80c0603778409b5bd95513b2341e2744af1da" +)] +#[test_case( + "0x4ddb9c2c28b29dca5d89fd38d9fde22e416a8bb9f6b3815cbdd62b226ea6f3f", + "0x2893e6637a84cd1355d8ed026877d582a1734b2798eb90c2b2245ca7c4919996" +)] +#[test_case( + "0x228f58ab1cab846c87211fffb92053ef2c7efb0406f8e0078ca56794c8c7bbd7", + "0x162da3d4f6f9bf7a7de6d4100bacb6d9cf26e581a542d289e5048e153a478842" +)] +#[test_case( + "0x298a47d0e457aef872496d0a6058a20c0d0de976f4abcfeaf77c7ba62a6c85f0", + "0x97ed3849361a48adeeda2b1b965f766888bd5ea86e2bd2c1557d9d6112cc9ef" +)] +#[test_case( + "0x23461d9d06998da33f825a43a9d9404683276247982cd246804a95541e4b43ec", + "0x1ae2b0c60cbad700eb49676a1c168426a100fc3f513c56014abcf2e3738cc3c6" +)] +#[test_case( + "0xcc026e32762deb9eb877f8a685f8e98436374bb63f890bb68726dc0fd55c7c5", + "0x159fb466e83311f0beebde81342ee8053ded5b305e2ed7b52137dbcdecd6ba9c" +)] +#[test_case( + "0x26fc751063de000a640f48ed75faa36f28ea42602d10b8bf929cbb2b31637e28", + "0x110d7550473d227718817190b68e5782f7cecf94884bddedff5bfa3ba73c33fc" +)] +#[test_case( + "0x25d5773bc558d40a75f23b7a5599b7ac6fd00baa41e6e17a6d860621d0aac597", + "0x29421c8b0b99e5fecb154e2c297aba0b622e775912c4ac23e008c7afe42db0a1" +)] +#[test_case( + "0xccdbd8a83a7e3345d6dd502b4977afc50195b586a2eab7e5c8acd96b77af1b8", + "0xc862bfc4d21cd661a2cceff3d9f07a375d264b8586abb89e2e174fbbccf022b" +)] +#[test_case( + "0x8e132b417a8197928d55e603e7d43103aae1dab03e386a816ef813caa44f58d", + "0x9bc7fae5a35ef284c1aa104fe83f895521a47f26046fb0217629afd42d8c5b1" +)] +#[test_case( + "0xaa22fabd917e5a0de47b98167f65117a9e445f2dd15b2f6207a832d3aeac1d9", + "0x2180f9dbc447a10449a847221daaaa3078bbacb3157d2068d9acfe478c205e15" +)] +#[test_case( + "0x1bcf716ba5418321de4bc2729833b9a6eca98cac0f66a66748f8dc95ac847425", + "0x252498df4065a1ecab39b386a73f5ae77b0099408c9e5178cd45f0a88f140f46" +)] +#[test_case( + "0x24bc31040e89ac8640f77510f7aabed0f62c33f7e174cf23a8e5c125b09e5ce7", + "0x26ff5d9eec53a8147791d60dec7334f0e15c0c1562bd39a5507b25a55fdc1117" +)] +#[test_case( + "0x47bd7a63bc81d5630fb6947a7a997b8f71810b21b76246553fb8abae24ceda0", + "0x86b905261721192ae90fcb738ede465b021e6f21e131648213b9c18e5f27a4d" +)] +#[test_case( + "0x216a2d2f7c18050abaf32c9a8d0c6e6ebd37d30591dbeabba69f19558be37fd9", + "0x229782d104cd55b2255bc599d7903eea5ee45f2a6361d8721487eb8345e0e495" +)] +#[test_case( + "0x82604fa7c06b4e38e62c5e7eeb23728c4abe4ffdedb56d2a1c0072cca87f5c8", + "0xbaa0693f0ade943fc7ba01deb817c6b7388abc36fcea8996f0da5e737b4c17a" +)] +#[test_case( + "0x2a4bb9a545b055e62e92b6d00169dc4561eefc34c6cdc287bb6ed139fe180b89", + "0x8116d95da4da06976f4ed7acd0a84b19f45e9be22f13a9cb334c3b4fa5e26e" +)] +#[test_case( + "0x22160fe0f41fc35a7bcd52616c3e618d2b3070ea58805938250cadd9d02b2c8f", + "0x7983df4fcc4b04301be325ceefad2ef40ccf1620f551881e1cbe3d512376fba" +)] +#[test_case( + "0x2c133130b0190187a11ba3e5fa520aaadf5ebb470aab4d4e386b007520ab50f", + "0x20260047fa8d1fad6f5e75d575e4c7d9861fd38d2fa9f3da7d3b1cdf73fffedf" +)] +#[test_case( + "0x22315f658108887f29c99f4876b62b0d40a6fa3014274db2d04831ca36179f15", + "0x202a1b12731bb44996d4223e623a0507e0c2f91140bc0655454c2e87f594b1fa" +)] +#[test_case( + "0x2c4d0bacc44d53bebc8f0429f39708adfd43bf145155705721be0de3460f94bf", + "0x57a992098a1e36d340b17d33fb89577108adb827d571f9c6d9824a6923217d7" +)] +#[test_case( + "0x11849bca64f56566527fe827288dc6d3540e7e929efc5e4d7eafd509f095305d", + "0x13813f285b8214856bc99fbeaa441c6838be9f4e5fb6971edf0b22364395b7a" +)] +#[test_case( + "0x18ca311fd3804a43a56ce7fa94fc7052e1d56437314dcdb03b3151c7c86331e3", + "0x1d7a1d018f905cd4212c8d6456eba53f7b4631eae6ac5f55051e16e11789afc0" +)] +#[test_case( + "0x15748ac4dbe0a797ab1eac7959e1cfeb804d04ed3ed8bc9accacb643254db99d", + "0x5c863ddf01e2e60916fa54489a3f7f90fd8dac5a39fcb07cdaf293756cfa8f" +)] +#[test_case( + "0x27a611ee7aac4b7fdd20edeb5878948d4c609315b9e33daecbd89356db8d0a27", + "0x403f5943ef58f9eb2de53a52173b4a91d24c90c3fb9cd55a554264d0d1ee874" +)] +#[test_case( + "0x2d30e8e7214f50b604fb598c1efaa457d9db73de8d4cf5c7857265dcbfa581da", + "0x1576cfeabe6034354bcb1cb32dc405599a803571a1a66bb5e15516c2c78e6606" +)] +#[test_case( + "0x20711e31b7b5bc43d5ca145b9cfeb0be30c41edd881b1d833f2e54ee0d4be0c1", + "0x1e8e340529e0d63c1b0a997e59b82bad4a94ff335b5a5f8a5a3b213014e924b2" +)] +#[test_case( + "0x18c287dc14fe88b0adbef8d1ef2d58d422d0fab8de961340ceb7299ad7fe636a", + "0x1af4d9c17247b56835cf26e29827ad91d1938f1605c5ceb3da3e6caf12ea30d9" +)] +#[test_case( + "0xc2cc6f790940d65de04675d511459d0bfdad3a43d9a4c4677e381315c1452ec", + "0x11288e1e3b0221d1aab000997c79f01eff73965e6fa18386b4b0bc265b2b7d7b" +)] +#[test_case( + "0x19b3db61f6f3630bae4dc6b48015c8c5f467ea3419711bf0b5aad428f0a16802", + "0x1fcc156cd904fc210f7b78a942e12e5fc3119dcd2a3dd82b498cbb1cd73089e" +)] +#[test_case( + "0x21c92e918870e59b6987a256d184f010dd02adcbe2142b6ed4243019b7b9ab30", + "0x1316f508bfcb400cd688f70dc30fce11e54f22bed5c31ea40c91800cc44ebde8" +)] +#[test_case( + "0x624af0698ca0f02b1a2ed852b238e8b979ee621b223afca4f945aa569657167", + "0x1ca5e2f0e4295e0149e5ba772121e9ef3e2fe0ac83f24b4aa8b471b40a9019fb" +)] +#[test_case( + "0x22a5e74c6f717869bec28f8eac0e2f0c1f2fc5a1d67e797e2d89e2568710b94c", + "0x2283ee08f3361ac672bcb978193c158f2af5169532cb3a612fc3e16ba070c077" +)] +#[test_case( + "0x22cbd852b1135aed92cc6ccffa5aa346a0cebad916cf6fc93407f799c7f0b51", + "0x1e48d46617abbe1221b75ebf92d02715bc7a5933239be0e1c154ffd0a2d6ad2f" +)] +#[test_case( + "0x1b4ce54781392eeaed00d156eb301df99aed84044022acda6ef3880971f785f6", + "0x22116f6d5a5056ac8d6a94b0944dd5b6be597c3def921d89069d21b5bd567e63" +)] +#[test_case( + "0x195dad8e80a40291befcc391d6d90d96ac5da23517bf785bd43f9a694d0ace34", + "0x10fa4dfb4cbf444f0cc15afbfe546e3da23ba9b31ed189f5bb788b2d2406abc8" +)] +#[test_case( + "0x1b079c1ec1929783ed16ffbe1b3ce3cf799317cf50b9ccf4c7341c50ec003461", + "0x1011ada74bb36a88828d810855f95725400d5c1f96bd16c234dc993a80b55f8b" +)] +#[test_case( + "0xa79cdb7dadb55b1b2c86dabe430652ce2322793aa1ad3b8fbddc455b7b5bd48", + "0x277c82ed5154c26d4b5005c0167a8824f4c4f78bd3ad73daf818f14551e5e235" +)] +#[test_case( + "0x22b1ce3c072298900163dc4c8ef37c8abf6045bd8eac512ee84973723a889413", + "0x18b349fa56cd37819656b1ca9870f3c5395c60dd2ff30812b02e865b88e1101d" +)] +#[test_case( + "0x2d0c639d5a35a1391dc74e6f33152c898e7a963eaced5ebe7bc1ba283b6220a6", + "0x1441b4908bea213aee6d1a8d798f6bf7392ee0c854faa12d71c918ae2c68c917" +)] +#[test_case( + "0x2c55a5feedc228b663424fa4779406f58f90e5e0c378b14a01e0a5f99ecc3768", + "0x229aaa49d261a890a9408edf4e53f8a807d107c82720d5af9c447adbd3e93df8" +)] +#[test_case( + "0x2afcf0984f35aa2c805cdd1dbfa56c9fa869a32325115e633d855a2c4dc7bc7a", + "0x22afc3e5832384badabfe1379d664ef0ce42504f1eebf4652562c12ff5fcd257" +)] +#[test_case( + "0x2bd3c2c2b03cbb1bb9ea88c701e5331850345cdac3943ea4539fe18019cbcb5a", + "0x1bbd5402cc56aa14b613632c83b44e17f32ddc90cb491c4fe36fc3db693870e3" +)] +#[test_case( + "0x22b98fd8367db019f6e709e9761546868e1bd1b939cb37207ad7d2f4e61af6f3", + "0x158196138d686cb065500e993011921b5a5e6b205aba5a1a917b61c8e6b61cfd" +)] +#[test_case( + "0x2a00ef95a6225d666a5d0589b6ae18d3da939728ac6db46927f710a5902f46c1", + "0x1b9d6226897f1ea4699b28dda521da10c0d1f928ba596ad25760dc701c2c4bf4" +)] +#[test_case( + "0xaba72bc0da80375377698c6ab06913f5a1142eff7567e05f8de904f05399e2a", + "0x25dc3060f58c0ce8180ccabf0853a82fdd59435859ca08a9bf8f442931b7c053" +)] +#[test_case( + "0x17214d772a126a4bbe0939b3745d0741c333a1492004301411bae7a8e2ca5ef7", + "0x208f48cf1b85082d8af1244483cf65bc3dd4a59f0090870be8892606b3dfad82" +)] +#[test_case( + "0x650adf2d70e0d7b6c9da45b62125447e93f5af95674ac637b766f3a2d7e3b61", + "0x11bf807322cbcf29edde405d0d5674b15e17c0abd160049d9b32b8bc3ff8aff7" +)] +#[test_case( + "0x13782e4510c3427a26c6b1275e087148048bf7c630fa008de9aa579bac954dcc", + "0x82821d57bf038dce7610450ddb72115b0371abd588dc00e85c49f09026e2553" +)] +#[test_case( + "0x268b3c2f9b1c3f9fb23dfb7e3e8562170ecfd7b8b22cf37711cf82a5a3a1256b", + "0x38495a00c1ea10a49f6347a1649c1f44e8000be59146c2d01da0fd6a699d48c" +)] +#[test_case( + "0x1ff54deb17fed0d2aabd02bf5f96bd1c43c8a149e68ccc4f58c09fb1c45a39a5", + "0xe7ba15a7898cb0b1aa319c2aca08a17e3cf3a2975298bb9e18a51e3e25192be" +)] +#[test_case( + "0x1c030143aa7bedb029228a35e42e35e750b97601d674bd9f40595d8652cac93d", + "0x1dda16d607c3571724edf8d8ac519fdd27c8e2fda0fa3f1e975b1d965589011f" +)] +#[test_case( + "0x2239d7a1669c6bb9371b76d4eb5da03cf57b0916f6aa88e0646fecdc30deedd4", + "0x15f84eb11e496cab754210bde053404f5b9117ca8c4046924d24969c29693519" +)] +#[test_case( + "0xd6d12c3ae70dabba3da219d3e917b1cda174ee015cff2cb1a89991dcee456a6", + "0x18dea13e72aed477445ee1649c506306aea918b1f05c572248827fed23db6c7b" +)] +#[test_case( + "0x38de326bedaac59a687789272a28fff3d110f20c037306f7232791c463b90ad", + "0x1dbeff41063016afd8b6e612eef54b6f02aef08e4aa527efe5bf4c908de1416" +)] +#[test_case( + "0x1c1fe12683d8d267c934bc0a3254bcc8056e9cb9e969ad170e3dfaa8e5586", + "0x183fd1dd6df336293c7ad0df4c4591c582103710ad9fdd20af29d2f00f2d8829" +)] +#[test_case( + "0x243f3876808e9351c8bec2a03382141c21e7264ddab391f92859c8acbcd2d06b", + "0x251e6a8eb4a0cb951331a5d4d311af0cfcf3e5e8ec38d8ba5da2616f3123f6ce" +)] +#[test_case( + "0x2a33a6cb3f56d63941e7feffa23ee3c4d442849f0fae2a35126c6c8e983ab4b8", + "0x25348748df332a5914ad1c5b3898683fa6d4413eeda26d9f37ec427dba5979b8" +)] +#[test_case( + "0x1a4c2fc79b31fd5eebf158a263d367de37ff049ffecd6dac2f83f68f2789ad67", + "0x1c7a391ae6060b2325d04c1afac3b2d9791f405e8149077309b6c61c33d1d59f" +)] +#[test_case( + "0x1ba4663350cee7dc26b4b77acc236d62b1c220cd5cb021524fce492632062322", + "0x237fb10067864ae6faa02aa24f13a92b8609d9617404d971176390e6e22057c9" +)] +#[test_case( + "0x29c9cd26d1b2711cc25abc1631fd25b2cb3566954e818db4b243aa7c3f55269f", + "0x15aa87b2b641244ea11090e09c38f46fcff0790c4b7d4881d60d1963ea7a7384" +)] +#[test_case( + "0x14ab2b813699abc3ffcfdef130f3a7d1de942db1060e8b942de7a5b7455f5579", + "0x12ea09450cabdc795b0eaa71106f49eabf27f617311ec4934f88381e778ca36" +)] +#[test_case( + "0x261d723c8f3446e0fa887e3994c192d36a9b7ae89bad278d641495a31707bdaf", + "0x177798c723be2859188df10cd6bfd6d22900fea5bcb17a8269c47c4ec57ecbc4" +)] +#[test_case( + "0x2415743f394b16da0ad801e5c0458816fe6879e2b104e4b932d25bbae1e4f495", + "0x21bbeef0c708be4177e609a980574acc4ee35694f7192bae93267480523dbfa6" +)] +#[test_case( + "0x1e019a2b15a59774495646a2c6e90af5f9513855e8790fda0410d821aa85c77a", + "0x2499a400283616bb886d1a50fae5aea968f2a9ba569e6ca66b3834bf24b5fe90" +)] +#[test_case( + "0x18e594f40bebfd4378559de7cfb8134792f54201e6f97f13eeec495db4b72043", + "0x2a05674c906911dd4174eff658b723d9cd196cfdb37899d5d5dd64033af8e049" +)] +#[test_case( + "0xb026f975ba448d1862b063999cf926a0926b149d2af3cf4174e56e060b1e40", + "0x18afdc9a34dfa214e17f298a56ac2cbf8e6da180fa3e83ab60f3310b5574f223" +)] +#[test_case( + "0x162b6cb9b09cfbd3eddd0665bdeab6f7696e63e57c883d74160b360e55bda75a", + "0x1dbcbbd18641d2b477e19ef0186c608f0e2775a3e80c6d12c30b2df6920b869" +)] +#[test_case( + "0x3053a306366ae73004adb183c86cc3a3e6b0e2d317d08da708c9e5731927d427", + "0x12a78b1fb6535610e34c60342df9aa6b7deba119bb371523b2554ff2490cc63d" +)] +#[test_case( + "0xa257789e2277e79f78bcda818dbe60264be4ba74a7f0e5512b77ebde009ab2", + "0x1ea57d595383619dacb68111240ba48a0fc88aac272c7c99b6d6bb52a28fa650" +)] +#[test_case( + "0x13c2c6e174d814c5d7eae609741101a78a451faf30d8c4ada0ddb50c2c32bb0c", + "0x37e2eb482c3ccd3db5eca84aadc20da4a7f62709892006753382d9c7d7efdd" +)] +#[test_case( + "0x29a1b6724661494c7d92de23d383dfee36bcd9a50948985fa15afcdc9fb4eefb", + "0x261bc9b7013c833ec7a28ea5ef230053165f00baec582163d1773fad0344e793" +)] +#[test_case( + "0x271d404a15841bfa3649f393f97e93740334697e842cdbaf6a506672cbb33ba1", + "0x1caa0bcbb5781345da0a936ce4fe20bbe70292aa5d6875ebba25cb901b1a8756" +)] +#[test_case( + "0x168dc2d2fc6ef1b60935ce41605391fee9d8293bae910b398093c645e352ceac", + "0x2e2cf3475af52022033343ba610d4648ee9d8e929f1156ee58422871973c0f02" +)] +#[test_case( + "0x697a67cafbabb021ac4e263bab1a3135b8eb0510287e63afed023d10e68faad", + "0x124fef2af38736220515457ef35a016ef1e93d7a07301a7fcab4492fbcb540d6" +)] +#[test_case( + "0x2d1a15910aea25812f7a713b58f69c67d1c1dd53afefcb469ce5ea550c63a0e1", + "0x23437cd1744acee9b189947dd7df550d86c9177928c1da7d29b42737b9f753aa" +)] +#[test_case( + "0x1923b06c682b6ab159e4179d06f5211606291fc5dac808d96ee9d92e06e8c19d", + "0x19ac34336363b25e6a6f3689e311f7ee402685073aba9751c677fc4fe5d37a67" +)] +#[test_case( + "0x23a31f65ff514776538165d29e89d78300cca5372e9846c8b753d3f9f8044be4", + "0x92804e5af8af3bd1f48ac2385ead58df2d844d2f734054704cea69463b1347f" +)] +#[test_case( + "0xf289591fdaf5296221ad7242f5df12d8a76043909630dbacfbac0a855fb0adf", + "0x374b18432af1867a8151a59b22a1825a361f46f17d5b1eec617d2436f41f925" +)] +#[test_case( + "0x16996e0caa62e15e8a84366e2cf51fb63c08b99be3bfc0fe611c15b1ffdb92d5", + "0x35eea3531ad25b1b30965c1c28405def501afc7f05e77cd4420f3b5f97fcab8" +)] +#[test_case( + "0x43eb07ab2d420f827b29288f5fc7d35c419690cf49555d779061809d652c0d5", + "0x289a0cf358c7694fa5ededf12f69ad9d1fe4135fdd66c8b24b98d4149ea22f9e" +)] +#[test_case( + "0x2c9d3863863bc9575b5674d0d89685e6c003afabde723d0d68de5a3337f34dfd", + "0x1a71ab341620d6ffe5b16000cbd4fc618bd1970a824239c6d45f2c25bb99eeab" +)] +#[test_case( + "0x279dfab7a5b2953cc11002895072278fcdac890e8ada0d5b7e51fb2471b92cca", + "0xbcee4169b31286d0e2fc40fe9da800ba7b4949807d1ccb4538e99ef0334628" +)] +#[test_case( + "0x14205cd4096bbca14a1155dfc76ceaed08d864aa680f2ff47cdf03aad0aad0ff", + "0x11501a4f908f5cd18c3920b6d0604b77391e2490fc92c626eeaf5f88201c4f57" +)] +#[test_case( + "0x1e0c56db882cb1718ed784d36b33fafd67bd7a836fe7b902e4d140bfe0b5f603", + "0x2f6b1576269ab9c5d9d6958c068f3c1e23b120ab08a1c9158d069e283597dc06" +)] +#[test_case( + "0x4f83ad44d04d568c28ae648ee6d61ceeb439c38e4bac0668655e623aa2519b3", + "0x1438f4f055d8add1f00cef9ec83e1cdf4ea3c19b31ecd5939670ea6c005b0e60" +)] +#[test_case( + "0xd136345c46ec42cd307d41cec11479e13f9f63a5b417c451d508b430eb70c59", + "0xb41a0fdbd96f5e6641dc32030cfb727479824c38cd155b8a2a5b3985de8ca5b" +)] +#[test_case( + "0x21d0e540338f197962ca54281b078cb0f2f1377e2ba8af4f4c317650cbc8474d", + "0x251712ec63d4586c2f626fa7c326211c1008624a7137acd58c89c749f3c45910" +)] +#[test_case( + "0x1a48e43fc78439d7830c56e6ad10939f412e49dd5bb0f91a0e6c691207c84fd1", + "0x28d487cf2e89c9ebcdbde1d36c2880fc3cec52de2fe81e24be84d7c29edba734" +)] +#[test_case( + "0x13dac95faecede510707379cc3d8aad5667c55ab22d88c541d92d5029311ec6b", + "0x8da8cb35003282b91955580d729d83bb24da6c3e2329a9b3f59946dc1101fd5" +)] +#[test_case( + "0x1005c9ba92045b0d2c091823efceb3e94c1309384093248fef60a7e4d214984a", + "0x2a29050ef5c587f114c022906469727cc992065ab652589bbb602769cb624a1d" +)] +#[test_case( + "0x26f00b244a6e09e33f340d3d9d9c70fe8bc8605ceba4ff60c4981ab964291cc8", + "0x61b7ef308917ff7b6546b4ac579d00a2d23c4c6e7148ab0a76f0cb58553f992" +)] +#[test_case( + "0x5ec65154b527ccee4fca4352dd70ee92af0c2acefc59efa0c3a01cf76666eb1", + "0x71c4282827915fe5aa46f7cd29bff6439af37b8c36626ef163c1632bbcd627" +)] +#[test_case( + "0x141d86e8db4e7dd156f37ef1b3e098397b91a16a7a958466ac3c1a4b88e93d59", + "0x676f6e126cd32af7b2c04f5344b3e61c988d78ab477825566831d9c65003312" +)] +#[test_case( + "0xba149f9370ab0600a294405707cf09266cebcc502e7a45ac3981ce267f91e97", + "0x110c91fd089137d7a14d413be56177a5a16624965c4ca2751b16357fc70f69f2" +)] +#[test_case( + "0x2e6f97e69d5118df6067cfcefaaf2911a823574180e7d33e81b614ac3993a31d", + "0x53e6cc4a2355704346053c7fc1e4e019975a4c1917fee4d2099ecf9d9bee025" +)] +#[test_case( + "0x1bc8fe8ca3b70b89b16c92588d9806b56d9ec5f800a6d8c4c9976de96c7276f9", + "0x1df64626b2aae1f91194590703bfc4ed4f9d3fbe8b0937f2c7ea5aa5c5376d0c" +)] +#[test_case( + "0x30428f6f569ab3465ef5ded3a0fe6d85622a4dfd4939749c5c3fb9d309a08a69", + "0xfe83078ead3d36826a82ba1f0ed693b81cc56586094770fe3b3a9b18db47e60" +)] +#[test_case( + "0x16c1f522802c3245334e31fb458f494b6dd60039e788e7cb24881abfc9c2c58", + "0x1daf898f07c8391c96339014c8235d424bc60f0df2da2a0286520c6e01c2e899" +)] +#[test_case( + "0x13ac03c94d96332158c4b7819cd271b032e9b88877284a2aca9620ee38e22ed9", + "0x14ed68b189d663a58373656cd3944a5f6d0555fd4bec19b5605ef575b1424851" +)] +#[test_case( + "0x542dbd7469f5c8501461055f3194ea70bbd3bdc5eeec2c5ded7899252799046", + "0x236f13e247951025c8f2324d469dc4ef4c2a9dc39c8fbaa0be30290379cb49f3" +)] +#[test_case( + "0x236078d5c1900bd459906b8393995385bc92ee0d53ba0464110f535cfd3acd60", + "0x2f90c6c26b7665eb35d95212f19f24c493596cc81be5e7bf27abb6cdb86774a4" +)] +#[test_case( + "0x1633354c0f110a08c8f587902ed44de52e02b0d819272f930ee4ecb4c31eeee", + "0x28c72daa079017ac2e59f90eb088dc9f2900d1d7a49427166173891827827541" +)] +#[test_case( + "0x16a8790a8c6cd1015cda32e8040e9b0e071f03e3c6c8e5fca421193c397cea9d", + "0x165e900e7abfcca2cea1a088f89779d194e903f39659f00666483afd2410416e" +)] +#[test_case( + "0x18e26d616cfc39c834d7e59ea96e6c289ad02426cbb195e3f6df65b1dc23055d", + "0x1a8cad9931fe0e7bef40d3b6c1770de5e472bdfca5e2665c83620548fd577b90" +)] +#[test_case( + "0xc263cf26d8fc42a248d39703c3712ad3b6e667c68287dfaa84651ad2f674ef5", + "0x1aa6e9e20da131b2d0522e57913c27e51dc9d5797ab1c3e9813e3502c1e7c0ad" +)] +#[test_case( + "0x23ad686412dd73cc77ffc7670572db2485ee7394837025ae48c289015c7e978d", + "0xdbf9264771c45378a72e5b5c3335adccab42552a9064cbc1c622675090f53a" +)] +#[test_case( + "0x220bfae06a1dceee83c753ea8e508cc774132e075a02f2096e3e7d81bd72172d", + "0x1b4e2f80a83b905494000aed02b250f4d79d62551e024c5aaed162b8d79c3e6d" +)] +#[test_case( + "0x24849513d1e063d79d8fa521a18ff3fbb7f3effb0d94f00877885d904d0314e6", + "0x1f3b52e053cea7725920af07142a03110811f73822d1193d7a7f71f71dd14c12" +)] +#[test_case( + "0x67d5f9c418e30438b48ec83ebeb2262a7a1ff60577bf38074d8abf732f2f264", + "0x10000009fd7e1620b7b607f2740183853d6c37a77bed816294f2c3dad6282105" +)] +#[test_case( + "0x5e88d3f870951bb54e1a186f064b988957d0e18acc6e9177576e15db5d9ef29", + "0x1087dff3386b75e87737469875476093efc5726d79fc6ef6d605b65694fd9c4c" +)] +#[test_case( + "0xc7ae5cabcc87938fbbd6ca8ce43032e04e77b5a31382dd3f155c32eaafba312", + "0x1d88f46f8b490622b5dc8ecb21496010ea7dcfedbdb03b0379b7a82b13e8d345" +)] +#[test_case( + "0x1f17d00517a321ddf7b37580b49ab1019704fb58c398cccc661574f7ad3bce84", + "0x10480c2fca990545bf4c75ebb9f3e0d2c1d1202064bec0a2538c361ed5383b40" +)] +#[test_case( + "0x186440545c491379b155591f000ee1a2966e0b06edf7f04821b6c5aa32560ae5", + "0x5fbc982c0451c06ad62cc26690b79eaa41a328e0c2dd0a2efc30142d800066c" +)] +#[test_case( + "0x1920006d20470b1e1c8915483ea8fc39d9c8d6d28ee99067fb3eacf811264b36", + "0x18cbd767b3a0abfbe14074c1ca66674582d21811b6db4d1646f0e3974a2bdd80" +)] +#[test_case( + "0x283a1eba0ffb8d78c6d6a83192fe0a3e8c8a85bf676ea15638aaed2298b5e4c2", + "0xc2f16dbd66f12ddbdca4d2598e1bfb0b4609c6ece9846c9ad206f9a84360c13" +)] +#[test_case( + "0x2a6b2dffe1bbbb905cc8ae33f94979d221254f2ee312f447931e78ae760f384", + "0x1223e3acf24d68f700fde34dda5b99d3d14a2e19e521d6bae5eb3208ec85da93" +)] +#[test_case( + "0x16ec2b2cfde5cdcc0ad46b2819883b7648cafc79c0ffa6b37495ce963fd9e73a", + "0x13efd7c9427a9230e2dd4f9912b2f89f2b1b7b8a5381b1c3545eea5329e3760b" +)] +#[test_case( + "0x1af1e3ac1ca9c2c6e50de497a5110f4fab04765a35c2e69f282a4622230f12c9", + "0x230a32aa27ab6ba91b54885db3f7743eddfce719c86d2f1dc53a66f933bb1d9c" +)] +#[test_case( + "0xa4ce73df237a1bc5586b30c1a90abf2635d85b3d05c19a2559f576c4aca68b2", + "0x2a8964830d7bb4137e6ccf55bd2d1769a3eb21883f4210a11898f047c990c54c" +)] +#[test_case( + "0x2013f902abddeaf7a59420acfca012978c86fb145710a778a1eb9e3a8cd3e0e", + "0xfa674fc2825d5cdf1c0b4354f82b81349f21423ac35e21c2941e07309094e99" +)] +#[test_case( + "0x1c8ca38b0b4b152d76bd669baff3df0c2fb5821f3a3409b02a27243920fc092d", + "0x245c4a67f70014be0c35c3249d26700bb93c8ce5944476fd8e080ccdcedc4f3c" +)] +#[test_case( + "0x28d993d130104cf376503225bb871cb6c7c04f6884d1966b856f0cb22cf12896", + "0x153ea86b6aced470fedb21490d39a49107706264da949981127ab06016df4413" +)] +#[test_case( + "0x255a0d5a19ab5fb26aadc41155a468d721044645b99a7e5d37f3832723703029", + "0x2cb62312a43afd94a89aa39701a030a88ce6f0f6fffe7cad0d2464b5b43a8b0c" +)] +#[test_case( + "0x8794e5bfc7d0af0a931f44e0be72f96dce52de694163df1dde09d1ed8f5acad", + "0x1271b41eaa2a672bfc42d7da1d001de8d2d92fd315f90335c13ea496249dd207" +)] +#[test_case( + "0x1ccc02c48f9c79d7185058e3e987a32f82df5cb5cb393ce878f9d72b87f4cc84", + "0x144c8f26f6199f4e7c847b18542ed6300491857b307a74e0b43341731752d43b" +)] +#[test_case( + "0x1d9d714ff4edea908f1cba490bbfe172e4678f302e60e43dd8d12a4db829e2cb", + "0x19493334bf21e33536df25e60eaf793b68ece2f347fd4f18774204e3366f3d2a" +)] +#[test_case( + "0x2c724d15b6f2a12d3c480012fe0b5ca437c82cf489e2d3a2dbdbdc2df75ebf8b", + "0x107b89affc90a92d066d2be0a0fab2c3c13cfeb3669a149ae70eef3e57b72a5c" +)] +#[test_case( + "0x14b878fab29234cf324d2ca450a17ea47dc6c8c163ee8e599e3bfd88929edda8", + "0x5db74c45062dcf13c08ac1ad090c4a94d205616846e2db65c6514df4dc4511c" +)] +#[test_case( + "0x19af4d9da562ab34189ead73da36968f1cfe5341764a07076227ac6ddc40331f", + "0x184ff15b8e41cc46504cea5ed36b2514b81fd02b0a0c0b8cb810ed763b9b5c68" +)] +#[test_case( + "0x8aa7975a01a9e3b97f38f81886f50f01d5f98c1dd342d00fbe0d058dce2ffbf", + "0x1e88df023cf4833fed24c591a93b757b7c81e102e370b26bd9669ded5b32a912" +)] +#[test_case( + "0x25134030c65aa48bc9f52e9f9de7d1700a0b2bc10a86d7fc8d935716959c398c", + "0x1349a04b5d6495b421f8bee2e6c5445b381a24612fe8fc43db658a72a538677c" +)] +#[test_case( + "0x21b068deff9b75e9607f7678139e876d3e0479dd9287a60ecbefb588f5fbdf4b", + "0x1fc5aa3aec42426da9585fc66cf4281f21d7c745a750d0dc6b0bb073e4b66eb1" +)] +#[test_case( + "0x81594083a63da6b2c349931c9236d758d57afa3f81eddb3f4a5fe2d53cb588c", + "0xfb30f6c0fdf0a8e4fb0a207baae49af20b4eb11bf95497e2b255eea6a7dc461" +)] +#[test_case( + "0x17492b875f2dda6c9b2c49c17b9095ca65de415170d3e9a4d834e5b5ef579182", + "0x31374fb9c8e46406fa83f5db0274d8d799a480a24b396db3e4df165691d02a6" +)] +#[test_case( + "0x1e6f690b9b2c5b4d3440c1907b38a8cbe2b3267e988c760d2bb426d19014eef9", + "0xb13a6b0f347da61aff1a5273fc125b6a050cc903022f0ebf366a5d2ed3debc3" +)] +fn pow_magic_number_works(x: &str, expected: &str) { + let x = Fq::from_str_prefixed(x).unwrap(); + let expected = Fq::from_str_prefixed(expected).unwrap(); + + let actual = pow_magic_number(x); + assert_eq!(actual, expected); +} diff --git a/zrml/combo/src/types/cryptographic_id_manager/hash_tuple.rs b/zrml/combo/src/types/cryptographic_id_manager/hash_tuple.rs new file mode 100644 index 000000000..94cc20648 --- /dev/null +++ b/zrml/combo/src/types/cryptographic_id_manager/hash_tuple.rs @@ -0,0 +1,115 @@ +use super::typedefs::Hash; +use frame_support::{Blake2_256, StorageHasher}; +use parity_scale_codec::Encode; +use zeitgeist_primitives::types::Asset; + +pub trait ToBytes { + fn to_bytes(&self) -> Vec; +} + +pub trait HashTuple { + fn hash_tuple(tuple: (T1, T2)) -> Hash + where + T1: ToBytes, + T2: ToBytes; +} + +impl HashTuple for Blake2_256 { + fn hash_tuple(tuple: (T1, T2)) -> Hash + where + T1: ToBytes, + T2: ToBytes, + { + let mut bytes = Vec::new(); + + bytes.extend_from_slice(&tuple.0.to_bytes()); + bytes.extend_from_slice(&tuple.1.to_bytes()); + + Blake2_256::hash(&bytes) + } +} + +/// Implements `ToBytes` for any type implementing `to_be_bytes`. +macro_rules! impl_to_bytes { + ($($t:ty),*) => { + $( + impl ToBytes for $t { + fn to_bytes(&self) -> Vec { + self.to_be_bytes().to_vec() + } + } + )* + }; +} + +impl_to_bytes!(u8, u16, u32, u64, u128, i8, i16, i32, i64, i128); + +impl ToBytes for bool { + fn to_bytes(&self) -> Vec { + vec![*self as u8] + } +} + +impl ToBytes for Hash { + fn to_bytes(&self) -> Vec { + self.to_vec() + } +} + +impl ToBytes for Vec +where + T: ToBytes, +{ + fn to_bytes(&self) -> Vec { + let mut result = Vec::new(); + + for b in self.iter() { + result.extend_from_slice(&b.to_bytes()); + } + + result + } +} + +/// Beware! All changes to this implementation need to be backwards compatible. Failure to follow this +/// restriction will result in assets changing hashes between versions, causing unreachable funds. +/// +/// Of course, this is true of any modification of the collection ID manager, but this is the place +/// where it's most likely to happen. We're using tests below to ensure that unintentional changes +/// are caught. +impl ToBytes for Asset +where + MarketId: Encode, +{ + fn to_bytes(&self) -> Vec { + self.encode() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use test_case::test_case; + + type MarketId = u128; + + // Beware! If you have to modify these tests, that means that you broke encoding of assets in a + // way that's not backwards compatible. + #[test_case(Asset::Ztg, vec![4])] + #[test_case(Asset::ForeignAsset(0), vec![5, 0, 0, 0, 0])] + #[test_case(Asset::ForeignAsset(1), vec![5, 1, 0, 0, 0])] + #[test_case(Asset::ForeignAsset(2), vec![5, 2, 0, 0, 0])] + #[test_case(Asset::ForeignAsset(3), vec![5, 3, 0, 0, 0])] + #[test_case(Asset::ForeignAsset(4), vec![5, 4, 0, 0, 0])] + #[test_case(Asset::ForeignAsset(5), vec![5, 5, 0, 0, 0])] + #[test_case(Asset::ForeignAsset(6), vec![5, 6, 0, 0, 0])] + #[test_case(Asset::ForeignAsset(7), vec![5, 7, 0, 0, 0])] + #[test_case(Asset::ForeignAsset(8), vec![5, 8, 0, 0, 0])] + #[test_case(Asset::ForeignAsset(9), vec![5, 9, 0, 0, 0])] + #[test_case(Asset::ForeignAsset(u32::MAX - 1), vec![5, 254, 255, 255, 255])] + #[test_case(Asset::ForeignAsset(u32::MAX), vec![5, 255, 255, 255, 255])] + fn asset_to_bytes_works(asset: Asset, expected: Vec) { + let actual = asset.to_bytes(); + assert_eq!(actual, expected); + } +} diff --git a/zrml/combo/src/types/cryptographic_id_manager/mod.rs b/zrml/combo/src/types/cryptographic_id_manager/mod.rs new file mode 100644 index 000000000..1f1e5facf --- /dev/null +++ b/zrml/combo/src/types/cryptographic_id_manager/mod.rs @@ -0,0 +1,41 @@ +mod decompressor; +mod hash_tuple; +mod typedefs; + +use crate::traits::IdManager; +use core::marker::PhantomData; +use ethnum::U256; +use frame_support::{Blake2_256, StorageHasher}; +use hash_tuple::{HashTuple, ToBytes}; +use parity_scale_codec::Encode; +use sp_runtime::DispatchError; +use typedefs::Hash; +use zeitgeist_primitives::types::Asset; + +pub(crate) struct CryptographicIdManager(PhantomData<(MarketId, Hasher)>); + +impl IdManager for CryptographicIdManager +where + MarketId: ToBytes + Encode, + Hasher: HashTuple +{ + type Asset = Asset; + type Id = Hash; + type MarketId = MarketId; + + fn get_collection_id( + parent_collection_id: Option, + market_id: Self::MarketId, + index_set: Vec, + force_max_work: bool, + ) -> Option { + let input = (market_id, index_set); + let hash = Hasher::hash_tuple(input); + decompressor::get_collection_id(hash, parent_collection_id, force_max_work) + } + + fn get_position_id(collateral: Self::Asset, collection_id: Self::Id) -> Option { + let input = (collateral, collection_id); + Some(Hasher::hash_tuple(input)) + } +} diff --git a/zrml/combo/src/types/cryptographic_id_manager/typedefs.rs b/zrml/combo/src/types/cryptographic_id_manager/typedefs.rs new file mode 100644 index 000000000..224b450a9 --- /dev/null +++ b/zrml/combo/src/types/cryptographic_id_manager/typedefs.rs @@ -0,0 +1 @@ +pub type Hash = [u8; 32]; diff --git a/zrml/combo/src/types/mod.rs b/zrml/combo/src/types/mod.rs new file mode 100644 index 000000000..3dad26231 --- /dev/null +++ b/zrml/combo/src/types/mod.rs @@ -0,0 +1,3 @@ +mod cryptographic_id_manager; + +pub(crate) use cryptographic_id_manager::CryptographicIdManager; From a04d02ec15f136ce7fa728102ab3d868fb22e4a3 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Fri, 4 Oct 2024 14:46:54 +0200 Subject: [PATCH 06/73] Implement zrml-combo extrinsics (#1369) * Extend `Config` * wip * Some refactors * Implement splitting tokens * Implement merging tokens --- Cargo.lock | 1 + primitives/src/asset.rs | 5 +- primitives/src/types.rs | 3 + zrml/combo/Cargo.toml | 1 + zrml/combo/src/lib.rs | 241 +++++++++++++++++- ...manager.rs => combinatorial_id_manager.rs} | 12 +- zrml/combo/src/traits/mod.rs | 4 +- .../decompressor/mod.rs | 24 +- .../tests/decompress_collection_id.rs | 4 +- .../decompressor/tests/decompress_hash.rs | 2 +- .../decompressor/tests/get_collection_id.rs | 6 +- .../cryptographic_id_manager/hash_tuple.rs | 8 +- .../src/types/cryptographic_id_manager/mod.rs | 23 +- .../cryptographic_id_manager/typedefs.rs | 1 - zrml/combo/src/types/hash.rs | 1 + zrml/combo/src/types/mod.rs | 4 +- 16 files changed, 283 insertions(+), 57 deletions(-) rename zrml/combo/src/traits/{id_manager.rs => combinatorial_id_manager.rs} (56%) delete mode 100644 zrml/combo/src/types/cryptographic_id_manager/typedefs.rs create mode 100644 zrml/combo/src/types/hash.rs diff --git a/Cargo.lock b/Cargo.lock index 71c880a47..77640a1b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15270,6 +15270,7 @@ dependencies = [ "frame-support", "frame-system", "halo2curves", + "orml-traits", "parity-scale-codec", "rstest", "scale-info", diff --git a/primitives/src/asset.rs b/primitives/src/asset.rs index d5c923537..2730a7a84 100644 --- a/primitives/src/asset.rs +++ b/primitives/src/asset.rs @@ -20,7 +20,7 @@ use crate::traits::ZeitgeistAssetEnumerator; use crate::{ traits::PoolSharesId, - types::{CategoryIndex, PoolId}, + types::{CategoryIndex, PoolId, CombinatorialId}, }; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; @@ -48,13 +48,14 @@ use serde::{Deserialize, Serialize}; pub enum Asset { CategoricalOutcome(MarketId, CategoryIndex), ScalarOutcome(MarketId, ScalarPosition), - CombinatorialOutcome, + CombinatorialToken(CombinatorialId), PoolShare(PoolId), #[default] Ztg, ForeignAsset(u32), ParimutuelShare(MarketId, CategoryIndex), } +// TODO Needs storage migration #[cfg(feature = "runtime-benchmarks")] impl ZeitgeistAssetEnumerator for Asset { diff --git a/primitives/src/types.rs b/primitives/src/types.rs index f361dd568..9b14f097f 100644 --- a/primitives/src/types.rs +++ b/primitives/src/types.rs @@ -54,6 +54,9 @@ pub type BlockNumber = u64; /// The index of the category for a `CategoricalOutcome` asset. pub type CategoryIndex = u16; +/// The type used to identify combinatorial outcomes. +pub type CombinatorialId = [u8; 32]; + /// Multihash for digest sizes up to 384 bit. /// The multicodec encoding the hash algorithm uses only 1 byte, /// effecitvely limiting the number of available hash types. diff --git a/zrml/combo/Cargo.toml b/zrml/combo/Cargo.toml index cee14a362..f7e005123 100644 --- a/zrml/combo/Cargo.toml +++ b/zrml/combo/Cargo.toml @@ -4,6 +4,7 @@ frame-benchmarking = { workspace = true, optional = true } frame-support = { workspace = true } frame-system = { workspace = true } halo2curves = { workspace = true } +orml-traits = { workspace = true } parity-scale-codec = { workspace = true, features = ["derive", "max-encoded-len"] } scale-info = { workspace = true, features = ["derive"] } sp-runtime = { workspace = true } diff --git a/zrml/combo/src/lib.rs b/zrml/combo/src/lib.rs index 2c8826368..0ac8350f6 100644 --- a/zrml/combo/src/lib.rs +++ b/zrml/combo/src/lib.rs @@ -29,16 +29,42 @@ pub use pallet::*; #[frame_support::pallet] mod pallet { + use crate::traits::CombinatorialIdManager; use core::marker::PhantomData; use frame_support::{ + ensure, pallet_prelude::{IsType, StorageVersion}, - require_transactional, transactional, + require_transactional, transactional, PalletId, + }; + use frame_system::{ + ensure_signed, + pallet_prelude::{BlockNumberFor, OriginFor}, + }; + use orml_traits::MultiCurrency; + use sp_runtime::{ + traits::{AccountIdConversion, Get}, + DispatchError, DispatchResult, + }; + use zeitgeist_primitives::{ + traits::MarketCommonsPalletApi, + types::{Asset, CombinatorialId}, }; - use frame_system::{ensure_signed, pallet_prelude::OriginFor}; - use sp_runtime::DispatchResult; #[pallet::config] pub trait Config: frame_system::Config { + type CombinatorialIdManager: CombinatorialIdManager< + Asset = AssetOf, + MarketId = MarketIdOf, + CombinatorialId = CombinatorialId, + >; + + type MarketCommons: MarketCommonsPalletApi>; + + type MultiCurrency: MultiCurrency>; + + #[pallet::constant] + type PalletId: Get; + type RuntimeEvent: From> + IsType<::RuntimeEvent>; } @@ -46,6 +72,15 @@ mod pallet { #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(PhantomData); + pub(crate) type AccountIdOf = ::AccountId; + pub(crate) type AssetOf = Asset>; + pub(crate) type BalanceOf = + <::MultiCurrency as MultiCurrency>>::Balance; + pub(crate) type CombinatorialIdOf = + <::CombinatorialIdManager as CombinatorialIdManager>::CombinatorialId; + pub(crate) type MarketIdOf = + <::MarketCommons as MarketCommonsPalletApi>::MarketId; + // TODO Types pub(crate) const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); @@ -58,36 +93,218 @@ mod pallet { T: Config, {} #[pallet::error] - pub enum Error {} + pub enum Error { + /// The specified partition is empty, contains overlaps or is too long. + InvalidPartition, + + /// The specified collection ID is invalid. + InvalidCollectionId, + } #[pallet::call] impl Pallet { #[pallet::call_index(0)] #[pallet::weight(0)] // TODO #[transactional] - pub fn split_position(origin: OriginFor) -> DispatchResult { - let _ = ensure_signed(origin)?; - Self::do_split_position() + pub fn split_position( + origin: OriginFor, + // TODO Abstract this into a separate type. + parent_collection_id: Option>, + market_id: MarketIdOf, + partition: Vec>, + amount: BalanceOf, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + Self::do_split_position(who, parent_collection_id, market_id, partition, amount) } #[pallet::call_index(1)] #[pallet::weight(0)] // TODO #[transactional] - pub fn merge_position(origin: OriginFor) -> DispatchResult { - let _ = ensure_signed(origin)?; - Self::do_merge_position() + pub fn merge_position( + origin: OriginFor, + parent_collection_id: Option>, + market_id: MarketIdOf, + partition: Vec>, + amount: BalanceOf, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + Self::do_merge_position(who, parent_collection_id, market_id, partition, amount) } } impl Pallet { #[require_transactional] - fn do_split_position() -> DispatchResult { + fn do_split_position( + who: AccountIdOf, + parent_collection_id: Option>, + market_id: MarketIdOf, + partition: Vec>, + amount: BalanceOf, + ) -> DispatchResult { + let market = T::MarketCommons::market(&market_id)?; + let collateral_token = market.base_asset; + + let free_index_set = Self::free_index_set(parent_collection_id, market_id, &partition)?; + + // Destroy/store the tokens to be split. + if free_index_set.iter().any(|&i| i) { + // Vertical split. + if let Some(pci) = parent_collection_id { + // Split combinatorial token into higher level position. Destroy the tokens. + let position_id = + T::CombinatorialIdManager::get_position_id(collateral_token, pci); + let position = Asset::CombinatorialToken(position_id); + T::MultiCurrency::withdraw(position, &who, amount)?; + } else { + // Split collateral into first level position. Store the collateral in the + // pallet account. This is the legacy `buy_complete_set`. + T::MultiCurrency::transfer( + collateral_token, + &who, + &Self::account_id(), + amount, + )?; + } + } else { + // Horizontal split. + let remaining_index_set = free_index_set.into_iter().map(|i| !i).collect(); + let position = Self::position_from_collection( + parent_collection_id, + market_id, + remaining_index_set, + )?; + T::MultiCurrency::withdraw(position, &who, amount)?; + } + + // Deposit the new tokens. + let position_ids = partition + .iter() + .cloned() + .map(|index_set| { + Self::position_from_collection(parent_collection_id, market_id, index_set) + }) + .collect::, _>>()?; + for &position in position_ids.iter() { + T::MultiCurrency::deposit(position, &who, amount)?; + } + Ok(()) } #[require_transactional] - fn do_merge_position() -> DispatchResult { + fn do_merge_position( + who: AccountIdOf, + parent_collection_id: Option>, + market_id: MarketIdOf, + partition: Vec>, + amount: BalanceOf, + ) -> DispatchResult { + let market = T::MarketCommons::market(&market_id)?; + let collateral_token = market.base_asset; + + let free_index_set = Self::free_index_set(parent_collection_id, market_id, &partition)?; + + // Destory the old tokens. + let position_ids = partition + .iter() + .cloned() + .map(|index_set| { + Self::position_from_collection(parent_collection_id, market_id, index_set) + }) + .collect::, _>>()?; + for &position in position_ids.iter() { + T::MultiCurrency::withdraw(position, &who, amount)?; + } + + // Destroy/store the tokens to be split. + if free_index_set.iter().any(|&i| i) { + // Vertical merge. + if let Some(pci) = parent_collection_id { + // Merge combinatorial token into higher level position. Destroy the tokens. + let position_id = + T::CombinatorialIdManager::get_position_id(collateral_token, pci); + let position = Asset::CombinatorialToken(position_id); + T::MultiCurrency::deposit(position, &who, amount)?; + } else { + // Merge first-level tokens into collateral. Move collateral from the pallet + // account to the user's wallet. This is the legacy `sell_complete_set`. + T::MultiCurrency::transfer( + collateral_token, + &Self::account_id(), + &who, + amount, + )?; + } + } else { + // Horizontal merge. + let remaining_index_set = free_index_set.into_iter().map(|i| !i).collect(); + let position = Self::position_from_collection( + parent_collection_id, + market_id, + remaining_index_set, + )?; + T::MultiCurrency::deposit(position, &who, amount)?; + } + Ok(()) } + + fn free_index_set( + parent_collection_id: Option>, + market_id: MarketIdOf, + partition: &Vec>, + ) -> Result, DispatchError> { + let market = T::MarketCommons::market(&market_id)?; + let asset_count = market.outcomes() as usize; + let mut free_index_set = vec![true; asset_count]; + + for index_set in partition.iter() { + // Ensure that the partition is not trivial. + let ones = index_set.iter().fold(0usize, |acc, &val| acc + (val as usize)); + ensure!(ones > 0, Error::::InvalidPartition); + ensure!(ones < asset_count, Error::::InvalidPartition); + + // Ensure that `index_set` is disjoint from the previously iterated elements of the + // partition. + ensure!( + free_index_set.iter().zip(index_set.iter()).all(|(i, j)| *i || !*j), + Error::::InvalidPartition + ); + + // Remove indices of `index_set` from `free_index_set`. + free_index_set = + free_index_set.iter().zip(index_set.iter()).map(|(i, j)| *i && !*j).collect(); + } + + Ok(free_index_set) + } + + fn position_from_collection( + parent_collection_id: Option>, + market_id: MarketIdOf, + index_set: Vec, + ) -> Result, DispatchError> { + let market = T::MarketCommons::market(&market_id)?; + let collateral_token = market.base_asset; + + let collection_id = T::CombinatorialIdManager::get_collection_id( + parent_collection_id, + market_id, + index_set, + false, // TODO Expose this parameter! + ) + .ok_or(Error::::InvalidCollectionId)?; + + let position_id = + T::CombinatorialIdManager::get_position_id(collateral_token, collection_id); + let asset = Asset::CombinatorialToken(position_id); + + Ok(asset) + } + + fn account_id() -> T::AccountId { + T::PalletId::get().into_account_truncating() + } } } diff --git a/zrml/combo/src/traits/id_manager.rs b/zrml/combo/src/traits/combinatorial_id_manager.rs similarity index 56% rename from zrml/combo/src/traits/id_manager.rs rename to zrml/combo/src/traits/combinatorial_id_manager.rs index 04c83ddc9..d74766668 100644 --- a/zrml/combo/src/traits/id_manager.rs +++ b/zrml/combo/src/traits/combinatorial_id_manager.rs @@ -1,20 +1,20 @@ use sp_runtime::DispatchError; -pub(crate) trait IdManager { +pub(crate) trait CombinatorialIdManager { type Asset; type MarketId; - type Id; + type CombinatorialId; // TODO Replace `Vec` with a more effective bit mask type. fn get_collection_id( - parent_collection_id: Option, + parent_collection_id: Option, market_id: Self::MarketId, index_set: Vec, force_max_work: bool, - ) -> Option; + ) -> Option; fn get_position_id( collateral: Self::Asset, - collection_id: Self::Id, - ) -> Option; + collection_id: Self::CombinatorialId, + ) -> Self::CombinatorialId; } diff --git a/zrml/combo/src/traits/mod.rs b/zrml/combo/src/traits/mod.rs index 2685f5d14..ff38688d4 100644 --- a/zrml/combo/src/traits/mod.rs +++ b/zrml/combo/src/traits/mod.rs @@ -1,3 +1,3 @@ -mod id_manager; +mod combinatorial_id_manager; -pub(crate) use id_manager::IdManager; +pub(crate) use combinatorial_id_manager::CombinatorialIdManager; diff --git a/zrml/combo/src/types/cryptographic_id_manager/decompressor/mod.rs b/zrml/combo/src/types/cryptographic_id_manager/decompressor/mod.rs index f7d46f50d..11e3f5ba4 100644 --- a/zrml/combo/src/types/cryptographic_id_manager/decompressor/mod.rs +++ b/zrml/combo/src/types/cryptographic_id_manager/decompressor/mod.rs @@ -1,7 +1,7 @@ /// Highest/lowest bit always refers to the big endian representation of each bit sequence. mod tests; -use super::typedefs::Hash; +use zeitgeist_primitives::types::CombinatorialId; use core::num::ParseIntError; use ethnum::U256; use halo2curves::{ @@ -12,10 +12,10 @@ use halo2curves::{ /// Will return `None` if and only if `parent_collection_id` is not a valid collection ID. pub(crate) fn get_collection_id( - hash: Hash, - parent_collection_id: Option, + hash: CombinatorialId, + parent_collection_id: Option, force_max_work: bool, -) -> Option { +) -> Option { let mut u = decompress_hash(hash, force_max_work)?; if let Some(pci) = parent_collection_id { @@ -47,7 +47,7 @@ const DECOMPRESS_HASH_MAX_ITERS: usize = 1_000; /// will use `1_000` iterations as maximum for now. /// /// Provided the assumption above is correct, this function cannot return `None`. -fn decompress_hash(hash: Hash, force_max_work: bool) -> Option { +fn decompress_hash(hash: CombinatorialId, force_max_work: bool) -> Option { // Calculate `odd` first, then get congruent point `x` in `Fq`. As `hash` might represent a // larger big endian number than `field_modulus()`, the MSB of `x` might be different from the // MSB of `x_u256`. @@ -89,8 +89,8 @@ fn decompress_hash(hash: Hash, force_max_work: bool) -> Option { } } } - std::mem::forget(dummy_x); // Ensure that the dummies are considered "read" by rustc. - std::mem::forget(dummy_y); + std::hint::black_box(dummy_x); // Ensure that the dummies are considered "read" by rustc. + std::hint::black_box(dummy_y); let mut y = y_opt?; // This **should** be infallible. // We have two options for the y-coordinate of the corresponding point: `y` and `P - y`. If @@ -102,7 +102,7 @@ fn decompress_hash(hash: Hash, force_max_work: bool) -> Option { G1Affine::from_xy(x, y).into() } -fn decompress_collection_id(mut collection_id: Hash) -> Option { +fn decompress_collection_id(mut collection_id: CombinatorialId) -> Option { let odd = is_second_msb_set(&collection_id); chop_off_two_highest_bits(&mut collection_id); collection_id.reverse(); // Big-endian to little-endian. @@ -129,22 +129,22 @@ fn field_modulus() -> U256 { } /// Flips the second highests bit of big-endian `bytes`. -fn flip_second_highest_bit(bytes: &mut Hash) { +fn flip_second_highest_bit(bytes: &mut CombinatorialId) { bytes[0] ^= 0b01000000; } /// Checks if the most significant bit of the big-endian `bytes` is set. -fn is_msb_set(bytes: &Hash) -> bool { +fn is_msb_set(bytes: &CombinatorialId) -> bool { (bytes[0] & 0b10000000) != 0 } /// Checks if the second most significant bit of the big-endian `bytes` is set. -fn is_second_msb_set(bytes: &Hash) -> bool { +fn is_second_msb_set(bytes: &CombinatorialId) -> bool { (bytes[0] & 0b01000000) != 0 } /// Zeroes out the two most significant bits off the big-endian `bytes`. -fn chop_off_two_highest_bits(bytes: &mut Hash) { +fn chop_off_two_highest_bits(bytes: &mut CombinatorialId) { bytes[0] &= 0b00111111; } diff --git a/zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/decompress_collection_id.rs b/zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/decompress_collection_id.rs index 2308e8d2b..2bdd239f9 100644 --- a/zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/decompress_collection_id.rs +++ b/zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/decompress_collection_id.rs @@ -390,7 +390,7 @@ use test_case::test_case; [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01], ("0x1", "0x2") )] -fn decompress_collection_id_works(collection_id: Hash, expected: (&str, &str)) { +fn decompress_collection_id_works(collection_id: CombinatorialId, expected: (&str, &str)) { let x = Fq::from_str_prefixed(expected.0).unwrap(); let y = Fq::from_str_prefixed(expected.1).unwrap(); let expected = G1Affine::from_xy(x, y).unwrap(); @@ -555,7 +555,7 @@ fn decompress_collection_id_works(collection_id: Hash, expected: (&str, &str)) { #[test_case( [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe] )] -fn decompress_collection_id_fails_on_invalid_collection_id(collection_id: Hash) { +fn decompress_collection_id_fails_on_invalid_collection_id(collection_id: CombinatorialId) { let actual = decompress_collection_id(collection_id); assert_eq!(actual, None); } diff --git a/zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/decompress_hash.rs b/zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/decompress_hash.rs index 1ba11da48..49fc4eeed 100644 --- a/zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/decompress_hash.rs +++ b/zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/decompress_hash.rs @@ -745,7 +745,7 @@ use rstest::rstest; ) )] fn decompress_hash_works( - #[case] hash: Hash, + #[case] hash: CombinatorialId, #[values(false, true)] force_max_work: bool, #[case] expected: (&str, &str), ) { diff --git a/zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/get_collection_id.rs b/zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/get_collection_id.rs index 7f4a2f03c..6c214ec97 100644 --- a/zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/get_collection_id.rs +++ b/zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/get_collection_id.rs @@ -64,10 +64,10 @@ use rstest::rstest; ]) )] fn get_collection_id_works( - #[case] hash: Hash, - #[case] parent_collection_id: Option, + #[case] hash: CombinatorialId, + #[case] parent_collection_id: Option, #[values(false, true)] force_max_work: bool, - #[case] expected: Option, + #[case] expected: Option, ) { assert_eq!(get_collection_id(hash, parent_collection_id, force_max_work), expected); } diff --git a/zrml/combo/src/types/cryptographic_id_manager/hash_tuple.rs b/zrml/combo/src/types/cryptographic_id_manager/hash_tuple.rs index 94cc20648..955f56f96 100644 --- a/zrml/combo/src/types/cryptographic_id_manager/hash_tuple.rs +++ b/zrml/combo/src/types/cryptographic_id_manager/hash_tuple.rs @@ -1,4 +1,4 @@ -use super::typedefs::Hash; +use crate::types::Hash256; use frame_support::{Blake2_256, StorageHasher}; use parity_scale_codec::Encode; use zeitgeist_primitives::types::Asset; @@ -8,14 +8,14 @@ pub trait ToBytes { } pub trait HashTuple { - fn hash_tuple(tuple: (T1, T2)) -> Hash + fn hash_tuple(tuple: (T1, T2)) -> Hash256 where T1: ToBytes, T2: ToBytes; } impl HashTuple for Blake2_256 { - fn hash_tuple(tuple: (T1, T2)) -> Hash + fn hash_tuple(tuple: (T1, T2)) -> Hash256 where T1: ToBytes, T2: ToBytes, @@ -50,7 +50,7 @@ impl ToBytes for bool { } } -impl ToBytes for Hash { +impl ToBytes for Hash256 { fn to_bytes(&self) -> Vec { self.to_vec() } diff --git a/zrml/combo/src/types/cryptographic_id_manager/mod.rs b/zrml/combo/src/types/cryptographic_id_manager/mod.rs index 1f1e5facf..eb5f25cc7 100644 --- a/zrml/combo/src/types/cryptographic_id_manager/mod.rs +++ b/zrml/combo/src/types/cryptographic_id_manager/mod.rs @@ -1,41 +1,42 @@ mod decompressor; mod hash_tuple; -mod typedefs; -use crate::traits::IdManager; +use crate::traits::CombinatorialIdManager; use core::marker::PhantomData; use ethnum::U256; use frame_support::{Blake2_256, StorageHasher}; use hash_tuple::{HashTuple, ToBytes}; use parity_scale_codec::Encode; use sp_runtime::DispatchError; -use typedefs::Hash; -use zeitgeist_primitives::types::Asset; +use zeitgeist_primitives::types::{Asset, CombinatorialId}; pub(crate) struct CryptographicIdManager(PhantomData<(MarketId, Hasher)>); -impl IdManager for CryptographicIdManager +impl CombinatorialIdManager for CryptographicIdManager where MarketId: ToBytes + Encode, - Hasher: HashTuple + Hasher: HashTuple, { type Asset = Asset; - type Id = Hash; + type CombinatorialId = CombinatorialId; type MarketId = MarketId; fn get_collection_id( - parent_collection_id: Option, + parent_collection_id: Option, market_id: Self::MarketId, index_set: Vec, force_max_work: bool, - ) -> Option { + ) -> Option { let input = (market_id, index_set); let hash = Hasher::hash_tuple(input); decompressor::get_collection_id(hash, parent_collection_id, force_max_work) } - fn get_position_id(collateral: Self::Asset, collection_id: Self::Id) -> Option { + fn get_position_id( + collateral: Self::Asset, + collection_id: Self::CombinatorialId, + ) -> Self::CombinatorialId { let input = (collateral, collection_id); - Some(Hasher::hash_tuple(input)) + Hasher::hash_tuple(input) } } diff --git a/zrml/combo/src/types/cryptographic_id_manager/typedefs.rs b/zrml/combo/src/types/cryptographic_id_manager/typedefs.rs deleted file mode 100644 index 224b450a9..000000000 --- a/zrml/combo/src/types/cryptographic_id_manager/typedefs.rs +++ /dev/null @@ -1 +0,0 @@ -pub type Hash = [u8; 32]; diff --git a/zrml/combo/src/types/hash.rs b/zrml/combo/src/types/hash.rs new file mode 100644 index 000000000..bc57e98ec --- /dev/null +++ b/zrml/combo/src/types/hash.rs @@ -0,0 +1 @@ +pub type Hash256 = [u8; 32]; diff --git a/zrml/combo/src/types/mod.rs b/zrml/combo/src/types/mod.rs index 3dad26231..842c44ddd 100644 --- a/zrml/combo/src/types/mod.rs +++ b/zrml/combo/src/types/mod.rs @@ -1,3 +1,5 @@ -mod cryptographic_id_manager; +pub(crate) mod cryptographic_id_manager; +pub(crate) mod hash; pub(crate) use cryptographic_id_manager::CryptographicIdManager; +pub(crate) use hash::Hash256; From fba1a07a37b2ad88d6b646d54ee624bc51af5f66 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Sun, 6 Oct 2024 10:40:46 +0200 Subject: [PATCH 07/73] Replace `halo2curves` and `ethnum` dependency and fix clippy issues (#1370) * . * Use `core::hint` * Replace `halo2curves` dependency with `ark-*` dependency * Fix clippy issues * Fix formatting --- Cargo.lock | 106 +--- Cargo.toml | 10 +- primitives/src/asset.rs | 2 +- primitives/src/constants.rs | 3 + runtime/battery-station/Cargo.toml | 8 +- runtime/battery-station/src/parameters.rs | 5 +- runtime/common/src/lib.rs | 13 +- runtime/zeitgeist/Cargo.toml | 8 +- runtime/zeitgeist/src/parameters.rs | 5 +- .../Cargo.toml | 10 +- .../{combo => combinatorial-tokens}/README.md | 0 .../src/lib.rs | 24 +- .../src/traits/combinatorial_id_manager.rs | 4 +- zrml/combinatorial-tokens/src/traits/mod.rs | 3 + .../decompressor/mod.rs | 553 +++++++++++++++++ .../tests/decompress_collection_id.rs | 6 +- .../decompressor/tests/decompress_hash.rs | 8 +- .../decompressor/tests/get_collection_id.rs | 0 .../tests/matching_y_coordinate.rs | 9 +- .../decompressor/tests/mod.rs | 33 + .../decompressor/tests/pow_magic_number.rs | 4 +- .../cryptographic_id_manager/hash_tuple.rs | 1 + .../src/types/cryptographic_id_manager/mod.rs | 6 +- .../src/types/hash.rs | 0 .../src/types/mod.rs | 2 +- zrml/combo/src/traits/mod.rs | 3 - .../decompressor/mod.rs | 582 ------------------ .../decompressor/tests/field_modulus.rs | 10 - .../decompressor/tests/mod.rs | 34 - 29 files changed, 682 insertions(+), 770 deletions(-) rename zrml/{combo => combinatorial-tokens}/Cargo.toml (83%) rename zrml/{combo => combinatorial-tokens}/README.md (100%) rename zrml/{combo => combinatorial-tokens}/src/lib.rs (95%) rename zrml/{combo => combinatorial-tokens}/src/traits/combinatorial_id_manager.rs (87%) create mode 100644 zrml/combinatorial-tokens/src/traits/mod.rs create mode 100644 zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/mod.rs rename zrml/{combo => combinatorial-tokens}/src/types/cryptographic_id_manager/decompressor/tests/decompress_collection_id.rs (99%) rename zrml/{combo => combinatorial-tokens}/src/types/cryptographic_id_manager/decompressor/tests/decompress_hash.rs (99%) rename zrml/{combo => combinatorial-tokens}/src/types/cryptographic_id_manager/decompressor/tests/get_collection_id.rs (100%) rename zrml/{combo => combinatorial-tokens}/src/types/cryptographic_id_manager/decompressor/tests/matching_y_coordinate.rs (98%) create mode 100644 zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/mod.rs rename zrml/{combo => combinatorial-tokens}/src/types/cryptographic_id_manager/decompressor/tests/pow_magic_number.rs (99%) rename zrml/{combo => combinatorial-tokens}/src/types/cryptographic_id_manager/hash_tuple.rs (99%) rename zrml/{combo => combinatorial-tokens}/src/types/cryptographic_id_manager/mod.rs (85%) rename zrml/{combo => combinatorial-tokens}/src/types/hash.rs (100%) rename zrml/{combo => combinatorial-tokens}/src/types/mod.rs (58%) delete mode 100644 zrml/combo/src/traits/mod.rs delete mode 100644 zrml/combo/src/types/cryptographic_id_manager/decompressor/mod.rs delete mode 100644 zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/field_modulus.rs delete mode 100644 zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 77640a1b6..f39fb7ece 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -238,6 +238,17 @@ dependencies = [ "ark-std", ] +[[package]] +name = "ark-bn254" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a22f4561524cd949590d78d7d4c5df8f592430d221f7f3c9497bbafd8972120f" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-std", +] + [[package]] name = "ark-ec" version = "0.4.2" @@ -881,7 +892,7 @@ dependencies = [ "xcm-emulator", "zeitgeist-primitives", "zrml-authorized", - "zrml-combo", + "zrml-combinatorial-tokens", "zrml-court", "zrml-global-disputes", "zrml-hybrid-router", @@ -2861,12 +2872,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "ethnum" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b90ca2580b73ab6a1f724b76ca11ab632df820fd6040c336200d2c1df7b3c82c" - [[package]] name = "event-listener" version = "2.5.3" @@ -3040,7 +3045,6 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ - "bitvec", "rand_core 0.6.4", "subtle", ] @@ -3799,47 +3803,6 @@ version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403" -[[package]] -name = "halo2curves" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d380afeef3f1d4d3245b76895172018cfb087d9976a7cabcd5597775b2933e07" -dependencies = [ - "blake2", - "digest 0.10.7", - "ff", - "group", - "halo2derive", - "lazy_static", - "num-bigint", - "num-integer", - "num-traits", - "pairing", - "pasta_curves", - "paste", - "rand 0.8.5", - "rand_core 0.6.4", - "rayon", - "sha2 0.10.8", - "static_assertions", - "subtle", - "unroll", -] - -[[package]] -name = "halo2derive" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdb99e7492b4f5ff469d238db464131b86c2eaac814a78715acba369f64d2c76" -dependencies = [ - "num-bigint", - "num-integer", - "num-traits", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "handlebars" version = "4.5.0" @@ -4628,9 +4591,6 @@ name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" -dependencies = [ - "spin 0.5.2", -] [[package]] name = "lazycell" @@ -6161,15 +6121,6 @@ dependencies = [ "staging-xcm-executor", ] -[[package]] -name = "pairing" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fec4625e73cf41ef4bb6846cafa6d44736525f442ba45e407c4a000a13996f" -dependencies = [ - "group", -] - [[package]] name = "pallet-asset-tx-payment" version = "4.0.0-dev" @@ -7531,21 +7482,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7924d1d0ad836f665c9065e26d016c673ece3993f30d340068b16f282afc1156" -[[package]] -name = "pasta_curves" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e57598f73cc7e1b2ac63c79c517b31a0877cd7c402cdcaa311b5208de7a095" -dependencies = [ - "blake2b_simd", - "ff", - "group", - "lazy_static", - "rand 0.8.5", - "static_assertions", - "subtle", -] - [[package]] name = "paste" version = "1.0.14" @@ -13887,16 +13823,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "unroll" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ad948c1cb799b1a70f836077721a92a35ac177d4daddf4c20a633786d4cf618" -dependencies = [ - "quote", - "syn 1.0.109", -] - [[package]] name = "unsigned-varint" version = "0.7.2" @@ -15189,7 +15115,7 @@ dependencies = [ "xcm-emulator", "zeitgeist-primitives", "zrml-authorized", - "zrml-combo", + "zrml-combinatorial-tokens", "zrml-court", "zrml-global-disputes", "zrml-hybrid-router", @@ -15262,14 +15188,14 @@ dependencies = [ ] [[package]] -name = "zrml-combo" +name = "zrml-combinatorial-tokens" version = "0.5.5" dependencies = [ - "ethnum", + "ark-bn254", + "ark-ff", "frame-benchmarking", "frame-support", "frame-system", - "halo2curves", "orml-traits", "parity-scale-codec", "rstest", diff --git a/Cargo.toml b/Cargo.toml index a746467dc..f9401cf22 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ default-members = [ "runtime/battery-station", "runtime/zeitgeist", "zrml/authorized", - "zrml/combo", + "zrml/combinatorial-tokens", "zrml/court", "zrml/hybrid-router", "zrml/global-disputes", @@ -37,7 +37,7 @@ members = [ "runtime/battery-station", "runtime/zeitgeist", "zrml/authorized", - "zrml/combo", + "zrml/combinatorial-tokens", "zrml/court", "zrml/hybrid-router", "zrml/global-disputes", @@ -246,7 +246,7 @@ common-runtime = { path = "runtime/common", default-features = false } zeitgeist-macros = { path = "macros", default-features = false } zeitgeist-primitives = { path = "primitives", default-features = false } zrml-authorized = { path = "zrml/authorized", default-features = false } -zrml-combo = { path = "zrml/combo", default-features = false } +zrml-combinatorial-tokens = { path = "zrml/combinatorial-tokens", default-features = false } zrml-court = { path = "zrml/court", default-features = false } zrml-global-disputes = { path = "zrml/global-disputes", default-features = false } zrml-hybrid-router = { path = "zrml/hybrid-router", default-features = false } @@ -274,9 +274,9 @@ url = "2.5.0" # Other (wasm) arbitrary = { version = "1.3.2", default-features = false } arrayvec = { version = "0.7.4", default-features = false } -halo2curves = { version = "0.7.0" } +ark-bn254 = { version = "0.4.0", default-features = false, features = ["curve"] } +ark-ff = { version = "0.4.0", default-features = false } cfg-if = { version = "1.0.0" } -ethnum = { version = "1.4.0" } fixed = { version = "=1.15.0", default-features = false, features = ["num-traits"] } # Hashbrown works in no_std by default and default features are used in Rikiddo hashbrown = { version = "0.14.3", default-features = true } diff --git a/primitives/src/asset.rs b/primitives/src/asset.rs index 2730a7a84..dbd22367d 100644 --- a/primitives/src/asset.rs +++ b/primitives/src/asset.rs @@ -20,7 +20,7 @@ use crate::traits::ZeitgeistAssetEnumerator; use crate::{ traits::PoolSharesId, - types::{CategoryIndex, PoolId, CombinatorialId}, + types::{CategoryIndex, CombinatorialId, PoolId}, }; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; diff --git a/primitives/src/constants.rs b/primitives/src/constants.rs index bdfc39226..3d831286e 100644 --- a/primitives/src/constants.rs +++ b/primitives/src/constants.rs @@ -70,6 +70,9 @@ parameter_types! { /// Pallet identifier, mainly used for named balance reserves. pub const AUTHORIZED_PALLET_ID: PalletId = PalletId(*b"zge/atzd"); +// Combinatorial Tokens +pub const COMBINATORIAL_TOKENS_PALLET_ID: PalletId = PalletId(*b"zge/coto"); + // Court /// Pallet identifier, mainly used for named balance reserves. pub const COURT_PALLET_ID: PalletId = PalletId(*b"zge/cout"); diff --git a/runtime/battery-station/Cargo.toml b/runtime/battery-station/Cargo.toml index 035b69562..01359557e 100644 --- a/runtime/battery-station/Cargo.toml +++ b/runtime/battery-station/Cargo.toml @@ -109,7 +109,7 @@ xcm-executor = { workspace = true, optional = true } common-runtime = { workspace = true } zeitgeist-primitives = { workspace = true } zrml-authorized = { workspace = true } -zrml-combo = { workspace = true } +zrml-combinatorial-tokens = { workspace = true } zrml-court = { workspace = true } zrml-global-disputes = { workspace = true, optional = true } zrml-hybrid-router = { workspace = true } @@ -215,7 +215,7 @@ runtime-benchmarks = [ "sp-runtime/runtime-benchmarks", "xcm-builder?/runtime-benchmarks", "zrml-authorized/runtime-benchmarks", - "zrml-combo/runtime-benchmarks", + "zrml-combinatorial-tokens/runtime-benchmarks", "zrml-court/runtime-benchmarks", "zrml-hybrid-router/runtime-benchmarks", "zrml-neo-swaps/runtime-benchmarks", @@ -329,7 +329,7 @@ std = [ "zeitgeist-primitives/std", "zrml-authorized/std", - "zrml-combo/std", + "zrml-combinatorial-tokens/std", "zrml-court/std", "zrml-hybrid-router/std", "zrml-market-commons/std", @@ -384,7 +384,7 @@ try-runtime = [ # Zeitgeist runtime pallets "zrml-authorized/try-runtime", - "zrml-combo/try-runtime", + "zrml-combinatorial-tokens/try-runtime", "zrml-court/try-runtime", "zrml-hybrid-router/try-runtime", "zrml-market-commons/try-runtime", diff --git a/runtime/battery-station/src/parameters.rs b/runtime/battery-station/src/parameters.rs index 076941ac8..92ad20e27 100644 --- a/runtime/battery-station/src/parameters.rs +++ b/runtime/battery-station/src/parameters.rs @@ -89,6 +89,9 @@ parameter_types! { pub const TechnicalCommitteeMaxProposals: u32 = 64; pub const TechnicalCommitteeMotionDuration: BlockNumber = 7 * BLOCKS_PER_DAY; + // CombinatorialTokens + pub const CombinatorialTokensPalletId: PalletId = COMBINATORIAL_TOKENS_PALLET_ID; + // Contracts pub const ContractsCodeHashLockupDepositPercent: Perbill = Perbill::from_percent(10); pub const ContractsDefaultDepositLimit: Balance = deposit(16, 16 * 1024 * 1024); @@ -448,7 +451,7 @@ parameter_type_with_key! { pub ExistentialDeposits: |currency_id: CurrencyId| -> Balance { match currency_id { Asset::CategoricalOutcome(_,_) => ExistentialDeposit::get(), - Asset::CombinatorialOutcome => ExistentialDeposit::get(), + Asset::CombinatorialToken(_) => ExistentialDeposit::get(), Asset::PoolShare(_) => ExistentialDeposit::get(), Asset::ScalarOutcome(_,_) => ExistentialDeposit::get(), #[cfg(feature = "parachain")] diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs index 083821a64..e47db9fe5 100644 --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -72,7 +72,7 @@ macro_rules! decl_common_types { parameter_types, storage::child, traits::{Currency, Get, Imbalance, NeverEnsureOrigin, OnRuntimeUpgrade, OnUnbalanced}, - BoundedVec, Twox64Concat, + Blake2_256, BoundedVec, Twox64Concat, }; use frame_system::EnsureSigned; #[cfg(feature = "try-runtime")] @@ -86,6 +86,7 @@ macro_rules! decl_common_types { generic, DispatchError, DispatchResult, RuntimeDebug, SaturatedConversion, }; use zeitgeist_primitives::traits::{DeployPoolApi, DistributeFees, MarketCommonsPalletApi}; + use zrml_combinatorial_tokens::types::CryptographicIdManager; pub type Block = generic::Block; @@ -358,7 +359,7 @@ macro_rules! create_runtime { Orderbook: zrml_orderbook::{Call, Event, Pallet, Storage} = 61, Parimutuel: zrml_parimutuel::{Call, Event, Pallet, Storage} = 62, HybridRouter: zrml_hybrid_router::{Call, Event, Pallet, Storage} = 64, - Combo: zrml_combo::{Pallet, Storage} = 65, + CombinatorialTokens: zrml_combinatorial_tokens::{Call, Event, Pallet, Storage} = 65, $($additional_pallets)* } @@ -1169,7 +1170,13 @@ macro_rules! impl_config_traits { type WeightInfo = zrml_authorized::weights::WeightInfo; } - impl zrml_combo::Config for Runtime {} + impl zrml_combinatorial_tokens::Config for Runtime { + type CombinatorialIdManager = CryptographicIdManager; + type MarketCommons = MarketCommons; + type MultiCurrency = AssetManager; + type PalletId = CombinatorialTokensPalletId; + type RuntimeEvent = RuntimeEvent; + } impl zrml_court::Config for Runtime { type AppealBond = AppealBond; diff --git a/runtime/zeitgeist/Cargo.toml b/runtime/zeitgeist/Cargo.toml index 7d7aa5360..1a43e9b51 100644 --- a/runtime/zeitgeist/Cargo.toml +++ b/runtime/zeitgeist/Cargo.toml @@ -108,7 +108,7 @@ xcm-executor = { workspace = true, optional = true } common-runtime = { workspace = true } zeitgeist-primitives = { workspace = true } zrml-authorized = { workspace = true } -zrml-combo = { workspace = true } +zrml-combinatorial-tokens = { workspace = true } zrml-court = { workspace = true } zrml-global-disputes = { workspace = true, optional = true } zrml-hybrid-router = { workspace = true } @@ -212,7 +212,7 @@ runtime-benchmarks = [ "sp-runtime/runtime-benchmarks", "xcm-builder?/runtime-benchmarks", "zrml-authorized/runtime-benchmarks", - "zrml-combo/runtime-benchmarks", + "zrml-combinatorial-tokens/runtime-benchmarks", "zrml-court/runtime-benchmarks", "zrml-hybrid-router/runtime-benchmarks", "zrml-neo-swaps/runtime-benchmarks", @@ -318,7 +318,7 @@ std = [ "zeitgeist-primitives/std", "zrml-authorized/std", - "zrml-combo/std", + "zrml-combinatorial-tokens/std", "zrml-court/std", "zrml-hybrid-router/std", "zrml-market-commons/std", @@ -372,7 +372,7 @@ try-runtime = [ # Zeitgeist runtime pallets "zrml-authorized/try-runtime", - "zrml-combo/try-runtime", + "zrml-combinatorial-tokens/try-runtime", "zrml-court/try-runtime", "zrml-hybrid-router/try-runtime", "zrml-market-commons/try-runtime", diff --git a/runtime/zeitgeist/src/parameters.rs b/runtime/zeitgeist/src/parameters.rs index a9bdfb485..9bf2ba3df 100644 --- a/runtime/zeitgeist/src/parameters.rs +++ b/runtime/zeitgeist/src/parameters.rs @@ -89,6 +89,9 @@ parameter_types! { pub const TechnicalCommitteeMaxProposals: u32 = 64; pub const TechnicalCommitteeMotionDuration: BlockNumber = 7 * BLOCKS_PER_DAY; + // CombinatorialTokens + pub const CombinatorialTokensPalletId: PalletId = COMBINATORIAL_TOKENS_PALLET_ID; + // Contracts pub const ContractsCodeHashLockupDepositPercent: Perbill = Perbill::from_percent(10); pub const ContractsDefaultDepositLimit: Balance = deposit(16, 16 * 1024 * 1024); @@ -448,7 +451,7 @@ parameter_type_with_key! { pub ExistentialDeposits: |currency_id: CurrencyId| -> Balance { match currency_id { Asset::CategoricalOutcome(_,_) => ExistentialDeposit::get(), - Asset::CombinatorialOutcome => ExistentialDeposit::get(), + Asset::CombinatorialToken(_) => ExistentialDeposit::get(), Asset::PoolShare(_) => ExistentialDeposit::get(), Asset::ScalarOutcome(_,_) => ExistentialDeposit::get(), #[cfg(feature = "parachain")] diff --git a/zrml/combo/Cargo.toml b/zrml/combinatorial-tokens/Cargo.toml similarity index 83% rename from zrml/combo/Cargo.toml rename to zrml/combinatorial-tokens/Cargo.toml index f7e005123..c89e6f8ac 100644 --- a/zrml/combo/Cargo.toml +++ b/zrml/combinatorial-tokens/Cargo.toml @@ -1,9 +1,9 @@ [dependencies] -ethnum = { workspace = true } +ark-bn254 = { workspace = true } +ark-ff = { workspace = true } frame-benchmarking = { workspace = true, optional = true } frame-support = { workspace = true } frame-system = { workspace = true } -halo2curves = { workspace = true } orml-traits = { workspace = true } parity-scale-codec = { workspace = true, features = ["derive", "max-encoded-len"] } scale-info = { workspace = true, features = ["derive"] } @@ -25,6 +25,10 @@ std = [ "frame-benchmarking?/std", "frame-support/std", "frame-system/std", + "ark-bn254/std", + "ark-ff/std", + "orml-traits/std", + "parity-scale-codec/std", "sp-runtime/std", "zeitgeist-primitives/std", ] @@ -35,5 +39,5 @@ try-runtime = [ [package] authors = ["Zeitgeist PM "] edition.workspace = true -name = "zrml-combo" +name = "zrml-combinatorial-tokens" version = "0.5.5" diff --git a/zrml/combo/README.md b/zrml/combinatorial-tokens/README.md similarity index 100% rename from zrml/combo/README.md rename to zrml/combinatorial-tokens/README.md diff --git a/zrml/combo/src/lib.rs b/zrml/combinatorial-tokens/src/lib.rs similarity index 95% rename from zrml/combo/src/lib.rs rename to zrml/combinatorial-tokens/src/lib.rs index 0ac8350f6..0dfbd4999 100644 --- a/zrml/combo/src/lib.rs +++ b/zrml/combinatorial-tokens/src/lib.rs @@ -23,13 +23,14 @@ extern crate alloc; mod traits; -mod types; +pub mod types; pub use pallet::*; #[frame_support::pallet] mod pallet { use crate::traits::CombinatorialIdManager; + use alloc::{vec, vec::Vec}; use core::marker::PhantomData; use frame_support::{ ensure, @@ -90,7 +91,11 @@ mod pallet { #[pallet::generate_deposit(fn deposit_event)] pub enum Event where - T: Config, {} + T: Config, + { + TokenSplit, + TokenMerged, + } #[pallet::error] pub enum Error { @@ -104,7 +109,7 @@ mod pallet { #[pallet::call] impl Pallet { #[pallet::call_index(0)] - #[pallet::weight(0)] // TODO + #[pallet::weight({0})] // TODO #[transactional] pub fn split_position( origin: OriginFor, @@ -119,7 +124,7 @@ mod pallet { } #[pallet::call_index(1)] - #[pallet::weight(0)] // TODO + #[pallet::weight({0})] // TODO #[transactional] pub fn merge_position( origin: OriginFor, @@ -145,7 +150,7 @@ mod pallet { let market = T::MarketCommons::market(&market_id)?; let collateral_token = market.base_asset; - let free_index_set = Self::free_index_set(parent_collection_id, market_id, &partition)?; + let free_index_set = Self::free_index_set(market_id, &partition)?; // Destroy/store the tokens to be split. if free_index_set.iter().any(|&i| i) { @@ -189,6 +194,8 @@ mod pallet { T::MultiCurrency::deposit(position, &who, amount)?; } + Self::deposit_event(Event::::TokenSplit); + Ok(()) } @@ -203,7 +210,7 @@ mod pallet { let market = T::MarketCommons::market(&market_id)?; let collateral_token = market.base_asset; - let free_index_set = Self::free_index_set(parent_collection_id, market_id, &partition)?; + let free_index_set = Self::free_index_set(market_id, &partition)?; // Destory the old tokens. let position_ids = partition @@ -247,13 +254,14 @@ mod pallet { T::MultiCurrency::deposit(position, &who, amount)?; } + Self::deposit_event(Event::::TokenMerged); + Ok(()) } fn free_index_set( - parent_collection_id: Option>, market_id: MarketIdOf, - partition: &Vec>, + partition: &[Vec], ) -> Result, DispatchError> { let market = T::MarketCommons::market(&market_id)?; let asset_count = market.outcomes() as usize; diff --git a/zrml/combo/src/traits/combinatorial_id_manager.rs b/zrml/combinatorial-tokens/src/traits/combinatorial_id_manager.rs similarity index 87% rename from zrml/combo/src/traits/combinatorial_id_manager.rs rename to zrml/combinatorial-tokens/src/traits/combinatorial_id_manager.rs index d74766668..847c91fbc 100644 --- a/zrml/combo/src/traits/combinatorial_id_manager.rs +++ b/zrml/combinatorial-tokens/src/traits/combinatorial_id_manager.rs @@ -1,6 +1,6 @@ -use sp_runtime::DispatchError; +use alloc::vec::Vec; -pub(crate) trait CombinatorialIdManager { +pub trait CombinatorialIdManager { type Asset; type MarketId; type CombinatorialId; diff --git a/zrml/combinatorial-tokens/src/traits/mod.rs b/zrml/combinatorial-tokens/src/traits/mod.rs new file mode 100644 index 000000000..98642f028 --- /dev/null +++ b/zrml/combinatorial-tokens/src/traits/mod.rs @@ -0,0 +1,3 @@ +mod combinatorial_id_manager; + +pub use combinatorial_id_manager::CombinatorialIdManager; diff --git a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/mod.rs b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/mod.rs new file mode 100644 index 000000000..6495589c3 --- /dev/null +++ b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/mod.rs @@ -0,0 +1,553 @@ +/// Highest/lowest bit always refers to the big endian representation of each bit sequence. +mod tests; + +use ark_bn254::{g1::G1Affine, Fq}; +use ark_ff::{BigInteger, PrimeField}; +use core::ops::Neg; +use sp_runtime::traits::{One, Zero}; +use zeitgeist_primitives::types::CombinatorialId; + +/// Will return `None` if and only if `parent_collection_id` is not a valid collection ID. +pub(crate) fn get_collection_id( + hash: CombinatorialId, + parent_collection_id: Option, + force_max_work: bool, +) -> Option { + let mut u = decompress_hash(hash, force_max_work)?; + + if let Some(pci) = parent_collection_id { + let v = decompress_collection_id(pci)?; + let w = u + v; // Projective coordinates. + u = w.into(); // Affine coordinates. + } + + // Convert back to bytes _before_ flipping, as flipping will sometimes result in numbers larger + // than the base field modulus. + let mut bytes: CombinatorialId = u.x.into_bigint().to_bytes_be().try_into().ok()?; + + if u.y.into_bigint().is_odd() { + flip_second_highest_bit(&mut bytes); + } + + Some(bytes) +} + +const DECOMPRESS_HASH_MAX_ITERS: usize = 1_000; + +/// Decompresses a collection ID `hash` to a point of `alt_bn128`. The amount of work done can be +/// forced to be independent of the input by setting the `force_max_work` flag. +/// +/// We don't have mathematical proof that the points of `alt_bn128` are distributed so that the +/// required number of iterations is below the specified limit of iterations, but there's good +/// evidence that input hash requires more than `log_2(P) = 507.19338271000436` iterations. We +/// will use `1_000` iterations as maximum for now. +/// +/// Provided the assumption above is correct, this function cannot return `None`. +fn decompress_hash(hash: CombinatorialId, force_max_work: bool) -> Option { + // Calculate `odd` first, then get congruent point `x` in `Fq`. As `hash` might represent a + // larger big endian number than `field_modulus()`, the MSB of `x` might be different from the + // MSB of `x_u256`. + let odd = is_msb_set(&hash); + + let mut x = Fq::from_be_bytes_mod_order(&hash); + let mut y_opt = None; + let mut dummy_x = Fq::zero(); // Used to prevent rustc from optimizing dummy work away. + let mut dummy_y = None; + for _ in 0..DECOMPRESS_HASH_MAX_ITERS { + // If `y_opt.is_some()` and we're still in the loop, then `force_max_work` is set and we're + // jus here to spin our wheels for the benchmarks. + if y_opt.is_some() { + // Perform the same calculations as below, but store them in the dummy variables to + // avoid setting off rustc optimizations. + dummy_x = x + Fq::one(); + + let matching_y = matching_y_coordinate(dummy_x); + + if matching_y.is_some() { + dummy_y = matching_y; + } + } else { + x += Fq::one(); + + let matching_y = matching_y_coordinate(x); + + if matching_y.is_some() { + y_opt = matching_y; + + if !force_max_work { + break; + } + } + } + } + core::hint::black_box(dummy_x); // Ensure that the dummies are considered "read" by rustc. + core::hint::black_box(dummy_y); + let mut y = y_opt?; // This **should** be infallible. + + // We have two options for the y-coordinate of the corresponding point: `y` and `P - y`. If + // `odd` is set but `y` isn't odd, we switch to the other option. + if (odd && y.into_bigint().is_even()) || (!odd && y.into_bigint().is_odd()) { + y = y.neg(); + } + + Some(G1Affine::new(x, y)) +} + +fn decompress_collection_id(mut collection_id: CombinatorialId) -> Option { + let odd = is_second_msb_set(&collection_id); + chop_off_two_highest_bits(&mut collection_id); + let x = Fq::from_be_bytes_mod_order(&collection_id); + + // Ensure that the big-endian integer represented by `collection_id` was less than the field + // modulus. Otherwise, we consider `collection_id` an invalid ID. + if x.into_bigint().to_bytes_be() != collection_id { + return None; + } + + let mut y = matching_y_coordinate(x)?; // Fails if `collection_id` is not a collection ID. + + // We have two options for the y-coordinate of the corresponding point: `y` and `P - y`. If + // `odd` is set but `y` isn't odd, we switch to the other option. + if (odd && y.into_bigint().is_even()) || (!odd && y.into_bigint().is_odd()) { + y = y.neg(); + } + + Some(G1Affine::new(x, y)) +} + +/// Flips the second highests bit of big-endian `bytes`. +fn flip_second_highest_bit(bytes: &mut CombinatorialId) { + bytes[0] ^= 0b01000000; +} + +/// Checks if the most significant bit of the big-endian `bytes` is set. +fn is_msb_set(bytes: &CombinatorialId) -> bool { + (bytes[0] & 0b10000000) != 0 +} + +/// Checks if the second most significant bit of the big-endian `bytes` is set. +fn is_second_msb_set(bytes: &CombinatorialId) -> bool { + (bytes[0] & 0b01000000) != 0 +} + +/// Zeroes out the two most significant bits off the big-endian `bytes`. +fn chop_off_two_highest_bits(bytes: &mut CombinatorialId) { + bytes[0] &= 0b00111111; +} + +/// Returns a value `y` of `Fq` so that `(x, y)` is a point on `alt_bn128` or `None` if there is no +/// such value. +fn matching_y_coordinate(x: Fq) -> Option { + let xx = x * x; + let xxx = x * xx; + let yy = xxx + Fq::from(3); + let y = pow_magic_number(yy); + + if y * y == yy { Some(y) } else { None } +} + +/// Returns `x` to the power of `(P + 1) / 4` where `P` is the base field modulus of `alt_bn128`. +fn pow_magic_number(mut x: Fq) -> Fq { + let x_1 = x; + x *= x; + let x_2 = x; + x *= x; + x *= x; + x *= x_2; + let x_10 = x; + x *= x_1; + let x_11 = x; + x *= x_10; + let x_21 = x; + x *= x; + let x_42 = x; + x *= x; + x *= x_42; + x *= x; + x *= x; + x *= x_42; + x *= x_11; + let x_557 = x; + x *= x; + x *= x; + x *= x_21; + let x_2249 = x; + x *= x; + x *= x; + x *= x; + x *= x_2249; + x *= x_557; + let x_20798 = x; + x *= x; + x *= x; + x *= x; + x *= x_20798; + x *= x_2249; + let x_189431 = x; + x *= x_20798; + let x_210229 = x; + x *= x; + x *= x; + x *= x_189431; + let x_1030347 = x; + x *= x; + let x_2060694 = x; + x *= x; + x *= x; + x *= x; + x *= x_2060694; + x *= x_210229; + let x_18756475 = x; + x *= x_1030347; + let x_19786822 = x; + x *= x; + x *= x; + x *= x; + x *= x_18756475; + let x_177051051 = x; + x *= x; + x *= x; + x *= x_177051051; + x *= x; + x *= x; + x *= x_177051051; + x *= x_19786822; + let x_3737858893 = x; + x *= x; + let x_7475717786 = x; + x *= x; + x *= x; + x *= x_7475717786; + x *= x_3737858893; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x_7475717786; + x *= x_177051051; + let x_665515934005 = x; + x *= x; + x *= x_665515934005; + x *= x_3737858893; + let x_2000285660908 = x; + x *= x; + x *= x_2000285660908; + x *= x; + let x_12001713965448 = x; + x *= x; + x *= x_12001713965448; + let x_36005141896344 = x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x_36005141896344; + x *= x_12001713965448; + x *= x_665515934005; + let x_1200836912478805 = x; + x *= x_2000285660908; + let x_1202837198139713 = x; + x *= x; + x *= x_1200836912478805; + let x_3606511308758231 = x; + x *= x_1202837198139713; + let x_4809348506897944 = x; + x *= x_3606511308758231; + let x_8415859815656175 = x; + x *= x_4809348506897944; + let x_13225208322554119 = x; + x *= x_8415859815656175; + let x_21641068138210294 = x; + x *= x; + x *= x_21641068138210294; + x *= x; + x *= x_13225208322554119; + let x_143071617151815883 = x; + x *= x; + x *= x; + x *= x_21641068138210294; + let x_593927536745473826 = x; + x *= x_143071617151815883; + let x_736999153897289709 = x; + x *= x; + x *= x_736999153897289709; + x *= x_593927536745473826; + let x_2804924998437342953 = x; + x *= x_736999153897289709; + let x_3541924152334632662 = x; + x *= x_2804924998437342953; + let x_6346849150771975615 = x; + x *= x_3541924152334632662; + let x_9888773303106608277 = x; + x *= x; + x *= x; + x *= x_9888773303106608277; + x *= x_6346849150771975615; + let x_55790715666305017000 = x; + x *= x; + x *= x_55790715666305017000; + x *= x_9888773303106608277; + let x_177260920302021659277 = x; + x *= x_55790715666305017000; + let x_233051635968326676277 = x; + x *= x_177260920302021659277; + let x_410312556270348335554 = x; + x *= x_233051635968326676277; + let x_643364192238675011831 = x; + x *= x_410312556270348335554; + let x_1053676748509023347385 = x; + x *= x; + x *= x_1053676748509023347385; + x *= x; + x *= x_643364192238675011831; + let x_6965424683292815096141 = x; + x *= x_1053676748509023347385; + let x_8019101431801838443526 = x; + x *= x; + x *= x_8019101431801838443526; + x *= x; + x *= x_6965424683292815096141; + let x_55080033274103845757297 = x; + x *= x; + let x_110160066548207691514594 = x; + x *= x; + x *= x; + x *= x_110160066548207691514594; + x *= x_55080033274103845757297; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x_110160066548207691514594; + x *= x_8019101431801838443526; + let x_9812265024222286383242392 = x; + x *= x_55080033274103845757297; + let x_9867345057496390228999689 = x; + x *= x_9812265024222286383242392; + let x_19679610081718676612242081 = x; + x *= x_9867345057496390228999689; + let x_29546955139215066841241770 = x; + x *= x; + x *= x_29546955139215066841241770; + x *= x; + x *= x; + x *= x; + x *= x_29546955139215066841241770; + x *= x_19679610081718676612242081; + let x_758353488562095347643286331 = x; + x *= x; + x *= x_758353488562095347643286331; + x *= x; + x *= x_29546955139215066841241770; + let x_4579667886511787152700959756 = x; + x *= x; + x *= x_4579667886511787152700959756; + x *= x_758353488562095347643286331; + let x_14497357148097456805746165599 = x; + x *= x_4579667886511787152700959756; + let x_19077025034609243958447125355 = x; + x *= x; + x *= x; + x *= x_14497357148097456805746165599; + let x_90805457286534432639534667019 = x; + x *= x_19077025034609243958447125355; + let x_109882482321143676597981792374 = x; + x *= x; + x *= x_90805457286534432639534667019; + let x_310570421928821785835498251767 = x; + x *= x_109882482321143676597981792374; + let x_420452904249965462433480044141 = x; + x *= x_310570421928821785835498251767; + let x_731023326178787248268978295908 = x; + x *= x; + x *= x_731023326178787248268978295908; + x *= x_420452904249965462433480044141; + let x_2613522882786327207240414931865 = x; + x *= x_731023326178787248268978295908; + let x_3344546208965114455509393227773 = x; + x *= x; + x *= x_3344546208965114455509393227773; + x *= x; + x *= x; + x *= x_2613522882786327207240414931865; + let x_42748077390367700673353133665141 = x; + x *= x; + x *= x; + x *= x; + x *= x_42748077390367700673353133665141; + x *= x_3344546208965114455509393227773; + let x_388077242722274420515687596214042 = x; + x *= x_42748077390367700673353133665141; + let x_430825320112642121189040729879183 = x; + x *= x; + let x_861650640225284242378081459758366 = x; + x *= x_430825320112642121189040729879183; + x *= x; + x *= x; + x *= x_861650640225284242378081459758366; + x *= x_388077242722274420515687596214042; + let x_6419631724299264117162257814522604 = x; + x *= x; + x *= x_430825320112642121189040729879183; + let x_13270088768711170355513556358924391 = x; + x *= x_6419631724299264117162257814522604; + let x_19689720493010434472675814173446995 = x; + x *= x_13270088768711170355513556358924391; + let x_32959809261721604828189370532371386 = x; + x *= x_19689720493010434472675814173446995; + let x_52649529754732039300865184705818381 = x; + x *= x_32959809261721604828189370532371386; + let x_85609339016453644129054555238189767 = x; + x *= x_52649529754732039300865184705818381; + let x_138258868771185683429919739944008148 = x; + x *= x; + x *= x_138258868771185683429919739944008148; + let x_414776606313557050289759219832024444 = x; + x *= x_138258868771185683429919739944008148; + x *= x; + x *= x; + x *= x_414776606313557050289759219832024444; + x *= x_85609339016453644129054555238189767; + let x_2712527845668981629297529614174344579 = x; + x *= x_138258868771185683429919739944008148; + let x_2850786714440167312727449354118352727 = x; + x *= x_2712527845668981629297529614174344579; + let x_5563314560109148942024978968292697306 = x; + x *= x_2850786714440167312727449354118352727; + let x_8414101274549316254752428322411050033 = x; + x *= x_5563314560109148942024978968292697306; + let x_13977415834658465196777407290703747339 = x; + x *= x; + x *= x_13977415834658465196777407290703747339; + x *= x_8414101274549316254752428322411050033; + let x_50346348778524711845084650194522292050 = x; + x *= x_13977415834658465196777407290703747339; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x *= x; + x * x_50346348778524711845084650194522292050 +} diff --git a/zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/decompress_collection_id.rs b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/decompress_collection_id.rs similarity index 99% rename from zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/decompress_collection_id.rs rename to zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/decompress_collection_id.rs index 2bdd239f9..d93b25645 100644 --- a/zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/decompress_collection_id.rs +++ b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/decompress_collection_id.rs @@ -391,9 +391,9 @@ use test_case::test_case; ("0x1", "0x2") )] fn decompress_collection_id_works(collection_id: CombinatorialId, expected: (&str, &str)) { - let x = Fq::from_str_prefixed(expected.0).unwrap(); - let y = Fq::from_str_prefixed(expected.1).unwrap(); - let expected = G1Affine::from_xy(x, y).unwrap(); + let x = Fq::from_hex_str(expected.0); + let y = Fq::from_hex_str(expected.1); + let expected = G1Affine::new(x, y); let actual = decompress_collection_id(collection_id).unwrap(); assert_eq!(actual, expected); diff --git a/zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/decompress_hash.rs b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/decompress_hash.rs similarity index 99% rename from zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/decompress_hash.rs rename to zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/decompress_hash.rs index 49fc4eeed..f8cedde5b 100644 --- a/zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/decompress_hash.rs +++ b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/decompress_hash.rs @@ -749,8 +749,8 @@ fn decompress_hash_works( #[values(false, true)] force_max_work: bool, #[case] expected: (&str, &str), ) { - let x = Fq::from_str_prefixed(expected.0).unwrap(); - let y = Fq::from_str_prefixed(expected.1).unwrap(); - let expected: Option<_> = G1Affine::from_xy(x, y).into(); - assert_eq!(decompress_hash(hash, force_max_work), expected); + let x = Fq::from_hex_str(expected.0); + let y = Fq::from_hex_str(expected.1); + let expected = G1Affine::new(x, y); + assert_eq!(decompress_hash(hash, force_max_work).unwrap(), expected); } diff --git a/zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/get_collection_id.rs b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/get_collection_id.rs similarity index 100% rename from zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/get_collection_id.rs rename to zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/get_collection_id.rs diff --git a/zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/matching_y_coordinate.rs b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/matching_y_coordinate.rs similarity index 98% rename from zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/matching_y_coordinate.rs rename to zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/matching_y_coordinate.rs index e36652770..fc31a3685 100644 --- a/zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/matching_y_coordinate.rs +++ b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/matching_y_coordinate.rs @@ -2,8 +2,8 @@ use super::*; use test_case::test_case; // Empty string in the `expected` argument signals `None`. -#[test_case("0", "")] -#[test_case("1", "2")] +#[test_case("0x00", "")] +#[test_case("0x01", "0x02")] // Python-generated: #[test_case( "0x20e949416f9b53d227472744dcc6e807311aa8cf1f3de6e23d9f146759d5afe2", @@ -247,9 +247,8 @@ use test_case::test_case; "0x2f3a7f5710ff2bc13fd4b2cd3a84a61f6c7bc7ea3a2e125ae89fcfabfd9e737d" )] fn matching_y_coordinate_works(x: &str, expected: &str) { - let x = Fq::from_str_prefixed(x).unwrap(); - let expected = - if expected.is_empty() { None } else { Some(Fq::from_str_prefixed(expected).unwrap()) }; + let x = Fq::from_hex_str(x); + let expected = if expected.is_empty() { None } else { Some(Fq::from_hex_str(expected)) }; let result = matching_y_coordinate(x); assert_eq!(result, expected); diff --git a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/mod.rs b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/mod.rs new file mode 100644 index 000000000..9097b9364 --- /dev/null +++ b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/mod.rs @@ -0,0 +1,33 @@ +#![cfg(test)] + +use super::*; + +mod decompress_collection_id; +mod decompress_hash; +mod get_collection_id; +mod matching_y_coordinate; +mod pow_magic_number; + +trait FromHexStr { + fn from_hex_str(hex_str: &str) -> Self + where + Self: Sized; +} + +impl FromHexStr for Fq { + fn from_hex_str(hex_str: &str) -> Fq { + let hex_str_sans_prefix = &hex_str[2..]; + + // Pad with zeroes on the left. + let hex_str_padded = format!("{:0>64}", hex_str_sans_prefix); + + let bytes: Vec = (0..hex_str_padded.len()) + .step_by(2) + .map(|i| u8::from_str_radix(&hex_str_padded[i..i + 2], 16).unwrap()) + .collect(); + + let fixed_bytes: [u8; 32] = bytes.try_into().unwrap(); + + Fq::from_be_bytes_mod_order(&fixed_bytes) + } +} diff --git a/zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/pow_magic_number.rs b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/pow_magic_number.rs similarity index 99% rename from zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/pow_magic_number.rs rename to zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/pow_magic_number.rs index 14707a3ba..f42582800 100644 --- a/zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/pow_magic_number.rs +++ b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/pow_magic_number.rs @@ -1073,8 +1073,8 @@ use test_case::test_case; "0xb13a6b0f347da61aff1a5273fc125b6a050cc903022f0ebf366a5d2ed3debc3" )] fn pow_magic_number_works(x: &str, expected: &str) { - let x = Fq::from_str_prefixed(x).unwrap(); - let expected = Fq::from_str_prefixed(expected).unwrap(); + let x = Fq::from_hex_str(x); + let expected = Fq::from_hex_str(expected); let actual = pow_magic_number(x); assert_eq!(actual, expected); diff --git a/zrml/combo/src/types/cryptographic_id_manager/hash_tuple.rs b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/hash_tuple.rs similarity index 99% rename from zrml/combo/src/types/cryptographic_id_manager/hash_tuple.rs rename to zrml/combinatorial-tokens/src/types/cryptographic_id_manager/hash_tuple.rs index 955f56f96..0aabbeccf 100644 --- a/zrml/combo/src/types/cryptographic_id_manager/hash_tuple.rs +++ b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/hash_tuple.rs @@ -1,4 +1,5 @@ use crate::types::Hash256; +use alloc::{vec, vec::Vec}; use frame_support::{Blake2_256, StorageHasher}; use parity_scale_codec::Encode; use zeitgeist_primitives::types::Asset; diff --git a/zrml/combo/src/types/cryptographic_id_manager/mod.rs b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/mod.rs similarity index 85% rename from zrml/combo/src/types/cryptographic_id_manager/mod.rs rename to zrml/combinatorial-tokens/src/types/cryptographic_id_manager/mod.rs index eb5f25cc7..18c8993ef 100644 --- a/zrml/combo/src/types/cryptographic_id_manager/mod.rs +++ b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/mod.rs @@ -2,15 +2,13 @@ mod decompressor; mod hash_tuple; use crate::traits::CombinatorialIdManager; +use alloc::vec::Vec; use core::marker::PhantomData; -use ethnum::U256; -use frame_support::{Blake2_256, StorageHasher}; use hash_tuple::{HashTuple, ToBytes}; use parity_scale_codec::Encode; -use sp_runtime::DispatchError; use zeitgeist_primitives::types::{Asset, CombinatorialId}; -pub(crate) struct CryptographicIdManager(PhantomData<(MarketId, Hasher)>); +pub struct CryptographicIdManager(PhantomData<(MarketId, Hasher)>); impl CombinatorialIdManager for CryptographicIdManager where diff --git a/zrml/combo/src/types/hash.rs b/zrml/combinatorial-tokens/src/types/hash.rs similarity index 100% rename from zrml/combo/src/types/hash.rs rename to zrml/combinatorial-tokens/src/types/hash.rs diff --git a/zrml/combo/src/types/mod.rs b/zrml/combinatorial-tokens/src/types/mod.rs similarity index 58% rename from zrml/combo/src/types/mod.rs rename to zrml/combinatorial-tokens/src/types/mod.rs index 842c44ddd..a4d7d01fb 100644 --- a/zrml/combo/src/types/mod.rs +++ b/zrml/combinatorial-tokens/src/types/mod.rs @@ -1,5 +1,5 @@ pub(crate) mod cryptographic_id_manager; pub(crate) mod hash; -pub(crate) use cryptographic_id_manager::CryptographicIdManager; +pub use cryptographic_id_manager::CryptographicIdManager; pub(crate) use hash::Hash256; diff --git a/zrml/combo/src/traits/mod.rs b/zrml/combo/src/traits/mod.rs deleted file mode 100644 index ff38688d4..000000000 --- a/zrml/combo/src/traits/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod combinatorial_id_manager; - -pub(crate) use combinatorial_id_manager::CombinatorialIdManager; diff --git a/zrml/combo/src/types/cryptographic_id_manager/decompressor/mod.rs b/zrml/combo/src/types/cryptographic_id_manager/decompressor/mod.rs deleted file mode 100644 index 11e3f5ba4..000000000 --- a/zrml/combo/src/types/cryptographic_id_manager/decompressor/mod.rs +++ /dev/null @@ -1,582 +0,0 @@ -/// Highest/lowest bit always refers to the big endian representation of each bit sequence. -mod tests; - -use zeitgeist_primitives::types::CombinatorialId; -use core::num::ParseIntError; -use ethnum::U256; -use halo2curves::{ - bn256::{Fq, G1Affine}, - ff::PrimeField, - CurveAffine, -}; - -/// Will return `None` if and only if `parent_collection_id` is not a valid collection ID. -pub(crate) fn get_collection_id( - hash: CombinatorialId, - parent_collection_id: Option, - force_max_work: bool, -) -> Option { - let mut u = decompress_hash(hash, force_max_work)?; - - if let Some(pci) = parent_collection_id { - let v = decompress_collection_id(pci)?; - let w = u + v; // Projective coordinates. - u = w.into(); // Affine coordinates. - } - - // Convert back to bytes _before_ flipping, as flipping will sometimes result in numbers larger - // than the base field modulus. - let mut bytes = u.x.to_bytes(); - bytes.reverse(); // Little-endian to big-endian. - - if u.y.is_odd().into() { - flip_second_highest_bit(&mut bytes); - } - - Some(bytes) -} - -const DECOMPRESS_HASH_MAX_ITERS: usize = 1_000; - -/// Decompresses a collection ID `hash` to a point of `alt_bn128`. The amount of work done can be -/// forced to be independent of the input by setting the `force_max_work` flag. -/// -/// We don't have mathematical proof that the points of `alt_bn128` are distributed so that the -/// required number of iterations is below the specified limit of iterations, but there's good -/// evidence that input hash requires more than `log_2(P) = 507.19338271000436` iterations. We -/// will use `1_000` iterations as maximum for now. -/// -/// Provided the assumption above is correct, this function cannot return `None`. -fn decompress_hash(hash: CombinatorialId, force_max_work: bool) -> Option { - // Calculate `odd` first, then get congruent point `x` in `Fq`. As `hash` might represent a - // larger big endian number than `field_modulus()`, the MSB of `x` might be different from the - // MSB of `x_u256`. - let odd = is_msb_set(&hash); - - // `Fq` won't let us create an element of the Galois field if the number `x` represented by - // `hash` does not satisfy `x < P`, so we need to use `U256` to calculate the remainder of `x` - // when dividing by `P`. That's the whole reason we need ethnum. - let x_u256 = U256::from_be_bytes(hash); - let mut x = Fq::from_u256(x_u256.checked_rem(field_modulus())?)?; // Infallible. - - let mut y_opt = None; - let mut dummy_x = Fq::zero(); // Used to prevent rustc from optimizing dummy work away. - let mut dummy_y = None; - for _ in 0..DECOMPRESS_HASH_MAX_ITERS { - // If `y_opt.is_some()` and we're still in the loop, then `force_max_work` is set and we're - // jus here to spin our wheels for the benchmarks. - if y_opt.is_some() { - // Perform the same calculations as below, but store them in the dummy variables to - // avoid setting off rustc optimizations. - let dummy_x = x + Fq::one(); - - let matching_y = matching_y_coordinate(dummy_x); - - if matching_y.is_some() { - dummy_y = matching_y; - } - } else { - x = x + Fq::one(); - - let matching_y = matching_y_coordinate(x); - - if matching_y.is_some() { - y_opt = matching_y; - - if !force_max_work { - break; - } - } - } - } - std::hint::black_box(dummy_x); // Ensure that the dummies are considered "read" by rustc. - std::hint::black_box(dummy_y); - let mut y = y_opt?; // This **should** be infallible. - - // We have two options for the y-coordinate of the corresponding point: `y` and `P - y`. If - // `odd` is set but `y` isn't odd, we switch to the other option. - if (odd && y.is_even().into()) || (!odd && y.is_odd().into()) { - y = y.neg(); - } - - G1Affine::from_xy(x, y).into() -} - -fn decompress_collection_id(mut collection_id: CombinatorialId) -> Option { - let odd = is_second_msb_set(&collection_id); - chop_off_two_highest_bits(&mut collection_id); - collection_id.reverse(); // Big-endian to little-endian. - let x_opt: Option<_> = Fq::from_bytes(&collection_id).into(); - let x = x_opt?; // Fails if `collection_id` is not a collection ID. - - let mut y = matching_y_coordinate(x)?; // Fails if `collection_id` is not a collection ID. - - // We have two options for the y-coordinate of the corresponding point: `y` and `P - y`. If - // `odd` is set but `y` isn't odd, we switch to the other option. - if (odd && y.is_even().into()) || (!odd && y.is_odd().into()) { - y = y.neg(); - } - - G1Affine::from_xy(x, y).into() -} - -fn field_modulus() -> U256 { - U256::from_be_bytes([ - 0x30, 0x64, 0x4e, 0x72, 0xe1, 0x31, 0xa0, 0x29, 0xb8, 0x50, 0x45, 0xb6, 0x81, 0x81, 0x58, - 0x5d, 0x97, 0x81, 0x6a, 0x91, 0x68, 0x71, 0xca, 0x8d, 0x3c, 0x20, 0x8c, 0x16, 0xd8, 0x7c, - 0xfd, 0x47, - ]) -} - -/// Flips the second highests bit of big-endian `bytes`. -fn flip_second_highest_bit(bytes: &mut CombinatorialId) { - bytes[0] ^= 0b01000000; -} - -/// Checks if the most significant bit of the big-endian `bytes` is set. -fn is_msb_set(bytes: &CombinatorialId) -> bool { - (bytes[0] & 0b10000000) != 0 -} - -/// Checks if the second most significant bit of the big-endian `bytes` is set. -fn is_second_msb_set(bytes: &CombinatorialId) -> bool { - (bytes[0] & 0b01000000) != 0 -} - -/// Zeroes out the two most significant bits off the big-endian `bytes`. -fn chop_off_two_highest_bits(bytes: &mut CombinatorialId) { - bytes[0] &= 0b00111111; -} - -/// Returns a value `y` of `Fq` so that `(x, y)` is a point on `alt_bn128` or `None` if there is no -/// such value. -fn matching_y_coordinate(x: Fq) -> Option { - let xx = x * x; - let xxx = x * xx; - let yy = xxx + Fq::from(3); - let y = pow_magic_number(yy); - - if y * y == yy { Some(y) } else { None } -} - -/// Returns `x` to the power of `(P + 1) / 4` where `P` is the base field modulus of `alt_bn128`. -fn pow_magic_number(mut x: Fq) -> Fq { - let x_1 = x; - x = x * x; - let x_2 = x; - x = x * x; - x = x * x; - x = x * x_2; - let x_10 = x; - x = x * x_1; - let x_11 = x; - x = x * x_10; - let x_21 = x; - x = x * x; - let x_42 = x; - x = x * x; - x = x * x_42; - x = x * x; - x = x * x; - x = x * x_42; - x = x * x_11; - let x_557 = x; - x = x * x; - x = x * x; - x = x * x_21; - let x_2249 = x; - x = x * x; - x = x * x; - x = x * x; - x = x * x_2249; - x = x * x_557; - let x_20798 = x; - x = x * x; - x = x * x; - x = x * x; - x = x * x_20798; - x = x * x_2249; - let x_189431 = x; - x = x * x_20798; - let x_210229 = x; - x = x * x; - x = x * x; - x = x * x_189431; - let x_1030347 = x; - x = x * x; - let x_2060694 = x; - x = x * x; - x = x * x; - x = x * x; - x = x * x_2060694; - x = x * x_210229; - let x_18756475 = x; - x = x * x_1030347; - let x_19786822 = x; - x = x * x; - x = x * x; - x = x * x; - x = x * x_18756475; - let x_177051051 = x; - x = x * x; - x = x * x; - x = x * x_177051051; - x = x * x; - x = x * x; - x = x * x_177051051; - x = x * x_19786822; - let x_3737858893 = x; - x = x * x; - let x_7475717786 = x; - x = x * x; - x = x * x; - x = x * x_7475717786; - x = x * x_3737858893; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x_7475717786; - x = x * x_177051051; - let x_665515934005 = x; - x = x * x; - x = x * x_665515934005; - x = x * x_3737858893; - let x_2000285660908 = x; - x = x * x; - x = x * x_2000285660908; - x = x * x; - let x_12001713965448 = x; - x = x * x; - x = x * x_12001713965448; - let x_36005141896344 = x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x_36005141896344; - x = x * x_12001713965448; - x = x * x_665515934005; - let x_1200836912478805 = x; - x = x * x_2000285660908; - let x_1202837198139713 = x; - x = x * x; - x = x * x_1200836912478805; - let x_3606511308758231 = x; - x = x * x_1202837198139713; - let x_4809348506897944 = x; - x = x * x_3606511308758231; - let x_8415859815656175 = x; - x = x * x_4809348506897944; - let x_13225208322554119 = x; - x = x * x_8415859815656175; - let x_21641068138210294 = x; - x = x * x; - x = x * x_21641068138210294; - x = x * x; - x = x * x_13225208322554119; - let x_143071617151815883 = x; - x = x * x; - x = x * x; - x = x * x_21641068138210294; - let x_593927536745473826 = x; - x = x * x_143071617151815883; - let x_736999153897289709 = x; - x = x * x; - x = x * x_736999153897289709; - x = x * x_593927536745473826; - let x_2804924998437342953 = x; - x = x * x_736999153897289709; - let x_3541924152334632662 = x; - x = x * x_2804924998437342953; - let x_6346849150771975615 = x; - x = x * x_3541924152334632662; - let x_9888773303106608277 = x; - x = x * x; - x = x * x; - x = x * x_9888773303106608277; - x = x * x_6346849150771975615; - let x_55790715666305017000 = x; - x = x * x; - x = x * x_55790715666305017000; - x = x * x_9888773303106608277; - let x_177260920302021659277 = x; - x = x * x_55790715666305017000; - let x_233051635968326676277 = x; - x = x * x_177260920302021659277; - let x_410312556270348335554 = x; - x = x * x_233051635968326676277; - let x_643364192238675011831 = x; - x = x * x_410312556270348335554; - let x_1053676748509023347385 = x; - x = x * x; - x = x * x_1053676748509023347385; - x = x * x; - x = x * x_643364192238675011831; - let x_6965424683292815096141 = x; - x = x * x_1053676748509023347385; - let x_8019101431801838443526 = x; - x = x * x; - x = x * x_8019101431801838443526; - x = x * x; - x = x * x_6965424683292815096141; - let x_55080033274103845757297 = x; - x = x * x; - let x_110160066548207691514594 = x; - x = x * x; - x = x * x; - x = x * x_110160066548207691514594; - x = x * x_55080033274103845757297; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x_110160066548207691514594; - x = x * x_8019101431801838443526; - let x_9812265024222286383242392 = x; - x = x * x_55080033274103845757297; - let x_9867345057496390228999689 = x; - x = x * x_9812265024222286383242392; - let x_19679610081718676612242081 = x; - x = x * x_9867345057496390228999689; - let x_29546955139215066841241770 = x; - x = x * x; - x = x * x_29546955139215066841241770; - x = x * x; - x = x * x; - x = x * x; - x = x * x_29546955139215066841241770; - x = x * x_19679610081718676612242081; - let x_758353488562095347643286331 = x; - x = x * x; - x = x * x_758353488562095347643286331; - x = x * x; - x = x * x_29546955139215066841241770; - let x_4579667886511787152700959756 = x; - x = x * x; - x = x * x_4579667886511787152700959756; - x = x * x_758353488562095347643286331; - let x_14497357148097456805746165599 = x; - x = x * x_4579667886511787152700959756; - let x_19077025034609243958447125355 = x; - x = x * x; - x = x * x; - x = x * x_14497357148097456805746165599; - let x_90805457286534432639534667019 = x; - x = x * x_19077025034609243958447125355; - let x_109882482321143676597981792374 = x; - x = x * x; - x = x * x_90805457286534432639534667019; - let x_310570421928821785835498251767 = x; - x = x * x_109882482321143676597981792374; - let x_420452904249965462433480044141 = x; - x = x * x_310570421928821785835498251767; - let x_731023326178787248268978295908 = x; - x = x * x; - x = x * x_731023326178787248268978295908; - x = x * x_420452904249965462433480044141; - let x_2613522882786327207240414931865 = x; - x = x * x_731023326178787248268978295908; - let x_3344546208965114455509393227773 = x; - x = x * x; - x = x * x_3344546208965114455509393227773; - x = x * x; - x = x * x; - x = x * x_2613522882786327207240414931865; - let x_42748077390367700673353133665141 = x; - x = x * x; - x = x * x; - x = x * x; - x = x * x_42748077390367700673353133665141; - x = x * x_3344546208965114455509393227773; - let x_388077242722274420515687596214042 = x; - x = x * x_42748077390367700673353133665141; - let x_430825320112642121189040729879183 = x; - x = x * x; - let x_861650640225284242378081459758366 = x; - x = x * x_430825320112642121189040729879183; - x = x * x; - x = x * x; - x = x * x_861650640225284242378081459758366; - x = x * x_388077242722274420515687596214042; - let x_6419631724299264117162257814522604 = x; - x = x * x; - x = x * x_430825320112642121189040729879183; - let x_13270088768711170355513556358924391 = x; - x = x * x_6419631724299264117162257814522604; - let x_19689720493010434472675814173446995 = x; - x = x * x_13270088768711170355513556358924391; - let x_32959809261721604828189370532371386 = x; - x = x * x_19689720493010434472675814173446995; - let x_52649529754732039300865184705818381 = x; - x = x * x_32959809261721604828189370532371386; - let x_85609339016453644129054555238189767 = x; - x = x * x_52649529754732039300865184705818381; - let x_138258868771185683429919739944008148 = x; - x = x * x; - x = x * x_138258868771185683429919739944008148; - let x_414776606313557050289759219832024444 = x; - x = x * x_138258868771185683429919739944008148; - x = x * x; - x = x * x; - x = x * x_414776606313557050289759219832024444; - x = x * x_85609339016453644129054555238189767; - let x_2712527845668981629297529614174344579 = x; - x = x * x_138258868771185683429919739944008148; - let x_2850786714440167312727449354118352727 = x; - x = x * x_2712527845668981629297529614174344579; - let x_5563314560109148942024978968292697306 = x; - x = x * x_2850786714440167312727449354118352727; - let x_8414101274549316254752428322411050033 = x; - x = x * x_5563314560109148942024978968292697306; - let x_13977415834658465196777407290703747339 = x; - x = x * x; - x = x * x_13977415834658465196777407290703747339; - x = x * x_8414101274549316254752428322411050033; - let x_50346348778524711845084650194522292050 = x; - x = x * x_13977415834658465196777407290703747339; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x = x * x; - x * x_50346348778524711845084650194522292050 -} - -trait FromU256 -where - Self: Sized, -{ - fn from_u256(x: U256) -> Option; -} - -impl FromU256 for Fq { - fn from_u256(x: U256) -> Option { - let le_bytes = x.to_le_bytes(); - let ct_opt = Fq::from_bytes(&le_bytes); - - ct_opt.into() - } -} diff --git a/zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/field_modulus.rs b/zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/field_modulus.rs deleted file mode 100644 index 4cc4a554e..000000000 --- a/zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/field_modulus.rs +++ /dev/null @@ -1,10 +0,0 @@ -use super::*; - -fn field_modulus_works() { - let expected = U256::from_str_prefixed( - "0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47", - ) - .unwrap(); - assert_eq!(field_modulus(), expected); -} - diff --git a/zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/mod.rs b/zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/mod.rs deleted file mode 100644 index 18ff08ec0..000000000 --- a/zrml/combo/src/types/cryptographic_id_manager/decompressor/tests/mod.rs +++ /dev/null @@ -1,34 +0,0 @@ -#![cfg(test)] - -use super::*; - -mod decompress_collection_id; -mod decompress_hash; -mod field_modulus; -mod get_collection_id; -mod matching_y_coordinate; -mod pow_magic_number; - -#[derive(Debug)] -enum FromStrPrefixedError { - /// Failed to convert bytes to scalar. - FromBytesError, - - /// Failed to convert prefixed string to U256. - ParseIntError(core::num::ParseIntError), -} - -trait FromStrPrefixed -where - Self: Sized, -{ - fn from_str_prefixed(x: &str) -> Result; -} - -impl FromStrPrefixed for Fq { - fn from_str_prefixed(x: &str) -> Result { - let x_u256 = - U256::from_str_prefixed(x).map_err(|e| FromStrPrefixedError::ParseIntError(e))?; - Fq::from_u256(x_u256).ok_or(FromStrPrefixedError::FromBytesError) - } -} From 964e651c10a86ee134f7ac1453cb26bcb8ca34de Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Fri, 11 Oct 2024 17:31:35 +0200 Subject: [PATCH 08/73] Add tests for `combinatorial-tokens` (#1371) * Add mock for zrml-combinatorial-tokens * Add test framework * Add negative tests for `split_token` * Add more tests, fix some bugs, extend `Event` object * Add more tests * Add more integration tests * Add more integration tests * Add more integration tests * More tests * Add merge tests * final tests * fixes --- Cargo.lock | 8 + primitives/src/constants/base_multiples.rs | 1 + primitives/src/constants/mock.rs | 5 + zrml/combinatorial-tokens/Cargo.toml | 21 + zrml/combinatorial-tokens/src/lib.rs | 157 ++++-- zrml/combinatorial-tokens/src/mock/consts.rs | 5 + .../src/mock/ext_builder.rs | 55 ++ zrml/combinatorial-tokens/src/mock/mod.rs | 5 + zrml/combinatorial-tokens/src/mock/runtime.rs | 110 ++++ .../src/tests/integration.rs | 495 ++++++++++++++++++ .../src/tests/merge_position.rs | 231 ++++++++ zrml/combinatorial-tokens/src/tests/mod.rs | 81 +++ .../src/tests/split_position.rs | 351 +++++++++++++ 13 files changed, 1492 insertions(+), 33 deletions(-) create mode 100644 zrml/combinatorial-tokens/src/mock/consts.rs create mode 100644 zrml/combinatorial-tokens/src/mock/ext_builder.rs create mode 100644 zrml/combinatorial-tokens/src/mock/mod.rs create mode 100644 zrml/combinatorial-tokens/src/mock/runtime.rs create mode 100644 zrml/combinatorial-tokens/src/tests/integration.rs create mode 100644 zrml/combinatorial-tokens/src/tests/merge_position.rs create mode 100644 zrml/combinatorial-tokens/src/tests/mod.rs create mode 100644 zrml/combinatorial-tokens/src/tests/split_position.rs diff --git a/Cargo.lock b/Cargo.lock index f39fb7ece..3b4ac9ba8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15193,16 +15193,24 @@ version = "0.5.5" dependencies = [ "ark-bn254", "ark-ff", + "env_logger 0.10.2", "frame-benchmarking", "frame-support", "frame-system", + "orml-currencies", + "orml-tokens", "orml-traits", + "pallet-balances", + "pallet-timestamp", "parity-scale-codec", "rstest", "scale-info", + "sp-io", "sp-runtime", "test-case", "zeitgeist-primitives", + "zrml-combinatorial-tokens", + "zrml-market-commons", ] [[package]] diff --git a/primitives/src/constants/base_multiples.rs b/primitives/src/constants/base_multiples.rs index 2f8c41d8e..5d2c4de2d 100644 --- a/primitives/src/constants/base_multiples.rs +++ b/primitives/src/constants/base_multiples.rs @@ -39,6 +39,7 @@ pub const _36: u128 = 36 * _1; pub const _40: u128 = 40 * _1; pub const _70: u128 = 70 * _1; pub const _80: u128 = 80 * _1; +pub const _99: u128 = 99 * _1; pub const _100: u128 = 100 * _1; pub const _101: u128 = 101 * _1; pub const _444: u128 = 444 * _1; diff --git a/primitives/src/constants/mock.rs b/primitives/src/constants/mock.rs index 215bdeade..d33f5386e 100644 --- a/primitives/src/constants/mock.rs +++ b/primitives/src/constants/mock.rs @@ -33,6 +33,11 @@ parameter_types! { pub const CorrectionPeriod: BlockNumber = 4; } +// CombinatorialTokens +parameter_types! { + pub const CombinatorialTokensPalletId: PalletId = PalletId(*b"zge/coto"); +} + // Court parameter_types! { pub const AppealBond: Balance = 5 * BASE; diff --git a/zrml/combinatorial-tokens/Cargo.toml b/zrml/combinatorial-tokens/Cargo.toml index c89e6f8ac..d64d8f42a 100644 --- a/zrml/combinatorial-tokens/Cargo.toml +++ b/zrml/combinatorial-tokens/Cargo.toml @@ -10,12 +10,33 @@ scale-info = { workspace = true, features = ["derive"] } sp-runtime = { workspace = true } zeitgeist-primitives = { workspace = true } +# mock + +env_logger = { workspace = true, optional = true } +orml-currencies = { workspace = true, optional = true } +orml-tokens = { workspace = true, optional = true } +pallet-balances = { workspace = true, optional = true } +pallet-timestamp = { workspace = true, optional = true } +sp-io = { workspace = true, optional = true } +zrml-market-commons = { workspace = true, optional = true } + [dev-dependencies] test-case = { workspace = true } rstest = { workspace = true } +zrml-combinatorial-tokens = { workspace = true, features = ["default", "mock"] } [features] default = ["std"] +mock = [ + "env_logger/default", + "orml-currencies/default", + "orml-tokens/default", + "sp-io/default", + "pallet-balances/default", + "pallet-timestamp/default", + "zrml-market-commons/default", + "zeitgeist-primitives/mock", +] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", diff --git a/zrml/combinatorial-tokens/src/lib.rs b/zrml/combinatorial-tokens/src/lib.rs index 0dfbd4999..4c8c08b97 100644 --- a/zrml/combinatorial-tokens/src/lib.rs +++ b/zrml/combinatorial-tokens/src/lib.rs @@ -22,6 +22,8 @@ extern crate alloc; +pub mod mock; +mod tests; mod traits; pub mod types; @@ -82,24 +84,44 @@ mod pallet { pub(crate) type MarketIdOf = <::MarketCommons as MarketCommonsPalletApi>::MarketId; - // TODO Types pub(crate) const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); - // TODO Storage Items - #[pallet::event] #[pallet::generate_deposit(fn deposit_event)] pub enum Event where T: Config, { - TokenSplit, - TokenMerged, + /// User `who` has split `amount` units of token `asset_in` into the same amount of each + /// token in `assets_out` using `partition`. The ith element of `partition` matches the ith + /// element of `assets_out`, so `assets_out[i]` is the outcome represented by the specified + /// `parent_collection_id` together with `partition` in `market_id`. + /// TODO The second sentence is confusing. + TokenSplit { + who: AccountIdOf, + parent_collection_id: Option, + market_id: MarketIdOf, + partition: Vec>, + asset_in: AssetOf, + assets_out: Vec>, + collection_ids: Vec, + amount: BalanceOf, + }, + + /// User `who` has merged `amount` units of each of the tokens in `assets_in` into the same + /// amount of `asset_out`. + TokenMerged { + who: AccountIdOf, + asset_out: AssetOf, + assets_in: Vec>, + amount: BalanceOf, + }, } #[pallet::error] pub enum Error { - /// The specified partition is empty, contains overlaps or is too long. + /// The specified partition is empty, contains overlaps, is too long or doesn't match the + /// market's number of outcomes. InvalidPartition, /// The specified collection ID is invalid. @@ -153,48 +175,78 @@ mod pallet { let free_index_set = Self::free_index_set(market_id, &partition)?; // Destroy/store the tokens to be split. - if free_index_set.iter().any(|&i| i) { + let split_asset = if !free_index_set.iter().any(|&i| i) { // Vertical split. if let Some(pci) = parent_collection_id { // Split combinatorial token into higher level position. Destroy the tokens. let position_id = T::CombinatorialIdManager::get_position_id(collateral_token, pci); let position = Asset::CombinatorialToken(position_id); + + // This will fail if the market has a different collateral than the previous + // markets. TODO A cleaner error message would be nice though... + T::MultiCurrency::ensure_can_withdraw(position, &who, amount)?; T::MultiCurrency::withdraw(position, &who, amount)?; + + position } else { // Split collateral into first level position. Store the collateral in the // pallet account. This is the legacy `buy_complete_set`. + T::MultiCurrency::ensure_can_withdraw(collateral_token, &who, amount)?; T::MultiCurrency::transfer( collateral_token, &who, &Self::account_id(), amount, )?; + + collateral_token } } else { // Horizontal split. let remaining_index_set = free_index_set.into_iter().map(|i| !i).collect(); - let position = Self::position_from_collection( + let position = Self::position_from_parent_collection( parent_collection_id, market_id, remaining_index_set, )?; + T::MultiCurrency::ensure_can_withdraw(position, &who, amount)?; T::MultiCurrency::withdraw(position, &who, amount)?; - } + + position + }; // Deposit the new tokens. - let position_ids = partition + let collection_ids = partition .iter() .cloned() .map(|index_set| { - Self::position_from_collection(parent_collection_id, market_id, index_set) + Self::collection_id_from_parent_collection( + parent_collection_id, + market_id, + index_set, + ) }) .collect::, _>>()?; - for &position in position_ids.iter() { + let positions = collection_ids + .iter() + .cloned() + .map(|collection_id| Self::position_from_collection_id(market_id, collection_id)) + .collect::, _>>()?; + for &position in positions.iter() { T::MultiCurrency::deposit(position, &who, amount)?; } - Self::deposit_event(Event::::TokenSplit); + Self::deposit_event(Event::::TokenSplit { + who, + parent_collection_id, + market_id, + partition, + asset_in: split_asset, + assets_out: positions, + collection_ids, + amount, + }); Ok(()) } @@ -213,19 +265,23 @@ mod pallet { let free_index_set = Self::free_index_set(market_id, &partition)?; // Destory the old tokens. - let position_ids = partition + let positions = partition .iter() .cloned() .map(|index_set| { - Self::position_from_collection(parent_collection_id, market_id, index_set) + Self::position_from_parent_collection( + parent_collection_id, + market_id, + index_set, + ) }) .collect::, _>>()?; - for &position in position_ids.iter() { + for &position in positions.iter() { T::MultiCurrency::withdraw(position, &who, amount)?; } // Destroy/store the tokens to be split. - if free_index_set.iter().any(|&i| i) { + let merged_token = if !free_index_set.iter().any(|&i| i) { // Vertical merge. if let Some(pci) = parent_collection_id { // Merge combinatorial token into higher level position. Destroy the tokens. @@ -233,32 +289,52 @@ mod pallet { T::CombinatorialIdManager::get_position_id(collateral_token, pci); let position = Asset::CombinatorialToken(position_id); T::MultiCurrency::deposit(position, &who, amount)?; + + position } else { // Merge first-level tokens into collateral. Move collateral from the pallet // account to the user's wallet. This is the legacy `sell_complete_set`. + T::MultiCurrency::ensure_can_withdraw( + collateral_token, + &Self::account_id(), + amount, + )?; // Required because `transfer` throws `Underflow` errors sometimes. T::MultiCurrency::transfer( collateral_token, &Self::account_id(), &who, amount, )?; + + collateral_token } } else { // Horizontal merge. let remaining_index_set = free_index_set.into_iter().map(|i| !i).collect(); - let position = Self::position_from_collection( + let position = Self::position_from_parent_collection( parent_collection_id, market_id, remaining_index_set, )?; T::MultiCurrency::deposit(position, &who, amount)?; - } - Self::deposit_event(Event::::TokenMerged); + position + }; + + Self::deposit_event(Event::::TokenMerged { + who, + asset_out: merged_token, + assets_in: positions, + amount, + }); Ok(()) } + pub(crate) fn account_id() -> T::AccountId { + T::PalletId::get().into_account_truncating() + } + fn free_index_set( market_id: MarketIdOf, partition: &[Vec], @@ -268,10 +344,10 @@ mod pallet { let mut free_index_set = vec![true; asset_count]; for index_set in partition.iter() { - // Ensure that the partition is not trivial. - let ones = index_set.iter().fold(0usize, |acc, &val| acc + (val as usize)); - ensure!(ones > 0, Error::::InvalidPartition); - ensure!(ones < asset_count, Error::::InvalidPartition); + // Ensure that the partition is not trivial and matches the market's outcomes. + ensure!(index_set.iter().any(|&i| i), Error::::InvalidPartition); + ensure!(index_set.len() == asset_count, Error::::InvalidPartition); + ensure!(!index_set.iter().all(|&i| i), Error::::InvalidPartition); // Ensure that `index_set` is disjoint from the previously iterated elements of the // partition. @@ -288,21 +364,26 @@ mod pallet { Ok(free_index_set) } - fn position_from_collection( + fn collection_id_from_parent_collection( parent_collection_id: Option>, market_id: MarketIdOf, index_set: Vec, - ) -> Result, DispatchError> { - let market = T::MarketCommons::market(&market_id)?; - let collateral_token = market.base_asset; - - let collection_id = T::CombinatorialIdManager::get_collection_id( + ) -> Result, DispatchError> { + T::CombinatorialIdManager::get_collection_id( parent_collection_id, market_id, index_set, false, // TODO Expose this parameter! ) - .ok_or(Error::::InvalidCollectionId)?; + .ok_or(Error::::InvalidCollectionId.into()) + } + + fn position_from_collection_id( + market_id: MarketIdOf, + collection_id: CombinatorialIdOf, + ) -> Result, DispatchError> { + let market = T::MarketCommons::market(&market_id)?; + let collateral_token = market.base_asset; let position_id = T::CombinatorialIdManager::get_position_id(collateral_token, collection_id); @@ -311,8 +392,18 @@ mod pallet { Ok(asset) } - fn account_id() -> T::AccountId { - T::PalletId::get().into_account_truncating() + fn position_from_parent_collection( + parent_collection_id: Option>, + market_id: MarketIdOf, + index_set: Vec, + ) -> Result, DispatchError> { + let collection_id = Self::collection_id_from_parent_collection( + parent_collection_id, + market_id, + index_set, + )?; + + Self::position_from_collection_id(market_id, collection_id) } } } diff --git a/zrml/combinatorial-tokens/src/mock/consts.rs b/zrml/combinatorial-tokens/src/mock/consts.rs new file mode 100644 index 000000000..6aecaf6f8 --- /dev/null +++ b/zrml/combinatorial-tokens/src/mock/consts.rs @@ -0,0 +1,5 @@ +#[cfg(feature = "parachain")] +use zeitgeist_primitives::types::{Asset, MarketId}; + +#[cfg(feature = "parachain")] +pub(crate) const FOREIGN_ASSET: Asset = Asset::ForeignAsset(1); diff --git a/zrml/combinatorial-tokens/src/mock/ext_builder.rs b/zrml/combinatorial-tokens/src/mock/ext_builder.rs new file mode 100644 index 000000000..7a12e7d41 --- /dev/null +++ b/zrml/combinatorial-tokens/src/mock/ext_builder.rs @@ -0,0 +1,55 @@ +use crate::mock::runtime::{Runtime, System}; +use sp_io::TestExternalities; +use sp_runtime::BuildStorage; + +#[cfg(feature = "parachain")] +use {crate::mock::consts::FOREIGN_ASSET, zeitgeist_primitives::types::CustomMetadata}; + +pub struct ExtBuilder; + +impl ExtBuilder { + pub fn build() -> TestExternalities { + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); + + // See the logs in tests when using `RUST_LOG=debug cargo test -- --nocapture` + let _ = env_logger::builder().is_test(true).try_init(); + + pallet_balances::GenesisConfig:: { balances: vec![] } + .assimilate_storage(&mut t) + .unwrap(); + + #[cfg(feature = "parachain")] + { + orml_tokens::GenesisConfig:: { balances: vec![] } + .assimilate_storage(&mut t) + .unwrap(); + + let custom_metadata = + CustomMetadata { allow_as_base_asset: true, ..Default::default() }; + + orml_asset_registry::GenesisConfig:: { + assets: vec![( + FOREIGN_ASSET, + AssetMetadata { + decimals: 18, + name: "MKL".as_bytes().to_vec().try_into().unwrap(), + symbol: "MKL".as_bytes().to_vec().try_into().unwrap(), + existential_deposit: 0, + location: None, + additional: custom_metadata, + } + .encode(), + )], + last_asset_id: FOREIGN_ASSET, + } + .assimilate_storage(&mut t) + .unwrap(); + } + + let mut test_ext: sp_io::TestExternalities = t.into(); + + test_ext.execute_with(|| System::set_block_number(1)); + + test_ext + } +} diff --git a/zrml/combinatorial-tokens/src/mock/mod.rs b/zrml/combinatorial-tokens/src/mock/mod.rs new file mode 100644 index 000000000..f0140b64e --- /dev/null +++ b/zrml/combinatorial-tokens/src/mock/mod.rs @@ -0,0 +1,5 @@ +#![cfg(feature = "mock")] + +pub(crate) mod consts; +pub mod ext_builder; +pub(crate) mod runtime; diff --git a/zrml/combinatorial-tokens/src/mock/runtime.rs b/zrml/combinatorial-tokens/src/mock/runtime.rs new file mode 100644 index 000000000..07c9c6a54 --- /dev/null +++ b/zrml/combinatorial-tokens/src/mock/runtime.rs @@ -0,0 +1,110 @@ +use crate as zrml_combinatorial_tokens; +use crate::types::CryptographicIdManager; +use frame_support::{construct_runtime, traits::Everything, Blake2_256}; +use frame_system::mocking::MockBlock; +use sp_runtime::traits::{BlakeTwo256, ConstU32, IdentityLookup}; +use zeitgeist_primitives::{ + constants::mock::{ + BlockHashCount, CombinatorialTokensPalletId, ExistentialDeposit, ExistentialDeposits, + GetNativeCurrencyId, MaxLocks, MaxReserves, MinimumPeriod, + }, + types::{ + AccountIdTest, Amount, Balance, BasicCurrencyAdapter, CurrencyId, Hash, MarketId, Moment, + }, +}; + +construct_runtime! { + pub enum Runtime { + CombinatorialTokens: zrml_combinatorial_tokens, + Balances: pallet_balances, + Currencies: orml_currencies, + MarketCommons: zrml_market_commons, + System: frame_system, + Timestamp: pallet_timestamp, + Tokens: orml_tokens, + } +} + +impl zrml_combinatorial_tokens::Config for Runtime { + type CombinatorialIdManager = CryptographicIdManager; + type MarketCommons = MarketCommons; + type MultiCurrency = Currencies; + type PalletId = CombinatorialTokensPalletId; + type RuntimeEvent = RuntimeEvent; +} + +impl orml_currencies::Config for Runtime { + type GetNativeCurrencyId = GetNativeCurrencyId; + type MultiCurrency = Tokens; + type NativeCurrency = BasicCurrencyAdapter; + type WeightInfo = (); +} + +impl pallet_balances::Config for Runtime { + type AccountStore = System; + type Balance = Balance; + type DustRemoval = (); + type FreezeIdentifier = (); + type RuntimeHoldReason = (); + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ExistentialDeposit; + type MaxHolds = (); + type MaxFreezes = (); + type MaxLocks = MaxLocks; + type MaxReserves = MaxReserves; + type ReserveIdentifier = [u8; 8]; + type WeightInfo = (); +} + +impl zrml_market_commons::Config for Runtime { + type Balance = Balance; + type MarketId = MarketId; + type Timestamp = Timestamp; +} + +impl frame_system::Config for Runtime { + type AccountData = pallet_balances::AccountData; + type AccountId = AccountIdTest; + type BaseCallFilter = Everything; + type Block = MockBlock; + type BlockHashCount = BlockHashCount; + type BlockLength = (); + type BlockWeights = (); + type RuntimeCall = RuntimeCall; + type DbWeight = (); + type RuntimeEvent = RuntimeEvent; + type Hash = Hash; + type Hashing = BlakeTwo256; + type Lookup = IdentityLookup; + type Nonce = u64; + type MaxConsumers = ConstU32<16>; + type OnKilledAccount = (); + type OnNewAccount = (); + type RuntimeOrigin = RuntimeOrigin; + type PalletInfo = PalletInfo; + type SS58Prefix = (); + type SystemWeightInfo = (); + type Version = (); + type OnSetCode = (); +} + +impl pallet_timestamp::Config for Runtime { + type MinimumPeriod = MinimumPeriod; + type Moment = Moment; + type OnTimestampSet = (); + type WeightInfo = (); +} + +impl orml_tokens::Config for Runtime { + type Amount = Amount; + type Balance = Balance; + type CurrencyId = CurrencyId; + type DustRemovalWhitelist = Everything; + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposits = ExistentialDeposits; + type MaxLocks = MaxLocks; + type MaxReserves = MaxReserves; + type CurrencyHooks = (); + type ReserveIdentifier = [u8; 8]; + type WeightInfo = (); +} diff --git a/zrml/combinatorial-tokens/src/tests/integration.rs b/zrml/combinatorial-tokens/src/tests/integration.rs new file mode 100644 index 000000000..db95a2b6b --- /dev/null +++ b/zrml/combinatorial-tokens/src/tests/integration.rs @@ -0,0 +1,495 @@ +use super::*; + +#[test] +fn split_followed_by_merge_vertical_no_parent() { + ExtBuilder::build().execute_with(|| { + let alice = Account::new(0).deposit(Asset::Ztg, _100).unwrap(); + let pallet = Account::new(Pallet::::account_id()); + + let market_id = create_market(Asset::Ztg, MarketType::Categorical(3)); + let partition = vec![vec![B0, B0, B1], vec![B1, B1, B0]]; + let amount = _1; + + let ct_001 = CombinatorialToken([ + 207, 168, 160, 93, 238, 221, 197, 1, 171, 102, 28, 24, 18, 107, 205, 231, 227, 98, 220, + 105, 211, 29, 181, 30, 53, 7, 200, 154, 134, 246, 38, 139, + ]); + let ct_110 = CombinatorialToken([ + 101, 210, 61, 196, 5, 247, 150, 41, 186, 49, 11, 63, 139, 53, 25, 65, 161, 83, 24, 142, + 225, 102, 57, 241, 199, 18, 226, 137, 68, 3, 219, 131, + ]); + + assert_ok!(CombinatorialTokens::split_position( + alice.signed(), + None, + market_id, + partition.clone(), + amount, + )); + assert_eq!(alice.free_balance(Asset::Ztg), _99); + assert_eq!(alice.free_balance(ct_001), _1); + assert_eq!(alice.free_balance(ct_110), _1); + assert_eq!(pallet.free_balance(Asset::Ztg), _1); + + assert_ok!(CombinatorialTokens::merge_position( + alice.signed(), + None, + market_id, + partition, + amount, + )); + assert_eq!(alice.free_balance(Asset::Ztg), _100); + assert_eq!(alice.free_balance(ct_001), 0); + assert_eq!(alice.free_balance(ct_110), 0); + assert_eq!(pallet.free_balance(Asset::Ztg), 0); + }); +} + +#[test] +fn split_followed_by_merge_vertical_with_parent() { + ExtBuilder::build().execute_with(|| { + let alice = Account::new(0).deposit(Asset::Ztg, _100).unwrap(); + let pallet = Account::new(Pallet::::account_id()); + + let ct_001 = CombinatorialToken([ + 207, 168, 160, 93, 238, 221, 197, 1, 171, 102, 28, 24, 18, 107, 205, 231, 227, 98, 220, + 105, 211, 29, 181, 30, 53, 7, 200, 154, 134, 246, 38, 139, + ]); + let ct_110 = CombinatorialToken([ + 101, 210, 61, 196, 5, 247, 150, 41, 186, 49, 11, 63, 139, 53, 25, 65, 161, 83, 24, 142, + 225, 102, 57, 241, 199, 18, 226, 137, 68, 3, 219, 131, + ]); + let ct_001_0101 = CombinatorialToken([ + 38, 14, 141, 152, 199, 40, 88, 165, 208, 236, 195, 198, 208, 75, 93, 85, 114, 4, 175, + 225, 211, 72, 142, 210, 98, 202, 168, 193, 245, 217, 239, 28, + ]); + let ct_001_1010 = CombinatorialToken([ + 107, 142, 3, 38, 49, 137, 237, 239, 1, 131, 197, 221, 236, 46, 246, 93, 185, 197, 228, + 184, 75, 79, 107, 73, 89, 19, 22, 124, 15, 58, 110, 100, + ]); + + let parent_market_id = create_market(Asset::Ztg, MarketType::Categorical(3)); + let parent_amount = _3; + let parent_partition = vec![vec![B0, B0, B1], vec![B1, B1, B0]]; + assert_ok!(CombinatorialTokens::split_position( + alice.signed(), + None, + parent_market_id, + parent_partition.clone(), + parent_amount, + )); + + let child_market_id = create_market(Asset::Ztg, MarketType::Categorical(4)); + let child_amount = _1; + // Collection ID of [0, 0, 1]. + let parent_collection_id = [ + 6, 44, 173, 50, 122, 106, 144, 185, 253, 19, 252, 218, 215, 241, 218, 37, 196, 112, 45, + 133, 165, 48, 231, 189, 87, 123, 131, 18, 190, 5, 110, 93, + ]; + let child_partition = vec![vec![B0, B1, B0, B1], vec![B1, B0, B1, B0]]; + assert_ok!(CombinatorialTokens::split_position( + alice.signed(), + Some(parent_collection_id), + child_market_id, + child_partition.clone(), + child_amount, + )); + assert_eq!(alice.free_balance(ct_001), parent_amount - child_amount); + assert_eq!(alice.free_balance(ct_110), parent_amount); + assert_eq!(alice.free_balance(Asset::Ztg), _100 - parent_amount); + assert_eq!(alice.free_balance(ct_001_0101), child_amount); + assert_eq!(alice.free_balance(ct_001_1010), child_amount); + assert_eq!(pallet.free_balance(Asset::Ztg), parent_amount); + + assert_ok!(CombinatorialTokens::merge_position( + alice.signed(), + Some(parent_collection_id), + child_market_id, + child_partition, + child_amount, + )); + assert_eq!(alice.free_balance(ct_001), parent_amount); + assert_eq!(alice.free_balance(ct_110), parent_amount); + assert_eq!(alice.free_balance(Asset::Ztg), _100 - parent_amount); + assert_eq!(alice.free_balance(ct_001_0101), 0); + assert_eq!(alice.free_balance(ct_001_1010), 0); + assert_eq!(pallet.free_balance(Asset::Ztg), parent_amount); + + assert_ok!(CombinatorialTokens::merge_position( + alice.signed(), + None, + parent_market_id, + parent_partition, + parent_amount, + )); + assert_eq!(alice.free_balance(ct_001), 0); + assert_eq!(alice.free_balance(ct_110), 0); + assert_eq!(alice.free_balance(Asset::Ztg), _100); + assert_eq!(alice.free_balance(ct_001_0101), 0); + assert_eq!(alice.free_balance(ct_001_1010), 0); + assert_eq!(pallet.free_balance(Asset::Ztg), 0); + }); +} + +#[test] +fn split_followed_by_merge_vertical_with_parent_in_opposite_order() { + ExtBuilder::build().execute_with(|| { + let alice = Account::new(0).deposit(Asset::Ztg, _100).unwrap(); + + let market_0 = create_market(Asset::Ztg, MarketType::Categorical(3)); + let market_1 = create_market(Asset::Ztg, MarketType::Categorical(4)); + + let partition_0 = vec![vec![B0, B0, B1], vec![B1, B1, B0]]; + let partition_1 = vec![vec![B0, B0, B1, B1], vec![B1, B1, B0, B0]]; + + let amount = _1; + + let ct_001 = CombinatorialToken([ + 207, 168, 160, 93, 238, 221, 197, 1, 171, 102, 28, 24, 18, 107, 205, 231, 227, 98, 220, + 105, 211, 29, 181, 30, 53, 7, 200, 154, 134, 246, 38, 139, + ]); + let ct_110 = CombinatorialToken([ + 101, 210, 61, 196, 5, 247, 150, 41, 186, 49, 11, 63, 139, 53, 25, 65, 161, 83, 24, 142, + 225, 102, 57, 241, 199, 18, 226, 137, 68, 3, 219, 131, + ]); + let id_001 = [ + 6, 44, 173, 50, 122, 106, 144, 185, 253, 19, 252, 218, 215, 241, 218, 37, 196, 112, 45, + 133, 165, 48, 231, 189, 87, 123, 131, 18, 190, 5, 110, 93, + ]; + let id_110 = [ + 1, 189, 94, 224, 153, 162, 145, 214, 33, 231, 230, 19, 122, 179, 122, 117, 193, 123, + 73, 220, 240, 131, 180, 180, 137, 14, 179, 148, 188, 13, 107, 65, + ]; + + let ct_0011 = CombinatorialToken([ + 32, 70, 65, 46, 183, 161, 122, 58, 80, 224, 102, 106, 63, 89, 191, 19, 235, 137, 64, + 182, 25, 222, 198, 172, 230, 42, 120, 101, 100, 150, 172, 125, + ]); + let ct_1100 = CombinatorialToken([ + 28, 158, 82, 180, 87, 230, 168, 233, 74, 123, 50, 76, 131, 203, 82, 194, 214, 165, 87, + 200, 58, 244, 23, 184, 79, 127, 201, 39, 82, 243, 186, 1, + ]); + let id_0011 = [ + 77, 83, 228, 134, 221, 156, 53, 34, 133, 83, 120, 8, 232, 53, 54, 200, 181, 110, 13, + 145, 238, 130, 69, 147, 108, 167, 41, 217, 105, 22, 126, 136, + ]; + let id_1100 = [ + 10, 211, 115, 219, 24, 177, 205, 243, 234, 68, 234, 119, 21, 211, 103, 229, 185, 23, + 63, 75, 206, 10, 196, 75, 10, 110, 147, 40, 90, 61, 145, 90, + ]; + + let ct_001_0011 = CombinatorialToken([ + 156, 47, 254, 154, 29, 5, 149, 94, 214, 135, 92, 36, 188, 120, 42, 144, 136, 151, 255, + 91, 232, 152, 91, 236, 177, 66, 36, 72, 134, 234, 212, 177, + ]); + let ct_001_1100 = CombinatorialToken([ + 224, 47, 73, 22, 156, 226, 199, 74, 28, 251, 44, 108, 73, 125, 192, 151, 193, 60, 156, + 240, 215, 23, 138, 168, 181, 175, 241, 70, 71, 126, 48, 45, + ]); + let ct_110_0011 = CombinatorialToken([ + 191, 106, 159, 227, 136, 131, 143, 101, 127, 7, 109, 82, 45, 169, 246, 45, 250, 217, + 33, 147, 166, 174, 232, 35, 58, 20, 111, 167, 6, 6, 73, 67, + ]); + let ct_110_1100 = CombinatorialToken([ + 184, 155, 104, 90, 231, 10, 30, 1, 213, 7, 1, 58, 117, 172, 118, 72, 118, 89, 219, 216, + 140, 27, 228, 2, 87, 26, 169, 150, 172, 154, 49, 219, + ]); + + // Split ZTG into A|B and C. + assert_ok!(CombinatorialTokens::split_position( + alice.signed(), + None, + market_0, + partition_0.clone(), + amount, + )); + + // Split C into C&(U|V) and C&(W|X). + assert_ok!(CombinatorialTokens::split_position( + alice.signed(), + Some(id_001), + market_1, + partition_1.clone(), + amount, + )); + + // Split A|B into into (A|B)&(U|V) and (A|B)&(W|X). + assert_ok!(CombinatorialTokens::split_position( + alice.signed(), + Some(id_110), + market_1, + partition_1.clone(), + amount, + )); + + assert_eq!(alice.free_balance(ct_001), 0); + assert_eq!(alice.free_balance(ct_110), 0); + assert_eq!(alice.free_balance(ct_001_0011), _1); + assert_eq!(alice.free_balance(ct_001_1100), _1); + assert_eq!(alice.free_balance(ct_110_0011), _1); + assert_eq!(alice.free_balance(ct_110_1100), _1); + assert_eq!(alice.free_balance(ct_0011), 0); + assert_eq!(alice.free_balance(ct_1100), 0); + assert_eq!(alice.free_balance(Asset::Ztg), _99); + + // Merge C&(U|V) and (A|B)&(U|V) into U|V. + assert_ok!(CombinatorialTokens::merge_position( + alice.signed(), + Some(id_1100), + market_0, + partition_0.clone(), + amount, + )); + + assert_eq!(alice.free_balance(ct_001), 0); + assert_eq!(alice.free_balance(ct_110), 0); + assert_eq!(alice.free_balance(ct_001_0011), _1); + assert_eq!(alice.free_balance(ct_001_1100), 0); + assert_eq!(alice.free_balance(ct_110_0011), _1); + assert_eq!(alice.free_balance(ct_110_1100), 0); + assert_eq!(alice.free_balance(ct_0011), 0); + assert_eq!(alice.free_balance(ct_1100), _1); + assert_eq!(alice.free_balance(Asset::Ztg), _99); + + // Merge C&(W|X) and (A|B)&(W|X) into W|X. + assert_ok!(CombinatorialTokens::merge_position( + alice.signed(), + Some(id_0011), + market_0, + partition_0, + amount, + )); + + assert_eq!(alice.free_balance(ct_001), 0); + assert_eq!(alice.free_balance(ct_110), 0); + assert_eq!(alice.free_balance(ct_001_0011), 0); + assert_eq!(alice.free_balance(ct_001_1100), 0); + assert_eq!(alice.free_balance(ct_110_0011), 0); + assert_eq!(alice.free_balance(ct_110_1100), 0); + assert_eq!(alice.free_balance(ct_0011), _1); + assert_eq!(alice.free_balance(ct_1100), _1); + assert_eq!(alice.free_balance(Asset::Ztg), _99); + + // Merge U|V and W|X into ZTG. + assert_ok!(CombinatorialTokens::merge_position( + alice.signed(), + None, + market_1, + partition_1, + amount, + )); + + assert_eq!(alice.free_balance(ct_001), 0); + assert_eq!(alice.free_balance(ct_110), 0); + assert_eq!(alice.free_balance(ct_001_0011), 0); + assert_eq!(alice.free_balance(ct_001_1100), 0); + assert_eq!(alice.free_balance(ct_110_0011), 0); + assert_eq!(alice.free_balance(ct_110_1100), 0); + assert_eq!(alice.free_balance(ct_0011), 0); + assert_eq!(alice.free_balance(ct_1100), 0); + assert_eq!(alice.free_balance(Asset::Ztg), _100); + }); +} + +// This test shows that splitting a token horizontally can be accomplished by splitting the parent +// token vertically with a finer partition. +#[test] +fn split_vertical_followed_by_horizontal_split_no_parent() { + ExtBuilder::build().execute_with(|| { + let alice = Account::new(0).deposit(Asset::Ztg, _100).unwrap(); + + let market_id = create_market(Asset::Ztg, MarketType::Categorical(3)); + let amount = _1; + + // Split vertically and then horizontally. + assert_ok!(CombinatorialTokens::split_position( + alice.signed(), + None, + market_id, + vec![vec![B0, B0, B1], vec![B1, B1, B0]], + amount, + )); + assert_ok!(CombinatorialTokens::split_position( + alice.signed(), + None, + market_id, + vec![vec![B1, B0, B0], vec![B0, B1, B0]], + amount, + )); + + let ct_001 = CombinatorialToken([ + 207, 168, 160, 93, 238, 221, 197, 1, 171, 102, 28, 24, 18, 107, 205, 231, 227, 98, 220, + 105, 211, 29, 181, 30, 53, 7, 200, 154, 134, 246, 38, 139, + ]); + let ct_010 = CombinatorialToken([ + 23, 108, 101, 109, 145, 51, 201, 192, 240, 28, 43, 57, 53, 4, 75, 101, 116, 20, 184, + 25, 227, 71, 149, 136, 59, 82, 81, 105, 41, 160, 39, 142, + ]); + let ct_100 = CombinatorialToken([ + 63, 95, 93, 48, 199, 160, 113, 178, 33, 24, 52, 193, 247, 121, 229, 30, 231, 100, 209, + 14, 57, 98, 193, 214, 34, 251, 53, 51, 136, 146, 93, 26, + ]); + + assert_eq!(alice.free_balance(ct_001), amount); + assert_eq!(alice.free_balance(ct_010), amount); + assert_eq!(alice.free_balance(ct_100), amount); + + // Split vertically. This should yield the same amount as the two splits above. + assert_ok!(CombinatorialTokens::split_position( + alice.signed(), + None, + market_id, + vec![vec![B1, B0, B0], vec![B0, B1, B0], vec![B0, B0, B1]], + amount, + )); + + assert_eq!(alice.free_balance(ct_001), 2 * amount); + assert_eq!(alice.free_balance(ct_010), 2 * amount); + assert_eq!(alice.free_balance(ct_100), 2 * amount); + }); +} + +// This test shows that splitting a token horizontally can be accomplished by splitting a the parent +// token vertically with a finer partition. +#[test] +fn split_vertical_followed_by_horizontal_split_with_parent() { + ExtBuilder::build().execute_with(|| { + let alice = Account::new(0).deposit(Asset::Ztg, _100).unwrap(); + let pallet = Account::new(Pallet::::account_id()); + + // Prepare level 1 token. + let parent_market_id = create_market(Asset::Ztg, MarketType::Categorical(3)); + let parent_amount = _6; + assert_ok!(CombinatorialTokens::split_position( + alice.signed(), + None, + parent_market_id, + vec![vec![B0, B0, B1], vec![B1, B1, B0]], + parent_amount, + )); + + let child_market_id = create_market(Asset::Ztg, MarketType::Categorical(4)); + let child_amount_first_pass = _3; + // Collection ID of [0, 0, 1]. + let parent_collection_id = [ + 6, 44, 173, 50, 122, 106, 144, 185, 253, 19, 252, 218, 215, 241, 218, 37, 196, 112, 45, + 133, 165, 48, 231, 189, 87, 123, 131, 18, 190, 5, 110, 93, + ]; + + let ct_001 = CombinatorialToken([ + 207, 168, 160, 93, 238, 221, 197, 1, 171, 102, 28, 24, 18, 107, 205, 231, 227, 98, 220, + 105, 211, 29, 181, 30, 53, 7, 200, 154, 134, 246, 38, 139, + ]); + let ct_110 = CombinatorialToken([ + 101, 210, 61, 196, 5, 247, 150, 41, 186, 49, 11, 63, 139, 53, 25, 65, 161, 83, 24, 142, + 225, 102, 57, 241, 199, 18, 226, 137, 68, 3, 219, 131, + ]); + let ct_001_0011 = CombinatorialToken([ + 156, 47, 254, 154, 29, 5, 149, 94, 214, 135, 92, 36, 188, 120, 42, 144, 136, 151, 255, + 91, 232, 152, 91, 236, 177, 66, 36, 72, 134, 234, 212, 177, + ]); + let ct_001_1100 = CombinatorialToken([ + 224, 47, 73, 22, 156, 226, 199, 74, 28, 251, 44, 108, 73, 125, 192, 151, 193, 60, 156, + 240, 215, 23, 138, 168, 181, 175, 241, 70, 71, 126, 48, 45, + ]); + let ct_001_1000 = CombinatorialToken([ + 9, 208, 130, 141, 130, 87, 234, 29, 150, 109, 181, 68, 138, 137, 66, 8, 251, 157, 224, + 152, 176, 104, 231, 193, 178, 99, 184, 123, 78, 213, 63, 150, + ]); + let ct_001_0100 = CombinatorialToken([ + 220, 137, 106, 212, 207, 90, 155, 125, 22, 15, 184, 90, 227, 159, 173, 59, 33, 73, 50, + 245, 183, 245, 46, 56, 66, 199, 94, 129, 154, 18, 48, 73, + ]); + + // Split vertically and then horizontally. + assert_ok!(CombinatorialTokens::split_position( + alice.signed(), + Some(parent_collection_id), + child_market_id, + vec![vec![B0, B0, B1, B1], vec![B1, B1, B0, B0]], + child_amount_first_pass, + )); + assert_ok!(CombinatorialTokens::split_position( + alice.signed(), + Some(parent_collection_id), + child_market_id, + vec![vec![B1, B0, B0, B0], vec![B0, B1, B0, B0]], + child_amount_first_pass, + )); + + assert_eq!(alice.free_balance(ct_001), parent_amount - child_amount_first_pass); + assert_eq!(alice.free_balance(ct_110), parent_amount); + assert_eq!(alice.free_balance(ct_001_0011), child_amount_first_pass); + assert_eq!(alice.free_balance(ct_001_1100), 0); + assert_eq!(alice.free_balance(ct_001_1000), child_amount_first_pass); + assert_eq!(alice.free_balance(ct_001_0100), child_amount_first_pass); + assert_eq!(pallet.free_balance(Asset::Ztg), parent_amount); + assert_eq!(pallet.free_balance(ct_001_1100), 0); + + // Split vertically. This should yield the same amount as the two splits above. + let child_amount_second_pass = _2; + assert_ok!(CombinatorialTokens::split_position( + alice.signed(), + Some(parent_collection_id), + child_market_id, + vec![vec![B1, B0, B0, B0], vec![B0, B1, B0, B0], vec![B0, B0, B1, B1]], + child_amount_second_pass, + )); + + let total_child_amount = child_amount_first_pass + child_amount_second_pass; + assert_eq!(alice.free_balance(ct_001), parent_amount - total_child_amount); + assert_eq!(alice.free_balance(ct_110), parent_amount); + assert_eq!(alice.free_balance(ct_001_0011), total_child_amount); + assert_eq!(alice.free_balance(ct_001_1100), 0); + assert_eq!(alice.free_balance(ct_001_1000), total_child_amount); + assert_eq!(alice.free_balance(ct_001_0100), total_child_amount); + assert_eq!(pallet.free_balance(Asset::Ztg), parent_amount); + assert_eq!(pallet.free_balance(ct_001_1100), 0); + }); +} + +#[test] +fn split_horizontal_followed_by_merge_horizontal() { + ExtBuilder::build().execute_with(|| { + let alice = Account::new(0).deposit(Asset::Ztg, _100).unwrap(); + + let market_id = create_market(Asset::Ztg, MarketType::Categorical(3)); + let amount = _1; + + let ct_001 = CombinatorialToken([ + 207, 168, 160, 93, 238, 221, 197, 1, 171, 102, 28, 24, 18, 107, 205, 231, 227, 98, 220, + 105, 211, 29, 181, 30, 53, 7, 200, 154, 134, 246, 38, 139, + ]); + let ct_110 = CombinatorialToken([ + 101, 210, 61, 196, 5, 247, 150, 41, 186, 49, 11, 63, 139, 53, 25, 65, 161, 83, 24, 142, + 225, 102, 57, 241, 199, 18, 226, 137, 68, 3, 219, 131, + ]); + + assert_ok!(CombinatorialTokens::split_position( + alice.signed(), + None, + market_id, + vec![vec![B0, B0, B1], vec![B1, B1, B0]], + amount, + )); + + assert_ok!(CombinatorialTokens::split_position( + alice.signed(), + None, + market_id, + vec![vec![B1, B0, B0], vec![B0, B1, B0]], + amount, + )); + + assert_ok!(CombinatorialTokens::merge_position( + alice.signed(), + None, + market_id, + vec![vec![B1, B0, B0], vec![B0, B1, B0]], + amount, + )); + + assert_eq!(alice.free_balance(ct_001), _1); + assert_eq!(alice.free_balance(ct_110), _1); + }); +} diff --git a/zrml/combinatorial-tokens/src/tests/merge_position.rs b/zrml/combinatorial-tokens/src/tests/merge_position.rs new file mode 100644 index 000000000..5b9f8bea0 --- /dev/null +++ b/zrml/combinatorial-tokens/src/tests/merge_position.rs @@ -0,0 +1,231 @@ +use super::*; +use test_case::test_case; + +#[test_case( + Asset::Ztg, + CombinatorialToken([207, 168, 160, 93, 238, 221, 197, 1, 171, 102, 28, 24, 18, 107, 205, 231, 227, 98, 220, 105, 211, 29, 181, 30, 53, 7, 200, 154, 134, 246, 38, 139]), + CombinatorialToken([101, 210, 61, 196, 5, 247, 150, 41, 186, 49, 11, 63, 139, 53, 25, 65, 161, 83, 24, 142, 225, 102, 57, 241, 199, 18, 226, 137, 68, 3, 219, 131]) +)] +#[test_case( + Asset::ForeignAsset(1), + CombinatorialToken([97, 71, 129, 186, 219, 73, 163, 242, 183, 111, 224, 26, 45, 104, 11, 229, 241, 31, 154, 126, 118, 218, 142, 191, 3, 255, 156, 77, 32, 1, 66, 227]), + CombinatorialToken([156, 42, 42, 43, 18, 242, 8, 247, 100, 196, 173, 111, 167, 225, 207, 149, 166, 194, 255, 1, 238, 128, 72, 199, 188, 57, 236, 168, 26, 58, 104, 156]) +)] +fn merge_position_works_no_parent( + collateral: Asset, + ct_001: Asset, + ct_110: Asset, +) { + ExtBuilder::build().execute_with(|| { + let amount = _100; + let alice = + Account::new(0).deposit(ct_001, amount).unwrap().deposit(ct_110, amount).unwrap(); + // Mock a deposit into the pallet's account. + let pallet = + Account::new(Pallet::::account_id()).deposit(collateral, amount).unwrap(); + + let market_id = create_market(collateral, MarketType::Categorical(3)); + + assert_ok!(CombinatorialTokens::merge_position( + alice.signed(), + None, + market_id, + vec![vec![B0, B0, B1], vec![B1, B1, B0]], + amount, + )); + + assert_eq!(alice.free_balance(ct_001), 0); + assert_eq!(alice.free_balance(ct_110), 0); + assert_eq!(alice.free_balance(collateral), _100); + assert_eq!(pallet.free_balance(collateral), 0); + }); +} + +#[test] +fn merge_position_works_parent() { + ExtBuilder::build().execute_with(|| { + let ct_001 = CombinatorialToken([ + 207, 168, 160, 93, 238, 221, 197, 1, 171, 102, 28, 24, 18, 107, 205, 231, 227, 98, 220, + 105, 211, 29, 181, 30, 53, 7, 200, 154, 134, 246, 38, 139, + ]); + let ct_001_0101 = CombinatorialToken([ + 38, 14, 141, 152, 199, 40, 88, 165, 208, 236, 195, 198, 208, 75, 93, 85, 114, 4, 175, + 225, 211, 72, 142, 210, 98, 202, 168, 193, 245, 217, 239, 28, + ]); + let ct_001_1010 = CombinatorialToken([ + 107, 142, 3, 38, 49, 137, 237, 239, 1, 131, 197, 221, 236, 46, 246, 93, 185, 197, 228, + 184, 75, 79, 107, 73, 89, 19, 22, 124, 15, 58, 110, 100, + ]); + + let amount = _100; + let alice = Account::new(0) + .deposit(ct_001_0101, amount) + .unwrap() + .deposit(ct_001_1010, amount) + .unwrap(); + + let _ = create_market(Asset::Ztg, MarketType::Categorical(3)); + let market_id = create_market(Asset::Ztg, MarketType::Categorical(4)); + + // Collection ID of [0, 0, 1]. + let parent_collection_id = [ + 6, 44, 173, 50, 122, 106, 144, 185, 253, 19, 252, 218, 215, 241, 218, 37, 196, 112, 45, + 133, 165, 48, 231, 189, 87, 123, 131, 18, 190, 5, 110, 93, + ]; + + assert_ok!(CombinatorialTokens::merge_position( + alice.signed(), + Some(parent_collection_id), + market_id, + vec![vec![B0, B1, B0, B1], vec![B1, B0, B1, B0]], + amount, + )); + + assert_eq!(alice.free_balance(ct_001), amount); + assert_eq!(alice.free_balance(ct_001_0101), 0); + assert_eq!(alice.free_balance(ct_001_1010), 0); + }); +} + +#[test] +fn merge_position_horizontal_works() { + ExtBuilder::build().execute_with(|| { + let ct_100 = CombinatorialToken([ + 63, 95, 93, 48, 199, 160, 113, 178, 33, 24, 52, 193, 247, 121, 229, 30, 231, 100, 209, + 14, 57, 98, 193, 214, 34, 251, 53, 51, 136, 146, 93, 26, + ]); + let ct_010 = CombinatorialToken([ + 23, 108, 101, 109, 145, 51, 201, 192, 240, 28, 43, 57, 53, 4, 75, 101, 116, 20, 184, + 25, 227, 71, 149, 136, 59, 82, 81, 105, 41, 160, 39, 142, + ]); + let ct_110 = CombinatorialToken([ + 101, 210, 61, 196, 5, 247, 150, 41, 186, 49, 11, 63, 139, 53, 25, 65, 161, 83, 24, 142, + 225, 102, 57, 241, 199, 18, 226, 137, 68, 3, 219, 131, + ]); + + let amount = _100; + let alice = Account::new(0).deposit(ct_100, _100).unwrap().deposit(ct_010, _100).unwrap(); + + let market_id = create_market(Asset::Ztg, MarketType::Categorical(3)); + + assert_ok!(CombinatorialTokens::merge_position( + alice.signed(), + None, + market_id, + vec![vec![B0, B1, B0], vec![B1, B0, B0]], + amount, + )); + + assert_eq!(alice.free_balance(ct_110), amount); + assert_eq!(alice.free_balance(ct_100), 0); + assert_eq!(alice.free_balance(ct_010), 0); + }); +} + +#[test] +fn merge_position_fails_if_market_not_found() { + ExtBuilder::build().execute_with(|| { + let alice = Account::new(0).deposit(Asset::Ztg, _100).unwrap(); + + assert_noop!( + CombinatorialTokens::merge_position( + alice.signed(), + None, + 0, + vec![vec![B0, B0, B1], vec![B1, B1, B0]], + 1, + ), + zrml_market_commons::Error::::MarketDoesNotExist, + ); + }); +} + +#[test] +fn merge_position_fails_on_invalid_partition_length() { + ExtBuilder::build().execute_with(|| { + let alice = Account::new(0).deposit(Asset::Ztg, _100).unwrap(); + + // Market has three outcomes, but there's an element in the partition of size two. + let market_id = create_market(Asset::Ztg, MarketType::Categorical(3)); + let partition = vec![vec![B1, B0, B1], vec![B0, B1]]; + + assert_noop!( + CombinatorialTokens::merge_position(alice.signed(), None, market_id, partition, _1,), + Error::::InvalidPartition + ); + }); +} + +#[test] +fn merge_position_fails_on_trivial_partition_member() { + ExtBuilder::build().execute_with(|| { + let alice = Account::new(0).deposit(Asset::Ztg, _100).unwrap(); + + // Market has three outcomes, but there's an element in the partition of size two. + let market_id = create_market(Asset::Ztg, MarketType::Categorical(3)); + let partition = vec![vec![B1, B0, B1], vec![B0, B0, B0]]; + + assert_noop!( + CombinatorialTokens::merge_position(alice.signed(), None, market_id, partition, _1,), + Error::::InvalidPartition + ); + }); +} + +#[test] +fn merge_position_fails_on_overlapping_partition_members() { + ExtBuilder::build().execute_with(|| { + let alice = Account::new(0).deposit(Asset::Ztg, _100).unwrap(); + + // Market has three outcomes, but there's an element in the partition of size two. + let market_id = create_market(Asset::Ztg, MarketType::Categorical(3)); + let partition = vec![vec![B1, B0, B1], vec![B0, B0, B1]]; + + assert_noop!( + CombinatorialTokens::merge_position(alice.signed(), None, market_id, partition, _1,), + Error::::InvalidPartition + ); + }); +} + +#[test] +fn merge_position_fails_on_insufficient_funds() { + ExtBuilder::build().execute_with(|| { + let alice = Account::new(0).deposit(Asset::Ztg, _99).unwrap(); + + // Market has three outcomes, but there's an element in the partition of size two. + let market_id = create_market(Asset::Ztg, MarketType::Categorical(3)); + + assert_noop!( + CombinatorialTokens::merge_position( + alice.signed(), + None, + market_id, + vec![vec![B1, B0, B1], vec![B0, B1, B0]], + _100, + ), + orml_tokens::Error::::BalanceTooLow + ); + }); +} + +#[test] +fn merge_position_fails_on_insufficient_funds_foreign_token() { + ExtBuilder::build().execute_with(|| { + let alice = Account::new(0).deposit(Asset::ForeignAsset(1), _99).unwrap(); + + // Market has three outcomes, but there's an element in the partition of size two. + let market_id = create_market(Asset::Ztg, MarketType::Categorical(3)); + + assert_noop!( + CombinatorialTokens::merge_position( + alice.signed(), + None, + market_id, + vec![vec![B1, B0, B1], vec![B0, B1, B0]], + _100, + ), + orml_tokens::Error::::BalanceTooLow + ); + }); +} diff --git a/zrml/combinatorial-tokens/src/tests/mod.rs b/zrml/combinatorial-tokens/src/tests/mod.rs new file mode 100644 index 000000000..4f0bd785c --- /dev/null +++ b/zrml/combinatorial-tokens/src/tests/mod.rs @@ -0,0 +1,81 @@ +#![cfg(all(feature = "mock", test))] + +mod integration; +mod merge_position; +mod split_position; + +use crate::{ + mock::{ + ext_builder::ExtBuilder, + runtime::{CombinatorialTokens, Currencies, MarketCommons, Runtime, RuntimeOrigin, System}, + }, + Error, Event, Pallet, +}; +use frame_support::{assert_noop, assert_ok}; +use orml_traits::MultiCurrency; +use sp_runtime::{DispatchError, Perbill}; +use zeitgeist_primitives::{ + constants::base_multiples::*, + types::{ + AccountIdTest, Asset, Asset::CombinatorialToken, Balance, Market, MarketBonds, + MarketCreation, MarketId, MarketPeriod, MarketStatus, MarketType, ScoringRule, + }, +}; +use zrml_market_commons::MarketCommonsPalletApi; + +// For better readability of index sets. +pub(crate) const B0: bool = false; +pub(crate) const B1: bool = true; + +fn create_market(base_asset: Asset, market_type: MarketType) -> MarketId { + let market = Market { + base_asset, + market_id: Default::default(), + creation: MarketCreation::Permissionless, + creator_fee: Perbill::zero(), + creator: Default::default(), + market_type, + dispute_mechanism: None, + metadata: Default::default(), + oracle: Default::default(), + period: MarketPeriod::Block(Default::default()), + deadlines: Default::default(), + report: None, + resolved_outcome: None, + scoring_rule: ScoringRule::AmmCdaHybrid, + status: MarketStatus::Disputed, + bonds: MarketBonds::default(), + early_close: None, + }; + MarketCommons::push_market(market).unwrap(); + MarketCommons::latest_market_id().unwrap() +} + +/// Utility struct for managing test accounts. +pub(crate) struct Account { + id: AccountIdTest, +} + +impl Account { + // TODO Not a pressing issue, but double booking accounts should be illegal. + pub(crate) fn new(id: AccountIdTest) -> Account { + Account { id } + } + + /// Deposits `amount` of `asset` and returns the account to allow call chains. + pub(crate) fn deposit( + self, + asset: Asset, + amount: Balance, + ) -> Result { + Currencies::deposit(asset, &self.id, amount).map(|_| self) + } + + pub(crate) fn signed(&self) -> RuntimeOrigin { + RuntimeOrigin::signed(self.id) + } + + pub(crate) fn free_balance(&self, asset: Asset) -> Balance { + Currencies::free_balance(asset, &self.id) + } +} diff --git a/zrml/combinatorial-tokens/src/tests/split_position.rs b/zrml/combinatorial-tokens/src/tests/split_position.rs new file mode 100644 index 000000000..7109fc13d --- /dev/null +++ b/zrml/combinatorial-tokens/src/tests/split_position.rs @@ -0,0 +1,351 @@ +use super::*; + +#[test] +fn split_position_works_vertical_no_parent() { + ExtBuilder::build().execute_with(|| { + let alice = Account::new(0).deposit(Asset::Ztg, _100).unwrap(); + let pallet = Account::new(Pallet::::account_id()); + + let parent_collection_id = None; + let market_id = create_market(Asset::Ztg, MarketType::Categorical(3)); + let partition = vec![vec![B0, B0, B1], vec![B1, B1, B0]]; + + let amount = _1; + assert_ok!(CombinatorialTokens::split_position( + alice.signed(), + parent_collection_id, + market_id, + partition.clone(), + amount, + )); + + let ct_001 = CombinatorialToken([ + 207, 168, 160, 93, 238, 221, 197, 1, 171, 102, 28, 24, 18, 107, 205, 231, 227, 98, 220, + 105, 211, 29, 181, 30, 53, 7, 200, 154, 134, 246, 38, 139, + ]); + let ct_110 = CombinatorialToken([ + 101, 210, 61, 196, 5, 247, 150, 41, 186, 49, 11, 63, 139, 53, 25, 65, 161, 83, 24, 142, + 225, 102, 57, 241, 199, 18, 226, 137, 68, 3, 219, 131, + ]); + + assert_eq!(alice.free_balance(ct_001), amount); + assert_eq!(alice.free_balance(ct_110), amount); + assert_eq!(alice.free_balance(Asset::Ztg), _100 - amount); + assert_eq!(pallet.free_balance(Asset::Ztg), amount); + + System::assert_last_event( + Event::::TokenSplit { + who: alice.id, + parent_collection_id, + market_id, + partition, + asset_in: Asset::Ztg, + assets_out: vec![ct_001, ct_110], + collection_ids: vec![ + [ + 6, 44, 173, 50, 122, 106, 144, 185, 253, 19, 252, 218, 215, 241, 218, 37, + 196, 112, 45, 133, 165, 48, 231, 189, 87, 123, 131, 18, 190, 5, 110, 93, + ], + [ + 1, 189, 94, 224, 153, 162, 145, 214, 33, 231, 230, 19, 122, 179, 122, 117, + 193, 123, 73, 220, 240, 131, 180, 180, 137, 14, 179, 148, 188, 13, 107, 65, + ], + ], + amount, + } + .into(), + ); + }); +} + +#[test] +fn split_position_works_vertical_with_parent() { + ExtBuilder::build().execute_with(|| { + let alice = Account::new(0).deposit(Asset::Ztg, _100).unwrap(); + let pallet = Account::new(Pallet::::account_id()); + + let parent_market_id = create_market(Asset::Ztg, MarketType::Categorical(3)); + let parent_amount = _3; + assert_ok!(CombinatorialTokens::split_position( + alice.signed(), + None, + parent_market_id, + vec![vec![B0, B0, B1], vec![B1, B1, B0]], + parent_amount, + )); + + let child_market_id = create_market(Asset::Ztg, MarketType::Categorical(4)); + let child_amount = _1; + // Collection ID of [0, 0, 1]. + let parent_collection_id = [ + 6, 44, 173, 50, 122, 106, 144, 185, 253, 19, 252, 218, 215, 241, 218, 37, 196, 112, 45, + 133, 165, 48, 231, 189, 87, 123, 131, 18, 190, 5, 110, 93, + ]; + let partition = vec![vec![B0, B1, B0, B1], vec![B1, B0, B1, B0]]; + assert_ok!(CombinatorialTokens::split_position( + alice.signed(), + Some(parent_collection_id), + child_market_id, + partition.clone(), + child_amount, + )); + + // Alice is left with 1 unit of [0, 0, 1], 2 units of [1, 1, 0] and one unit of each of the + // two new tokens. + let ct_001 = CombinatorialToken([ + 207, 168, 160, 93, 238, 221, 197, 1, 171, 102, 28, 24, 18, 107, 205, 231, 227, 98, 220, + 105, 211, 29, 181, 30, 53, 7, 200, 154, 134, 246, 38, 139, + ]); + let ct_110 = CombinatorialToken([ + 101, 210, 61, 196, 5, 247, 150, 41, 186, 49, 11, 63, 139, 53, 25, 65, 161, 83, 24, 142, + 225, 102, 57, 241, 199, 18, 226, 137, 68, 3, 219, 131, + ]); + let ct_001_0101 = CombinatorialToken([ + 38, 14, 141, 152, 199, 40, 88, 165, 208, 236, 195, 198, 208, 75, 93, 85, 114, 4, 175, + 225, 211, 72, 142, 210, 98, 202, 168, 193, 245, 217, 239, 28, + ]); + let ct_001_1010 = CombinatorialToken([ + 107, 142, 3, 38, 49, 137, 237, 239, 1, 131, 197, 221, 236, 46, 246, 93, 185, 197, 228, + 184, 75, 79, 107, 73, 89, 19, 22, 124, 15, 58, 110, 100, + ]); + + assert_eq!(alice.free_balance(Asset::Ztg), _100 - parent_amount); + assert_eq!(alice.free_balance(ct_001), parent_amount - child_amount); + assert_eq!(alice.free_balance(ct_110), parent_amount); + assert_eq!(alice.free_balance(ct_001_0101), child_amount); + assert_eq!(alice.free_balance(ct_001_1010), child_amount); + assert_eq!(pallet.free_balance(Asset::Ztg), parent_amount); + assert_eq!(pallet.free_balance(ct_001), 0); // Combinatorial tokens are destroyed when split. + + System::assert_last_event( + Event::::TokenSplit { + who: alice.id, + parent_collection_id: Some(parent_collection_id), + market_id: child_market_id, + partition, + asset_in: ct_001, + assets_out: vec![ct_001_0101, ct_001_1010], + collection_ids: vec![ + [ + 93, 24, 254, 39, 137, 146, 204, 128, 95, 226, 32, 110, 212, 68, 65, 13, + 128, 86, 96, 119, 117, 240, 144, 57, 224, 160, 106, 176, 250, 172, 157, 47, + ], + [ + 98, 123, 162, 148, 54, 175, 126, 250, 173, 76, 229, 156, 108, 125, 245, 68, + 132, 230, 48, 72, 247, 45, 233, 27, 100, 225, 243, 113, 21, 69, 45, 113, + ], + ], + amount: child_amount, + } + .into(), + ); + }); +} + +// Intentionally left out as it is covered by +// `integration::vertical_split_followed_by_horizontal_split_no_parent`. +// #[test] +// fn split_position_works_horizontal_no_parent() {} + +// Intentionally left out as it is covered by +// `integration::vertical_split_followed_by_horizontal_split_with_parent`. +// #[test] +// fn split_position_works_horizontal_with_parent() {} + +#[test] +fn split_position_fails_if_market_not_found() { + ExtBuilder::build().execute_with(|| { + let alice = Account::new(0).deposit(Asset::Ztg, _100).unwrap(); + assert_noop!( + CombinatorialTokens::split_position( + alice.signed(), + None, + 0, + vec![vec![B0, B0, B1], vec![B1, B1, B0]], + 1, + ), + zrml_market_commons::Error::::MarketDoesNotExist, + ); + }); +} + +#[test] +fn split_position_fails_on_invalid_partition_length() { + ExtBuilder::build().execute_with(|| { + let alice = Account::new(0).deposit(Asset::Ztg, _100).unwrap(); + + // Market has three outcomes, but there's an element in the partition of size two. + let market_id = create_market(Asset::Ztg, MarketType::Categorical(3)); + let partition = vec![vec![B1, B0, B1], vec![B0, B1]]; + + assert_noop!( + CombinatorialTokens::split_position(alice.signed(), None, market_id, partition, _1), + Error::::InvalidPartition + ); + }); +} + +#[test] +fn split_position_fails_on_empty_partition_member() { + ExtBuilder::build().execute_with(|| { + let alice = Account::new(0).deposit(Asset::Ztg, _100).unwrap(); + + // Second element is empty. + let market_id = create_market(Asset::Ztg, MarketType::Categorical(3)); + let partition = vec![vec![B1, B0, B1], vec![B0, B0, B0]]; + + assert_noop!( + CombinatorialTokens::split_position(alice.signed(), None, market_id, partition, _1,), + Error::::InvalidPartition + ); + }); +} + +#[test] +fn split_position_fails_on_overlapping_partition_members() { + ExtBuilder::build().execute_with(|| { + let alice = Account::new(0).deposit(Asset::Ztg, _100).unwrap(); + + // Last elements overlap. + let market_id = create_market(Asset::Ztg, MarketType::Categorical(3)); + let partition = vec![vec![B1, B0, B1], vec![B0, B0, B1]]; + + assert_noop!( + CombinatorialTokens::split_position(alice.signed(), None, market_id, partition, _1), + Error::::InvalidPartition + ); + }); +} + +#[test] +fn split_position_fails_on_trivial_partition() { + ExtBuilder::build().execute_with(|| { + let alice = Account::new(0).deposit(Asset::Ztg, _100).unwrap(); + + let market_id = create_market(Asset::Ztg, MarketType::Categorical(3)); + let partition = vec![vec![B1, B1, B1]]; + + assert_noop!( + CombinatorialTokens::split_position(alice.signed(), None, market_id, partition, _1), + Error::::InvalidPartition + ); + }); +} + +#[test] +fn split_position_fails_on_insufficient_funds_native_token_no_parent() { + ExtBuilder::build().execute_with(|| { + let alice = Account::new(0).deposit(Asset::Ztg, _99).unwrap(); + + // Market has three outcomes, but there's an element in the partition of size two. + let market_id = create_market(Asset::Ztg, MarketType::Categorical(3)); + + assert_noop!( + CombinatorialTokens::split_position( + alice.signed(), + None, + market_id, + vec![vec![B1, B0, B1], vec![B0, B1, B0]], + _100, + ), + orml_currencies::Error::::BalanceTooLow + ); + }); +} + +#[test] +fn split_position_fails_on_insufficient_funds_foreign_token_no_parent() { + ExtBuilder::build().execute_with(|| { + let alice = Account::new(0).deposit(Asset::ForeignAsset(1), _99).unwrap(); + + // Market has three outcomes, but there's an element in the partition of size two. + let market_id = create_market(Asset::Ztg, MarketType::Categorical(3)); + + assert_noop!( + CombinatorialTokens::split_position( + alice.signed(), + None, + market_id, + vec![vec![B1, B0, B1], vec![B0, B1, B0]], + _100, + ), + orml_currencies::Error::::BalanceTooLow + ); + }); +} + +#[test] +fn split_position_vertical_fails_on_insufficient_funds_combinatorial_token() { + ExtBuilder::build().execute_with(|| { + let ct_001 = CombinatorialToken([ + 207, 168, 160, 93, 238, 221, 197, 1, 171, 102, 28, 24, 18, 107, 205, 231, 227, 98, 220, + 105, 211, 29, 181, 30, 53, 7, 200, 154, 134, 246, 38, 139, + ]); + + let alice = Account::new(0).deposit(ct_001, _99).unwrap(); + + // Collection ID of [0, 0, 1]. + let parent_collection_id = [ + 6, 44, 173, 50, 122, 106, 144, 185, 253, 19, 252, 218, 215, 241, 218, 37, 196, 112, 45, + 133, 165, 48, 231, 189, 87, 123, 131, 18, 190, 5, 110, 93, + ]; + + let _ = create_market(Asset::Ztg, MarketType::Categorical(3)); + let market_id = create_market(Asset::Ztg, MarketType::Categorical(4)); + + assert_noop!( + CombinatorialTokens::split_position( + alice.signed(), + Some(parent_collection_id), + market_id, + vec![vec![B1, B0, B1, B0], vec![B0, B1, B0, B1]], + _100, + ), + orml_tokens::Error::::BalanceTooLow + ); + + // Make sure that we're testing for the right balance. This call should work! + assert_ok!(CombinatorialTokens::split_position( + alice.signed(), + Some(parent_collection_id), + market_id, + vec![vec![B1, B0, B1, B0], vec![B0, B1, B0, B1]], + _99, + )); + }); +} + +#[test] +fn split_position_horizontal_fails_on_insufficient_funds_combinatorial_token() { + ExtBuilder::build().execute_with(|| { + let ct_110 = CombinatorialToken([ + 101, 210, 61, 196, 5, 247, 150, 41, 186, 49, 11, 63, 139, 53, 25, 65, 161, 83, 24, 142, + 225, 102, 57, 241, 199, 18, 226, 137, 68, 3, 219, 131, + ]); + + let alice = Account::new(0).deposit(ct_110, _99).unwrap(); + + // Market has three outcomes, but there's an element in the partition of size two. + let market_id = create_market(Asset::Ztg, MarketType::Categorical(3)); + + assert_noop!( + CombinatorialTokens::split_position( + alice.signed(), + None, + market_id, + vec![vec![B1, B0, B0], vec![B0, B1, B0]], + _100, + ), + orml_tokens::Error::::BalanceTooLow + ); + + // Make sure that we're testing for the right balance. This call should work! + assert_ok!(CombinatorialTokens::split_position( + alice.signed(), + None, + market_id, + vec![vec![B1, B0, B0], vec![B0, B1, B0]], + _99, + )); + }); +} From d0a07a56184dad48dd50e2137b0ba07d25f88e12 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Sun, 13 Oct 2024 20:22:25 +0200 Subject: [PATCH 09/73] Refine combinatorial betting (#1372) * Add numerical thresholds to combinatorial betting * Add protected `exp` for normal bets * Ensure correctness of partitions * Check partitions --- primitives/src/constants/base_multiples.rs | 2 + zrml/neo-swaps/src/lib.rs | 59 +++++++++++---- zrml/neo-swaps/src/math/types/combo_math.rs | 19 +++-- zrml/neo-swaps/src/math/types/math.rs | 22 +++--- zrml/neo-swaps/src/tests/combo_buy.rs | 60 +++++++++------- zrml/neo-swaps/src/tests/combo_sell.rs | 80 +++++++++++++-------- 6 files changed, 157 insertions(+), 85 deletions(-) diff --git a/primitives/src/constants/base_multiples.rs b/primitives/src/constants/base_multiples.rs index 5d2c4de2d..bdbba4657 100644 --- a/primitives/src/constants/base_multiples.rs +++ b/primitives/src/constants/base_multiples.rs @@ -60,6 +60,8 @@ pub const _1_5: u128 = _1 / 5; pub const _1_6: u128 = _1 / 6; pub const _5_6: u128 = _5 / 6; +pub const _1_7: u128 = _1 / 7; + pub const _1_10: u128 = _1 / 10; pub const _2_10: u128 = _2 / 10; pub const _3_10: u128 = _3 / 10; diff --git a/zrml/neo-swaps/src/lib.rs b/zrml/neo-swaps/src/lib.rs index afb42d490..7784ec8e0 100644 --- a/zrml/neo-swaps/src/lib.rs +++ b/zrml/neo-swaps/src/lib.rs @@ -46,7 +46,11 @@ mod pallet { types::{FeeDistribution, MaxAssets, Pool}, weights::*, }; - use alloc::{collections::BTreeMap, vec, vec::Vec}; + use alloc::{ + collections::{BTreeMap, BTreeSet}, + vec, + vec::Vec, + }; use core::marker::PhantomData; use frame_support::{ dispatch::DispatchResultWithPostInfo, @@ -297,6 +301,10 @@ mod pallet { MinRelativeLiquidityThresholdViolated, /// Narrowing type conversion occurred. NarrowingConversion, + + /// The buy/sell/keep partition specified is empty, or contains overlaps or assets that don't + /// belong to the market. + InvalidPartition, } #[derive(Decode, Encode, Eq, PartialEq, PalletError, RuntimeDebug, TypeInfo)] @@ -1027,13 +1035,21 @@ mod pallet { let market = T::MarketCommons::market(&market_id)?; ensure!(market.status == MarketStatus::Active, Error::::MarketNotActive); Self::try_mutate_pool(&market_id, |pool| { - for asset in buy.iter().chain(sell.iter()) { - ensure!(pool.contains(asset), Error::::AssetNotFound); + // Ensure that `buy` and `sell` partition are disjoint, only contain assets from + // the market and don't contain dupliates. + ensure!(!buy.is_empty(), Error::::InvalidPartition); + ensure!(!sell.is_empty(), Error::::InvalidPartition); + for asset in buy.iter() { + ensure!(!sell.contains(asset), Error::::InvalidPartition); + ensure!(market.outcome_assets().contains(asset), Error::::InvalidPartition); } - - // TODO Ensure that buy, sell partition the assets! - - // TODO Ensure that numerical limits are observed. + for asset in sell.iter() { + ensure!(market.outcome_assets().contains(asset), Error::::InvalidPartition); + } + let buy_set = buy.iter().collect::>(); + let sell_set = sell.iter().collect::>(); + ensure!(buy_set.len() == buy.len(), Error::::InvalidPartition); + ensure!(sell_set.len() == sell.len(), Error::::InvalidPartition); let FeeDistribution { remaining: amount_in_minus_fees, @@ -1100,13 +1116,30 @@ mod pallet { let market = T::MarketCommons::market(&market_id)?; ensure!(market.status == MarketStatus::Active, Error::::MarketNotActive); Self::try_mutate_pool(&market_id, |pool| { - for asset in buy.iter().chain(sell.iter()).chain(keep.iter()) { - ensure!(pool.contains(asset), Error::::AssetNotFound); + // Ensure that `buy` and `sell` partition are disjoint and only contain assets from + // the market. + ensure!(!buy.is_empty(), Error::::InvalidPartition); + ensure!(!sell.is_empty(), Error::::InvalidPartition); + for asset in buy.iter() { + ensure!(!keep.contains(asset), Error::::InvalidPartition); + ensure!(!sell.contains(asset), Error::::InvalidPartition); + ensure!(market.outcome_assets().contains(asset), Error::::InvalidPartition); } - - // TODO Ensure that buy, sell partition the assets! - - // TODO Ensure that numerical limits are observed. + for asset in sell.iter() { + ensure!(!keep.contains(asset), Error::::InvalidPartition); + ensure!(market.outcome_assets().contains(asset), Error::::InvalidPartition); + } + for asset in keep.iter() { + ensure!(market.outcome_assets().contains(asset), Error::::InvalidPartition); + } + let buy_set = buy.iter().collect::>(); + let keep_set = keep.iter().collect::>(); + let sell_set = sell.iter().collect::>(); + ensure!(buy_set.len() == buy.len(), Error::::InvalidPartition); + ensure!(keep_set.len() == keep.len(), Error::::InvalidPartition); + ensure!(sell_set.len() == sell.len(), Error::::InvalidPartition); + let total_assets = buy.len().saturating_add(keep.len()).saturating_add(sell.len()); + ensure!(total_assets == market.outcomes() as usize, Error::::InvalidPartition); // This is the amount of collateral the user will receive in the end, or, // equivalently, the amount of each asset in `sell` that the user intermittently diff --git a/zrml/neo-swaps/src/math/types/combo_math.rs b/zrml/neo-swaps/src/math/types/combo_math.rs index 3e4afe59a..bfdb4e62a 100644 --- a/zrml/neo-swaps/src/math/types/combo_math.rs +++ b/zrml/neo-swaps/src/math/types/combo_math.rs @@ -31,9 +31,8 @@ use typenum::U80; type Fractional = U80; type FixedType = FixedU128; -/// The point at which 32.44892769177272 -#[allow(dead_code)] // TODO Block calls that go outside of these bounds. -const EXP_OVERFLOW_THRESHOLD: FixedType = FixedType::from_bits(0x20_72EC_ECDA_6EBE_EACC_40C7); +/// The point at which `exp` values become too large, 32.44892769177272. +const EXP_NUMERICAL_THRESHOLD: FixedType = FixedType::from_bits(0x20_72EC_ECDA_6EBE_EACC_40C7); pub(crate) struct ComboMath(PhantomData); @@ -134,12 +133,18 @@ mod detail { value.to_fixed_decimal(DECIMALS).ok() } + /// Calculates `exp(value)` but returns `None` if `value` lies outside of the numerical + /// boundaries. + fn protected_exp(value: FixedType, neg: bool) -> Option { + if value < EXP_NUMERICAL_THRESHOLD { exp(value, neg).ok() } else { None } + } + /// Returns `\sum_{r \in R} e^{-r/b}`, where `R` denotes `reserves` and `b` denotes `liquidity`. - /// The result is `None` if and only if one of the `exp` calculations has failed. + /// The result is `None` if and only if any of the `exp` calculations has failed. fn exp_sum(reserves: Vec, liquidity: FixedType) -> Option { reserves .iter() - .map(|r| exp(r.checked_div(liquidity)?, true).ok()) + .map(|r| protected_exp(r.checked_div(liquidity)?, true)) .collect::>>()? .iter() .try_fold(FixedType::zero(), |acc, &val| acc.checked_add(val)) @@ -222,7 +227,7 @@ mod detail { let exp_sum_buy = exp_sum(buy, liquidity)?; let exp_sum_sell = exp_sum(sell, liquidity)?; let amount_in_div_liquidity = amount_in.checked_div(liquidity)?; - let exp_of_minus_amount_in: FixedType = exp(amount_in_div_liquidity, true).ok()?; + let exp_of_minus_amount_in: FixedType = protected_exp(amount_in_div_liquidity, true)?; let exp_of_minus_amount_in_times_exp_sum_sell = exp_of_minus_amount_in.checked_mul(exp_sum_sell)?; let numerator = exp_sum_buy @@ -249,7 +254,7 @@ mod detail { let numerator = exp_sum_buy.checked_add(exp_sum_sell)?; let delta = amount_buy.checked_sub(amount_sell)?; let delta_div_liquidity = delta.checked_div(liquidity)?; - let exp_delta: FixedType = exp(delta_div_liquidity, false).ok()?; + let exp_delta: FixedType = protected_exp(delta_div_liquidity, false)?; let exp_delta_times_exp_sum_sell = exp_delta.checked_mul(exp_sum_sell)?; let denominator = exp_sum_buy.checked_add(exp_delta_times_exp_sum_sell)?; let ln_arg = numerator.checked_div(denominator)?; diff --git a/zrml/neo-swaps/src/math/types/math.rs b/zrml/neo-swaps/src/math/types/math.rs index 8b311e28f..68cc38ac6 100644 --- a/zrml/neo-swaps/src/math/types/math.rs +++ b/zrml/neo-swaps/src/math/types/math.rs @@ -35,7 +35,7 @@ type Fractional = U80; type FixedType = FixedU128; // 32.44892769177272 -const EXP_OVERFLOW_THRESHOLD: FixedType = FixedType::from_bits(0x20_72EC_ECDA_6EBE_EACC_40C7); +const EXP_NUMERICAL_THRESHOLD: FixedType = FixedType::from_bits(0x20_72EC_ECDA_6EBE_EACC_40C7); pub(crate) struct Math(PhantomData); @@ -242,6 +242,12 @@ mod detail { value.to_fixed_decimal(DECIMALS).ok() } + /// Calculates `exp(value)` but returns `None` if `value` lies outside of the numerical + /// boundaries. + fn protected_exp(value: FixedType, neg: bool) -> Option { + if value < EXP_NUMERICAL_THRESHOLD { exp(value, neg).ok() } else { None } + } + fn calculate_swap_amount_out_for_buy_fixed( reserve: FixedType, amount_in: FixedType, @@ -264,8 +270,8 @@ mod detail { // Ensure that if the reserve is zero, we don't accidentally return a non-zero value. return None; } - let exp_neg_x_over_b: FixedType = exp(amount_in.checked_div(liquidity)?, true).ok()?; - let exp_r_over_b = exp(reserve.checked_div(liquidity)?, false).ok()?; + let exp_neg_x_over_b: FixedType = protected_exp(amount_in.checked_div(liquidity)?, true)?; + let exp_r_over_b = protected_exp(reserve.checked_div(liquidity)?, false)?; let inside_ln = exp_neg_x_over_b .checked_add(exp_r_over_b)? .checked_sub(FixedType::checked_from_num(1)?)?; @@ -278,7 +284,7 @@ mod detail { reserve: FixedType, liquidity: FixedType, ) -> Option { - exp(reserve.checked_div(liquidity)?, true).ok() + protected_exp(reserve.checked_div(liquidity)?, true) } fn calculate_reserve_from_spot_prices_fixed( @@ -308,10 +314,10 @@ mod detail { amount_in: FixedType, liquidity: FixedType, ) -> Option { - let exp_x_over_b: FixedType = exp(amount_in.checked_div(liquidity)?, false).ok()?; + let exp_x_over_b: FixedType = protected_exp(amount_in.checked_div(liquidity)?, false)?; let r_over_b = reserve.checked_div(liquidity)?; - let exp_neg_r_over_b = if r_over_b < EXP_OVERFLOW_THRESHOLD { - exp(reserve.checked_div(liquidity)?, true).ok()? + let exp_neg_r_over_b = if r_over_b < EXP_NUMERICAL_THRESHOLD { + protected_exp(reserve.checked_div(liquidity)?, true)? } else { FixedType::checked_from_num(0)? // Underflow to zero. }; @@ -573,7 +579,7 @@ mod tests { #[test_case(true, FixedType::from_str("0.000000000000008083692034").unwrap())] fn exp_does_not_overflow_or_underflow(neg: bool, expected: FixedType) { let result: FixedType = - exp(FixedType::checked_from_num(EXP_OVERFLOW_THRESHOLD).unwrap(), neg).unwrap(); + exp(FixedType::checked_from_num(EXP_NUMERICAL_THRESHOLD).unwrap(), neg).unwrap(); assert_eq!(result, expected); } diff --git a/zrml/neo-swaps/src/tests/combo_buy.rs b/zrml/neo-swaps/src/tests/combo_buy.rs index c589a6aff..ae9492789 100644 --- a/zrml/neo-swaps/src/tests/combo_buy.rs +++ b/zrml/neo-swaps/src/tests/combo_buy.rs @@ -19,6 +19,7 @@ use super::*; #[cfg(not(feature = "parachain"))] use sp_runtime::{DispatchError, TokenError}; use test_case::test_case; +use zeitgeist_primitives::types::Asset::CategoricalOutcome; // Example taken from // https://docs.gnosis.io/conditionaltokens/docs/introduction3/#an-example-with-lmsr @@ -210,33 +211,6 @@ fn combo_buy_fails_on_pool_not_found() { }); } -#[test_case(MarketType::Categorical(2))] -#[test_case(MarketType::Scalar(0..=1))] -fn combo_buy_fails_on_asset_not_found(market_type: MarketType) { - ExtBuilder::default().build().execute_with(|| { - let market_id = create_market_and_deploy_pool( - ALICE, - BASE_ASSET, - market_type, - _10, - vec![_1_2, _1_2], - CENT, - ); - assert_noop!( - NeoSwaps::combo_buy( - RuntimeOrigin::signed(BOB), - market_id, - 2, - vec![Asset::CategoricalOutcome(market_id, 2)], - vec![Asset::CategoricalOutcome(market_id, 1)], - _1, - 0 - ), - Error::::AssetNotFound, - ); - }); -} - #[test] fn combo_buy_fails_on_insufficient_funds() { ExtBuilder::default().build().execute_with(|| { @@ -293,3 +267,35 @@ fn combo_buy_fails_on_amount_out_below_min() { ); }); } + +#[test_case(vec![0], vec![0]; "overlap")] +#[test_case(vec![], vec![0, 1]; "empty_buy")] +#[test_case(vec![2, 3], vec![]; "empty_sell")] +#[test_case(vec![0, 2, 3], vec![1, 3, 4]; "overlap2")] +#[test_case(vec![0, 1, 3, 1], vec![2]; "duplicate_buy")] +#[test_case(vec![0, 1, 3], vec![4, 2, 4]; "duplicate_sell")] +#[test_case(vec![999], vec![0, 1, 2, 3, 4]; "out_of_bounds_buy")] +#[test_case(vec![0, 1, 3], vec![999]; "out_of_bounds_sell")] +fn combo_buy_fails_on_invalid_partition(indices_buy: Vec, indices_sell: Vec) { + ExtBuilder::default().build().execute_with(|| { + let market_id = create_market_and_deploy_pool( + ALICE, + BASE_ASSET, + MarketType::Categorical(5), + _10, + vec![_1_5, _1_5, _1_5, _1_5, _1_5], + CENT, + ); + let amount_in = _1; + assert_ok!(AssetManager::deposit(BASE_ASSET, &BOB, amount_in)); + + let buy = indices_buy.into_iter().map(|i| CategoricalOutcome(market_id, i)).collect(); + let sell = indices_sell.into_iter().map(|i| CategoricalOutcome(market_id, i)).collect(); + + // Buying 1 at price of .5 will return less than 2 outcomes due to slippage. + assert_noop!( + NeoSwaps::combo_buy(RuntimeOrigin::signed(BOB), market_id, 5, buy, sell, amount_in, 0), + Error::::InvalidPartition, + ); + }); +} diff --git a/zrml/neo-swaps/src/tests/combo_sell.rs b/zrml/neo-swaps/src/tests/combo_sell.rs index 4e9a92ad1..57ed1ee3b 100644 --- a/zrml/neo-swaps/src/tests/combo_sell.rs +++ b/zrml/neo-swaps/src/tests/combo_sell.rs @@ -17,6 +17,7 @@ use super::*; use test_case::test_case; +use zeitgeist_primitives::types::Asset::CategoricalOutcome; #[test] fn combo_sell_works() { @@ -215,36 +216,6 @@ fn combo_sell_fails_on_pool_not_found() { }); } -// TODO Needs to be expanded. -#[test_case(MarketType::Categorical(2))] -#[test_case(MarketType::Scalar(0..=1))] -fn combo_sell_fails_on_asset_not_found(market_type: MarketType) { - ExtBuilder::default().build().execute_with(|| { - let market_id = create_market_and_deploy_pool( - ALICE, - BASE_ASSET, - market_type, - _10, - vec![_1_2, _1_2], - CENT, - ); - assert_noop!( - NeoSwaps::combo_sell( - RuntimeOrigin::signed(BOB), - market_id, - 2, - vec![Asset::CategoricalOutcome(market_id, 3)], - vec![Asset::CategoricalOutcome(market_id, 5)], - vec![Asset::CategoricalOutcome(market_id, 4)], - _1, - 0, - u128::MAX, - ), - Error::::AssetNotFound, - ); - }); -} - #[test] fn combo_sell_fails_on_insufficient_funds() { ExtBuilder::default().build().execute_with(|| { @@ -307,3 +278,52 @@ fn combo_sell_fails_on_amount_out_below_min() { ); }); } + +#[test_case(vec![], vec![], vec![2]; "empty_buy")] +#[test_case(vec![0], vec![], vec![]; "empty_sell")] +#[test_case(vec![0, 1], vec![2, 1], vec![3, 4]; "buy_keep_overlap")] +#[test_case(vec![0, 1], vec![2, 4], vec![3, 1]; "buy_sell_overlap")] +#[test_case(vec![0, 1], vec![2, 4], vec![4, 3]; "keep_sell_overlap")] +#[test_case(vec![0, 1, 999], vec![2, 4], vec![5, 3]; "out_of_bounds_buy")] +#[test_case(vec![0, 1], vec![2, 4, 999], vec![5, 3]; "out_of_bounds_keep")] +#[test_case(vec![0, 1], vec![2, 4], vec![5, 999, 3]; "out_of_bounds_sell")] +#[test_case(vec![0, 6, 1, 6], vec![2, 4], vec![5, 3]; "duplicate_buy")] +#[test_case(vec![0, 1], vec![2, 2, 4], vec![5, 3]; "duplicate_keep")] +#[test_case(vec![0, 1], vec![2, 4], vec![5, 3, 6, 6, 6]; "duplicate_sell")] +fn combo_buy_fails_on_invalid_partition( + indices_buy: Vec, + indices_keep: Vec, + indices_sell: Vec, +) { + ExtBuilder::default().build().execute_with(|| { + println!("{:?}", _1_7); + let market_id = create_market_and_deploy_pool( + ALICE, + BASE_ASSET, + MarketType::Categorical(7), + _10, + vec![_1_7, _1_7, _1_7, _1_7, _1_7, _1_7, _1_7 + 4], + CENT, + ); + + let buy = indices_buy.into_iter().map(|i| CategoricalOutcome(market_id, i)).collect(); + let keep = indices_keep.into_iter().map(|i| CategoricalOutcome(market_id, i)).collect(); + let sell = indices_sell.into_iter().map(|i| CategoricalOutcome(market_id, i)).collect(); + + // Buying 1 at price of .5 will return less than 2 outcomes due to slippage. + assert_noop!( + NeoSwaps::combo_sell( + RuntimeOrigin::signed(BOB), + market_id, + 7, + buy, + keep, + sell, + _2, + _1, + 0 + ), + Error::::InvalidPartition, + ); + }); +} From 45aeeb8e0b049d904e323cca6de066639086717d Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Sun, 13 Oct 2024 21:29:29 +0200 Subject: [PATCH 10/73] Update copyright notices (#1373) * Add licenses * Add remark about LGPL-3.0 licensing by Gnosis --- zrml/combinatorial-tokens/src/lib.rs | 8 ++++++ zrml/combinatorial-tokens/src/mock/consts.rs | 3 +-- .../src/mock/ext_builder.rs | 19 ++++++++++++-- zrml/combinatorial-tokens/src/mock/mod.rs | 17 ++++++++++++- zrml/combinatorial-tokens/src/mock/runtime.rs | 19 ++++++++++++-- .../src/tests/integration.rs | 17 ++++++++++++- .../src/tests/merge_position.rs | 18 +++++++++++-- zrml/combinatorial-tokens/src/tests/mod.rs | 17 ++++++++++++- .../src/tests/split_position.rs | 17 ++++++++++++- .../src/traits/combinatorial_id_manager.rs | 25 ++++++++++++++++++- zrml/combinatorial-tokens/src/traits/mod.rs | 17 ++++++++++++- .../decompressor/mod.rs | 25 +++++++++++++++++-- .../tests/decompress_collection_id.rs | 18 +++++++++++-- .../decompressor/tests/decompress_hash.rs | 18 +++++++++++-- .../decompressor/tests/get_collection_id.rs | 18 +++++++++++-- .../tests/matching_y_coordinate.rs | 18 +++++++++++-- .../decompressor/tests/mod.rs | 17 ++++++++++++- .../decompressor/tests/pow_magic_number.rs | 18 +++++++++++-- .../cryptographic_id_manager/hash_tuple.rs | 19 ++++++++++++-- .../src/types/cryptographic_id_manager/mod.rs | 25 +++++++++++++++++-- zrml/combinatorial-tokens/src/types/hash.rs | 1 - zrml/combinatorial-tokens/src/types/mod.rs | 18 +++++++++++-- zrml/neo-swaps/src/math/types/mod.rs | 18 +++++++++++-- zrml/neo-swaps/src/tests/buy_and_sell.rs | 2 +- 24 files changed, 355 insertions(+), 37 deletions(-) delete mode 100644 zrml/combinatorial-tokens/src/types/hash.rs diff --git a/zrml/combinatorial-tokens/src/lib.rs b/zrml/combinatorial-tokens/src/lib.rs index 4c8c08b97..f12e77c2b 100644 --- a/zrml/combinatorial-tokens/src/lib.rs +++ b/zrml/combinatorial-tokens/src/lib.rs @@ -14,6 +14,14 @@ // // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . +// +// This file incorporates work licensed under the GNU Lesser General +// Public License 3.0 but published without copyright notice by Gnosis +// (, info@gnosis.io) in the +// conditional-tokens-contracts repository +// , +// and has been relicensed under GPL-3.0-or-later in this repository. + // TODO Refactor so that collection IDs are their own type with an `Fq` field and an `odd` field? diff --git a/zrml/combinatorial-tokens/src/mock/consts.rs b/zrml/combinatorial-tokens/src/mock/consts.rs index 6aecaf6f8..d4f1be3c2 100644 --- a/zrml/combinatorial-tokens/src/mock/consts.rs +++ b/zrml/combinatorial-tokens/src/mock/consts.rs @@ -1,5 +1,4 @@ -#[cfg(feature = "parachain")] -use zeitgeist_primitives::types::{Asset, MarketId}; +// Copyright 2024 Forecasting Technologies LTD. #[cfg(feature = "parachain")] pub(crate) const FOREIGN_ASSET: Asset = Asset::ForeignAsset(1); diff --git a/zrml/combinatorial-tokens/src/mock/ext_builder.rs b/zrml/combinatorial-tokens/src/mock/ext_builder.rs index 7a12e7d41..9fec5aaef 100644 --- a/zrml/combinatorial-tokens/src/mock/ext_builder.rs +++ b/zrml/combinatorial-tokens/src/mock/ext_builder.rs @@ -1,5 +1,20 @@ -use crate::mock::runtime::{Runtime, System}; -use sp_io::TestExternalities; +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + use sp_runtime::BuildStorage; #[cfg(feature = "parachain")] diff --git a/zrml/combinatorial-tokens/src/mock/mod.rs b/zrml/combinatorial-tokens/src/mock/mod.rs index f0140b64e..ab40046ad 100644 --- a/zrml/combinatorial-tokens/src/mock/mod.rs +++ b/zrml/combinatorial-tokens/src/mock/mod.rs @@ -1,4 +1,19 @@ -#![cfg(feature = "mock")] +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . pub(crate) mod consts; pub mod ext_builder; diff --git a/zrml/combinatorial-tokens/src/mock/runtime.rs b/zrml/combinatorial-tokens/src/mock/runtime.rs index 07c9c6a54..e66a929d8 100644 --- a/zrml/combinatorial-tokens/src/mock/runtime.rs +++ b/zrml/combinatorial-tokens/src/mock/runtime.rs @@ -1,5 +1,20 @@ -use crate as zrml_combinatorial_tokens; -use crate::types::CryptographicIdManager; +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + use frame_support::{construct_runtime, traits::Everything, Blake2_256}; use frame_system::mocking::MockBlock; use sp_runtime::traits::{BlakeTwo256, ConstU32, IdentityLookup}; diff --git a/zrml/combinatorial-tokens/src/tests/integration.rs b/zrml/combinatorial-tokens/src/tests/integration.rs index db95a2b6b..38d6020a6 100644 --- a/zrml/combinatorial-tokens/src/tests/integration.rs +++ b/zrml/combinatorial-tokens/src/tests/integration.rs @@ -1,4 +1,19 @@ -use super::*; +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . #[test] fn split_followed_by_merge_vertical_no_parent() { diff --git a/zrml/combinatorial-tokens/src/tests/merge_position.rs b/zrml/combinatorial-tokens/src/tests/merge_position.rs index 5b9f8bea0..40c8869cf 100644 --- a/zrml/combinatorial-tokens/src/tests/merge_position.rs +++ b/zrml/combinatorial-tokens/src/tests/merge_position.rs @@ -1,5 +1,19 @@ -use super::*; -use test_case::test_case; +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . #[test_case( Asset::Ztg, diff --git a/zrml/combinatorial-tokens/src/tests/mod.rs b/zrml/combinatorial-tokens/src/tests/mod.rs index 4f0bd785c..8eab9ea5c 100644 --- a/zrml/combinatorial-tokens/src/tests/mod.rs +++ b/zrml/combinatorial-tokens/src/tests/mod.rs @@ -1,4 +1,19 @@ -#![cfg(all(feature = "mock", test))] +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . mod integration; mod merge_position; diff --git a/zrml/combinatorial-tokens/src/tests/split_position.rs b/zrml/combinatorial-tokens/src/tests/split_position.rs index 7109fc13d..0191fb57d 100644 --- a/zrml/combinatorial-tokens/src/tests/split_position.rs +++ b/zrml/combinatorial-tokens/src/tests/split_position.rs @@ -1,4 +1,19 @@ -use super::*; +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . #[test] fn split_position_works_vertical_no_parent() { diff --git a/zrml/combinatorial-tokens/src/traits/combinatorial_id_manager.rs b/zrml/combinatorial-tokens/src/traits/combinatorial_id_manager.rs index 847c91fbc..18614d582 100644 --- a/zrml/combinatorial-tokens/src/traits/combinatorial_id_manager.rs +++ b/zrml/combinatorial-tokens/src/traits/combinatorial_id_manager.rs @@ -1,4 +1,27 @@ -use alloc::vec::Vec; +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . +// +// This file incorporates work licensed under the GNU Lesser General +// Public License 3.0 but published without copyright notice by Gnosis +// (, info@gnosis.io) in the +// conditional-tokens-contracts repository +// , +// and has been relicensed under GPL-3.0-or-later in this repository. + pub trait CombinatorialIdManager { type Asset; diff --git a/zrml/combinatorial-tokens/src/traits/mod.rs b/zrml/combinatorial-tokens/src/traits/mod.rs index 98642f028..2fdd5ba4f 100644 --- a/zrml/combinatorial-tokens/src/traits/mod.rs +++ b/zrml/combinatorial-tokens/src/traits/mod.rs @@ -1,3 +1,18 @@ -mod combinatorial_id_manager; +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . pub use combinatorial_id_manager::CombinatorialIdManager; diff --git a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/mod.rs b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/mod.rs index 6495589c3..392ae3c39 100644 --- a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/mod.rs +++ b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/mod.rs @@ -1,5 +1,26 @@ -/// Highest/lowest bit always refers to the big endian representation of each bit sequence. -mod tests; +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . +// +// This file incorporates work licensed under the GNU Lesser General +// Public License 3.0 but published without copyright notice by Gnosis +// (, info@gnosis.io) in the +// conditional-tokens-contracts repository +// , +// and has been relicensed under GPL-3.0-or-later in this repository. use ark_bn254::{g1::G1Affine, Fq}; use ark_ff::{BigInteger, PrimeField}; diff --git a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/decompress_collection_id.rs b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/decompress_collection_id.rs index d93b25645..33a6c4ecf 100644 --- a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/decompress_collection_id.rs +++ b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/decompress_collection_id.rs @@ -1,5 +1,19 @@ -use super::*; -use test_case::test_case; +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . #[test_case( [0x16, 0x74, 0xab, 0x10, 0xed, 0xf8, 0xc4, 0xe2, 0x25, 0x72, 0x9e, 0x20, 0x9a, 0x58, 0x75, 0xa1, 0x9f, 0x14, 0x46, 0xba, 0xec, 0x3b, 0x30, 0xdf, 0x9b, 0xa8, 0x65, 0x75, 0xd5, 0x2d, 0xe3, 0xd3], diff --git a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/decompress_hash.rs b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/decompress_hash.rs index f8cedde5b..2535b478e 100644 --- a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/decompress_hash.rs +++ b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/decompress_hash.rs @@ -1,5 +1,19 @@ -use super::*; -use rstest::rstest; +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . #[rstest] #[case( diff --git a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/get_collection_id.rs b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/get_collection_id.rs index 6c214ec97..8840c1577 100644 --- a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/get_collection_id.rs +++ b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/get_collection_id.rs @@ -1,5 +1,19 @@ -use super::*; -use rstest::rstest; +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . // Gnosis test cases using mocked keccak256 results, found here: https://docs.gnosis.io/conditionaltokens/docs/devguide05 #[rstest] diff --git a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/matching_y_coordinate.rs b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/matching_y_coordinate.rs index fc31a3685..6c6ba741a 100644 --- a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/matching_y_coordinate.rs +++ b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/matching_y_coordinate.rs @@ -1,5 +1,19 @@ -use super::*; -use test_case::test_case; +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . // Empty string in the `expected` argument signals `None`. #[test_case("0x00", "")] diff --git a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/mod.rs b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/mod.rs index 9097b9364..5fda00d14 100644 --- a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/mod.rs +++ b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/mod.rs @@ -1,4 +1,19 @@ -#![cfg(test)] +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . use super::*; diff --git a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/pow_magic_number.rs b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/pow_magic_number.rs index f42582800..a76eca901 100644 --- a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/pow_magic_number.rs +++ b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/pow_magic_number.rs @@ -1,5 +1,19 @@ -use super::*; -use test_case::test_case; +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . #[test_case("0x0", "0x0")] #[test_case("0x1", "0x1")] diff --git a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/hash_tuple.rs b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/hash_tuple.rs index 0aabbeccf..46377fdad 100644 --- a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/hash_tuple.rs +++ b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/hash_tuple.rs @@ -1,5 +1,20 @@ -use crate::types::Hash256; -use alloc::{vec, vec::Vec}; +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + use frame_support::{Blake2_256, StorageHasher}; use parity_scale_codec::Encode; use zeitgeist_primitives::types::Asset; diff --git a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/mod.rs b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/mod.rs index 18c8993ef..10ca64d8f 100644 --- a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/mod.rs +++ b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/mod.rs @@ -1,5 +1,26 @@ -mod decompressor; -mod hash_tuple; +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . +// +// This file incorporates work licensed under the GNU Lesser General +// Public License 3.0 but published without copyright notice by Gnosis +// (, info@gnosis.io) in the +// conditional-tokens-contracts repository +// , +// and has been relicensed under GPL-3.0-or-later in this repository. use crate::traits::CombinatorialIdManager; use alloc::vec::Vec; diff --git a/zrml/combinatorial-tokens/src/types/hash.rs b/zrml/combinatorial-tokens/src/types/hash.rs deleted file mode 100644 index bc57e98ec..000000000 --- a/zrml/combinatorial-tokens/src/types/hash.rs +++ /dev/null @@ -1 +0,0 @@ -pub type Hash256 = [u8; 32]; diff --git a/zrml/combinatorial-tokens/src/types/mod.rs b/zrml/combinatorial-tokens/src/types/mod.rs index a4d7d01fb..108ebf78b 100644 --- a/zrml/combinatorial-tokens/src/types/mod.rs +++ b/zrml/combinatorial-tokens/src/types/mod.rs @@ -1,5 +1,19 @@ -pub(crate) mod cryptographic_id_manager; -pub(crate) mod hash; +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . pub use cryptographic_id_manager::CryptographicIdManager; pub(crate) use hash::Hash256; diff --git a/zrml/neo-swaps/src/math/types/mod.rs b/zrml/neo-swaps/src/math/types/mod.rs index 43275d80f..d641dc96f 100644 --- a/zrml/neo-swaps/src/math/types/mod.rs +++ b/zrml/neo-swaps/src/math/types/mod.rs @@ -1,5 +1,19 @@ -mod combo_math; -mod math; +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . pub(crate) use combo_math::ComboMath; pub(crate) use math::Math; diff --git a/zrml/neo-swaps/src/tests/buy_and_sell.rs b/zrml/neo-swaps/src/tests/buy_and_sell.rs index 3d29969a3..8b596e725 100644 --- a/zrml/neo-swaps/src/tests/buy_and_sell.rs +++ b/zrml/neo-swaps/src/tests/buy_and_sell.rs @@ -1,4 +1,4 @@ -// Copyright 2023 Forecasting Technologies LTD. +// Copyright 2023-2024 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // From d87ae7c00713f4b30686e12e5905ee317cf1f129 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Sun, 13 Oct 2024 22:17:47 +0200 Subject: [PATCH 11/73] Fix compiler errors (#1374) * Fix formatting * Fix mess with copyright notices --- zrml/combinatorial-tokens/src/lib.rs | 1 - zrml/combinatorial-tokens/src/mock/consts.rs | 3 +++ .../src/mock/ext_builder.rs | 2 ++ zrml/combinatorial-tokens/src/mock/mod.rs | 2 ++ zrml/combinatorial-tokens/src/mock/runtime.rs | 2 ++ .../src/tests/integration.rs | 2 ++ .../src/tests/merge_position.rs | 3 +++ zrml/combinatorial-tokens/src/tests/mod.rs | 2 ++ .../src/tests/split_position.rs | 2 ++ .../src/traits/combinatorial_id_manager.rs | 1 + zrml/combinatorial-tokens/src/traits/mod.rs | 2 ++ .../decompressor/mod.rs | 4 ++++ .../tests/decompress_collection_id.rs | 3 +++ .../decompressor/tests/decompress_hash.rs | 3 +++ .../decompressor/tests/get_collection_id.rs | 3 +++ .../tests/matching_y_coordinate.rs | 3 +++ .../decompressor/tests/mod.rs | 2 ++ .../decompressor/tests/pow_magic_number.rs | 3 +++ .../cryptographic_id_manager/hash_tuple.rs | 3 +++ .../src/types/cryptographic_id_manager/mod.rs | 3 +++ zrml/combinatorial-tokens/src/types/hash.rs | 18 ++++++++++++++++++ zrml/combinatorial-tokens/src/types/mod.rs | 3 +++ zrml/neo-swaps/src/math/types/mod.rs | 3 +++ 23 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 zrml/combinatorial-tokens/src/types/hash.rs diff --git a/zrml/combinatorial-tokens/src/lib.rs b/zrml/combinatorial-tokens/src/lib.rs index f12e77c2b..e4c48c96d 100644 --- a/zrml/combinatorial-tokens/src/lib.rs +++ b/zrml/combinatorial-tokens/src/lib.rs @@ -22,7 +22,6 @@ // , // and has been relicensed under GPL-3.0-or-later in this repository. - // TODO Refactor so that collection IDs are their own type with an `Fq` field and an `odd` field? #![doc = include_str!("../README.md")] diff --git a/zrml/combinatorial-tokens/src/mock/consts.rs b/zrml/combinatorial-tokens/src/mock/consts.rs index d4f1be3c2..7d579595e 100644 --- a/zrml/combinatorial-tokens/src/mock/consts.rs +++ b/zrml/combinatorial-tokens/src/mock/consts.rs @@ -1,4 +1,7 @@ // Copyright 2024 Forecasting Technologies LTD. +#[cfg(feature = "parachain")] +use zeitgeist_primitives::types::{Asset, MarketId}; + #[cfg(feature = "parachain")] pub(crate) const FOREIGN_ASSET: Asset = Asset::ForeignAsset(1); diff --git a/zrml/combinatorial-tokens/src/mock/ext_builder.rs b/zrml/combinatorial-tokens/src/mock/ext_builder.rs index 9fec5aaef..ddd2d2e10 100644 --- a/zrml/combinatorial-tokens/src/mock/ext_builder.rs +++ b/zrml/combinatorial-tokens/src/mock/ext_builder.rs @@ -15,6 +15,8 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . +use crate::mock::runtime::{Runtime, System}; +use sp_io::TestExternalities; use sp_runtime::BuildStorage; #[cfg(feature = "parachain")] diff --git a/zrml/combinatorial-tokens/src/mock/mod.rs b/zrml/combinatorial-tokens/src/mock/mod.rs index ab40046ad..8700d164d 100644 --- a/zrml/combinatorial-tokens/src/mock/mod.rs +++ b/zrml/combinatorial-tokens/src/mock/mod.rs @@ -15,6 +15,8 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . +#![cfg(feature = "mock")] + pub(crate) mod consts; pub mod ext_builder; pub(crate) mod runtime; diff --git a/zrml/combinatorial-tokens/src/mock/runtime.rs b/zrml/combinatorial-tokens/src/mock/runtime.rs index e66a929d8..08e4a00cc 100644 --- a/zrml/combinatorial-tokens/src/mock/runtime.rs +++ b/zrml/combinatorial-tokens/src/mock/runtime.rs @@ -15,6 +15,8 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . +use crate as zrml_combinatorial_tokens; +use crate::types::CryptographicIdManager; use frame_support::{construct_runtime, traits::Everything, Blake2_256}; use frame_system::mocking::MockBlock; use sp_runtime::traits::{BlakeTwo256, ConstU32, IdentityLookup}; diff --git a/zrml/combinatorial-tokens/src/tests/integration.rs b/zrml/combinatorial-tokens/src/tests/integration.rs index 38d6020a6..ced2f9ae1 100644 --- a/zrml/combinatorial-tokens/src/tests/integration.rs +++ b/zrml/combinatorial-tokens/src/tests/integration.rs @@ -15,6 +15,8 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . +use super::*; + #[test] fn split_followed_by_merge_vertical_no_parent() { ExtBuilder::build().execute_with(|| { diff --git a/zrml/combinatorial-tokens/src/tests/merge_position.rs b/zrml/combinatorial-tokens/src/tests/merge_position.rs index 40c8869cf..b724e57c9 100644 --- a/zrml/combinatorial-tokens/src/tests/merge_position.rs +++ b/zrml/combinatorial-tokens/src/tests/merge_position.rs @@ -15,6 +15,9 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . +use super::*; +use test_case::test_case; + #[test_case( Asset::Ztg, CombinatorialToken([207, 168, 160, 93, 238, 221, 197, 1, 171, 102, 28, 24, 18, 107, 205, 231, 227, 98, 220, 105, 211, 29, 181, 30, 53, 7, 200, 154, 134, 246, 38, 139]), diff --git a/zrml/combinatorial-tokens/src/tests/mod.rs b/zrml/combinatorial-tokens/src/tests/mod.rs index 8eab9ea5c..d5a989daf 100644 --- a/zrml/combinatorial-tokens/src/tests/mod.rs +++ b/zrml/combinatorial-tokens/src/tests/mod.rs @@ -15,6 +15,8 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . +#![cfg(all(feature = "mock", test))] + mod integration; mod merge_position; mod split_position; diff --git a/zrml/combinatorial-tokens/src/tests/split_position.rs b/zrml/combinatorial-tokens/src/tests/split_position.rs index 0191fb57d..4a1460931 100644 --- a/zrml/combinatorial-tokens/src/tests/split_position.rs +++ b/zrml/combinatorial-tokens/src/tests/split_position.rs @@ -15,6 +15,8 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . +use super::*; + #[test] fn split_position_works_vertical_no_parent() { ExtBuilder::build().execute_with(|| { diff --git a/zrml/combinatorial-tokens/src/traits/combinatorial_id_manager.rs b/zrml/combinatorial-tokens/src/traits/combinatorial_id_manager.rs index 18614d582..547d2f9d8 100644 --- a/zrml/combinatorial-tokens/src/traits/combinatorial_id_manager.rs +++ b/zrml/combinatorial-tokens/src/traits/combinatorial_id_manager.rs @@ -22,6 +22,7 @@ // , // and has been relicensed under GPL-3.0-or-later in this repository. +use alloc::vec::Vec; pub trait CombinatorialIdManager { type Asset; diff --git a/zrml/combinatorial-tokens/src/traits/mod.rs b/zrml/combinatorial-tokens/src/traits/mod.rs index 2fdd5ba4f..5ef0075ef 100644 --- a/zrml/combinatorial-tokens/src/traits/mod.rs +++ b/zrml/combinatorial-tokens/src/traits/mod.rs @@ -15,4 +15,6 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . +mod combinatorial_id_manager; + pub use combinatorial_id_manager::CombinatorialIdManager; diff --git a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/mod.rs b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/mod.rs index 392ae3c39..4a437d956 100644 --- a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/mod.rs +++ b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/mod.rs @@ -22,6 +22,10 @@ // , // and has been relicensed under GPL-3.0-or-later in this repository. +//! Highest/lowest bit always refers to the big endian representation of each bit sequence. + +mod tests; + use ark_bn254::{g1::G1Affine, Fq}; use ark_ff::{BigInteger, PrimeField}; use core::ops::Neg; diff --git a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/decompress_collection_id.rs b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/decompress_collection_id.rs index 33a6c4ecf..5764626b1 100644 --- a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/decompress_collection_id.rs +++ b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/decompress_collection_id.rs @@ -15,6 +15,9 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . +use super::*; +use test_case::test_case; + #[test_case( [0x16, 0x74, 0xab, 0x10, 0xed, 0xf8, 0xc4, 0xe2, 0x25, 0x72, 0x9e, 0x20, 0x9a, 0x58, 0x75, 0xa1, 0x9f, 0x14, 0x46, 0xba, 0xec, 0x3b, 0x30, 0xdf, 0x9b, 0xa8, 0x65, 0x75, 0xd5, 0x2d, 0xe3, 0xd3], ( diff --git a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/decompress_hash.rs b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/decompress_hash.rs index 2535b478e..c0de02291 100644 --- a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/decompress_hash.rs +++ b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/decompress_hash.rs @@ -15,6 +15,9 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . +use super::*; +use rstest::rstest; + #[rstest] #[case( [0x83, 0x15, 0x48, 0x88, 0xe8, 0x2c, 0xe4, 0xfc, 0x32, 0xc2, 0xd5, 0xcd, 0x76, 0x6f, 0xfd, 0xc1, 0x8a, 0x8b, 0x00, 0xd9, 0xb7, 0x18, 0x15, 0xc7, 0x2c, 0x52, 0x38, 0x91, 0x11, 0x4e, 0x19, 0xca], diff --git a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/get_collection_id.rs b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/get_collection_id.rs index 8840c1577..26420b038 100644 --- a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/get_collection_id.rs +++ b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/get_collection_id.rs @@ -15,6 +15,9 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . +use super::*; +use rstest::rstest; + // Gnosis test cases using mocked keccak256 results, found here: https://docs.gnosis.io/conditionaltokens/docs/devguide05 #[rstest] #[case( diff --git a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/matching_y_coordinate.rs b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/matching_y_coordinate.rs index 6c6ba741a..93c2a46a5 100644 --- a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/matching_y_coordinate.rs +++ b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/matching_y_coordinate.rs @@ -15,6 +15,9 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . +use super::*; +use test_case::test_case; + // Empty string in the `expected` argument signals `None`. #[test_case("0x00", "")] #[test_case("0x01", "0x02")] diff --git a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/mod.rs b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/mod.rs index 5fda00d14..242d06093 100644 --- a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/mod.rs +++ b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/mod.rs @@ -15,6 +15,8 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . +#![cfg(test)] + use super::*; mod decompress_collection_id; diff --git a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/pow_magic_number.rs b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/pow_magic_number.rs index a76eca901..f09adce59 100644 --- a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/pow_magic_number.rs +++ b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/pow_magic_number.rs @@ -15,6 +15,9 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . +use super::*; +use test_case::test_case; + #[test_case("0x0", "0x0")] #[test_case("0x1", "0x1")] #[test_case( diff --git a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/hash_tuple.rs b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/hash_tuple.rs index 46377fdad..15ae10f3d 100644 --- a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/hash_tuple.rs +++ b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/hash_tuple.rs @@ -15,6 +15,9 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . +use crate::types::Hash256; +use alloc::{vec, vec::Vec}; + use frame_support::{Blake2_256, StorageHasher}; use parity_scale_codec::Encode; use zeitgeist_primitives::types::Asset; diff --git a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/mod.rs b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/mod.rs index 10ca64d8f..087498fce 100644 --- a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/mod.rs +++ b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/mod.rs @@ -22,6 +22,9 @@ // , // and has been relicensed under GPL-3.0-or-later in this repository. +mod decompressor; +mod hash_tuple; + use crate::traits::CombinatorialIdManager; use alloc::vec::Vec; use core::marker::PhantomData; diff --git a/zrml/combinatorial-tokens/src/types/hash.rs b/zrml/combinatorial-tokens/src/types/hash.rs new file mode 100644 index 000000000..115239fc5 --- /dev/null +++ b/zrml/combinatorial-tokens/src/types/hash.rs @@ -0,0 +1,18 @@ +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +pub type Hash256 = [u8; 32]; diff --git a/zrml/combinatorial-tokens/src/types/mod.rs b/zrml/combinatorial-tokens/src/types/mod.rs index 108ebf78b..2679ae583 100644 --- a/zrml/combinatorial-tokens/src/types/mod.rs +++ b/zrml/combinatorial-tokens/src/types/mod.rs @@ -15,5 +15,8 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . +pub(crate) mod cryptographic_id_manager; +pub(crate) mod hash; + pub use cryptographic_id_manager::CryptographicIdManager; pub(crate) use hash::Hash256; diff --git a/zrml/neo-swaps/src/math/types/mod.rs b/zrml/neo-swaps/src/math/types/mod.rs index d641dc96f..69628b16a 100644 --- a/zrml/neo-swaps/src/math/types/mod.rs +++ b/zrml/neo-swaps/src/math/types/mod.rs @@ -15,5 +15,8 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . +mod combo_math; +mod math; + pub(crate) use combo_math::ComboMath; pub(crate) use math::Math; From c4e50d4ba9a3952ecac7b836c71a9791bed4afa4 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Sun, 13 Oct 2024 23:02:39 +0200 Subject: [PATCH 12/73] Format `Cargo.toml` files (#1375) --- Cargo.toml | 2 +- zrml/combinatorial-tokens/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f9401cf22..70a221993 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -273,9 +273,9 @@ url = "2.5.0" # Other (wasm) arbitrary = { version = "1.3.2", default-features = false } -arrayvec = { version = "0.7.4", default-features = false } ark-bn254 = { version = "0.4.0", default-features = false, features = ["curve"] } ark-ff = { version = "0.4.0", default-features = false } +arrayvec = { version = "0.7.4", default-features = false } cfg-if = { version = "1.0.0" } fixed = { version = "=1.15.0", default-features = false, features = ["num-traits"] } # Hashbrown works in no_std by default and default features are used in Rikiddo diff --git a/zrml/combinatorial-tokens/Cargo.toml b/zrml/combinatorial-tokens/Cargo.toml index d64d8f42a..fca934403 100644 --- a/zrml/combinatorial-tokens/Cargo.toml +++ b/zrml/combinatorial-tokens/Cargo.toml @@ -21,8 +21,8 @@ sp-io = { workspace = true, optional = true } zrml-market-commons = { workspace = true, optional = true } [dev-dependencies] -test-case = { workspace = true } rstest = { workspace = true } +test-case = { workspace = true } zrml-combinatorial-tokens = { workspace = true, features = ["default", "mock"] } [features] From 0b0e60788572908af195e3a4021628475d957825 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Mon, 14 Oct 2024 15:51:33 +0200 Subject: [PATCH 13/73] Implement and test numerical limits for combinatorial betting (#1376) * Add numerical limits and tests * Add missing license --- zrml/combinatorial-tokens/src/mock/consts.rs | 15 +++ zrml/neo-swaps/src/lib.rs | 57 ++++++++- zrml/neo-swaps/src/tests/combo_buy.rs | 75 +++++++++++ zrml/neo-swaps/src/tests/combo_sell.rs | 125 ++++++++++++++++++- 4 files changed, 265 insertions(+), 7 deletions(-) diff --git a/zrml/combinatorial-tokens/src/mock/consts.rs b/zrml/combinatorial-tokens/src/mock/consts.rs index 7d579595e..d614e0775 100644 --- a/zrml/combinatorial-tokens/src/mock/consts.rs +++ b/zrml/combinatorial-tokens/src/mock/consts.rs @@ -1,4 +1,19 @@ // Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . #[cfg(feature = "parachain")] use zeitgeist_primitives::types::{Asset, MarketId}; diff --git a/zrml/neo-swaps/src/lib.rs b/zrml/neo-swaps/src/lib.rs index 7784ec8e0..1e04c849f 100644 --- a/zrml/neo-swaps/src/lib.rs +++ b/zrml/neo-swaps/src/lib.rs @@ -94,6 +94,10 @@ mod pallet { pub(crate) const MAX_SPOT_PRICE: u128 = BASE - CENT / 2; /// The minimum allowed spot price when creating a pool. pub(crate) const MIN_SPOT_PRICE: u128 = CENT / 2; + /// The maximum value the spot price is allowed to take in a combinatorial market. + pub(crate) const COMBO_MAX_SPOT_PRICE: u128 = BASE - CENT / 10; + /// The minimum value the spot price is allowed to take in a combinatorial market. + pub(crate) const COMBO_MIN_SPOT_PRICE: u128 = CENT / 10; /// The minimum vallowed value of a pool's liquidity parameter. pub(crate) const MIN_LIQUIDITY: u128 = BASE; /// The minimum percentage each new LP position must increase the liquidity by, represented as @@ -311,12 +315,14 @@ mod pallet { pub enum NumericalLimitsError { /// Selling is not allowed at prices this low. SpotPriceTooLow, - /// Sells which move the price below this threshold are not allowed. + /// Interactions which move the price below a particular threshold are not allowed. SpotPriceSlippedTooLow, /// The maximum buy or sell amount was exceeded. MaxAmountExceeded, /// The minimum buy or sell amount was exceeded. MinAmountNotMet, + /// Interactions which move the price above a particular threshold are not allowed. + SpotPriceSlippedTooHigh, } #[pallet::call] @@ -1041,10 +1047,10 @@ mod pallet { ensure!(!sell.is_empty(), Error::::InvalidPartition); for asset in buy.iter() { ensure!(!sell.contains(asset), Error::::InvalidPartition); - ensure!(market.outcome_assets().contains(asset), Error::::InvalidPartition); + ensure!(pool.assets().contains(asset), Error::::InvalidPartition); } for asset in sell.iter() { - ensure!(market.outcome_assets().contains(asset), Error::::InvalidPartition); + ensure!(pool.assets().contains(asset), Error::::InvalidPartition); } let buy_set = buy.iter().collect::>(); let sell_set = sell.iter().collect::>(); @@ -1084,6 +1090,19 @@ mod pallet { pool.increase_reserve(&asset, &amount_in_minus_fees)?; } + // Ensure that numerical limits of all prices are respected. + for &asset in pool.assets().iter() { + let spot_price = pool.calculate_spot_price(asset)?; + ensure!( + spot_price >= COMBO_MIN_SPOT_PRICE.saturated_into(), + Error::::NumericalLimits(NumericalLimitsError::SpotPriceSlippedTooLow) + ); + ensure!( + spot_price <= COMBO_MAX_SPOT_PRICE.saturated_into(), + Error::::NumericalLimits(NumericalLimitsError::SpotPriceSlippedTooHigh) + ); + } + Self::deposit_event(Event::::ComboBuyExecuted { who: who.clone(), market_id, @@ -1123,14 +1142,14 @@ mod pallet { for asset in buy.iter() { ensure!(!keep.contains(asset), Error::::InvalidPartition); ensure!(!sell.contains(asset), Error::::InvalidPartition); - ensure!(market.outcome_assets().contains(asset), Error::::InvalidPartition); + ensure!(pool.assets().contains(asset), Error::::InvalidPartition); } for asset in sell.iter() { ensure!(!keep.contains(asset), Error::::InvalidPartition); - ensure!(market.outcome_assets().contains(asset), Error::::InvalidPartition); + ensure!(pool.assets().contains(asset), Error::::InvalidPartition); } for asset in keep.iter() { - ensure!(market.outcome_assets().contains(asset), Error::::InvalidPartition); + ensure!(pool.assets().contains(asset), Error::::InvalidPartition); } let buy_set = buy.iter().collect::>(); let keep_set = keep.iter().collect::>(); @@ -1193,6 +1212,32 @@ mod pallet { amount_out_minus_fees, )?; + // Ensure that numerical limits of all prices are respected. + for &asset in pool.assets().iter() { + let spot_price = pool.calculate_spot_price(asset)?; + ensure!( + spot_price >= COMBO_MIN_SPOT_PRICE.saturated_into(), + Error::::NumericalLimits(NumericalLimitsError::SpotPriceSlippedTooLow) + ); + ensure!( + spot_price <= COMBO_MAX_SPOT_PRICE.saturated_into(), + Error::::NumericalLimits(NumericalLimitsError::SpotPriceSlippedTooHigh) + ); + } + + // Ensure that numerical limits of all prices are respected. + for &asset in pool.assets().iter() { + let spot_price = pool.calculate_spot_price(asset)?; + ensure!( + spot_price >= COMBO_MIN_SPOT_PRICE.saturated_into(), + Error::::NumericalLimits(NumericalLimitsError::SpotPriceSlippedTooLow) + ); + ensure!( + spot_price <= COMBO_MAX_SPOT_PRICE.saturated_into(), + Error::::NumericalLimits(NumericalLimitsError::SpotPriceSlippedTooHigh) + ); + } + Self::deposit_event(Event::::ComboSellExecuted { who: who.clone(), market_id, diff --git a/zrml/neo-swaps/src/tests/combo_buy.rs b/zrml/neo-swaps/src/tests/combo_buy.rs index ae9492789..7fb5ae5c8 100644 --- a/zrml/neo-swaps/src/tests/combo_buy.rs +++ b/zrml/neo-swaps/src/tests/combo_buy.rs @@ -299,3 +299,78 @@ fn combo_buy_fails_on_invalid_partition(indices_buy: Vec, indices_sell: Vec ); }); } + +#[test] +fn combo_buy_fails_on_spot_price_slipping_too_low() { + ExtBuilder::default().build().execute_with(|| { + let market_id = create_market_and_deploy_pool( + ALICE, + BASE_ASSET, + MarketType::Categorical(5), + _10, + vec![_1_5, _1_5, _1_5, _1_5, _1_5], + CENT, + ); + let amount_in = _100; + + assert_ok!(AssetManager::deposit(BASE_ASSET, &BOB, amount_in)); + + let buy = [0, 1, 2, 3].into_iter().map(|i| CategoricalOutcome(market_id, i)).collect(); + let sell = [4].into_iter().map(|i| CategoricalOutcome(market_id, i)).collect(); + + assert_noop!( + NeoSwaps::combo_buy(RuntimeOrigin::signed(BOB), market_id, 5, buy, sell, amount_in, 0), + Error::::NumericalLimits(NumericalLimitsError::SpotPriceSlippedTooLow), + ); + }); +} + +#[test] +fn combo_buy_fails_on_spot_price_slipping_too_high() { + ExtBuilder::default().build().execute_with(|| { + let market_id = create_market_and_deploy_pool( + ALICE, + BASE_ASSET, + MarketType::Categorical(5), + _10, + vec![_1_5, _1_5, _1_5, _1_5, _1_5], + CENT, + ); + let amount_in = _100; + + assert_ok!(AssetManager::deposit(BASE_ASSET, &BOB, amount_in)); + + let buy = [0].into_iter().map(|i| CategoricalOutcome(market_id, i)).collect(); + let sell = [1, 2, 3, 4].into_iter().map(|i| CategoricalOutcome(market_id, i)).collect(); + + assert_noop!( + NeoSwaps::combo_buy(RuntimeOrigin::signed(BOB), market_id, 5, buy, sell, amount_in, 0), + Error::::NumericalLimits(NumericalLimitsError::SpotPriceSlippedTooHigh), + ); + }); +} + +#[test] +fn combo_buy_fails_on_large_buy() { + ExtBuilder::default().build().execute_with(|| { + let market_id = create_market_and_deploy_pool( + ALICE, + BASE_ASSET, + MarketType::Categorical(5), + _10, + vec![_1_5, _1_5, _1_5, _1_5, _1_5], + CENT, + ); + let amount_in = 100 * _100; + + assert_ok!(AssetManager::deposit(BASE_ASSET, &BOB, amount_in)); + + let buy = [0].into_iter().map(|i| CategoricalOutcome(market_id, i)).collect(); + let sell = [1, 2].into_iter().map(|i| CategoricalOutcome(market_id, i)).collect(); + + assert_noop!( + NeoSwaps::combo_buy(RuntimeOrigin::signed(BOB), market_id, 5, buy, sell, amount_in, 0), + Error::::MathError, + ); + }); +} diff --git a/zrml/neo-swaps/src/tests/combo_sell.rs b/zrml/neo-swaps/src/tests/combo_sell.rs index 57ed1ee3b..f8f117201 100644 --- a/zrml/neo-swaps/src/tests/combo_sell.rs +++ b/zrml/neo-swaps/src/tests/combo_sell.rs @@ -290,7 +290,7 @@ fn combo_sell_fails_on_amount_out_below_min() { #[test_case(vec![0, 6, 1, 6], vec![2, 4], vec![5, 3]; "duplicate_buy")] #[test_case(vec![0, 1], vec![2, 2, 4], vec![5, 3]; "duplicate_keep")] #[test_case(vec![0, 1], vec![2, 4], vec![5, 3, 6, 6, 6]; "duplicate_sell")] -fn combo_buy_fails_on_invalid_partition( +fn combo_sell_fails_on_invalid_partition( indices_buy: Vec, indices_keep: Vec, indices_sell: Vec, @@ -327,3 +327,126 @@ fn combo_buy_fails_on_invalid_partition( ); }); } + +#[test] +fn combo_sell_fails_on_spot_price_slipping_too_low() { + ExtBuilder::default().build().execute_with(|| { + let market_id = create_market_and_deploy_pool( + ALICE, + BASE_ASSET, + MarketType::Categorical(5), + _10, + vec![_1_5, _1_5, _1_5, _1_5, _1_5], + CENT, + ); + let amount_buy = _100; + + for i in 0..4 { + assert_ok!(AssetManager::deposit( + Asset::CategoricalOutcome(market_id, i), + &BOB, + amount_buy + )); + } + + let buy = [0, 1, 2, 3].into_iter().map(|i| CategoricalOutcome(market_id, i)).collect(); + let sell = [4].into_iter().map(|i| CategoricalOutcome(market_id, i)).collect(); + + assert_noop!( + NeoSwaps::combo_sell( + RuntimeOrigin::signed(BOB), + market_id, + 5, + buy, + vec![], + sell, + amount_buy, + 0, + 0 + ), + Error::::NumericalLimits(NumericalLimitsError::SpotPriceSlippedTooLow), + ); + }); +} + +#[test] +fn combo_sell_fails_on_spot_price_slipping_too_high() { + ExtBuilder::default().build().execute_with(|| { + let market_id = create_market_and_deploy_pool( + ALICE, + BASE_ASSET, + MarketType::Categorical(5), + _10, + vec![_1_5, _1_5, _1_5, _1_5, _1_5], + CENT, + ); + let amount_buy = _100; + + for i in 0..4 { + assert_ok!(AssetManager::deposit( + Asset::CategoricalOutcome(market_id, i), + &BOB, + amount_buy + )); + } + + let buy = [0].into_iter().map(|i| CategoricalOutcome(market_id, i)).collect(); + let sell = [1, 2, 3, 4].into_iter().map(|i| CategoricalOutcome(market_id, i)).collect(); + + assert_noop!( + NeoSwaps::combo_sell( + RuntimeOrigin::signed(BOB), + market_id, + 5, + buy, + vec![], + sell, + amount_buy, + 0, + 0 + ), + Error::::NumericalLimits(NumericalLimitsError::SpotPriceSlippedTooLow), + ); + }); +} + +#[test] +fn combo_sell_fails_on_large_amount() { + ExtBuilder::default().build().execute_with(|| { + let market_id = create_market_and_deploy_pool( + ALICE, + BASE_ASSET, + MarketType::Categorical(5), + _10, + vec![_1_5, _1_5, _1_5, _1_5, _1_5], + CENT, + ); + let amount_buy = 100 * _100; + + for i in 0..4 { + assert_ok!(AssetManager::deposit( + Asset::CategoricalOutcome(market_id, i), + &BOB, + amount_buy + )); + } + + let buy = [0].into_iter().map(|i| CategoricalOutcome(market_id, i)).collect(); + let sell = [1, 2, 3, 4].into_iter().map(|i| CategoricalOutcome(market_id, i)).collect(); + + assert_noop!( + NeoSwaps::combo_sell( + RuntimeOrigin::signed(BOB), + market_id, + 5, + buy, + vec![], + sell, + amount_buy, + 0, + 0 + ), + Error::::MathError, + ); + }); +} From e586f668a2ceabc5677e7a32df25b2e156711d28 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Mon, 14 Oct 2024 20:50:55 +0200 Subject: [PATCH 14/73] Implement multi-market combinatorial betting tests (#1377) * . * Add more tests * . * Detailed testing * . * Add tests for `InvalidAmountKeep` * Clippy fixes --- primitives/src/constants.rs | 1 + primitives/src/constants/base_multiples.rs | 3 + zrml/neo-swaps/src/lib.rs | 13 +- zrml/neo-swaps/src/macros.rs | 2 +- zrml/neo-swaps/src/tests/combo_buy.rs | 126 +++++++++++++++- zrml/neo-swaps/src/tests/combo_sell.rs | 161 ++++++++++++++++++++- 6 files changed, 301 insertions(+), 5 deletions(-) diff --git a/primitives/src/constants.rs b/primitives/src/constants.rs index 3d831286e..7b13887d8 100644 --- a/primitives/src/constants.rs +++ b/primitives/src/constants.rs @@ -41,6 +41,7 @@ pub const BLOCKS_PER_HOUR: BlockNumber = BLOCKS_PER_MINUTE * 60; // 300 // Definitions for currency pub const DECIMALS: u8 = 10; pub const BASE: u128 = 10u128.pow(DECIMALS as u32); +pub const DIME: Balance = BASE / 10; // 1_000_000_000 pub const CENT: Balance = BASE / 100; // 100_000_000 pub const MILLI: Balance = CENT / 10; // 10_000_000 pub const MICRO: Balance = MILLI / 1000; // 10_000 diff --git a/primitives/src/constants/base_multiples.rs b/primitives/src/constants/base_multiples.rs index bdbba4657..42ca28f4a 100644 --- a/primitives/src/constants/base_multiples.rs +++ b/primitives/src/constants/base_multiples.rs @@ -42,6 +42,9 @@ pub const _80: u128 = 80 * _1; pub const _99: u128 = 99 * _1; pub const _100: u128 = 100 * _1; pub const _101: u128 = 101 * _1; +pub const _300: u128 = 300 * _1; +pub const _321: u128 = 321 * _1; +pub const _400: u128 = 400 * _1; pub const _444: u128 = 444 * _1; pub const _500: u128 = 500 * _1; pub const _777: u128 = 777 * _1; diff --git a/zrml/neo-swaps/src/lib.rs b/zrml/neo-swaps/src/lib.rs index 1e04c849f..4b548fa8d 100644 --- a/zrml/neo-swaps/src/lib.rs +++ b/zrml/neo-swaps/src/lib.rs @@ -309,6 +309,10 @@ mod pallet { /// The buy/sell/keep partition specified is empty, or contains overlaps or assets that don't /// belong to the market. InvalidPartition, + + /// The `amount_keep` parameter must be zero if `keep` is empty and less than `amount_buy` + /// if `keep` is not empty. + InvalidAmountKeep, } #[derive(Decode, Encode, Eq, PartialEq, PalletError, RuntimeDebug, TypeInfo)] @@ -1134,6 +1138,13 @@ mod pallet { ensure!(amount_buy != Zero::zero(), Error::::ZeroAmount); let market = T::MarketCommons::market(&market_id)?; ensure!(market.status == MarketStatus::Active, Error::::MarketNotActive); + + if keep.is_empty() { + ensure!(amount_keep.is_zero(), Error::::InvalidAmountKeep); + } else { + ensure!(amount_keep < amount_buy, Error::::InvalidAmountKeep); + } + Self::try_mutate_pool(&market_id, |pool| { // Ensure that `buy` and `sell` partition are disjoint and only contain assets from // the market. @@ -1185,7 +1196,7 @@ mod pallet { } for &asset in keep.iter() { - T::MultiCurrency::transfer(asset, &pool.account_id, &who, amount_keep)?; + T::MultiCurrency::transfer(asset, &who, &pool.account_id, amount_keep)?; pool.increase_reserve(&asset, &amount_keep)?; } diff --git a/zrml/neo-swaps/src/macros.rs b/zrml/neo-swaps/src/macros.rs index 0c7bbb986..a688fb368 100644 --- a/zrml/neo-swaps/src/macros.rs +++ b/zrml/neo-swaps/src/macros.rs @@ -100,7 +100,7 @@ macro_rules! assert_pool_state { .fold(0u128, |acc, node| acc + node.fees + node.lazy_fees); assert_eq!(actual_total_fees, $total_fees); let invariant = actual_spot_prices.iter().sum::(); - assert_approx!(invariant, _1, 1); + assert_approx!(invariant, _1, 2); }; } diff --git a/zrml/neo-swaps/src/tests/combo_buy.rs b/zrml/neo-swaps/src/tests/combo_buy.rs index 7fb5ae5c8..39b3e772a 100644 --- a/zrml/neo-swaps/src/tests/combo_buy.rs +++ b/zrml/neo-swaps/src/tests/combo_buy.rs @@ -50,7 +50,6 @@ fn combo_buy_works() { let buy = vec![pool.assets()[0]]; let sell = pool.assets_complement(&buy); assert_ok!(AssetManager::deposit(BASE_ASSET, &BOB, amount_in)); - println!("{}", AssetManager::free_balance(BASE_ASSET, &BOB)); // Deposit some stuff in the pool account to check that the pools `reserves` fields tracks // the reserve correctly. assert_ok!(AssetManager::deposit(sell[0], &pool.account_id, _100)); @@ -103,6 +102,131 @@ fn combo_buy_works() { }); } +#[test_case( + 333 * _1, + vec![10 * CENT, 30 * CENT, 25 * CENT, 13 * CENT, 22 * CENT], + vec![0, 2], + vec![3], + vec![1, 4], + 102_040_816_327, + 236_865_613_849, + 100_000_000_001, + vec![3193134386152, 1841186221785, 1867994157274, 2950568636818, 2289732472863], + vec![1_099_260_911, 2_799_569_315, 2_748_152_277, 1_300_000_000, 2_053_017_497], + 1_020_408_163 +)] +#[test_case( + _100, + vec![80 * CENT, 5 * CENT, 5 * CENT, 5 * CENT, 5 * CENT], + vec![4], + vec![1, 2, 3], + vec![0], + 336_734_693_877, + 1_131_842_030_026, + 329_999_999_999, + vec![404_487_147_360, _100, _100, _100, 198_157_969_973], + vec![2_976_802_957, 5 * CENT, 5 * CENT, 5 * CENT, 5_523_197_043], + 3_367_346_939 +)] +#[test_case( + 1000 * _1, + vec![1_250_000_000; 8], + vec![0, 2, 5, 6, 7], + vec![], + vec![1, 3, 4], + 5_102_040_816_326, + 6_576_234_413_776, + 5_000_000_000_000, + vec![ + 8_423_765_586_224, + 1500 * _1, + 8_423_765_586_224, + 1500 * _1, + 1500 * _1, + 8_423_765_586_224, + 8_423_765_586_224, + 8_423_765_586_224, + ], + vec![ + 1_734_834_957, + 441_941_738, + 1_734_834_957, + 441_941_738, + 441_941_738, + 1_734_834_957, + 1_734_834_957, + 1_734_834_957, + ], + 51_020_408_163 +)] +fn combo_buy_works_multi_market( + liquidity: u128, + spot_prices: Vec, + buy_indices: Vec, + keep_indices: Vec, + sell_indices: Vec, + amount_in: u128, + expected_amount_out_buy: u128, + expected_amount_out_keep: u128, + expected_reserves: Vec, + expected_spot_prices: Vec, + expected_fees: u128, +) { + ExtBuilder::default().build().execute_with(|| { + let asset_count = spot_prices.len() as u16; + let swap_fee = CENT; + let market_id = create_market_and_deploy_pool( + ALICE, + BASE_ASSET, + MarketType::Categorical(asset_count), + liquidity, + spot_prices.clone(), + swap_fee, + ); + let sentinel = 123_456_789; + assert_ok!(AssetManager::deposit(BASE_ASSET, &BOB, amount_in + sentinel)); + + let pool = Pools::::get(market_id).unwrap(); + let expected_liquidity = pool.liquidity_parameter; + + let buy: Vec<_> = + buy_indices.iter().map(|&i| Asset::CategoricalOutcome(market_id, i)).collect(); + let keep: Vec<_> = + keep_indices.iter().map(|&i| Asset::CategoricalOutcome(market_id, i)).collect(); + let sell: Vec<_> = + sell_indices.iter().map(|&i| Asset::CategoricalOutcome(market_id, i)).collect(); + assert_ok!(NeoSwaps::combo_buy( + RuntimeOrigin::signed(BOB), + market_id, + asset_count, + buy.clone(), + sell.clone(), + amount_in, + 0, + )); + + assert_balance!(BOB, BASE_ASSET, sentinel); + for &asset in buy.iter() { + assert_balance!(BOB, asset, expected_amount_out_buy); + } + for &asset in keep.iter() { + assert_balance!(BOB, asset, expected_amount_out_keep); + } + for &asset in sell.iter() { + assert_balance!(BOB, asset, 0); + } + + assert_pool_state!( + market_id, + expected_reserves, + expected_spot_prices, + expected_liquidity, + create_b_tree_map!({ ALICE => liquidity }), + expected_fees, + ); + }); +} + #[test] fn combo_buy_fails_on_incorrect_asset_count() { ExtBuilder::default().build().execute_with(|| { diff --git a/zrml/neo-swaps/src/tests/combo_sell.rs b/zrml/neo-swaps/src/tests/combo_sell.rs index f8f117201..52d0ea810 100644 --- a/zrml/neo-swaps/src/tests/combo_sell.rs +++ b/zrml/neo-swaps/src/tests/combo_sell.rs @@ -100,6 +100,127 @@ fn combo_sell_works() { }); } +#[test_case( + 1000 * _1, + vec![1_250_000_000; 8], + vec![0, 2, 5], + vec![6, 7], + vec![1, 3, 4], + _500, + _300, + 2_091_832_646_248, + vec![ + 12_865_476_891_584, + 7_865_476_891_584, + 12_865_476_891_584, + 7_865_476_891_584, + 7_865_476_891_584, + 12_865_476_891_584, + 10_865_476_891_584, + 10_865_476_891_584, + ], + vec![ + 688_861_105, + 1_948_393_435, + 688_861_105, + 1_948_393_435, + 1_948_393_435, + 688_861_105, + 1_044_118_189, + 1_044_118_189, + ], + 21_345_231_084 +)] +#[test_case( + _321, + vec![20 * CENT, 30 * CENT, 50 * CENT], + vec![0, 2], + vec![], + vec![1], + _500, + 0, + 2_012_922_832_062, + vec![ + 6_155_997_110_140, + 347_302_977_256, + 4_328_468_861_556, + ], + vec![ + 456_610_616, + 8_401_862_845, + 1_141_526_539, + ], + 20_540_028_899 +)] +fn combo_sell_works_multi_market( + liquidity: u128, + spot_prices: Vec, + buy_indices: Vec, + keep_indices: Vec, + sell_indices: Vec, + amount_in_buy: u128, + amount_in_keep: u128, + expected_amount_out: u128, + expected_reserves: Vec, + expected_spot_prices: Vec, + expected_fees: u128, +) { + ExtBuilder::default().build().execute_with(|| { + let asset_count = spot_prices.len() as u16; + let swap_fee = CENT; + let market_id = create_market_and_deploy_pool( + ALICE, + BASE_ASSET, + MarketType::Categorical(asset_count), + liquidity, + spot_prices.clone(), + swap_fee, + ); + + let buy: Vec<_> = + buy_indices.iter().map(|&i| Asset::CategoricalOutcome(market_id, i)).collect(); + let keep: Vec<_> = + keep_indices.iter().map(|&i| Asset::CategoricalOutcome(market_id, i)).collect(); + let sell: Vec<_> = + sell_indices.iter().map(|&i| Asset::CategoricalOutcome(market_id, i)).collect(); + + for &asset in buy.iter() { + assert_ok!(AssetManager::deposit(asset, &BOB, amount_in_buy)); + } + for &asset in keep.iter() { + assert_ok!(AssetManager::deposit(asset, &BOB, amount_in_keep)); + } + + let pool = Pools::::get(market_id).unwrap(); + let expected_liquidity = pool.liquidity_parameter; + + assert_ok!(NeoSwaps::combo_sell( + RuntimeOrigin::signed(BOB), + market_id, + asset_count, + buy.clone(), + keep.clone(), + sell.clone(), + amount_in_buy, + amount_in_keep, + 0, + )); + + assert_balance!(BOB, BASE_ASSET, expected_amount_out); + for asset in pool.assets() { + assert_balance!(BOB, asset, 0); + } + assert_pool_state!( + market_id, + expected_reserves, + expected_spot_prices, + expected_liquidity, + create_b_tree_map!({ ALICE => liquidity }), + expected_fees, + ); + }); +} + #[test] fn combo_sell_fails_on_incorrect_asset_count() { ExtBuilder::default().build().execute_with(|| { @@ -296,7 +417,6 @@ fn combo_sell_fails_on_invalid_partition( indices_sell: Vec, ) { ExtBuilder::default().build().execute_with(|| { - println!("{:?}", _1_7); let market_id = create_market_and_deploy_pool( ALICE, BASE_ASSET, @@ -320,7 +440,7 @@ fn combo_sell_fails_on_invalid_partition( keep, sell, _2, - _1, + 0, // Keep this zero to avoid a different error due to invalid `amount_keep` param. 0 ), Error::::InvalidPartition, @@ -450,3 +570,40 @@ fn combo_sell_fails_on_large_amount() { ); }); } + +#[test_case(vec![], 1)] +#[test_case(vec![2], _2)] +fn combo_sell_fails_on_invalid_amount_keep(keep_indices: Vec, amount_in_keep: u128) { + ExtBuilder::default().build().execute_with(|| { + let spot_prices = vec![25 * CENT; 4]; + let asset_count = spot_prices.len() as u16; + let market_id = create_market_and_deploy_pool( + ALICE, + BASE_ASSET, + MarketType::Categorical(asset_count), + _10, + spot_prices, + CENT, + ); + + let buy = vec![Asset::CategoricalOutcome(market_id, 0)]; + let sell = vec![Asset::CategoricalOutcome(market_id, 1)]; + let keep: Vec<_> = + keep_indices.iter().map(|&i| Asset::CategoricalOutcome(market_id, i)).collect(); + + assert_noop!( + NeoSwaps::combo_sell( + RuntimeOrigin::signed(BOB), + market_id, + asset_count, + buy.clone(), + keep.clone(), + sell.clone(), + _1, + amount_in_keep, + 0, + ), + Error::::InvalidAmountKeep + ); + }); +} From 27b3db0bdca2376085431de1e827d0eb60237c41 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Wed, 16 Oct 2024 20:20:46 +0200 Subject: [PATCH 15/73] Scaffold futarchy pallet (#1378) * Scaffold futarchy pallet * Scaffold test suite * . * . --- Cargo.lock | 25 +++++ Cargo.toml | 3 + runtime/battery-station/Cargo.toml | 4 + runtime/common/src/lib.rs | 8 ++ runtime/zeitgeist/Cargo.toml | 4 + zrml/futarchy/Cargo.toml | 58 ++++++++++++ zrml/futarchy/README.md | 1 + zrml/futarchy/src/lib.rs | 99 +++++++++++++++++++ zrml/futarchy/src/mock/ext_builder.rs | 72 ++++++++++++++ zrml/futarchy/src/mock/mod.rs | 21 +++++ zrml/futarchy/src/mock/runtime.rs | 131 ++++++++++++++++++++++++++ zrml/futarchy/src/tests/mod.rs | 18 ++++ zrml/futarchy/src/traits/mod.rs | 16 ++++ zrml/futarchy/src/types/mod.rs | 16 ++++ 14 files changed, 476 insertions(+) create mode 100644 zrml/futarchy/Cargo.toml create mode 100644 zrml/futarchy/README.md create mode 100644 zrml/futarchy/src/lib.rs create mode 100644 zrml/futarchy/src/mock/ext_builder.rs create mode 100644 zrml/futarchy/src/mock/mod.rs create mode 100644 zrml/futarchy/src/mock/runtime.rs create mode 100644 zrml/futarchy/src/tests/mod.rs create mode 100644 zrml/futarchy/src/traits/mod.rs create mode 100644 zrml/futarchy/src/types/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 3b4ac9ba8..81586cbd9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -894,6 +894,7 @@ dependencies = [ "zrml-authorized", "zrml-combinatorial-tokens", "zrml-court", + "zrml-futarchy", "zrml-global-disputes", "zrml-hybrid-router", "zrml-market-commons", @@ -15117,6 +15118,7 @@ dependencies = [ "zrml-authorized", "zrml-combinatorial-tokens", "zrml-court", + "zrml-futarchy", "zrml-global-disputes", "zrml-hybrid-router", "zrml-market-commons", @@ -15240,6 +15242,29 @@ dependencies = [ "zrml-market-commons", ] +[[package]] +name = "zrml-futarchy" +version = "0.5.5" +dependencies = [ + "env_logger 0.10.2", + "frame-benchmarking", + "frame-support", + "frame-system", + "orml-currencies", + "orml-tokens", + "orml-traits", + "pallet-balances", + "pallet-preimage", + "pallet-timestamp", + "parity-scale-codec", + "scale-info", + "sp-io", + "sp-runtime", + "test-case", + "zeitgeist-primitives", + "zrml-futarchy", +] + [[package]] name = "zrml-global-disputes" version = "0.5.5" diff --git a/Cargo.toml b/Cargo.toml index 70a221993..bd41ad2d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ default-members = [ "zrml/authorized", "zrml/combinatorial-tokens", "zrml/court", + "zrml/futarchy", "zrml/hybrid-router", "zrml/global-disputes", "zrml/market-commons", @@ -39,6 +40,7 @@ members = [ "zrml/authorized", "zrml/combinatorial-tokens", "zrml/court", + "zrml/futarchy", "zrml/hybrid-router", "zrml/global-disputes", "zrml/market-commons", @@ -247,6 +249,7 @@ zeitgeist-macros = { path = "macros", default-features = false } zeitgeist-primitives = { path = "primitives", default-features = false } zrml-authorized = { path = "zrml/authorized", default-features = false } zrml-combinatorial-tokens = { path = "zrml/combinatorial-tokens", default-features = false } +zrml-futarchy = { path = "zrml/futarchy", default-features = false } zrml-court = { path = "zrml/court", default-features = false } zrml-global-disputes = { path = "zrml/global-disputes", default-features = false } zrml-hybrid-router = { path = "zrml/hybrid-router", default-features = false } diff --git a/runtime/battery-station/Cargo.toml b/runtime/battery-station/Cargo.toml index 01359557e..43f71abcb 100644 --- a/runtime/battery-station/Cargo.toml +++ b/runtime/battery-station/Cargo.toml @@ -111,6 +111,7 @@ zeitgeist-primitives = { workspace = true } zrml-authorized = { workspace = true } zrml-combinatorial-tokens = { workspace = true } zrml-court = { workspace = true } +zrml-futarchy = { workspace = true } zrml-global-disputes = { workspace = true, optional = true } zrml-hybrid-router = { workspace = true } zrml-market-commons = { workspace = true } @@ -217,6 +218,7 @@ runtime-benchmarks = [ "zrml-authorized/runtime-benchmarks", "zrml-combinatorial-tokens/runtime-benchmarks", "zrml-court/runtime-benchmarks", + "zrml-futarchy/runtime-benchmarks", "zrml-hybrid-router/runtime-benchmarks", "zrml-neo-swaps/runtime-benchmarks", "zrml-parimutuel/runtime-benchmarks", @@ -331,6 +333,7 @@ std = [ "zrml-authorized/std", "zrml-combinatorial-tokens/std", "zrml-court/std", + "zrml-futarchy/std", "zrml-hybrid-router/std", "zrml-market-commons/std", "zrml-neo-swaps/std", @@ -386,6 +389,7 @@ try-runtime = [ "zrml-authorized/try-runtime", "zrml-combinatorial-tokens/try-runtime", "zrml-court/try-runtime", + "zrml-futarchy/try-runtime", "zrml-hybrid-router/try-runtime", "zrml-market-commons/try-runtime", "zrml-neo-swaps/try-runtime", diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs index e47db9fe5..20a3fcecc 100644 --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -360,6 +360,7 @@ macro_rules! create_runtime { Parimutuel: zrml_parimutuel::{Call, Event, Pallet, Storage} = 62, HybridRouter: zrml_hybrid_router::{Call, Event, Pallet, Storage} = 64, CombinatorialTokens: zrml_combinatorial_tokens::{Call, Event, Pallet, Storage} = 65, + Futarchy: zrml_futarchy::{Call, Event, Pallet, Storage} = 66, $($additional_pallets)* } @@ -1205,6 +1206,13 @@ macro_rules! impl_config_traits { type WeightInfo = zrml_court::weights::WeightInfo; } + impl zrml_futarchy::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type MultiCurrency = AssetManager; + type Preimages = Preimage; + type SubmitOrigin = EnsureRoot; + } + impl zrml_market_commons::Config for Runtime { type Balance = Balance; type MarketId = MarketId; diff --git a/runtime/zeitgeist/Cargo.toml b/runtime/zeitgeist/Cargo.toml index 1a43e9b51..49a01c3b5 100644 --- a/runtime/zeitgeist/Cargo.toml +++ b/runtime/zeitgeist/Cargo.toml @@ -110,6 +110,7 @@ zeitgeist-primitives = { workspace = true } zrml-authorized = { workspace = true } zrml-combinatorial-tokens = { workspace = true } zrml-court = { workspace = true } +zrml-futarchy = { workspace = true } zrml-global-disputes = { workspace = true, optional = true } zrml-hybrid-router = { workspace = true } zrml-market-commons = { workspace = true } @@ -214,6 +215,7 @@ runtime-benchmarks = [ "zrml-authorized/runtime-benchmarks", "zrml-combinatorial-tokens/runtime-benchmarks", "zrml-court/runtime-benchmarks", + "zrml-futarchy/runtime-benchmarks", "zrml-hybrid-router/runtime-benchmarks", "zrml-neo-swaps/runtime-benchmarks", "zrml-parimutuel/runtime-benchmarks", @@ -320,6 +322,7 @@ std = [ "zrml-authorized/std", "zrml-combinatorial-tokens/std", "zrml-court/std", + "zrml-futarchy/std", "zrml-hybrid-router/std", "zrml-market-commons/std", "zrml-neo-swaps/std", @@ -374,6 +377,7 @@ try-runtime = [ "zrml-authorized/try-runtime", "zrml-combinatorial-tokens/try-runtime", "zrml-court/try-runtime", + "zrml-futarchy/try-runtime", "zrml-hybrid-router/try-runtime", "zrml-market-commons/try-runtime", "zrml-neo-swaps/try-runtime", diff --git a/zrml/futarchy/Cargo.toml b/zrml/futarchy/Cargo.toml new file mode 100644 index 000000000..d8495e45f --- /dev/null +++ b/zrml/futarchy/Cargo.toml @@ -0,0 +1,58 @@ +[dependencies] +frame-benchmarking = { workspace = true, optional = true } +frame-support = { workspace = true } +frame-system = { workspace = true } +orml-traits = { workspace = true } +pallet-preimage = { workspace = true } +parity-scale-codec = { workspace = true, features = ["derive", "max-encoded-len"] } +scale-info = { workspace = true, features = ["derive"] } +sp-runtime = { workspace = true } +zeitgeist-primitives = { workspace = true } + +# mock + +env_logger = { workspace = true, optional = true } +orml-currencies = { workspace = true, optional = true } +orml-tokens = { workspace = true, optional = true } +pallet-balances = { workspace = true, optional = true } +pallet-timestamp = { workspace = true, optional = true } +sp-io = { workspace = true, optional = true } + +[dev-dependencies] +test-case = { workspace = true } +zrml-futarchy = { workspace = true, features = ["default", "mock"] } + +[features] +default = ["std"] +mock = [ + "env_logger/default", + "orml-currencies/default", + "orml-tokens/default", + "sp-io/default", + "pallet-balances/default", + "pallet-timestamp/default", + "zeitgeist-primitives/mock", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", +] +std = [ + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "orml-traits/std", + "parity-scale-codec/std", + "sp-runtime/std", + "zeitgeist-primitives/std", +] +try-runtime = [ + "frame-support/try-runtime", +] + +[package] +authors = ["Zeitgeist PM "] +edition.workspace = true +name = "zrml-futarchy" +version = "0.5.5" diff --git a/zrml/futarchy/README.md b/zrml/futarchy/README.md new file mode 100644 index 000000000..19f6917fb --- /dev/null +++ b/zrml/futarchy/README.md @@ -0,0 +1 @@ +# Futarchy Module diff --git a/zrml/futarchy/src/lib.rs b/zrml/futarchy/src/lib.rs new file mode 100644 index 000000000..e8ea5c08b --- /dev/null +++ b/zrml/futarchy/src/lib.rs @@ -0,0 +1,99 @@ +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +#![doc = include_str!("../README.md")] +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; + +pub mod mock; +mod tests; +mod traits; +pub mod types; + +pub use pallet::*; + +#[frame_support::pallet] +mod pallet { + use core::marker::PhantomData; + use frame_support::{ + pallet_prelude::{EnsureOrigin, IsType, StorageVersion}, + require_transactional, + traits::{QueryPreimage, StorePreimage}, + transactional, + }; + use frame_system::pallet_prelude::OriginFor; + use orml_traits::MultiCurrency; + use sp_runtime::DispatchResult; + + #[pallet::config] + pub trait Config: frame_system::Config { + type MultiCurrency: MultiCurrency; + + // Preimage interface for acquiring call data. + type Preimages: QueryPreimage + StorePreimage; + + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + type SubmitOrigin: EnsureOrigin; + + // // TODO + // // The origin from which proposals may be whitelisted. + // type WhitelistOrigin: EnsureOrigin; + + // TODO Scheduler, EnactmentPeriod + } + + #[pallet::pallet] + #[pallet::storage_version(STORAGE_VERSION)] + pub struct Pallet(PhantomData); + + pub(crate) type AccountIdOf = ::AccountId; + pub(crate) type BalanceOf = + <::MultiCurrency as MultiCurrency>>::Balance; + + pub(crate) const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); + + // TODO Storage Items + + #[pallet::event] + #[pallet::generate_deposit(fn deposit_event)] + pub enum Event + where + T: Config, {} + + #[pallet::error] + pub enum Error {} + + #[pallet::call] + impl Pallet { + #[pallet::call_index(0)] + #[transactional] + #[pallet::weight({0})] + pub fn submit(origin: OriginFor) -> DispatchResult { + T::SubmitOrigin::ensure_origin(origin)?; + Self::do_submit() + } + } + + impl Pallet { + #[require_transactional] + fn do_submit() -> DispatchResult { + Ok(()) + } + } +} diff --git a/zrml/futarchy/src/mock/ext_builder.rs b/zrml/futarchy/src/mock/ext_builder.rs new file mode 100644 index 000000000..ddd2d2e10 --- /dev/null +++ b/zrml/futarchy/src/mock/ext_builder.rs @@ -0,0 +1,72 @@ +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use crate::mock::runtime::{Runtime, System}; +use sp_io::TestExternalities; +use sp_runtime::BuildStorage; + +#[cfg(feature = "parachain")] +use {crate::mock::consts::FOREIGN_ASSET, zeitgeist_primitives::types::CustomMetadata}; + +pub struct ExtBuilder; + +impl ExtBuilder { + pub fn build() -> TestExternalities { + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); + + // See the logs in tests when using `RUST_LOG=debug cargo test -- --nocapture` + let _ = env_logger::builder().is_test(true).try_init(); + + pallet_balances::GenesisConfig:: { balances: vec![] } + .assimilate_storage(&mut t) + .unwrap(); + + #[cfg(feature = "parachain")] + { + orml_tokens::GenesisConfig:: { balances: vec![] } + .assimilate_storage(&mut t) + .unwrap(); + + let custom_metadata = + CustomMetadata { allow_as_base_asset: true, ..Default::default() }; + + orml_asset_registry::GenesisConfig:: { + assets: vec![( + FOREIGN_ASSET, + AssetMetadata { + decimals: 18, + name: "MKL".as_bytes().to_vec().try_into().unwrap(), + symbol: "MKL".as_bytes().to_vec().try_into().unwrap(), + existential_deposit: 0, + location: None, + additional: custom_metadata, + } + .encode(), + )], + last_asset_id: FOREIGN_ASSET, + } + .assimilate_storage(&mut t) + .unwrap(); + } + + let mut test_ext: sp_io::TestExternalities = t.into(); + + test_ext.execute_with(|| System::set_block_number(1)); + + test_ext + } +} diff --git a/zrml/futarchy/src/mock/mod.rs b/zrml/futarchy/src/mock/mod.rs new file mode 100644 index 000000000..762e0a01a --- /dev/null +++ b/zrml/futarchy/src/mock/mod.rs @@ -0,0 +1,21 @@ +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +#![cfg(feature = "mock")] + +pub mod ext_builder; +pub(crate) mod runtime; diff --git a/zrml/futarchy/src/mock/runtime.rs b/zrml/futarchy/src/mock/runtime.rs new file mode 100644 index 000000000..3ff62020d --- /dev/null +++ b/zrml/futarchy/src/mock/runtime.rs @@ -0,0 +1,131 @@ +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use crate as zrml_futarchy; +use frame_support::{construct_runtime, parameter_types, traits::Everything}; +use frame_system::{mocking::MockBlock, EnsureRoot}; +use sp_runtime::traits::{BlakeTwo256, ConstU32, IdentityLookup}; +use zeitgeist_primitives::{ + constants::mock::{ + BlockHashCount, ExistentialDeposit, ExistentialDeposits, GetNativeCurrencyId, MaxLocks, + MaxReserves, MinimumPeriod, + }, + types::{AccountIdTest, Amount, Balance, BasicCurrencyAdapter, CurrencyId, Hash, Moment}, +}; + +parameter_types! { + pub const PreimageBaseDeposit: Balance = 0; + pub const PreimageByteDeposit: Balance = 0; +} + +construct_runtime! { + pub enum Runtime { + Futarchy: zrml_futarchy, + Balances: pallet_balances, + Currencies: orml_currencies, + Preimage: pallet_preimage, + System: frame_system, + Timestamp: pallet_timestamp, + Tokens: orml_tokens, + } +} + +impl zrml_futarchy::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type MultiCurrency = Currencies; + type Preimages = Preimage; + type SubmitOrigin = EnsureRoot<::AccountId>; +} + +impl orml_currencies::Config for Runtime { + type GetNativeCurrencyId = GetNativeCurrencyId; + type MultiCurrency = Tokens; + type NativeCurrency = BasicCurrencyAdapter; + type WeightInfo = (); +} + +impl pallet_balances::Config for Runtime { + type AccountStore = System; + type Balance = Balance; + type DustRemoval = (); + type FreezeIdentifier = (); + type RuntimeHoldReason = (); + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ExistentialDeposit; + type MaxHolds = (); + type MaxFreezes = (); + type MaxLocks = MaxLocks; + type MaxReserves = MaxReserves; + type ReserveIdentifier = [u8; 8]; + type WeightInfo = (); +} + +impl pallet_preimage::Config for Runtime { + type WeightInfo = (); + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type ManagerOrigin = EnsureRoot; + type BaseDeposit = PreimageBaseDeposit; + type ByteDeposit = PreimageByteDeposit; +} + +impl frame_system::Config for Runtime { + type AccountData = pallet_balances::AccountData; + type AccountId = AccountIdTest; + type BaseCallFilter = Everything; + type Block = MockBlock; + type BlockHashCount = BlockHashCount; + type BlockLength = (); + type BlockWeights = (); + type RuntimeCall = RuntimeCall; + type DbWeight = (); + type RuntimeEvent = RuntimeEvent; + type Hash = Hash; + type Hashing = BlakeTwo256; + type Lookup = IdentityLookup; + type Nonce = u64; + type MaxConsumers = ConstU32<16>; + type OnKilledAccount = (); + type OnNewAccount = (); + type RuntimeOrigin = RuntimeOrigin; + type PalletInfo = PalletInfo; + type SS58Prefix = (); + type SystemWeightInfo = (); + type Version = (); + type OnSetCode = (); +} + +impl pallet_timestamp::Config for Runtime { + type MinimumPeriod = MinimumPeriod; + type Moment = Moment; + type OnTimestampSet = (); + type WeightInfo = (); +} + +impl orml_tokens::Config for Runtime { + type Amount = Amount; + type Balance = Balance; + type CurrencyId = CurrencyId; + type DustRemovalWhitelist = Everything; + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposits = ExistentialDeposits; + type MaxLocks = MaxLocks; + type MaxReserves = MaxReserves; + type CurrencyHooks = (); + type ReserveIdentifier = [u8; 8]; + type WeightInfo = (); +} diff --git a/zrml/futarchy/src/tests/mod.rs b/zrml/futarchy/src/tests/mod.rs new file mode 100644 index 000000000..3c2c4444b --- /dev/null +++ b/zrml/futarchy/src/tests/mod.rs @@ -0,0 +1,18 @@ +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +#![cfg(all(feature = "mock", test))] diff --git a/zrml/futarchy/src/traits/mod.rs b/zrml/futarchy/src/traits/mod.rs new file mode 100644 index 000000000..1032ee726 --- /dev/null +++ b/zrml/futarchy/src/traits/mod.rs @@ -0,0 +1,16 @@ +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . diff --git a/zrml/futarchy/src/types/mod.rs b/zrml/futarchy/src/types/mod.rs new file mode 100644 index 000000000..1032ee726 --- /dev/null +++ b/zrml/futarchy/src/types/mod.rs @@ -0,0 +1,16 @@ +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . From 0e0080bd9827cc1e7c7fdbefc3181389e49c72f6 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Thu, 17 Oct 2024 18:34:55 +0200 Subject: [PATCH 16/73] . --- zrml/futarchy/src/lib.rs | 137 ++++++++++++++++--- zrml/futarchy/src/mock/mod.rs | 1 + zrml/futarchy/src/mock/runtime.rs | 4 +- zrml/futarchy/src/mock/types/mod.rs | 3 + zrml/futarchy/src/mock/types/oracle_query.rs | 20 +++ zrml/futarchy/src/traits/mod.rs | 4 + zrml/futarchy/src/traits/oracle_query.rs | 12 ++ zrml/futarchy/src/types/mod.rs | 4 + zrml/futarchy/src/types/proposal.rs | 16 +++ 9 files changed, 181 insertions(+), 20 deletions(-) create mode 100644 zrml/futarchy/src/mock/types/mod.rs create mode 100644 zrml/futarchy/src/mock/types/oracle_query.rs create mode 100644 zrml/futarchy/src/traits/oracle_query.rs create mode 100644 zrml/futarchy/src/types/proposal.rs diff --git a/zrml/futarchy/src/lib.rs b/zrml/futarchy/src/lib.rs index e8ea5c08b..2aaf83c1f 100644 --- a/zrml/futarchy/src/lib.rs +++ b/zrml/futarchy/src/lib.rs @@ -29,33 +29,44 @@ pub use pallet::*; #[frame_support::pallet] mod pallet { + use crate::{traits::OracleQuery, types::Proposal}; use core::marker::PhantomData; use frame_support::{ - pallet_prelude::{EnsureOrigin, IsType, StorageVersion}, + ensure, + pallet_prelude::{EnsureOrigin, IsType, StorageMap, StorageVersion, ValueQuery}, require_transactional, - traits::{QueryPreimage, StorePreimage}, - transactional, + traits::{ + schedule::{v3::Anon as ScheduleAnon, DispatchTime}, + Bounded, Hooks, QueryPreimage, StorePreimage, + }, + transactional, Blake2_128Concat, BoundedVec, }; - use frame_system::pallet_prelude::OriginFor; + use frame_system::pallet_prelude::{BlockNumberFor, OriginFor}; use orml_traits::MultiCurrency; - use sp_runtime::DispatchResult; + use sp_runtime::{ + traits::{ConstU32, Get}, + DispatchResult, Saturating, + }; + use frame_support::pallet_prelude::Weight;use frame_support::dispatch::RawOrigin; #[pallet::config] pub trait Config: frame_system::Config { type MultiCurrency: MultiCurrency; - // Preimage interface for acquiring call data. + type MinDuration: Get>; + + type OracleQuery: OracleQuery; + + /// Preimage interface for acquiring call data. type Preimages: QueryPreimage + StorePreimage; type RuntimeEvent: From> + IsType<::RuntimeEvent>; - type SubmitOrigin: EnsureOrigin; - - // // TODO - // // The origin from which proposals may be whitelisted. - // type WhitelistOrigin: EnsureOrigin; + /// Scheduler interface for executing proposals. + type Scheduler: ScheduleAnon, CallOf, OriginFor>; - // TODO Scheduler, EnactmentPeriod + /// The origin that is allowed to submit proposals. + type SubmitOrigin: EnsureOrigin; } #[pallet::pallet] @@ -65,35 +76,123 @@ mod pallet { pub(crate) type AccountIdOf = ::AccountId; pub(crate) type BalanceOf = <::MultiCurrency as MultiCurrency>>::Balance; + pub(crate) type CacheSize = ConstU32<16>; + pub(crate) type CallOf = ::RuntimeCall; + pub(crate) type BoundedCallOf = Bounded>; + pub(crate) type OracleQueryOf = ::OracleQuery; + pub(crate) type ProposalOf = Proposal, BoundedCallOf, OracleQueryOf>; pub(crate) const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); - // TODO Storage Items + #[pallet::storage] + pub type Proposals = StorageMap< + _, + Blake2_128Concat, + BlockNumberFor, + BoundedVec, CacheSize>, + ValueQuery, + >; #[pallet::event] #[pallet::generate_deposit(fn deposit_event)] pub enum Event where - T: Config, {} + T: Config, + { + /// A proposal has been submitted. + Submitted { duration: BlockNumberFor, proposal: ProposalOf }, + + /// A proposal has been rejected by the oracle. + Rejected, + + /// A proposal has been scheduled for execution. + Scheduled, + + /// This is a logic error. You shouldn't see this. + UnexpectedSchedulerError, + } #[pallet::error] - pub enum Error {} + pub enum Error { + /// The cache for this particular block is full. Try another block. + CacheFull, + /// The specified duration must be at least equal to `MinDuration`. + DurationTooShort, + } + // TODO: Index for proposal? #[pallet::call] impl Pallet { #[pallet::call_index(0)] #[transactional] #[pallet::weight({0})] - pub fn submit(origin: OriginFor) -> DispatchResult { + pub fn submit_proposal( + origin: OriginFor, + duration: BlockNumberFor, + proposal: ProposalOf, + ) -> DispatchResult { T::SubmitOrigin::ensure_origin(origin)?; - Self::do_submit() + Self::do_submit_proposal(duration, proposal) } } impl Pallet { #[require_transactional] - fn do_submit() -> DispatchResult { - Ok(()) + fn do_submit_proposal( + duration: BlockNumberFor, + proposal: ProposalOf, + ) -> DispatchResult { + ensure!(duration >= T::MinDuration::get(), Error::::DurationTooShort); + + let now = frame_system::Pallet::::block_number(); + let to_be_scheduled_at = now.saturating_add(duration); + + Ok(Proposals::::try_mutate(to_be_scheduled_at, |proposals| { + proposals.try_push(proposal).map_err(|_| Error::::CacheFull) + })?) + } + + /// Evaluates `proposal` using the specified oracle and schedules the contained call if the + /// oracle approves. + fn maybe_schedule_proposal(proposal: ProposalOf) -> Weight { + let (evaluate_weight, approved) = proposal.query.evaluate(); + + if approved { + let result = T::Scheduler::schedule( + DispatchTime::At(proposal.when), + None, + 63, + RawOrigin::Root.into(), + proposal.call, + ); + + if result.is_ok() { + Self::deposit_event(Event::::Scheduled); + } else { + Self::deposit_event(Event::::UnexpectedSchedulerError); + } + + evaluate_weight // TODO Add benchmark! + } else { + Self::deposit_event(Event::::Rejected); + + evaluate_weight + } + } + } + + #[pallet::hooks] + impl Hooks> for Pallet { + fn on_initialize(now: BlockNumberFor) -> Weight { + let mut total_weight = Weight::zero(); + + let proposals = Proposals::::take(now); + for proposal in proposals.into_iter() { + let weight = Self::maybe_schedule_proposal(proposal); + total_weight = total_weight.saturating_add(weight); + } + + total_weight } } } diff --git a/zrml/futarchy/src/mock/mod.rs b/zrml/futarchy/src/mock/mod.rs index 762e0a01a..e0345b76b 100644 --- a/zrml/futarchy/src/mock/mod.rs +++ b/zrml/futarchy/src/mock/mod.rs @@ -19,3 +19,4 @@ pub mod ext_builder; pub(crate) mod runtime; +pub(crate) mod types; diff --git a/zrml/futarchy/src/mock/runtime.rs b/zrml/futarchy/src/mock/runtime.rs index 3ff62020d..44b29221b 100644 --- a/zrml/futarchy/src/mock/runtime.rs +++ b/zrml/futarchy/src/mock/runtime.rs @@ -16,6 +16,7 @@ // along with Zeitgeist. If not, see . use crate as zrml_futarchy; +use crate::mock::types::MockOracleQuery; use frame_support::{construct_runtime, parameter_types, traits::Everything}; use frame_system::{mocking::MockBlock, EnsureRoot}; use sp_runtime::traits::{BlakeTwo256, ConstU32, IdentityLookup}; @@ -45,9 +46,10 @@ construct_runtime! { } impl zrml_futarchy::Config for Runtime { - type RuntimeEvent = RuntimeEvent; type MultiCurrency = Currencies; + type OracleQuery = MockOracleQuery; type Preimages = Preimage; + type RuntimeEvent = RuntimeEvent; type SubmitOrigin = EnsureRoot<::AccountId>; } diff --git a/zrml/futarchy/src/mock/types/mod.rs b/zrml/futarchy/src/mock/types/mod.rs new file mode 100644 index 000000000..438c6f098 --- /dev/null +++ b/zrml/futarchy/src/mock/types/mod.rs @@ -0,0 +1,3 @@ +pub mod oracle_query; + +pub use oracle_query::MockOracleQuery; diff --git a/zrml/futarchy/src/mock/types/oracle_query.rs b/zrml/futarchy/src/mock/types/oracle_query.rs new file mode 100644 index 000000000..adb3edf37 --- /dev/null +++ b/zrml/futarchy/src/mock/types/oracle_query.rs @@ -0,0 +1,20 @@ +use crate::traits::OracleQuery; +use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; + +#[derive(Decode, Encode, MaxEncodedLen, TypeInfo)] +pub struct MockOracleQuery { + value: bool, +} + +impl MockOracleQuery { + fn new(value: bool) -> Self { + Self { value } + } +} + +impl OracleQuery for MockOracleQuery { + fn evaluate(&self) -> bool { + self.value + } +} diff --git a/zrml/futarchy/src/traits/mod.rs b/zrml/futarchy/src/traits/mod.rs index 1032ee726..744956dca 100644 --- a/zrml/futarchy/src/traits/mod.rs +++ b/zrml/futarchy/src/traits/mod.rs @@ -14,3 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . + +mod oracle_query; + +pub(crate) use oracle_query::OracleQuery; diff --git a/zrml/futarchy/src/traits/oracle_query.rs b/zrml/futarchy/src/traits/oracle_query.rs new file mode 100644 index 000000000..699d9a5e7 --- /dev/null +++ b/zrml/futarchy/src/traits/oracle_query.rs @@ -0,0 +1,12 @@ +use alloc::fmt::Debug; +use frame_support::pallet_prelude::Weight; +use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; + +pub trait OracleQuery: + Clone + Debug + Decode + Encode + MaxEncodedLen + PartialEq + TypeInfo +{ + /// Evaluates the query at the current block and returns the weight consumed and a `bool` + /// indicating whether the query evaluated positively. + fn evaluate(&self) -> (Weight, bool); +} diff --git a/zrml/futarchy/src/types/mod.rs b/zrml/futarchy/src/types/mod.rs index 1032ee726..0bce47392 100644 --- a/zrml/futarchy/src/types/mod.rs +++ b/zrml/futarchy/src/types/mod.rs @@ -14,3 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . + +mod proposal; + +pub use proposal::Proposal; diff --git a/zrml/futarchy/src/types/proposal.rs b/zrml/futarchy/src/types/proposal.rs new file mode 100644 index 000000000..85de59099 --- /dev/null +++ b/zrml/futarchy/src/types/proposal.rs @@ -0,0 +1,16 @@ +use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; +use alloc::fmt::Debug; + +// TODO Make config a generic, keeps things simple. +#[derive(Clone, Debug, Decode, Encode, MaxEncodedLen, PartialEq, TypeInfo)] +pub struct Proposal +where + When: Clone + Debug + Decode + Encode + MaxEncodedLen + PartialEq + TypeInfo, + BoundedCall: Clone + Debug + Decode + Encode + MaxEncodedLen + PartialEq + TypeInfo, + OracleQuery: Clone + Debug + Decode + Encode + MaxEncodedLen + PartialEq + TypeInfo, +{ + pub when: When, + pub call: BoundedCall, + pub query: OracleQuery, +} From 26d5b8ae5da0619eb20ce94ebb6235fb00c1d312 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Thu, 17 Oct 2024 19:53:22 +0200 Subject: [PATCH 17/73] Implement Scheduler mock --- Cargo.lock | 1 + runtime/common/src/lib.rs | 7 +- runtime/zeitgeist/src/parameters.rs | 3 + zrml/futarchy/Cargo.toml | 5 +- zrml/futarchy/src/lib.rs | 14 ++-- zrml/futarchy/src/mock/runtime.rs | 35 +++++++-- zrml/futarchy/src/mock/types/mod.rs | 2 + zrml/futarchy/src/mock/types/oracle_query.rs | 15 ++-- zrml/futarchy/src/mock/types/scheduler.rs | 79 ++++++++++++++++++++ 9 files changed, 136 insertions(+), 25 deletions(-) create mode 100644 zrml/futarchy/src/mock/types/scheduler.rs diff --git a/Cargo.lock b/Cargo.lock index 81586cbd9..395e39bf2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15255,6 +15255,7 @@ dependencies = [ "orml-traits", "pallet-balances", "pallet-preimage", + "pallet-scheduler", "pallet-timestamp", "parity-scale-codec", "scale-info", diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs index 20a3fcecc..cf215a6d4 100644 --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -1207,9 +1207,12 @@ macro_rules! impl_config_traits { } impl zrml_futarchy::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type MultiCurrency = AssetManager; + type MultiCurrency = Currencies; + type MinDuration = MinDuration; + type OracleQuery = MockOracleQuery; type Preimages = Preimage; + type RuntimeEvent = RuntimeEvent; + type Scheduler = Scheduler; type SubmitOrigin = EnsureRoot; } diff --git a/runtime/zeitgeist/src/parameters.rs b/runtime/zeitgeist/src/parameters.rs index 9bf2ba3df..9792b5f35 100644 --- a/runtime/zeitgeist/src/parameters.rs +++ b/runtime/zeitgeist/src/parameters.rs @@ -167,6 +167,9 @@ parameter_types! { /// The maximum number of public proposals that can exist at any time. pub const MaxProposals: u32 = 100; + // Futarchy + pub const MinDuration: BlockNumber = 7 * BLOCKS_PER_DAY; + // Hybrid Router parameters pub const HybridRouterPalletId: PalletId = HYBRID_ROUTER_PALLET_ID; /// Maximum number of orders that can be placed in a single trade transaction. diff --git a/zrml/futarchy/Cargo.toml b/zrml/futarchy/Cargo.toml index d8495e45f..8d5d7f053 100644 --- a/zrml/futarchy/Cargo.toml +++ b/zrml/futarchy/Cargo.toml @@ -3,7 +3,6 @@ frame-benchmarking = { workspace = true, optional = true } frame-support = { workspace = true } frame-system = { workspace = true } orml-traits = { workspace = true } -pallet-preimage = { workspace = true } parity-scale-codec = { workspace = true, features = ["derive", "max-encoded-len"] } scale-info = { workspace = true, features = ["derive"] } sp-runtime = { workspace = true } @@ -15,6 +14,8 @@ env_logger = { workspace = true, optional = true } orml-currencies = { workspace = true, optional = true } orml-tokens = { workspace = true, optional = true } pallet-balances = { workspace = true, optional = true } +pallet-preimage = { workspace = true } +pallet-scheduler = { workspace = true, optional = true } pallet-timestamp = { workspace = true, optional = true } sp-io = { workspace = true, optional = true } @@ -30,6 +31,8 @@ mock = [ "orml-tokens/default", "sp-io/default", "pallet-balances/default", + "pallet-preimage/default", + "pallet-scheduler/default", "pallet-timestamp/default", "zeitgeist-primitives/mock", ] diff --git a/zrml/futarchy/src/lib.rs b/zrml/futarchy/src/lib.rs index 2aaf83c1f..ad2d1c609 100644 --- a/zrml/futarchy/src/lib.rs +++ b/zrml/futarchy/src/lib.rs @@ -32,8 +32,9 @@ mod pallet { use crate::{traits::OracleQuery, types::Proposal}; use core::marker::PhantomData; use frame_support::{ + dispatch::RawOrigin, ensure, - pallet_prelude::{EnsureOrigin, IsType, StorageMap, StorageVersion, ValueQuery}, + pallet_prelude::{EnsureOrigin, IsType, StorageMap, StorageVersion, ValueQuery, Weight}, require_transactional, traits::{ schedule::{v3::Anon as ScheduleAnon, DispatchTime}, @@ -47,7 +48,6 @@ mod pallet { traits::{ConstU32, Get}, DispatchResult, Saturating, }; - use frame_support::pallet_prelude::Weight;use frame_support::dispatch::RawOrigin; #[pallet::config] pub trait Config: frame_system::Config { @@ -58,7 +58,7 @@ mod pallet { type OracleQuery: OracleQuery; /// Preimage interface for acquiring call data. - type Preimages: QueryPreimage + StorePreimage; + type Preimages: QueryPreimage + StorePreimage; // TODO Why do we even need this? type RuntimeEvent: From> + IsType<::RuntimeEvent>; @@ -116,6 +116,7 @@ mod pallet { pub enum Error { /// The cache for this particular block is full. Try another block. CacheFull, + /// The specified duration must be at least equal to `MinDuration`. DurationTooShort, } @@ -132,6 +133,7 @@ mod pallet { proposal: ProposalOf, ) -> DispatchResult { T::SubmitOrigin::ensure_origin(origin)?; + Self::do_submit_proposal(duration, proposal) } } @@ -147,9 +149,11 @@ mod pallet { let now = frame_system::Pallet::::block_number(); let to_be_scheduled_at = now.saturating_add(duration); - Ok(Proposals::::try_mutate(to_be_scheduled_at, |proposals| { + let try_mutate_result = Proposals::::try_mutate(to_be_scheduled_at, |proposals| { proposals.try_push(proposal).map_err(|_| Error::::CacheFull) - })?) + }); + + Ok(try_mutate_result?) } /// Evaluates `proposal` using the specified oracle and schedules the contained call if the diff --git a/zrml/futarchy/src/mock/runtime.rs b/zrml/futarchy/src/mock/runtime.rs index 44b29221b..90edfa97f 100644 --- a/zrml/futarchy/src/mock/runtime.rs +++ b/zrml/futarchy/src/mock/runtime.rs @@ -16,19 +16,36 @@ // along with Zeitgeist. If not, see . use crate as zrml_futarchy; -use crate::mock::types::MockOracleQuery; -use frame_support::{construct_runtime, parameter_types, traits::Everything}; +use crate::mock::types::{MockOracleQuery, MockScheduler}; +use frame_support::{ + construct_runtime, + pallet_prelude::Weight, + parameter_types, + traits::{EqualPrivilegeOnly, Everything}, +}; use frame_system::{mocking::MockBlock, EnsureRoot}; -use sp_runtime::traits::{BlakeTwo256, ConstU32, IdentityLookup}; +use sp_runtime::{ + traits::{BlakeTwo256, ConstU32, IdentityLookup}, + Perbill, +}; use zeitgeist_primitives::{ - constants::mock::{ - BlockHashCount, ExistentialDeposit, ExistentialDeposits, GetNativeCurrencyId, MaxLocks, - MaxReserves, MinimumPeriod, + constants::{ + mock::{ + BlockHashCount, ExistentialDeposit, ExistentialDeposits, GetNativeCurrencyId, MaxLocks, + MaxReserves, MinimumPeriod, + }, + BLOCKS_PER_MINUTE, + }, + types::{ + AccountIdTest, Amount, Balance, BasicCurrencyAdapter, BlockNumber, CurrencyId, Hash, Moment, }, - types::{AccountIdTest, Amount, Balance, BasicCurrencyAdapter, CurrencyId, Hash, Moment}, }; parameter_types! { + // zrml-futarchy + pub const MinDuration: BlockNumber = 10; + + // pallet-preimage pub const PreimageBaseDeposit: Balance = 0; pub const PreimageByteDeposit: Balance = 0; } @@ -47,9 +64,11 @@ construct_runtime! { impl zrml_futarchy::Config for Runtime { type MultiCurrency = Currencies; + type MinDuration = MinDuration; type OracleQuery = MockOracleQuery; type Preimages = Preimage; type RuntimeEvent = RuntimeEvent; + type Scheduler = MockScheduler; type SubmitOrigin = EnsureRoot<::AccountId>; } @@ -80,7 +99,7 @@ impl pallet_preimage::Config for Runtime { type WeightInfo = (); type RuntimeEvent = RuntimeEvent; type Currency = Balances; - type ManagerOrigin = EnsureRoot; + type ManagerOrigin = EnsureRoot<::AccountId>; type BaseDeposit = PreimageBaseDeposit; type ByteDeposit = PreimageByteDeposit; } diff --git a/zrml/futarchy/src/mock/types/mod.rs b/zrml/futarchy/src/mock/types/mod.rs index 438c6f098..a41cb9bfb 100644 --- a/zrml/futarchy/src/mock/types/mod.rs +++ b/zrml/futarchy/src/mock/types/mod.rs @@ -1,3 +1,5 @@ pub mod oracle_query; +pub mod scheduler; pub use oracle_query::MockOracleQuery; +pub use scheduler::MockScheduler; diff --git a/zrml/futarchy/src/mock/types/oracle_query.rs b/zrml/futarchy/src/mock/types/oracle_query.rs index adb3edf37..86666c536 100644 --- a/zrml/futarchy/src/mock/types/oracle_query.rs +++ b/zrml/futarchy/src/mock/types/oracle_query.rs @@ -1,20 +1,17 @@ use crate::traits::OracleQuery; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; +use alloc::fmt::Debug; +use frame_support::pallet_prelude::Weight; -#[derive(Decode, Encode, MaxEncodedLen, TypeInfo)] +#[derive(Clone, Debug, Decode, Encode, MaxEncodedLen, PartialEq, TypeInfo)] pub struct MockOracleQuery { + weight: Weight, value: bool, } -impl MockOracleQuery { - fn new(value: bool) -> Self { - Self { value } - } -} - impl OracleQuery for MockOracleQuery { - fn evaluate(&self) -> bool { - self.value + fn evaluate(&self) -> (Weight, bool) { + (self.weight, self.value) } } diff --git a/zrml/futarchy/src/mock/types/scheduler.rs b/zrml/futarchy/src/mock/types/scheduler.rs new file mode 100644 index 000000000..6391387e8 --- /dev/null +++ b/zrml/futarchy/src/mock/types/scheduler.rs @@ -0,0 +1,79 @@ +use crate::{mock::runtime::Runtime, BoundedCallOf, CallOf}; +use core::cell::RefCell; +use frame_support::traits::schedule::{v3::Anon as ScheduleAnon, DispatchTime, Period, Priority}; +use frame_system::{ + pallet_prelude::{BlockNumberFor, OriginFor}, + Call, Origin, +}; +use sp_runtime::{traits::Bounded, DispatchError, DispatchResult}; + +pub struct MockScheduler; + +impl MockScheduler { + fn set_return_value(value: DispatchResult) { + SCHEDULER_RETURN_VALUE.with(|v| *v.borrow_mut() = Some(value)); + } + + fn called_once_with( + when: DispatchTime>, + call: BoundedCallOf, + ) -> bool { + if SCHEDULER_CALL_DATA.with(|value| value.borrow().len()) != 1 { + return false; + } + + let args = SCHEDULER_CALL_DATA + .with(|value| value.borrow().first().expect("can't be empty").clone()); + + args == SchedulerCallData { when, call } + } +} + +#[derive(Clone, PartialEq)] +struct SchedulerCallData { + when: DispatchTime>, + call: BoundedCallOf, +} + +impl ScheduleAnon, CallOf, OriginFor> for MockScheduler { + type Address = (); + + fn schedule( + when: DispatchTime>, + _maybe_periodic: Option>>, + _priority: Priority, + _origin: OriginFor, + call: BoundedCallOf, + ) -> Result { + SCHEDULER_CALL_DATA + .with(|values| values.borrow_mut().push(SchedulerCallData { when, call })); + + SCHEDULER_RETURN_VALUE + .with(|value| *value.borrow()) + .expect("no return value configured for scheduler mock") + } + + fn cancel(_address: Self::Address) -> Result<(), DispatchError> { + unimplemented!(); + } + + fn reschedule( + _address: Self::Address, + _when: DispatchTime>, + ) -> Result { + unimplemented!(); + } + + fn next_dispatch_time( + _address: Self::Address, + ) -> Result, DispatchError> { + unimplemented!(); + } +} + +thread_local! { + pub static SCHEDULER_CALL_DATA: RefCell> = + const { RefCell::new(vec![]) }; + pub static SCHEDULER_RETURN_VALUE: RefCell> = + const { RefCell::new(None) }; +} From a2cc42f147361c10da4e2324e0a8cf33127d1e8e Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Thu, 17 Oct 2024 21:26:41 +0200 Subject: [PATCH 18/73] . --- zrml/futarchy/src/dispatchable_impls.rs | 23 ++++++++++ zrml/futarchy/src/lib.rs | 53 ++-------------------- zrml/futarchy/src/pallet_impls.rs | 33 ++++++++++++++ zrml/futarchy/src/tests/mod.rs | 2 + zrml/futarchy/src/tests/submit_proposal.rs | 2 + 5 files changed, 64 insertions(+), 49 deletions(-) create mode 100644 zrml/futarchy/src/dispatchable_impls.rs create mode 100644 zrml/futarchy/src/pallet_impls.rs create mode 100644 zrml/futarchy/src/tests/submit_proposal.rs diff --git a/zrml/futarchy/src/dispatchable_impls.rs b/zrml/futarchy/src/dispatchable_impls.rs new file mode 100644 index 000000000..cb033b61c --- /dev/null +++ b/zrml/futarchy/src/dispatchable_impls.rs @@ -0,0 +1,23 @@ +use crate::{Config, Error, Pallet, ProposalOf, Proposals}; +use frame_support::{ensure, require_transactional, traits::Get}; +use frame_system::pallet_prelude::BlockNumberFor; +use sp_runtime::{DispatchResult, Saturating}; + +impl Pallet { + #[require_transactional] + pub(crate) fn do_submit_proposal( + duration: BlockNumberFor, + proposal: ProposalOf, + ) -> DispatchResult { + ensure!(duration >= T::MinDuration::get(), Error::::DurationTooShort); + + let now = frame_system::Pallet::::block_number(); + let to_be_scheduled_at = now.saturating_add(duration); + + let try_mutate_result = Proposals::::try_mutate(to_be_scheduled_at, |proposals| { + proposals.try_push(proposal).map_err(|_| Error::::CacheFull) + }); + + Ok(try_mutate_result?) + } +} diff --git a/zrml/futarchy/src/lib.rs b/zrml/futarchy/src/lib.rs index ad2d1c609..2955e229d 100644 --- a/zrml/futarchy/src/lib.rs +++ b/zrml/futarchy/src/lib.rs @@ -20,6 +20,8 @@ extern crate alloc; +mod dispatchable_impls; +mod pallet_impls; pub mod mock; mod tests; mod traits; @@ -51,7 +53,7 @@ mod pallet { #[pallet::config] pub trait Config: frame_system::Config { - type MultiCurrency: MultiCurrency; + type MultiCurrency: MultiCurrency; // TODO Do we need this? type MinDuration: Get>; @@ -94,7 +96,7 @@ mod pallet { >; #[pallet::event] - #[pallet::generate_deposit(fn deposit_event)] + #[pallet::generate_deposit(pub(crate) fn deposit_event)] pub enum Event where T: Config, @@ -138,53 +140,6 @@ mod pallet { } } - impl Pallet { - #[require_transactional] - fn do_submit_proposal( - duration: BlockNumberFor, - proposal: ProposalOf, - ) -> DispatchResult { - ensure!(duration >= T::MinDuration::get(), Error::::DurationTooShort); - - let now = frame_system::Pallet::::block_number(); - let to_be_scheduled_at = now.saturating_add(duration); - - let try_mutate_result = Proposals::::try_mutate(to_be_scheduled_at, |proposals| { - proposals.try_push(proposal).map_err(|_| Error::::CacheFull) - }); - - Ok(try_mutate_result?) - } - - /// Evaluates `proposal` using the specified oracle and schedules the contained call if the - /// oracle approves. - fn maybe_schedule_proposal(proposal: ProposalOf) -> Weight { - let (evaluate_weight, approved) = proposal.query.evaluate(); - - if approved { - let result = T::Scheduler::schedule( - DispatchTime::At(proposal.when), - None, - 63, - RawOrigin::Root.into(), - proposal.call, - ); - - if result.is_ok() { - Self::deposit_event(Event::::Scheduled); - } else { - Self::deposit_event(Event::::UnexpectedSchedulerError); - } - - evaluate_weight // TODO Add benchmark! - } else { - Self::deposit_event(Event::::Rejected); - - evaluate_weight - } - } - } - #[pallet::hooks] impl Hooks> for Pallet { fn on_initialize(now: BlockNumberFor) -> Weight { diff --git a/zrml/futarchy/src/pallet_impls.rs b/zrml/futarchy/src/pallet_impls.rs new file mode 100644 index 000000000..605be8abe --- /dev/null +++ b/zrml/futarchy/src/pallet_impls.rs @@ -0,0 +1,33 @@ +use crate::{traits::OracleQuery, Config, Event, Pallet, ProposalOf}; +use frame_support::{dispatch::RawOrigin, pallet_prelude::Weight, traits::schedule::DispatchTime}; +use frame_support::traits::schedule::v3::Anon; + +impl Pallet { + /// Evaluates `proposal` using the specified oracle and schedules the contained call if the + /// oracle approves. + pub(crate) fn maybe_schedule_proposal(proposal: ProposalOf) -> Weight { + let (evaluate_weight, approved) = proposal.query.evaluate(); + + if approved { + let result = T::Scheduler::schedule( + DispatchTime::At(proposal.when), + None, + 63, + RawOrigin::Root.into(), + proposal.call, + ); + + if result.is_ok() { + Self::deposit_event(Event::::Scheduled); + } else { + Self::deposit_event(Event::::UnexpectedSchedulerError); + } + + evaluate_weight // TODO Add benchmark! + } else { + Self::deposit_event(Event::::Rejected); + + evaluate_weight + } + } +} diff --git a/zrml/futarchy/src/tests/mod.rs b/zrml/futarchy/src/tests/mod.rs index 3c2c4444b..d6dd1e1d4 100644 --- a/zrml/futarchy/src/tests/mod.rs +++ b/zrml/futarchy/src/tests/mod.rs @@ -16,3 +16,5 @@ // along with Zeitgeist. If not, see . #![cfg(all(feature = "mock", test))] + +mod submit_proposal; diff --git a/zrml/futarchy/src/tests/submit_proposal.rs b/zrml/futarchy/src/tests/submit_proposal.rs new file mode 100644 index 000000000..c61e54b5d --- /dev/null +++ b/zrml/futarchy/src/tests/submit_proposal.rs @@ -0,0 +1,2 @@ +#[test] +fn test() {} From afaf92bd6b1bca4b65e5385e9b16dc36b975f536 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Thu, 17 Oct 2024 22:52:56 +0200 Subject: [PATCH 19/73] wip --- zrml/futarchy/src/dispatchable_impls.rs | 4 +- zrml/futarchy/src/lib.rs | 17 +++------ zrml/futarchy/src/mock/runtime.rs | 21 +++-------- zrml/futarchy/src/mock/types/oracle_query.rs | 2 +- zrml/futarchy/src/mock/types/scheduler.rs | 1 - zrml/futarchy/src/pallet_impls.rs | 4 +- zrml/futarchy/src/tests/mod.rs | 28 ++++++++++++++ zrml/futarchy/src/tests/submit_proposal.rs | 39 +++++++++++++++++++- zrml/futarchy/src/traits/oracle_query.rs | 2 +- zrml/futarchy/src/types/proposal.rs | 22 ++++++----- 10 files changed, 96 insertions(+), 44 deletions(-) diff --git a/zrml/futarchy/src/dispatchable_impls.rs b/zrml/futarchy/src/dispatchable_impls.rs index cb033b61c..6e6972dc1 100644 --- a/zrml/futarchy/src/dispatchable_impls.rs +++ b/zrml/futarchy/src/dispatchable_impls.rs @@ -1,4 +1,4 @@ -use crate::{Config, Error, Pallet, ProposalOf, Proposals}; +use crate::{Config, Error, Pallet, types::Proposal, Proposals}; use frame_support::{ensure, require_transactional, traits::Get}; use frame_system::pallet_prelude::BlockNumberFor; use sp_runtime::{DispatchResult, Saturating}; @@ -7,7 +7,7 @@ impl Pallet { #[require_transactional] pub(crate) fn do_submit_proposal( duration: BlockNumberFor, - proposal: ProposalOf, + proposal: Proposal, ) -> DispatchResult { ensure!(duration >= T::MinDuration::get(), Error::::DurationTooShort); diff --git a/zrml/futarchy/src/lib.rs b/zrml/futarchy/src/lib.rs index 2955e229d..bb789ff29 100644 --- a/zrml/futarchy/src/lib.rs +++ b/zrml/futarchy/src/lib.rs @@ -21,8 +21,8 @@ extern crate alloc; mod dispatchable_impls; -mod pallet_impls; pub mod mock; +mod pallet_impls; mod tests; mod traits; pub mod types; @@ -34,13 +34,9 @@ mod pallet { use crate::{traits::OracleQuery, types::Proposal}; use core::marker::PhantomData; use frame_support::{ - dispatch::RawOrigin, - ensure, pallet_prelude::{EnsureOrigin, IsType, StorageMap, StorageVersion, ValueQuery, Weight}, - require_transactional, traits::{ - schedule::{v3::Anon as ScheduleAnon, DispatchTime}, - Bounded, Hooks, QueryPreimage, StorePreimage, + schedule::v3::Anon as ScheduleAnon, Bounded, Hooks, QueryPreimage, StorePreimage, }, transactional, Blake2_128Concat, BoundedVec, }; @@ -48,7 +44,7 @@ mod pallet { use orml_traits::MultiCurrency; use sp_runtime::{ traits::{ConstU32, Get}, - DispatchResult, Saturating, + DispatchResult, }; #[pallet::config] @@ -82,7 +78,6 @@ mod pallet { pub(crate) type CallOf = ::RuntimeCall; pub(crate) type BoundedCallOf = Bounded>; pub(crate) type OracleQueryOf = ::OracleQuery; - pub(crate) type ProposalOf = Proposal, BoundedCallOf, OracleQueryOf>; pub(crate) const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); @@ -91,7 +86,7 @@ mod pallet { _, Blake2_128Concat, BlockNumberFor, - BoundedVec, CacheSize>, + BoundedVec, CacheSize>, ValueQuery, >; @@ -102,7 +97,7 @@ mod pallet { T: Config, { /// A proposal has been submitted. - Submitted { duration: BlockNumberFor, proposal: ProposalOf }, + Submitted { duration: BlockNumberFor, proposal: Proposal }, /// A proposal has been rejected by the oracle. Rejected, @@ -132,7 +127,7 @@ mod pallet { pub fn submit_proposal( origin: OriginFor, duration: BlockNumberFor, - proposal: ProposalOf, + proposal: Proposal, ) -> DispatchResult { T::SubmitOrigin::ensure_origin(origin)?; diff --git a/zrml/futarchy/src/mock/runtime.rs b/zrml/futarchy/src/mock/runtime.rs index 90edfa97f..0591bb305 100644 --- a/zrml/futarchy/src/mock/runtime.rs +++ b/zrml/futarchy/src/mock/runtime.rs @@ -17,24 +17,13 @@ use crate as zrml_futarchy; use crate::mock::types::{MockOracleQuery, MockScheduler}; -use frame_support::{ - construct_runtime, - pallet_prelude::Weight, - parameter_types, - traits::{EqualPrivilegeOnly, Everything}, -}; +use frame_support::{construct_runtime, parameter_types, traits::Everything}; use frame_system::{mocking::MockBlock, EnsureRoot}; -use sp_runtime::{ - traits::{BlakeTwo256, ConstU32, IdentityLookup}, - Perbill, -}; +use sp_runtime::traits::{BlakeTwo256, ConstU32, IdentityLookup}; use zeitgeist_primitives::{ - constants::{ - mock::{ - BlockHashCount, ExistentialDeposit, ExistentialDeposits, GetNativeCurrencyId, MaxLocks, - MaxReserves, MinimumPeriod, - }, - BLOCKS_PER_MINUTE, + constants::mock::{ + BlockHashCount, ExistentialDeposit, ExistentialDeposits, GetNativeCurrencyId, MaxLocks, + MaxReserves, MinimumPeriod, }, types::{ AccountIdTest, Amount, Balance, BasicCurrencyAdapter, BlockNumber, CurrencyId, Hash, Moment, diff --git a/zrml/futarchy/src/mock/types/oracle_query.rs b/zrml/futarchy/src/mock/types/oracle_query.rs index 86666c536..6703b94de 100644 --- a/zrml/futarchy/src/mock/types/oracle_query.rs +++ b/zrml/futarchy/src/mock/types/oracle_query.rs @@ -4,7 +4,7 @@ use scale_info::TypeInfo; use alloc::fmt::Debug; use frame_support::pallet_prelude::Weight; -#[derive(Clone, Debug, Decode, Encode, MaxEncodedLen, PartialEq, TypeInfo)] +#[derive(Clone, Debug, Decode, Encode, Eq, MaxEncodedLen, PartialEq, TypeInfo)] pub struct MockOracleQuery { weight: Weight, value: bool, diff --git a/zrml/futarchy/src/mock/types/scheduler.rs b/zrml/futarchy/src/mock/types/scheduler.rs index 6391387e8..f9313e28b 100644 --- a/zrml/futarchy/src/mock/types/scheduler.rs +++ b/zrml/futarchy/src/mock/types/scheduler.rs @@ -3,7 +3,6 @@ use core::cell::RefCell; use frame_support::traits::schedule::{v3::Anon as ScheduleAnon, DispatchTime, Period, Priority}; use frame_system::{ pallet_prelude::{BlockNumberFor, OriginFor}, - Call, Origin, }; use sp_runtime::{traits::Bounded, DispatchError, DispatchResult}; diff --git a/zrml/futarchy/src/pallet_impls.rs b/zrml/futarchy/src/pallet_impls.rs index 605be8abe..12b7222dd 100644 --- a/zrml/futarchy/src/pallet_impls.rs +++ b/zrml/futarchy/src/pallet_impls.rs @@ -1,11 +1,11 @@ -use crate::{traits::OracleQuery, Config, Event, Pallet, ProposalOf}; +use crate::{traits::OracleQuery, Config, Event, Pallet, types::Proposal}; use frame_support::{dispatch::RawOrigin, pallet_prelude::Weight, traits::schedule::DispatchTime}; use frame_support::traits::schedule::v3::Anon; impl Pallet { /// Evaluates `proposal` using the specified oracle and schedules the contained call if the /// oracle approves. - pub(crate) fn maybe_schedule_proposal(proposal: ProposalOf) -> Weight { + pub(crate) fn maybe_schedule_proposal(proposal: Proposal) -> Weight { let (evaluate_weight, approved) = proposal.query.evaluate(); if approved { diff --git a/zrml/futarchy/src/tests/mod.rs b/zrml/futarchy/src/tests/mod.rs index d6dd1e1d4..69609ceb4 100644 --- a/zrml/futarchy/src/tests/mod.rs +++ b/zrml/futarchy/src/tests/mod.rs @@ -18,3 +18,31 @@ #![cfg(all(feature = "mock", test))] mod submit_proposal; + +use crate::{ + mock::{ + ext_builder::ExtBuilder, + runtime::{Futarchy, Runtime, RuntimeCall, RuntimeOrigin}, + types::MockOracleQuery, + }, + types::Proposal, + Config, +}; +use frame_support::{assert_noop, pallet_prelude::Weight}; +use sp_runtime::DispatchError; + +/// Utility struct for managing test accounts. +pub(crate) struct Account { + id: ::AccountId, +} + +impl Account { + // TODO Not a pressing issue, but double booking accounts should be illegal. + pub(crate) fn new(id: ::AccountId) -> Account { + Account { id } + } + + pub(crate) fn signed(&self) -> RuntimeOrigin { + RuntimeOrigin::signed(self.id) + } +} diff --git a/zrml/futarchy/src/tests/submit_proposal.rs b/zrml/futarchy/src/tests/submit_proposal.rs index c61e54b5d..d861e01e9 100644 --- a/zrml/futarchy/src/tests/submit_proposal.rs +++ b/zrml/futarchy/src/tests/submit_proposal.rs @@ -1,2 +1,39 @@ +use super::*; + #[test] -fn test() {} +fn submit_proposal_fails_on_bad_origin() { + ExtBuilder::build().execute_with(|| { + let alice = Account::new(0); + + let duration = ::MinDuration::get(); + + let call = RuntimeCall::System(SystemCall::remark { + msg: "hullo", + weight: Weight::from_parts(1, 2), + }); + let query = MockOracleQuery { weight: Default::default(), value: Default::default() }; + let proposal = Proposal { when: Default::default(), call, query }; + assert_noop!( + Futarchy::submit_proposal(alice.signed(), duration, proposal), + DispatchError::BadOrigin, + ); + }); +} + +// #[test] +// fn submit_proposal_fails_if_duration_is_too_short() { +// ExtBuilder::build().execute_with(|| { +// +// let duration = ::MinDuration::get() - 1; +// let query = MockQuery { weight: 1u128, value: false }; +// let proposal = Proposal { +// when: Default(), +// call: Default(), +// query, +// } +// assert_noop!( +// Futarchy::submit_proposal(duration, proposal), +// Er +// ) +// }); +// } diff --git a/zrml/futarchy/src/traits/oracle_query.rs b/zrml/futarchy/src/traits/oracle_query.rs index 699d9a5e7..e1a005758 100644 --- a/zrml/futarchy/src/traits/oracle_query.rs +++ b/zrml/futarchy/src/traits/oracle_query.rs @@ -4,7 +4,7 @@ use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; pub trait OracleQuery: - Clone + Debug + Decode + Encode + MaxEncodedLen + PartialEq + TypeInfo + Clone + Debug + Decode + Encode + Eq + MaxEncodedLen + PartialEq + TypeInfo { /// Evaluates the query at the current block and returns the weight consumed and a `bool` /// indicating whether the query evaluated positively. diff --git a/zrml/futarchy/src/types/proposal.rs b/zrml/futarchy/src/types/proposal.rs index 85de59099..9f6bc7f3f 100644 --- a/zrml/futarchy/src/types/proposal.rs +++ b/zrml/futarchy/src/types/proposal.rs @@ -1,16 +1,20 @@ +use crate::{BoundedCallOf, Config, OracleQueryOf}; +use alloc::fmt::Debug; +use frame_support::{CloneNoBound, PartialEqNoBound, RuntimeDebugNoBound}; +use frame_system::pallet_prelude::BlockNumberFor; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; -use alloc::fmt::Debug; // TODO Make config a generic, keeps things simple. -#[derive(Clone, Debug, Decode, Encode, MaxEncodedLen, PartialEq, TypeInfo)] -pub struct Proposal +#[derive( + CloneNoBound, Decode, Encode, Eq, MaxEncodedLen, PartialEqNoBound, RuntimeDebugNoBound, TypeInfo, +)] +#[scale_info(skip_type_params(S, T))] +pub struct Proposal where - When: Clone + Debug + Decode + Encode + MaxEncodedLen + PartialEq + TypeInfo, - BoundedCall: Clone + Debug + Decode + Encode + MaxEncodedLen + PartialEq + TypeInfo, - OracleQuery: Clone + Debug + Decode + Encode + MaxEncodedLen + PartialEq + TypeInfo, + T: Config, { - pub when: When, - pub call: BoundedCall, - pub query: OracleQuery, + pub when: BlockNumberFor, + pub call: BoundedCallOf, + pub query: OracleQueryOf, } From 17ab0e07c8e001eafd30747f520dda4ac9b4ab4b Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Sat, 19 Oct 2024 14:52:44 +0200 Subject: [PATCH 20/73] Implement and test futarchy --- zrml/futarchy/src/dispatchable_impls.rs | 6 +- zrml/futarchy/src/lib.rs | 7 - zrml/futarchy/src/mock/mod.rs | 1 + zrml/futarchy/src/mock/runtime.rs | 87 ++++-------- zrml/futarchy/src/mock/types/oracle_query.rs | 10 +- zrml/futarchy/src/mock/types/scheduler.rs | 14 +- zrml/futarchy/src/mock/utility.rs | 22 +++ zrml/futarchy/src/tests/mod.rs | 17 ++- zrml/futarchy/src/tests/submit_proposal.rs | 137 ++++++++++++++++--- 9 files changed, 197 insertions(+), 104 deletions(-) create mode 100644 zrml/futarchy/src/mock/utility.rs diff --git a/zrml/futarchy/src/dispatchable_impls.rs b/zrml/futarchy/src/dispatchable_impls.rs index 6e6972dc1..a9b863591 100644 --- a/zrml/futarchy/src/dispatchable_impls.rs +++ b/zrml/futarchy/src/dispatchable_impls.rs @@ -1,4 +1,4 @@ -use crate::{Config, Error, Pallet, types::Proposal, Proposals}; +use crate::{types::Proposal, Config, Error, Event, Pallet, Proposals}; use frame_support::{ensure, require_transactional, traits::Get}; use frame_system::pallet_prelude::BlockNumberFor; use sp_runtime::{DispatchResult, Saturating}; @@ -15,9 +15,11 @@ impl Pallet { let to_be_scheduled_at = now.saturating_add(duration); let try_mutate_result = Proposals::::try_mutate(to_be_scheduled_at, |proposals| { - proposals.try_push(proposal).map_err(|_| Error::::CacheFull) + proposals.try_push(proposal.clone()).map_err(|_| Error::::CacheFull) }); + Self::deposit_event(Event::::Submitted { duration, proposal }); + Ok(try_mutate_result?) } } diff --git a/zrml/futarchy/src/lib.rs b/zrml/futarchy/src/lib.rs index bb789ff29..0bacb3fe9 100644 --- a/zrml/futarchy/src/lib.rs +++ b/zrml/futarchy/src/lib.rs @@ -49,15 +49,10 @@ mod pallet { #[pallet::config] pub trait Config: frame_system::Config { - type MultiCurrency: MultiCurrency; // TODO Do we need this? - type MinDuration: Get>; type OracleQuery: OracleQuery; - /// Preimage interface for acquiring call data. - type Preimages: QueryPreimage + StorePreimage; // TODO Why do we even need this? - type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// Scheduler interface for executing proposals. @@ -72,8 +67,6 @@ mod pallet { pub struct Pallet(PhantomData); pub(crate) type AccountIdOf = ::AccountId; - pub(crate) type BalanceOf = - <::MultiCurrency as MultiCurrency>>::Balance; pub(crate) type CacheSize = ConstU32<16>; pub(crate) type CallOf = ::RuntimeCall; pub(crate) type BoundedCallOf = Bounded>; diff --git a/zrml/futarchy/src/mock/mod.rs b/zrml/futarchy/src/mock/mod.rs index e0345b76b..3d4de37fb 100644 --- a/zrml/futarchy/src/mock/mod.rs +++ b/zrml/futarchy/src/mock/mod.rs @@ -20,3 +20,4 @@ pub mod ext_builder; pub(crate) mod runtime; pub(crate) mod types; +pub(crate) mod utility; diff --git a/zrml/futarchy/src/mock/runtime.rs b/zrml/futarchy/src/mock/runtime.rs index 0591bb305..41892cc6a 100644 --- a/zrml/futarchy/src/mock/runtime.rs +++ b/zrml/futarchy/src/mock/runtime.rs @@ -41,58 +41,13 @@ parameter_types! { construct_runtime! { pub enum Runtime { - Futarchy: zrml_futarchy, + System: frame_system, Balances: pallet_balances, - Currencies: orml_currencies, Preimage: pallet_preimage, - System: frame_system, - Timestamp: pallet_timestamp, - Tokens: orml_tokens, + Futarchy: zrml_futarchy, } } -impl zrml_futarchy::Config for Runtime { - type MultiCurrency = Currencies; - type MinDuration = MinDuration; - type OracleQuery = MockOracleQuery; - type Preimages = Preimage; - type RuntimeEvent = RuntimeEvent; - type Scheduler = MockScheduler; - type SubmitOrigin = EnsureRoot<::AccountId>; -} - -impl orml_currencies::Config for Runtime { - type GetNativeCurrencyId = GetNativeCurrencyId; - type MultiCurrency = Tokens; - type NativeCurrency = BasicCurrencyAdapter; - type WeightInfo = (); -} - -impl pallet_balances::Config for Runtime { - type AccountStore = System; - type Balance = Balance; - type DustRemoval = (); - type FreezeIdentifier = (); - type RuntimeHoldReason = (); - type RuntimeEvent = RuntimeEvent; - type ExistentialDeposit = ExistentialDeposit; - type MaxHolds = (); - type MaxFreezes = (); - type MaxLocks = MaxLocks; - type MaxReserves = MaxReserves; - type ReserveIdentifier = [u8; 8]; - type WeightInfo = (); -} - -impl pallet_preimage::Config for Runtime { - type WeightInfo = (); - type RuntimeEvent = RuntimeEvent; - type Currency = Balances; - type ManagerOrigin = EnsureRoot<::AccountId>; - type BaseDeposit = PreimageBaseDeposit; - type ByteDeposit = PreimageByteDeposit; -} - impl frame_system::Config for Runtime { type AccountData = pallet_balances::AccountData; type AccountId = AccountIdTest; @@ -119,23 +74,35 @@ impl frame_system::Config for Runtime { type OnSetCode = (); } -impl pallet_timestamp::Config for Runtime { - type MinimumPeriod = MinimumPeriod; - type Moment = Moment; - type OnTimestampSet = (); - type WeightInfo = (); -} - -impl orml_tokens::Config for Runtime { - type Amount = Amount; +impl pallet_balances::Config for Runtime { + type AccountStore = System; type Balance = Balance; - type CurrencyId = CurrencyId; - type DustRemovalWhitelist = Everything; + type DustRemoval = (); + type FreezeIdentifier = (); + type RuntimeHoldReason = (); type RuntimeEvent = RuntimeEvent; - type ExistentialDeposits = ExistentialDeposits; + type ExistentialDeposit = ExistentialDeposit; + type MaxHolds = (); + type MaxFreezes = (); type MaxLocks = MaxLocks; type MaxReserves = MaxReserves; - type CurrencyHooks = (); type ReserveIdentifier = [u8; 8]; type WeightInfo = (); } + +impl pallet_preimage::Config for Runtime { + type WeightInfo = (); + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type ManagerOrigin = EnsureRoot<::AccountId>; + type BaseDeposit = PreimageBaseDeposit; + type ByteDeposit = PreimageByteDeposit; +} + +impl zrml_futarchy::Config for Runtime { + type MinDuration = MinDuration; + type OracleQuery = MockOracleQuery; + type RuntimeEvent = RuntimeEvent; + type Scheduler = MockScheduler; + type SubmitOrigin = EnsureRoot<::AccountId>; +} diff --git a/zrml/futarchy/src/mock/types/oracle_query.rs b/zrml/futarchy/src/mock/types/oracle_query.rs index 6703b94de..504e380e2 100644 --- a/zrml/futarchy/src/mock/types/oracle_query.rs +++ b/zrml/futarchy/src/mock/types/oracle_query.rs @@ -1,8 +1,8 @@ use crate::traits::OracleQuery; -use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; -use scale_info::TypeInfo; use alloc::fmt::Debug; use frame_support::pallet_prelude::Weight; +use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; #[derive(Clone, Debug, Decode, Encode, Eq, MaxEncodedLen, PartialEq, TypeInfo)] pub struct MockOracleQuery { @@ -10,6 +10,12 @@ pub struct MockOracleQuery { value: bool, } +impl MockOracleQuery { + pub fn new(weight: Weight, value: bool) -> Self { + Self { weight, value } + } +} + impl OracleQuery for MockOracleQuery { fn evaluate(&self) -> (Weight, bool) { (self.weight, self.value) diff --git a/zrml/futarchy/src/mock/types/scheduler.rs b/zrml/futarchy/src/mock/types/scheduler.rs index f9313e28b..8eb52be68 100644 --- a/zrml/futarchy/src/mock/types/scheduler.rs +++ b/zrml/futarchy/src/mock/types/scheduler.rs @@ -1,23 +1,25 @@ use crate::{mock::runtime::Runtime, BoundedCallOf, CallOf}; use core::cell::RefCell; use frame_support::traits::schedule::{v3::Anon as ScheduleAnon, DispatchTime, Period, Priority}; -use frame_system::{ - pallet_prelude::{BlockNumberFor, OriginFor}, -}; +use frame_system::pallet_prelude::{BlockNumberFor, OriginFor}; use sp_runtime::{traits::Bounded, DispatchError, DispatchResult}; pub struct MockScheduler; impl MockScheduler { - fn set_return_value(value: DispatchResult) { + pub(crate) fn set_return_value(value: DispatchResult) { SCHEDULER_RETURN_VALUE.with(|v| *v.borrow_mut() = Some(value)); } - fn called_once_with( + pub(crate) fn not_called() -> bool { + SCHEDULER_CALL_DATA.with(|values| values.borrow().is_empty()) + } + + pub(crate) fn called_once_with( when: DispatchTime>, call: BoundedCallOf, ) -> bool { - if SCHEDULER_CALL_DATA.with(|value| value.borrow().len()) != 1 { + if SCHEDULER_CALL_DATA.with(|values| values.borrow().len()) != 1 { return false; } diff --git a/zrml/futarchy/src/mock/utility.rs b/zrml/futarchy/src/mock/utility.rs new file mode 100644 index 000000000..1a6d39bc4 --- /dev/null +++ b/zrml/futarchy/src/mock/utility.rs @@ -0,0 +1,22 @@ +use crate::mock::runtime::{Balances, Futarchy, Preimage, System}; +use frame_support::traits::Hooks; +use zeitgeist_primitives::types::BlockNumber; + +pub fn run_to_block(to: BlockNumber) { + while System::block_number() < to { + let now = System::block_number(); + + Futarchy::on_finalize(now); + Preimage::on_finalize(now); + Balances::on_finalize(now); + System::on_finalize(now); + + let next = now + 1; + System::set_block_number(next); + + System::on_initialize(next); + Balances::on_initialize(next); + Preimage::on_initialize(next); + Futarchy::on_initialize(next); + } +} diff --git a/zrml/futarchy/src/tests/mod.rs b/zrml/futarchy/src/tests/mod.rs index 69609ceb4..7d2687f36 100644 --- a/zrml/futarchy/src/tests/mod.rs +++ b/zrml/futarchy/src/tests/mod.rs @@ -22,14 +22,21 @@ mod submit_proposal; use crate::{ mock::{ ext_builder::ExtBuilder, - runtime::{Futarchy, Runtime, RuntimeCall, RuntimeOrigin}, - types::MockOracleQuery, + runtime::{Futarchy, Preimage, Runtime, RuntimeCall, RuntimeOrigin, System}, + types::{MockOracleQuery, MockScheduler}, + utility, }, types::Proposal, - Config, + CacheSize, CallOf, Config, Error, Event, Proposals, }; -use frame_support::{assert_noop, pallet_prelude::Weight}; -use sp_runtime::DispatchError; +use frame_support::{ + assert_noop, assert_ok, + dispatch::RawOrigin, + pallet_prelude::Weight, + traits::{schedule::DispatchTime, StorePreimage}, +}; +use frame_system::Call as SystemCall; +use sp_runtime::{traits::Get, BoundedVec, DispatchError}; /// Utility struct for managing test accounts. pub(crate) struct Account { diff --git a/zrml/futarchy/src/tests/submit_proposal.rs b/zrml/futarchy/src/tests/submit_proposal.rs index d861e01e9..d98bd5fdc 100644 --- a/zrml/futarchy/src/tests/submit_proposal.rs +++ b/zrml/futarchy/src/tests/submit_proposal.rs @@ -1,5 +1,75 @@ use super::*; +#[test] +fn submit_proposal_rejects_proposals() { + ExtBuilder::build().execute_with(|| { + let duration = ::MinDuration::get(); + + let remark = SystemCall::remark { remark: "hullo".into() }; + let call = Preimage::bound(CallOf::::from(remark)).unwrap(); + let query = MockOracleQuery::new(Default::default(), false); + let proposal = Proposal { when: Default::default(), call, query }; + + // This ensures that if the scheduler is erroneously called, the test doesn't fail due to a + // failure to configure the return value. + MockScheduler::set_return_value(Ok(())); + + assert_ok!(Futarchy::submit_proposal(RawOrigin::Root.into(), duration, proposal.clone())); + + System::assert_last_event( + Event::::Submitted { duration, proposal: proposal.clone() }.into(), + ); + + // Check that vector now contains proposal. + let now = System::block_number(); + let to_be_scheduled_at = now + duration; + assert_eq!(Proposals::get(to_be_scheduled_at).pop(), Some(proposal)); + + utility::run_to_block(to_be_scheduled_at); + + // The proposal has now been removed and failed. + assert!(Proposals::::get(to_be_scheduled_at).is_empty()); + assert!(MockScheduler::not_called()); + + System::assert_last_event(Event::::Rejected.into()); + }); +} + +#[test] +fn submit_proposal_schedules_proposals() { + ExtBuilder::build().execute_with(|| { + let duration = ::MinDuration::get(); + + let remark = SystemCall::remark { remark: "hullo".into() }; + let call = Preimage::bound(CallOf::::from(remark)).unwrap(); + let query = MockOracleQuery::new(Default::default(), true); + let proposal = Proposal { when: Default::default(), call, query }; + + // This ensures that if the scheduler is erroneously called, the test doesn't fail due to a + // failure to configure the return value. + MockScheduler::set_return_value(Ok(())); + + assert_ok!(Futarchy::submit_proposal(RawOrigin::Root.into(), duration, proposal.clone())); + + System::assert_last_event( + Event::::Submitted { duration, proposal: proposal.clone() }.into(), + ); + + // Check that vector now contains proposal. + let now = System::block_number(); + let to_be_scheduled_at = now + duration; + assert_eq!(Proposals::get(to_be_scheduled_at).pop(), Some(proposal.clone())); + + utility::run_to_block(to_be_scheduled_at); + + // The proposal has now been removed and failed. + assert!(Proposals::::get(to_be_scheduled_at).is_empty()); + assert!(MockScheduler::called_once_with(DispatchTime::At(proposal.when), proposal.call)); + + System::assert_last_event(Event::::Scheduled.into()); + }); +} + #[test] fn submit_proposal_fails_on_bad_origin() { ExtBuilder::build().execute_with(|| { @@ -7,12 +77,11 @@ fn submit_proposal_fails_on_bad_origin() { let duration = ::MinDuration::get(); - let call = RuntimeCall::System(SystemCall::remark { - msg: "hullo", - weight: Weight::from_parts(1, 2), - }); - let query = MockOracleQuery { weight: Default::default(), value: Default::default() }; + let remark = SystemCall::remark { remark: "hullo".into() }; + let call = Preimage::bound(CallOf::::from(remark)).unwrap(); + let query = MockOracleQuery::new(Default::default(), Default::default()); let proposal = Proposal { when: Default::default(), call, query }; + assert_noop!( Futarchy::submit_proposal(alice.signed(), duration, proposal), DispatchError::BadOrigin, @@ -20,20 +89,44 @@ fn submit_proposal_fails_on_bad_origin() { }); } -// #[test] -// fn submit_proposal_fails_if_duration_is_too_short() { -// ExtBuilder::build().execute_with(|| { -// -// let duration = ::MinDuration::get() - 1; -// let query = MockQuery { weight: 1u128, value: false }; -// let proposal = Proposal { -// when: Default(), -// call: Default(), -// query, -// } -// assert_noop!( -// Futarchy::submit_proposal(duration, proposal), -// Er -// ) -// }); -// } +#[test] +fn submit_proposal_fails_if_duration_is_too_short() { + ExtBuilder::build().execute_with(|| { + let duration = ::MinDuration::get() - 1; + + let remark = SystemCall::remark { remark: "hullo".into() }; + let call = Preimage::bound(CallOf::::from(remark)).unwrap(); + let query = MockOracleQuery::new(Default::default(), Default::default()); + let proposal = Proposal { when: Default::default(), call, query }; + + assert_noop!( + Futarchy::submit_proposal(RawOrigin::Root.into(), duration, proposal), + Error::::DurationTooShort + ); + }); +} + +#[test] +fn submit_proposal_fails_if_cache_is_full() { + ExtBuilder::build().execute_with(|| { + let duration = ::MinDuration::get(); + + let remark = SystemCall::remark { remark: "hullo".into() }; + let call = Preimage::bound(CallOf::::from(remark)).unwrap(); + let query = MockOracleQuery::new(Default::default(), Default::default()); + let proposal = Proposal { when: Default::default(), call, query }; + + // Mock up a full vector of proposals. + let now = System::block_number(); + let to_be_scheduled_at = now + duration; + let cache_size: u32 = >>::get().unwrap(); + let proposals_vec = vec![proposal.clone(); cache_size as usize]; + let proposals: BoundedVec<_, CacheSize> = proposals_vec.try_into().unwrap(); + Proposals::::insert(to_be_scheduled_at, proposals); + + assert_noop!( + Futarchy::submit_proposal(RawOrigin::Root.into(), duration, proposal), + Error::::CacheFull + ); + }); +} From f4d27796dfcf546be8bc27dfe5ef769b32dcc6c1 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Sat, 19 Oct 2024 15:15:47 +0200 Subject: [PATCH 21/73] Implement Events --- zrml/futarchy/src/lib.rs | 4 ++-- zrml/futarchy/src/pallet_impls.rs | 8 ++++---- zrml/futarchy/src/tests/submit_proposal.rs | 21 ++++++++++++--------- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/zrml/futarchy/src/lib.rs b/zrml/futarchy/src/lib.rs index 0bacb3fe9..19e7d5921 100644 --- a/zrml/futarchy/src/lib.rs +++ b/zrml/futarchy/src/lib.rs @@ -93,10 +93,10 @@ mod pallet { Submitted { duration: BlockNumberFor, proposal: Proposal }, /// A proposal has been rejected by the oracle. - Rejected, + Rejected { proposal: Proposal }, /// A proposal has been scheduled for execution. - Scheduled, + Scheduled { proposal: Proposal }, /// This is a logic error. You shouldn't see this. UnexpectedSchedulerError, diff --git a/zrml/futarchy/src/pallet_impls.rs b/zrml/futarchy/src/pallet_impls.rs index 12b7222dd..affc75daa 100644 --- a/zrml/futarchy/src/pallet_impls.rs +++ b/zrml/futarchy/src/pallet_impls.rs @@ -10,22 +10,22 @@ impl Pallet { if approved { let result = T::Scheduler::schedule( - DispatchTime::At(proposal.when), + DispatchTime::At(proposal.when.clone()), None, 63, RawOrigin::Root.into(), - proposal.call, + proposal.call.clone(), ); if result.is_ok() { - Self::deposit_event(Event::::Scheduled); + Self::deposit_event(Event::::Scheduled { proposal }); } else { Self::deposit_event(Event::::UnexpectedSchedulerError); } evaluate_weight // TODO Add benchmark! } else { - Self::deposit_event(Event::::Rejected); + Self::deposit_event(Event::::Rejected { proposal }); evaluate_weight } diff --git a/zrml/futarchy/src/tests/submit_proposal.rs b/zrml/futarchy/src/tests/submit_proposal.rs index d98bd5fdc..651dde7b2 100644 --- a/zrml/futarchy/src/tests/submit_proposal.rs +++ b/zrml/futarchy/src/tests/submit_proposal.rs @@ -1,13 +1,13 @@ use super::*; #[test] -fn submit_proposal_rejects_proposals() { +fn submit_proposal_schedules_proposals() { ExtBuilder::build().execute_with(|| { let duration = ::MinDuration::get(); let remark = SystemCall::remark { remark: "hullo".into() }; let call = Preimage::bound(CallOf::::from(remark)).unwrap(); - let query = MockOracleQuery::new(Default::default(), false); + let query = MockOracleQuery::new(Default::default(), true); let proposal = Proposal { when: Default::default(), call, query }; // This ensures that if the scheduler is erroneously called, the test doesn't fail due to a @@ -23,26 +23,29 @@ fn submit_proposal_rejects_proposals() { // Check that vector now contains proposal. let now = System::block_number(); let to_be_scheduled_at = now + duration; - assert_eq!(Proposals::get(to_be_scheduled_at).pop(), Some(proposal)); + assert_eq!(Proposals::get(to_be_scheduled_at).pop(), Some(proposal.clone())); utility::run_to_block(to_be_scheduled_at); // The proposal has now been removed and failed. assert!(Proposals::::get(to_be_scheduled_at).is_empty()); - assert!(MockScheduler::not_called()); + assert!(MockScheduler::called_once_with( + DispatchTime::At(proposal.when.clone()), + proposal.call.clone() + )); - System::assert_last_event(Event::::Rejected.into()); + System::assert_last_event(Event::::Scheduled { proposal }.into()); }); } #[test] -fn submit_proposal_schedules_proposals() { +fn submit_proposal_rejects_proposals() { ExtBuilder::build().execute_with(|| { let duration = ::MinDuration::get(); let remark = SystemCall::remark { remark: "hullo".into() }; let call = Preimage::bound(CallOf::::from(remark)).unwrap(); - let query = MockOracleQuery::new(Default::default(), true); + let query = MockOracleQuery::new(Default::default(), false); let proposal = Proposal { when: Default::default(), call, query }; // This ensures that if the scheduler is erroneously called, the test doesn't fail due to a @@ -64,9 +67,9 @@ fn submit_proposal_schedules_proposals() { // The proposal has now been removed and failed. assert!(Proposals::::get(to_be_scheduled_at).is_empty()); - assert!(MockScheduler::called_once_with(DispatchTime::At(proposal.when), proposal.call)); + assert!(MockScheduler::not_called()); - System::assert_last_event(Event::::Scheduled.into()); + System::assert_last_event(Event::::Rejected { proposal }.into()); }); } From fcb74fae0b92f03641951ce07d3ac29b3878fd21 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Sat, 19 Oct 2024 22:38:58 +0200 Subject: [PATCH 22/73] Fix futarchy errors (#1380) * Fix clippy errors * Move `FutarchyOracle` to primitives * Implement `FutarchyOracle` for neo-swaps --- primitives/src/traits.rs | 2 + .../src/traits/futarchy_oracle.rs | 7 +-- runtime/battery-station/src/parameters.rs | 3 ++ runtime/common/src/lib.rs | 5 +- zrml/futarchy/Cargo.toml | 2 +- zrml/futarchy/src/lib.rs | 29 +++++++---- zrml/futarchy/src/mock/mod.rs | 2 +- zrml/futarchy/src/mock/runtime.rs | 10 ++-- zrml/futarchy/src/mock/types/mod.rs | 8 +-- .../mock/types/{oracle_query.rs => oracle.rs} | 8 +-- zrml/futarchy/src/mock/types/scheduler.rs | 18 ++++--- zrml/futarchy/src/pallet_impls.rs | 7 +-- zrml/futarchy/src/tests/mod.rs | 5 +- zrml/futarchy/src/tests/submit_proposal.rs | 22 ++++---- zrml/futarchy/src/traits/mod.rs | 4 -- zrml/futarchy/src/types/proposal.rs | 5 +- .../src/types/decision_market_oracle.rs | 50 +++++++++++++++++++ zrml/neo-swaps/src/types/mod.rs | 2 + 18 files changed, 124 insertions(+), 65 deletions(-) rename zrml/futarchy/src/traits/oracle_query.rs => primitives/src/traits/futarchy_oracle.rs (53%) rename zrml/futarchy/src/mock/types/{oracle_query.rs => oracle.rs} (77%) create mode 100644 zrml/neo-swaps/src/types/decision_market_oracle.rs diff --git a/primitives/src/traits.rs b/primitives/src/traits.rs index 1298baa68..9d69f2c15 100644 --- a/primitives/src/traits.rs +++ b/primitives/src/traits.rs @@ -20,6 +20,7 @@ mod complete_set_operations_api; mod deploy_pool_api; mod dispute_api; mod distribute_fees; +mod futarchy_oracle; mod hybrid_router_amm_api; mod hybrid_router_orderbook_api; mod market_builder; @@ -32,6 +33,7 @@ pub use complete_set_operations_api::*; pub use deploy_pool_api::*; pub use dispute_api::*; pub use distribute_fees::*; +pub use futarchy_oracle::*; pub use hybrid_router_amm_api::*; pub use hybrid_router_orderbook_api::*; pub use market_builder::*; diff --git a/zrml/futarchy/src/traits/oracle_query.rs b/primitives/src/traits/futarchy_oracle.rs similarity index 53% rename from zrml/futarchy/src/traits/oracle_query.rs rename to primitives/src/traits/futarchy_oracle.rs index e1a005758..7e6e1077d 100644 --- a/zrml/futarchy/src/traits/oracle_query.rs +++ b/primitives/src/traits/futarchy_oracle.rs @@ -1,11 +1,6 @@ -use alloc::fmt::Debug; use frame_support::pallet_prelude::Weight; -use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; -use scale_info::TypeInfo; -pub trait OracleQuery: - Clone + Debug + Decode + Encode + Eq + MaxEncodedLen + PartialEq + TypeInfo -{ +pub trait FutarchyOracle { /// Evaluates the query at the current block and returns the weight consumed and a `bool` /// indicating whether the query evaluated positively. fn evaluate(&self) -> (Weight, bool); diff --git a/runtime/battery-station/src/parameters.rs b/runtime/battery-station/src/parameters.rs index 92ad20e27..214410ec6 100644 --- a/runtime/battery-station/src/parameters.rs +++ b/runtime/battery-station/src/parameters.rs @@ -167,6 +167,9 @@ parameter_types! { /// The maximum number of public proposals that can exist at any time. pub const MaxProposals: u32 = 100; + // Futarchy + pub const MinDuration: BlockNumber = 7 * BLOCKS_PER_DAY; + // Hybrid Router parameters pub const HybridRouterPalletId: PalletId = HYBRID_ROUTER_PALLET_ID; /// Maximum number of orders that can be placed in a single trade transaction. diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs index cf215a6d4..a7e435894 100644 --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -87,6 +87,7 @@ macro_rules! decl_common_types { }; use zeitgeist_primitives::traits::{DeployPoolApi, DistributeFees, MarketCommonsPalletApi}; use zrml_combinatorial_tokens::types::CryptographicIdManager; + use zrml_neo_swaps::types::DecisionMarketOracle; pub type Block = generic::Block; @@ -1207,10 +1208,8 @@ macro_rules! impl_config_traits { } impl zrml_futarchy::Config for Runtime { - type MultiCurrency = Currencies; type MinDuration = MinDuration; - type OracleQuery = MockOracleQuery; - type Preimages = Preimage; + type Oracle = DecisionMarketOracle; type RuntimeEvent = RuntimeEvent; type Scheduler = Scheduler; type SubmitOrigin = EnsureRoot; diff --git a/zrml/futarchy/Cargo.toml b/zrml/futarchy/Cargo.toml index 8d5d7f053..c13da8548 100644 --- a/zrml/futarchy/Cargo.toml +++ b/zrml/futarchy/Cargo.toml @@ -14,7 +14,7 @@ env_logger = { workspace = true, optional = true } orml-currencies = { workspace = true, optional = true } orml-tokens = { workspace = true, optional = true } pallet-balances = { workspace = true, optional = true } -pallet-preimage = { workspace = true } +pallet-preimage = { workspace = true, optional = true } pallet-scheduler = { workspace = true, optional = true } pallet-timestamp = { workspace = true, optional = true } sp-io = { workspace = true, optional = true } diff --git a/zrml/futarchy/src/lib.rs b/zrml/futarchy/src/lib.rs index 19e7d5921..3bf0e9798 100644 --- a/zrml/futarchy/src/lib.rs +++ b/zrml/futarchy/src/lib.rs @@ -31,32 +31,42 @@ pub use pallet::*; #[frame_support::pallet] mod pallet { - use crate::{traits::OracleQuery, types::Proposal}; + use crate::types::Proposal; + use alloc::fmt::Debug; use core::marker::PhantomData; use frame_support::{ pallet_prelude::{EnsureOrigin, IsType, StorageMap, StorageVersion, ValueQuery, Weight}, - traits::{ - schedule::v3::Anon as ScheduleAnon, Bounded, Hooks, QueryPreimage, StorePreimage, - }, + traits::{schedule::v3::Anon as ScheduleAnon, Bounded, Hooks, OriginTrait}, transactional, Blake2_128Concat, BoundedVec, }; use frame_system::pallet_prelude::{BlockNumberFor, OriginFor}; - use orml_traits::MultiCurrency; + use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; + use scale_info::TypeInfo; use sp_runtime::{ traits::{ConstU32, Get}, DispatchResult, }; + use zeitgeist_primitives::traits::FutarchyOracle; #[pallet::config] pub trait Config: frame_system::Config { type MinDuration: Get>; - type OracleQuery: OracleQuery; + // The type used to define the oracle for each proposal. + type Oracle: FutarchyOracle + + Clone + + Debug + + Decode + + Encode + + Eq + + MaxEncodedLen + + PartialEq + + TypeInfo; type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// Scheduler interface for executing proposals. - type Scheduler: ScheduleAnon, CallOf, OriginFor>; + type Scheduler: ScheduleAnon, CallOf, PalletsOriginOf>; /// The origin that is allowed to submit proposals. type SubmitOrigin: EnsureOrigin; @@ -66,11 +76,12 @@ mod pallet { #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(PhantomData); - pub(crate) type AccountIdOf = ::AccountId; pub(crate) type CacheSize = ConstU32<16>; pub(crate) type CallOf = ::RuntimeCall; pub(crate) type BoundedCallOf = Bounded>; - pub(crate) type OracleQueryOf = ::OracleQuery; + pub(crate) type OracleOf = ::Oracle; + pub(crate) type PalletsOriginOf = + <::RuntimeOrigin as OriginTrait>::PalletsOrigin; pub(crate) const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); diff --git a/zrml/futarchy/src/mock/mod.rs b/zrml/futarchy/src/mock/mod.rs index 3d4de37fb..546789d7e 100644 --- a/zrml/futarchy/src/mock/mod.rs +++ b/zrml/futarchy/src/mock/mod.rs @@ -20,4 +20,4 @@ pub mod ext_builder; pub(crate) mod runtime; pub(crate) mod types; -pub(crate) mod utility; +pub mod utility; diff --git a/zrml/futarchy/src/mock/runtime.rs b/zrml/futarchy/src/mock/runtime.rs index 41892cc6a..a775c4288 100644 --- a/zrml/futarchy/src/mock/runtime.rs +++ b/zrml/futarchy/src/mock/runtime.rs @@ -16,17 +16,17 @@ // along with Zeitgeist. If not, see . use crate as zrml_futarchy; -use crate::mock::types::{MockOracleQuery, MockScheduler}; +use crate::mock::types::{MockOracle, MockScheduler}; use frame_support::{construct_runtime, parameter_types, traits::Everything}; use frame_system::{mocking::MockBlock, EnsureRoot}; use sp_runtime::traits::{BlakeTwo256, ConstU32, IdentityLookup}; use zeitgeist_primitives::{ constants::mock::{ - BlockHashCount, ExistentialDeposit, ExistentialDeposits, GetNativeCurrencyId, MaxLocks, - MaxReserves, MinimumPeriod, + BlockHashCount, ExistentialDeposit, MaxLocks, + MaxReserves, }, types::{ - AccountIdTest, Amount, Balance, BasicCurrencyAdapter, BlockNumber, CurrencyId, Hash, Moment, + AccountIdTest, Balance, BlockNumber, Hash, }, }; @@ -101,7 +101,7 @@ impl pallet_preimage::Config for Runtime { impl zrml_futarchy::Config for Runtime { type MinDuration = MinDuration; - type OracleQuery = MockOracleQuery; + type Oracle = MockOracle; type RuntimeEvent = RuntimeEvent; type Scheduler = MockScheduler; type SubmitOrigin = EnsureRoot<::AccountId>; diff --git a/zrml/futarchy/src/mock/types/mod.rs b/zrml/futarchy/src/mock/types/mod.rs index a41cb9bfb..e56d20c4c 100644 --- a/zrml/futarchy/src/mock/types/mod.rs +++ b/zrml/futarchy/src/mock/types/mod.rs @@ -1,5 +1,5 @@ -pub mod oracle_query; -pub mod scheduler; +mod oracle; +mod scheduler; -pub use oracle_query::MockOracleQuery; -pub use scheduler::MockScheduler; +pub(crate) use oracle::MockOracle; +pub(crate) use scheduler::MockScheduler; diff --git a/zrml/futarchy/src/mock/types/oracle_query.rs b/zrml/futarchy/src/mock/types/oracle.rs similarity index 77% rename from zrml/futarchy/src/mock/types/oracle_query.rs rename to zrml/futarchy/src/mock/types/oracle.rs index 504e380e2..779a633e6 100644 --- a/zrml/futarchy/src/mock/types/oracle_query.rs +++ b/zrml/futarchy/src/mock/types/oracle.rs @@ -1,22 +1,22 @@ -use crate::traits::OracleQuery; use alloc::fmt::Debug; use frame_support::pallet_prelude::Weight; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; +use zeitgeist_primitives::traits::FutarchyOracle; #[derive(Clone, Debug, Decode, Encode, Eq, MaxEncodedLen, PartialEq, TypeInfo)] -pub struct MockOracleQuery { +pub struct MockOracle { weight: Weight, value: bool, } -impl MockOracleQuery { +impl MockOracle { pub fn new(weight: Weight, value: bool) -> Self { Self { weight, value } } } -impl OracleQuery for MockOracleQuery { +impl FutarchyOracle for MockOracle { fn evaluate(&self) -> (Weight, bool) { (self.weight, self.value) } diff --git a/zrml/futarchy/src/mock/types/scheduler.rs b/zrml/futarchy/src/mock/types/scheduler.rs index 8eb52be68..ed2ed2d00 100644 --- a/zrml/futarchy/src/mock/types/scheduler.rs +++ b/zrml/futarchy/src/mock/types/scheduler.rs @@ -1,21 +1,21 @@ -use crate::{mock::runtime::Runtime, BoundedCallOf, CallOf}; +use crate::{mock::runtime::Runtime, BoundedCallOf, CallOf, PalletsOriginOf}; use core::cell::RefCell; use frame_support::traits::schedule::{v3::Anon as ScheduleAnon, DispatchTime, Period, Priority}; -use frame_system::pallet_prelude::{BlockNumberFor, OriginFor}; -use sp_runtime::{traits::Bounded, DispatchError, DispatchResult}; +use frame_system::pallet_prelude::BlockNumberFor; +use sp_runtime::{DispatchError, DispatchResult}; pub struct MockScheduler; impl MockScheduler { - pub(crate) fn set_return_value(value: DispatchResult) { + pub fn set_return_value(value: DispatchResult) { SCHEDULER_RETURN_VALUE.with(|v| *v.borrow_mut() = Some(value)); } - pub(crate) fn not_called() -> bool { + pub fn not_called() -> bool { SCHEDULER_CALL_DATA.with(|values| values.borrow().is_empty()) } - pub(crate) fn called_once_with( + pub fn called_once_with( when: DispatchTime>, call: BoundedCallOf, ) -> bool { @@ -36,14 +36,16 @@ struct SchedulerCallData { call: BoundedCallOf, } -impl ScheduleAnon, CallOf, OriginFor> for MockScheduler { +impl ScheduleAnon, CallOf, PalletsOriginOf> + for MockScheduler +{ type Address = (); fn schedule( when: DispatchTime>, _maybe_periodic: Option>>, _priority: Priority, - _origin: OriginFor, + _origin: PalletsOriginOf, call: BoundedCallOf, ) -> Result { SCHEDULER_CALL_DATA diff --git a/zrml/futarchy/src/pallet_impls.rs b/zrml/futarchy/src/pallet_impls.rs index affc75daa..1da63e1db 100644 --- a/zrml/futarchy/src/pallet_impls.rs +++ b/zrml/futarchy/src/pallet_impls.rs @@ -1,4 +1,5 @@ -use crate::{traits::OracleQuery, Config, Event, Pallet, types::Proposal}; +use crate::{Config, Event, Pallet, types::Proposal}; +use zeitgeist_primitives::traits::FutarchyOracle; use frame_support::{dispatch::RawOrigin, pallet_prelude::Weight, traits::schedule::DispatchTime}; use frame_support::traits::schedule::v3::Anon; @@ -6,11 +7,11 @@ impl Pallet { /// Evaluates `proposal` using the specified oracle and schedules the contained call if the /// oracle approves. pub(crate) fn maybe_schedule_proposal(proposal: Proposal) -> Weight { - let (evaluate_weight, approved) = proposal.query.evaluate(); + let (evaluate_weight, approved) = proposal.oracle.evaluate(); if approved { let result = T::Scheduler::schedule( - DispatchTime::At(proposal.when.clone()), + DispatchTime::At(proposal.when), None, 63, RawOrigin::Root.into(), diff --git a/zrml/futarchy/src/tests/mod.rs b/zrml/futarchy/src/tests/mod.rs index 7d2687f36..8e9d6f048 100644 --- a/zrml/futarchy/src/tests/mod.rs +++ b/zrml/futarchy/src/tests/mod.rs @@ -22,8 +22,8 @@ mod submit_proposal; use crate::{ mock::{ ext_builder::ExtBuilder, - runtime::{Futarchy, Preimage, Runtime, RuntimeCall, RuntimeOrigin, System}, - types::{MockOracleQuery, MockScheduler}, + runtime::{Futarchy, Preimage, Runtime, RuntimeOrigin, System}, + types::{MockOracle, MockScheduler}, utility, }, types::Proposal, @@ -32,7 +32,6 @@ use crate::{ use frame_support::{ assert_noop, assert_ok, dispatch::RawOrigin, - pallet_prelude::Weight, traits::{schedule::DispatchTime, StorePreimage}, }; use frame_system::Call as SystemCall; diff --git a/zrml/futarchy/src/tests/submit_proposal.rs b/zrml/futarchy/src/tests/submit_proposal.rs index 651dde7b2..2f7d31dbf 100644 --- a/zrml/futarchy/src/tests/submit_proposal.rs +++ b/zrml/futarchy/src/tests/submit_proposal.rs @@ -7,8 +7,8 @@ fn submit_proposal_schedules_proposals() { let remark = SystemCall::remark { remark: "hullo".into() }; let call = Preimage::bound(CallOf::::from(remark)).unwrap(); - let query = MockOracleQuery::new(Default::default(), true); - let proposal = Proposal { when: Default::default(), call, query }; + let oracle = MockOracle::new(Default::default(), true); + let proposal = Proposal { when: Default::default(), call, oracle }; // This ensures that if the scheduler is erroneously called, the test doesn't fail due to a // failure to configure the return value. @@ -30,7 +30,7 @@ fn submit_proposal_schedules_proposals() { // The proposal has now been removed and failed. assert!(Proposals::::get(to_be_scheduled_at).is_empty()); assert!(MockScheduler::called_once_with( - DispatchTime::At(proposal.when.clone()), + DispatchTime::At(proposal.when), proposal.call.clone() )); @@ -45,8 +45,8 @@ fn submit_proposal_rejects_proposals() { let remark = SystemCall::remark { remark: "hullo".into() }; let call = Preimage::bound(CallOf::::from(remark)).unwrap(); - let query = MockOracleQuery::new(Default::default(), false); - let proposal = Proposal { when: Default::default(), call, query }; + let oracle = MockOracle::new(Default::default(), false); + let proposal = Proposal { when: Default::default(), call, oracle }; // This ensures that if the scheduler is erroneously called, the test doesn't fail due to a // failure to configure the return value. @@ -82,8 +82,8 @@ fn submit_proposal_fails_on_bad_origin() { let remark = SystemCall::remark { remark: "hullo".into() }; let call = Preimage::bound(CallOf::::from(remark)).unwrap(); - let query = MockOracleQuery::new(Default::default(), Default::default()); - let proposal = Proposal { when: Default::default(), call, query }; + let oracle = MockOracle::new(Default::default(), Default::default()); + let proposal = Proposal { when: Default::default(), call, oracle }; assert_noop!( Futarchy::submit_proposal(alice.signed(), duration, proposal), @@ -99,8 +99,8 @@ fn submit_proposal_fails_if_duration_is_too_short() { let remark = SystemCall::remark { remark: "hullo".into() }; let call = Preimage::bound(CallOf::::from(remark)).unwrap(); - let query = MockOracleQuery::new(Default::default(), Default::default()); - let proposal = Proposal { when: Default::default(), call, query }; + let oracle = MockOracle::new(Default::default(), Default::default()); + let proposal = Proposal { when: Default::default(), call, oracle }; assert_noop!( Futarchy::submit_proposal(RawOrigin::Root.into(), duration, proposal), @@ -116,8 +116,8 @@ fn submit_proposal_fails_if_cache_is_full() { let remark = SystemCall::remark { remark: "hullo".into() }; let call = Preimage::bound(CallOf::::from(remark)).unwrap(); - let query = MockOracleQuery::new(Default::default(), Default::default()); - let proposal = Proposal { when: Default::default(), call, query }; + let oracle = MockOracle::new(Default::default(), Default::default()); + let proposal = Proposal { when: Default::default(), call, oracle }; // Mock up a full vector of proposals. let now = System::block_number(); diff --git a/zrml/futarchy/src/traits/mod.rs b/zrml/futarchy/src/traits/mod.rs index 744956dca..1032ee726 100644 --- a/zrml/futarchy/src/traits/mod.rs +++ b/zrml/futarchy/src/traits/mod.rs @@ -14,7 +14,3 @@ // // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . - -mod oracle_query; - -pub(crate) use oracle_query::OracleQuery; diff --git a/zrml/futarchy/src/types/proposal.rs b/zrml/futarchy/src/types/proposal.rs index 9f6bc7f3f..7e2217d6b 100644 --- a/zrml/futarchy/src/types/proposal.rs +++ b/zrml/futarchy/src/types/proposal.rs @@ -1,5 +1,4 @@ -use crate::{BoundedCallOf, Config, OracleQueryOf}; -use alloc::fmt::Debug; +use crate::{BoundedCallOf, Config, OracleOf}; use frame_support::{CloneNoBound, PartialEqNoBound, RuntimeDebugNoBound}; use frame_system::pallet_prelude::BlockNumberFor; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; @@ -16,5 +15,5 @@ where { pub when: BlockNumberFor, pub call: BoundedCallOf, - pub query: OracleQueryOf, + pub oracle: OracleOf, } diff --git a/zrml/neo-swaps/src/types/decision_market_oracle.rs b/zrml/neo-swaps/src/types/decision_market_oracle.rs new file mode 100644 index 000000000..aa2da8d2f --- /dev/null +++ b/zrml/neo-swaps/src/types/decision_market_oracle.rs @@ -0,0 +1,50 @@ +use crate::{traits::pool_operations::PoolOperations, AssetOf, Config, Error, MarketIdOf, Pools}; +use frame_support::pallet_prelude::Weight; +use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; +use sp_runtime::DispatchError; +use zeitgeist_primitives::traits::FutarchyOracle; + +#[derive(Clone, Debug, Decode, Encode, Eq, MaxEncodedLen, PartialEq, TypeInfo)] +pub struct DecisionMarketOracle +where + T: Config, +{ + market_id: MarketIdOf, + positive_outcome: AssetOf, + negative_outcome: AssetOf, +} + +// Utility implementation that uses the question mark operator to implement a fallible version of +// `evaluate`. +impl DecisionMarketOracle +where + T: Config, +{ + fn try_evaluate(&self) -> Result<(Weight, bool), DispatchError> { + let pool = Pools::::get(self.market_id) + .ok_or::(Error::::PoolNotFound.into())?; + + let positive_value = pool.calculate_spot_price(self.positive_outcome)?; + let negative_value = pool.calculate_spot_price(self.negative_outcome)?; + + let success = positive_value > negative_value; + // TODO Benchmark + Ok((Default::default(), success)) + } +} + +impl FutarchyOracle for DecisionMarketOracle +where + T: Config, +{ + fn evaluate(&self) -> (Weight, bool) { + // Err on the side of caution if the pool is not found or a calculation fails by not + // enacting the policy. + match self.try_evaluate() { + Ok(result) => result, + // TODO Benchmark + Err(_) => (Default::default(), false), + } + } +} diff --git a/zrml/neo-swaps/src/types/mod.rs b/zrml/neo-swaps/src/types/mod.rs index 14da6c7fc..8a0845647 100644 --- a/zrml/neo-swaps/src/types/mod.rs +++ b/zrml/neo-swaps/src/types/mod.rs @@ -15,10 +15,12 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . +mod decision_market_oracle; mod fee_distribution; mod max_assets; mod pool; +pub use decision_market_oracle::*; pub(crate) use fee_distribution::*; pub(crate) use max_assets::*; pub(crate) use pool::*; From 2482bb38d9c9e29b0a4a42a412f1f804718616b4 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Sat, 19 Oct 2024 22:48:20 +0200 Subject: [PATCH 23/73] Add missing licenses --- primitives/src/traits/futarchy_oracle.rs | 17 +++++++++++++++++ zrml/futarchy/src/dispatchable_impls.rs | 17 +++++++++++++++++ zrml/futarchy/src/lib.rs | 18 ++++++++++++++++++ zrml/futarchy/src/mock/types/mod.rs | 17 +++++++++++++++++ zrml/futarchy/src/mock/types/oracle.rs | 17 +++++++++++++++++ zrml/futarchy/src/mock/types/scheduler.rs | 17 +++++++++++++++++ zrml/futarchy/src/mock/utility.rs | 17 +++++++++++++++++ zrml/futarchy/src/pallet_impls.rs | 17 +++++++++++++++++ zrml/futarchy/src/tests/submit_proposal.rs | 17 +++++++++++++++++ zrml/futarchy/src/types/proposal.rs | 17 +++++++++++++++++ .../src/types/decision_market_oracle.rs | 17 +++++++++++++++++ 11 files changed, 188 insertions(+) diff --git a/primitives/src/traits/futarchy_oracle.rs b/primitives/src/traits/futarchy_oracle.rs index 7e6e1077d..0a4530a6d 100644 --- a/primitives/src/traits/futarchy_oracle.rs +++ b/primitives/src/traits/futarchy_oracle.rs @@ -1,3 +1,20 @@ +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + use frame_support::pallet_prelude::Weight; pub trait FutarchyOracle { diff --git a/zrml/futarchy/src/dispatchable_impls.rs b/zrml/futarchy/src/dispatchable_impls.rs index a9b863591..ea786f4ef 100644 --- a/zrml/futarchy/src/dispatchable_impls.rs +++ b/zrml/futarchy/src/dispatchable_impls.rs @@ -1,3 +1,20 @@ +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + use crate::{types::Proposal, Config, Error, Event, Pallet, Proposals}; use frame_support::{ensure, require_transactional, traits::Get}; use frame_system::pallet_prelude::BlockNumberFor; diff --git a/zrml/futarchy/src/lib.rs b/zrml/futarchy/src/lib.rs index 3bf0e9798..6c59ded41 100644 --- a/zrml/futarchy/src/lib.rs +++ b/zrml/futarchy/src/lib.rs @@ -14,6 +14,24 @@ // // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . +// +// This file incorporates work covered by the following copyright and +// permission notice: +// +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. #![doc = include_str!("../README.md")] #![cfg_attr(not(feature = "std"), no_std)] diff --git a/zrml/futarchy/src/mock/types/mod.rs b/zrml/futarchy/src/mock/types/mod.rs index e56d20c4c..66198cf42 100644 --- a/zrml/futarchy/src/mock/types/mod.rs +++ b/zrml/futarchy/src/mock/types/mod.rs @@ -1,3 +1,20 @@ +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + mod oracle; mod scheduler; diff --git a/zrml/futarchy/src/mock/types/oracle.rs b/zrml/futarchy/src/mock/types/oracle.rs index 779a633e6..5e54f3fbc 100644 --- a/zrml/futarchy/src/mock/types/oracle.rs +++ b/zrml/futarchy/src/mock/types/oracle.rs @@ -1,3 +1,20 @@ +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + use alloc::fmt::Debug; use frame_support::pallet_prelude::Weight; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; diff --git a/zrml/futarchy/src/mock/types/scheduler.rs b/zrml/futarchy/src/mock/types/scheduler.rs index ed2ed2d00..23e296003 100644 --- a/zrml/futarchy/src/mock/types/scheduler.rs +++ b/zrml/futarchy/src/mock/types/scheduler.rs @@ -1,3 +1,20 @@ +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + use crate::{mock::runtime::Runtime, BoundedCallOf, CallOf, PalletsOriginOf}; use core::cell::RefCell; use frame_support::traits::schedule::{v3::Anon as ScheduleAnon, DispatchTime, Period, Priority}; diff --git a/zrml/futarchy/src/mock/utility.rs b/zrml/futarchy/src/mock/utility.rs index 1a6d39bc4..d5c2b2ffb 100644 --- a/zrml/futarchy/src/mock/utility.rs +++ b/zrml/futarchy/src/mock/utility.rs @@ -1,3 +1,20 @@ +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + use crate::mock::runtime::{Balances, Futarchy, Preimage, System}; use frame_support::traits::Hooks; use zeitgeist_primitives::types::BlockNumber; diff --git a/zrml/futarchy/src/pallet_impls.rs b/zrml/futarchy/src/pallet_impls.rs index 1da63e1db..947efa714 100644 --- a/zrml/futarchy/src/pallet_impls.rs +++ b/zrml/futarchy/src/pallet_impls.rs @@ -1,3 +1,20 @@ +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + use crate::{Config, Event, Pallet, types::Proposal}; use zeitgeist_primitives::traits::FutarchyOracle; use frame_support::{dispatch::RawOrigin, pallet_prelude::Weight, traits::schedule::DispatchTime}; diff --git a/zrml/futarchy/src/tests/submit_proposal.rs b/zrml/futarchy/src/tests/submit_proposal.rs index 2f7d31dbf..15c2d45fc 100644 --- a/zrml/futarchy/src/tests/submit_proposal.rs +++ b/zrml/futarchy/src/tests/submit_proposal.rs @@ -1,3 +1,20 @@ +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + use super::*; #[test] diff --git a/zrml/futarchy/src/types/proposal.rs b/zrml/futarchy/src/types/proposal.rs index 7e2217d6b..84ddf6748 100644 --- a/zrml/futarchy/src/types/proposal.rs +++ b/zrml/futarchy/src/types/proposal.rs @@ -1,3 +1,20 @@ +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + use crate::{BoundedCallOf, Config, OracleOf}; use frame_support::{CloneNoBound, PartialEqNoBound, RuntimeDebugNoBound}; use frame_system::pallet_prelude::BlockNumberFor; diff --git a/zrml/neo-swaps/src/types/decision_market_oracle.rs b/zrml/neo-swaps/src/types/decision_market_oracle.rs index aa2da8d2f..f855f3cbd 100644 --- a/zrml/neo-swaps/src/types/decision_market_oracle.rs +++ b/zrml/neo-swaps/src/types/decision_market_oracle.rs @@ -1,3 +1,20 @@ +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + use crate::{traits::pool_operations::PoolOperations, AssetOf, Config, Error, MarketIdOf, Pools}; use frame_support::pallet_prelude::Weight; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; From d903a9bde62ba31fdeba85c3b97acead137c466d Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Sat, 19 Oct 2024 22:48:30 +0200 Subject: [PATCH 24/73] Fix formatting --- zrml/futarchy/src/mock/runtime.rs | 9 ++------- zrml/futarchy/src/pallet_impls.rs | 9 ++++++--- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/zrml/futarchy/src/mock/runtime.rs b/zrml/futarchy/src/mock/runtime.rs index a775c4288..039644943 100644 --- a/zrml/futarchy/src/mock/runtime.rs +++ b/zrml/futarchy/src/mock/runtime.rs @@ -21,13 +21,8 @@ use frame_support::{construct_runtime, parameter_types, traits::Everything}; use frame_system::{mocking::MockBlock, EnsureRoot}; use sp_runtime::traits::{BlakeTwo256, ConstU32, IdentityLookup}; use zeitgeist_primitives::{ - constants::mock::{ - BlockHashCount, ExistentialDeposit, MaxLocks, - MaxReserves, - }, - types::{ - AccountIdTest, Balance, BlockNumber, Hash, - }, + constants::mock::{BlockHashCount, ExistentialDeposit, MaxLocks, MaxReserves}, + types::{AccountIdTest, Balance, BlockNumber, Hash}, }; parameter_types! { diff --git a/zrml/futarchy/src/pallet_impls.rs b/zrml/futarchy/src/pallet_impls.rs index 947efa714..cd72ee95e 100644 --- a/zrml/futarchy/src/pallet_impls.rs +++ b/zrml/futarchy/src/pallet_impls.rs @@ -15,10 +15,13 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . -use crate::{Config, Event, Pallet, types::Proposal}; +use crate::{types::Proposal, Config, Event, Pallet}; +use frame_support::{ + dispatch::RawOrigin, + pallet_prelude::Weight, + traits::schedule::{v3::Anon, DispatchTime}, +}; use zeitgeist_primitives::traits::FutarchyOracle; -use frame_support::{dispatch::RawOrigin, pallet_prelude::Weight, traits::schedule::DispatchTime}; -use frame_support::traits::schedule::v3::Anon; impl Pallet { /// Evaluates `proposal` using the specified oracle and schedules the contained call if the From b453a7c6146008033ad1cd74b86d66508c85f185 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Sat, 19 Oct 2024 22:53:39 +0200 Subject: [PATCH 25/73] Fix toml formatting --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index bd41ad2d0..eac6a6327 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -249,8 +249,8 @@ zeitgeist-macros = { path = "macros", default-features = false } zeitgeist-primitives = { path = "primitives", default-features = false } zrml-authorized = { path = "zrml/authorized", default-features = false } zrml-combinatorial-tokens = { path = "zrml/combinatorial-tokens", default-features = false } -zrml-futarchy = { path = "zrml/futarchy", default-features = false } zrml-court = { path = "zrml/court", default-features = false } +zrml-futarchy = { path = "zrml/futarchy", default-features = false } zrml-global-disputes = { path = "zrml/global-disputes", default-features = false } zrml-hybrid-router = { path = "zrml/hybrid-router", default-features = false } zrml-market-commons = { path = "zrml/market-commons", default-features = false } From e7b311e0a357674cd15525bcf8d576c97209ca28 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Sun, 20 Oct 2024 14:21:08 +0200 Subject: [PATCH 26/73] Implement benchmarking --- Cargo.lock | 4 ---- zrml/futarchy/Cargo.toml | 8 -------- zrml/futarchy/src/lib.rs | 8 ++++++++ zrml/futarchy/src/mock/ext_builder.rs | 1 + zrml/futarchy/src/mock/types/oracle.rs | 2 +- zrml/futarchy/src/tests/mod.rs | 4 ++-- zrml/futarchy/src/tests/submit_proposal.rs | 15 +++++---------- .../neo-swaps/src/types/decision_market_oracle.rs | 2 +- 8 files changed, 18 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 395e39bf2..89aa08087 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15250,13 +15250,9 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "orml-currencies", - "orml-tokens", - "orml-traits", "pallet-balances", "pallet-preimage", "pallet-scheduler", - "pallet-timestamp", "parity-scale-codec", "scale-info", "sp-io", diff --git a/zrml/futarchy/Cargo.toml b/zrml/futarchy/Cargo.toml index c13da8548..a9054cb2a 100644 --- a/zrml/futarchy/Cargo.toml +++ b/zrml/futarchy/Cargo.toml @@ -2,7 +2,6 @@ frame-benchmarking = { workspace = true, optional = true } frame-support = { workspace = true } frame-system = { workspace = true } -orml-traits = { workspace = true } parity-scale-codec = { workspace = true, features = ["derive", "max-encoded-len"] } scale-info = { workspace = true, features = ["derive"] } sp-runtime = { workspace = true } @@ -11,12 +10,9 @@ zeitgeist-primitives = { workspace = true } # mock env_logger = { workspace = true, optional = true } -orml-currencies = { workspace = true, optional = true } -orml-tokens = { workspace = true, optional = true } pallet-balances = { workspace = true, optional = true } pallet-preimage = { workspace = true, optional = true } pallet-scheduler = { workspace = true, optional = true } -pallet-timestamp = { workspace = true, optional = true } sp-io = { workspace = true, optional = true } [dev-dependencies] @@ -27,13 +23,10 @@ zrml-futarchy = { workspace = true, features = ["default", "mock"] } default = ["std"] mock = [ "env_logger/default", - "orml-currencies/default", - "orml-tokens/default", "sp-io/default", "pallet-balances/default", "pallet-preimage/default", "pallet-scheduler/default", - "pallet-timestamp/default", "zeitgeist-primitives/mock", ] runtime-benchmarks = [ @@ -45,7 +38,6 @@ std = [ "frame-benchmarking?/std", "frame-support/std", "frame-system/std", - "orml-traits/std", "parity-scale-codec/std", "sp-runtime/std", "zeitgeist-primitives/std", diff --git a/zrml/futarchy/src/lib.rs b/zrml/futarchy/src/lib.rs index 6c59ded41..852076610 100644 --- a/zrml/futarchy/src/lib.rs +++ b/zrml/futarchy/src/lib.rs @@ -38,6 +38,7 @@ extern crate alloc; +mod benchmarking; mod dispatchable_impls; pub mod mock; mod pallet_impls; @@ -66,8 +67,14 @@ mod pallet { }; use zeitgeist_primitives::traits::FutarchyOracle; + // #[cfg(feature = "runtime-benchmarks")] + // use crate::traits::BenchmarkHelper; + #[pallet::config] pub trait Config: frame_system::Config { + // #[cfg(feature = "runtime-benchmarks")] + // type BenchmarkHelper: BenchmarkHelper; + type MinDuration: Get>; // The type used to define the oracle for each proposal. @@ -75,6 +82,7 @@ mod pallet { + Clone + Debug + Decode + + Default + Encode + Eq + MaxEncodedLen diff --git a/zrml/futarchy/src/mock/ext_builder.rs b/zrml/futarchy/src/mock/ext_builder.rs index ddd2d2e10..333b8dcb1 100644 --- a/zrml/futarchy/src/mock/ext_builder.rs +++ b/zrml/futarchy/src/mock/ext_builder.rs @@ -22,6 +22,7 @@ use sp_runtime::BuildStorage; #[cfg(feature = "parachain")] use {crate::mock::consts::FOREIGN_ASSET, zeitgeist_primitives::types::CustomMetadata}; +#[derive(Default)] pub struct ExtBuilder; impl ExtBuilder { diff --git a/zrml/futarchy/src/mock/types/oracle.rs b/zrml/futarchy/src/mock/types/oracle.rs index 5e54f3fbc..25d07f7be 100644 --- a/zrml/futarchy/src/mock/types/oracle.rs +++ b/zrml/futarchy/src/mock/types/oracle.rs @@ -21,7 +21,7 @@ use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; use zeitgeist_primitives::traits::FutarchyOracle; -#[derive(Clone, Debug, Decode, Encode, Eq, MaxEncodedLen, PartialEq, TypeInfo)] +#[derive(Clone, Debug, Decode, Default, Encode, Eq, MaxEncodedLen, PartialEq, TypeInfo)] pub struct MockOracle { weight: Weight, value: bool, diff --git a/zrml/futarchy/src/tests/mod.rs b/zrml/futarchy/src/tests/mod.rs index 8e9d6f048..b73388007 100644 --- a/zrml/futarchy/src/tests/mod.rs +++ b/zrml/futarchy/src/tests/mod.rs @@ -22,7 +22,7 @@ mod submit_proposal; use crate::{ mock::{ ext_builder::ExtBuilder, - runtime::{Futarchy, Preimage, Runtime, RuntimeOrigin, System}, + runtime::{Futarchy, Runtime, RuntimeOrigin, System}, types::{MockOracle, MockScheduler}, utility, }, @@ -32,7 +32,7 @@ use crate::{ use frame_support::{ assert_noop, assert_ok, dispatch::RawOrigin, - traits::{schedule::DispatchTime, StorePreimage}, + traits::{schedule::DispatchTime, Bounded, StorePreimage}, }; use frame_system::Call as SystemCall; use sp_runtime::{traits::Get, BoundedVec, DispatchError}; diff --git a/zrml/futarchy/src/tests/submit_proposal.rs b/zrml/futarchy/src/tests/submit_proposal.rs index 15c2d45fc..3eabcfafe 100644 --- a/zrml/futarchy/src/tests/submit_proposal.rs +++ b/zrml/futarchy/src/tests/submit_proposal.rs @@ -22,8 +22,7 @@ fn submit_proposal_schedules_proposals() { ExtBuilder::build().execute_with(|| { let duration = ::MinDuration::get(); - let remark = SystemCall::remark { remark: "hullo".into() }; - let call = Preimage::bound(CallOf::::from(remark)).unwrap(); + let call = Bounded::Inline(vec![7u8; 128].try_into().unwrap()); let oracle = MockOracle::new(Default::default(), true); let proposal = Proposal { when: Default::default(), call, oracle }; @@ -60,8 +59,7 @@ fn submit_proposal_rejects_proposals() { ExtBuilder::build().execute_with(|| { let duration = ::MinDuration::get(); - let remark = SystemCall::remark { remark: "hullo".into() }; - let call = Preimage::bound(CallOf::::from(remark)).unwrap(); + let call = Bounded::Inline(vec![7u8; 128].try_into().unwrap()); let oracle = MockOracle::new(Default::default(), false); let proposal = Proposal { when: Default::default(), call, oracle }; @@ -97,8 +95,7 @@ fn submit_proposal_fails_on_bad_origin() { let duration = ::MinDuration::get(); - let remark = SystemCall::remark { remark: "hullo".into() }; - let call = Preimage::bound(CallOf::::from(remark)).unwrap(); + let call = Bounded::Inline(vec![7u8; 128].try_into().unwrap()); let oracle = MockOracle::new(Default::default(), Default::default()); let proposal = Proposal { when: Default::default(), call, oracle }; @@ -114,8 +111,7 @@ fn submit_proposal_fails_if_duration_is_too_short() { ExtBuilder::build().execute_with(|| { let duration = ::MinDuration::get() - 1; - let remark = SystemCall::remark { remark: "hullo".into() }; - let call = Preimage::bound(CallOf::::from(remark)).unwrap(); + let call = Bounded::Inline(vec![7u8; 128].try_into().unwrap()); let oracle = MockOracle::new(Default::default(), Default::default()); let proposal = Proposal { when: Default::default(), call, oracle }; @@ -131,8 +127,7 @@ fn submit_proposal_fails_if_cache_is_full() { ExtBuilder::build().execute_with(|| { let duration = ::MinDuration::get(); - let remark = SystemCall::remark { remark: "hullo".into() }; - let call = Preimage::bound(CallOf::::from(remark)).unwrap(); + let call = Bounded::Inline(vec![7u8; 128].try_into().unwrap()); let oracle = MockOracle::new(Default::default(), Default::default()); let proposal = Proposal { when: Default::default(), call, oracle }; diff --git a/zrml/neo-swaps/src/types/decision_market_oracle.rs b/zrml/neo-swaps/src/types/decision_market_oracle.rs index f855f3cbd..c14ce5e28 100644 --- a/zrml/neo-swaps/src/types/decision_market_oracle.rs +++ b/zrml/neo-swaps/src/types/decision_market_oracle.rs @@ -22,7 +22,7 @@ use scale_info::TypeInfo; use sp_runtime::DispatchError; use zeitgeist_primitives::traits::FutarchyOracle; -#[derive(Clone, Debug, Decode, Encode, Eq, MaxEncodedLen, PartialEq, TypeInfo)] +#[derive(Clone, Debug, Decode, Default, Encode, Eq, MaxEncodedLen, PartialEq, TypeInfo)] pub struct DecisionMarketOracle where T: Config, From 780704f1175f6befc69aa28fd71dc4ab845e74cf Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Sun, 20 Oct 2024 14:35:10 +0200 Subject: [PATCH 27/73] Fix clippy errors --- zrml/futarchy/src/tests/mod.rs | 5 ++--- .../src/types/decision_market_oracle.rs | 18 +++++++++++++++++- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/zrml/futarchy/src/tests/mod.rs b/zrml/futarchy/src/tests/mod.rs index b73388007..a9cdbd360 100644 --- a/zrml/futarchy/src/tests/mod.rs +++ b/zrml/futarchy/src/tests/mod.rs @@ -27,14 +27,13 @@ use crate::{ utility, }, types::Proposal, - CacheSize, CallOf, Config, Error, Event, Proposals, + CacheSize, Config, Error, Event, Proposals, }; use frame_support::{ assert_noop, assert_ok, dispatch::RawOrigin, - traits::{schedule::DispatchTime, Bounded, StorePreimage}, + traits::{schedule::DispatchTime, Bounded}, }; -use frame_system::Call as SystemCall; use sp_runtime::{traits::Get, BoundedVec, DispatchError}; /// Utility struct for managing test accounts. diff --git a/zrml/neo-swaps/src/types/decision_market_oracle.rs b/zrml/neo-swaps/src/types/decision_market_oracle.rs index c14ce5e28..94c10b83d 100644 --- a/zrml/neo-swaps/src/types/decision_market_oracle.rs +++ b/zrml/neo-swaps/src/types/decision_market_oracle.rs @@ -22,7 +22,7 @@ use scale_info::TypeInfo; use sp_runtime::DispatchError; use zeitgeist_primitives::traits::FutarchyOracle; -#[derive(Clone, Debug, Decode, Default, Encode, Eq, MaxEncodedLen, PartialEq, TypeInfo)] +#[derive(Clone, Debug, Decode, Encode, Eq, MaxEncodedLen, PartialEq, TypeInfo)] pub struct DecisionMarketOracle where T: Config, @@ -51,6 +51,22 @@ where } } +// This "trivial" implementations prevents rustc from requiring that T implement Default. +impl Default for DecisionMarketOracle +where + T: Config, + MarketIdOf: Default, + AssetOf: Default, +{ + fn default() -> Self { + Self { + market_id: Default::default(), + positive_outcome: Default::default(), + negative_outcome: Default::default(), + } + } +} + impl FutarchyOracle for DecisionMarketOracle where T: Config, From 60ab25663eb9a0a9944f135de214b964635b5e98 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Sun, 20 Oct 2024 14:59:30 +0200 Subject: [PATCH 28/73] . --- zrml/futarchy/src/benchmarking.rs | 79 +++++++++++++++++++++++++++++++ zrml/futarchy/src/lib.rs | 4 +- 2 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 zrml/futarchy/src/benchmarking.rs diff --git a/zrml/futarchy/src/benchmarking.rs b/zrml/futarchy/src/benchmarking.rs new file mode 100644 index 000000000..ceeddc5b7 --- /dev/null +++ b/zrml/futarchy/src/benchmarking.rs @@ -0,0 +1,79 @@ +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +#![cfg(feature = "runtime-benchmarks")] + +use crate::{types::Proposal, Call, Config, Event, Pallet, Proposals}; +use alloc::vec; +use frame_benchmarking::v2::*; +use frame_support::{ + assert_ok, + dispatch::RawOrigin, + traits::{Bounded, Get}, +}; +use frame_system::{pallet_prelude::BlockNumberFor, Pallet as System}; + +#[benchmarks] +mod benchmarks { + use super::*; + + #[benchmark] + fn submit_proposal() { + let duration = T::MinDuration::get(); + + let proposal = Proposal { + when: Default::default(), + call: Bounded::Inline(vec![7u8; 128].try_into().unwrap()), + oracle: Default::default(), + }; + + #[extrinsic_call] + _(RawOrigin::Root, duration, proposal.clone()); + + let expected_event = + ::RuntimeEvent::from(Event::::Submitted { duration, proposal }); + System::::assert_last_event(expected_event.into()); + } + + #[benchmark] + fn maybe_schedule_proposal() { + let proposal = Proposal { + when: Default::default(), + call: Bounded::Inline(vec![7u8; 128].try_into().unwrap()), + oracle: Default::default(), + }; + + let block_number: BlockNumberFor = 1u32.into(); + assert_ok!(Proposals::::try_mutate(block_number, |proposals| { + proposals.try_push(proposal.clone()) + })); + + #[block] + { + Pallet::::maybe_schedule_proposal(proposal.clone()); + } + + let expected_event = ::RuntimeEvent::from(Event::::Scheduled { proposal }); + System::::assert_last_event(expected_event.into()); + } + + impl_benchmark_test_suite!( + Pallet, + crate::mock::ext_builder::ExtBuilder::build(), + crate::mock::runtime::Runtime + ); +} diff --git a/zrml/futarchy/src/lib.rs b/zrml/futarchy/src/lib.rs index 852076610..29e54f7ed 100644 --- a/zrml/futarchy/src/lib.rs +++ b/zrml/futarchy/src/lib.rs @@ -168,9 +168,11 @@ mod pallet { #[pallet::hooks] impl Hooks> for Pallet { fn on_initialize(now: BlockNumberFor) -> Weight { - let mut total_weight = Weight::zero(); + let mut total_weight = Weight::zero(); // Add buffer. let proposals = Proposals::::take(now); + // TODO Add one storage read. + for proposal in proposals.into_iter() { let weight = Self::maybe_schedule_proposal(proposal); total_weight = total_weight.saturating_add(weight); From 041194559f6574e4b0c8879988f7ad1c5b09fcfd Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Sun, 20 Oct 2024 21:56:02 +0200 Subject: [PATCH 29/73] . --- primitives/src/traits.rs | 2 + .../src/traits/futarchy_benchmark_helper.rs | 6 ++ runtime/common/src/lib.rs | 6 +- scripts/benchmarks/configuration.sh | 11 ++-- zrml/futarchy/src/benchmarking.rs | 7 +- zrml/futarchy/src/lib.rs | 11 ++-- zrml/futarchy/src/mock/runtime.rs | 5 ++ .../src/mock/types/benchmark_helper.rs | 13 ++++ zrml/futarchy/src/mock/types/mod.rs | 2 + zrml/futarchy/src/mock/types/oracle.rs | 8 ++- zrml/futarchy/src/mock/types/scheduler.rs | 6 +- zrml/neo-swaps/src/lib.rs | 25 +++++--- .../types/decision_market_benchmark_helper.rs | 64 +++++++++++++++++++ .../src/types/decision_market_oracle.rs | 16 ----- zrml/neo-swaps/src/types/mod.rs | 3 + 15 files changed, 139 insertions(+), 46 deletions(-) create mode 100644 primitives/src/traits/futarchy_benchmark_helper.rs create mode 100644 zrml/futarchy/src/mock/types/benchmark_helper.rs create mode 100644 zrml/neo-swaps/src/types/decision_market_benchmark_helper.rs diff --git a/primitives/src/traits.rs b/primitives/src/traits.rs index 9d69f2c15..33371924c 100644 --- a/primitives/src/traits.rs +++ b/primitives/src/traits.rs @@ -20,6 +20,7 @@ mod complete_set_operations_api; mod deploy_pool_api; mod dispute_api; mod distribute_fees; +mod futarchy_benchmark_helper; mod futarchy_oracle; mod hybrid_router_amm_api; mod hybrid_router_orderbook_api; @@ -33,6 +34,7 @@ pub use complete_set_operations_api::*; pub use deploy_pool_api::*; pub use dispute_api::*; pub use distribute_fees::*; +pub use futarchy_benchmark_helper::*; pub use futarchy_oracle::*; pub use hybrid_router_amm_api::*; pub use hybrid_router_orderbook_api::*; diff --git a/primitives/src/traits/futarchy_benchmark_helper.rs b/primitives/src/traits/futarchy_benchmark_helper.rs new file mode 100644 index 000000000..3caaffa5b --- /dev/null +++ b/primitives/src/traits/futarchy_benchmark_helper.rs @@ -0,0 +1,6 @@ +pub trait FutarchyBenchmarkHelper +{ + /// Creates an oracle which returns `value` when evaluated, provided that state is not modified + /// any further. + fn create_oracle(value: bool) -> Oracle; +} diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs index a7e435894..0d45b306c 100644 --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -1453,7 +1453,7 @@ macro_rules! create_runtime_api { list_benchmark!(list, extra, pallet_balances, Balances); list_benchmark!(list, extra, pallet_bounties, Bounties); list_benchmark!(list, extra, pallet_collective, AdvisoryCommittee); - list_benchmark!(list, extra, pallet_contracts, Contracts); + // list_benchmark!(list, extra, pallet_contracts, Contracts); list_benchmark!(list, extra, pallet_democracy, Democracy); list_benchmark!(list, extra, pallet_identity, Identity); list_benchmark!(list, extra, pallet_membership, AdvisoryCommitteeMembership); @@ -1468,6 +1468,7 @@ macro_rules! create_runtime_api { list_benchmark!(list, extra, zrml_swaps, Swaps); list_benchmark!(list, extra, zrml_authorized, Authorized); list_benchmark!(list, extra, zrml_court, Court); + list_benchmark!(list, extra, zrml_futarchy, Futarchy); list_benchmark!(list, extra, zrml_global_disputes, GlobalDisputes); list_benchmark!(list, extra, zrml_orderbook, Orderbook); list_benchmark!(list, extra, zrml_parimutuel, Parimutuel); @@ -1542,7 +1543,7 @@ macro_rules! create_runtime_api { add_benchmark!(params, batches, pallet_balances, Balances); add_benchmark!(params, batches, pallet_bounties, Bounties); add_benchmark!(params, batches, pallet_collective, AdvisoryCommittee); - add_benchmark!(params, batches, pallet_contracts, Contracts); + // add_benchmark!(params, batches, pallet_contracts, Contracts); add_benchmark!(params, batches, pallet_democracy, Democracy); add_benchmark!(params, batches, pallet_identity, Identity); add_benchmark!(params, batches, pallet_membership, AdvisoryCommitteeMembership); @@ -1557,6 +1558,7 @@ macro_rules! create_runtime_api { add_benchmark!(params, batches, zrml_swaps, Swaps); add_benchmark!(params, batches, zrml_authorized, Authorized); add_benchmark!(params, batches, zrml_court, Court); + add_benchmark!(params, batches, zrml_futarchy, Futarchy); add_benchmark!(params, batches, zrml_global_disputes, GlobalDisputes); add_benchmark!(params, batches, zrml_orderbook, Orderbook); add_benchmark!(params, batches, zrml_parimutuel, Parimutuel); diff --git a/scripts/benchmarks/configuration.sh b/scripts/benchmarks/configuration.sh index f363f34ec..41ec1f9f9 100644 --- a/scripts/benchmarks/configuration.sh +++ b/scripts/benchmarks/configuration.sh @@ -4,10 +4,9 @@ EXTERNAL_WEIGHTS_PATH="./runtime/common/src/weights/" # This script contains the configuration for other benchmarking scripts. export FRAME_PALLETS=( - frame_system pallet_balances pallet_bounties pallet_collective pallet_contracts \ - pallet_democracy pallet_identity pallet_membership pallet_multisig pallet_preimage \ - pallet_proxy pallet_scheduler pallet_timestamp pallet_treasury pallet_utility \ - pallet_vesting \ + frame_system pallet_balances pallet_bounties pallet_collective pallet_democracy \ + pallet_identity pallet_membership pallet_multisig pallet_preimage pallet_proxy \ + pallet_scheduler pallet_timestamp pallet_treasury pallet_utility pallet_vesting ) export FRAME_PALLETS_RUNS="${FRAME_PALLETS_RUNS:-20}" export FRAME_PALLETS_STEPS="${FRAME_PALLETS_STEPS:-50}" @@ -27,8 +26,8 @@ export ORML_PALLETS_STEPS="${ORML_PALLETS_STEPS:-50}" export ORML_WEIGHT_TEMPLATE="./misc/orml_weight_template.hbs" export ZEITGEIST_PALLETS=( - zrml_authorized zrml_court zrml_global_disputes zrml_hybrid_router zrml_neo_swaps \ - zrml_orderbook zrml_parimutuel zrml_prediction_markets zrml_swaps zrml_styx \ + zrml_authorized zrml_court zrml_futarchy zrml_global_disputes zrml_hybrid_router \ + zrml_neo_swaps zrml_orderbook zrml_parimutuel zrml_prediction_markets zrml_swaps zrml_styx \ ) export ZEITGEIST_PALLETS_RUNS="${ZEITGEIST_PALLETS_RUNS:-20}" export ZEITGEIST_PALLETS_STEPS="${ZEITGEIST_PALLETS_STEPS:-50}" diff --git a/zrml/futarchy/src/benchmarking.rs b/zrml/futarchy/src/benchmarking.rs index ceeddc5b7..3f8316e32 100644 --- a/zrml/futarchy/src/benchmarking.rs +++ b/zrml/futarchy/src/benchmarking.rs @@ -26,6 +26,7 @@ use frame_support::{ traits::{Bounded, Get}, }; use frame_system::{pallet_prelude::BlockNumberFor, Pallet as System}; +use zeitgeist_primitives::traits::FutarchyBenchmarkHelper; #[benchmarks] mod benchmarks { @@ -35,10 +36,11 @@ mod benchmarks { fn submit_proposal() { let duration = T::MinDuration::get(); + let oracle = T::BenchmarkHelper::create_oracle(true); let proposal = Proposal { when: Default::default(), call: Bounded::Inline(vec![7u8; 128].try_into().unwrap()), - oracle: Default::default(), + oracle, }; #[extrinsic_call] @@ -51,10 +53,11 @@ mod benchmarks { #[benchmark] fn maybe_schedule_proposal() { + let oracle = T::BenchmarkHelper::create_oracle(true); let proposal = Proposal { when: Default::default(), call: Bounded::Inline(vec![7u8; 128].try_into().unwrap()), - oracle: Default::default(), + oracle, }; let block_number: BlockNumberFor = 1u32.into(); diff --git a/zrml/futarchy/src/lib.rs b/zrml/futarchy/src/lib.rs index 29e54f7ed..54af76d87 100644 --- a/zrml/futarchy/src/lib.rs +++ b/zrml/futarchy/src/lib.rs @@ -43,7 +43,7 @@ mod dispatchable_impls; pub mod mock; mod pallet_impls; mod tests; -mod traits; +pub mod traits; pub mod types; pub use pallet::*; @@ -67,13 +67,13 @@ mod pallet { }; use zeitgeist_primitives::traits::FutarchyOracle; - // #[cfg(feature = "runtime-benchmarks")] - // use crate::traits::BenchmarkHelper; + #[cfg(feature = "runtime-benchmarks")] + use zeitgeist_primitives::traits::FutarchyBenchmarkHelper; #[pallet::config] pub trait Config: frame_system::Config { - // #[cfg(feature = "runtime-benchmarks")] - // type BenchmarkHelper: BenchmarkHelper; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper: FutarchyBenchmarkHelper; type MinDuration: Get>; @@ -82,7 +82,6 @@ mod pallet { + Clone + Debug + Decode - + Default + Encode + Eq + MaxEncodedLen diff --git a/zrml/futarchy/src/mock/runtime.rs b/zrml/futarchy/src/mock/runtime.rs index 039644943..d0cb5303a 100644 --- a/zrml/futarchy/src/mock/runtime.rs +++ b/zrml/futarchy/src/mock/runtime.rs @@ -25,6 +25,9 @@ use zeitgeist_primitives::{ types::{AccountIdTest, Balance, BlockNumber, Hash}, }; +#[cfg(feature = "runtime-benchmarks")] +use crate::mock::types::MockBenchmarkHelper; + parameter_types! { // zrml-futarchy pub const MinDuration: BlockNumber = 10; @@ -95,6 +98,8 @@ impl pallet_preimage::Config for Runtime { } impl zrml_futarchy::Config for Runtime { + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = MockBenchmarkHelper; type MinDuration = MinDuration; type Oracle = MockOracle; type RuntimeEvent = RuntimeEvent; diff --git a/zrml/futarchy/src/mock/types/benchmark_helper.rs b/zrml/futarchy/src/mock/types/benchmark_helper.rs new file mode 100644 index 000000000..6b7bf253d --- /dev/null +++ b/zrml/futarchy/src/mock/types/benchmark_helper.rs @@ -0,0 +1,13 @@ +use crate::{ + mock::{runtime::Runtime, types::MockOracle}, + OracleOf, +}; +use zeitgeist_primitives::traits::FutarchyBenchmarkHelper; + +pub struct MockBenchmarkHelper; + +impl FutarchyBenchmarkHelper> for MockBenchmarkHelper { + fn create_oracle(value: bool) -> OracleOf { + MockOracle::new(Default::default(), value) + } +} diff --git a/zrml/futarchy/src/mock/types/mod.rs b/zrml/futarchy/src/mock/types/mod.rs index 66198cf42..cca6c5103 100644 --- a/zrml/futarchy/src/mock/types/mod.rs +++ b/zrml/futarchy/src/mock/types/mod.rs @@ -15,8 +15,10 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . +mod benchmark_helper; mod oracle; mod scheduler; +pub use benchmark_helper::MockBenchmarkHelper; pub(crate) use oracle::MockOracle; pub(crate) use scheduler::MockScheduler; diff --git a/zrml/futarchy/src/mock/types/oracle.rs b/zrml/futarchy/src/mock/types/oracle.rs index 25d07f7be..4b8a7f7e4 100644 --- a/zrml/futarchy/src/mock/types/oracle.rs +++ b/zrml/futarchy/src/mock/types/oracle.rs @@ -21,12 +21,18 @@ use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; use zeitgeist_primitives::traits::FutarchyOracle; -#[derive(Clone, Debug, Decode, Default, Encode, Eq, MaxEncodedLen, PartialEq, TypeInfo)] +#[derive(Clone, Debug, Decode, Encode, Eq, MaxEncodedLen, PartialEq, TypeInfo)] pub struct MockOracle { weight: Weight, value: bool, } +impl Default for MockOracle { + fn default() -> Self { + MockOracle { weight: Default::default(), value: true } + } +} + impl MockOracle { pub fn new(weight: Weight, value: bool) -> Self { Self { weight, value } diff --git a/zrml/futarchy/src/mock/types/scheduler.rs b/zrml/futarchy/src/mock/types/scheduler.rs index 23e296003..806e90b62 100644 --- a/zrml/futarchy/src/mock/types/scheduler.rs +++ b/zrml/futarchy/src/mock/types/scheduler.rs @@ -25,7 +25,7 @@ pub struct MockScheduler; impl MockScheduler { pub fn set_return_value(value: DispatchResult) { - SCHEDULER_RETURN_VALUE.with(|v| *v.borrow_mut() = Some(value)); + SCHEDULER_RETURN_VALUE.with(|v| *v.borrow_mut() = value); } pub fn not_called() -> bool { @@ -70,7 +70,6 @@ impl ScheduleAnon, CallOf, PalletsOriginOf Result<(), DispatchError> { @@ -94,6 +93,5 @@ impl ScheduleAnon, CallOf, PalletsOriginOf> = const { RefCell::new(vec![]) }; - pub static SCHEDULER_RETURN_VALUE: RefCell> = - const { RefCell::new(None) }; + pub static SCHEDULER_RETURN_VALUE: RefCell = const { RefCell::new(Ok(())) }; } diff --git a/zrml/neo-swaps/src/lib.rs b/zrml/neo-swaps/src/lib.rs index 4b548fa8d..aa000f960 100644 --- a/zrml/neo-swaps/src/lib.rs +++ b/zrml/neo-swaps/src/lib.rs @@ -132,7 +132,11 @@ mod pallet { MarketId = MarketIdOf, >; - type MarketCommons: MarketCommonsPalletApi>; + type MarketCommons: MarketCommonsPalletApi< + AccountId = Self::AccountId, + BlockNumber = BlockNumberFor, + Balance = BalanceOf, + >; type MultiCurrency: MultiCurrency>; @@ -638,7 +642,7 @@ mod pallet { impl Pallet { #[require_transactional] - fn do_buy( + pub(crate) fn do_buy( who: T::AccountId, market_id: MarketIdOf, asset_out: AssetOf, @@ -700,7 +704,7 @@ mod pallet { } #[require_transactional] - fn do_sell( + pub(crate) fn do_sell( who: T::AccountId, market_id: MarketIdOf, asset_in: AssetOf, @@ -789,7 +793,7 @@ mod pallet { } #[require_transactional] - fn do_join( + pub(crate) fn do_join( who: T::AccountId, market_id: MarketIdOf, pool_shares_amount: BalanceOf, @@ -848,7 +852,7 @@ mod pallet { } #[require_transactional] - fn do_exit( + pub(crate) fn do_exit( who: T::AccountId, market_id: MarketIdOf, pool_shares_amount: BalanceOf, @@ -935,7 +939,10 @@ mod pallet { } #[require_transactional] - fn do_withdraw_fees(who: T::AccountId, market_id: MarketIdOf) -> DispatchResult { + pub(crate) fn do_withdraw_fees( + who: T::AccountId, + market_id: MarketIdOf, + ) -> DispatchResult { Self::try_mutate_pool(&market_id, |pool| { let amount = pool.liquidity_shares_manager.withdraw_fees(&who)?; T::MultiCurrency::transfer(pool.collateral, &pool.account_id, &who, amount)?; // Should never fail. @@ -949,7 +956,7 @@ mod pallet { } #[require_transactional] - fn do_deploy_pool( + pub(crate) fn do_deploy_pool( who: T::AccountId, market_id: MarketIdOf, amount: BalanceOf, @@ -1032,7 +1039,7 @@ mod pallet { #[allow(clippy::too_many_arguments)] // TODO Bundle `buy`/`keep`/`sell` into one arg. #[require_transactional] - fn do_combo_buy( + pub(crate) fn do_combo_buy( who: T::AccountId, market_id: MarketIdOf, // TODO Replace `buy`/`keep`/`sell` with a struct. @@ -1125,7 +1132,7 @@ mod pallet { // TODO Replace `buy`/`keep`/`sell` with a struct. #[allow(clippy::too_many_arguments)] #[require_transactional] - fn do_combo_sell( + pub(crate) fn do_combo_sell( who: T::AccountId, market_id: MarketIdOf, buy: Vec>, diff --git a/zrml/neo-swaps/src/types/decision_market_benchmark_helper.rs b/zrml/neo-swaps/src/types/decision_market_benchmark_helper.rs new file mode 100644 index 000000000..df67a36ed --- /dev/null +++ b/zrml/neo-swaps/src/types/decision_market_benchmark_helper.rs @@ -0,0 +1,64 @@ +#![cfg(feature = "runtime-benchmarks")] + +use crate::{types::DecisionMarketOracle, BalanceOf, Config, Pallet, MIN_SWAP_FEE}; +use core::marker::PhantomData; +use frame_benchmarking::whitelisted_caller; +use orml_traits::MultiCurrency; +use sp_runtime::{Perbill, SaturatedConversion, Saturating}; +use zeitgeist_primitives::{ + constants::{BASE, CENT}, + traits::{CompleteSetOperationsApi, FutarchyBenchmarkHelper, MarketBuilderTrait}, + types::{Asset, MarketCreation, MarketPeriod, MarketStatus, MarketType, ScoringRule}, +}; +use zrml_market_commons::{types::MarketBuilder, MarketCommonsPalletApi}; + +pub struct DecisionMarketBenchmarkHelper(PhantomData); + +impl FutarchyBenchmarkHelper> for DecisionMarketBenchmarkHelper +where + T: Config + zrml_market_commons::Config, +{ + fn create_oracle(value: bool) -> DecisionMarketOracle { + let collateral = Asset::Ztg; + let alice: T::AccountId = whitelisted_caller(); + + let mut market_builder: MarketBuilder = MarketBuilder::new(); + market_builder + .base_asset(collateral) + .creation(MarketCreation::Permissionless) + .creator(alice.clone()) + .creator_fee(Perbill::zero()) + .oracle(alice.clone()) + .metadata(vec![0; 50]) + .market_type(MarketType::Categorical(2)) + .period(MarketPeriod::Block(0u32.into()..1u32.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); + let (market_id, _) = T::MarketCommons::build_market(market_builder).unwrap(); + + let amount: BalanceOf = (100 * BASE).saturated_into(); + let double_amount = amount.saturating_mul(2u8.into()); + T::MultiCurrency::deposit(collateral, &alice, amount).unwrap(); + T::CompleteSetOperations::buy_complete_set(alice, market_id, amount); + + Pallet::::do_deploy_pool( + alice, + market_id, + amount, + vec![(51 * CENT).saturated_into(), (49 * CENT).saturated_into()], + MIN_SWAP_FEE.saturated_into(), + ) + .unwrap(); + + let positive_outcome = Asset::CategoricalOutcome(market_id, (!value).into()); + let negative_outcome = Asset::CategoricalOutcome(market_id, value.into()); + + DecisionMarketOracle { market_id, positive_outcome, negative_outcome } + } +} diff --git a/zrml/neo-swaps/src/types/decision_market_oracle.rs b/zrml/neo-swaps/src/types/decision_market_oracle.rs index 94c10b83d..f855f3cbd 100644 --- a/zrml/neo-swaps/src/types/decision_market_oracle.rs +++ b/zrml/neo-swaps/src/types/decision_market_oracle.rs @@ -51,22 +51,6 @@ where } } -// This "trivial" implementations prevents rustc from requiring that T implement Default. -impl Default for DecisionMarketOracle -where - T: Config, - MarketIdOf: Default, - AssetOf: Default, -{ - fn default() -> Self { - Self { - market_id: Default::default(), - positive_outcome: Default::default(), - negative_outcome: Default::default(), - } - } -} - impl FutarchyOracle for DecisionMarketOracle where T: Config, diff --git a/zrml/neo-swaps/src/types/mod.rs b/zrml/neo-swaps/src/types/mod.rs index 8a0845647..754ba2bcb 100644 --- a/zrml/neo-swaps/src/types/mod.rs +++ b/zrml/neo-swaps/src/types/mod.rs @@ -15,11 +15,14 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . +mod decision_market_benchmark_helper; mod decision_market_oracle; mod fee_distribution; mod max_assets; mod pool; +#[cfg(feature = "runtime-benchmarks")] +pub use decision_market_benchmark_helper::*; pub use decision_market_oracle::*; pub(crate) use fee_distribution::*; pub(crate) use max_assets::*; From b1844346446989f3ee9aac2f1a35dfb093a57255 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Sun, 20 Oct 2024 22:50:51 +0200 Subject: [PATCH 30/73] . --- runtime/common/src/lib.rs | 10 ++- .../types/decision_market_benchmark_helper.rs | 78 ++++++++++--------- .../src/types/decision_market_oracle.rs | 12 ++- 3 files changed, 58 insertions(+), 42 deletions(-) diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs index 0d45b306c..5a563ce15 100644 --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -75,8 +75,6 @@ macro_rules! decl_common_types { Blake2_256, BoundedVec, Twox64Concat, }; use frame_system::EnsureSigned; - #[cfg(feature = "try-runtime")] - use frame_try_runtime::{TryStateSelect, UpgradeCheckSelect}; use orml_traits::MultiCurrency; use pallet_balances::CreditOf; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; @@ -89,6 +87,12 @@ macro_rules! decl_common_types { use zrml_combinatorial_tokens::types::CryptographicIdManager; use zrml_neo_swaps::types::DecisionMarketOracle; + #[cfg(feature = "try-runtime")] + use frame_try_runtime::{TryStateSelect, UpgradeCheckSelect}; + + #[cfg(feature = "runtime-benchmarks")] + use zrml_neo_swaps::types::DecisionMarketBenchmarkHelper; + pub type Block = generic::Block; type Address = sp_runtime::MultiAddress; @@ -1208,6 +1212,8 @@ macro_rules! impl_config_traits { } impl zrml_futarchy::Config for Runtime { + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = DecisionMarketBenchmarkHelper; type MinDuration = MinDuration; type Oracle = DecisionMarketOracle; type RuntimeEvent = RuntimeEvent; diff --git a/zrml/neo-swaps/src/types/decision_market_benchmark_helper.rs b/zrml/neo-swaps/src/types/decision_market_benchmark_helper.rs index df67a36ed..b58104489 100644 --- a/zrml/neo-swaps/src/types/decision_market_benchmark_helper.rs +++ b/zrml/neo-swaps/src/types/decision_market_benchmark_helper.rs @@ -1,12 +1,18 @@ #![cfg(feature = "runtime-benchmarks")] -use crate::{types::DecisionMarketOracle, BalanceOf, Config, Pallet, MIN_SWAP_FEE}; +use crate::{ + liquidity_tree::types::LiquidityTree, + types::{DecisionMarketOracle, Pool}, + BalanceOf, Config, MarketIdOf, Pallet, Pools, MIN_SWAP_FEE, +}; +use alloc::collections::BTreeMap; use core::marker::PhantomData; use frame_benchmarking::whitelisted_caller; use orml_traits::MultiCurrency; -use sp_runtime::{Perbill, SaturatedConversion, Saturating}; +use sp_runtime::{traits::Zero, Perbill, SaturatedConversion, Saturating}; use zeitgeist_primitives::{ constants::{BASE, CENT}, + math::fixed::{BaseProvider, ZeitgeistBase}, traits::{CompleteSetOperationsApi, FutarchyBenchmarkHelper, MarketBuilderTrait}, types::{Asset, MarketCreation, MarketPeriod, MarketStatus, MarketType, ScoringRule}, }; @@ -17,48 +23,44 @@ pub struct DecisionMarketBenchmarkHelper(PhantomData); impl FutarchyBenchmarkHelper> for DecisionMarketBenchmarkHelper where T: Config + zrml_market_commons::Config, + as MarketCommonsPalletApi>::MarketId: + Into<::MarketId>, + ::MarketId: + Into< as MarketCommonsPalletApi>::MarketId>, { + /// Creates a mocked up pool with prices so that the returned decision market oracle evaluates + /// to `value`. The pool is technically in invalid state. fn create_oracle(value: bool) -> DecisionMarketOracle { + let market_id: MarketIdOf = 0u8.into(); let collateral = Asset::Ztg; - let alice: T::AccountId = whitelisted_caller(); - - let mut market_builder: MarketBuilder = MarketBuilder::new(); - market_builder - .base_asset(collateral) - .creation(MarketCreation::Permissionless) - .creator(alice.clone()) - .creator_fee(Perbill::zero()) - .oracle(alice.clone()) - .metadata(vec![0; 50]) - .market_type(MarketType::Categorical(2)) - .period(MarketPeriod::Block(0u32.into()..1u32.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); - let (market_id, _) = T::MarketCommons::build_market(market_builder).unwrap(); - let amount: BalanceOf = (100 * BASE).saturated_into(); - let double_amount = amount.saturating_mul(2u8.into()); - T::MultiCurrency::deposit(collateral, &alice, amount).unwrap(); - T::CompleteSetOperations::buy_complete_set(alice, market_id, amount); + // Create a `reserves` map so that `positive_outcome` has a higher price if and only if + // `value` is `true`. + let positive_outcome = Asset::CategoricalOutcome(market_id, 0u16); + let negative_outcome = Asset::CategoricalOutcome(market_id, 1u16); + let mut reserves = BTreeMap::new(); + let one: BalanceOf = ZeitgeistBase::get().unwrap(); + let two: BalanceOf = one.saturating_mul(2u8.into()); + if value { + reserves.insert(positive_outcome, one); + reserves.insert(negative_outcome, two); + } else { + reserves.insert(positive_outcome, two); + reserves.insert(negative_outcome, one); + } - Pallet::::do_deploy_pool( - alice, - market_id, - amount, - vec![(51 * CENT).saturated_into(), (49 * CENT).saturated_into()], - MIN_SWAP_FEE.saturated_into(), - ) - .unwrap(); + let account_id: T::AccountId = Pallet::::pool_account_id(&market_id); + let pool = Pool { + account_id: account_id.clone(), + reserves: reserves.try_into().unwrap(), + collateral, + liquidity_parameter: one.clone(), + liquidity_shares_manager: LiquidityTree::new(account_id, one).unwrap(), + swap_fee: Zero::zero(), + }; - let positive_outcome = Asset::CategoricalOutcome(market_id, (!value).into()); - let negative_outcome = Asset::CategoricalOutcome(market_id, value.into()); + Pools::::insert(market_id, pool); - DecisionMarketOracle { market_id, positive_outcome, negative_outcome } + DecisionMarketOracle::new(market_id, positive_outcome, negative_outcome) } } diff --git a/zrml/neo-swaps/src/types/decision_market_oracle.rs b/zrml/neo-swaps/src/types/decision_market_oracle.rs index f855f3cbd..f01045288 100644 --- a/zrml/neo-swaps/src/types/decision_market_oracle.rs +++ b/zrml/neo-swaps/src/types/decision_market_oracle.rs @@ -32,12 +32,20 @@ where negative_outcome: AssetOf, } -// Utility implementation that uses the question mark operator to implement a fallible version of -// `evaluate`. impl DecisionMarketOracle where T: Config, { + pub fn new( + market_id: MarketIdOf, + positive_outcome: AssetOf, + negative_outcome: AssetOf, + ) -> Self { + Self { market_id, positive_outcome, negative_outcome } + } + + // Utility implementation that uses the question mark operator to implement a fallible version + // of `evaluate`. fn try_evaluate(&self) -> Result<(Weight, bool), DispatchError> { let pool = Pools::::get(self.market_id) .ok_or::(Error::::PoolNotFound.into())?; From e93db7872206d69f81c4fea2ff8bfcf5493fce56 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Sun, 20 Oct 2024 23:40:09 +0200 Subject: [PATCH 31/73] benchmarks work --- primitives/src/traits/futarchy_benchmark_helper.rs | 3 +-- zrml/futarchy/src/benchmarking.rs | 13 +++---------- zrml/futarchy/src/mock/types/scheduler.rs | 3 +-- zrml/neo-swaps/src/types/decision_market_oracle.rs | 1 + 4 files changed, 6 insertions(+), 14 deletions(-) diff --git a/primitives/src/traits/futarchy_benchmark_helper.rs b/primitives/src/traits/futarchy_benchmark_helper.rs index 3caaffa5b..7e4299fa7 100644 --- a/primitives/src/traits/futarchy_benchmark_helper.rs +++ b/primitives/src/traits/futarchy_benchmark_helper.rs @@ -1,5 +1,4 @@ -pub trait FutarchyBenchmarkHelper -{ +pub trait FutarchyBenchmarkHelper { /// Creates an oracle which returns `value` when evaluated, provided that state is not modified /// any further. fn create_oracle(value: bool) -> Oracle; diff --git a/zrml/futarchy/src/benchmarking.rs b/zrml/futarchy/src/benchmarking.rs index 3f8316e32..c78bddb9b 100644 --- a/zrml/futarchy/src/benchmarking.rs +++ b/zrml/futarchy/src/benchmarking.rs @@ -53,17 +53,10 @@ mod benchmarks { #[benchmark] fn maybe_schedule_proposal() { + let when = u32::MAX.into(); let oracle = T::BenchmarkHelper::create_oracle(true); - let proposal = Proposal { - when: Default::default(), - call: Bounded::Inline(vec![7u8; 128].try_into().unwrap()), - oracle, - }; - - let block_number: BlockNumberFor = 1u32.into(); - assert_ok!(Proposals::::try_mutate(block_number, |proposals| { - proposals.try_push(proposal.clone()) - })); + let proposal = + Proposal { when, call: Bounded::Inline(vec![7u8; 128].try_into().unwrap()), oracle }; #[block] { diff --git a/zrml/futarchy/src/mock/types/scheduler.rs b/zrml/futarchy/src/mock/types/scheduler.rs index 806e90b62..9d3dc92ab 100644 --- a/zrml/futarchy/src/mock/types/scheduler.rs +++ b/zrml/futarchy/src/mock/types/scheduler.rs @@ -68,8 +68,7 @@ impl ScheduleAnon, CallOf, PalletsOriginOf Result<(), DispatchError> { diff --git a/zrml/neo-swaps/src/types/decision_market_oracle.rs b/zrml/neo-swaps/src/types/decision_market_oracle.rs index f01045288..ddce82184 100644 --- a/zrml/neo-swaps/src/types/decision_market_oracle.rs +++ b/zrml/neo-swaps/src/types/decision_market_oracle.rs @@ -16,6 +16,7 @@ // along with Zeitgeist. If not, see . use crate::{traits::pool_operations::PoolOperations, AssetOf, Config, Error, MarketIdOf, Pools}; +use alloc::format; use frame_support::pallet_prelude::Weight; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; From c9edaa437000db027b781ba66274101934549a68 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Mon, 21 Oct 2024 02:25:26 +0200 Subject: [PATCH 32/73] . --- zrml/futarchy/src/benchmarking.rs | 5 +- zrml/futarchy/src/weights.rs | 83 +++++++++++++++++++ .../types/decision_market_benchmark_helper.rs | 20 ++--- .../src/types/decision_market_oracle.rs | 1 - 4 files changed, 91 insertions(+), 18 deletions(-) create mode 100644 zrml/futarchy/src/weights.rs diff --git a/zrml/futarchy/src/benchmarking.rs b/zrml/futarchy/src/benchmarking.rs index c78bddb9b..23de777c8 100644 --- a/zrml/futarchy/src/benchmarking.rs +++ b/zrml/futarchy/src/benchmarking.rs @@ -17,15 +17,14 @@ #![cfg(feature = "runtime-benchmarks")] -use crate::{types::Proposal, Call, Config, Event, Pallet, Proposals}; +use crate::{types::Proposal, Call, Config, Event, Pallet}; use alloc::vec; use frame_benchmarking::v2::*; use frame_support::{ - assert_ok, dispatch::RawOrigin, traits::{Bounded, Get}, }; -use frame_system::{pallet_prelude::BlockNumberFor, Pallet as System}; +use frame_system::Pallet as System; use zeitgeist_primitives::traits::FutarchyBenchmarkHelper; #[benchmarks] diff --git a/zrml/futarchy/src/weights.rs b/zrml/futarchy/src/weights.rs new file mode 100644 index 000000000..db21cdab6 --- /dev/null +++ b/zrml/futarchy/src/weights.rs @@ -0,0 +1,83 @@ +// Copyright 2022-2024 Forecasting Technologies LTD. +// Copyright 2021-2022 Zeitgeist PM LLC. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +//! Autogenerated weights for zrml_futarchy +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: `2024-10-20`, STEPS: `2`, REPEAT: `0`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `blackbird`, CPU: `` +//! EXECUTION: ``, WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` + +// Executed Command: +// ./target/release/zeitgeist +// benchmark +// pallet +// --chain=dev +// --steps=2 +// --repeat=0 +// --pallet=zrml_futarchy +// --extrinsic=* +// --execution=native +// --wasm-execution=compiled +// --heap-pages=4096 +// --template=./misc/weight_template.hbs +// --header=./HEADER_GPL3 +// --output=./zrml/futarchy/src/weights.rs + +#![allow(unused_parens)] +#![allow(unused_imports)] + +use core::marker::PhantomData; +use frame_support::{traits::Get, weights::Weight}; + +/// Trait containing the required functions for weight retrival within +/// zrml_futarchy (automatically generated) +pub trait WeightInfoZeitgeist { + fn submit_proposal() -> Weight; + fn maybe_schedule_proposal() -> Weight; +} + +/// Weight functions for zrml_futarchy (automatically generated) +pub struct WeightInfo(PhantomData); +impl WeightInfoZeitgeist for WeightInfo { + /// Storage: `Futarchy::Proposals` (r:1 w:1) + /// Proof: `Futarchy::Proposals` (`max_values`: None, `max_size`: Some(3561), added: 6036, mode: `MaxEncodedLen`) + fn submit_proposal() -> Weight { + // Proof Size summary in bytes: + // Measured: `41` + // Estimated: `7026` + // Minimum execution time: 13_000 nanoseconds. + Weight::from_parts(13_000_000, 7026) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `NeoSwaps::Pools` (r:1 w:0) + /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(146552), added: 149027, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(109074), added: 111549, mode: `MaxEncodedLen`) + fn maybe_schedule_proposal() -> Weight { + // Proof Size summary in bytes: + // Measured: `368` + // Estimated: `150017` + // Minimum execution time: 55_000 nanoseconds. + Weight::from_parts(55_000_000, 150017) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } +} diff --git a/zrml/neo-swaps/src/types/decision_market_benchmark_helper.rs b/zrml/neo-swaps/src/types/decision_market_benchmark_helper.rs index b58104489..8e367e357 100644 --- a/zrml/neo-swaps/src/types/decision_market_benchmark_helper.rs +++ b/zrml/neo-swaps/src/types/decision_market_benchmark_helper.rs @@ -3,30 +3,22 @@ use crate::{ liquidity_tree::types::LiquidityTree, types::{DecisionMarketOracle, Pool}, - BalanceOf, Config, MarketIdOf, Pallet, Pools, MIN_SWAP_FEE, + BalanceOf, Config, MarketIdOf, Pallet, Pools, }; use alloc::collections::BTreeMap; use core::marker::PhantomData; -use frame_benchmarking::whitelisted_caller; -use orml_traits::MultiCurrency; -use sp_runtime::{traits::Zero, Perbill, SaturatedConversion, Saturating}; +use sp_runtime::{traits::Zero, Saturating}; use zeitgeist_primitives::{ - constants::{BASE, CENT}, math::fixed::{BaseProvider, ZeitgeistBase}, - traits::{CompleteSetOperationsApi, FutarchyBenchmarkHelper, MarketBuilderTrait}, - types::{Asset, MarketCreation, MarketPeriod, MarketStatus, MarketType, ScoringRule}, + traits::FutarchyBenchmarkHelper, + types::Asset, }; -use zrml_market_commons::{types::MarketBuilder, MarketCommonsPalletApi}; pub struct DecisionMarketBenchmarkHelper(PhantomData); impl FutarchyBenchmarkHelper> for DecisionMarketBenchmarkHelper where - T: Config + zrml_market_commons::Config, - as MarketCommonsPalletApi>::MarketId: - Into<::MarketId>, - ::MarketId: - Into< as MarketCommonsPalletApi>::MarketId>, + T: Config, { /// Creates a mocked up pool with prices so that the returned decision market oracle evaluates /// to `value`. The pool is technically in invalid state. @@ -54,7 +46,7 @@ where account_id: account_id.clone(), reserves: reserves.try_into().unwrap(), collateral, - liquidity_parameter: one.clone(), + liquidity_parameter: one, liquidity_shares_manager: LiquidityTree::new(account_id, one).unwrap(), swap_fee: Zero::zero(), }; diff --git a/zrml/neo-swaps/src/types/decision_market_oracle.rs b/zrml/neo-swaps/src/types/decision_market_oracle.rs index ddce82184..f01045288 100644 --- a/zrml/neo-swaps/src/types/decision_market_oracle.rs +++ b/zrml/neo-swaps/src/types/decision_market_oracle.rs @@ -16,7 +16,6 @@ // along with Zeitgeist. If not, see . use crate::{traits::pool_operations::PoolOperations, AssetOf, Config, Error, MarketIdOf, Pools}; -use alloc::format; use frame_support::pallet_prelude::Weight; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; From 2d339bc63c9b5143f8e5867e4aaf31b554f5baec Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Mon, 21 Oct 2024 02:36:52 +0200 Subject: [PATCH 33/73] . --- runtime/common/src/lib.rs | 1 + zrml/futarchy/src/lib.rs | 10 ++++++---- zrml/futarchy/src/mock/runtime.rs | 6 +++++- zrml/futarchy/src/pallet_impls.rs | 8 +++----- 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs index 5a563ce15..beb867658 100644 --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -1219,6 +1219,7 @@ macro_rules! impl_config_traits { type RuntimeEvent = RuntimeEvent; type Scheduler = Scheduler; type SubmitOrigin = EnsureRoot; + type WeightInfo = zrml_futarchy::weights::WeightInfo; } impl zrml_market_commons::Config for Runtime { diff --git a/zrml/futarchy/src/lib.rs b/zrml/futarchy/src/lib.rs index 54af76d87..500c61457 100644 --- a/zrml/futarchy/src/lib.rs +++ b/zrml/futarchy/src/lib.rs @@ -45,12 +45,13 @@ mod pallet_impls; mod tests; pub mod traits; pub mod types; +pub mod weights; pub use pallet::*; #[frame_support::pallet] mod pallet { - use crate::types::Proposal; + use crate::{types::Proposal, weights::WeightInfoZeitgeist}; use alloc::fmt::Debug; use core::marker::PhantomData; use frame_support::{ @@ -95,6 +96,8 @@ mod pallet { /// The origin that is allowed to submit proposals. type SubmitOrigin: EnsureOrigin; + + type WeightInfo: WeightInfoZeitgeist; } #[pallet::pallet] @@ -147,12 +150,11 @@ mod pallet { DurationTooShort, } - // TODO: Index for proposal? #[pallet::call] impl Pallet { #[pallet::call_index(0)] #[transactional] - #[pallet::weight({0})] + #[pallet::weight(T::WeightInfo::submit_proposal())] pub fn submit_proposal( origin: OriginFor, duration: BlockNumberFor, @@ -170,7 +172,7 @@ mod pallet { let mut total_weight = Weight::zero(); // Add buffer. let proposals = Proposals::::take(now); - // TODO Add one storage read. + total_weight = total_weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); for proposal in proposals.into_iter() { let weight = Self::maybe_schedule_proposal(proposal); diff --git a/zrml/futarchy/src/mock/runtime.rs b/zrml/futarchy/src/mock/runtime.rs index d0cb5303a..2dd402112 100644 --- a/zrml/futarchy/src/mock/runtime.rs +++ b/zrml/futarchy/src/mock/runtime.rs @@ -16,7 +16,10 @@ // along with Zeitgeist. If not, see . use crate as zrml_futarchy; -use crate::mock::types::{MockOracle, MockScheduler}; +use crate::{ + mock::types::{MockOracle, MockScheduler}, + weights::WeightInfo, +}; use frame_support::{construct_runtime, parameter_types, traits::Everything}; use frame_system::{mocking::MockBlock, EnsureRoot}; use sp_runtime::traits::{BlakeTwo256, ConstU32, IdentityLookup}; @@ -105,4 +108,5 @@ impl zrml_futarchy::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Scheduler = MockScheduler; type SubmitOrigin = EnsureRoot<::AccountId>; + type WeightInfo = WeightInfo; } diff --git a/zrml/futarchy/src/pallet_impls.rs b/zrml/futarchy/src/pallet_impls.rs index cd72ee95e..9ab3fe5ce 100644 --- a/zrml/futarchy/src/pallet_impls.rs +++ b/zrml/futarchy/src/pallet_impls.rs @@ -15,7 +15,7 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . -use crate::{types::Proposal, Config, Event, Pallet}; +use crate::{types::Proposal, weights::WeightInfoZeitgeist, Config, Event, Pallet}; use frame_support::{ dispatch::RawOrigin, pallet_prelude::Weight, @@ -43,12 +43,10 @@ impl Pallet { } else { Self::deposit_event(Event::::UnexpectedSchedulerError); } - - evaluate_weight // TODO Add benchmark! } else { Self::deposit_event(Event::::Rejected { proposal }); - - evaluate_weight } + + T::WeightInfo::maybe_schedule_proposal().saturating_add(evaluate_weight) } } From eef4bdad70a60bb55805c8ea51d95e5f5a51c373 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Mon, 21 Oct 2024 13:52:11 +0200 Subject: [PATCH 34/73] Update copyright notices --- .../src/traits/futarchy_benchmark_helper.rs | 17 +++++++++++++++++ .../futarchy/src/mock/types/benchmark_helper.rs | 17 +++++++++++++++++ .../types/decision_market_benchmark_helper.rs | 17 +++++++++++++++++ 3 files changed, 51 insertions(+) diff --git a/primitives/src/traits/futarchy_benchmark_helper.rs b/primitives/src/traits/futarchy_benchmark_helper.rs index 7e4299fa7..625a7f5a9 100644 --- a/primitives/src/traits/futarchy_benchmark_helper.rs +++ b/primitives/src/traits/futarchy_benchmark_helper.rs @@ -1,3 +1,20 @@ +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + pub trait FutarchyBenchmarkHelper { /// Creates an oracle which returns `value` when evaluated, provided that state is not modified /// any further. diff --git a/zrml/futarchy/src/mock/types/benchmark_helper.rs b/zrml/futarchy/src/mock/types/benchmark_helper.rs index 6b7bf253d..5ba2c2238 100644 --- a/zrml/futarchy/src/mock/types/benchmark_helper.rs +++ b/zrml/futarchy/src/mock/types/benchmark_helper.rs @@ -1,3 +1,20 @@ +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + use crate::{ mock::{runtime::Runtime, types::MockOracle}, OracleOf, diff --git a/zrml/neo-swaps/src/types/decision_market_benchmark_helper.rs b/zrml/neo-swaps/src/types/decision_market_benchmark_helper.rs index 8e367e357..9c8b60e6e 100644 --- a/zrml/neo-swaps/src/types/decision_market_benchmark_helper.rs +++ b/zrml/neo-swaps/src/types/decision_market_benchmark_helper.rs @@ -1,3 +1,20 @@ +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + #![cfg(feature = "runtime-benchmarks")] use crate::{ From d393cd1f936d029f16549d73d75fc60307e00219 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Mon, 21 Oct 2024 14:17:02 +0200 Subject: [PATCH 35/73] Implement and run Decision Market Oracle Benchmarks (#1381) * Add benchmark for the decision market oracle * Add benchmarks to `DecisionMarketOracle` calls * Fix clippy errors --- zrml/neo-swaps/src/benchmarking.rs | 26 ++++++++++++++++++- .../src/types/decision_market_oracle.rs | 19 +++++++------- zrml/neo-swaps/src/weights.rs | 10 +++++++ 3 files changed, 45 insertions(+), 10 deletions(-) diff --git a/zrml/neo-swaps/src/benchmarking.rs b/zrml/neo-swaps/src/benchmarking.rs index 22c0d58b2..5846b8283 100644 --- a/zrml/neo-swaps/src/benchmarking.rs +++ b/zrml/neo-swaps/src/benchmarking.rs @@ -21,6 +21,7 @@ use super::*; use crate::{ liquidity_tree::{traits::LiquidityTreeHelper, types::LiquidityTree}, traits::{liquidity_shares_manager::LiquiditySharesManager, pool_operations::PoolOperations}, + types::DecisionMarketOracle, AssetOf, BalanceOf, MarketIdOf, Pallet as NeoSwaps, Pools, MIN_SPOT_PRICE, }; use alloc::{vec, vec::Vec}; @@ -39,7 +40,7 @@ use sp_runtime::{ use zeitgeist_primitives::{ constants::{base_multiples::*, CENT}, math::fixed::{BaseProvider, FixedDiv, FixedMul, ZeitgeistBase}, - traits::CompleteSetOperationsApi, + traits::{CompleteSetOperationsApi, FutarchyOracle}, types::{Asset, Market, MarketCreation, MarketPeriod, MarketStatus, MarketType, ScoringRule}, }; use zrml_market_commons::MarketCommonsPalletApi; @@ -492,6 +493,29 @@ mod benchmarks { ); } + #[benchmark] + fn decision_market_oracle_evaluate() { + let alice = whitelisted_caller(); + let base_asset = Asset::Ztg; + let asset_count = 2; + let market_id = create_market_and_deploy_pool::( + alice, + base_asset, + asset_count, + _10.saturated_into(), + ); + + let pool = Pools::::get(market_id).unwrap(); + let assets = pool.assets(); + + let oracle = DecisionMarketOracle::::new(market_id, assets[0], assets[1]); + + #[block] + { + let _ = oracle.evaluate(); + } + } + impl_benchmark_test_suite!( NeoSwaps, crate::mock::ExtBuilder::default().build(), diff --git a/zrml/neo-swaps/src/types/decision_market_oracle.rs b/zrml/neo-swaps/src/types/decision_market_oracle.rs index f01045288..7c8e8239d 100644 --- a/zrml/neo-swaps/src/types/decision_market_oracle.rs +++ b/zrml/neo-swaps/src/types/decision_market_oracle.rs @@ -15,7 +15,10 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . -use crate::{traits::pool_operations::PoolOperations, AssetOf, Config, Error, MarketIdOf, Pools}; +use crate::{ + traits::pool_operations::PoolOperations, weights::WeightInfoZeitgeist, AssetOf, Config, Error, + MarketIdOf, Pools, +}; use frame_support::pallet_prelude::Weight; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; @@ -46,7 +49,7 @@ where // Utility implementation that uses the question mark operator to implement a fallible version // of `evaluate`. - fn try_evaluate(&self) -> Result<(Weight, bool), DispatchError> { + fn try_evaluate(&self) -> Result { let pool = Pools::::get(self.market_id) .ok_or::(Error::::PoolNotFound.into())?; @@ -54,8 +57,8 @@ where let negative_value = pool.calculate_spot_price(self.negative_outcome)?; let success = positive_value > negative_value; - // TODO Benchmark - Ok((Default::default(), success)) + + Ok(success) } } @@ -66,10 +69,8 @@ where fn evaluate(&self) -> (Weight, bool) { // Err on the side of caution if the pool is not found or a calculation fails by not // enacting the policy. - match self.try_evaluate() { - Ok(result) => result, - // TODO Benchmark - Err(_) => (Default::default(), false), - } + let value = self.try_evaluate().unwrap_or(false); + + (T::WeightInfo::decision_market_oracle_evaluate(), value) } } diff --git a/zrml/neo-swaps/src/weights.rs b/zrml/neo-swaps/src/weights.rs index 74e948d50..88801707e 100644 --- a/zrml/neo-swaps/src/weights.rs +++ b/zrml/neo-swaps/src/weights.rs @@ -57,6 +57,7 @@ pub trait WeightInfoZeitgeist { fn exit(n: u32) -> Weight; fn withdraw_fees() -> Weight; fn deploy_pool(n: u32) -> Weight; + fn decision_market_oracle_evaluate() -> Weight; } /// Weight functions for zrml_neo_swaps (automatically generated) @@ -240,4 +241,13 @@ impl WeightInfoZeitgeist for WeightInfo { .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(n.into()))) .saturating_add(Weight::from_parts(0, 5196).saturating_mul(n.into())) } + /// Storage: `NeoSwaps::Pools` (r:1 w:0) + /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(146552), added: 149027, mode: `MaxEncodedLen`) + fn decision_market_oracle_evaluate() -> Weight { + // Proof Size summary in bytes: + // Measured: `365` + // Estimated: `150017` + // Minimum execution time: 44_000 nanoseconds. + Weight::from_parts(44_000_000, 150017).saturating_add(T::DbWeight::get().reads(1)) + } } From 5ba674f18e78506a6688f531142c4d8eb9c69cc1 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Mon, 21 Oct 2024 19:42:06 +0200 Subject: [PATCH 36/73] Remove old migrations (#1379) (#1382) * Remove old migrations * Update licenses --- runtime/common/src/lib.rs | 25 +- zrml/neo-swaps/src/migration.rs | 248 ------------------ .../src/traits/liquidity_shares_manager.rs | 3 +- 3 files changed, 3 insertions(+), 273 deletions(-) diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs index beb867658..dcdc6cfbc 100644 --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -97,30 +97,7 @@ macro_rules! decl_common_types { type Address = sp_runtime::MultiAddress; - parameter_types! { - pub const CampaignAssetsPalletStr: &'static str = "CampaignAssets"; - pub const CustomAssetsPalletStr: &'static str = "CustomAssets"; - pub const MarketAssetsPalletStr: &'static str = "MarketAssets"; - pub const LiquidityMiningPalletStr: &'static str = "LiquidityMining"; - pub const RikiddoPalletStr: &'static str = "RikiddoSigmoidFeeMarketEma"; - pub const SimpleDisputesPalletStr: &'static str = "SimpleDisputes"; - } - - type RemoveCustomAssets = RemovePallet; - type RemoveCampaignAssets = RemovePallet; - type RemoveMarketAssets = RemovePallet; - type RemoveLiquidityMining = RemovePallet; - type RemoveRikiddo = RemovePallet; - type RemoveSimpleDisputes = RemovePallet; - - type Migrations = ( - RemoveCustomAssets, - RemoveCampaignAssets, - RemoveMarketAssets, - RemoveLiquidityMining, - RemoveRikiddo, - RemoveSimpleDisputes, - ); + type Migrations = (); pub type Executive = frame_executive::Executive< Runtime, diff --git a/zrml/neo-swaps/src/migration.rs b/zrml/neo-swaps/src/migration.rs index 8650ec124..2e9ba478f 100644 --- a/zrml/neo-swaps/src/migration.rs +++ b/zrml/neo-swaps/src/migration.rs @@ -14,251 +14,3 @@ // // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . - -use crate::{ - traits::LiquiditySharesManager, types::Pool, AssetOf, BalanceOf, Config, LiquidityTreeOf, - Pallet, Pools, -}; -use alloc::collections::BTreeMap; -use core::marker::PhantomData; -use frame_support::{ - traits::{Get, OnRuntimeUpgrade, StorageVersion}, - weights::Weight, -}; -use log; -use parity_scale_codec::{Decode, Encode}; -use scale_info::TypeInfo; -use sp_runtime::{RuntimeDebug, Saturating}; - -cfg_if::cfg_if! { - if #[cfg(feature = "try-runtime")] { - use crate::{MarketIdOf}; - use alloc::{format, vec::Vec}; - use frame_support::{migration::storage_key_iter, pallet_prelude::Twox64Concat}; - use sp_runtime::DispatchError; - } -} - -cfg_if::cfg_if! { - if #[cfg(any(feature = "try-runtime", test))] { - const NEO_SWAPS: &[u8] = b"NeoSwaps"; - const POOLS: &[u8] = b"Pools"; - } -} - -const NEO_SWAPS_REQUIRED_STORAGE_VERSION: u16 = 1; -const NEO_SWAPS_NEXT_STORAGE_VERSION: u16 = NEO_SWAPS_REQUIRED_STORAGE_VERSION + 1; - -#[derive(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo)] -#[scale_info(skip_type_params(T))] -pub struct OldPool -where - T: Config, - LSM: LiquiditySharesManager, -{ - pub account_id: T::AccountId, - pub reserves: BTreeMap, BalanceOf>, - pub collateral: AssetOf, - pub liquidity_parameter: BalanceOf, - pub liquidity_shares_manager: LSM, - pub swap_fee: BalanceOf, -} - -type OldPoolOf = OldPool>; - -pub struct MigratePoolReservesToBoundedBTreeMap(PhantomData); - -impl OnRuntimeUpgrade for MigratePoolReservesToBoundedBTreeMap -where - T: Config, -{ - fn on_runtime_upgrade() -> Weight { - let mut total_weight = T::DbWeight::get().reads(1); - let neo_swaps_version = StorageVersion::get::>(); - if neo_swaps_version != NEO_SWAPS_REQUIRED_STORAGE_VERSION { - log::info!( - "MigratePoolReservesToBoundedBTreeMap: neo-swaps version is {:?}, but {:?} is \ - required", - neo_swaps_version, - NEO_SWAPS_REQUIRED_STORAGE_VERSION, - ); - return total_weight; - } - log::info!("MigratePoolReservesToBoundedBTreeMap: Starting..."); - let mut translated = 0u64; - Pools::::translate::, _>(|_, pool| { - // Can't fail unless `MaxAssets` is misconfigured. If it fails after all, we delete the - // pool. This may seem drastic, but is actually cleaner than trying some half-baked - // recovery and allows us to do a manual recovery of funds. - let reserves = pool.reserves.try_into().ok()?; - translated.saturating_inc(); - Some(Pool { - account_id: pool.account_id, - reserves, - collateral: pool.collateral, - liquidity_parameter: pool.liquidity_parameter, - liquidity_shares_manager: pool.liquidity_shares_manager, - swap_fee: pool.swap_fee, - }) - }); - log::info!("MigratePoolReservesToBoundedBTreeMap: Upgraded {} pools.", translated); - total_weight = - total_weight.saturating_add(T::DbWeight::get().reads_writes(translated, translated)); - StorageVersion::new(NEO_SWAPS_NEXT_STORAGE_VERSION).put::>(); - total_weight = total_weight.saturating_add(T::DbWeight::get().writes(1)); - log::info!("MigratePoolReservesToBoundedBTreeMap: Done!"); - total_weight - } - - #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result, DispatchError> { - let old_pools = - storage_key_iter::, OldPoolOf, Twox64Concat>(NEO_SWAPS, POOLS) - .collect::>(); - Ok(old_pools.encode()) - } - - #[cfg(feature = "try-runtime")] - fn post_upgrade(previous_state: Vec) -> Result<(), DispatchError> { - let old_pools: BTreeMap, OldPoolOf> = - Decode::decode(&mut &previous_state[..]) - .map_err(|_| "Failed to decode state: Invalid state")?; - let new_pool_count = Pools::::iter().count(); - assert_eq!(old_pools.len(), new_pool_count); - for (market_id, new_pool) in Pools::::iter() { - let old_pool = - old_pools.get(&market_id).expect(&format!("Pool {:?} not found", market_id)[..]); - assert_eq!(new_pool.account_id, old_pool.account_id); - assert_eq!(new_pool.reserves.into_inner(), old_pool.reserves); - assert_eq!(new_pool.collateral, old_pool.collateral); - assert_eq!(new_pool.liquidity_parameter, old_pool.liquidity_parameter); - assert_eq!(new_pool.liquidity_shares_manager, old_pool.liquidity_shares_manager); - assert_eq!(new_pool.swap_fee, old_pool.swap_fee); - } - log::info!( - "MigratePoolReservesToBoundedBTreeMap: Post-upgrade pool count is {}!", - new_pool_count - ); - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - liquidity_tree::types::LiquidityTree, - mock::{ExtBuilder, Runtime}, - MarketIdOf, PoolOf, Pools, - }; - use alloc::collections::BTreeMap; - use core::fmt::Debug; - use frame_support::{migration::put_storage_value, StorageHasher, Twox64Concat}; - use parity_scale_codec::Encode; - use sp_io::storage::root as storage_root; - use sp_runtime::StateVersion; - use zeitgeist_primitives::types::Asset; - - #[test] - fn on_runtime_upgrade_increments_the_storage_version() { - ExtBuilder::default().build().execute_with(|| { - set_up_version(); - MigratePoolReservesToBoundedBTreeMap::::on_runtime_upgrade(); - assert_eq!(StorageVersion::get::>(), NEO_SWAPS_NEXT_STORAGE_VERSION); - }); - } - - #[test] - fn on_runtime_upgrade_is_noop_if_versions_are_not_correct() { - ExtBuilder::default().build().execute_with(|| { - StorageVersion::new(NEO_SWAPS_NEXT_STORAGE_VERSION).put::>(); - let (_, new_pools) = construct_old_new_tuple(); - populate_test_data::, PoolOf>( - NEO_SWAPS, POOLS, new_pools, - ); - let tmp = storage_root(StateVersion::V1); - MigratePoolReservesToBoundedBTreeMap::::on_runtime_upgrade(); - assert_eq!(tmp, storage_root(StateVersion::V1)); - }); - } - - #[test] - fn on_runtime_upgrade_correctly_updates_markets() { - ExtBuilder::default().build().execute_with(|| { - set_up_version(); - let (old_pools, new_pools) = construct_old_new_tuple(); - populate_test_data::, OldPoolOf>( - NEO_SWAPS, POOLS, old_pools, - ); - MigratePoolReservesToBoundedBTreeMap::::on_runtime_upgrade(); - let actual = Pools::get(0u128).unwrap(); - assert_eq!(actual, new_pools[0]); - }); - } - - fn set_up_version() { - StorageVersion::new(NEO_SWAPS_REQUIRED_STORAGE_VERSION).put::>(); - } - - fn construct_old_new_tuple() -> (Vec>, Vec>) { - let account_id = 1; - let mut old_reserves = BTreeMap::new(); - old_reserves.insert(Asset::CategoricalOutcome(2, 3), 4); - let new_reserves = old_reserves.clone().try_into().unwrap(); - let collateral = Asset::Ztg; - let liquidity_parameter = 5; - let swap_fee = 6; - let total_shares = 7; - let fees = 8; - - let mut liquidity_shares_manager = LiquidityTree::new(account_id, total_shares).unwrap(); - liquidity_shares_manager.nodes.get_mut(0).unwrap().fees = fees; - - let old_pool = OldPoolOf { - account_id, - reserves: old_reserves, - collateral, - liquidity_parameter, - liquidity_shares_manager: liquidity_shares_manager.clone(), - swap_fee, - }; - let new_pool = Pool { - account_id, - reserves: new_reserves, - collateral, - liquidity_parameter, - liquidity_shares_manager, - swap_fee, - }; - (vec![old_pool], vec![new_pool]) - } - - #[allow(unused)] - fn populate_test_data(pallet: &[u8], prefix: &[u8], data: Vec) - where - H: StorageHasher, - K: TryFrom + Encode, - V: Encode + Clone, - >::Error: Debug, - { - for (key, value) in data.iter().enumerate() { - let storage_hash = utility::key_to_hash::(K::try_from(key).unwrap()); - put_storage_value::(pallet, prefix, &storage_hash, (*value).clone()); - } - } -} - -mod utility { - use alloc::vec::Vec; - use frame_support::StorageHasher; - use parity_scale_codec::Encode; - - #[allow(unused)] - pub fn key_to_hash(key: K) -> Vec - where - H: StorageHasher, - K: Encode, - { - key.using_encoded(H::hash).as_ref().to_vec() - } -} diff --git a/zrml/neo-swaps/src/traits/liquidity_shares_manager.rs b/zrml/neo-swaps/src/traits/liquidity_shares_manager.rs index b99302fb1..e7b070374 100644 --- a/zrml/neo-swaps/src/traits/liquidity_shares_manager.rs +++ b/zrml/neo-swaps/src/traits/liquidity_shares_manager.rs @@ -1,4 +1,4 @@ -// Copyright 2023 Forecasting Technologies LTD. +// Copyright 2023-2024 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // @@ -34,6 +34,7 @@ pub trait LiquiditySharesManager { fn exit(&mut self, who: &T::AccountId, amount: BalanceOf) -> DispatchResult; /// Transfer `amount` units of pool shares from `sender` to `receiver`. + #[allow(unused)] fn split( &mut self, sender: &T::AccountId, From 72aa56d4f73509790720d2f973fed28e55fd046f Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Mon, 21 Oct 2024 19:54:29 +0200 Subject: [PATCH 37/73] Implement integration test for zrml-futarchy (#1383) * Implement integration test for zrml-futarchy * Fix test --- Cargo.lock | 2 - runtime/common/src/lib.rs | 154 ++++++++++++++++++++++++++ zrml/futarchy/Cargo.toml | 4 - zrml/futarchy/src/mock/runtime.rs | 14 --- zrml/futarchy/src/mock/types/mod.rs | 2 + zrml/futarchy/src/mock/utility.rs | 4 +- zrml/hybrid-router/src/mock.rs | 2 +- zrml/neo-swaps/src/tests/combo_buy.rs | 2 - 8 files changed, 158 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 89aa08087..66f78b6db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15251,8 +15251,6 @@ dependencies = [ "frame-support", "frame-system", "pallet-balances", - "pallet-preimage", - "pallet-scheduler", "parity-scale-codec", "scale-info", "sp-io", diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs index dcdc6cfbc..d1b87aa09 100644 --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -2184,6 +2184,33 @@ macro_rules! create_common_tests { mod common_tests { common_runtime::fee_tests!(); + mod utility { + use crate::{Balances, BlockNumber, Futarchy, Preimage, Scheduler, System}; + use frame_support::traits::Hooks; + + // Beware! This only advances certain pallets. + pub(crate) fn run_to_block(to: BlockNumber) { + while System::block_number() < to { + let now = System::block_number(); + + Futarchy::on_finalize(now); + Balances::on_finalize(now); + Preimage::on_finalize(now); + Scheduler::on_finalize(now); + System::on_finalize(now); + + let next = now + 1; + System::set_block_number(next); + + System::on_initialize(next); + Scheduler::on_initialize(next); + Preimage::on_initialize(next); + Balances::on_initialize(next); + Futarchy::on_initialize(next); + } + } + } + mod dust_removal { use crate::*; use frame_support::PalletId; @@ -2223,6 +2250,133 @@ macro_rules! create_common_tests { }); } } + + mod futarchy { + use crate::{ + common_tests::utility, AccountId, Asset, AssetManager, Balance, Balances, + Futarchy, MarketId, NeoSwaps, PredictionMarkets, Preimage, Runtime, + RuntimeCall, RuntimeOrigin, Scheduler, System, + }; + use frame_support::{assert_ok, dispatch::RawOrigin, traits::StorePreimage}; + use orml_traits::MultiCurrency; + use sp_runtime::{ + traits::{Hash, Zero}, + BuildStorage, Perbill, + }; + use zeitgeist_primitives::{ + math::fixed::{BaseProvider, ZeitgeistBase}, + traits::MarketBuilderTrait, + types::{ + Deadlines, MarketCreation, MarketPeriod, MarketType, MultiHash, ScoringRule, + }, + }; + use zrml_futarchy::types::Proposal; + use zrml_market_commons::types::MarketBuilder; + use zrml_neo_swaps::types::DecisionMarketOracle; + + #[test] + fn futarchy_schedules_and_executes_call() { + let mut t: sp_io::TestExternalities = + frame_system::GenesisConfig::::default() + .build_storage() + .unwrap() + .into(); + t.execute_with(|| { + let alice = AccountId::from([0u8; 32]); + + let collateral: Asset = Asset::Ztg; + let one: Balance = ZeitgeistBase::get().unwrap(); + let total_cost: Balance = one.saturating_mul(100_000u128); + assert_ok!(AssetManager::deposit(collateral, &alice, total_cost)); + + let mut metadata = [0x01; 50]; + metadata[0] = 0x15; + metadata[1] = 0x30; + let multihash = MultiHash::Sha3_384(metadata); + + let oracle_duration = + ::MinOracleDuration::get(); + let deadlines = Deadlines { + grace_period: Default::default(), + oracle_duration, + dispute_duration: Zero::zero(), + }; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(alice.clone()), + collateral, + Perbill::zero(), + alice.clone(), + MarketPeriod::Block(0..999), + deadlines, + multihash, + MarketCreation::Permissionless, + MarketType::Categorical(2), + None, + ScoringRule::AmmCdaHybrid, + )); + + let market_id = 0; + let amount = one * 100u128; + assert_ok!(PredictionMarkets::buy_complete_set( + RuntimeOrigin::signed(alice.clone()), + market_id, + amount, + )); + + assert_ok!(NeoSwaps::deploy_pool( + RuntimeOrigin::signed(alice.clone()), + market_id, + amount, + vec![one / 10u128 * 9u128, one / 10u128], + one / 100, + )); + + let duration = ::MinDuration::get(); + + // Wrap `remark_with_event` call in `dispatch_as` so that it doesn't error + // with `BadOrigin`. + let bob = AccountId::from([0x01; 32]); + let remark = b"hullo".to_vec(); + let remark_dispatched_as = pallet_utility::Call::::dispatch_as { + as_origin: Box::new(RawOrigin::Signed(bob.clone()).into()), + call: Box::new( + frame_system::Call::remark_with_event { remark: remark.clone() } + .into(), + ), + }; + let call = + Preimage::bound(RuntimeCall::from(remark_dispatched_as)).unwrap(); + let oracle = DecisionMarketOracle::new( + market_id, + Asset::CategoricalOutcome(market_id, 0), + Asset::CategoricalOutcome(market_id, 1), + ); + let when = duration + 10; + let proposal = Proposal { when, call, oracle }; + + assert_ok!(Futarchy::submit_proposal( + RawOrigin::Root.into(), + duration, + proposal.clone() + )); + + utility::run_to_block(when); + + let hash = ::Hashing::hash(&remark); + System::assert_has_event( + frame_system::Event::::Remarked { sender: bob, hash }.into(), + ); + System::assert_has_event( + pallet_scheduler::Event::::Dispatched { + task: (when, 0), + id: None, + result: Ok(()), + } + .into(), + ); + }); + } + } } }; } diff --git a/zrml/futarchy/Cargo.toml b/zrml/futarchy/Cargo.toml index a9054cb2a..ab9b7ceff 100644 --- a/zrml/futarchy/Cargo.toml +++ b/zrml/futarchy/Cargo.toml @@ -11,8 +11,6 @@ zeitgeist-primitives = { workspace = true } env_logger = { workspace = true, optional = true } pallet-balances = { workspace = true, optional = true } -pallet-preimage = { workspace = true, optional = true } -pallet-scheduler = { workspace = true, optional = true } sp-io = { workspace = true, optional = true } [dev-dependencies] @@ -25,8 +23,6 @@ mock = [ "env_logger/default", "sp-io/default", "pallet-balances/default", - "pallet-preimage/default", - "pallet-scheduler/default", "zeitgeist-primitives/mock", ] runtime-benchmarks = [ diff --git a/zrml/futarchy/src/mock/runtime.rs b/zrml/futarchy/src/mock/runtime.rs index 2dd402112..87dc92f29 100644 --- a/zrml/futarchy/src/mock/runtime.rs +++ b/zrml/futarchy/src/mock/runtime.rs @@ -34,17 +34,12 @@ use crate::mock::types::MockBenchmarkHelper; parameter_types! { // zrml-futarchy pub const MinDuration: BlockNumber = 10; - - // pallet-preimage - pub const PreimageBaseDeposit: Balance = 0; - pub const PreimageByteDeposit: Balance = 0; } construct_runtime! { pub enum Runtime { System: frame_system, Balances: pallet_balances, - Preimage: pallet_preimage, Futarchy: zrml_futarchy, } } @@ -91,15 +86,6 @@ impl pallet_balances::Config for Runtime { type WeightInfo = (); } -impl pallet_preimage::Config for Runtime { - type WeightInfo = (); - type RuntimeEvent = RuntimeEvent; - type Currency = Balances; - type ManagerOrigin = EnsureRoot<::AccountId>; - type BaseDeposit = PreimageBaseDeposit; - type ByteDeposit = PreimageByteDeposit; -} - impl zrml_futarchy::Config for Runtime { #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = MockBenchmarkHelper; diff --git a/zrml/futarchy/src/mock/types/mod.rs b/zrml/futarchy/src/mock/types/mod.rs index cca6c5103..104e8b8ef 100644 --- a/zrml/futarchy/src/mock/types/mod.rs +++ b/zrml/futarchy/src/mock/types/mod.rs @@ -15,10 +15,12 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . +#[cfg(feature = "runtime-benchmarks")] mod benchmark_helper; mod oracle; mod scheduler; +#[cfg(feature = "runtime-benchmarks")] pub use benchmark_helper::MockBenchmarkHelper; pub(crate) use oracle::MockOracle; pub(crate) use scheduler::MockScheduler; diff --git a/zrml/futarchy/src/mock/utility.rs b/zrml/futarchy/src/mock/utility.rs index d5c2b2ffb..75f501821 100644 --- a/zrml/futarchy/src/mock/utility.rs +++ b/zrml/futarchy/src/mock/utility.rs @@ -15,7 +15,7 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . -use crate::mock::runtime::{Balances, Futarchy, Preimage, System}; +use crate::mock::runtime::{Balances, Futarchy, System}; use frame_support::traits::Hooks; use zeitgeist_primitives::types::BlockNumber; @@ -24,7 +24,6 @@ pub fn run_to_block(to: BlockNumber) { let now = System::block_number(); Futarchy::on_finalize(now); - Preimage::on_finalize(now); Balances::on_finalize(now); System::on_finalize(now); @@ -33,7 +32,6 @@ pub fn run_to_block(to: BlockNumber) { System::on_initialize(next); Balances::on_initialize(next); - Preimage::on_initialize(next); Futarchy::on_initialize(next); } } diff --git a/zrml/hybrid-router/src/mock.rs b/zrml/hybrid-router/src/mock.rs index 1a7a454ad..685a84a9d 100644 --- a/zrml/hybrid-router/src/mock.rs +++ b/zrml/hybrid-router/src/mock.rs @@ -304,7 +304,7 @@ impl frame_system::Config for Runtime { type Hashing = BlakeTwo256; type Lookup = IdentityLookup; type Nonce = u64; - type MaxConsumers = frame_support::traits::ConstU32<16>; + type MaxConsumers = ConstU32<16>; type OnKilledAccount = (); type OnNewAccount = (); type RuntimeOrigin = RuntimeOrigin; diff --git a/zrml/neo-swaps/src/tests/combo_buy.rs b/zrml/neo-swaps/src/tests/combo_buy.rs index 39b3e772a..461992db8 100644 --- a/zrml/neo-swaps/src/tests/combo_buy.rs +++ b/zrml/neo-swaps/src/tests/combo_buy.rs @@ -16,8 +16,6 @@ // along with Zeitgeist. If not, see . use super::*; -#[cfg(not(feature = "parachain"))] -use sp_runtime::{DispatchError, TokenError}; use test_case::test_case; use zeitgeist_primitives::types::Asset::CategoricalOutcome; From dd58dc6f1d33d44fe99fca139b0d65651f43036f Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Tue, 22 Oct 2024 15:15:01 +0200 Subject: [PATCH 38/73] Introduce `PoolId` (#1384) --- runtime/common/src/lib.rs | 1 + zrml/hybrid-router/src/mock.rs | 1 + zrml/neo-swaps/src/lib.rs | 21 +++++++++++++++++---- zrml/neo-swaps/src/mock.rs | 3 ++- 4 files changed, 21 insertions(+), 5 deletions(-) diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs index d1b87aa09..bb59feaa4 100644 --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -1296,6 +1296,7 @@ macro_rules! impl_config_traits { type ExternalFees = MarketCreatorFee; type MarketCommons = MarketCommons; type MultiCurrency = AssetManager; + type PoolId = MarketId; type RuntimeEvent = RuntimeEvent; type WeightInfo = zrml_neo_swaps::weights::WeightInfo; type MaxLiquidityTreeDepth = MaxLiquidityTreeDepth; diff --git a/zrml/hybrid-router/src/mock.rs b/zrml/hybrid-router/src/mock.rs index 685a84a9d..fc4ff4f7e 100644 --- a/zrml/hybrid-router/src/mock.rs +++ b/zrml/hybrid-router/src/mock.rs @@ -197,6 +197,7 @@ impl zrml_neo_swaps::Config for Runtime { type ExternalFees = ExternalFees; type MarketCommons = MarketCommons; type RuntimeEvent = RuntimeEvent; + type PoolId = MarketId; type MaxLiquidityTreeDepth = MaxLiquidityTreeDepth; type MaxSwapFee = NeoMaxSwapFee; type PalletId = NeoSwapsPalletId; diff --git a/zrml/neo-swaps/src/lib.rs b/zrml/neo-swaps/src/lib.rs index aa000f960..c5cc23c37 100644 --- a/zrml/neo-swaps/src/lib.rs +++ b/zrml/neo-swaps/src/lib.rs @@ -58,17 +58,20 @@ mod pallet { pallet_prelude::StorageMap, require_transactional, traits::{Get, IsType, StorageVersion}, - transactional, PalletError, PalletId, Twox64Concat, + transactional, PalletError, PalletId, Parameter, Twox64Concat, }; use frame_system::{ ensure_signed, pallet_prelude::{BlockNumberFor, OriginFor}, }; use orml_traits::MultiCurrency; - use parity_scale_codec::{Decode, Encode}; + use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; use sp_runtime::{ - traits::{AccountIdConversion, CheckedSub, Saturating, Zero}, + traits::{ + AccountIdConversion, AtLeast32Bit, CheckedSub, MaybeSerializeDeserialize, Member, + Saturating, Zero, + }, DispatchError, DispatchResult, Perbill, RuntimeDebug, SaturatedConversion, }; use zeitgeist_primitives::{ @@ -113,6 +116,7 @@ mod pallet { <::MarketCommons as MarketCommonsPalletApi>::MarketId; pub(crate) type LiquidityTreeOf = LiquidityTree::MaxLiquidityTreeDepth>; pub(crate) type PoolOf = Pool, MaxAssets>; + pub(crate) type PoolIdOf = ::PoolId; pub(crate) type AmmTradeOf = AmmTrade>; #[pallet::config] @@ -136,10 +140,19 @@ mod pallet { AccountId = Self::AccountId, BlockNumber = BlockNumberFor, Balance = BalanceOf, + MarketId = Self::PoolId, >; type MultiCurrency: MultiCurrency>; + type PoolId: AtLeast32Bit + + Copy + + Default + + MaxEncodedLen + + MaybeSerializeDeserialize + + Member + + Parameter; + type RuntimeEvent: From> + IsType<::RuntimeEvent>; type WeightInfo: WeightInfoZeitgeist; @@ -161,7 +174,7 @@ mod pallet { pub struct Pallet(PhantomData); #[pallet::storage] - pub(crate) type Pools = StorageMap<_, Twox64Concat, MarketIdOf, PoolOf>; + pub(crate) type Pools = StorageMap<_, Twox64Concat, PoolIdOf, PoolOf>; #[pallet::event] #[pallet::generate_deposit(fn deposit_event)] diff --git a/zrml/neo-swaps/src/mock.rs b/zrml/neo-swaps/src/mock.rs index 05d17fdcf..e6a23d0e2 100644 --- a/zrml/neo-swaps/src/mock.rs +++ b/zrml/neo-swaps/src/mock.rs @@ -181,10 +181,11 @@ construct_runtime!( ); impl crate::Config for Runtime { - type MultiCurrency = AssetManager; type CompleteSetOperations = PredictionMarkets; type ExternalFees = ExternalFees; type MarketCommons = MarketCommons; + type MultiCurrency = AssetManager; + type PoolId = MarketId; type RuntimeEvent = RuntimeEvent; type MaxLiquidityTreeDepth = MaxLiquidityTreeDepth; type MaxSwapFee = NeoMaxSwapFee; From f57bf6b6fe4d788378ccc440cec98732945e8ded Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Tue, 22 Oct 2024 22:57:05 +0200 Subject: [PATCH 39/73] Implement `redeem_position` (#1385) * Add `PayoutApi` and the corresponding mock * Implement redeeming tokens * Add tests for `redeem_position` * Test `redeem_position` and dummy implement `Payout` --- primitives/src/traits.rs | 2 + primitives/src/traits/payout_api.rs | 25 +++ runtime/common/src/lib.rs | 3 +- zrml/combinatorial-tokens/src/lib.rs | 107 +++++++++++-- zrml/combinatorial-tokens/src/mock/mod.rs | 1 + zrml/combinatorial-tokens/src/mock/runtime.rs | 5 +- .../src/mock/types/mod.rs | 3 + .../src/mock/types/payout.rs | 47 ++++++ zrml/combinatorial-tokens/src/tests/mod.rs | 2 + .../src/tests/redeem_position.rs | 150 ++++++++++++++++++ zrml/prediction-markets/src/lib.rs | 107 ++++++++++++- 11 files changed, 434 insertions(+), 18 deletions(-) create mode 100644 primitives/src/traits/payout_api.rs create mode 100644 zrml/combinatorial-tokens/src/mock/types/mod.rs create mode 100644 zrml/combinatorial-tokens/src/mock/types/payout.rs create mode 100644 zrml/combinatorial-tokens/src/tests/redeem_position.rs diff --git a/primitives/src/traits.rs b/primitives/src/traits.rs index 33371924c..4ed5639d6 100644 --- a/primitives/src/traits.rs +++ b/primitives/src/traits.rs @@ -27,6 +27,7 @@ mod hybrid_router_orderbook_api; mod market_builder; mod market_commons_pallet_api; mod market_id; +mod payout_api; mod swaps; mod zeitgeist_asset; @@ -41,5 +42,6 @@ pub use hybrid_router_orderbook_api::*; pub use market_builder::*; pub use market_commons_pallet_api::*; pub use market_id::*; +pub use payout_api::*; pub use swaps::*; pub use zeitgeist_asset::*; diff --git a/primitives/src/traits/payout_api.rs b/primitives/src/traits/payout_api.rs new file mode 100644 index 000000000..675ef411f --- /dev/null +++ b/primitives/src/traits/payout_api.rs @@ -0,0 +1,25 @@ +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use alloc::vec::Vec; + +pub trait PayoutApi { + type Balance; + type MarketId; + + fn payout_vector(market_id: Self::MarketId) -> Option>; +} diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs index bb59feaa4..05b85a417 100644 --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -1157,8 +1157,9 @@ macro_rules! impl_config_traits { type CombinatorialIdManager = CryptographicIdManager; type MarketCommons = MarketCommons; type MultiCurrency = AssetManager; - type PalletId = CombinatorialTokensPalletId; + type Payout = PredictionMarkets; type RuntimeEvent = RuntimeEvent; + type PalletId = CombinatorialTokensPalletId; } impl zrml_court::Config for Runtime { diff --git a/zrml/combinatorial-tokens/src/lib.rs b/zrml/combinatorial-tokens/src/lib.rs index e4c48c96d..47d348e54 100644 --- a/zrml/combinatorial-tokens/src/lib.rs +++ b/zrml/combinatorial-tokens/src/lib.rs @@ -22,8 +22,6 @@ // , // and has been relicensed under GPL-3.0-or-later in this repository. -// TODO Refactor so that collection IDs are their own type with an `Fq` field and an `odd` field? - #![doc = include_str!("../README.md")] #![cfg_attr(not(feature = "std"), no_std)] @@ -52,11 +50,15 @@ mod pallet { }; use orml_traits::MultiCurrency; use sp_runtime::{ - traits::{AccountIdConversion, Get}, + traits::{AccountIdConversion, Get, Zero}, DispatchError, DispatchResult, }; use zeitgeist_primitives::{ - traits::MarketCommonsPalletApi, + math::{ + checked_ops_res::{CheckedAddRes}, + fixed::FixedMul, + }, + traits::{MarketCommonsPalletApi, PayoutApi}, types::{Asset, CombinatorialId}, }; @@ -72,10 +74,12 @@ mod pallet { type MultiCurrency: MultiCurrency>; - #[pallet::constant] - type PalletId: Get; + type Payout: PayoutApi, MarketId = MarketIdOf>; type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + #[pallet::constant] + type PalletId: Get; } #[pallet::pallet] @@ -127,12 +131,27 @@ mod pallet { #[pallet::error] pub enum Error { - /// The specified partition is empty, contains overlaps, is too long or doesn't match the + /// Specified index set is trival, empty, or doesn't match the market's number of outcomes. + InvalidIndexSet, + + /// Specified partition is empty, contains overlaps, is too long or doesn't match the /// market's number of outcomes. InvalidPartition, - /// The specified collection ID is invalid. + /// Specified collection ID is invalid. InvalidCollectionId, + + /// Specified market is not resolved. + PayoutVectorNotFound, + + /// Account holds no tokens of this type. + NoTokensFound, + + /// Specified token holds no redeemable value. + TokenHasNoValue, + + /// Something unexpected happened. You shouldn't see this. + UnexpectedError, } #[pallet::call] @@ -165,6 +184,19 @@ mod pallet { let who = ensure_signed(origin)?; Self::do_merge_position(who, parent_collection_id, market_id, partition, amount) } + + #[pallet::call_index(2)] + #[pallet::weight({0})] // TODO + #[transactional] + pub fn redeem_position( + origin: OriginFor, + parent_collection_id: Option>, + market_id: MarketIdOf, + index_set: Vec, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + Self::do_redeem_position(who, parent_collection_id, market_id, index_set) + } } impl Pallet { @@ -191,7 +223,7 @@ mod pallet { let position = Asset::CombinatorialToken(position_id); // This will fail if the market has a different collateral than the previous - // markets. TODO A cleaner error message would be nice though... + // markets. FIXME A cleaner error message would be nice though... T::MultiCurrency::ensure_can_withdraw(position, &who, amount)?; T::MultiCurrency::withdraw(position, &who, amount)?; @@ -301,11 +333,6 @@ mod pallet { } else { // Merge first-level tokens into collateral. Move collateral from the pallet // account to the user's wallet. This is the legacy `sell_complete_set`. - T::MultiCurrency::ensure_can_withdraw( - collateral_token, - &Self::account_id(), - amount, - )?; // Required because `transfer` throws `Underflow` errors sometimes. T::MultiCurrency::transfer( collateral_token, &Self::account_id(), @@ -338,6 +365,58 @@ mod pallet { Ok(()) } + fn do_redeem_position( + who: T::AccountId, + parent_collection_id: Option>, + market_id: MarketIdOf, + index_set: Vec, + ) -> DispatchResult { + let payout_vector = + T::Payout::payout_vector(market_id).ok_or(Error::::PayoutVectorNotFound)?; + + let market = T::MarketCommons::market(&market_id)?; + let asset_count = market.outcomes() as usize; + let collateral_token = market.base_asset; + + ensure!(index_set.len() == asset_count, Error::::InvalidIndexSet); + ensure!(index_set.iter().any(|&b| b), Error::::InvalidIndexSet); + ensure!(!index_set.iter().all(|&b| b), Error::::InvalidIndexSet); + + // Add up values of each outcome. + let mut total_stake: BalanceOf = Zero::zero(); + for (&index, value) in index_set.iter().zip(payout_vector.iter()) { + if index { + total_stake = total_stake.checked_add_res(value)?; + } + } + + ensure!(!total_stake.is_zero(), Error::::TokenHasNoValue); + + let position = + Self::position_from_parent_collection(parent_collection_id, market_id, index_set)?; + let amount = T::MultiCurrency::free_balance(position, &who); + ensure!(!amount.is_zero(), Error::::NoTokensFound); + T::MultiCurrency::withdraw(position, &who, amount)?; + + let total_payout = total_stake.bmul(amount)?; + + if let Some(pci) = parent_collection_id { + // Merge combinatorial token into higher level position. Destroy the tokens. + let position_id = T::CombinatorialIdManager::get_position_id(collateral_token, pci); + let position = Asset::CombinatorialToken(position_id); + T::MultiCurrency::deposit(position, &who, total_payout)?; + } else { + T::MultiCurrency::transfer( + collateral_token, + &Self::account_id(), + &who, + total_payout, + )?; + } + + Ok(()) + } + pub(crate) fn account_id() -> T::AccountId { T::PalletId::get().into_account_truncating() } diff --git a/zrml/combinatorial-tokens/src/mock/mod.rs b/zrml/combinatorial-tokens/src/mock/mod.rs index 8700d164d..8d9831fb3 100644 --- a/zrml/combinatorial-tokens/src/mock/mod.rs +++ b/zrml/combinatorial-tokens/src/mock/mod.rs @@ -19,4 +19,5 @@ pub(crate) mod consts; pub mod ext_builder; +pub(crate) mod types; pub(crate) mod runtime; diff --git a/zrml/combinatorial-tokens/src/mock/runtime.rs b/zrml/combinatorial-tokens/src/mock/runtime.rs index 08e4a00cc..157841ca7 100644 --- a/zrml/combinatorial-tokens/src/mock/runtime.rs +++ b/zrml/combinatorial-tokens/src/mock/runtime.rs @@ -16,7 +16,7 @@ // along with Zeitgeist. If not, see . use crate as zrml_combinatorial_tokens; -use crate::types::CryptographicIdManager; +use crate::{mock::types::MockPayout, types::CryptographicIdManager}; use frame_support::{construct_runtime, traits::Everything, Blake2_256}; use frame_system::mocking::MockBlock; use sp_runtime::traits::{BlakeTwo256, ConstU32, IdentityLookup}; @@ -46,8 +46,9 @@ impl zrml_combinatorial_tokens::Config for Runtime { type CombinatorialIdManager = CryptographicIdManager; type MarketCommons = MarketCommons; type MultiCurrency = Currencies; - type PalletId = CombinatorialTokensPalletId; + type Payout = MockPayout; type RuntimeEvent = RuntimeEvent; + type PalletId = CombinatorialTokensPalletId; } impl orml_currencies::Config for Runtime { diff --git a/zrml/combinatorial-tokens/src/mock/types/mod.rs b/zrml/combinatorial-tokens/src/mock/types/mod.rs new file mode 100644 index 000000000..f1234e807 --- /dev/null +++ b/zrml/combinatorial-tokens/src/mock/types/mod.rs @@ -0,0 +1,3 @@ +mod payout; + +pub use payout::MockPayout; diff --git a/zrml/combinatorial-tokens/src/mock/types/payout.rs b/zrml/combinatorial-tokens/src/mock/types/payout.rs new file mode 100644 index 000000000..407a0a6e1 --- /dev/null +++ b/zrml/combinatorial-tokens/src/mock/types/payout.rs @@ -0,0 +1,47 @@ +use alloc::vec; +use core::cell::RefCell; +use zeitgeist_primitives::{ + traits::PayoutApi, + types::{Balance, MarketId}, +}; + +pub struct MockPayout; + +impl MockPayout { + pub fn set_return_value(value: Option>) { + PAYOUT_VECTOR_RETURN_VALUE.with(|v| *v.borrow_mut() = Some(value)); + } + + pub fn not_called() -> bool { + PAYOUT_VECTOR_CALL_DATA.with(|values| values.borrow().is_empty()) + } + + pub fn called_once_with(expected: MarketId) -> bool { + if PAYOUT_VECTOR_CALL_DATA.with(|values| values.borrow().len()) != 1 { + return false; + } + + let actual = + PAYOUT_VECTOR_CALL_DATA.with(|value| *value.borrow().first().expect("can't be empty")); + + actual == expected + } +} + +impl PayoutApi for MockPayout { + type Balance = Balance; + type MarketId = MarketId; + + fn payout_vector(market_id: Self::MarketId) -> Option> { + PAYOUT_VECTOR_CALL_DATA.with(|values| values.borrow_mut().push(market_id)); + + PAYOUT_VECTOR_RETURN_VALUE + .with(|value| value.borrow().clone()) + .expect("MockPayout: No return value configured") + } +} + +thread_local! { + pub static PAYOUT_VECTOR_CALL_DATA: RefCell> = const { RefCell::new(vec![]) }; + pub static PAYOUT_VECTOR_RETURN_VALUE: RefCell>>> = const { RefCell::new(None) }; +} diff --git a/zrml/combinatorial-tokens/src/tests/mod.rs b/zrml/combinatorial-tokens/src/tests/mod.rs index d5a989daf..ebcb3d751 100644 --- a/zrml/combinatorial-tokens/src/tests/mod.rs +++ b/zrml/combinatorial-tokens/src/tests/mod.rs @@ -19,12 +19,14 @@ mod integration; mod merge_position; +mod redeem_position; mod split_position; use crate::{ mock::{ ext_builder::ExtBuilder, runtime::{CombinatorialTokens, Currencies, MarketCommons, Runtime, RuntimeOrigin, System}, + types::MockPayout, }, Error, Event, Pallet, }; diff --git a/zrml/combinatorial-tokens/src/tests/redeem_position.rs b/zrml/combinatorial-tokens/src/tests/redeem_position.rs new file mode 100644 index 000000000..a58381206 --- /dev/null +++ b/zrml/combinatorial-tokens/src/tests/redeem_position.rs @@ -0,0 +1,150 @@ +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use super::*; +use test_case::test_case; + +#[test] +fn redeem_position_fails_on_no_payout_vector() { + ExtBuilder::build().execute_with(|| { + let alice = Account::new(0).deposit(Asset::Ztg, _100).unwrap(); + MockPayout::set_return_value(None); + assert_noop!( + CombinatorialTokens::redeem_position(alice.signed(), None, 0, vec![]), + Error::::PayoutVectorNotFound + ); + }); +} + +#[test] +fn redeem_position_fails_on_market_not_found() { + ExtBuilder::build().execute_with(|| { + let alice = Account::new(0).deposit(Asset::Ztg, _100).unwrap(); + MockPayout::set_return_value(Some(vec![_1_2, _1_2])); + assert_noop!( + CombinatorialTokens::redeem_position(alice.signed(), None, 0, vec![]), + zrml_market_commons::Error::::MarketDoesNotExist + ); + }); +} + +#[test_case(vec![B0, B1, B0, B1]; "incorrect_len")] +#[test_case(vec![B0, B0, B0]; "all_zero")] +#[test_case(vec![B0, B0, B0]; "all_one")] +fn redeem_position_fails_on_incorrect_index_set(index_set: Vec) { + ExtBuilder::build().execute_with(|| { + let alice = Account::new(0).deposit(Asset::Ztg, _100).unwrap(); + MockPayout::set_return_value(Some(vec![_1_3, _1_3, _1_3])); + let market_id = create_market(Asset::Ztg, MarketType::Categorical(3)); + assert_noop!( + CombinatorialTokens::redeem_position(alice.signed(), None, market_id, index_set), + Error::::InvalidIndexSet + ); + }); +} + +#[test] +fn redeem_position_fails_if_tokens_have_to_value() { + ExtBuilder::build().execute_with(|| { + let alice = Account::new(0).deposit(Asset::Ztg, _100).unwrap(); + MockPayout::set_return_value(Some(vec![0, _1_2, _1_2, 0])); + let market_id = create_market(Asset::Ztg, MarketType::Categorical(4)); + let index_set = vec![B1, B0, B0, B1]; + assert_noop!( + CombinatorialTokens::redeem_position(alice.signed(), None, market_id, index_set), + Error::::TokenHasNoValue + ); + }); +} + +#[test] +fn redeem_position_fails_if_user_holds_no_winning_tokens() { + ExtBuilder::build().execute_with(|| { + let alice = Account::new(0).deposit(Asset::Ztg, _100).unwrap(); + MockPayout::set_return_value(Some(vec![0, _1_2, _1_2, 0])); + let market_id = create_market(Asset::Ztg, MarketType::Categorical(4)); + let index_set = vec![B0, B1, B0, B1]; + assert_noop!( + CombinatorialTokens::redeem_position(alice.signed(), None, market_id, index_set), + Error::::NoTokensFound, + ); + }); +} + +#[test] +fn redeem_position_works_sans_parent() { + ExtBuilder::build().execute_with(|| { + let ct_110 = CombinatorialToken([ + 101, 210, 61, 196, 5, 247, 150, 41, 186, 49, 11, 63, 139, 53, 25, 65, 161, 83, 24, 142, + 225, 102, 57, 241, 199, 18, 226, 137, 68, 3, 219, 131, + ]); + let alice = Account::new(0).deposit(ct_110, _3).unwrap(); + let pallet = Account::new(Pallet::::account_id()).deposit(Asset::Ztg, _3).unwrap(); + + MockPayout::set_return_value(Some(vec![_1_4, _1_2, _1_4])); + + let market_id = create_market(Asset::Ztg, MarketType::Categorical(3)); + let index_set = vec![B1, B1, B0]; + assert_ok!(CombinatorialTokens::redeem_position( + alice.signed(), + None, + market_id, + index_set + )); + + assert_eq!(alice.free_balance(ct_110), 0); + assert_eq!(alice.free_balance(Asset::Ztg), _2 + _1_4); + assert_eq!(pallet.free_balance(Asset::Ztg), _3_4); + }); +} + +#[test] +fn redeem_position_works_with_parent() { + ExtBuilder::build().execute_with(|| { + let ct_001 = CombinatorialToken([ + 207, 168, 160, 93, 238, 221, 197, 1, 171, 102, 28, 24, 18, 107, 205, 231, 227, 98, 220, + 105, 211, 29, 181, 30, 53, 7, 200, 154, 134, 246, 38, 139, + ]); + let ct_001_0101 = CombinatorialToken([ + 38, 14, 141, 152, 199, 40, 88, 165, 208, 236, 195, 198, 208, 75, 93, 85, 114, 4, 175, + 225, 211, 72, 142, 210, 98, 202, 168, 193, 245, 217, 239, 28, + ]); + + let alice = Account::new(0).deposit(ct_001_0101, _7).unwrap(); + + MockPayout::set_return_value(Some(vec![_1_4, 0, _1_2, _1_4])); + + let _ = create_market(Asset::Ztg, MarketType::Categorical(3)); + let child_market_id = create_market(Asset::Ztg, MarketType::Categorical(4)); + + // Collection ID of [0, 0, 1]. + let parent_collection_id = [ + 6, 44, 173, 50, 122, 106, 144, 185, 253, 19, 252, 218, 215, 241, 218, 37, 196, 112, 45, + 133, 165, 48, 231, 189, 87, 123, 131, 18, 190, 5, 110, 93, + ]; + let index_set = vec![B0, B1, B0, B1]; + assert_ok!(CombinatorialTokens::redeem_position( + alice.signed(), + Some(parent_collection_id), + child_market_id, + index_set + )); + + assert_eq!(alice.free_balance(ct_001_0101), 0); + assert_eq!(alice.free_balance(ct_001), _1 + _3_4); + }); +} diff --git a/zrml/prediction-markets/src/lib.rs b/zrml/prediction-markets/src/lib.rs index 6a07968e3..1e5e4ff2a 100644 --- a/zrml/prediction-markets/src/lib.rs +++ b/zrml/prediction-markets/src/lib.rs @@ -67,7 +67,7 @@ mod pallet { constants::MILLISECS_PER_BLOCK, traits::{ CompleteSetOperationsApi, DeployPoolApi, DisputeApi, DisputeMaxWeightApi, - DisputeResolutionApi, MarketBuilderTrait, + DisputeResolutionApi, MarketBuilderTrait, PayoutApi, }, types::{ Asset, Bond, Deadlines, EarlyClose, EarlyCloseState, GlobalDisputeItem, Market, @@ -3052,4 +3052,109 @@ mod pallet { Self::do_sell_complete_set(who, market_id, amount) } } + + impl PayoutApi for Pallet where T: Config { + type Balance = BalanceOf; + type MarketId = MarketIdOf; + + fn payout_vector(_market_id: Self::MarketId) -> Option> { + None + // // TODO Abstract into separate function so we don't have to litter this with ok() calls. + // let market = >::market(&market_id).ok()?; + // let market_account = Self::market_account(market_id); + + // ensure!(market.status == MarketStatus::Resolved, Error::::MarketIsNotResolved); + // ensure!(market.is_redeemable(), Error::::InvalidResolutionMechanism); + + // let winning_assets = match resolved_outcome { + // OutcomeReport::Categorical(category_index) => { + // vec![(winning_currency_id, ZeitgeistBase::get(), ZeitgeistBase::get())], + // } + // OutcomeReport::Scalar(value) => { + // let long_currency_id = Asset::ScalarOutcome(market_id, ScalarPosition::Long); + // let short_currency_id = Asset::ScalarOutcome(market_id, ScalarPosition::Short); + + // let bound = if let MarketType::Scalar(range) = market.market_type { + // range + // } else { + // return None; + // }; + + // let calc_payouts = |final_value: u128, + // low: u128, + // high: u128| + // -> (Perbill, Perbill) { + // if final_value <= low { + // return (Perbill::zero(), Perbill::one()); + // } + // if final_value >= high { + // return (Perbill::one(), Perbill::zero()); + // } + + // let payout_long: Perbill = Perbill::from_rational( + // final_value.saturating_sub(low), + // high.saturating_sub(low), + // ); + // let payout_short: Perbill = Perbill::from_parts( + // Perbill::one().deconstruct().saturating_sub(payout_long.deconstruct()), + // ); + // (payout_long, payout_short) + // }; + + // let (long_percent, short_percent) = + // calc_payouts(value, *bound.start(), *bound.end()); + + // let long_payout = long_percent.mul_floor(long_balance); + // let short_payout = short_percent.mul_floor(short_balance); + // // Ensure the market account has enough to pay out - if this is + // // ever not true then we have an accounting problem. + // ensure!( + // T::AssetManager::free_balance(market.base_asset, &market_account) + // >= long_payout.saturating_add(short_payout), + // Error::::InsufficientFundsInMarketAccount, + // ); + + // vec![ + // (long_currency_id, long_payout, long_balance), + // (short_currency_id, short_payout, short_balance), + // ] + // } + // }; + + // for (currency_id, payout, balance) in winning_assets { + // // Destroy the shares. + // let missing = T::AssetManager::slash(currency_id, &sender, balance); + // debug_assert!( + // missing.is_zero(), + // "Could not slash all of the amount. currency_id {:?}, sender: {:?}, balance: \ + // {:?}.", + // currency_id, + // &sender, + // balance, + // ); + + // // Pay out the winner. + // let remaining_bal = + // T::AssetManager::free_balance(market.base_asset, &market_account); + // let actual_payout = payout.min(remaining_bal); + + // T::AssetManager::transfer( + // market.base_asset, + // &market_account, + // &sender, + // actual_payout, + // )?; + // // The if-check prevents scalar markets to emit events even if sender only owns one + // // of the outcome tokens. + // if balance != BalanceOf::::zero() { + // Self::deposit_event(Event::TokensRedeemed( + // market_id, + // currency_id, + // balance, + // actual_payout, + // sender.clone(), + // )); + // } + } + } } From 3bda7450c5ff08c625566cf776eb49df69ac0d17 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Wed, 23 Oct 2024 12:01:27 +0200 Subject: [PATCH 40/73] Implement `Payout` for `PredictionMarkets` (#1386) * Fix copyright and formatting * Implement `payout_vector` and test * Fix copyright --- zrml/combinatorial-tokens/src/lib.rs | 5 +- zrml/combinatorial-tokens/src/mock/mod.rs | 2 +- .../src/mock/types/mod.rs | 17 ++ .../src/mock/types/payout.rs | 17 ++ zrml/prediction-markets/src/lib.rs | 145 ++++++------------ zrml/prediction-markets/src/tests/mod.rs | 1 + .../src/tests/payout_vector.rs | 131 ++++++++++++++++ 7 files changed, 213 insertions(+), 105 deletions(-) create mode 100644 zrml/prediction-markets/src/tests/payout_vector.rs diff --git a/zrml/combinatorial-tokens/src/lib.rs b/zrml/combinatorial-tokens/src/lib.rs index 47d348e54..6f4a34c9c 100644 --- a/zrml/combinatorial-tokens/src/lib.rs +++ b/zrml/combinatorial-tokens/src/lib.rs @@ -54,10 +54,7 @@ mod pallet { DispatchError, DispatchResult, }; use zeitgeist_primitives::{ - math::{ - checked_ops_res::{CheckedAddRes}, - fixed::FixedMul, - }, + math::{checked_ops_res::CheckedAddRes, fixed::FixedMul}, traits::{MarketCommonsPalletApi, PayoutApi}, types::{Asset, CombinatorialId}, }; diff --git a/zrml/combinatorial-tokens/src/mock/mod.rs b/zrml/combinatorial-tokens/src/mock/mod.rs index 8d9831fb3..f2933dea7 100644 --- a/zrml/combinatorial-tokens/src/mock/mod.rs +++ b/zrml/combinatorial-tokens/src/mock/mod.rs @@ -19,5 +19,5 @@ pub(crate) mod consts; pub mod ext_builder; -pub(crate) mod types; pub(crate) mod runtime; +pub(crate) mod types; diff --git a/zrml/combinatorial-tokens/src/mock/types/mod.rs b/zrml/combinatorial-tokens/src/mock/types/mod.rs index f1234e807..03136bcda 100644 --- a/zrml/combinatorial-tokens/src/mock/types/mod.rs +++ b/zrml/combinatorial-tokens/src/mock/types/mod.rs @@ -1,3 +1,20 @@ +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + mod payout; pub use payout::MockPayout; diff --git a/zrml/combinatorial-tokens/src/mock/types/payout.rs b/zrml/combinatorial-tokens/src/mock/types/payout.rs index 407a0a6e1..f3fbd8d2a 100644 --- a/zrml/combinatorial-tokens/src/mock/types/payout.rs +++ b/zrml/combinatorial-tokens/src/mock/types/payout.rs @@ -1,3 +1,20 @@ +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + use alloc::vec; use core::cell::RefCell; use zeitgeist_primitives::{ diff --git a/zrml/prediction-markets/src/lib.rs b/zrml/prediction-markets/src/lib.rs index 1e5e4ff2a..abaf617df 100644 --- a/zrml/prediction-markets/src/lib.rs +++ b/zrml/prediction-markets/src/lib.rs @@ -60,11 +60,12 @@ mod pallet { use orml_traits::{MultiCurrency, NamedMultiReservableCurrency}; use sp_arithmetic::per_things::{Perbill, Percent}; use sp_runtime::{ - traits::{Saturating, Zero}, + traits::{CheckedSub, Saturating, Zero}, DispatchError, DispatchResult, SaturatedConversion, }; use zeitgeist_primitives::{ constants::MILLISECS_PER_BLOCK, + math::fixed::{BaseProvider, FixedDiv, ZeitgeistBase}, traits::{ CompleteSetOperationsApi, DeployPoolApi, DisputeApi, DisputeMaxWeightApi, DisputeResolutionApi, MarketBuilderTrait, PayoutApi, @@ -3053,108 +3054,52 @@ mod pallet { } } - impl PayoutApi for Pallet where T: Config { + impl PayoutApi for Pallet + where + T: Config, + { type Balance = BalanceOf; type MarketId = MarketIdOf; - fn payout_vector(_market_id: Self::MarketId) -> Option> { - None - // // TODO Abstract into separate function so we don't have to litter this with ok() calls. - // let market = >::market(&market_id).ok()?; - // let market_account = Self::market_account(market_id); - - // ensure!(market.status == MarketStatus::Resolved, Error::::MarketIsNotResolved); - // ensure!(market.is_redeemable(), Error::::InvalidResolutionMechanism); - - // let winning_assets = match resolved_outcome { - // OutcomeReport::Categorical(category_index) => { - // vec![(winning_currency_id, ZeitgeistBase::get(), ZeitgeistBase::get())], - // } - // OutcomeReport::Scalar(value) => { - // let long_currency_id = Asset::ScalarOutcome(market_id, ScalarPosition::Long); - // let short_currency_id = Asset::ScalarOutcome(market_id, ScalarPosition::Short); - - // let bound = if let MarketType::Scalar(range) = market.market_type { - // range - // } else { - // return None; - // }; - - // let calc_payouts = |final_value: u128, - // low: u128, - // high: u128| - // -> (Perbill, Perbill) { - // if final_value <= low { - // return (Perbill::zero(), Perbill::one()); - // } - // if final_value >= high { - // return (Perbill::one(), Perbill::zero()); - // } - - // let payout_long: Perbill = Perbill::from_rational( - // final_value.saturating_sub(low), - // high.saturating_sub(low), - // ); - // let payout_short: Perbill = Perbill::from_parts( - // Perbill::one().deconstruct().saturating_sub(payout_long.deconstruct()), - // ); - // (payout_long, payout_short) - // }; - - // let (long_percent, short_percent) = - // calc_payouts(value, *bound.start(), *bound.end()); - - // let long_payout = long_percent.mul_floor(long_balance); - // let short_payout = short_percent.mul_floor(short_balance); - // // Ensure the market account has enough to pay out - if this is - // // ever not true then we have an accounting problem. - // ensure!( - // T::AssetManager::free_balance(market.base_asset, &market_account) - // >= long_payout.saturating_add(short_payout), - // Error::::InsufficientFundsInMarketAccount, - // ); - - // vec![ - // (long_currency_id, long_payout, long_balance), - // (short_currency_id, short_payout, short_balance), - // ] - // } - // }; - - // for (currency_id, payout, balance) in winning_assets { - // // Destroy the shares. - // let missing = T::AssetManager::slash(currency_id, &sender, balance); - // debug_assert!( - // missing.is_zero(), - // "Could not slash all of the amount. currency_id {:?}, sender: {:?}, balance: \ - // {:?}.", - // currency_id, - // &sender, - // balance, - // ); - - // // Pay out the winner. - // let remaining_bal = - // T::AssetManager::free_balance(market.base_asset, &market_account); - // let actual_payout = payout.min(remaining_bal); - - // T::AssetManager::transfer( - // market.base_asset, - // &market_account, - // &sender, - // actual_payout, - // )?; - // // The if-check prevents scalar markets to emit events even if sender only owns one - // // of the outcome tokens. - // if balance != BalanceOf::::zero() { - // Self::deposit_event(Event::TokensRedeemed( - // market_id, - // currency_id, - // balance, - // actual_payout, - // sender.clone(), - // )); - // } + fn payout_vector(market_id: Self::MarketId) -> Option> { + // TODO Abstract into separate function so we don't have to litter this with ok() calls. + let market = >::market(&market_id).ok()?; + + if market.status != MarketStatus::Resolved || !market.is_redeemable() { + return None; + } + let resolved_outcome = market.resolved_outcome.clone()?; + + let result = match resolved_outcome { + OutcomeReport::Categorical(category_index) => { + let mut result = vec![Zero::zero(); market.outcomes() as usize]; + *result.get_mut(category_index as usize)? = ZeitgeistBase::get().ok()?; + + result + } + OutcomeReport::Scalar(value) => { + let MarketType::Scalar(range) = market.market_type else { + return None; + }; + let low = *range.start(); + let high = *range.end(); + + let low_bal: BalanceOf = low.saturated_into(); + let high_bal: BalanceOf = high.saturated_into(); + let value_bal: BalanceOf = value.saturated_into(); + + let value_clamped = value_bal.max(low_bal).min(high_bal); + let nominator = value_clamped.checked_sub(&low_bal)?; + let denominator = high_bal.checked_sub(&low_bal)?; + let payout_long = nominator.bdiv(denominator).ok()?; + let payout_short = + ZeitgeistBase::>::get().ok()?.checked_sub(&payout_long)?; + + vec![payout_long, payout_short] + } + }; + + Some(result) } } } diff --git a/zrml/prediction-markets/src/tests/mod.rs b/zrml/prediction-markets/src/tests/mod.rs index 80a6962ee..ee0c93570 100644 --- a/zrml/prediction-markets/src/tests/mod.rs +++ b/zrml/prediction-markets/src/tests/mod.rs @@ -33,6 +33,7 @@ mod manually_close_market; mod on_initialize; mod on_market_close; mod on_resolution; +mod payout_vector; mod redeem_shares; mod reject_early_close; mod reject_market; diff --git a/zrml/prediction-markets/src/tests/payout_vector.rs b/zrml/prediction-markets/src/tests/payout_vector.rs new file mode 100644 index 000000000..94a4d0ddb --- /dev/null +++ b/zrml/prediction-markets/src/tests/payout_vector.rs @@ -0,0 +1,131 @@ +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use super::*; +use test_case::test_case; +use zeitgeist_primitives::traits::PayoutApi; + +#[test] +fn payout_vector_works_categorical() { + ExtBuilder::default().build().execute_with(|| { + let end = 2; + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Permissionless, + 0..end, + ScoringRule::AmmCdaHybrid, + ); + + let market_id = 0; + + let market = MarketCommons::market(&market_id).unwrap(); + let grace_period = end + market.deadlines.grace_period; + run_to_block(grace_period + 1); + + assert_ok!(PredictionMarkets::report( + RuntimeOrigin::signed(BOB), + 0, + OutcomeReport::Categorical(1) + )); + + run_blocks(market.deadlines.dispute_duration); + + assert_eq!(PredictionMarkets::payout_vector(market_id), Some(vec![0, BASE])); + }); +} + +#[test_case(50, vec![0, BASE])] +#[test_case(100, vec![0, BASE])] +#[test_case(130, vec![30 * CENT, 70 * CENT])] +#[test_case(200, vec![BASE, 0])] +#[test_case(250, vec![BASE, 0])] +fn payout_vector_works_scalar(value: u128, expected: Vec>) { + ExtBuilder::default().build().execute_with(|| { + let end = 2; + simple_create_scalar_market( + Asset::Ztg, + MarketCreation::Permissionless, + 0..end, + ScoringRule::AmmCdaHybrid, + ); + + let market_id = 0; + + let market = MarketCommons::market(&market_id).unwrap(); + let grace_period = end + market.deadlines.grace_period; + run_to_block(grace_period + 1); + + assert_ok!(PredictionMarkets::report( + RuntimeOrigin::signed(BOB), + 0, + OutcomeReport::Scalar(value) + )); + + run_blocks(market.deadlines.dispute_duration); + + assert_eq!(PredictionMarkets::payout_vector(market_id), Some(expected)); + }); +} + +#[test] +fn payout_vector_fails_on_market_not_found() { + ExtBuilder::default().build().execute_with(|| { + assert_eq!(PredictionMarkets::payout_vector(1), None); + }); +} + +#[test] +fn payout_vector_fails_if_market_is_not_redeemable() { + ExtBuilder::default().build().execute_with(|| { + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Permissionless, + 0..2, + ScoringRule::Parimutuel, + ); + + assert_ok!(MarketCommons::mutate_market(&0, |market_inner| { + market_inner.status = MarketStatus::Resolved; + Ok(()) + })); + + assert_eq!(PredictionMarkets::payout_vector(0), None); + }); +} + +#[test_case(MarketStatus::Proposed)] +#[test_case(MarketStatus::Active)] +#[test_case(MarketStatus::Closed)] +#[test_case(MarketStatus::Reported)] +#[test_case(MarketStatus::Disputed)] +fn payout_vector_fails_on_invalid_market_status(status: MarketStatus) { + ExtBuilder::default().build().execute_with(|| { + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Permissionless, + 0..2, + ScoringRule::AmmCdaHybrid, + ); + + assert_ok!(MarketCommons::mutate_market(&0, |market_inner| { + market_inner.status = status; + Ok(()) + })); + + assert_eq!(PredictionMarkets::payout_vector(0), None); + }); +} From 26600961ff9c12a7b9f789e19f4472a49b90e27b Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Thu, 24 Oct 2024 21:38:18 +0200 Subject: [PATCH 41/73] Benchmark combinatorial-tokens (#1387) * Expose benchmarking parameter * Add security notes to code * Update and test `TokenMerged` event * Implement `TokenRedeemed` event * Test `TokenRedeemed` event * Add `CombinatorialTokensBenchmarkHelper` * Implement `redeem_position` benchmark * Implement first benchmarks, prediction markets benchmark helper * Clear up errors * Include benchmarks * Extend tests * Implement more benchmarks * . * . * Use weight in `redeem_position` * . * . * . * First split bench * Horizontal splits benchmarked * . * . * . --- primitives/src/traits.rs | 2 + .../combinatorial_tokens_benchmark_helper.rs | 13 + runtime/common/src/lib.rs | 8 + scripts/benchmarks/configuration.sh | 5 +- zrml/combinatorial-tokens/src/benchmarking.rs | 594 ++++++++++++++++++ zrml/combinatorial-tokens/src/lib.rs | 218 +++++-- zrml/combinatorial-tokens/src/mock/runtime.rs | 8 +- .../src/mock/types/benchmark_helper.rs | 25 + .../src/mock/types/mod.rs | 6 +- .../src/tests/integration.rs | 22 + .../src/tests/merge_position.rs | 78 ++- .../src/tests/redeem_position.rs | 76 ++- .../src/tests/split_position.rs | 46 +- .../decompressor/mod.rs | 7 +- zrml/combinatorial-tokens/src/weights.rs | 199 ++++++ zrml/prediction-markets/src/lib.rs | 1 + .../combinatorial_tokens_benchmark_helper.rs | 40 ++ zrml/prediction-markets/src/types/mod.rs | 3 + 18 files changed, 1271 insertions(+), 80 deletions(-) create mode 100644 primitives/src/traits/combinatorial_tokens_benchmark_helper.rs create mode 100644 zrml/combinatorial-tokens/src/benchmarking.rs create mode 100644 zrml/combinatorial-tokens/src/mock/types/benchmark_helper.rs create mode 100644 zrml/combinatorial-tokens/src/weights.rs create mode 100644 zrml/prediction-markets/src/types/combinatorial_tokens_benchmark_helper.rs create mode 100644 zrml/prediction-markets/src/types/mod.rs diff --git a/primitives/src/traits.rs b/primitives/src/traits.rs index 4ed5639d6..5ad12e800 100644 --- a/primitives/src/traits.rs +++ b/primitives/src/traits.rs @@ -16,6 +16,7 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . +mod combinatorial_tokens_benchmark_helper; mod complete_set_operations_api; mod deploy_pool_api; mod dispute_api; @@ -31,6 +32,7 @@ mod payout_api; mod swaps; mod zeitgeist_asset; +pub use combinatorial_tokens_benchmark_helper::*; pub use complete_set_operations_api::*; pub use deploy_pool_api::*; pub use dispute_api::*; diff --git a/primitives/src/traits/combinatorial_tokens_benchmark_helper.rs b/primitives/src/traits/combinatorial_tokens_benchmark_helper.rs new file mode 100644 index 000000000..267d20b56 --- /dev/null +++ b/primitives/src/traits/combinatorial_tokens_benchmark_helper.rs @@ -0,0 +1,13 @@ +use alloc::vec::Vec; +use sp_runtime::DispatchResult; + +pub trait CombinatorialTokensBenchmarkHelper { + type Balance; + type MarketId; + + /// Prepares the market with the specified `market_id` to have a particular `payout`. + fn setup_payout_vector( + market_id: Self::MarketId, + payout: Option>, + ) -> DispatchResult; +} diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs index 05b85a417..ea6a299c1 100644 --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -93,6 +93,9 @@ macro_rules! decl_common_types { #[cfg(feature = "runtime-benchmarks")] use zrml_neo_swaps::types::DecisionMarketBenchmarkHelper; + #[cfg(feature = "runtime-benchmarks")] + use zrml_prediction_markets::types::PredictionMarketsCombinatorialTokensBenchmarkHelper; + pub type Block = generic::Block; type Address = sp_runtime::MultiAddress; @@ -1154,12 +1157,15 @@ macro_rules! impl_config_traits { } impl zrml_combinatorial_tokens::Config for Runtime { + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = PredictionMarketsCombinatorialTokensBenchmarkHelper; type CombinatorialIdManager = CryptographicIdManager; type MarketCommons = MarketCommons; type MultiCurrency = AssetManager; type Payout = PredictionMarkets; type RuntimeEvent = RuntimeEvent; type PalletId = CombinatorialTokensPalletId; + type WeightInfo = zrml_combinatorial_tokens::weights::WeightInfo; } impl zrml_court::Config for Runtime { @@ -1453,6 +1459,7 @@ macro_rules! create_runtime_api { list_benchmark!(list, extra, pallet_vesting, Vesting); list_benchmark!(list, extra, zrml_swaps, Swaps); list_benchmark!(list, extra, zrml_authorized, Authorized); + list_benchmark!(list, extra, zrml_combinatorial_tokens, CombinatorialTokens); list_benchmark!(list, extra, zrml_court, Court); list_benchmark!(list, extra, zrml_futarchy, Futarchy); list_benchmark!(list, extra, zrml_global_disputes, GlobalDisputes); @@ -1543,6 +1550,7 @@ macro_rules! create_runtime_api { add_benchmark!(params, batches, pallet_vesting, Vesting); add_benchmark!(params, batches, zrml_swaps, Swaps); add_benchmark!(params, batches, zrml_authorized, Authorized); + add_benchmark!(params, batches, zrml_combinatorial_tokens, CombinatorialTokens); add_benchmark!(params, batches, zrml_court, Court); add_benchmark!(params, batches, zrml_futarchy, Futarchy); add_benchmark!(params, batches, zrml_global_disputes, GlobalDisputes); diff --git a/scripts/benchmarks/configuration.sh b/scripts/benchmarks/configuration.sh index 41ec1f9f9..41409c06a 100644 --- a/scripts/benchmarks/configuration.sh +++ b/scripts/benchmarks/configuration.sh @@ -26,8 +26,9 @@ export ORML_PALLETS_STEPS="${ORML_PALLETS_STEPS:-50}" export ORML_WEIGHT_TEMPLATE="./misc/orml_weight_template.hbs" export ZEITGEIST_PALLETS=( - zrml_authorized zrml_court zrml_futarchy zrml_global_disputes zrml_hybrid_router \ - zrml_neo_swaps zrml_orderbook zrml_parimutuel zrml_prediction_markets zrml_swaps zrml_styx \ + zrml_authorized zrml_combinatorial_tokens zrml_court zrml_futarchy zrml_global_disputes \ + zrml_hybrid_router zrml_neo_swaps zrml_orderbook zrml_parimutuel zrml_prediction_markets \ + zrml_swaps zrml_styx \ ) export ZEITGEIST_PALLETS_RUNS="${ZEITGEIST_PALLETS_RUNS:-20}" export ZEITGEIST_PALLETS_STEPS="${ZEITGEIST_PALLETS_STEPS:-50}" diff --git a/zrml/combinatorial-tokens/src/benchmarking.rs b/zrml/combinatorial-tokens/src/benchmarking.rs new file mode 100644 index 000000000..901821fb1 --- /dev/null +++ b/zrml/combinatorial-tokens/src/benchmarking.rs @@ -0,0 +1,594 @@ +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +#![cfg(feature = "runtime-benchmarks")] + +use crate::{BalanceOf, Call, Config, Event, MarketIdOf, Pallet}; +use alloc::{vec, vec::Vec}; +use frame_benchmarking::v2::*; +use frame_support::dispatch::RawOrigin; +use frame_system::Pallet as System; +use orml_traits::MultiCurrency; +use sp_runtime::{traits::Zero, Perbill}; +use zeitgeist_primitives::{ + math::fixed::{BaseProvider, ZeitgeistBase}, + traits::{CombinatorialTokensBenchmarkHelper, MarketCommonsPalletApi}, + types::{Asset, Market, MarketCreation, MarketPeriod, MarketStatus, MarketType, ScoringRule}, +}; + +fn create_market(caller: T::AccountId, asset_count: u16) -> MarketIdOf { + let market = Market { + market_id: Default::default(), + base_asset: Asset::Ztg, + creation: MarketCreation::Permissionless, + creator_fee: Perbill::zero(), + creator: caller.clone(), + oracle: caller, + metadata: Default::default(), + market_type: MarketType::Categorical(asset_count), + period: MarketPeriod::Block(0u32.into()..1u32.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, + }; + T::MarketCommons::push_market(market).unwrap() +} + +fn create_payout_vector(asset_count: u16) -> Vec> { + let mut result = vec![Zero::zero(); asset_count as usize]; + result[0] = ZeitgeistBase::get().unwrap(); + + result +} + +#[benchmarks] +mod benchmarks { + use super::*; + + #[benchmark] + fn split_position_vertical_sans_parent(n: Linear<2, 32>) { + let alice: T::AccountId = whitelisted_caller(); + + let position_count: usize = n.try_into().unwrap(); + + let parent_collection_id = None; + let market_id = create_market::(alice.clone(), position_count.try_into().unwrap()); + // Partition is 10...0, 010...0, ..., 0...01. + let partition: Vec<_> = (0..position_count) + .map(|index| { + let mut index_set = vec![false; position_count]; + index_set[index] = true; + + index_set + }) + .collect(); + let amount = ZeitgeistBase::get().unwrap(); + + T::MultiCurrency::deposit(Asset::Ztg, &alice, amount).unwrap(); + + #[extrinsic_call] + split_position( + RawOrigin::Signed(alice.clone()), + parent_collection_id, + market_id, + partition.clone(), + amount, + true, + ); + + let collection_ids: Vec<_> = partition + .iter() + .cloned() + .map(|index_set| { + Pallet::::collection_id_from_parent_collection( + parent_collection_id, + market_id, + index_set, + false, + ) + .unwrap() + }) + .collect(); + let assets_out: Vec<_> = collection_ids + .iter() + .cloned() + .map(|collection_id| { + Pallet::::position_from_collection_id(market_id, collection_id).unwrap() + }) + .collect(); + let expected_event = ::RuntimeEvent::from(Event::::TokenSplit { + who: alice, + parent_collection_id, + market_id, + partition, + asset_in: Asset::Ztg, + assets_out, + collection_ids, + amount, + }); + System::::assert_last_event(expected_event.into()); + } + + #[benchmark] + fn split_position_vertical_with_parent(n: Linear<2, 32>) { + let alice: T::AccountId = whitelisted_caller(); + + let position_count: usize = n.try_into().unwrap(); + + let parent_collection_id = None; + let parent_market_id = create_market::(alice.clone(), 2); + + // The collection/position that we're merging into. + let cid_01 = Pallet::::collection_id_from_parent_collection( + parent_collection_id, + parent_market_id, + vec![false, true], + false, + ) + .unwrap(); + let pos_01 = Pallet::::position_from_collection_id(parent_market_id, cid_01).unwrap(); + + let child_market_id = create_market::(alice.clone(), position_count.try_into().unwrap()); + let partition: Vec<_> = (0..position_count) + .map(|index| { + let mut index_set = vec![false; position_count]; + index_set[index] = true; + + index_set + }) + .collect(); + let amount = ZeitgeistBase::get().unwrap(); + + T::MultiCurrency::deposit(pos_01, &alice, amount).unwrap(); + + #[extrinsic_call] + split_position( + RawOrigin::Signed(alice.clone()), + Some(cid_01), + child_market_id, + partition.clone(), + amount, + true, + ); + + let collection_ids: Vec<_> = partition + .iter() + .cloned() + .map(|index_set| { + Pallet::::collection_id_from_parent_collection( + Some(cid_01), + child_market_id, + index_set, + false, + ) + .unwrap() + }) + .collect(); + let assets_out: Vec<_> = collection_ids + .iter() + .cloned() + .map(|collection_id| { + Pallet::::position_from_collection_id(child_market_id, collection_id).unwrap() + }) + .collect(); + let expected_event = ::RuntimeEvent::from(Event::::TokenSplit { + who: alice, + parent_collection_id: Some(cid_01), + market_id: child_market_id, + partition, + asset_in: pos_01, + assets_out, + collection_ids, + amount, + }); + System::::assert_last_event(expected_event.into()); + } + + #[benchmark] + fn split_position_horizontal(n: Linear<2, 32>) { + let alice: T::AccountId = whitelisted_caller(); + + let position_count: usize = n.try_into().unwrap(); + let asset_count = position_count + 1; + + let parent_collection_id = None; + let market_id = create_market::(alice.clone(), asset_count.try_into().unwrap()); + // Partition is 10...0, 010...0, ..., 0...010. Doesn't contain 0...01. + let partition: Vec<_> = (0..position_count) + .map(|index| { + let mut index_set = vec![false; asset_count]; + index_set[index] = true; + + index_set + }) + .collect(); + let amount = ZeitgeistBase::get().unwrap(); + + // Add 1...10 to Alice's account. + let mut asset_in_index_set = vec![true; asset_count]; + *asset_in_index_set.last_mut().unwrap() = false; + let asset_in = Pallet::::position_from_parent_collection( + parent_collection_id, + market_id, + asset_in_index_set, + false, + ) + .unwrap(); + T::MultiCurrency::deposit(asset_in, &alice, amount).unwrap(); + + #[extrinsic_call] + split_position( + RawOrigin::Signed(alice.clone()), + parent_collection_id, + market_id, + partition.clone(), + amount, + true, + ); + + let collection_ids: Vec<_> = partition + .iter() + .cloned() + .map(|index_set| { + Pallet::::collection_id_from_parent_collection( + parent_collection_id, + market_id, + index_set, + false, + ) + .unwrap() + }) + .collect(); + let assets_out: Vec<_> = collection_ids + .iter() + .cloned() + .map(|collection_id| { + Pallet::::position_from_collection_id(market_id, collection_id).unwrap() + }) + .collect(); + let expected_event = ::RuntimeEvent::from(Event::::TokenSplit { + who: alice, + parent_collection_id, + market_id, + partition, + asset_in, + assets_out, + collection_ids, + amount, + }); + System::::assert_last_event(expected_event.into()); + } + + #[benchmark] + fn merge_position_vertical_sans_parent(n: Linear<2, 32>) { + let alice: T::AccountId = whitelisted_caller(); + + let position_count: usize = n.try_into().unwrap(); + + let parent_collection_id = None; + let market_id = create_market::(alice.clone(), position_count.try_into().unwrap()); + let partition: Vec<_> = (0..position_count) + .map(|index| { + let mut index_set = vec![false; position_count]; + index_set[index] = true; + + index_set + }) + .collect(); + let amount = ZeitgeistBase::get().unwrap(); + + let assets_in: Vec<_> = partition + .iter() + .cloned() + .map(|index_set| { + Pallet::::position_from_parent_collection( + parent_collection_id, + market_id, + index_set, + false, + ) + .unwrap() + }) + .collect(); + + for &asset in assets_in.iter() { + T::MultiCurrency::deposit(asset, &alice, amount).unwrap(); + } + T::MultiCurrency::deposit(Asset::Ztg, &Pallet::::account_id(), amount).unwrap(); + + #[extrinsic_call] + merge_position( + RawOrigin::Signed(alice.clone()), + parent_collection_id, + market_id, + partition.clone(), + amount, + true, + ); + + let expected_event = ::RuntimeEvent::from(Event::::TokenMerged { + who: alice, + parent_collection_id, + market_id, + partition, + asset_out: Asset::Ztg, + assets_in, + amount, + }); + System::::assert_last_event(expected_event.into()); + } + + #[benchmark] + fn merge_position_vertical_with_parent(n: Linear<2, 32>) { + let alice: T::AccountId = whitelisted_caller(); + + let position_count: usize = n.try_into().unwrap(); + + let parent_collection_id = None; + let parent_market_id = create_market::(alice.clone(), 2); + + // The collection/position that we're merging into. + let cid_01 = Pallet::::collection_id_from_parent_collection( + parent_collection_id, + parent_market_id, + vec![false, true], + false, + ) + .unwrap(); + let pos_01 = Pallet::::position_from_collection_id(parent_market_id, cid_01).unwrap(); + + let child_market_id = create_market::(alice.clone(), position_count.try_into().unwrap()); + let partition: Vec<_> = (0..position_count) + .map(|index| { + let mut index_set = vec![false; position_count]; + index_set[index] = true; + + index_set + }) + .collect(); + let amount = ZeitgeistBase::get().unwrap(); + + let assets_in: Vec<_> = partition + .iter() + .cloned() + .map(|index_set| { + Pallet::::position_from_parent_collection( + Some(cid_01), + child_market_id, + index_set, + false, + ) + .unwrap() + }) + .collect(); + + for &asset in assets_in.iter() { + T::MultiCurrency::deposit(asset, &alice, amount).unwrap(); + } + + #[extrinsic_call] + merge_position( + RawOrigin::Signed(alice.clone()), + Some(cid_01), + child_market_id, + partition.clone(), + amount, + true, + ); + + let expected_event = ::RuntimeEvent::from(Event::::TokenMerged { + who: alice, + parent_collection_id: Some(cid_01), + market_id: child_market_id, + partition, + asset_out: pos_01, + assets_in, + amount, + }); + System::::assert_last_event(expected_event.into()); + } + + #[benchmark] + fn merge_position_horizontal(n: Linear<2, 32>) { + let alice: T::AccountId = whitelisted_caller(); + + let position_count: usize = n.try_into().unwrap(); + let asset_count = position_count + 1; + + let parent_collection_id = None; + let market_id = create_market::(alice.clone(), asset_count.try_into().unwrap()); + // Partition is 10...0, 010...0, ..., 0...010. Doesn't contain 0...01. + let partition: Vec<_> = (0..position_count) + .map(|index| { + let mut index_set = vec![false; asset_count]; + index_set[index] = true; + + index_set + }) + .collect(); + let amount = ZeitgeistBase::get().unwrap(); + + let assets_in: Vec<_> = partition + .iter() + .cloned() + .map(|index_set| { + Pallet::::position_from_parent_collection( + parent_collection_id, + market_id, + index_set, + false, + ) + .unwrap() + }) + .collect(); + + for &asset in assets_in.iter() { + T::MultiCurrency::deposit(asset, &alice, amount).unwrap(); + } + + #[extrinsic_call] + merge_position( + RawOrigin::Signed(alice.clone()), + parent_collection_id, + market_id, + partition.clone(), + amount, + true, + ); + + let mut asset_out_index_set = vec![true; asset_count]; + *asset_out_index_set.last_mut().unwrap() = false; + let asset_out = Pallet::::position_from_parent_collection( + parent_collection_id, + market_id, + asset_out_index_set, + false, + ) + .unwrap(); + let expected_event = ::RuntimeEvent::from(Event::::TokenMerged { + who: alice, + parent_collection_id, + market_id, + partition, + asset_out, + assets_in, + amount, + }); + System::::assert_last_event(expected_event.into()); + } + + #[benchmark] + fn redeem_position_sans_parent(n: Linear<2, 32>) { + let alice: T::AccountId = whitelisted_caller(); + + let n_u16: u16 = n.try_into().unwrap(); + let asset_count = n_u16 + 1; + + // `index_set` has `n` entries that are `true`, which results in `n` iterations in the `for` + // loop in `redeem_position`. + let mut index_set = vec![true; asset_count as usize]; + *index_set.last_mut().unwrap() = false; + + let parent_collection_id = None; + let market_id = create_market::(alice.clone(), asset_count); + + let payout_vector = create_payout_vector::(asset_count); + T::BenchmarkHelper::setup_payout_vector(market_id, Some(payout_vector)).unwrap(); + + // Deposit tokens for Alice and the pallet account. + let position = Pallet::::position_from_parent_collection( + parent_collection_id, + market_id, + index_set.clone(), + false, + ) + .unwrap(); + let amount = ZeitgeistBase::get().unwrap(); + T::MultiCurrency::deposit(position, &alice, amount).unwrap(); + T::MultiCurrency::deposit(Asset::Ztg, &Pallet::::account_id(), amount).unwrap(); + + #[extrinsic_call] + redeem_position( + RawOrigin::Signed(alice.clone()), + parent_collection_id, + market_id, + index_set.clone(), + true, + ); + + let expected_event = ::RuntimeEvent::from(Event::::TokenRedeemed { + who: alice, + parent_collection_id, + market_id, + index_set, + asset_in: position, + amount_in: amount, + asset_out: Asset::Ztg, + amount_out: amount, + }); + System::::assert_last_event(expected_event.into()); + } + + #[benchmark] + fn redeem_position_with_parent(n: Linear<2, 32>) { + let alice: T::AccountId = whitelisted_caller(); + + let n_u16: u16 = n.try_into().unwrap(); + let asset_count = n_u16 + 1; + + // `index_set` has `n` entries that are `true`, which results in `n` iterations in the `for` + // loop in `redeem_position`. + let mut index_set = vec![true; asset_count as usize]; + *index_set.last_mut().unwrap() = false; + + let parent_market_id = create_market::(alice.clone(), 2); + let cid_01 = Pallet::::collection_id_from_parent_collection( + None, + parent_market_id, + vec![false, true], + false, + ) + .unwrap(); + let pos_01 = Pallet::::position_from_collection_id(parent_market_id, cid_01).unwrap(); + + let child_market_id = create_market::(alice.clone(), asset_count); + let pos_01_10 = Pallet::::position_from_parent_collection( + Some(cid_01), + child_market_id, + index_set.clone(), + false, + ) + .unwrap(); + let amount = ZeitgeistBase::get().unwrap(); + T::MultiCurrency::deposit(pos_01_10, &alice, amount).unwrap(); + + let payout_vector = create_payout_vector::(asset_count); + T::BenchmarkHelper::setup_payout_vector(child_market_id, Some(payout_vector)).unwrap(); + + #[extrinsic_call] + redeem_position( + RawOrigin::Signed(alice.clone()), + Some(cid_01), + child_market_id, + index_set.clone(), + true, + ); + + let expected_event = ::RuntimeEvent::from(Event::::TokenRedeemed { + who: alice, + parent_collection_id: Some(cid_01), + market_id: child_market_id, + index_set, + asset_in: pos_01_10, + amount_in: amount, + asset_out: pos_01, + amount_out: amount, + }); + System::::assert_last_event(expected_event.into()); + } + + impl_benchmark_test_suite!( + Pallet, + crate::mock::ext_builder::ExtBuilder::build(), + crate::mock::runtime::Runtime + ); +} diff --git a/zrml/combinatorial-tokens/src/lib.rs b/zrml/combinatorial-tokens/src/lib.rs index 6f4a34c9c..0706ca56a 100644 --- a/zrml/combinatorial-tokens/src/lib.rs +++ b/zrml/combinatorial-tokens/src/lib.rs @@ -27,21 +27,23 @@ extern crate alloc; +mod benchmarking; pub mod mock; mod tests; mod traits; pub mod types; +pub mod weights; pub use pallet::*; #[frame_support::pallet] mod pallet { - use crate::traits::CombinatorialIdManager; + use crate::{traits::CombinatorialIdManager, weights::WeightInfoZeitgeist}; use alloc::{vec, vec::Vec}; use core::marker::PhantomData; use frame_support::{ ensure, - pallet_prelude::{IsType, StorageVersion}, + pallet_prelude::{DispatchResultWithPostInfo, IsType, StorageVersion}, require_transactional, transactional, PalletId, }; use frame_system::{ @@ -51,7 +53,7 @@ mod pallet { use orml_traits::MultiCurrency; use sp_runtime::{ traits::{AccountIdConversion, Get, Zero}, - DispatchError, DispatchResult, + DispatchError, SaturatedConversion, }; use zeitgeist_primitives::{ math::{checked_ops_res::CheckedAddRes, fixed::FixedMul}, @@ -59,8 +61,17 @@ mod pallet { types::{Asset, CombinatorialId}, }; + #[cfg(feature = "runtime-benchmarks")] + use zeitgeist_primitives::traits::CombinatorialTokensBenchmarkHelper; + #[pallet::config] pub trait Config: frame_system::Config { + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper: CombinatorialTokensBenchmarkHelper< + Balance = BalanceOf, + MarketId = MarketIdOf, + >; + type CombinatorialIdManager: CombinatorialIdManager< Asset = AssetOf, MarketId = MarketIdOf, @@ -77,6 +88,8 @@ mod pallet { #[pallet::constant] type PalletId: Get; + + type WeightInfo: WeightInfoZeitgeist; } #[pallet::pallet] @@ -103,8 +116,9 @@ mod pallet { /// User `who` has split `amount` units of token `asset_in` into the same amount of each /// token in `assets_out` using `partition`. The ith element of `partition` matches the ith /// element of `assets_out`, so `assets_out[i]` is the outcome represented by the specified - /// `parent_collection_id` together with `partition` in `market_id`. - /// TODO The second sentence is confusing. + /// `parent_collection_id` when split using `partition[i]` in `market_id`. The same goes for + /// the `collection_ids` vector, the ith element of which specifies the collection ID of + /// `assets_out[i]`. TokenSplit { who: AccountIdOf, parent_collection_id: Option, @@ -117,13 +131,35 @@ mod pallet { }, /// User `who` has merged `amount` units of each of the tokens in `assets_in` into the same - /// amount of `asset_out`. + /// amount of `asset_out`. The ith element of the `partition` matches the ith element of + /// `assets_in`, so `assets_in[i]` is the outcome represented by the specified + /// `parent_collection_id` when split using `partition[i]` in `market_id`. Note that the + /// `parent_collection_id` is equal to the collection ID of the position `asset_out`; if + /// `asset_out` is the collateral token, then `parent_collection_id` is `None`. TokenMerged { who: AccountIdOf, + parent_collection_id: Option, + market_id: MarketIdOf, + partition: Vec>, asset_out: AssetOf, assets_in: Vec>, amount: BalanceOf, }, + + /// User `who` has redeemed `amount_in` units of `asset_in` for `amount_out` units of + /// `asset_out` using the report for the market specified by `market_id`. The + /// `parent_collection_id` specifies the collection ID of the `asset_out`; it is `None` if + /// the `asset_out` is the collateral token. + TokenRedeemed { + who: AccountIdOf, + parent_collection_id: Option, + market_id: MarketIdOf, + index_set: Vec, + asset_in: AssetOf, + amount_in: BalanceOf, + asset_out: AssetOf, + amount_out: BalanceOf, + }, } #[pallet::error] @@ -154,7 +190,13 @@ mod pallet { #[pallet::call] impl Pallet { #[pallet::call_index(0)] - #[pallet::weight({0})] // TODO + #[pallet::weight( + T::WeightInfo::split_position_vertical_sans_parent(partition.len().saturated_into()) + .max(T::WeightInfo::split_position_vertical_with_parent( + partition.len().saturated_into(), + )) + .max(T::WeightInfo::split_position_horizontal(partition.len().saturated_into())) + )] #[transactional] pub fn split_position( origin: OriginFor, @@ -163,13 +205,27 @@ mod pallet { market_id: MarketIdOf, partition: Vec>, amount: BalanceOf, - ) -> DispatchResult { + force_max_work: bool, + ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; - Self::do_split_position(who, parent_collection_id, market_id, partition, amount) + Self::do_split_position( + who, + parent_collection_id, + market_id, + partition, + amount, + force_max_work, + ) } #[pallet::call_index(1)] - #[pallet::weight({0})] // TODO + #[pallet::weight( + T::WeightInfo::merge_position_vertical_sans_parent(partition.len().saturated_into()) + .max(T::WeightInfo::merge_position_vertical_with_parent( + partition.len().saturated_into(), + )) + .max(T::WeightInfo::merge_position_horizontal(partition.len().saturated_into())) + )] #[transactional] pub fn merge_position( origin: OriginFor, @@ -177,22 +233,40 @@ mod pallet { market_id: MarketIdOf, partition: Vec>, amount: BalanceOf, - ) -> DispatchResult { + force_max_work: bool, + ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; - Self::do_merge_position(who, parent_collection_id, market_id, partition, amount) + Self::do_merge_position( + who, + parent_collection_id, + market_id, + partition, + amount, + force_max_work, + ) } #[pallet::call_index(2)] - #[pallet::weight({0})] // TODO + #[pallet::weight( + T::WeightInfo::redeem_position_with_parent(index_set.len().saturated_into()) + .max(T::WeightInfo::redeem_position_sans_parent(index_set.len().saturated_into())) + )] #[transactional] pub fn redeem_position( origin: OriginFor, parent_collection_id: Option>, market_id: MarketIdOf, index_set: Vec, - ) -> DispatchResult { + force_max_work: bool, + ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; - Self::do_redeem_position(who, parent_collection_id, market_id, index_set) + Self::do_redeem_position( + who, + parent_collection_id, + market_id, + index_set, + force_max_work, + ) } } @@ -204,14 +278,15 @@ mod pallet { market_id: MarketIdOf, partition: Vec>, amount: BalanceOf, - ) -> DispatchResult { + force_max_work: bool, + ) -> DispatchResultWithPostInfo { let market = T::MarketCommons::market(&market_id)?; let collateral_token = market.base_asset; let free_index_set = Self::free_index_set(market_id, &partition)?; // Destroy/store the tokens to be split. - let split_asset = if !free_index_set.iter().any(|&i| i) { + let (weight, split_asset) = if !free_index_set.iter().any(|&i| i) { // Vertical split. if let Some(pci) = parent_collection_id { // Split combinatorial token into higher level position. Destroy the tokens. @@ -224,7 +299,11 @@ mod pallet { T::MultiCurrency::ensure_can_withdraw(position, &who, amount)?; T::MultiCurrency::withdraw(position, &who, amount)?; - position + let weight = T::WeightInfo::split_position_vertical_with_parent( + partition.len().saturated_into(), + ); + + (weight, position) } else { // Split collateral into first level position. Store the collateral in the // pallet account. This is the legacy `buy_complete_set`. @@ -236,7 +315,11 @@ mod pallet { amount, )?; - collateral_token + let weight = T::WeightInfo::split_position_vertical_sans_parent( + partition.len().saturated_into(), + ); + + (weight, collateral_token) } } else { // Horizontal split. @@ -245,11 +328,15 @@ mod pallet { parent_collection_id, market_id, remaining_index_set, + force_max_work, )?; T::MultiCurrency::ensure_can_withdraw(position, &who, amount)?; T::MultiCurrency::withdraw(position, &who, amount)?; - position + let weight = + T::WeightInfo::split_position_horizontal(partition.len().saturated_into()); + + (weight, position) }; // Deposit the new tokens. @@ -261,6 +348,7 @@ mod pallet { parent_collection_id, market_id, index_set, + force_max_work, ) }) .collect::, _>>()?; @@ -269,6 +357,8 @@ mod pallet { .cloned() .map(|collection_id| Self::position_from_collection_id(market_id, collection_id)) .collect::, _>>()?; + // Security note: Safe as iterations are limited to the number of assets in the market + // thanks to the `ensure!` invocations in `Self::free_index_set`. for &position in positions.iter() { T::MultiCurrency::deposit(position, &who, amount)?; } @@ -284,7 +374,7 @@ mod pallet { amount, }); - Ok(()) + Ok(Some(weight).into()) } #[require_transactional] @@ -294,13 +384,14 @@ mod pallet { market_id: MarketIdOf, partition: Vec>, amount: BalanceOf, - ) -> DispatchResult { + force_max_work: bool, + ) -> DispatchResultWithPostInfo { let market = T::MarketCommons::market(&market_id)?; let collateral_token = market.base_asset; let free_index_set = Self::free_index_set(market_id, &partition)?; - // Destory the old tokens. + // Destroy the old tokens. let positions = partition .iter() .cloned() @@ -309,15 +400,18 @@ mod pallet { parent_collection_id, market_id, index_set, + force_max_work, ) }) .collect::, _>>()?; + // Security note: Safe as iterations are limited to the number of assets in the market + // thanks to the `ensure!` invocations in `Self::free_index_set`. for &position in positions.iter() { T::MultiCurrency::withdraw(position, &who, amount)?; } // Destroy/store the tokens to be split. - let merged_token = if !free_index_set.iter().any(|&i| i) { + let (weight, merged_token) = if !free_index_set.iter().any(|&i| i) { // Vertical merge. if let Some(pci) = parent_collection_id { // Merge combinatorial token into higher level position. Destroy the tokens. @@ -326,7 +420,11 @@ mod pallet { let position = Asset::CombinatorialToken(position_id); T::MultiCurrency::deposit(position, &who, amount)?; - position + let weight = T::WeightInfo::merge_position_vertical_with_parent( + partition.len().saturated_into(), + ); + + (weight, position) } else { // Merge first-level tokens into collateral. Move collateral from the pallet // account to the user's wallet. This is the legacy `sell_complete_set`. @@ -337,7 +435,11 @@ mod pallet { amount, )?; - collateral_token + let weight = T::WeightInfo::merge_position_vertical_sans_parent( + partition.len().saturated_into(), + ); + + (weight, collateral_token) } } else { // Horizontal merge. @@ -346,20 +448,27 @@ mod pallet { parent_collection_id, market_id, remaining_index_set, + force_max_work, )?; T::MultiCurrency::deposit(position, &who, amount)?; - position + let weight = + T::WeightInfo::merge_position_horizontal(partition.len().saturated_into()); + + (weight, position) }; Self::deposit_event(Event::::TokenMerged { who, + parent_collection_id, + market_id, + partition, asset_out: merged_token, assets_in: positions, amount, }); - Ok(()) + Ok(Some(weight).into()) } fn do_redeem_position( @@ -367,7 +476,8 @@ mod pallet { parent_collection_id: Option>, market_id: MarketIdOf, index_set: Vec, - ) -> DispatchResult { + force_max_work: bool, + ) -> DispatchResultWithPostInfo { let payout_vector = T::Payout::payout_vector(market_id).ok_or(Error::::PayoutVectorNotFound)?; @@ -381,6 +491,8 @@ mod pallet { // Add up values of each outcome. let mut total_stake: BalanceOf = Zero::zero(); + // Security note: Safe because `zip` will limit this loop to `payout_vector.len()` + // iterations. for (&index, value) in index_set.iter().zip(payout_vector.iter()) { if index { total_stake = total_stake.checked_add_res(value)?; @@ -389,19 +501,28 @@ mod pallet { ensure!(!total_stake.is_zero(), Error::::TokenHasNoValue); - let position = - Self::position_from_parent_collection(parent_collection_id, market_id, index_set)?; + let position = Self::position_from_parent_collection( + parent_collection_id, + market_id, + index_set.clone(), + force_max_work, + )?; let amount = T::MultiCurrency::free_balance(position, &who); ensure!(!amount.is_zero(), Error::::NoTokensFound); T::MultiCurrency::withdraw(position, &who, amount)?; let total_payout = total_stake.bmul(amount)?; - if let Some(pci) = parent_collection_id { + let (weight, asset_out) = if let Some(pci) = parent_collection_id { // Merge combinatorial token into higher level position. Destroy the tokens. let position_id = T::CombinatorialIdManager::get_position_id(collateral_token, pci); let position = Asset::CombinatorialToken(position_id); T::MultiCurrency::deposit(position, &who, total_payout)?; + + let weight = + T::WeightInfo::redeem_position_with_parent(index_set.len().saturated_into()); + + (weight, position) } else { T::MultiCurrency::transfer( collateral_token, @@ -409,16 +530,32 @@ mod pallet { &who, total_payout, )?; - } - Ok(()) + let weight = + T::WeightInfo::redeem_position_sans_parent(index_set.len().saturated_into()); + + (weight, collateral_token) + }; + + Self::deposit_event(Event::::TokenRedeemed { + who, + parent_collection_id, + market_id, + index_set, + asset_in: position, + amount_in: amount, + asset_out, + amount_out: total_payout, + }); + + Ok(Some(weight).into()) } pub(crate) fn account_id() -> T::AccountId { T::PalletId::get().into_account_truncating() } - fn free_index_set( + pub(crate) fn free_index_set( market_id: MarketIdOf, partition: &[Vec], ) -> Result, DispatchError> { @@ -447,21 +584,22 @@ mod pallet { Ok(free_index_set) } - fn collection_id_from_parent_collection( + pub(crate) fn collection_id_from_parent_collection( parent_collection_id: Option>, market_id: MarketIdOf, index_set: Vec, + force_max_work: bool, ) -> Result, DispatchError> { T::CombinatorialIdManager::get_collection_id( parent_collection_id, market_id, index_set, - false, // TODO Expose this parameter! + force_max_work, ) .ok_or(Error::::InvalidCollectionId.into()) } - fn position_from_collection_id( + pub(crate) fn position_from_collection_id( market_id: MarketIdOf, collection_id: CombinatorialIdOf, ) -> Result, DispatchError> { @@ -475,15 +613,17 @@ mod pallet { Ok(asset) } - fn position_from_parent_collection( + pub(crate) fn position_from_parent_collection( parent_collection_id: Option>, market_id: MarketIdOf, index_set: Vec, + force_max_work: bool, ) -> Result, DispatchError> { let collection_id = Self::collection_id_from_parent_collection( parent_collection_id, market_id, index_set, + force_max_work, )?; Self::position_from_collection_id(market_id, collection_id) diff --git a/zrml/combinatorial-tokens/src/mock/runtime.rs b/zrml/combinatorial-tokens/src/mock/runtime.rs index 157841ca7..ede07e829 100644 --- a/zrml/combinatorial-tokens/src/mock/runtime.rs +++ b/zrml/combinatorial-tokens/src/mock/runtime.rs @@ -16,7 +16,7 @@ // along with Zeitgeist. If not, see . use crate as zrml_combinatorial_tokens; -use crate::{mock::types::MockPayout, types::CryptographicIdManager}; +use crate::{mock::types::MockPayout, types::CryptographicIdManager, weights::WeightInfo}; use frame_support::{construct_runtime, traits::Everything, Blake2_256}; use frame_system::mocking::MockBlock; use sp_runtime::traits::{BlakeTwo256, ConstU32, IdentityLookup}; @@ -30,6 +30,9 @@ use zeitgeist_primitives::{ }, }; +#[cfg(feature = "runtime-benchmarks")] +use crate::mock::types::BenchmarkHelper; + construct_runtime! { pub enum Runtime { CombinatorialTokens: zrml_combinatorial_tokens, @@ -43,12 +46,15 @@ construct_runtime! { } impl zrml_combinatorial_tokens::Config for Runtime { + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = BenchmarkHelper; type CombinatorialIdManager = CryptographicIdManager; type MarketCommons = MarketCommons; type MultiCurrency = Currencies; type Payout = MockPayout; type RuntimeEvent = RuntimeEvent; type PalletId = CombinatorialTokensPalletId; + type WeightInfo = WeightInfo; } impl orml_currencies::Config for Runtime { diff --git a/zrml/combinatorial-tokens/src/mock/types/benchmark_helper.rs b/zrml/combinatorial-tokens/src/mock/types/benchmark_helper.rs new file mode 100644 index 000000000..36d1438fa --- /dev/null +++ b/zrml/combinatorial-tokens/src/mock/types/benchmark_helper.rs @@ -0,0 +1,25 @@ +use crate::{ + mock::{runtime::Runtime, types::MockPayout}, + BalanceOf, MarketIdOf, +}; +use alloc::vec::Vec; +use sp_runtime::DispatchResult; +use zeitgeist_primitives::traits::CombinatorialTokensBenchmarkHelper; + +pub struct BenchmarkHelper; + +impl CombinatorialTokensBenchmarkHelper for BenchmarkHelper { + type Balance = BalanceOf; + type MarketId = MarketIdOf; + + /// A bit of a messy implementation as this sets the return value of the next `payout_vector` + /// call, regardless of what `_market_id` is. + fn setup_payout_vector( + _market_id: Self::MarketId, + payout: Option>, + ) -> DispatchResult { + MockPayout::set_return_value(payout); + + Ok(()) + } +} diff --git a/zrml/combinatorial-tokens/src/mock/types/mod.rs b/zrml/combinatorial-tokens/src/mock/types/mod.rs index 03136bcda..6f3afbeaf 100644 --- a/zrml/combinatorial-tokens/src/mock/types/mod.rs +++ b/zrml/combinatorial-tokens/src/mock/types/mod.rs @@ -15,6 +15,10 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . +#[cfg(feature = "runtime-benchmarks")] +mod benchmark_helper; mod payout; -pub use payout::MockPayout; +#[cfg(feature = "runtime-benchmarks")] +pub(crate) use benchmark_helper::BenchmarkHelper; +pub(crate) use payout::MockPayout; diff --git a/zrml/combinatorial-tokens/src/tests/integration.rs b/zrml/combinatorial-tokens/src/tests/integration.rs index ced2f9ae1..83a5162bd 100644 --- a/zrml/combinatorial-tokens/src/tests/integration.rs +++ b/zrml/combinatorial-tokens/src/tests/integration.rs @@ -42,6 +42,7 @@ fn split_followed_by_merge_vertical_no_parent() { market_id, partition.clone(), amount, + false, )); assert_eq!(alice.free_balance(Asset::Ztg), _99); assert_eq!(alice.free_balance(ct_001), _1); @@ -54,6 +55,7 @@ fn split_followed_by_merge_vertical_no_parent() { market_id, partition, amount, + false, )); assert_eq!(alice.free_balance(Asset::Ztg), _100); assert_eq!(alice.free_balance(ct_001), 0); @@ -94,6 +96,7 @@ fn split_followed_by_merge_vertical_with_parent() { parent_market_id, parent_partition.clone(), parent_amount, + false, )); let child_market_id = create_market(Asset::Ztg, MarketType::Categorical(4)); @@ -110,6 +113,7 @@ fn split_followed_by_merge_vertical_with_parent() { child_market_id, child_partition.clone(), child_amount, + false, )); assert_eq!(alice.free_balance(ct_001), parent_amount - child_amount); assert_eq!(alice.free_balance(ct_110), parent_amount); @@ -124,6 +128,7 @@ fn split_followed_by_merge_vertical_with_parent() { child_market_id, child_partition, child_amount, + false, )); assert_eq!(alice.free_balance(ct_001), parent_amount); assert_eq!(alice.free_balance(ct_110), parent_amount); @@ -138,6 +143,7 @@ fn split_followed_by_merge_vertical_with_parent() { parent_market_id, parent_partition, parent_amount, + false, )); assert_eq!(alice.free_balance(ct_001), 0); assert_eq!(alice.free_balance(ct_110), 0); @@ -219,6 +225,7 @@ fn split_followed_by_merge_vertical_with_parent_in_opposite_order() { market_0, partition_0.clone(), amount, + false, )); // Split C into C&(U|V) and C&(W|X). @@ -228,6 +235,7 @@ fn split_followed_by_merge_vertical_with_parent_in_opposite_order() { market_1, partition_1.clone(), amount, + false, )); // Split A|B into into (A|B)&(U|V) and (A|B)&(W|X). @@ -237,6 +245,7 @@ fn split_followed_by_merge_vertical_with_parent_in_opposite_order() { market_1, partition_1.clone(), amount, + false, )); assert_eq!(alice.free_balance(ct_001), 0); @@ -256,6 +265,7 @@ fn split_followed_by_merge_vertical_with_parent_in_opposite_order() { market_0, partition_0.clone(), amount, + false, )); assert_eq!(alice.free_balance(ct_001), 0); @@ -275,6 +285,7 @@ fn split_followed_by_merge_vertical_with_parent_in_opposite_order() { market_0, partition_0, amount, + false, )); assert_eq!(alice.free_balance(ct_001), 0); @@ -294,6 +305,7 @@ fn split_followed_by_merge_vertical_with_parent_in_opposite_order() { market_1, partition_1, amount, + false, )); assert_eq!(alice.free_balance(ct_001), 0); @@ -325,6 +337,7 @@ fn split_vertical_followed_by_horizontal_split_no_parent() { market_id, vec![vec![B0, B0, B1], vec![B1, B1, B0]], amount, + false, )); assert_ok!(CombinatorialTokens::split_position( alice.signed(), @@ -332,6 +345,7 @@ fn split_vertical_followed_by_horizontal_split_no_parent() { market_id, vec![vec![B1, B0, B0], vec![B0, B1, B0]], amount, + false, )); let ct_001 = CombinatorialToken([ @@ -358,6 +372,7 @@ fn split_vertical_followed_by_horizontal_split_no_parent() { market_id, vec![vec![B1, B0, B0], vec![B0, B1, B0], vec![B0, B0, B1]], amount, + false, )); assert_eq!(alice.free_balance(ct_001), 2 * amount); @@ -383,6 +398,7 @@ fn split_vertical_followed_by_horizontal_split_with_parent() { parent_market_id, vec![vec![B0, B0, B1], vec![B1, B1, B0]], parent_amount, + false, )); let child_market_id = create_market(Asset::Ztg, MarketType::Categorical(4)); @@ -425,6 +441,7 @@ fn split_vertical_followed_by_horizontal_split_with_parent() { child_market_id, vec![vec![B0, B0, B1, B1], vec![B1, B1, B0, B0]], child_amount_first_pass, + false, )); assert_ok!(CombinatorialTokens::split_position( alice.signed(), @@ -432,6 +449,7 @@ fn split_vertical_followed_by_horizontal_split_with_parent() { child_market_id, vec![vec![B1, B0, B0, B0], vec![B0, B1, B0, B0]], child_amount_first_pass, + false, )); assert_eq!(alice.free_balance(ct_001), parent_amount - child_amount_first_pass); @@ -451,6 +469,7 @@ fn split_vertical_followed_by_horizontal_split_with_parent() { child_market_id, vec![vec![B1, B0, B0, B0], vec![B0, B1, B0, B0], vec![B0, B0, B1, B1]], child_amount_second_pass, + false, )); let total_child_amount = child_amount_first_pass + child_amount_second_pass; @@ -488,6 +507,7 @@ fn split_horizontal_followed_by_merge_horizontal() { market_id, vec![vec![B0, B0, B1], vec![B1, B1, B0]], amount, + false, )); assert_ok!(CombinatorialTokens::split_position( @@ -496,6 +516,7 @@ fn split_horizontal_followed_by_merge_horizontal() { market_id, vec![vec![B1, B0, B0], vec![B0, B1, B0]], amount, + false, )); assert_ok!(CombinatorialTokens::merge_position( @@ -504,6 +525,7 @@ fn split_horizontal_followed_by_merge_horizontal() { market_id, vec![vec![B1, B0, B0], vec![B0, B1, B0]], amount, + false, )); assert_eq!(alice.free_balance(ct_001), _1); diff --git a/zrml/combinatorial-tokens/src/tests/merge_position.rs b/zrml/combinatorial-tokens/src/tests/merge_position.rs index b724e57c9..077685f29 100644 --- a/zrml/combinatorial-tokens/src/tests/merge_position.rs +++ b/zrml/combinatorial-tokens/src/tests/merge_position.rs @@ -41,20 +41,35 @@ fn merge_position_works_no_parent( let pallet = Account::new(Pallet::::account_id()).deposit(collateral, amount).unwrap(); + let parent_collection_id = None; let market_id = create_market(collateral, MarketType::Categorical(3)); - + let partition = vec![vec![B0, B0, B1], vec![B1, B1, B0]]; assert_ok!(CombinatorialTokens::merge_position( alice.signed(), - None, + parent_collection_id, market_id, - vec![vec![B0, B0, B1], vec![B1, B1, B0]], + partition.clone(), amount, + false, )); assert_eq!(alice.free_balance(ct_001), 0); assert_eq!(alice.free_balance(ct_110), 0); assert_eq!(alice.free_balance(collateral), _100); assert_eq!(pallet.free_balance(collateral), 0); + + System::assert_last_event( + Event::::TokenMerged { + who: alice.id, + parent_collection_id, + market_id, + partition, + assets_in: vec![ct_001, ct_110], + asset_out: collateral, + amount, + } + .into(), + ); }); } @@ -82,25 +97,39 @@ fn merge_position_works_parent() { .unwrap(); let _ = create_market(Asset::Ztg, MarketType::Categorical(3)); - let market_id = create_market(Asset::Ztg, MarketType::Categorical(4)); // Collection ID of [0, 0, 1]. - let parent_collection_id = [ + let parent_collection_id = Some([ 6, 44, 173, 50, 122, 106, 144, 185, 253, 19, 252, 218, 215, 241, 218, 37, 196, 112, 45, 133, 165, 48, 231, 189, 87, 123, 131, 18, 190, 5, 110, 93, - ]; - + ]); + let market_id = create_market(Asset::Ztg, MarketType::Categorical(4)); + let partition = vec![vec![B0, B1, B0, B1], vec![B1, B0, B1, B0]]; assert_ok!(CombinatorialTokens::merge_position( alice.signed(), - Some(parent_collection_id), + parent_collection_id, market_id, - vec![vec![B0, B1, B0, B1], vec![B1, B0, B1, B0]], + partition.clone(), amount, + false, )); assert_eq!(alice.free_balance(ct_001), amount); assert_eq!(alice.free_balance(ct_001_0101), 0); assert_eq!(alice.free_balance(ct_001_1010), 0); + + System::assert_last_event( + Event::::TokenMerged { + who: alice.id, + parent_collection_id, + market_id, + partition, + assets_in: vec![ct_001_0101, ct_001_1010], + asset_out: ct_001, + amount, + } + .into(), + ); }); } @@ -131,6 +160,7 @@ fn merge_position_horizontal_works() { market_id, vec![vec![B0, B1, B0], vec![B1, B0, B0]], amount, + false, )); assert_eq!(alice.free_balance(ct_110), amount); @@ -151,6 +181,7 @@ fn merge_position_fails_if_market_not_found() { 0, vec![vec![B0, B0, B1], vec![B1, B1, B0]], 1, + false, ), zrml_market_commons::Error::::MarketDoesNotExist, ); @@ -167,7 +198,14 @@ fn merge_position_fails_on_invalid_partition_length() { let partition = vec![vec![B1, B0, B1], vec![B0, B1]]; assert_noop!( - CombinatorialTokens::merge_position(alice.signed(), None, market_id, partition, _1,), + CombinatorialTokens::merge_position( + alice.signed(), + None, + market_id, + partition, + _1, + false + ), Error::::InvalidPartition ); }); @@ -183,7 +221,14 @@ fn merge_position_fails_on_trivial_partition_member() { let partition = vec![vec![B1, B0, B1], vec![B0, B0, B0]]; assert_noop!( - CombinatorialTokens::merge_position(alice.signed(), None, market_id, partition, _1,), + CombinatorialTokens::merge_position( + alice.signed(), + None, + market_id, + partition, + _1, + false + ), Error::::InvalidPartition ); }); @@ -199,7 +244,14 @@ fn merge_position_fails_on_overlapping_partition_members() { let partition = vec![vec![B1, B0, B1], vec![B0, B0, B1]]; assert_noop!( - CombinatorialTokens::merge_position(alice.signed(), None, market_id, partition, _1,), + CombinatorialTokens::merge_position( + alice.signed(), + None, + market_id, + partition, + _1, + false + ), Error::::InvalidPartition ); }); @@ -220,6 +272,7 @@ fn merge_position_fails_on_insufficient_funds() { market_id, vec![vec![B1, B0, B1], vec![B0, B1, B0]], _100, + false, ), orml_tokens::Error::::BalanceTooLow ); @@ -241,6 +294,7 @@ fn merge_position_fails_on_insufficient_funds_foreign_token() { market_id, vec![vec![B1, B0, B1], vec![B0, B1, B0]], _100, + false, ), orml_tokens::Error::::BalanceTooLow ); diff --git a/zrml/combinatorial-tokens/src/tests/redeem_position.rs b/zrml/combinatorial-tokens/src/tests/redeem_position.rs index a58381206..64a0d9944 100644 --- a/zrml/combinatorial-tokens/src/tests/redeem_position.rs +++ b/zrml/combinatorial-tokens/src/tests/redeem_position.rs @@ -22,11 +22,13 @@ use test_case::test_case; fn redeem_position_fails_on_no_payout_vector() { ExtBuilder::build().execute_with(|| { let alice = Account::new(0).deposit(Asset::Ztg, _100).unwrap(); + let market_id = 0; MockPayout::set_return_value(None); assert_noop!( - CombinatorialTokens::redeem_position(alice.signed(), None, 0, vec![]), + CombinatorialTokens::redeem_position(alice.signed(), None, market_id, vec![], false), Error::::PayoutVectorNotFound ); + assert!(MockPayout::called_once_with(market_id)); }); } @@ -36,7 +38,7 @@ fn redeem_position_fails_on_market_not_found() { let alice = Account::new(0).deposit(Asset::Ztg, _100).unwrap(); MockPayout::set_return_value(Some(vec![_1_2, _1_2])); assert_noop!( - CombinatorialTokens::redeem_position(alice.signed(), None, 0, vec![]), + CombinatorialTokens::redeem_position(alice.signed(), None, 0, vec![], false), zrml_market_commons::Error::::MarketDoesNotExist ); }); @@ -51,7 +53,7 @@ fn redeem_position_fails_on_incorrect_index_set(index_set: Vec) { MockPayout::set_return_value(Some(vec![_1_3, _1_3, _1_3])); let market_id = create_market(Asset::Ztg, MarketType::Categorical(3)); assert_noop!( - CombinatorialTokens::redeem_position(alice.signed(), None, market_id, index_set), + CombinatorialTokens::redeem_position(alice.signed(), None, market_id, index_set, false), Error::::InvalidIndexSet ); }); @@ -65,7 +67,7 @@ fn redeem_position_fails_if_tokens_have_to_value() { let market_id = create_market(Asset::Ztg, MarketType::Categorical(4)); let index_set = vec![B1, B0, B0, B1]; assert_noop!( - CombinatorialTokens::redeem_position(alice.signed(), None, market_id, index_set), + CombinatorialTokens::redeem_position(alice.signed(), None, market_id, index_set, false), Error::::TokenHasNoValue ); }); @@ -79,7 +81,7 @@ fn redeem_position_fails_if_user_holds_no_winning_tokens() { let market_id = create_market(Asset::Ztg, MarketType::Categorical(4)); let index_set = vec![B0, B1, B0, B1]; assert_noop!( - CombinatorialTokens::redeem_position(alice.signed(), None, market_id, index_set), + CombinatorialTokens::redeem_position(alice.signed(), None, market_id, index_set, false), Error::::NoTokensFound, ); }); @@ -93,22 +95,43 @@ fn redeem_position_works_sans_parent() { 225, 102, 57, 241, 199, 18, 226, 137, 68, 3, 219, 131, ]); let alice = Account::new(0).deposit(ct_110, _3).unwrap(); - let pallet = Account::new(Pallet::::account_id()).deposit(Asset::Ztg, _3).unwrap(); + let amount_in = _3; + let pallet = + Account::new(Pallet::::account_id()).deposit(Asset::Ztg, amount_in).unwrap(); MockPayout::set_return_value(Some(vec![_1_4, _1_2, _1_4])); + let parent_collection_id = None; let market_id = create_market(Asset::Ztg, MarketType::Categorical(3)); let index_set = vec![B1, B1, B0]; assert_ok!(CombinatorialTokens::redeem_position( alice.signed(), - None, + parent_collection_id, market_id, - index_set + index_set.clone(), + false, )); assert_eq!(alice.free_balance(ct_110), 0); - assert_eq!(alice.free_balance(Asset::Ztg), _2 + _1_4); + let amount_out = _2 + _1_4; + assert_eq!(alice.free_balance(Asset::Ztg), amount_out); assert_eq!(pallet.free_balance(Asset::Ztg), _3_4); + + System::assert_last_event( + Event::::TokenRedeemed { + who: alice.id, + parent_collection_id, + market_id, + index_set, + asset_in: ct_110, + amount_in, + asset_out: Asset::Ztg, + amount_out, + } + .into(), + ); + + assert!(MockPayout::called_once_with(market_id)); }); } @@ -124,27 +147,46 @@ fn redeem_position_works_with_parent() { 225, 211, 72, 142, 210, 98, 202, 168, 193, 245, 217, 239, 28, ]); - let alice = Account::new(0).deposit(ct_001_0101, _7).unwrap(); + let amount_in = _7; + let alice = Account::new(0).deposit(ct_001_0101, amount_in).unwrap(); MockPayout::set_return_value(Some(vec![_1_4, 0, _1_2, _1_4])); let _ = create_market(Asset::Ztg, MarketType::Categorical(3)); - let child_market_id = create_market(Asset::Ztg, MarketType::Categorical(4)); + let market_id = create_market(Asset::Ztg, MarketType::Categorical(4)); // Collection ID of [0, 0, 1]. - let parent_collection_id = [ + let parent_collection_id = Some([ 6, 44, 173, 50, 122, 106, 144, 185, 253, 19, 252, 218, 215, 241, 218, 37, 196, 112, 45, 133, 165, 48, 231, 189, 87, 123, 131, 18, 190, 5, 110, 93, - ]; + ]); let index_set = vec![B0, B1, B0, B1]; assert_ok!(CombinatorialTokens::redeem_position( alice.signed(), - Some(parent_collection_id), - child_market_id, - index_set + parent_collection_id, + market_id, + index_set.clone(), + false, )); assert_eq!(alice.free_balance(ct_001_0101), 0); - assert_eq!(alice.free_balance(ct_001), _1 + _3_4); + let amount_out = _1 + _3_4; + assert_eq!(alice.free_balance(ct_001), amount_out); + + System::assert_last_event( + Event::::TokenRedeemed { + who: alice.id, + parent_collection_id, + market_id, + index_set, + asset_in: ct_001_0101, + amount_in, + asset_out: ct_001, + amount_out, + } + .into(), + ); + + assert!(MockPayout::called_once_with(market_id)); }); } diff --git a/zrml/combinatorial-tokens/src/tests/split_position.rs b/zrml/combinatorial-tokens/src/tests/split_position.rs index 4a1460931..88873e02b 100644 --- a/zrml/combinatorial-tokens/src/tests/split_position.rs +++ b/zrml/combinatorial-tokens/src/tests/split_position.rs @@ -34,6 +34,7 @@ fn split_position_works_vertical_no_parent() { market_id, partition.clone(), amount, + false, )); let ct_001 = CombinatorialToken([ @@ -89,6 +90,7 @@ fn split_position_works_vertical_with_parent() { parent_market_id, vec![vec![B0, B0, B1], vec![B1, B1, B0]], parent_amount, + false, )); let child_market_id = create_market(Asset::Ztg, MarketType::Categorical(4)); @@ -105,6 +107,7 @@ fn split_position_works_vertical_with_parent() { child_market_id, partition.clone(), child_amount, + false, )); // Alice is left with 1 unit of [0, 0, 1], 2 units of [1, 1, 0] and one unit of each of the @@ -180,6 +183,7 @@ fn split_position_fails_if_market_not_found() { 0, vec![vec![B0, B0, B1], vec![B1, B1, B0]], 1, + false, ), zrml_market_commons::Error::::MarketDoesNotExist, ); @@ -196,7 +200,14 @@ fn split_position_fails_on_invalid_partition_length() { let partition = vec![vec![B1, B0, B1], vec![B0, B1]]; assert_noop!( - CombinatorialTokens::split_position(alice.signed(), None, market_id, partition, _1), + CombinatorialTokens::split_position( + alice.signed(), + None, + market_id, + partition, + _1, + false, + ), Error::::InvalidPartition ); }); @@ -212,7 +223,14 @@ fn split_position_fails_on_empty_partition_member() { let partition = vec![vec![B1, B0, B1], vec![B0, B0, B0]]; assert_noop!( - CombinatorialTokens::split_position(alice.signed(), None, market_id, partition, _1,), + CombinatorialTokens::split_position( + alice.signed(), + None, + market_id, + partition, + _1, + false + ), Error::::InvalidPartition ); }); @@ -228,7 +246,14 @@ fn split_position_fails_on_overlapping_partition_members() { let partition = vec![vec![B1, B0, B1], vec![B0, B0, B1]]; assert_noop!( - CombinatorialTokens::split_position(alice.signed(), None, market_id, partition, _1), + CombinatorialTokens::split_position( + alice.signed(), + None, + market_id, + partition, + _1, + false, + ), Error::::InvalidPartition ); }); @@ -243,7 +268,14 @@ fn split_position_fails_on_trivial_partition() { let partition = vec![vec![B1, B1, B1]]; assert_noop!( - CombinatorialTokens::split_position(alice.signed(), None, market_id, partition, _1), + CombinatorialTokens::split_position( + alice.signed(), + None, + market_id, + partition, + _1, + false + ), Error::::InvalidPartition ); }); @@ -264,6 +296,7 @@ fn split_position_fails_on_insufficient_funds_native_token_no_parent() { market_id, vec![vec![B1, B0, B1], vec![B0, B1, B0]], _100, + false, ), orml_currencies::Error::::BalanceTooLow ); @@ -285,6 +318,7 @@ fn split_position_fails_on_insufficient_funds_foreign_token_no_parent() { market_id, vec![vec![B1, B0, B1], vec![B0, B1, B0]], _100, + false, ), orml_currencies::Error::::BalanceTooLow ); @@ -317,6 +351,7 @@ fn split_position_vertical_fails_on_insufficient_funds_combinatorial_token() { market_id, vec![vec![B1, B0, B1, B0], vec![B0, B1, B0, B1]], _100, + false, ), orml_tokens::Error::::BalanceTooLow ); @@ -328,6 +363,7 @@ fn split_position_vertical_fails_on_insufficient_funds_combinatorial_token() { market_id, vec![vec![B1, B0, B1, B0], vec![B0, B1, B0, B1]], _99, + false, )); }); } @@ -352,6 +388,7 @@ fn split_position_horizontal_fails_on_insufficient_funds_combinatorial_token() { market_id, vec![vec![B1, B0, B0], vec![B0, B1, B0]], _100, + false, ), orml_tokens::Error::::BalanceTooLow ); @@ -363,6 +400,7 @@ fn split_position_horizontal_fails_on_insufficient_funds_combinatorial_token() { market_id, vec![vec![B1, B0, B0], vec![B0, B1, B0]], _99, + false, )); }); } diff --git a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/mod.rs b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/mod.rs index 4a437d956..f5f5d0a6f 100644 --- a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/mod.rs +++ b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/mod.rs @@ -57,15 +57,14 @@ pub(crate) fn get_collection_id( Some(bytes) } -const DECOMPRESS_HASH_MAX_ITERS: usize = 1_000; +const DECOMPRESS_HASH_MAX_ITERS: usize = 32; /// Decompresses a collection ID `hash` to a point of `alt_bn128`. The amount of work done can be /// forced to be independent of the input by setting the `force_max_work` flag. /// /// We don't have mathematical proof that the points of `alt_bn128` are distributed so that the /// required number of iterations is below the specified limit of iterations, but there's good -/// evidence that input hash requires more than `log_2(P) = 507.19338271000436` iterations. We -/// will use `1_000` iterations as maximum for now. +/// evidence that input hash requires more than `log_2(P) = 507.19338271000436` iterations. /// /// Provided the assumption above is correct, this function cannot return `None`. fn decompress_hash(hash: CombinatorialId, force_max_work: bool) -> Option { @@ -107,7 +106,7 @@ fn decompress_hash(hash: CombinatorialId, force_max_work: bool) -> Option. + +//! Autogenerated weights for zrml_combinatorial_tokens +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: `2024-10-24`, STEPS: `2`, REPEAT: `0`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `blackbird`, CPU: `` +//! EXECUTION: ``, WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` + +// Executed Command: +// ./target/release/zeitgeist +// benchmark +// pallet +// --chain=dev +// --steps=2 +// --repeat=0 +// --pallet=zrml_combinatorial_tokens +// --extrinsic=* +// --execution=native +// --wasm-execution=compiled +// --heap-pages=4096 +// --template=./misc/weight_template.hbs +// --header=./HEADER_GPL3 +// --output=./zrml/combinatorial-tokens/src/weights.rs + +#![allow(unused_parens)] +#![allow(unused_imports)] + +use core::marker::PhantomData; +use frame_support::{traits::Get, weights::Weight}; + +/// Trait containing the required functions for weight retrival within +/// zrml_combinatorial_tokens (automatically generated) +pub trait WeightInfoZeitgeist { + fn split_position_vertical_sans_parent(n: u32, ) -> Weight; + fn split_position_vertical_with_parent(n: u32, ) -> Weight; + fn split_position_horizontal(n: u32, ) -> Weight; + fn merge_position_vertical_sans_parent(n: u32, ) -> Weight; + fn merge_position_vertical_with_parent(n: u32, ) -> Weight; + fn merge_position_horizontal(n: u32, ) -> Weight; + fn redeem_position_sans_parent(n: u32, ) -> Weight; + fn redeem_position_with_parent(n: u32, ) -> Weight; +} + +/// Weight functions for zrml_combinatorial_tokens (automatically generated) +pub struct WeightInfo(PhantomData); +impl WeightInfoZeitgeist for WeightInfo { + /// Storage: `MarketCommons::Markets` (r:1 w:0) + /// Proof: `MarketCommons::Markets` (`max_values`: None, `max_size`: Some(708), added: 3183, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(132), added: 2607, mode: `MaxEncodedLen`) + /// Storage: `Tokens::Accounts` (r:32 w:32) + /// Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(137), added: 2612, mode: `MaxEncodedLen`) + /// Storage: `Tokens::TotalIssuance` (r:32 w:32) + /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) + /// The range of component `n` is `[2, 32]`. + fn split_position_vertical_sans_parent(_n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `441` + // Estimated: `84574` + // Minimum execution time: 1_923_000 nanoseconds. + Weight::from_parts(29_365_000_000, 84574) + .saturating_add(T::DbWeight::get().reads(66)) + .saturating_add(T::DbWeight::get().writes(65)) + } + /// Storage: `MarketCommons::Markets` (r:1 w:0) + /// Proof: `MarketCommons::Markets` (`max_values`: None, `max_size`: Some(708), added: 3183, mode: `MaxEncodedLen`) + /// Storage: `Tokens::Accounts` (r:33 w:33) + /// Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(137), added: 2612, mode: `MaxEncodedLen`) + /// Storage: `Tokens::TotalIssuance` (r:33 w:33) + /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) + /// The range of component `n` is `[2, 32]`. + fn split_position_vertical_with_parent(_n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `671` + // Estimated: `87186` + // Minimum execution time: 2_353_000 nanoseconds. + Weight::from_parts(37_193_000_000, 87186) + .saturating_add(T::DbWeight::get().reads(67)) + .saturating_add(T::DbWeight::get().writes(66)) + } + /// Storage: `MarketCommons::Markets` (r:1 w:0) + /// Proof: `MarketCommons::Markets` (`max_values`: None, `max_size`: Some(708), added: 3183, mode: `MaxEncodedLen`) + /// Storage: `Tokens::Accounts` (r:33 w:33) + /// Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(137), added: 2612, mode: `MaxEncodedLen`) + /// Storage: `Tokens::TotalIssuance` (r:33 w:33) + /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) + /// The range of component `n` is `[2, 32]`. + fn split_position_horizontal(_n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `633` + // Estimated: `87186` + // Minimum execution time: 2_773_000 nanoseconds. + Weight::from_parts(30_303_000_000, 87186) + .saturating_add(T::DbWeight::get().reads(67)) + .saturating_add(T::DbWeight::get().writes(66)) + } + /// Storage: `MarketCommons::Markets` (r:1 w:0) + /// Proof: `MarketCommons::Markets` (`max_values`: None, `max_size`: Some(708), added: 3183, mode: `MaxEncodedLen`) + /// Storage: `Tokens::Accounts` (r:32 w:32) + /// Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(137), added: 2612, mode: `MaxEncodedLen`) + /// Storage: `Tokens::TotalIssuance` (r:32 w:32) + /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(132), added: 2607, mode: `MaxEncodedLen`) + /// The range of component `n` is `[2, 32]`. + fn merge_position_vertical_sans_parent(_n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `624 + n * (160 ±0)` + // Estimated: `84574` + // Minimum execution time: 1_889_000 nanoseconds. + Weight::from_parts(29_394_000_000, 84574) + .saturating_add(T::DbWeight::get().reads(66)) + .saturating_add(T::DbWeight::get().writes(65)) + } + /// Storage: `MarketCommons::Markets` (r:1 w:0) + /// Proof: `MarketCommons::Markets` (`max_values`: None, `max_size`: Some(708), added: 3183, mode: `MaxEncodedLen`) + /// Storage: `Tokens::Accounts` (r:33 w:33) + /// Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(137), added: 2612, mode: `MaxEncodedLen`) + /// Storage: `Tokens::TotalIssuance` (r:33 w:33) + /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) + /// The range of component `n` is `[2, 32]`. + fn merge_position_vertical_with_parent(_n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `518 + n * (160 ±0)` + // Estimated: `87186` + // Minimum execution time: 2_376_000 nanoseconds. + Weight::from_parts(37_564_000_000, 87186) + .saturating_add(T::DbWeight::get().reads(67)) + .saturating_add(T::DbWeight::get().writes(66)) + } + /// Storage: `MarketCommons::Markets` (r:1 w:0) + /// Proof: `MarketCommons::Markets` (`max_values`: None, `max_size`: Some(708), added: 3183, mode: `MaxEncodedLen`) + /// Storage: `Tokens::Accounts` (r:33 w:33) + /// Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(137), added: 2612, mode: `MaxEncodedLen`) + /// Storage: `Tokens::TotalIssuance` (r:33 w:33) + /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) + /// The range of component `n` is `[2, 32]`. + fn merge_position_horizontal(_n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `480 + n * (160 ±0)` + // Estimated: `87186` + // Minimum execution time: 2_760_000 nanoseconds. + Weight::from_parts(30_589_000_000, 87186) + .saturating_add(T::DbWeight::get().reads(67)) + .saturating_add(T::DbWeight::get().writes(66)) + } + /// Storage: `MarketCommons::Markets` (r:1 w:0) + /// Proof: `MarketCommons::Markets` (`max_values`: None, `max_size`: Some(708), added: 3183, mode: `MaxEncodedLen`) + /// Storage: `Tokens::Accounts` (r:1 w:1) + /// Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(137), added: 2612, mode: `MaxEncodedLen`) + /// Storage: `Tokens::TotalIssuance` (r:1 w:1) + /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(132), added: 2607, mode: `MaxEncodedLen`) + /// The range of component `n` is `[2, 32]`. + fn redeem_position_sans_parent(_n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `780` + // Estimated: `4173` + // Minimum execution time: 979_000 nanoseconds. + Weight::from_parts(986_000_000, 4173) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(3)) + } + /// Storage: `MarketCommons::Markets` (r:1 w:0) + /// Proof: `MarketCommons::Markets` (`max_values`: None, `max_size`: Some(708), added: 3183, mode: `MaxEncodedLen`) + /// Storage: `Tokens::Accounts` (r:2 w:2) + /// Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(137), added: 2612, mode: `MaxEncodedLen`) + /// Storage: `Tokens::TotalIssuance` (r:2 w:2) + /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) + /// The range of component `n` is `[2, 32]`. + fn redeem_position_with_parent(_n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `674` + // Estimated: `6214` + // Minimum execution time: 1_193_000 nanoseconds. + Weight::from_parts(1_215_000_000, 6214) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(4)) + } +} diff --git a/zrml/prediction-markets/src/lib.rs b/zrml/prediction-markets/src/lib.rs index abaf617df..8d924151e 100644 --- a/zrml/prediction-markets/src/lib.rs +++ b/zrml/prediction-markets/src/lib.rs @@ -27,6 +27,7 @@ mod benchmarks; pub mod migrations; pub mod mock; mod tests; +pub mod types; pub mod weights; pub use pallet::*; diff --git a/zrml/prediction-markets/src/types/combinatorial_tokens_benchmark_helper.rs b/zrml/prediction-markets/src/types/combinatorial_tokens_benchmark_helper.rs new file mode 100644 index 000000000..1a8019122 --- /dev/null +++ b/zrml/prediction-markets/src/types/combinatorial_tokens_benchmark_helper.rs @@ -0,0 +1,40 @@ +use crate::{BalanceOf, Config, MarketIdOf}; +use alloc::vec::Vec; +use core::marker::PhantomData; +use sp_runtime::{traits::Zero, DispatchResult}; +use zeitgeist_primitives::{ + traits::{CombinatorialTokensBenchmarkHelper, MarketCommonsPalletApi}, + types::{MarketStatus, OutcomeReport}, +}; + +pub struct PredictionMarketsCombinatorialTokensBenchmarkHelper(PhantomData); + +impl CombinatorialTokensBenchmarkHelper + for PredictionMarketsCombinatorialTokensBenchmarkHelper +where + T: Config, +{ + type Balance = BalanceOf; + type MarketId = MarketIdOf; + + /// Aggressively modifies the market specified by `market_id` to be resolved. The payout vector + /// must contain exactly one non-zero entry. Does absolutely no error management. + fn setup_payout_vector( + market_id: Self::MarketId, + payout_vector: Option>, + ) -> DispatchResult { + let payout_vector = payout_vector.unwrap(); + let index = payout_vector.iter().position(|&value| !value.is_zero()).unwrap(); + + as MarketCommonsPalletApi>::mutate_market( + &market_id, + |market| { + market.resolved_outcome = + Some(OutcomeReport::Categorical(index.try_into().unwrap())); + market.status = MarketStatus::Resolved; + + Ok(()) + }, + ) + } +} diff --git a/zrml/prediction-markets/src/types/mod.rs b/zrml/prediction-markets/src/types/mod.rs new file mode 100644 index 000000000..04d4ef801 --- /dev/null +++ b/zrml/prediction-markets/src/types/mod.rs @@ -0,0 +1,3 @@ +mod combinatorial_tokens_benchmark_helper; + +pub use combinatorial_tokens_benchmark_helper::PredictionMarketsCombinatorialTokensBenchmarkHelper; From a56f9296e42c5444046001f9cd19ec9912359002 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Thu, 24 Oct 2024 21:44:50 +0200 Subject: [PATCH 42/73] Fix formatting and license notes (#1388) * Fix formatting * Fix copyright --- .../combinatorial_tokens_benchmark_helper.rs | 17 ++++++++++ .../src/mock/types/benchmark_helper.rs | 17 ++++++++++ zrml/combinatorial-tokens/src/weights.rs | 32 +++++++++---------- .../combinatorial_tokens_benchmark_helper.rs | 17 ++++++++++ zrml/prediction-markets/src/types/mod.rs | 17 ++++++++++ 5 files changed, 84 insertions(+), 16 deletions(-) diff --git a/primitives/src/traits/combinatorial_tokens_benchmark_helper.rs b/primitives/src/traits/combinatorial_tokens_benchmark_helper.rs index 267d20b56..dbb8cf889 100644 --- a/primitives/src/traits/combinatorial_tokens_benchmark_helper.rs +++ b/primitives/src/traits/combinatorial_tokens_benchmark_helper.rs @@ -1,3 +1,20 @@ +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + use alloc::vec::Vec; use sp_runtime::DispatchResult; diff --git a/zrml/combinatorial-tokens/src/mock/types/benchmark_helper.rs b/zrml/combinatorial-tokens/src/mock/types/benchmark_helper.rs index 36d1438fa..ea3f3309d 100644 --- a/zrml/combinatorial-tokens/src/mock/types/benchmark_helper.rs +++ b/zrml/combinatorial-tokens/src/mock/types/benchmark_helper.rs @@ -1,3 +1,20 @@ +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + use crate::{ mock::{runtime::Runtime, types::MockPayout}, BalanceOf, MarketIdOf, diff --git a/zrml/combinatorial-tokens/src/weights.rs b/zrml/combinatorial-tokens/src/weights.rs index e96cc7f6c..b509b599d 100644 --- a/zrml/combinatorial-tokens/src/weights.rs +++ b/zrml/combinatorial-tokens/src/weights.rs @@ -49,14 +49,14 @@ use frame_support::{traits::Get, weights::Weight}; /// Trait containing the required functions for weight retrival within /// zrml_combinatorial_tokens (automatically generated) pub trait WeightInfoZeitgeist { - fn split_position_vertical_sans_parent(n: u32, ) -> Weight; - fn split_position_vertical_with_parent(n: u32, ) -> Weight; - fn split_position_horizontal(n: u32, ) -> Weight; - fn merge_position_vertical_sans_parent(n: u32, ) -> Weight; - fn merge_position_vertical_with_parent(n: u32, ) -> Weight; - fn merge_position_horizontal(n: u32, ) -> Weight; - fn redeem_position_sans_parent(n: u32, ) -> Weight; - fn redeem_position_with_parent(n: u32, ) -> Weight; + fn split_position_vertical_sans_parent(n: u32) -> Weight; + fn split_position_vertical_with_parent(n: u32) -> Weight; + fn split_position_horizontal(n: u32) -> Weight; + fn merge_position_vertical_sans_parent(n: u32) -> Weight; + fn merge_position_vertical_with_parent(n: u32) -> Weight; + fn merge_position_horizontal(n: u32) -> Weight; + fn redeem_position_sans_parent(n: u32) -> Weight; + fn redeem_position_with_parent(n: u32) -> Weight; } /// Weight functions for zrml_combinatorial_tokens (automatically generated) @@ -71,7 +71,7 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `Tokens::TotalIssuance` (r:32 w:32) /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 32]`. - fn split_position_vertical_sans_parent(_n: u32, ) -> Weight { + fn split_position_vertical_sans_parent(_n: u32) -> Weight { // Proof Size summary in bytes: // Measured: `441` // Estimated: `84574` @@ -87,7 +87,7 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `Tokens::TotalIssuance` (r:33 w:33) /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 32]`. - fn split_position_vertical_with_parent(_n: u32, ) -> Weight { + fn split_position_vertical_with_parent(_n: u32) -> Weight { // Proof Size summary in bytes: // Measured: `671` // Estimated: `87186` @@ -103,7 +103,7 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `Tokens::TotalIssuance` (r:33 w:33) /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 32]`. - fn split_position_horizontal(_n: u32, ) -> Weight { + fn split_position_horizontal(_n: u32) -> Weight { // Proof Size summary in bytes: // Measured: `633` // Estimated: `87186` @@ -121,7 +121,7 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(132), added: 2607, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 32]`. - fn merge_position_vertical_sans_parent(_n: u32, ) -> Weight { + fn merge_position_vertical_sans_parent(_n: u32) -> Weight { // Proof Size summary in bytes: // Measured: `624 + n * (160 ±0)` // Estimated: `84574` @@ -137,7 +137,7 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `Tokens::TotalIssuance` (r:33 w:33) /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 32]`. - fn merge_position_vertical_with_parent(_n: u32, ) -> Weight { + fn merge_position_vertical_with_parent(_n: u32) -> Weight { // Proof Size summary in bytes: // Measured: `518 + n * (160 ±0)` // Estimated: `87186` @@ -153,7 +153,7 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `Tokens::TotalIssuance` (r:33 w:33) /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 32]`. - fn merge_position_horizontal(_n: u32, ) -> Weight { + fn merge_position_horizontal(_n: u32) -> Weight { // Proof Size summary in bytes: // Measured: `480 + n * (160 ±0)` // Estimated: `87186` @@ -171,7 +171,7 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(132), added: 2607, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 32]`. - fn redeem_position_sans_parent(_n: u32, ) -> Weight { + fn redeem_position_sans_parent(_n: u32) -> Weight { // Proof Size summary in bytes: // Measured: `780` // Estimated: `4173` @@ -187,7 +187,7 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `Tokens::TotalIssuance` (r:2 w:2) /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 32]`. - fn redeem_position_with_parent(_n: u32, ) -> Weight { + fn redeem_position_with_parent(_n: u32) -> Weight { // Proof Size summary in bytes: // Measured: `674` // Estimated: `6214` diff --git a/zrml/prediction-markets/src/types/combinatorial_tokens_benchmark_helper.rs b/zrml/prediction-markets/src/types/combinatorial_tokens_benchmark_helper.rs index 1a8019122..49e649453 100644 --- a/zrml/prediction-markets/src/types/combinatorial_tokens_benchmark_helper.rs +++ b/zrml/prediction-markets/src/types/combinatorial_tokens_benchmark_helper.rs @@ -1,3 +1,20 @@ +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + use crate::{BalanceOf, Config, MarketIdOf}; use alloc::vec::Vec; use core::marker::PhantomData; diff --git a/zrml/prediction-markets/src/types/mod.rs b/zrml/prediction-markets/src/types/mod.rs index 04d4ef801..0325d79b5 100644 --- a/zrml/prediction-markets/src/types/mod.rs +++ b/zrml/prediction-markets/src/types/mod.rs @@ -1,3 +1,20 @@ +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + mod combinatorial_tokens_benchmark_helper; pub use combinatorial_tokens_benchmark_helper::PredictionMarketsCombinatorialTokensBenchmarkHelper; From 84396ca2bc59b00f4d78f0b7ed77a3798a471a68 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Wed, 30 Oct 2024 12:38:00 +0100 Subject: [PATCH 43/73] Implement Combinatorial Pools (#1389) * Define and implement `CombinatorialTokensApi` * Abstract position calculation * Implement `CombinatorialTokensUnsafeApi::combinatorial_position` * Add `CombinatorialTokens*Api` to `neo-swaps` * Fix formatting * Add copyright notices * . * . * Implement `deploy_combinatorial_pool` * Replace `market_id` with `pool_id` where appropriate * pool storage * . * Implement `PoolType` * Use `PoolOperations::is_active` * Use market ID for complete set operations * Use PoolId more * Rewrite `distribute_fees` to make use of all markets * . * Fix duplicate pool problem / adapter * . * Fix sell tests * Fix really annoying problem * clean up tests * . * . * Update copyright * Fix tests --- Cargo.lock | 2 + primitives/src/traits.rs | 4 + .../src/traits/combinatorial_tokens_api.rs | 36 + .../traits/combinatorial_tokens_unsafe_api.rs | 42 ++ primitives/src/types.rs | 36 +- runtime/battery-station/src/parameters.rs | 1 + runtime/common/src/lib.rs | 4 + runtime/zeitgeist/src/parameters.rs | 1 + zrml/combinatorial-tokens/src/lib.rs | 278 +++++--- zrml/combinatorial-tokens/src/types/mod.rs | 2 + .../src/types/transmutation_type.rs | 29 + zrml/hybrid-router/Cargo.toml | 5 +- zrml/hybrid-router/src/lib.rs | 2 - zrml/hybrid-router/src/mock.rs | 52 +- zrml/hybrid-router/src/tests/buy.rs | 8 +- zrml/hybrid-router/src/tests/sell.rs | 8 +- zrml/neo-swaps/Cargo.toml | 4 + zrml/neo-swaps/src/benchmarking.rs | 2 +- zrml/neo-swaps/src/lib.rs | 618 ++++++++++++++---- .../neo-swaps/src/liquidity_tree/tests/mod.rs | 4 +- .../src/liquidity_tree/traits/mod.rs | 4 +- zrml/neo-swaps/src/macros.rs | 7 +- zrml/neo-swaps/src/mock.rs | 54 +- zrml/neo-swaps/src/pool_storage.rs | 56 ++ zrml/neo-swaps/src/tests/buy.rs | 31 +- zrml/neo-swaps/src/tests/combo_buy.rs | 244 +++---- zrml/neo-swaps/src/tests/combo_sell.rs | 403 ++++++------ .../src/tests/deploy_combinatorial_pool.rs | 515 +++++++++++++++ zrml/neo-swaps/src/tests/deploy_pool.rs | 2 + zrml/neo-swaps/src/tests/exit.rs | 4 +- zrml/neo-swaps/src/tests/join.rs | 4 +- zrml/neo-swaps/src/tests/mod.rs | 31 + zrml/neo-swaps/src/tests/sell.rs | 32 +- zrml/neo-swaps/src/tests/withdraw_fees.rs | 2 +- zrml/neo-swaps/src/traits/mod.rs | 8 +- zrml/neo-swaps/src/traits/pool_operations.rs | 3 + zrml/neo-swaps/src/traits/pool_storage.rs | 33 + .../types/decision_market_benchmark_helper.rs | 18 +- .../src/types/decision_market_oracle.rs | 13 +- zrml/neo-swaps/src/types/mod.rs | 2 + zrml/neo-swaps/src/types/pool.rs | 23 +- zrml/neo-swaps/src/types/pool_type.rs | 49 ++ 42 files changed, 2054 insertions(+), 622 deletions(-) create mode 100644 primitives/src/traits/combinatorial_tokens_api.rs create mode 100644 primitives/src/traits/combinatorial_tokens_unsafe_api.rs create mode 100644 zrml/combinatorial-tokens/src/types/transmutation_type.rs create mode 100644 zrml/neo-swaps/src/pool_storage.rs create mode 100644 zrml/neo-swaps/src/tests/deploy_combinatorial_pool.rs create mode 100644 zrml/neo-swaps/src/traits/pool_storage.rs create mode 100644 zrml/neo-swaps/src/types/pool_type.rs diff --git a/Cargo.lock b/Cargo.lock index 66f78b6db..45d4359cc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15310,6 +15310,7 @@ dependencies = [ "test-case", "zeitgeist-primitives", "zrml-authorized", + "zrml-combinatorial-tokens", "zrml-court", "zrml-global-disputes", "zrml-hybrid-router", @@ -15372,6 +15373,7 @@ dependencies = [ "typenum", "zeitgeist-primitives", "zrml-authorized", + "zrml-combinatorial-tokens", "zrml-court", "zrml-global-disputes", "zrml-market-commons", diff --git a/primitives/src/traits.rs b/primitives/src/traits.rs index 5ad12e800..cd36b113e 100644 --- a/primitives/src/traits.rs +++ b/primitives/src/traits.rs @@ -16,7 +16,9 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . +mod combinatorial_tokens_api; mod combinatorial_tokens_benchmark_helper; +mod combinatorial_tokens_unsafe_api; mod complete_set_operations_api; mod deploy_pool_api; mod dispute_api; @@ -32,7 +34,9 @@ mod payout_api; mod swaps; mod zeitgeist_asset; +pub use combinatorial_tokens_api::*; pub use combinatorial_tokens_benchmark_helper::*; +pub use combinatorial_tokens_unsafe_api::*; pub use complete_set_operations_api::*; pub use deploy_pool_api::*; pub use dispute_api::*; diff --git a/primitives/src/traits/combinatorial_tokens_api.rs b/primitives/src/traits/combinatorial_tokens_api.rs new file mode 100644 index 000000000..33230d703 --- /dev/null +++ b/primitives/src/traits/combinatorial_tokens_api.rs @@ -0,0 +1,36 @@ +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use crate::types::SplitPositionDispatchInfo; +use alloc::vec::Vec; +use sp_runtime::DispatchError; + +pub trait CombinatorialTokensApi { + type AccountId; + type Balance; + type CombinatorialId; + type MarketId; + + fn split_position( + who: Self::AccountId, + parent_collection_id: Option, + market_id: Self::MarketId, + partition: Vec>, + amount: Self::Balance, + force_max_work: bool, + ) -> Result, DispatchError>; +} diff --git a/primitives/src/traits/combinatorial_tokens_unsafe_api.rs b/primitives/src/traits/combinatorial_tokens_unsafe_api.rs new file mode 100644 index 000000000..4aca7c69f --- /dev/null +++ b/primitives/src/traits/combinatorial_tokens_unsafe_api.rs @@ -0,0 +1,42 @@ +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use crate::types::Asset; +use alloc::vec::Vec; +use sp_runtime::DispatchResult; + +// Very fast and very unsafe API for splitting and merging combinatorial tokens. Calling the exposed +// functions with a bad `assets` argument can break the reserve. +pub trait CombinatorialTokensUnsafeApi { + type AccountId; + type Balance; + type MarketId; + + fn split_position_unsafe( + who: Self::AccountId, + collateral: Asset, + assets: Vec>, + amount: Self::Balance, + ) -> DispatchResult; + + fn merge_position_unsafe( + who: Self::AccountId, + collateral: Asset, + assets: Vec>, + amount: Self::Balance, + ) -> DispatchResult; +} diff --git a/primitives/src/types.rs b/primitives/src/types.rs index 9b14f097f..d354399a5 100644 --- a/primitives/src/types.rs +++ b/primitives/src/types.rs @@ -16,21 +16,25 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . +use crate::traits::CombinatorialTokensBenchmarkHelper; pub use crate::{ asset::*, market::*, max_runtime_usize::*, outcome_report::OutcomeReport, proxy_type::*, serde_wrapper::*, }; -#[cfg(feature = "arbitrary")] -use arbitrary::{Arbitrary, Result, Unstructured}; -use frame_support::weights::Weight; +use alloc::vec::Vec; +use core::marker::PhantomData; +use frame_support::{dispatch::PostDispatchInfo, weights::Weight}; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; use sp_runtime::{ generic, traits::{BlakeTwo256, IdentifyAccount, Verify}, - MultiSignature, OpaqueExtrinsic, + DispatchResult, MultiSignature, OpaqueExtrinsic, }; +#[cfg(feature = "arbitrary")] +use arbitrary::{Arbitrary, Result, Unstructured}; + /// Signed counter-part of Balance pub type Amount = i128; @@ -180,3 +184,27 @@ pub struct XcmMetadata { /// Should be updated regularly. pub fee_factor: Option, } + +pub struct NoopCombinatorialTokensBenchmarkHelper( + PhantomData<(Balance, MarketId)>, +); + +impl CombinatorialTokensBenchmarkHelper + for NoopCombinatorialTokensBenchmarkHelper +{ + type Balance = Balance; + type MarketId = MarketId; + + fn setup_payout_vector( + _market_id: Self::MarketId, + _payout: Option>, + ) -> DispatchResult { + Ok(()) + } +} + +pub struct SplitPositionDispatchInfo { + pub collection_ids: Vec, + pub position_ids: Vec>, + pub post_dispatch_info: PostDispatchInfo, +} diff --git a/runtime/battery-station/src/parameters.rs b/runtime/battery-station/src/parameters.rs index 214410ec6..3c1757791 100644 --- a/runtime/battery-station/src/parameters.rs +++ b/runtime/battery-station/src/parameters.rs @@ -203,6 +203,7 @@ parameter_types! { pub const NeoSwapsMaxSwapFee: Balance = 10 * CENT; pub const NeoSwapsPalletId: PalletId = NS_PALLET_ID; pub const MaxLiquidityTreeDepth: u32 = 9u32; + pub const MaxSplits: u16 = 128u16; // ORML pub const GetNativeCurrencyId: CurrencyId = Asset::Ztg; diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs index ea6a299c1..2b26b806a 100644 --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -1299,6 +1299,9 @@ macro_rules! impl_config_traits { common_runtime::impl_market_creator_fees!(); impl zrml_neo_swaps::Config for Runtime { + type CombinatorialId = CombinatorialId; + type CombinatorialTokens = CombinatorialTokens; + type CombinatorialTokensUnsafe = CombinatorialTokens; type CompleteSetOperations = PredictionMarkets; type ExternalFees = MarketCreatorFee; type MarketCommons = MarketCommons; @@ -1307,6 +1310,7 @@ macro_rules! impl_config_traits { type RuntimeEvent = RuntimeEvent; type WeightInfo = zrml_neo_swaps::weights::WeightInfo; type MaxLiquidityTreeDepth = MaxLiquidityTreeDepth; + type MaxSplits = MaxSplits; type MaxSwapFee = NeoSwapsMaxSwapFee; type PalletId = NeoSwapsPalletId; } diff --git a/runtime/zeitgeist/src/parameters.rs b/runtime/zeitgeist/src/parameters.rs index 9792b5f35..8f89dc422 100644 --- a/runtime/zeitgeist/src/parameters.rs +++ b/runtime/zeitgeist/src/parameters.rs @@ -203,6 +203,7 @@ parameter_types! { pub const NeoSwapsMaxSwapFee: Balance = 10 * CENT; pub const NeoSwapsPalletId: PalletId = NS_PALLET_ID; pub const MaxLiquidityTreeDepth: u32 = 9u32; + pub const MaxSplits: u16 = 128u16; // ORML pub const GetNativeCurrencyId: CurrencyId = Asset::Ztg; diff --git a/zrml/combinatorial-tokens/src/lib.rs b/zrml/combinatorial-tokens/src/lib.rs index 0706ca56a..7feb249b3 100644 --- a/zrml/combinatorial-tokens/src/lib.rs +++ b/zrml/combinatorial-tokens/src/lib.rs @@ -38,7 +38,9 @@ pub use pallet::*; #[frame_support::pallet] mod pallet { - use crate::{traits::CombinatorialIdManager, weights::WeightInfoZeitgeist}; + use crate::{ + traits::CombinatorialIdManager, types::TransmutationType, weights::WeightInfoZeitgeist, + }; use alloc::{vec, vec::Vec}; use core::marker::PhantomData; use frame_support::{ @@ -53,12 +55,14 @@ mod pallet { use orml_traits::MultiCurrency; use sp_runtime::{ traits::{AccountIdConversion, Get, Zero}, - DispatchError, SaturatedConversion, + DispatchError, DispatchResult, SaturatedConversion, }; use zeitgeist_primitives::{ math::{checked_ops_res::CheckedAddRes, fixed::FixedMul}, - traits::{MarketCommonsPalletApi, PayoutApi}, - types::{Asset, CombinatorialId}, + traits::{ + CombinatorialTokensApi, CombinatorialTokensUnsafeApi, MarketCommonsPalletApi, PayoutApi, + }, + types::{Asset, CombinatorialId, SplitPositionDispatchInfo}, }; #[cfg(feature = "runtime-benchmarks")] @@ -104,6 +108,8 @@ mod pallet { <::CombinatorialIdManager as CombinatorialIdManager>::CombinatorialId; pub(crate) type MarketIdOf = <::MarketCommons as MarketCommonsPalletApi>::MarketId; + pub(crate) type SplitPositionDispatchInfoOf = + SplitPositionDispatchInfo, MarketIdOf>; pub(crate) const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); @@ -208,14 +214,17 @@ mod pallet { force_max_work: bool, ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; - Self::do_split_position( + + let SplitPositionDispatchInfo { post_dispatch_info, .. } = Self::do_split_position( who, parent_collection_id, market_id, partition, amount, force_max_work, - ) + )?; + + DispatchResultWithPostInfo::Ok(post_dispatch_info) } #[pallet::call_index(1)] @@ -279,64 +288,44 @@ mod pallet { partition: Vec>, amount: BalanceOf, force_max_work: bool, - ) -> DispatchResultWithPostInfo { - let market = T::MarketCommons::market(&market_id)?; - let collateral_token = market.base_asset; - - let free_index_set = Self::free_index_set(market_id, &partition)?; - - // Destroy/store the tokens to be split. - let (weight, split_asset) = if !free_index_set.iter().any(|&i| i) { - // Vertical split. - if let Some(pci) = parent_collection_id { - // Split combinatorial token into higher level position. Destroy the tokens. - let position_id = - T::CombinatorialIdManager::get_position_id(collateral_token, pci); - let position = Asset::CombinatorialToken(position_id); + ) -> Result, DispatchError> { + let (transmutation_type, position) = Self::transmutation_asset( + parent_collection_id, + market_id, + partition.clone(), + force_max_work, + )?; + // Destroy the token to be split. + let weight = match transmutation_type { + TransmutationType::VerticalWithParent => { + // Split combinatorial token into higher level position. // This will fail if the market has a different collateral than the previous // markets. FIXME A cleaner error message would be nice though... T::MultiCurrency::ensure_can_withdraw(position, &who, amount)?; T::MultiCurrency::withdraw(position, &who, amount)?; - let weight = T::WeightInfo::split_position_vertical_with_parent( + T::WeightInfo::split_position_vertical_with_parent( partition.len().saturated_into(), - ); - - (weight, position) - } else { + ) + } + TransmutationType::VerticalSansParent => { // Split collateral into first level position. Store the collateral in the // pallet account. This is the legacy `buy_complete_set`. - T::MultiCurrency::ensure_can_withdraw(collateral_token, &who, amount)?; - T::MultiCurrency::transfer( - collateral_token, - &who, - &Self::account_id(), - amount, - )?; - - let weight = T::WeightInfo::split_position_vertical_sans_parent( - partition.len().saturated_into(), - ); + T::MultiCurrency::ensure_can_withdraw(position, &who, amount)?; + T::MultiCurrency::transfer(position, &who, &Self::account_id(), amount)?; - (weight, collateral_token) + T::WeightInfo::split_position_vertical_sans_parent( + partition.len().saturated_into(), + ) } - } else { - // Horizontal split. - let remaining_index_set = free_index_set.into_iter().map(|i| !i).collect(); - let position = Self::position_from_parent_collection( - parent_collection_id, - market_id, - remaining_index_set, - force_max_work, - )?; - T::MultiCurrency::ensure_can_withdraw(position, &who, amount)?; - T::MultiCurrency::withdraw(position, &who, amount)?; - - let weight = - T::WeightInfo::split_position_horizontal(partition.len().saturated_into()); + TransmutationType::Horizontal => { + // Horizontal split. + T::MultiCurrency::ensure_can_withdraw(position, &who, amount)?; + T::MultiCurrency::withdraw(position, &who, amount)?; - (weight, position) + T::WeightInfo::split_position_horizontal(partition.len().saturated_into()) + } }; // Deposit the new tokens. @@ -368,13 +357,19 @@ mod pallet { parent_collection_id, market_id, partition, - asset_in: split_asset, - assets_out: positions, - collection_ids, + asset_in: position, + assets_out: positions.clone(), + collection_ids: collection_ids.clone(), amount, }); - Ok(Some(weight).into()) + let dispatch_info = SplitPositionDispatchInfo { + collection_ids, + position_ids: positions, + post_dispatch_info: Some(weight).into(), + }; + + Ok(dispatch_info) } #[require_transactional] @@ -386,10 +381,12 @@ mod pallet { amount: BalanceOf, force_max_work: bool, ) -> DispatchResultWithPostInfo { - let market = T::MarketCommons::market(&market_id)?; - let collateral_token = market.base_asset; - - let free_index_set = Self::free_index_set(market_id, &partition)?; + let (transmutation_type, position) = Self::transmutation_asset( + parent_collection_id, + market_id, + partition.clone(), + force_max_work, + )?; // Destroy the old tokens. let positions = partition @@ -410,52 +407,30 @@ mod pallet { T::MultiCurrency::withdraw(position, &who, amount)?; } - // Destroy/store the tokens to be split. - let (weight, merged_token) = if !free_index_set.iter().any(|&i| i) { - // Vertical merge. - if let Some(pci) = parent_collection_id { - // Merge combinatorial token into higher level position. Destroy the tokens. - let position_id = - T::CombinatorialIdManager::get_position_id(collateral_token, pci); - let position = Asset::CombinatorialToken(position_id); + let weight = match transmutation_type { + TransmutationType::VerticalWithParent => { + // Merge combinatorial token into higher level position. T::MultiCurrency::deposit(position, &who, amount)?; - let weight = T::WeightInfo::merge_position_vertical_with_parent( + T::WeightInfo::merge_position_vertical_with_parent( partition.len().saturated_into(), - ); - - (weight, position) - } else { + ) + } + TransmutationType::VerticalSansParent => { // Merge first-level tokens into collateral. Move collateral from the pallet // account to the user's wallet. This is the legacy `sell_complete_set`. - T::MultiCurrency::transfer( - collateral_token, - &Self::account_id(), - &who, - amount, - )?; - - let weight = T::WeightInfo::merge_position_vertical_sans_parent( - partition.len().saturated_into(), - ); + T::MultiCurrency::transfer(position, &Self::account_id(), &who, amount)?; - (weight, collateral_token) + T::WeightInfo::merge_position_vertical_sans_parent( + partition.len().saturated_into(), + ) } - } else { - // Horizontal merge. - let remaining_index_set = free_index_set.into_iter().map(|i| !i).collect(); - let position = Self::position_from_parent_collection( - parent_collection_id, - market_id, - remaining_index_set, - force_max_work, - )?; - T::MultiCurrency::deposit(position, &who, amount)?; - - let weight = - T::WeightInfo::merge_position_horizontal(partition.len().saturated_into()); + TransmutationType::Horizontal => { + // Horizontal merge. + T::MultiCurrency::deposit(position, &who, amount)?; - (weight, position) + T::WeightInfo::merge_position_horizontal(partition.len().saturated_into()) + } }; Self::deposit_event(Event::::TokenMerged { @@ -463,7 +438,7 @@ mod pallet { parent_collection_id, market_id, partition, - asset_out: merged_token, + asset_out: position, assets_in: positions, amount, }); @@ -584,6 +559,42 @@ mod pallet { Ok(free_index_set) } + pub(crate) fn transmutation_asset( + parent_collection_id: Option>, + market_id: MarketIdOf, + partition: Vec>, + force_max_work: bool, + ) -> Result<(TransmutationType, AssetOf), DispatchError> { + let market = T::MarketCommons::market(&market_id)?; + let collateral_token = market.base_asset; + let free_index_set = Self::free_index_set(market_id, &partition)?; + + let result = if !free_index_set.iter().any(|&i| i) { + // Vertical merge. + if let Some(pci) = parent_collection_id { + let position_id = + T::CombinatorialIdManager::get_position_id(collateral_token, pci); + let position = Asset::CombinatorialToken(position_id); + + (TransmutationType::VerticalWithParent, position) + } else { + (TransmutationType::VerticalSansParent, collateral_token) + } + } else { + let remaining_index_set = free_index_set.into_iter().map(|i| !i).collect(); + let position = Self::position_from_parent_collection( + parent_collection_id, + market_id, + remaining_index_set, + force_max_work, + )?; + + (TransmutationType::Horizontal, position) + }; + + Ok(result) + } + pub(crate) fn collection_id_from_parent_collection( parent_collection_id: Option>, market_id: MarketIdOf, @@ -629,4 +640,73 @@ mod pallet { Self::position_from_collection_id(market_id, collection_id) } } + + impl CombinatorialTokensApi for Pallet + where + T: Config, + { + type AccountId = T::AccountId; + type Balance = BalanceOf; + type CombinatorialId = CombinatorialIdOf; + type MarketId = MarketIdOf; + + fn split_position( + who: Self::AccountId, + parent_collection_id: Option, + market_id: Self::MarketId, + partition: Vec>, + amount: Self::Balance, + force_max_work: bool, + ) -> Result, DispatchError> { + Self::do_split_position( + who, + parent_collection_id, + market_id, + partition, + amount, + force_max_work, + ) + } + } + + impl CombinatorialTokensUnsafeApi for Pallet + where + T: Config, + { + type AccountId = T::AccountId; + type Balance = BalanceOf; + type MarketId = MarketIdOf; + + fn split_position_unsafe( + who: Self::AccountId, + collateral: Asset, + assets: Vec>, + amount: Self::Balance, + ) -> DispatchResult { + T::MultiCurrency::ensure_can_withdraw(collateral, &who, amount)?; + T::MultiCurrency::transfer(collateral, &who, &Pallet::::account_id(), amount)?; + + for &asset in assets.iter() { + T::MultiCurrency::deposit(asset, &who, amount)?; + } + + Ok(()) + } + + fn merge_position_unsafe( + who: Self::AccountId, + collateral: Asset, + assets: Vec>, + amount: Self::Balance, + ) -> DispatchResult { + T::MultiCurrency::transfer(collateral, &Pallet::::account_id(), &who, amount)?; + + for &asset in assets.iter() { + T::MultiCurrency::ensure_can_withdraw(asset, &who, amount)?; + T::MultiCurrency::withdraw(asset, &who, amount)?; + } + + Ok(()) + } + } } diff --git a/zrml/combinatorial-tokens/src/types/mod.rs b/zrml/combinatorial-tokens/src/types/mod.rs index 2679ae583..a9960976e 100644 --- a/zrml/combinatorial-tokens/src/types/mod.rs +++ b/zrml/combinatorial-tokens/src/types/mod.rs @@ -17,6 +17,8 @@ pub(crate) mod cryptographic_id_manager; pub(crate) mod hash; +mod transmutation_type; pub use cryptographic_id_manager::CryptographicIdManager; pub(crate) use hash::Hash256; +pub use transmutation_type::TransmutationType; diff --git a/zrml/combinatorial-tokens/src/types/transmutation_type.rs b/zrml/combinatorial-tokens/src/types/transmutation_type.rs new file mode 100644 index 000000000..2be4c2f5a --- /dev/null +++ b/zrml/combinatorial-tokens/src/types/transmutation_type.rs @@ -0,0 +1,29 @@ +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . +// +// This file incorporates work licensed under the GNU Lesser General +// Public License 3.0 but published without copyright notice by Gnosis +// (, info@gnosis.io) in the +// conditional-tokens-contracts repository +// , +// and has been relicensed under GPL-3.0-or-later in this repository. + +pub enum TransmutationType { + VerticalWithParent, + VerticalSansParent, + Horizontal, +} diff --git a/zrml/hybrid-router/Cargo.toml b/zrml/hybrid-router/Cargo.toml index a3a836c04..62d9ac8f9 100644 --- a/zrml/hybrid-router/Cargo.toml +++ b/zrml/hybrid-router/Cargo.toml @@ -10,6 +10,7 @@ zeitgeist-primitives = { workspace = true } zrml-market-commons = { workspace = true } cfg-if = { workspace = true, optional = true } +env_logger = { workspace = true, optional = true } orml-asset-registry = { workspace = true, optional = true } orml-currencies = { workspace = true, optional = true } orml-tokens = { workspace = true, optional = true } @@ -23,6 +24,7 @@ sp-io = { workspace = true, optional = true } xcm = { workspace = true, optional = true } xcm-builder = { workspace = true, optional = true } zrml-authorized = { workspace = true, optional = true } +zrml-combinatorial-tokens = { workspace = true, optional = true } zrml-court = { workspace = true, optional = true } zrml-global-disputes = { workspace = true, optional = true } zrml-neo-swaps = { workspace = true, optional = true } @@ -30,7 +32,6 @@ zrml-orderbook = { workspace = true, optional = true } zrml-prediction-markets = { workspace = true, optional = true } [dev-dependencies] -env_logger = { workspace = true } test-case = { workspace = true } zrml-hybrid-router = { workspace = true, features = ["mock"] } @@ -38,6 +39,7 @@ zrml-hybrid-router = { workspace = true, features = ["mock"] } default = ["std"] mock = [ "cfg-if", + "env_logger/default", "orml-asset-registry/default", "orml-currencies/default", "orml-tokens/default", @@ -50,6 +52,7 @@ mock = [ "sp-io/default", "xcm/default", "zeitgeist-primitives/mock", + "zrml-combinatorial-tokens/default", "zrml-market-commons/default", "zrml-neo-swaps/default", "zrml-orderbook/default", diff --git a/zrml/hybrid-router/src/lib.rs b/zrml/hybrid-router/src/lib.rs index 1544559d2..49f4145cc 100644 --- a/zrml/hybrid-router/src/lib.rs +++ b/zrml/hybrid-router/src/lib.rs @@ -20,9 +20,7 @@ extern crate alloc; -#[cfg(feature = "runtime-benchmarks")] mod benchmarking; -#[cfg(test)] mod mock; mod tests; mod types; diff --git a/zrml/hybrid-router/src/mock.rs b/zrml/hybrid-router/src/mock.rs index fc4ff4f7e..99d3bc72b 100644 --- a/zrml/hybrid-router/src/mock.rs +++ b/zrml/hybrid-router/src/mock.rs @@ -28,6 +28,7 @@ use core::marker::PhantomData; use frame_support::{ construct_runtime, ord_parameter_types, parameter_types, traits::{Contains, Everything, NeverEnsureOrigin}, + Blake2_256, }; use frame_system::{mocking::MockBlock, EnsureRoot, EnsureSignedBy}; use orml_traits::MultiCurrency; @@ -40,23 +41,26 @@ use zeitgeist_primitives::{ AddOutcomePeriod, AggregationPeriod, AppealBond, AppealPeriod, AuthorizedPalletId, BlockHashCount, BlocksPerYear, CloseEarlyBlockPeriod, CloseEarlyDisputeBond, CloseEarlyProtectionBlockPeriod, CloseEarlyProtectionTimeFramePeriod, - CloseEarlyRequestBond, CloseEarlyTimeFramePeriod, CorrectionPeriod, CourtPalletId, - ExistentialDeposit, ExistentialDeposits, GdVotingPeriod, GetNativeCurrencyId, - GlobalDisputeLockId, GlobalDisputesPalletId, HybridRouterPalletId, InflationPeriod, LockId, - MaxAppeals, MaxApprovals, MaxCourtParticipants, MaxCreatorFee, MaxDelegations, - MaxDisputeDuration, MaxDisputes, MaxEditReasonLen, MaxGlobalDisputeVotes, MaxGracePeriod, - MaxLiquidityTreeDepth, MaxLocks, MaxMarketLifetime, MaxOracleDuration, MaxOrders, - MaxOwners, MaxRejectReasonLen, MaxReserves, MaxSelectedDraws, MaxYearlyInflation, - MinCategories, MinDisputeDuration, MinJurorStake, MinOracleDuration, MinOutcomeVoteAmount, - MinimumPeriod, NeoMaxSwapFee, NeoSwapsPalletId, OrderbookPalletId, OutsiderBond, - PmPalletId, RemoveKeysLimit, RequestInterval, TreasuryPalletId, VotePeriod, + CloseEarlyRequestBond, CloseEarlyTimeFramePeriod, CombinatorialTokensPalletId, + CorrectionPeriod, CourtPalletId, ExistentialDeposit, ExistentialDeposits, GdVotingPeriod, + GetNativeCurrencyId, GlobalDisputeLockId, GlobalDisputesPalletId, HybridRouterPalletId, + InflationPeriod, LockId, MaxAppeals, MaxApprovals, MaxCourtParticipants, MaxCreatorFee, + MaxDelegations, MaxDisputeDuration, MaxDisputes, MaxEditReasonLen, MaxGlobalDisputeVotes, + MaxGracePeriod, MaxLiquidityTreeDepth, MaxLocks, MaxMarketLifetime, MaxOracleDuration, + MaxOrders, MaxOwners, MaxRejectReasonLen, MaxReserves, MaxSelectedDraws, + MaxYearlyInflation, MinCategories, MinDisputeDuration, MinJurorStake, MinOracleDuration, + MinOutcomeVoteAmount, MinimumPeriod, NeoMaxSwapFee, NeoSwapsPalletId, OrderbookPalletId, + OutsiderBond, PmPalletId, RemoveKeysLimit, RequestInterval, TreasuryPalletId, VotePeriod, VotingOutcomeFee, BASE, CENT, MAX_ASSETS, }, traits::DistributeFees, types::{ - AccountIdTest, Amount, Balance, BasicCurrencyAdapter, CurrencyId, Hash, MarketId, Moment, + AccountIdTest, Amount, Balance, BasicCurrencyAdapter, CombinatorialId, CurrencyId, Hash, + MarketId, Moment, }, }; +use zrml_combinatorial_tokens::types::CryptographicIdManager; + #[cfg(feature = "parachain")] use { orml_traits::asset_registry::AssetProcessor, parity_scale_codec::Encode, @@ -64,6 +68,9 @@ use { zeitgeist_primitives::types::CustomMetadata, }; +#[cfg(feature = "runtime-benchmarks")] +use zeitgeist_primitives::types::NoopCombinatorialTokensBenchmarkHelper; + pub const ALICE: AccountIdTest = 0; #[allow(unused)] pub const BOB: AccountIdTest = 1; @@ -74,6 +81,7 @@ pub const FEE_ACCOUNT: AccountIdTest = 5; pub const SUDO: AccountIdTest = 123456; pub const EXTERNAL_FEES: Balance = CENT; pub const INITIAL_BALANCE: Balance = 100 * BASE; +#[allow(unused)] pub const MARKET_CREATOR: AccountIdTest = ALICE; #[cfg(feature = "parachain")] @@ -90,6 +98,7 @@ ord_parameter_types! { } parameter_types! { pub storage NeoMinSwapFee: Balance = 0; + pub storage MaxSplits: u16 = 128; } parameter_types! { pub const AdvisoryBond: Balance = 0; @@ -154,6 +163,7 @@ construct_runtime!( AssetRegistry: orml_asset_registry, Authorized: zrml_authorized, Balances: pallet_balances, + CombinatorialTokens: zrml_combinatorial_tokens, Court: zrml_court, AssetManager: orml_currencies, MarketCommons: zrml_market_commons, @@ -192,13 +202,17 @@ impl zrml_orderbook::Config for Runtime { } impl zrml_neo_swaps::Config for Runtime { - type MultiCurrency = AssetManager; + type CombinatorialId = CombinatorialId; + type CombinatorialTokens = CombinatorialTokens; + type CombinatorialTokensUnsafe = CombinatorialTokens; type CompleteSetOperations = PredictionMarkets; type ExternalFees = ExternalFees; type MarketCommons = MarketCommons; - type RuntimeEvent = RuntimeEvent; + type MultiCurrency = AssetManager; type PoolId = MarketId; + type RuntimeEvent = RuntimeEvent; type MaxLiquidityTreeDepth = MaxLiquidityTreeDepth; + type MaxSplits = MaxSplits; type MaxSwapFee = NeoMaxSwapFee; type PalletId = NeoSwapsPalletId; type WeightInfo = zrml_neo_swaps::weights::WeightInfo; @@ -263,6 +277,18 @@ impl zrml_authorized::Config for Runtime { type WeightInfo = zrml_authorized::weights::WeightInfo; } +impl zrml_combinatorial_tokens::Config for Runtime { + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = NoopCombinatorialTokensBenchmarkHelper; + type CombinatorialIdManager = CryptographicIdManager; + type MarketCommons = MarketCommons; + type MultiCurrency = AssetManager; + type Payout = PredictionMarkets; + type RuntimeEvent = RuntimeEvent; + type PalletId = CombinatorialTokensPalletId; + type WeightInfo = zrml_combinatorial_tokens::weights::WeightInfo; +} + impl zrml_court::Config for Runtime { type AppealBond = AppealBond; type BlocksPerYear = BlocksPerYear; diff --git a/zrml/hybrid-router/src/tests/buy.rs b/zrml/hybrid-router/src/tests/buy.rs index b22ca3212..d642110f3 100644 --- a/zrml/hybrid-router/src/tests/buy.rs +++ b/zrml/hybrid-router/src/tests/buy.rs @@ -69,7 +69,7 @@ fn buy_from_amm_and_then_fill_specified_order() { System::assert_has_event( NeoSwapsEvent::::BuyExecuted { who: ALICE, - market_id, + pool_id: market_id, asset_out: asset, amount_in: amm_amount_in, amount_out: 5608094333, @@ -427,7 +427,7 @@ fn buy_from_amm() { System::assert_has_event( NeoSwapsEvent::::BuyExecuted { who: ALICE, - market_id, + pool_id: market_id, asset_out: asset, amount_in: 20000000000, amount_out: 36852900215, @@ -532,7 +532,7 @@ fn buy_from_amm_but_low_amount() { System::assert_has_event( NeoSwapsEvent::::BuyExecuted { who: ALICE, - market_id, + pool_id: market_id, asset_out: asset, amount_in: 30, amount_out: 60, @@ -595,7 +595,7 @@ fn buy_from_amm_only() { System::assert_has_event( NeoSwapsEvent::::BuyExecuted { who: ALICE, - market_id, + pool_id: market_id, asset_out: asset, amount_in: 20000000000, amount_out: 36852900215, diff --git a/zrml/hybrid-router/src/tests/sell.rs b/zrml/hybrid-router/src/tests/sell.rs index 1d76359f5..260a10dd7 100644 --- a/zrml/hybrid-router/src/tests/sell.rs +++ b/zrml/hybrid-router/src/tests/sell.rs @@ -71,7 +71,7 @@ fn sell_to_amm_and_then_fill_specified_order() { System::assert_has_event( NeoSwapsEvent::::SellExecuted { who: ALICE, - market_id, + pool_id: market_id, asset_in: asset, amount_in: amm_amount_in, amount_out: 2775447716, @@ -445,7 +445,7 @@ fn sell_to_amm() { System::assert_has_event( NeoSwapsEvent::::SellExecuted { who: ALICE, - market_id, + pool_id: market_id, asset_in: asset, amount_in: 20000000000, amount_out: 9460629504, @@ -556,7 +556,7 @@ fn sell_to_amm_but_low_amount() { System::assert_has_event( NeoSwapsEvent::::SellExecuted { who: ALICE, - market_id, + pool_id: market_id, asset_in: asset, amount_in: 58, amount_out: 29, @@ -672,7 +672,7 @@ fn sell_to_amm_only() { System::assert_has_event( NeoSwapsEvent::::SellExecuted { who: ALICE, - market_id, + pool_id: market_id, asset_in: asset, amount_in: 20000000000, amount_out: 9460629504, diff --git a/zrml/neo-swaps/Cargo.toml b/zrml/neo-swaps/Cargo.toml index 3c74b4f31..95847ed63 100644 --- a/zrml/neo-swaps/Cargo.toml +++ b/zrml/neo-swaps/Cargo.toml @@ -31,6 +31,7 @@ sp-io = { workspace = true, optional = true } xcm = { workspace = true, optional = true } xcm-builder = { workspace = true, optional = true } zrml-authorized = { workspace = true, optional = true } +zrml-combinatorial-tokens = { workspace = true, optional = true } zrml-court = { workspace = true, optional = true } zrml-global-disputes = { workspace = true, optional = true } zrml-prediction-markets = { workspace = true, optional = true } @@ -62,6 +63,9 @@ mock = [ "pallet-timestamp/default", "sp-api/default", "sp-io/default", + "zrml-combinatorial-tokens/std", + "zrml-combinatorial-tokens/mock", + "zrml-combinatorial-tokens/default", "zrml-court/std", "zrml-authorized/std", "zrml-global-disputes/std", diff --git a/zrml/neo-swaps/src/benchmarking.rs b/zrml/neo-swaps/src/benchmarking.rs index 5846b8283..8515ccf1c 100644 --- a/zrml/neo-swaps/src/benchmarking.rs +++ b/zrml/neo-swaps/src/benchmarking.rs @@ -20,7 +20,7 @@ use super::*; use crate::{ liquidity_tree::{traits::LiquidityTreeHelper, types::LiquidityTree}, - traits::{liquidity_shares_manager::LiquiditySharesManager, pool_operations::PoolOperations}, + traits::{LiquiditySharesManager, PoolOperations}, types::DecisionMarketOracle, AssetOf, BalanceOf, MarketIdOf, Pallet as NeoSwaps, Pools, MIN_SPOT_PRICE, }; diff --git a/zrml/neo-swaps/src/lib.rs b/zrml/neo-swaps/src/lib.rs index c5cc23c37..142005e26 100644 --- a/zrml/neo-swaps/src/lib.rs +++ b/zrml/neo-swaps/src/lib.rs @@ -18,6 +18,7 @@ #![doc = include_str!("../README.md")] #![cfg_attr(not(feature = "std"), no_std)] #![allow(clippy::too_many_arguments)] // TODO Try to remove this later! +#![allow(clippy::type_complexity)] // TODO Try to remove this later! extern crate alloc; @@ -29,6 +30,7 @@ mod macros; mod math; pub mod migration; mod mock; +mod pool_storage; mod tests; pub mod traits; pub mod types; @@ -42,8 +44,8 @@ mod pallet { consts::LN_NUMERICAL_LIMIT, liquidity_tree::types::{BenchmarkInfo, LiquidityTree, LiquidityTreeError}, math::{traits::MathOps, types::Math}, - traits::{pool_operations::PoolOperations, LiquiditySharesManager}, - types::{FeeDistribution, MaxAssets, Pool}, + traits::{LiquiditySharesManager, PoolOperations, PoolStorage}, + types::{FeeDistribution, MaxAssets, Pool, PoolType}, weights::*, }; use alloc::{ @@ -55,7 +57,7 @@ mod pallet { use frame_support::{ dispatch::DispatchResultWithPostInfo, ensure, - pallet_prelude::StorageMap, + pallet_prelude::{StorageMap, StorageValue, ValueQuery}, require_transactional, traits::{Get, IsType, StorageVersion}, transactional, PalletError, PalletId, Parameter, Twox64Concat, @@ -78,10 +80,13 @@ mod pallet { constants::{BASE, CENT}, hybrid_router_api_types::{AmmSoftFail, AmmTrade, ApiError}, math::{ - checked_ops_res::{CheckedAddRes, CheckedSubRes}, + checked_ops_res::{CheckedAddRes, CheckedMulRes, CheckedSubRes}, fixed::{BaseProvider, FixedDiv, FixedMul, ZeitgeistBase}, }, - traits::{CompleteSetOperationsApi, DeployPoolApi, DistributeFees, HybridRouterAmmApi}, + traits::{ + CombinatorialTokensApi, CombinatorialTokensUnsafeApi, CompleteSetOperationsApi, + DeployPoolApi, DistributeFees, HybridRouterAmmApi, + }, types::{Asset, MarketStatus, ScoringRule}, }; use zrml_market_commons::MarketCommonsPalletApi; @@ -116,11 +121,25 @@ mod pallet { <::MarketCommons as MarketCommonsPalletApi>::MarketId; pub(crate) type LiquidityTreeOf = LiquidityTree::MaxLiquidityTreeDepth>; pub(crate) type PoolOf = Pool, MaxAssets>; - pub(crate) type PoolIdOf = ::PoolId; pub(crate) type AmmTradeOf = AmmTrade>; #[pallet::config] pub trait Config: frame_system::Config { + type CombinatorialId: Clone; + + type CombinatorialTokens: CombinatorialTokensApi< + AccountId = Self::AccountId, + Balance = BalanceOf, + CombinatorialId = Self::CombinatorialId, + MarketId = MarketIdOf, + >; + + type CombinatorialTokensUnsafe: CombinatorialTokensUnsafeApi< + AccountId = Self::AccountId, + Balance = BalanceOf, + MarketId = MarketIdOf, + >; + type CompleteSetOperations: CompleteSetOperationsApi< AccountId = Self::AccountId, Balance = BalanceOf, @@ -162,6 +181,10 @@ mod pallet { #[pallet::constant] type MaxLiquidityTreeDepth: Get; + /// The maximum number of splits allowed when creating a combinatorial pool. + #[pallet::constant] + type MaxSplits: Get; + #[pallet::constant] type MaxSwapFee: Get>; @@ -174,7 +197,14 @@ mod pallet { pub struct Pallet(PhantomData); #[pallet::storage] - pub(crate) type Pools = StorageMap<_, Twox64Concat, PoolIdOf, PoolOf>; + pub(crate) type Pools = StorageMap<_, Twox64Concat, T::PoolId, PoolOf>; + + #[pallet::storage] + pub(crate) type PoolCount = StorageValue<_, T::PoolId, ValueQuery>; + + #[pallet::storage] + pub(crate) type MarketIdToPoolId = + StorageMap<_, Twox64Concat, MarketIdOf, T::PoolId>; #[pallet::event] #[pallet::generate_deposit(fn deposit_event)] @@ -186,7 +216,7 @@ mod pallet { /// including swap and external fees. BuyExecuted { who: T::AccountId, - market_id: MarketIdOf, + pool_id: T::PoolId, asset_out: AssetOf, amount_in: BalanceOf, amount_out: BalanceOf, @@ -197,7 +227,7 @@ mod pallet { /// with swap and external fees already deducted. SellExecuted { who: T::AccountId, - market_id: MarketIdOf, + pool_id: T::PoolId, asset_in: AssetOf, amount_in: BalanceOf, amount_out: BalanceOf, @@ -205,11 +235,11 @@ mod pallet { external_fee_amount: BalanceOf, }, /// Liquidity provider withdrew fees. - FeesWithdrawn { who: T::AccountId, market_id: MarketIdOf, amount: BalanceOf }, + FeesWithdrawn { who: T::AccountId, pool_id: T::PoolId, amount: BalanceOf }, /// Liquidity provider joined the pool. JoinExecuted { who: T::AccountId, - market_id: MarketIdOf, + pool_id: T::PoolId, pool_shares_amount: BalanceOf, amounts_in: Vec>, new_liquidity_parameter: BalanceOf, @@ -217,7 +247,7 @@ mod pallet { /// Liquidity provider left the pool. ExitExecuted { who: T::AccountId, - market_id: MarketIdOf, + pool_id: T::PoolId, pool_shares_amount: BalanceOf, amounts_out: Vec>, new_liquidity_parameter: BalanceOf, @@ -226,6 +256,7 @@ mod pallet { PoolDeployed { who: T::AccountId, market_id: MarketIdOf, + pool_id: T::PoolId, account_id: T::AccountId, reserves: BTreeMap, BalanceOf>, collateral: AssetOf, @@ -234,15 +265,11 @@ mod pallet { swap_fee: BalanceOf, }, /// Pool was destroyed. - PoolDestroyed { - who: T::AccountId, - market_id: MarketIdOf, - amounts_out: Vec>, - }, + PoolDestroyed { who: T::AccountId, pool_id: T::PoolId, amounts_out: Vec> }, /// A combinatorial position was opened. ComboBuyExecuted { who: AccountIdOf, - market_id: MarketIdOf, + pool_id: T::PoolId, buy: Vec>, sell: Vec>, amount_in: BalanceOf, @@ -253,7 +280,7 @@ mod pallet { /// A combinatorial position was closed. ComboSellExecuted { who: AccountIdOf, - market_id: MarketIdOf, + pool_id: T::PoolId, buy: Vec>, keep: Vec>, sell: Vec>, @@ -263,6 +290,18 @@ mod pallet { swap_fee_amount: BalanceOf, external_fee_amount: BalanceOf, }, + /// Pool was createed. + CombinatorialPoolDeployed { + who: T::AccountId, + market_ids: Vec>, + pool_id: T::PoolId, + account_id: T::AccountId, + reserves: BTreeMap, BalanceOf>, + collateral: AssetOf, + liquidity_parameter: BalanceOf, + pool_shares_amount: BalanceOf, + swap_fee: BalanceOf, + }, } #[pallet::error] @@ -330,6 +369,19 @@ mod pallet { /// The `amount_keep` parameter must be zero if `keep` is empty and less than `amount_buy` /// if `keep` is not empty. InvalidAmountKeep, + + /// The number of market IDs specified must be greater than two and no more than the + /// maximum. + InvalidMarketCount, + + /// Creating a combinatorial pool for these markets will require more splits than allowed. + MaxSplitsExceeded, + + /// The specified markets do not all use the same collateral. + CollateralMismatch, + + /// This function is not allowed to be called for this type of pool. + InvalidPoolType, } #[derive(Decode, Encode, Eq, PartialEq, PalletError, RuntimeDebug, TypeInfo)] @@ -365,7 +417,7 @@ mod pallet { /// # Parameters /// /// - `origin`: The origin account making the purchase. - /// - `market_id`: Identifier for the market related to the trade. + /// - `pool_id`: Identifier for the pool used to trade on. /// - `asset_count`: Number of assets in the pool. /// - `asset_out`: Asset to be purchased. /// - `amount_in`: Amount of collateral paid by the user. @@ -376,20 +428,26 @@ mod pallet { /// Depends on the implementation of `CompleteSetOperationsApi` and `ExternalFees`; when /// using the canonical implementations, the runtime complexity is `O(asset_count)`. #[pallet::call_index(0)] - #[pallet::weight(T::WeightInfo::buy((*asset_count).saturated_into()))] + #[pallet::weight(T::WeightInfo::buy((*asset_count).saturated_into()))] // TODO Use into() #[transactional] pub fn buy( origin: OriginFor, - #[pallet::compact] market_id: MarketIdOf, + #[pallet::compact] pool_id: T::PoolId, asset_count: AssetIndexType, asset_out: AssetOf, #[pallet::compact] amount_in: BalanceOf, #[pallet::compact] min_amount_out: BalanceOf, ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; - let asset_count_real = T::MarketCommons::market(&market_id)?.outcomes(); - ensure!(asset_count == asset_count_real, Error::::IncorrectAssetCount); - let _ = Self::do_buy(who, market_id, asset_out, amount_in, min_amount_out)?; + + let pool = ::get(pool_id)?; + let asset_count_real = pool.assets().len(); + let asset_count_real_u16: u16 = + asset_count_real.try_into().map_err(|_| Error::::NarrowingConversion)?; + ensure!(asset_count == asset_count_real_u16, Error::::IncorrectAssetCount); + + let _ = Self::do_buy(who, pool_id, asset_out, amount_in, min_amount_out)?; + Ok(Some(T::WeightInfo::buy(asset_count.into())).into()) } @@ -409,7 +467,7 @@ mod pallet { /// # Parameters /// /// - `origin`: The origin account making the sale. - /// - `market_id`: Identifier for the market related to the trade. + /// - `pool_id`: Identifier for the pool used to trade on. /// - `asset_count`: Number of assets in the pool. /// - `asset_in`: Asset to be sold. /// - `amount_in`: Amount of outcome tokens paid by the user. @@ -420,21 +478,27 @@ mod pallet { /// Depends on the implementation of `CompleteSetOperationsApi` and `ExternalFees`; when /// using the canonical implementations, the runtime complexity is `O(asset_count)`. #[pallet::call_index(1)] - #[pallet::weight(T::WeightInfo::sell((*asset_count).saturated_into()))] + #[pallet::weight(T::WeightInfo::sell((*asset_count).saturated_into()))] // TODO Use `into()` #[transactional] pub fn sell( origin: OriginFor, - #[pallet::compact] market_id: MarketIdOf, + #[pallet::compact] pool_id: T::PoolId, asset_count: AssetIndexType, asset_in: AssetOf, #[pallet::compact] amount_in: BalanceOf, #[pallet::compact] min_amount_out: BalanceOf, ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; - let asset_count_real = T::MarketCommons::market(&market_id)?.outcomes(); - ensure!(asset_count == asset_count_real, Error::::IncorrectAssetCount); - let _ = Self::do_sell(who, market_id, asset_in, amount_in, min_amount_out)?; - Ok(Some(T::WeightInfo::sell(asset_count.into())).into()) + + let pool = ::get(pool_id)?; + let asset_count_real = pool.assets().len(); + let asset_count_real_u16: u16 = + asset_count_real.try_into().map_err(|_| Error::::NarrowingConversion)?; + ensure!(asset_count == asset_count_real_u16, Error::::IncorrectAssetCount); + + let _ = Self::do_sell(who, pool_id, asset_in, amount_in, min_amount_out)?; + + Ok(Some(T::WeightInfo::sell(asset_count_real_u16.into())).into()) } /// Join the liquidity pool for the specified market. @@ -449,7 +513,7 @@ mod pallet { /// /// # Parameters /// - /// - `market_id`: Identifier for the market related to the pool. + /// - `pool`: Identifier for the pool to add liquidity to. /// - `pool_shares_amount`: The number of new pool shares the LP will receive. /// - `max_amounts_in`: Vector of the maximum amounts of each outcome token the LP is /// willing to deposit (with outcomes specified in the order of `MarketCommonsApi`). @@ -468,18 +532,21 @@ mod pallet { #[transactional] pub fn join( origin: OriginFor, - #[pallet::compact] market_id: MarketIdOf, + #[pallet::compact] pool_id: T::PoolId, #[pallet::compact] pool_shares_amount: BalanceOf, max_amounts_in: Vec>, ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; - let asset_count = T::MarketCommons::market(&market_id)?.outcomes(); - let asset_count_usize: usize = asset_count.into(); + // Ensure that the conversion in the weight calculation doesn't saturate. let _: u32 = max_amounts_in.len().try_into().map_err(|_| Error::::NarrowingConversion)?; - ensure!(max_amounts_in.len() == asset_count_usize, Error::::IncorrectVecLen); - Self::do_join(who, market_id, pool_shares_amount, max_amounts_in) + + let pool = ::get(pool_id)?; + let asset_count_real = pool.assets().len(); + ensure!(max_amounts_in.len() == asset_count_real, Error::::IncorrectVecLen); + + Self::do_join(who, pool_id, pool_shares_amount, max_amounts_in) } /// Exit the liquidity pool for the specified market. @@ -504,7 +571,7 @@ mod pallet { /// /// # Parameters /// - /// - `market_id`: Identifier for the market related to the pool. + /// - `poold_id`: Identifier for the pool to withdraw liquidity from. /// - `pool_shares_amount_out`: The number of pool shares the LP will relinquish. /// - `min_amounts_out`: Vector of the minimum amounts of each outcome token the LP expects /// to withdraw (with outcomes specified in the order given by `MarketCommonsApi`). @@ -519,18 +586,24 @@ mod pallet { #[transactional] pub fn exit( origin: OriginFor, - #[pallet::compact] market_id: MarketIdOf, + #[pallet::compact] pool_id: T::PoolId, #[pallet::compact] pool_shares_amount_out: BalanceOf, min_amounts_out: Vec>, ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; - let asset_count = T::MarketCommons::market(&market_id)?.outcomes(); - let asset_count_u32: u32 = asset_count.into(); - let min_amounts_out_len: u32 = - min_amounts_out.len().try_into().map_err(|_| Error::::NarrowingConversion)?; - ensure!(min_amounts_out_len == asset_count_u32, Error::::IncorrectVecLen); - Self::do_exit(who, market_id, pool_shares_amount_out, min_amounts_out)?; - Ok(Some(T::WeightInfo::exit(min_amounts_out_len)).into()) + + let pool = ::get(pool_id)?; + let asset_count_real = pool.assets().len(); + let min_amounts_out_len = min_amounts_out.len(); + ensure!(min_amounts_out_len == asset_count_real, Error::::IncorrectVecLen); + + // Ensure that the conversion in the weight calculation doesn't saturate. + let min_amounts_out_len_u32: u32 = + min_amounts_out_len.try_into().map_err(|_| Error::::NarrowingConversion)?; + + Self::do_exit(who, pool_id, pool_shares_amount_out, min_amounts_out)?; + + Ok(Some(T::WeightInfo::exit(min_amounts_out_len_u32)).into()) } /// Withdraw swap fees from the specified market. @@ -540,7 +613,7 @@ mod pallet { /// /// # Parameters /// - /// - `market_id`: Identifier for the market related to the pool. + /// - `pool_id`: Identifier for the market related to the pool. /// /// # Complexity /// @@ -550,10 +623,12 @@ mod pallet { #[transactional] pub fn withdraw_fees( origin: OriginFor, - #[pallet::compact] market_id: MarketIdOf, + #[pallet::compact] pool_id: T::PoolId, ) -> DispatchResult { let who = ensure_signed(origin)?; - Self::do_withdraw_fees(who, market_id)?; + + Self::do_withdraw_fees(who, pool_id)?; + Ok(()) } @@ -592,22 +667,25 @@ mod pallet { #[pallet::compact] swap_fee: BalanceOf, ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; + let asset_count = T::MarketCommons::market(&market_id)?.outcomes(); let asset_count_u32: u32 = asset_count.into(); let spot_prices_len: u32 = spot_prices.len().try_into().map_err(|_| Error::::NarrowingConversion)?; ensure!(spot_prices_len == asset_count_u32, Error::::IncorrectVecLen); + Self::do_deploy_pool(who, market_id, amount, spot_prices, swap_fee)?; + Ok(Some(T::WeightInfo::deploy_pool(spot_prices_len)).into()) } #[allow(clippy::too_many_arguments)] // TODO Bundle `buy`/`keep`/`sell` into one arg. #[pallet::call_index(6)] - #[pallet::weight(T::WeightInfo::buy((*asset_count).saturated_into()))] // TODO + #[pallet::weight(T::WeightInfo::buy((*asset_count).into()))] // TODO #[transactional] pub fn combo_buy( origin: OriginFor, - #[pallet::compact] market_id: MarketIdOf, + #[pallet::compact] pool_id: T::PoolId, asset_count: AssetIndexType, buy: Vec>, sell: Vec>, @@ -615,9 +693,15 @@ mod pallet { #[pallet::compact] min_amount_out: BalanceOf, ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; - let asset_count_real = T::MarketCommons::market(&market_id)?.outcomes(); - ensure!(asset_count == asset_count_real, Error::::IncorrectAssetCount); - Self::do_combo_buy(who, market_id, buy, sell, amount_in, min_amount_out)?; + + let pool = ::get(pool_id)?; + let asset_count_real = pool.assets().len(); + let asset_count_real_u16: u16 = + asset_count_real.try_into().map_err(|_| Error::::NarrowingConversion)?; + ensure!(asset_count == asset_count_real_u16, Error::::IncorrectAssetCount); + + Self::do_combo_buy(who, pool_id, buy, sell, amount_in, min_amount_out)?; + Ok(Some(T::WeightInfo::buy(asset_count.into())).into()) // TODO } @@ -627,7 +711,7 @@ mod pallet { #[transactional] pub fn combo_sell( origin: OriginFor, - #[pallet::compact] market_id: MarketIdOf, + #[pallet::compact] pool_id: T::PoolId, asset_count: AssetIndexType, buy: Vec>, keep: Vec>, @@ -637,11 +721,16 @@ mod pallet { #[pallet::compact] min_amount_out: BalanceOf, ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; - let asset_count_real = T::MarketCommons::market(&market_id)?.outcomes(); - ensure!(asset_count == asset_count_real, Error::::IncorrectAssetCount); + + let pool = ::get(pool_id)?; + let asset_count_real = pool.assets().len(); + let asset_count_real_u16: u16 = + asset_count_real.try_into().map_err(|_| Error::::NarrowingConversion)?; + ensure!(asset_count == asset_count_real_u16, Error::::IncorrectAssetCount); + Self::do_combo_sell( who, - market_id, + pool_id, buy, keep, sell, @@ -649,30 +738,58 @@ mod pallet { amount_keep, min_amount_out, )?; + Ok(Some(T::WeightInfo::buy(asset_count.into())).into()) // TODO } + + #[pallet::call_index(8)] + #[pallet::weight({0})] // TODO + #[transactional] + pub fn deploy_combinatorial_pool( + origin: OriginFor, + market_ids: Vec>, + amount: BalanceOf, + spot_prices: Vec>, + swap_fee: BalanceOf, + force_max_work: bool, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + + Self::do_deploy_combinatorial_pool( + who, + market_ids, + amount, + spot_prices, + swap_fee, + force_max_work, + ) + } } impl Pallet { #[require_transactional] pub(crate) fn do_buy( who: T::AccountId, - market_id: MarketIdOf, + pool_id: T::PoolId, asset_out: AssetOf, amount_in: BalanceOf, min_amount_out: BalanceOf, ) -> Result, DispatchError> { ensure!(amount_in != Zero::zero(), Error::::ZeroAmount); - let market = T::MarketCommons::market(&market_id)?; - ensure!(market.status == MarketStatus::Active, Error::::MarketNotActive); - Self::try_mutate_pool(&market_id, |pool| { + + ::try_mutate_pool(&pool_id, |pool| { + ensure!(pool.is_active()?, Error::::MarketNotActive); + ensure!( + matches!(pool.pool_type, PoolType::Standard(_)), + Error::::InvalidPoolType + ); ensure!(pool.contains(&asset_out), Error::::AssetNotFound); T::MultiCurrency::transfer(pool.collateral, &who, &pool.account_id, amount_in)?; let FeeDistribution { remaining: amount_in_minus_fees, swap_fees: swap_fee_amount, external_fees: external_fee_amount, - } = Self::distribute_fees(market_id, pool, &pool.account_id.clone(), amount_in)?; + } = Self::distribute_fees(pool, &pool.account_id.clone(), amount_in)?; ensure!( amount_in_minus_fees <= pool.calculate_numerical_threshold(), Error::::NumericalLimits(NumericalLimitsError::MaxAmountExceeded), @@ -691,6 +808,9 @@ mod pallet { // Instead of letting `who` buy the complete sets and then transfer almost all of // the outcomes to the pool account, we prevent `(n-1)` storage reads by using the // pool account to buy. Note that the fees are already in the pool at this point. + let PoolType::Standard(market_id) = pool.pool_type else { + return Err(Error::::Unexpected.into()); + }; T::CompleteSetOperations::buy_complete_set( pool.account_id.clone(), market_id, @@ -705,7 +825,7 @@ mod pallet { } Self::deposit_event(Event::::BuyExecuted { who: who.clone(), - market_id, + pool_id, asset_out, amount_in, amount_out, @@ -719,15 +839,19 @@ mod pallet { #[require_transactional] pub(crate) fn do_sell( who: T::AccountId, - market_id: MarketIdOf, + pool_id: T::PoolId, asset_in: AssetOf, amount_in: BalanceOf, min_amount_out: BalanceOf, ) -> Result, DispatchError> { ensure!(amount_in != Zero::zero(), Error::::ZeroAmount); - let market = T::MarketCommons::market(&market_id)?; - ensure!(market.status == MarketStatus::Active, Error::::MarketNotActive); - Self::try_mutate_pool(&market_id, |pool| { + + ::try_mutate_pool(&pool_id, |pool| { + ensure!(pool.is_active()?, Error::::MarketNotActive); + ensure!( + matches!(pool.pool_type, PoolType::Standard(_)), + Error::::InvalidPoolType + ); ensure!(pool.contains(&asset_in), Error::::AssetNotFound); // Ensure that the price of `asset_in` is at least `exp(-EXP_NUMERICAL_LIMITS) = // 4.5399...e-05`. @@ -758,6 +882,9 @@ mod pallet { // `amount_out_minus_fees` units of collateral to `who`. The fees automatically end // up in the pool. T::MultiCurrency::transfer(asset_in, &who, &pool.account_id, amount_in)?; + let PoolType::Standard(market_id) = pool.pool_type else { + return Err(Error::::Unexpected.into()); + }; T::CompleteSetOperations::sell_complete_set( pool.account_id.clone(), market_id, @@ -767,7 +894,7 @@ mod pallet { remaining: amount_out_minus_fees, swap_fees: swap_fee_amount, external_fees: external_fee_amount, - } = Self::distribute_fees(market_id, pool, &pool.account_id.clone(), amount_out)?; + } = Self::distribute_fees(pool, &pool.account_id.clone(), amount_out)?; ensure!(amount_out_minus_fees >= min_amount_out, Error::::AmountOutBelowMin); T::MultiCurrency::transfer( pool.collateral, @@ -789,7 +916,7 @@ mod pallet { ); Self::deposit_event(Event::::SellExecuted { who: who.clone(), - market_id, + pool_id, asset_in, amount_in, amount_out: amount_out_minus_fees, @@ -808,18 +935,20 @@ mod pallet { #[require_transactional] pub(crate) fn do_join( who: T::AccountId, - market_id: MarketIdOf, + pool_id: T::PoolId, pool_shares_amount: BalanceOf, max_amounts_in: Vec>, ) -> DispatchResultWithPostInfo { ensure!(pool_shares_amount != Zero::zero(), Error::::ZeroAmount); - let market = T::MarketCommons::market(&market_id)?; - ensure!(market.status == MarketStatus::Active, Error::::MarketNotActive); - let asset_count_u16: u16 = - max_amounts_in.len().try_into().map_err(|_| Error::::NarrowingConversion)?; - let asset_count_u32: u32 = asset_count_u16.into(); - ensure!(asset_count_u16 == market.outcomes(), Error::::IncorrectAssetCount); - let benchmark_info = Self::try_mutate_pool(&market_id, |pool| { + + let weight = ::try_mutate_pool(&pool_id, |pool| { + ensure!(pool.is_active()?, Error::::MarketNotActive); + ensure!( + max_amounts_in.len() == pool.assets().len(), + Error::::IncorrectAssetCount + ); + let asset_count_u32 = + max_amounts_in.len().try_into().map_err(|_| Error::::NarrowingConversion)?; let ratio = pool_shares_amount.bdiv_ceil(pool.liquidity_shares_manager.total_shares()?)?; // Ensure that new LPs contribute at least MIN_RELATIVE_LP_POSITION_VALUE. Note that @@ -849,37 +978,38 @@ mod pallet { pool.liquidity_parameter = new_liquidity_parameter; Self::deposit_event(Event::::JoinExecuted { who: who.clone(), - market_id, + pool_id, pool_shares_amount, amounts_in, new_liquidity_parameter, }); - Ok(benchmark_info) + let weight = match benchmark_info { + BenchmarkInfo::InPlace => T::WeightInfo::join_in_place(asset_count_u32), + BenchmarkInfo::Reassigned => T::WeightInfo::join_reassigned(asset_count_u32), + BenchmarkInfo::Leaf => T::WeightInfo::join_leaf(asset_count_u32), + }; + Ok(weight) })?; - let weight = match benchmark_info { - BenchmarkInfo::InPlace => T::WeightInfo::join_in_place(asset_count_u32), - BenchmarkInfo::Reassigned => T::WeightInfo::join_reassigned(asset_count_u32), - BenchmarkInfo::Leaf => T::WeightInfo::join_leaf(asset_count_u32), - }; Ok((Some(weight)).into()) } #[require_transactional] pub(crate) fn do_exit( who: T::AccountId, - market_id: MarketIdOf, + pool_id: T::PoolId, pool_shares_amount: BalanceOf, min_amounts_out: Vec>, ) -> DispatchResult { ensure!(pool_shares_amount != Zero::zero(), Error::::ZeroAmount); - let market = T::MarketCommons::market(&market_id)?; - Pools::::try_mutate_exists(market_id, |maybe_pool| { + + // FIXME Should this also be made part of the `PoolStorage` interface? + Pools::::try_mutate_exists(pool_id, |maybe_pool| { let pool = maybe_pool.as_mut().ok_or::(Error::::PoolNotFound.into())?; let ratio = { let mut ratio = pool_shares_amount .bdiv_floor(pool.liquidity_shares_manager.total_shares()?)?; - if market.status == MarketStatus::Active { + if pool.is_active()? { let multiplier = ZeitgeistBase::>::get()? .checked_sub_res(&EXIT_FEE.saturated_into())?; ratio = ratio.bmul_floor(multiplier)?; @@ -914,9 +1044,10 @@ mod pallet { *maybe_pool = None; // Delete the storage map entry. Self::deposit_event(Event::::PoolDestroyed { who: who.clone(), - market_id, + pool_id, amounts_out, }); + // No need to clear `MarketIdToPoolId`. } else { let old_liquidity_parameter = pool.liquidity_parameter; let new_liquidity_parameter = old_liquidity_parameter @@ -941,7 +1072,7 @@ mod pallet { pool.liquidity_parameter = new_liquidity_parameter; Self::deposit_event(Event::::ExitExecuted { who: who.clone(), - market_id, + pool_id, pool_shares_amount, amounts_out, new_liquidity_parameter, @@ -952,16 +1083,13 @@ mod pallet { } #[require_transactional] - pub(crate) fn do_withdraw_fees( - who: T::AccountId, - market_id: MarketIdOf, - ) -> DispatchResult { - Self::try_mutate_pool(&market_id, |pool| { + pub(crate) fn do_withdraw_fees(who: T::AccountId, pool_id: T::PoolId) -> DispatchResult { + ::try_mutate_pool(&pool_id, |pool| { let amount = pool.liquidity_shares_manager.withdraw_fees(&who)?; T::MultiCurrency::transfer(pool.collateral, &pool.account_id, &who, amount)?; // Should never fail. Self::deposit_event(Event::::FeesWithdrawn { who: who.clone(), - market_id, + pool_id, amount, }); Ok(()) @@ -976,7 +1104,12 @@ mod pallet { spot_prices: Vec>, swap_fee: BalanceOf, ) -> DispatchResult { - ensure!(!Pools::::contains_key(market_id), Error::::DuplicatePool); + // MarketIdToPoolId is not cleared when a pool is destroyed, so checking if + // `MarketIdToPoolId` holds a key is not enough. + if let Some(pool_id) = MarketIdToPoolId::::get(market_id) { + ensure!(!Pools::::contains_key(pool_id), Error::::DuplicatePool); + } + let market = T::MarketCommons::market(&market_id)?; ensure!(market.status == MarketStatus::Active, Error::::MarketNotActive); ensure!( @@ -1022,11 +1155,13 @@ mod pallet { let collateral = market.base_asset; let pool = Pool { account_id: pool_account_id.clone(), + assets: market.outcome_assets().try_into().map_err(|_| Error::::Unexpected)?, reserves: reserves.clone().try_into().map_err(|_| Error::::Unexpected)?, collateral, liquidity_parameter, liquidity_shares_manager: LiquidityTree::new(who.clone(), amount)?, swap_fee, + pool_type: PoolType::Standard(market_id), }; // TODO(#1220): Ensure that the existential deposit doesn't kill fees. This is an ugly // hack and system should offer the option to whitelist accounts. @@ -1036,10 +1171,98 @@ mod pallet { &pool.account_id, T::MultiCurrency::minimum_balance(collateral), )?; - Pools::::insert(market_id, pool); + let pool_id = ::add(pool)?; + MarketIdToPoolId::::insert(market_id, pool_id); Self::deposit_event(Event::::PoolDeployed { who, market_id, + pool_id, + account_id: pool_account_id, + reserves, + collateral, + liquidity_parameter, + pool_shares_amount: amount, + swap_fee, + }); + Ok(()) + } + + #[require_transactional] + pub(crate) fn do_deploy_combinatorial_pool( + who: T::AccountId, + market_ids: Vec>, + amount: BalanceOf, + spot_prices: Vec>, + swap_fee: BalanceOf, + force_max_work: bool, + ) -> DispatchResult { + ensure!(swap_fee >= MIN_SWAP_FEE.saturated_into(), Error::::SwapFeeBelowMin); + ensure!(swap_fee <= T::MaxSwapFee::get(), Error::::SwapFeeAboveMax); + + let (collection_ids, position_ids, collateral) = + Self::split_markets(who.clone(), market_ids.clone(), amount, force_max_work)?; + + ensure!(spot_prices.len() == collection_ids.len(), Error::::IncorrectVecLen); + ensure!( + spot_prices + .iter() + .fold(Zero::zero(), |acc: BalanceOf, &val| acc.saturating_add(val)) + == BASE.saturated_into(), + Error::::InvalidSpotPrices + ); + for &p in spot_prices.iter() { + ensure!( + p.saturated_into::() >= MIN_SPOT_PRICE, + Error::::SpotPriceBelowMin + ); + ensure!( + p.saturated_into::() <= MAX_SPOT_PRICE, + Error::::SpotPriceAboveMax + ); + } + + // TODO This is where the common code begins! + let (liquidity_parameter, amounts_in) = + Math::::calculate_reserves_from_spot_prices(amount, spot_prices)?; + ensure!( + liquidity_parameter >= MIN_LIQUIDITY.saturated_into(), + Error::::LiquidityTooLow + ); + let pool_id = ::next_pool_id(); + let pool_account_id = Self::pool_account_id(&pool_id); + let mut reserves = BTreeMap::new(); + for (&amount_in, &asset) in amounts_in.iter().zip(position_ids.iter()) { + T::MultiCurrency::transfer(asset, &who, &pool_account_id, amount_in)?; + let _ = reserves.insert(asset, amount_in); + } + let pool = Pool { + account_id: pool_account_id.clone(), + assets: position_ids.try_into().map_err(|_| Error::::Unexpected)?, + reserves: reserves.clone().try_into().map_err(|_| Error::::Unexpected)?, + collateral, + liquidity_parameter, + liquidity_shares_manager: LiquidityTree::new(who.clone(), amount)?, + swap_fee, + pool_type: PoolType::Combinatorial( + market_ids.clone().try_into().map_err(|_| Error::::Unexpected)?, + ), + }; + + ensure!(pool.is_active()?, Error::::MarketNotActive); + + // TODO(#1220): Ensure that the existential deposit doesn't kill fees. This is an ugly + // hack and system should offer the option to whitelist accounts. + T::MultiCurrency::transfer( + pool.collateral, + &who, + &pool.account_id, + T::MultiCurrency::minimum_balance(collateral), + )?; + let _ = ::add(pool); + Self::deposit_event(Event::::CombinatorialPoolDeployed { + who, + market_ids, + pool_id, account_id: pool_account_id, reserves, collateral, @@ -1054,7 +1277,7 @@ mod pallet { #[require_transactional] pub(crate) fn do_combo_buy( who: T::AccountId, - market_id: MarketIdOf, + pool_id: T::PoolId, // TODO Replace `buy`/`keep`/`sell` with a struct. buy: Vec>, sell: Vec>, @@ -1062,9 +1285,14 @@ mod pallet { min_amount_out: BalanceOf, ) -> DispatchResult { ensure!(amount_in != Zero::zero(), Error::::ZeroAmount); - let market = T::MarketCommons::market(&market_id)?; - ensure!(market.status == MarketStatus::Active, Error::::MarketNotActive); - Self::try_mutate_pool(&market_id, |pool| { + + ::try_mutate_pool(&pool_id, |pool| { + ensure!(pool.is_active()?, Error::::MarketNotActive); + ensure!( + matches!(pool.pool_type, PoolType::Combinatorial(_)), + Error::::InvalidPoolType + ); + // Ensure that `buy` and `sell` partition are disjoint, only contain assets from // the market and don't contain dupliates. ensure!(!buy.is_empty(), Error::::InvalidPartition); @@ -1085,7 +1313,7 @@ mod pallet { remaining: amount_in_minus_fees, swap_fees: swap_fee_amount, external_fees: external_fee_amount, - } = Self::distribute_fees(market_id, pool, &who, amount_in)?; + } = Self::distribute_fees(pool, &who, amount_in)?; let swap_amount_out = pool.calculate_swap_amount_out_for_buy( buy.clone(), sell.clone(), @@ -1094,9 +1322,10 @@ mod pallet { let amount_out = swap_amount_out.checked_add_res(&amount_in_minus_fees)?; ensure!(amount_out >= min_amount_out, Error::::AmountOutBelowMin); - T::CompleteSetOperations::buy_complete_set( + T::CombinatorialTokensUnsafe::split_position_unsafe( who.clone(), - market_id, + pool.collateral, + pool.assets(), amount_in_minus_fees, )?; @@ -1129,7 +1358,7 @@ mod pallet { Self::deposit_event(Event::::ComboBuyExecuted { who: who.clone(), - market_id, + pool_id, buy: buy.clone(), sell: sell.clone(), amount_in, @@ -1147,7 +1376,7 @@ mod pallet { #[require_transactional] pub(crate) fn do_combo_sell( who: T::AccountId, - market_id: MarketIdOf, + pool_id: T::PoolId, buy: Vec>, keep: Vec>, sell: Vec>, @@ -1156,8 +1385,6 @@ mod pallet { min_amount_out: BalanceOf, ) -> DispatchResult { ensure!(amount_buy != Zero::zero(), Error::::ZeroAmount); - let market = T::MarketCommons::market(&market_id)?; - ensure!(market.status == MarketStatus::Active, Error::::MarketNotActive); if keep.is_empty() { ensure!(amount_keep.is_zero(), Error::::InvalidAmountKeep); @@ -1165,7 +1392,13 @@ mod pallet { ensure!(amount_keep < amount_buy, Error::::InvalidAmountKeep); } - Self::try_mutate_pool(&market_id, |pool| { + ::try_mutate_pool(&pool_id, |pool| { + ensure!(pool.is_active()?, Error::::MarketNotActive); + ensure!( + matches!(pool.pool_type, PoolType::Combinatorial(_)), + Error::::InvalidPoolType + ); + // Ensure that `buy` and `sell` partition are disjoint and only contain assets from // the market. ensure!(!buy.is_empty(), Error::::InvalidPartition); @@ -1189,7 +1422,7 @@ mod pallet { ensure!(keep_set.len() == keep.len(), Error::::InvalidPartition); ensure!(sell_set.len() == sell.len(), Error::::InvalidPartition); let total_assets = buy.len().saturating_add(keep.len()).saturating_add(sell.len()); - ensure!(total_assets == market.outcomes() as usize, Error::::InvalidPartition); + ensure!(total_assets == pool.assets().len(), Error::::InvalidPartition); // This is the amount of collateral the user will receive in the end, or, // equivalently, the amount of each asset in `sell` that the user intermittently @@ -1220,9 +1453,10 @@ mod pallet { pool.increase_reserve(&asset, &amount_keep)?; } - T::CompleteSetOperations::sell_complete_set( + T::CombinatorialTokensUnsafe::merge_position_unsafe( pool.account_id.clone(), - market_id, + pool.collateral, + pool.assets(), amount_out, )?; @@ -1234,7 +1468,7 @@ mod pallet { remaining: amount_out_minus_fees, swap_fees: swap_fee_amount, external_fees: external_fee_amount, - } = Self::distribute_fees(market_id, pool, &pool.account_id.clone(), amount_out)?; + } = Self::distribute_fees(pool, &pool.account_id.clone(), amount_out)?; T::MultiCurrency::transfer( pool.collateral, @@ -1271,7 +1505,7 @@ mod pallet { Self::deposit_event(Event::::ComboSellExecuted { who: who.clone(), - market_id, + pool_id, buy: buy.clone(), keep: keep.clone(), sell: sell.clone(), @@ -1287,15 +1521,15 @@ mod pallet { } #[inline] - pub(crate) fn pool_account_id(market_id: &MarketIdOf) -> T::AccountId { - T::PalletId::get().into_sub_account_truncating((*market_id).saturated_into::()) + pub(crate) fn pool_account_id(pool_id: &T::PoolId) -> T::AccountId { + T::PalletId::get().into_sub_account_truncating((*pool_id).saturated_into::()) } /// Distribute swap fees and external fees and returns the remaining amount. /// /// # Arguments /// - /// - `market_id`: The ID of the market to which the pool belongs. + /// - `pool_id`: The ID of the pool on which the trade was executed. /// - `pool`: The pool on which the trade was executed. /// - `account`: The account that the fee is deducted from. /// - `amount`: The gross amount from which the fee is deduced. @@ -1304,7 +1538,6 @@ mod pallet { /// function will fail if the external fees exceed the gross amount. #[require_transactional] fn distribute_fees( - market_id: MarketIdOf, pool: &mut PoolOf, account: &AccountIdOf, amount: BalanceOf, @@ -1312,23 +1545,131 @@ mod pallet { let swap_fees = pool.swap_fee.bmul(amount)?; T::MultiCurrency::transfer(pool.collateral, account, &pool.account_id, swap_fees)?; pool.liquidity_shares_manager.deposit_fees(swap_fees)?; // Should only error unexpectedly! - let external_fees = - T::ExternalFees::distribute(market_id, pool.collateral, account, amount); + + let mut external_fees: BalanceOf = Zero::zero(); + for &market_id in pool.pool_type.iter_market_ids() { + let f = T::ExternalFees::distribute(market_id, pool.collateral, account, amount); + external_fees = external_fees.saturating_add(f); + } + let total_fees = external_fees.saturating_add(swap_fees); let remaining = amount.checked_sub(&total_fees).ok_or(Error::::Unexpected)?; Ok(FeeDistribution { remaining, swap_fees, external_fees }) } - pub(crate) fn try_mutate_pool( - market_id: &MarketIdOf, - mutator: F, - ) -> Result - where - F: FnMut(&mut PoolOf) -> Result, - { - Pools::::try_mutate(market_id, |maybe_pool| { - maybe_pool.as_mut().ok_or(Error::::PoolNotFound.into()).and_then(mutator) - }) + /// Takes `amount` units of collateral and splits these tokens into the elementary outcome + /// tokens of the combinatorial market comprised of the specified markets (all specified + /// markets must have the same collateral). Returns the collateral token type and a list of + /// outcome tokens. + pub(crate) fn split_markets( + who: T::AccountId, + market_ids: Vec>, + amount: BalanceOf, + force_max_work: bool, + ) -> Result<(Vec, Vec>, AssetOf), DispatchError> { + let markets = + market_ids.iter().map(T::MarketCommons::market).collect::, _>>()?; + + // Calculate the total amount of split operations required. One split for splitting + // collateral into the positions of the first market, and then it's one split for each + // position created in the previous step. + let mut total_splits = 0u16; + let mut prev_positions = 0u16; + for market in markets.iter() { + ensure!( + market.scoring_rule == ScoringRule::AmmCdaHybrid, + Error::::InvalidTradingMechanism + ); + + if total_splits == 0u16 { + total_splits = 1u16; + prev_positions = market.outcomes(); + } else { + total_splits = total_splits.checked_add_res(&prev_positions)?; + prev_positions = prev_positions.checked_mul_res(&market.outcomes())?; + } + } + ensure!(total_splits <= T::MaxSplits::get(), Error::::MaxSplitsExceeded); + + let collateral = Self::try_common_collateral(market_ids.clone())?; + + let mut split_count = 0u16; + let mut collection_ids: Vec = vec![]; + let mut position_ids = vec![]; + for market_id in market_ids.iter() { + let asset_count = T::MarketCommons::market(market_id)?.outcomes() as usize; + let partition: Vec> = (0..asset_count) + .map(|index| { + let mut index_set = vec![false; asset_count]; + if let Some(value) = index_set.get_mut(index) { + *value = true; + } + + index_set + }) + .collect(); + + if split_count == 0 { + let split_position_info = T::CombinatorialTokens::split_position( + who.clone(), + None, + *market_id, + partition.clone(), + amount, + force_max_work, + )?; + + collection_ids.extend_from_slice(&split_position_info.collection_ids); + position_ids.extend_from_slice(&split_position_info.position_ids); + + split_count.saturating_inc(); + } else { + let mut new_collection_ids = vec![]; + let mut new_position_ids = vec![]; + + for parent_collection_id in collection_ids.iter() { + if split_count > total_splits { + return Err(Error::::Unexpected.into()); + } + + let split_position_info = T::CombinatorialTokens::split_position( + who.clone(), + Some(parent_collection_id.clone()), + *market_id, + partition.clone(), + amount, + force_max_work, + )?; + + new_collection_ids.extend_from_slice(&split_position_info.collection_ids); + new_position_ids.extend_from_slice(&split_position_info.position_ids); + + split_count.saturating_inc(); + } + + collection_ids = new_collection_ids; + position_ids = new_position_ids; + } + } + + let result = (collection_ids, position_ids, collateral); + + Ok(result) + } + + pub(crate) fn try_common_collateral( + market_ids: Vec>, + ) -> Result, DispatchError> { + let first_market_id = market_ids.first().ok_or(Error::::InvalidMarketCount)?; + let first_market = T::MarketCommons::market(first_market_id)?; + let collateral = first_market.base_asset; + + for market_id in market_ids.iter() { + let market = T::MarketCommons::market(market_id)?; + ensure!(market.base_asset == collateral, Error::::CollateralMismatch); + } + + Ok(collateral) } } @@ -1396,14 +1737,17 @@ mod pallet { type Asset = AssetOf; fn pool_exists(market_id: Self::MarketId) -> bool { - Pools::::contains_key(market_id) + let Some(pool_id) = MarketIdToPoolId::::get(market_id) else { + return false; + }; + Pools::::contains_key(pool_id) } fn get_spot_price( market_id: Self::MarketId, asset: Self::Asset, ) -> Result { - let pool = Pools::::get(market_id).ok_or(Error::::PoolNotFound)?; + let pool = ::get(market_id)?; pool.calculate_spot_price(asset) } @@ -1412,7 +1756,7 @@ mod pallet { asset: Self::Asset, until: Self::Balance, ) -> Result { - let pool = Pools::::get(market_id).ok_or(Error::::PoolNotFound)?; + let pool = ::get(market_id)?; let buy_amount = pool.calculate_buy_amount_until(asset, until)?; let total_fee_fractional = Self::total_fee_fractional( pool.swap_fee, @@ -1439,7 +1783,7 @@ mod pallet { asset: Self::Asset, until: Self::Balance, ) -> Result { - let pool = Pools::::get(market_id).ok_or(Error::::PoolNotFound)?; + let pool = ::get(market_id)?; pool.calculate_sell_amount_until(asset, until) } diff --git a/zrml/neo-swaps/src/liquidity_tree/tests/mod.rs b/zrml/neo-swaps/src/liquidity_tree/tests/mod.rs index 49ff92aab..1839f12e5 100644 --- a/zrml/neo-swaps/src/liquidity_tree/tests/mod.rs +++ b/zrml/neo-swaps/src/liquidity_tree/tests/mod.rs @@ -20,11 +20,11 @@ use crate::{ assert_liquidity_tree_state, create_b_tree_map, liquidity_tree::{ - traits::liquidity_tree_helper::LiquidityTreeHelper, + traits::LiquidityTreeHelper, types::{LiquidityTreeError, Node}, }, mock::Runtime, - traits::liquidity_shares_manager::LiquiditySharesManager, + traits::LiquiditySharesManager, LiquidityTreeOf, }; use alloc::collections::BTreeMap; diff --git a/zrml/neo-swaps/src/liquidity_tree/traits/mod.rs b/zrml/neo-swaps/src/liquidity_tree/traits/mod.rs index 08dcddd9d..76780562c 100644 --- a/zrml/neo-swaps/src/liquidity_tree/traits/mod.rs +++ b/zrml/neo-swaps/src/liquidity_tree/traits/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2023 Forecasting Technologies LTD. +// Copyright 2023-2024 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // @@ -15,6 +15,6 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . -pub(crate) mod liquidity_tree_helper; +mod liquidity_tree_helper; pub(crate) use liquidity_tree_helper::*; diff --git a/zrml/neo-swaps/src/macros.rs b/zrml/neo-swaps/src/macros.rs index a688fb368..8dbe2f10a 100644 --- a/zrml/neo-swaps/src/macros.rs +++ b/zrml/neo-swaps/src/macros.rs @@ -61,11 +61,8 @@ macro_rules! assert_pool_state { $(,)? ) => { let pool = Pools::::get($market_id).unwrap(); - assert_eq!( - pool.reserves.values().cloned().collect::>(), - $reserves, - "assert_pool_state: Reserves mismatch" - ); + let pool_reserves = pool.assets().iter().map(|a| pool.reserves[a]).collect::>(); + assert_eq!(pool_reserves, $reserves, "assert_pool_state: Reserves mismatch"); let actual_spot_prices = pool .assets() .iter() diff --git a/zrml/neo-swaps/src/mock.rs b/zrml/neo-swaps/src/mock.rs index e6a23d0e2..627b7db6a 100644 --- a/zrml/neo-swaps/src/mock.rs +++ b/zrml/neo-swaps/src/mock.rs @@ -29,6 +29,7 @@ use core::marker::PhantomData; use frame_support::{ construct_runtime, ord_parameter_types, parameter_types, traits::{Contains, Everything, NeverEnsureOrigin}, + Blake2_256, }; use frame_system::{mocking::MockBlock, EnsureRoot, EnsureSignedBy}; use orml_traits::MultiCurrency; @@ -36,8 +37,6 @@ use sp_runtime::{ traits::{BlakeTwo256, ConstU32, Get, IdentityLookup, Zero}, BuildStorage, DispatchResult, Perbill, Percent, SaturatedConversion, }; -#[cfg(feature = "parachain")] -use zeitgeist_primitives::types::Asset; use zeitgeist_primitives::{ constants::{ base_multiples::*, @@ -45,32 +44,39 @@ use zeitgeist_primitives::{ AddOutcomePeriod, AggregationPeriod, AppealBond, AppealPeriod, AuthorizedPalletId, BlockHashCount, BlocksPerYear, CloseEarlyBlockPeriod, CloseEarlyDisputeBond, CloseEarlyProtectionBlockPeriod, CloseEarlyProtectionTimeFramePeriod, - CloseEarlyRequestBond, CloseEarlyTimeFramePeriod, CorrectionPeriod, CourtPalletId, - ExistentialDeposit, ExistentialDeposits, GdVotingPeriod, GetNativeCurrencyId, - GlobalDisputeLockId, GlobalDisputesPalletId, InflationPeriod, LockId, MaxAppeals, - MaxApprovals, MaxCourtParticipants, MaxCreatorFee, MaxDelegations, MaxDisputeDuration, - MaxDisputes, MaxEditReasonLen, MaxGlobalDisputeVotes, MaxGracePeriod, - MaxLiquidityTreeDepth, MaxLocks, MaxMarketLifetime, MaxOracleDuration, MaxOwners, - MaxRejectReasonLen, MaxReserves, MaxSelectedDraws, MaxYearlyInflation, MinCategories, - MinDisputeDuration, MinJurorStake, MinOracleDuration, MinOutcomeVoteAmount, - MinimumPeriod, NeoMaxSwapFee, NeoSwapsPalletId, OutsiderBond, PmPalletId, - RemoveKeysLimit, RequestInterval, TreasuryPalletId, VotePeriod, VotingOutcomeFee, BASE, - CENT, + CloseEarlyRequestBond, CloseEarlyTimeFramePeriod, CombinatorialTokensPalletId, + CorrectionPeriod, CourtPalletId, ExistentialDeposit, ExistentialDeposits, + GdVotingPeriod, GetNativeCurrencyId, GlobalDisputeLockId, GlobalDisputesPalletId, + InflationPeriod, LockId, MaxAppeals, MaxApprovals, MaxCourtParticipants, MaxCreatorFee, + MaxDelegations, MaxDisputeDuration, MaxDisputes, MaxEditReasonLen, + MaxGlobalDisputeVotes, MaxGracePeriod, MaxLiquidityTreeDepth, MaxLocks, + MaxMarketLifetime, MaxOracleDuration, MaxOwners, MaxRejectReasonLen, MaxReserves, + MaxSelectedDraws, MaxYearlyInflation, MinCategories, MinDisputeDuration, MinJurorStake, + MinOracleDuration, MinOutcomeVoteAmount, MinimumPeriod, NeoMaxSwapFee, + NeoSwapsPalletId, OutsiderBond, PmPalletId, RemoveKeysLimit, RequestInterval, + TreasuryPalletId, VotePeriod, VotingOutcomeFee, BASE, CENT, }, }, math::fixed::FixedMul, traits::{DeployPoolApi, DistributeFees}, types::{ - AccountIdTest, Amount, Balance, BasicCurrencyAdapter, CurrencyId, Hash, MarketId, Moment, + AccountIdTest, Amount, Balance, BasicCurrencyAdapter, CombinatorialId, CurrencyId, Hash, + MarketId, Moment, }, }; +use zrml_combinatorial_tokens::types::CryptographicIdManager; use zrml_neo_swaps::BalanceOf; + #[cfg(feature = "parachain")] use { orml_traits::asset_registry::AssetProcessor, parity_scale_codec::Encode, - sp_runtime::DispatchError, zeitgeist_primitives::types::CustomMetadata, + sp_runtime::DispatchError, zeitgeist_primitives::types::Asset, + zeitgeist_primitives::types::CustomMetadata, }; +#[cfg(feature = "runtime-benchmarks")] +use zeitgeist_primitives::types::NoopCombinatorialTokensBenchmarkHelper; + pub const ALICE: AccountIdTest = 0; #[allow(unused)] pub const BOB: AccountIdTest = 1; @@ -95,6 +101,7 @@ ord_parameter_types! { } parameter_types! { pub storage NeoMinSwapFee: Balance = 0; + pub storage MaxSplits: u16 = 128; } parameter_types! { pub const AdvisoryBond: Balance = 0; @@ -168,6 +175,7 @@ construct_runtime!( AssetRegistry: orml_asset_registry, Authorized: zrml_authorized, Balances: pallet_balances, + CombinatorialTokens: zrml_combinatorial_tokens, Court: zrml_court, MarketCommons: zrml_market_commons, PredictionMarkets: zrml_prediction_markets, @@ -181,6 +189,9 @@ construct_runtime!( ); impl crate::Config for Runtime { + type CombinatorialId = CombinatorialId; + type CombinatorialTokens = CombinatorialTokens; + type CombinatorialTokensUnsafe = CombinatorialTokens; type CompleteSetOperations = PredictionMarkets; type ExternalFees = ExternalFees; type MarketCommons = MarketCommons; @@ -188,6 +199,7 @@ impl crate::Config for Runtime { type PoolId = MarketId; type RuntimeEvent = RuntimeEvent; type MaxLiquidityTreeDepth = MaxLiquidityTreeDepth; + type MaxSplits = MaxSplits; type MaxSwapFee = NeoMaxSwapFee; type PalletId = NeoSwapsPalletId; type WeightInfo = zrml_neo_swaps::weights::WeightInfo; @@ -252,6 +264,18 @@ impl zrml_authorized::Config for Runtime { type WeightInfo = zrml_authorized::weights::WeightInfo; } +impl zrml_combinatorial_tokens::Config for Runtime { + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = NoopCombinatorialTokensBenchmarkHelper; + type CombinatorialIdManager = CryptographicIdManager; + type MarketCommons = MarketCommons; + type MultiCurrency = AssetManager; + type Payout = PredictionMarkets; + type RuntimeEvent = RuntimeEvent; + type PalletId = CombinatorialTokensPalletId; + type WeightInfo = zrml_combinatorial_tokens::weights::WeightInfo; +} + impl zrml_court::Config for Runtime { type AppealBond = AppealBond; type BlocksPerYear = BlocksPerYear; diff --git a/zrml/neo-swaps/src/pool_storage.rs b/zrml/neo-swaps/src/pool_storage.rs new file mode 100644 index 000000000..603b4b8a2 --- /dev/null +++ b/zrml/neo-swaps/src/pool_storage.rs @@ -0,0 +1,56 @@ +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use crate::{traits::PoolStorage, Config, Error, Pallet, PoolCount, PoolOf, Pools}; +use sp_runtime::DispatchError; +use zeitgeist_primitives::math::checked_ops_res::CheckedAddRes; + +impl PoolStorage for Pallet +where + T: Config, +{ + type PoolId = T::PoolId; + type Pool = PoolOf; + + // TODO Make `PoolId` as u32. + fn next_pool_id() -> T::PoolId { + PoolCount::::get() + } + + fn add(pool: Self::Pool) -> Result { + let pool_id = Self::next_pool_id(); + Pools::::insert(pool_id, pool); + + let pool_count = pool_id.checked_add_res(&1u8.into())?; // TODO Add CheckedInc. + PoolCount::::set(pool_count); + + Ok(pool_id) + } + + fn get(pool_id: Self::PoolId) -> Result { + Pools::::get(pool_id).ok_or(Error::::PoolNotFound.into()) + } + + fn try_mutate_pool(pool_id: &Self::PoolId, mutator: F) -> Result + where + F: FnMut(&mut PoolOf) -> Result, + { + Pools::::try_mutate(pool_id, |maybe_pool| { + maybe_pool.as_mut().ok_or(Error::::PoolNotFound.into()).and_then(mutator) + }) + } +} diff --git a/zrml/neo-swaps/src/tests/buy.rs b/zrml/neo-swaps/src/tests/buy.rs index 4267fca21..62a7f4cf0 100644 --- a/zrml/neo-swaps/src/tests/buy.rs +++ b/zrml/neo-swaps/src/tests/buy.rs @@ -86,7 +86,7 @@ fn buy_works() { System::assert_last_event( Event::BuyExecuted { who: BOB, - market_id, + pool_id: market_id, asset_out, amount_in, amount_out: expected_amount_out, @@ -346,3 +346,32 @@ fn buy_fails_on_amount_out_below_min() { ); }); } + +#[test] +fn buy_fails_on_invalid_pool_type() { + ExtBuilder::default().build().execute_with(|| { + let (_, pool_id) = create_markets_and_deploy_combinatorial_pool( + ALICE, + BASE_ASSET, + vec![MarketType::Scalar(0..=1)], + _10, + vec![_1_2, _1_2], + CENT, + ); + + let pool = as PoolStorage>::get(pool_id).unwrap(); + let assets = pool.assets(); + + assert_noop!( + NeoSwaps::buy( + RuntimeOrigin::signed(BOB), + pool_id, + 2, + assets[0], + _1, + 0 + ), + Error::::InvalidPoolType, + ); + }); +} diff --git a/zrml/neo-swaps/src/tests/combo_buy.rs b/zrml/neo-swaps/src/tests/combo_buy.rs index 461992db8..df1185e45 100644 --- a/zrml/neo-swaps/src/tests/combo_buy.rs +++ b/zrml/neo-swaps/src/tests/combo_buy.rs @@ -17,7 +17,6 @@ use super::*; use test_case::test_case; -use zeitgeist_primitives::types::Asset::CategoricalOutcome; // Example taken from // https://docs.gnosis.io/conditionaltokens/docs/introduction3/#an-example-with-lmsr @@ -27,15 +26,15 @@ fn combo_buy_works() { let liquidity = _10; let spot_prices = vec![_1_2, _1_2]; let swap_fee = CENT; - let market_id = create_market_and_deploy_pool( + let (_, pool_id) = create_markets_and_deploy_combinatorial_pool( ALICE, BASE_ASSET, - MarketType::Categorical(2), + vec![MarketType::Categorical(2)], liquidity, spot_prices.clone(), swap_fee, ); - let pool = Pools::::get(market_id).unwrap(); + let pool = Pools::::get(pool_id).unwrap(); let total_fee_percentage = swap_fee + EXTERNAL_FEES; let amount_in_minus_fees = _10; let amount_in = amount_in_minus_fees.bdiv(_1 - total_fee_percentage).unwrap(); // This is exactly _10 after deducting fees. @@ -53,14 +52,14 @@ fn combo_buy_works() { assert_ok!(AssetManager::deposit(sell[0], &pool.account_id, _100)); assert_ok!(NeoSwaps::combo_buy( RuntimeOrigin::signed(BOB), - market_id, + pool_id, 2, buy.clone(), sell.clone(), amount_in, 0, )); - let pool = Pools::::get(market_id).unwrap(); + let pool = Pools::::get(pool_id).unwrap(); let expected_swap_amount_out = 58496250072; let expected_amount_in_minus_fees = _10 + 1; // Note: This is 1 Pennock off of the correct result. let expected_reserves = vec![ @@ -68,7 +67,7 @@ fn combo_buy_works() { pool_outcomes_before[0] + expected_amount_in_minus_fees, ]; assert_pool_state!( - market_id, + pool_id, expected_reserves, vec![_3_4, _1_4], liquidity_parameter_before, @@ -87,7 +86,7 @@ fn combo_buy_works() { System::assert_last_event( Event::ComboBuyExecuted { who: BOB, - market_id, + pool_id, buy, sell, amount_in, @@ -101,6 +100,7 @@ fn combo_buy_works() { } #[test_case( + vec![MarketType::Categorical(5)], 333 * _1, vec![10 * CENT, 30 * CENT, 25 * CENT, 13 * CENT, 22 * CENT], vec![0, 2], @@ -114,6 +114,7 @@ fn combo_buy_works() { 1_020_408_163 )] #[test_case( + vec![MarketType::Categorical(5)], _100, vec![80 * CENT, 5 * CENT, 5 * CENT, 5 * CENT, 5 * CENT], vec![4], @@ -127,23 +128,24 @@ fn combo_buy_works() { 3_367_346_939 )] #[test_case( + vec![MarketType::Categorical(2), MarketType::Categorical(2), MarketType::Scalar(0..=1)], 1000 * _1, vec![1_250_000_000; 8], vec![0, 2, 5, 6, 7], vec![], vec![1, 3, 4], - 5_102_040_816_326, - 6_576_234_413_776, + 5_208_333_333_333, + 6_576_234_413_778, 5_000_000_000_000, vec![ - 8_423_765_586_224, - 1500 * _1, - 8_423_765_586_224, - 1500 * _1, - 1500 * _1, - 8_423_765_586_224, - 8_423_765_586_224, - 8_423_765_586_224, + 8_423_765_586_223, + 1500 * _1 + 1, + 8_423_765_586_223, + 1500 * _1 + 1, + 1500 * _1 + 1, + 8_423_765_586_223, + 8_423_765_586_223, + 8_423_765_586_223, ], vec![ 1_734_834_957, @@ -155,9 +157,10 @@ fn combo_buy_works() { 1_734_834_957, 1_734_834_957, ], - 51_020_408_163 + 52_083_333_333 )] fn combo_buy_works_multi_market( + market_types: Vec, liquidity: u128, spot_prices: Vec, buy_indices: Vec, @@ -168,15 +171,15 @@ fn combo_buy_works_multi_market( expected_amount_out_keep: u128, expected_reserves: Vec, expected_spot_prices: Vec, - expected_fees: u128, + expected_fees: u128, // pool fees, not market fees ) { ExtBuilder::default().build().execute_with(|| { let asset_count = spot_prices.len() as u16; let swap_fee = CENT; - let market_id = create_market_and_deploy_pool( + let (_, pool_id) = create_markets_and_deploy_combinatorial_pool( ALICE, BASE_ASSET, - MarketType::Categorical(asset_count), + market_types, liquidity, spot_prices.clone(), swap_fee, @@ -184,18 +187,16 @@ fn combo_buy_works_multi_market( let sentinel = 123_456_789; assert_ok!(AssetManager::deposit(BASE_ASSET, &BOB, amount_in + sentinel)); - let pool = Pools::::get(market_id).unwrap(); + let pool = Pools::::get(pool_id).unwrap(); let expected_liquidity = pool.liquidity_parameter; - let buy: Vec<_> = - buy_indices.iter().map(|&i| Asset::CategoricalOutcome(market_id, i)).collect(); - let keep: Vec<_> = - keep_indices.iter().map(|&i| Asset::CategoricalOutcome(market_id, i)).collect(); - let sell: Vec<_> = - sell_indices.iter().map(|&i| Asset::CategoricalOutcome(market_id, i)).collect(); + let buy: Vec<_> = buy_indices.iter().map(|&i| pool.assets()[i as usize]).collect(); + let keep: Vec<_> = keep_indices.iter().map(|&i| pool.assets()[i as usize]).collect(); + let sell: Vec<_> = sell_indices.iter().map(|&i| pool.assets()[i as usize]).collect(); + assert_ok!(NeoSwaps::combo_buy( RuntimeOrigin::signed(BOB), - market_id, + pool_id, asset_count, buy.clone(), sell.clone(), @@ -215,7 +216,7 @@ fn combo_buy_works_multi_market( } assert_pool_state!( - market_id, + pool_id, expected_reserves, expected_spot_prices, expected_liquidity, @@ -228,21 +229,23 @@ fn combo_buy_works_multi_market( #[test] fn combo_buy_fails_on_incorrect_asset_count() { ExtBuilder::default().build().execute_with(|| { - let market_id = create_market_and_deploy_pool( + let (_, pool_id) = create_markets_and_deploy_combinatorial_pool( ALICE, BASE_ASSET, - MarketType::Scalar(0..=1), + vec![MarketType::Categorical(2), MarketType::Scalar(0..=1)], _10, - vec![_1_2, _1_2], + vec![_1_4, _1_4, _1_4, _1_4], CENT, ); + let pool = as PoolStorage>::get(pool_id).unwrap(); + let assets = pool.assets(); assert_noop!( NeoSwaps::combo_buy( RuntimeOrigin::signed(BOB), - market_id, - 1, - vec![Asset::ScalarOutcome(market_id, ScalarPosition::Long)], - vec![Asset::ScalarOutcome(market_id, ScalarPosition::Short)], + pool_id, + 3, + vec![assets[0]], + vec![assets[1]], _1, 0 ), @@ -254,26 +257,27 @@ fn combo_buy_fails_on_incorrect_asset_count() { #[test] fn combo_buy_fails_on_market_not_found() { ExtBuilder::default().build().execute_with(|| { - let market_id = create_market_and_deploy_pool( + let (_, pool_id) = create_markets_and_deploy_combinatorial_pool( ALICE, BASE_ASSET, - MarketType::Scalar(0..=1), + vec![MarketType::Categorical(2), MarketType::Scalar(0..=1)], _10, - vec![_1_2, _1_2], + vec![_1_4, _1_4, _1_4, _1_4], CENT, ); - Markets::::remove(market_id); + let pool = as PoolStorage>::get(pool_id).unwrap(); + let assets = pool.assets(); assert_noop!( NeoSwaps::combo_buy( RuntimeOrigin::signed(BOB), - market_id, - 2, - vec![Asset::ScalarOutcome(market_id, ScalarPosition::Long)], - vec![Asset::ScalarOutcome(market_id, ScalarPosition::Short)], + 1, + 4, + vec![assets[0]], + vec![assets[1]], _1, 0 ), - zrml_market_commons::Error::::MarketDoesNotExist, + Error::::PoolNotFound, ); }); } @@ -285,15 +289,17 @@ fn combo_buy_fails_on_market_not_found() { #[test_case(MarketStatus::Resolved)] fn combo_buy_fails_on_inactive_market(market_status: MarketStatus) { ExtBuilder::default().build().execute_with(|| { - let market_id = create_market_and_deploy_pool( + let (market_ids, pool_id) = create_markets_and_deploy_combinatorial_pool( ALICE, BASE_ASSET, - MarketType::Scalar(0..=1), + vec![MarketType::Categorical(2), MarketType::Scalar(0..=1)], _10, - vec![_1_2, _1_2], + vec![_1_4, _1_4, _1_4, _1_4], CENT, ); - MarketCommons::mutate_market(&market_id, |market| { + let pool = as PoolStorage>::get(pool_id).unwrap(); + let assets = pool.assets(); + MarketCommons::mutate_market(&market_ids[1], |market| { market.status = market_status; Ok(()) }) @@ -301,10 +307,10 @@ fn combo_buy_fails_on_inactive_market(market_status: MarketStatus) { assert_noop!( NeoSwaps::combo_buy( RuntimeOrigin::signed(BOB), - market_id, - 2, - vec![Asset::ScalarOutcome(market_id, ScalarPosition::Long)], - vec![Asset::ScalarOutcome(market_id, ScalarPosition::Short)], + pool_id, + 4, + vec![assets[0]], + vec![assets[1]], _1, 0 ), @@ -313,50 +319,38 @@ fn combo_buy_fails_on_inactive_market(market_status: MarketStatus) { }); } -#[test] -fn combo_buy_fails_on_pool_not_found() { - ExtBuilder::default().build().execute_with(|| { - let market_id = - create_market(ALICE, BASE_ASSET, MarketType::Scalar(0..=1), ScoringRule::AmmCdaHybrid); - assert_noop!( - NeoSwaps::combo_buy( - RuntimeOrigin::signed(BOB), - market_id, - 2, - vec![Asset::ScalarOutcome(market_id, ScalarPosition::Long)], - vec![Asset::ScalarOutcome(market_id, ScalarPosition::Short)], - _1, - 0 - ), - Error::::PoolNotFound, - ); - }); -} - #[test] fn combo_buy_fails_on_insufficient_funds() { ExtBuilder::default().build().execute_with(|| { - let market_id = create_market_and_deploy_pool( + let (_, pool_id) = create_markets_and_deploy_combinatorial_pool( ALICE, BASE_ASSET, - MarketType::Scalar(0..=1), + vec![MarketType::Categorical(2), MarketType::Scalar(0..=1)], _10, - vec![_1_2, _1_2], + vec![_1_4, _1_4, _1_4, _1_4], CENT, ); + let pool = as PoolStorage>::get(pool_id).unwrap(); + let assets = pool.assets(); let amount_in = _10; assert_ok!(AssetManager::deposit(BASE_ASSET, &BOB, amount_in - 1)); + + #[cfg(feature = "parachain")] + let expected_error = orml_tokens::Error::::BalanceTooLow; + #[cfg(not(feature = "parachain"))] + let expected_error = orml_currencies::Error::::BalanceTooLow; + assert_noop!( NeoSwaps::combo_buy( RuntimeOrigin::signed(BOB), - market_id, - 2, - vec![Asset::ScalarOutcome(market_id, ScalarPosition::Long)], - vec![Asset::ScalarOutcome(market_id, ScalarPosition::Short)], + pool_id, + 4, + vec![assets[0]], + vec![assets[1]], amount_in, 0, ), - zrml_prediction_markets::Error::::NotEnoughBalance, + expected_error ); }); } @@ -364,26 +358,28 @@ fn combo_buy_fails_on_insufficient_funds() { #[test] fn combo_buy_fails_on_amount_out_below_min() { ExtBuilder::default().build().execute_with(|| { - let market_id = create_market_and_deploy_pool( + let (_, pool_id) = create_markets_and_deploy_combinatorial_pool( ALICE, BASE_ASSET, - MarketType::Scalar(0..=1), + vec![MarketType::Categorical(2), MarketType::Scalar(0..=1)], _10, - vec![_1_2, _1_2], + vec![_1_4, _1_4, _1_4, _1_4], CENT, ); + let pool = as PoolStorage>::get(pool_id).unwrap(); + let assets = pool.assets(); let amount_in = _1; assert_ok!(AssetManager::deposit(BASE_ASSET, &BOB, amount_in)); - // Buying 1 at price of .5 will return less than 2 outcomes due to slippage. + // Buying 1 at price of .25 will return less than 4 outcomes due to slippage. assert_noop!( NeoSwaps::combo_buy( RuntimeOrigin::signed(BOB), - market_id, - 2, - vec![Asset::ScalarOutcome(market_id, ScalarPosition::Long)], - vec![Asset::ScalarOutcome(market_id, ScalarPosition::Short)], + pool_id, + 4, + vec![assets[0]], + vec![assets[1]], amount_in, - _2, + _4, ), Error::::AmountOutBelowMin, ); @@ -396,27 +392,26 @@ fn combo_buy_fails_on_amount_out_below_min() { #[test_case(vec![0, 2, 3], vec![1, 3, 4]; "overlap2")] #[test_case(vec![0, 1, 3, 1], vec![2]; "duplicate_buy")] #[test_case(vec![0, 1, 3], vec![4, 2, 4]; "duplicate_sell")] -#[test_case(vec![999], vec![0, 1, 2, 3, 4]; "out_of_bounds_buy")] -#[test_case(vec![0, 1, 3], vec![999]; "out_of_bounds_sell")] -fn combo_buy_fails_on_invalid_partition(indices_buy: Vec, indices_sell: Vec) { +fn combo_buy_fails_on_invalid_partition(buy_indices: Vec, sell_indices: Vec) { ExtBuilder::default().build().execute_with(|| { - let market_id = create_market_and_deploy_pool( + let (_, pool_id) = create_markets_and_deploy_combinatorial_pool( ALICE, BASE_ASSET, - MarketType::Categorical(5), + vec![MarketType::Categorical(5)], _10, vec![_1_5, _1_5, _1_5, _1_5, _1_5], CENT, ); + let pool = as PoolStorage>::get(pool_id).unwrap(); + let assets = pool.assets(); let amount_in = _1; assert_ok!(AssetManager::deposit(BASE_ASSET, &BOB, amount_in)); - let buy = indices_buy.into_iter().map(|i| CategoricalOutcome(market_id, i)).collect(); - let sell = indices_sell.into_iter().map(|i| CategoricalOutcome(market_id, i)).collect(); + let buy: Vec<_> = buy_indices.iter().map(|&i| assets[i as usize]).collect(); + let sell: Vec<_> = sell_indices.iter().map(|&i| assets[i as usize]).collect(); - // Buying 1 at price of .5 will return less than 2 outcomes due to slippage. assert_noop!( - NeoSwaps::combo_buy(RuntimeOrigin::signed(BOB), market_id, 5, buy, sell, amount_in, 0), + NeoSwaps::combo_buy(RuntimeOrigin::signed(BOB), pool_id, 5, buy, sell, amount_in, 0), Error::::InvalidPartition, ); }); @@ -425,57 +420,61 @@ fn combo_buy_fails_on_invalid_partition(indices_buy: Vec, indices_sell: Vec #[test] fn combo_buy_fails_on_spot_price_slipping_too_low() { ExtBuilder::default().build().execute_with(|| { - let market_id = create_market_and_deploy_pool( + let (_, pool_id) = create_markets_and_deploy_combinatorial_pool( ALICE, BASE_ASSET, - MarketType::Categorical(5), + vec![MarketType::Categorical(5)], _10, vec![_1_5, _1_5, _1_5, _1_5, _1_5], CENT, ); let amount_in = _100; + let pool = as PoolStorage>::get(pool_id).unwrap(); + let assets = pool.assets(); assert_ok!(AssetManager::deposit(BASE_ASSET, &BOB, amount_in)); - let buy = [0, 1, 2, 3].into_iter().map(|i| CategoricalOutcome(market_id, i)).collect(); - let sell = [4].into_iter().map(|i| CategoricalOutcome(market_id, i)).collect(); + let buy = assets[0..4].to_vec(); + let sell = vec![assets[4]]; assert_noop!( - NeoSwaps::combo_buy(RuntimeOrigin::signed(BOB), market_id, 5, buy, sell, amount_in, 0), + NeoSwaps::combo_buy(RuntimeOrigin::signed(BOB), pool_id, 5, buy, sell, amount_in, 0), Error::::NumericalLimits(NumericalLimitsError::SpotPriceSlippedTooLow), ); }); } #[test] -fn combo_buy_fails_on_spot_price_slipping_too_high() { +fn combo_buy_fails_on_large_buy() { ExtBuilder::default().build().execute_with(|| { - let market_id = create_market_and_deploy_pool( + let (_, pool_id) = create_markets_and_deploy_combinatorial_pool( ALICE, BASE_ASSET, - MarketType::Categorical(5), + vec![MarketType::Categorical(5)], _10, vec![_1_5, _1_5, _1_5, _1_5, _1_5], CENT, ); - let amount_in = _100; + let amount_in = 100 * _100; + let pool = as PoolStorage>::get(pool_id).unwrap(); + let assets = pool.assets(); assert_ok!(AssetManager::deposit(BASE_ASSET, &BOB, amount_in)); - let buy = [0].into_iter().map(|i| CategoricalOutcome(market_id, i)).collect(); - let sell = [1, 2, 3, 4].into_iter().map(|i| CategoricalOutcome(market_id, i)).collect(); + let buy = vec![assets[4]]; + let sell = assets[0..2].to_vec(); assert_noop!( - NeoSwaps::combo_buy(RuntimeOrigin::signed(BOB), market_id, 5, buy, sell, amount_in, 0), - Error::::NumericalLimits(NumericalLimitsError::SpotPriceSlippedTooHigh), + NeoSwaps::combo_buy(RuntimeOrigin::signed(BOB), pool_id, 5, buy, sell, amount_in, 0), + Error::::MathError, ); }); } #[test] -fn combo_buy_fails_on_large_buy() { +fn combo_buy_fails_on_invalid_pool_type() { ExtBuilder::default().build().execute_with(|| { - let market_id = create_market_and_deploy_pool( + let pool_id = create_market_and_deploy_pool( ALICE, BASE_ASSET, MarketType::Categorical(5), @@ -483,16 +482,19 @@ fn combo_buy_fails_on_large_buy() { vec![_1_5, _1_5, _1_5, _1_5, _1_5], CENT, ); - let amount_in = 100 * _100; + + let amount_in = _1; + let pool = as PoolStorage>::get(pool_id).unwrap(); + let assets = pool.assets(); assert_ok!(AssetManager::deposit(BASE_ASSET, &BOB, amount_in)); - let buy = [0].into_iter().map(|i| CategoricalOutcome(market_id, i)).collect(); - let sell = [1, 2].into_iter().map(|i| CategoricalOutcome(market_id, i)).collect(); + let buy = vec![assets[4]]; + let sell = assets[0..2].to_vec(); assert_noop!( - NeoSwaps::combo_buy(RuntimeOrigin::signed(BOB), market_id, 5, buy, sell, amount_in, 0), - Error::::MathError, + NeoSwaps::combo_buy(RuntimeOrigin::signed(BOB), pool_id, 5, buy, sell, amount_in, 0), + Error::::InvalidPoolType, ); }); } diff --git a/zrml/neo-swaps/src/tests/combo_sell.rs b/zrml/neo-swaps/src/tests/combo_sell.rs index 52d0ea810..8bb6cb5e8 100644 --- a/zrml/neo-swaps/src/tests/combo_sell.rs +++ b/zrml/neo-swaps/src/tests/combo_sell.rs @@ -17,7 +17,6 @@ use super::*; use test_case::test_case; -use zeitgeist_primitives::types::Asset::CategoricalOutcome; #[test] fn combo_sell_works() { @@ -25,25 +24,32 @@ fn combo_sell_works() { let liquidity = _10; let spot_prices = vec![_1_4, _3_4]; let swap_fee = CENT; - let market_id = create_market_and_deploy_pool( + let (_, pool_id) = create_markets_and_deploy_combinatorial_pool( ALICE, BASE_ASSET, - MarketType::Scalar(0..=1), + vec![MarketType::Scalar(0..=1)], liquidity, spot_prices.clone(), swap_fee, ); - let pool = Pools::::get(market_id).unwrap(); + let pool = Pools::::get(pool_id).unwrap(); let amount_buy = _10; let amount_keep = 0; let liquidity_parameter_before = pool.liquidity_parameter; - deposit_complete_set(market_id, BOB, amount_buy); - let buy = vec![pool.assets()[1]]; + + let buy_asset = pool.assets()[1]; + let sell_asset = pool.assets()[0]; + let buy = vec![buy_asset]; let keep = vec![]; - let sell = vec![pool.assets()[0]]; + let sell = vec![sell_asset]; + + for &asset in buy.iter() { + assert_ok!(AssetManager::deposit(asset, &BOB, amount_buy)); + } + assert_ok!(NeoSwaps::combo_sell( RuntimeOrigin::signed(BOB), - market_id, + pool_id, 2, buy.clone(), keep.clone(), @@ -59,9 +65,9 @@ fn combo_sell_works() { let expected_external_fee_amount = expected_fees - expected_swap_fee_amount; let expected_amount_out_minus_fees = expected_amount_out - expected_fees; assert_balance!(BOB, BASE_ASSET, expected_amount_out_minus_fees); - assert_balance!(BOB, buy[0], 0); + assert_balance!(BOB, buy_asset, 0); assert_pool_state!( - market_id, + pool_id, vec![40367746103, 61119621067], [5_714_285_714, 4_285_714_286], liquidity_parameter_before, @@ -74,18 +80,10 @@ fn combo_sell_works() { expected_swap_fee_amount + AssetManager::minimum_balance(pool.collateral) ); assert_balance!(FEE_ACCOUNT, BASE_ASSET, expected_external_fee_amount); - assert_eq!( - AssetManager::total_issuance(pool.assets()[0]), - liquidity + amount_buy - expected_amount_out - ); - assert_eq!( - AssetManager::total_issuance(pool.assets()[1]), - liquidity + amount_buy - expected_amount_out - ); System::assert_last_event( Event::ComboSellExecuted { who: BOB, - market_id, + pool_id, buy, keep, sell, @@ -101,6 +99,7 @@ fn combo_sell_works() { } #[test_case( + vec![MarketType::Categorical(2), MarketType::Categorical(2), MarketType::Scalar(0..=1)], 1000 * _1, vec![1_250_000_000; 8], vec![0, 2, 5], @@ -108,7 +107,7 @@ fn combo_sell_works() { vec![1, 3, 4], _500, _300, - 2_091_832_646_248, + 2_049_142_184_080, vec![ 12_865_476_891_584, 7_865_476_891_584, @@ -132,6 +131,7 @@ fn combo_sell_works() { 21_345_231_084 )] #[test_case( + vec![MarketType::Categorical(3)], _321, vec![20 * CENT, 30 * CENT, 50 * CENT], vec![0, 2], @@ -153,6 +153,7 @@ fn combo_sell_works() { 20_540_028_899 )] fn combo_sell_works_multi_market( + market_types: Vec, liquidity: u128, spot_prices: Vec, buy_indices: Vec, @@ -168,21 +169,21 @@ fn combo_sell_works_multi_market( ExtBuilder::default().build().execute_with(|| { let asset_count = spot_prices.len() as u16; let swap_fee = CENT; - let market_id = create_market_and_deploy_pool( + let (_, pool_id) = create_markets_and_deploy_combinatorial_pool( ALICE, BASE_ASSET, - MarketType::Categorical(asset_count), + market_types, liquidity, spot_prices.clone(), swap_fee, ); - let buy: Vec<_> = - buy_indices.iter().map(|&i| Asset::CategoricalOutcome(market_id, i)).collect(); - let keep: Vec<_> = - keep_indices.iter().map(|&i| Asset::CategoricalOutcome(market_id, i)).collect(); - let sell: Vec<_> = - sell_indices.iter().map(|&i| Asset::CategoricalOutcome(market_id, i)).collect(); + let pool = as PoolStorage>::get(pool_id).unwrap(); + let expected_liquidity = pool.liquidity_parameter; + + let buy: Vec<_> = buy_indices.iter().map(|&i| pool.assets()[i as usize]).collect(); + let keep: Vec<_> = keep_indices.iter().map(|&i| pool.assets()[i as usize]).collect(); + let sell: Vec<_> = sell_indices.iter().map(|&i| pool.assets()[i as usize]).collect(); for &asset in buy.iter() { assert_ok!(AssetManager::deposit(asset, &BOB, amount_in_buy)); @@ -191,12 +192,9 @@ fn combo_sell_works_multi_market( assert_ok!(AssetManager::deposit(asset, &BOB, amount_in_keep)); } - let pool = Pools::::get(market_id).unwrap(); - let expected_liquidity = pool.liquidity_parameter; - assert_ok!(NeoSwaps::combo_sell( RuntimeOrigin::signed(BOB), - market_id, + pool_id, asset_count, buy.clone(), keep.clone(), @@ -211,7 +209,7 @@ fn combo_sell_works_multi_market( assert_balance!(BOB, asset, 0); } assert_pool_state!( - market_id, + pool_id, expected_reserves, expected_spot_prices, expected_liquidity, @@ -224,22 +222,24 @@ fn combo_sell_works_multi_market( #[test] fn combo_sell_fails_on_incorrect_asset_count() { ExtBuilder::default().build().execute_with(|| { - let market_id = create_market_and_deploy_pool( + let (_, pool_id) = create_markets_and_deploy_combinatorial_pool( ALICE, BASE_ASSET, - MarketType::Scalar(0..=1), - _10, - vec![_1_2, _1_2], + vec![MarketType::Categorical(2), MarketType::Categorical(2), MarketType::Scalar(0..=1)], + _100, + vec![1_250_000_000; 8], CENT, ); + let pool = as PoolStorage>::get(pool_id).unwrap(); + assert_noop!( NeoSwaps::combo_sell( RuntimeOrigin::signed(BOB), - market_id, - 1, - vec![Asset::ScalarOutcome(market_id, ScalarPosition::Long)], + pool_id, + 7, + vec![pool.assets()[0]], vec![], - vec![Asset::ScalarOutcome(market_id, ScalarPosition::Short)], + vec![pool.assets()[1]], _1, 0, 0 @@ -250,30 +250,31 @@ fn combo_sell_fails_on_incorrect_asset_count() { } #[test] -fn combo_sell_fails_on_market_not_found() { +fn combo_sell_fails_on_pool_not_found() { ExtBuilder::default().build().execute_with(|| { - let market_id = create_market_and_deploy_pool( + let (_, pool_id) = create_markets_and_deploy_combinatorial_pool( ALICE, BASE_ASSET, - MarketType::Scalar(0..=1), - _10, - vec![_1_2, _1_2], + vec![MarketType::Categorical(2), MarketType::Categorical(2), MarketType::Scalar(0..=1)], + _100, + vec![1_250_000_000; 8], CENT, ); - Markets::::remove(market_id); + let pool = as PoolStorage>::get(pool_id).unwrap(); + assert_noop!( NeoSwaps::combo_sell( RuntimeOrigin::signed(BOB), - market_id, + 1, 2, - vec![Asset::ScalarOutcome(market_id, ScalarPosition::Long)], + vec![pool.assets()[0]], vec![], - vec![Asset::ScalarOutcome(market_id, ScalarPosition::Short)], + vec![pool.assets()[1]], _1, 0, 0 ), - zrml_market_commons::Error::::MarketDoesNotExist, + Error::::PoolNotFound, ); }); } @@ -285,15 +286,16 @@ fn combo_sell_fails_on_market_not_found() { #[test_case(MarketStatus::Resolved)] fn combo_sell_fails_on_inactive_market(market_status: MarketStatus) { ExtBuilder::default().build().execute_with(|| { - let market_id = create_market_and_deploy_pool( + let (market_ids, pool_id) = create_markets_and_deploy_combinatorial_pool( ALICE, BASE_ASSET, - MarketType::Scalar(0..=1), - _10, - vec![_1_2, _1_2], + vec![MarketType::Categorical(2), MarketType::Categorical(2), MarketType::Scalar(0..=1)], + _100, + vec![1_250_000_000; 8], CENT, ); - MarketCommons::mutate_market(&market_id, |market| { + let pool = as PoolStorage>::get(pool_id).unwrap(); + MarketCommons::mutate_market(&market_ids[1], |market| { market.status = market_status; Ok(()) }) @@ -301,11 +303,11 @@ fn combo_sell_fails_on_inactive_market(market_status: MarketStatus) { assert_noop!( NeoSwaps::combo_sell( RuntimeOrigin::signed(BOB), - market_id, - 2, - vec![Asset::ScalarOutcome(market_id, ScalarPosition::Long)], + pool_id, + 8, + vec![pool.assets()[0]], vec![], - vec![Asset::ScalarOutcome(market_id, ScalarPosition::Short)], + vec![pool.assets()[1]], _1, 0, 0 @@ -316,49 +318,77 @@ fn combo_sell_fails_on_inactive_market(market_status: MarketStatus) { } #[test] -fn combo_sell_fails_on_pool_not_found() { +fn combo_sell_fails_on_insufficient_funds_with_keep() { ExtBuilder::default().build().execute_with(|| { - let market_id = - create_market(ALICE, BASE_ASSET, MarketType::Scalar(0..=1), ScoringRule::AmmCdaHybrid); + let (_, pool_id) = create_markets_and_deploy_combinatorial_pool( + ALICE, + BASE_ASSET, + vec![MarketType::Categorical(2), MarketType::Categorical(2), MarketType::Scalar(0..=1)], + _100, + vec![1_250_000_000; 8], + CENT, + ); + let pool = as PoolStorage>::get(pool_id).unwrap(); + let buy_amount = _10; + let keep_amount = _9; + + assert_ok!(AssetManager::deposit(pool.assets()[0], &BOB, buy_amount)); + assert_ok!(AssetManager::deposit(pool.assets()[1], &BOB, buy_amount)); + assert_ok!(AssetManager::deposit(pool.assets()[2], &BOB, buy_amount)); + assert_ok!(AssetManager::deposit(pool.assets()[3], &BOB, buy_amount)); + assert_ok!(AssetManager::deposit(pool.assets()[4], &BOB, buy_amount)); + assert_ok!(AssetManager::deposit(pool.assets()[5], &BOB, buy_amount)); + assert_ok!(AssetManager::deposit(pool.assets()[6], &BOB, keep_amount)); + assert_ok!(AssetManager::deposit(pool.assets()[7], &BOB, keep_amount - 1)); + assert_noop!( NeoSwaps::combo_sell( RuntimeOrigin::signed(BOB), - market_id, - 2, - vec![Asset::ScalarOutcome(market_id, ScalarPosition::Long)], - vec![], - vec![Asset::ScalarOutcome(market_id, ScalarPosition::Short)], - _1, + pool_id, + 8, + vec![pool.assets()[0], pool.assets()[2], pool.assets()[4]], + vec![pool.assets()[6], pool.assets()[7]], + vec![pool.assets()[1], pool.assets()[3], pool.assets()[5]], + buy_amount, + keep_amount, 0, - 0 ), - Error::::PoolNotFound, + orml_tokens::Error::::BalanceTooLow, ); }); } #[test] -fn combo_sell_fails_on_insufficient_funds() { +fn combo_sell_fails_on_insufficient_funds_sans_keep() { ExtBuilder::default().build().execute_with(|| { - let market_id = create_market_and_deploy_pool( + let (_, pool_id) = create_markets_and_deploy_combinatorial_pool( ALICE, BASE_ASSET, - MarketType::Scalar(0..=1), - _10, - vec![_1_2, _1_2], + vec![MarketType::Categorical(2), MarketType::Categorical(2), MarketType::Scalar(0..=1)], + _100, + vec![1_250_000_000; 8], CENT, ); + let pool = as PoolStorage>::get(pool_id).unwrap(); let amount_in = _10; - let asset_in = Asset::ScalarOutcome(market_id, ScalarPosition::Long); - assert_ok!(AssetManager::deposit(asset_in, &BOB, amount_in - 1)); + + assert_ok!(AssetManager::deposit(pool.assets()[0], &BOB, amount_in)); + assert_ok!(AssetManager::deposit(pool.assets()[1], &BOB, amount_in)); + assert_ok!(AssetManager::deposit(pool.assets()[2], &BOB, amount_in - 1)); + assert_ok!(AssetManager::deposit(pool.assets()[3], &BOB, amount_in)); + assert_ok!(AssetManager::deposit(pool.assets()[4], &BOB, amount_in)); + assert_ok!(AssetManager::deposit(pool.assets()[5], &BOB, amount_in)); + assert_ok!(AssetManager::deposit(pool.assets()[6], &BOB, amount_in)); + assert_ok!(AssetManager::deposit(pool.assets()[7], &BOB, amount_in)); + assert_noop!( NeoSwaps::combo_sell( RuntimeOrigin::signed(BOB), - market_id, - 2, - vec![Asset::ScalarOutcome(market_id, ScalarPosition::Long)], + pool_id, + 8, + vec![pool.assets()[0], pool.assets()[2], pool.assets()[4], pool.assets()[6]], vec![], - vec![Asset::ScalarOutcome(market_id, ScalarPosition::Short)], + vec![pool.assets()[1], pool.assets()[3], pool.assets()[5], pool.assets()[7]], amount_in, 0, 0, @@ -371,26 +401,34 @@ fn combo_sell_fails_on_insufficient_funds() { #[test] fn combo_sell_fails_on_amount_out_below_min() { ExtBuilder::default().build().execute_with(|| { - let market_id = create_market_and_deploy_pool( + let (_, pool_id) = create_markets_and_deploy_combinatorial_pool( ALICE, BASE_ASSET, - MarketType::Scalar(0..=1), + vec![MarketType::Categorical(2), MarketType::Categorical(2), MarketType::Scalar(0..=1)], _100, - vec![_1_2, _1_2], + vec![1_250_000_000; 8], CENT, ); - let amount_in = _20; - let asset_in = Asset::ScalarOutcome(market_id, ScalarPosition::Long); - assert_ok!(AssetManager::deposit(asset_in, &BOB, amount_in)); - // Selling 20 at price of .5 will return less than 10 dollars due to slippage. + + let pool = as PoolStorage>::get(pool_id).unwrap(); + let amount_in = _10; + assert_ok!(AssetManager::deposit(pool.assets()[0], &BOB, amount_in)); + assert_ok!(AssetManager::deposit(pool.assets()[1], &BOB, amount_in)); + assert_ok!(AssetManager::deposit(pool.assets()[2], &BOB, amount_in)); + assert_ok!(AssetManager::deposit(pool.assets()[3], &BOB, amount_in)); + assert_ok!(AssetManager::deposit(pool.assets()[4], &BOB, amount_in)); + assert_ok!(AssetManager::deposit(pool.assets()[5], &BOB, amount_in)); + assert_ok!(AssetManager::deposit(pool.assets()[6], &BOB, amount_in)); + assert_ok!(AssetManager::deposit(pool.assets()[7], &BOB, amount_in)); + assert_noop!( NeoSwaps::combo_sell( RuntimeOrigin::signed(BOB), - market_id, - 2, - vec![Asset::ScalarOutcome(market_id, ScalarPosition::Long)], + pool_id, + 8, + vec![pool.assets()[0], pool.assets()[2], pool.assets()[4], pool.assets()[6]], vec![], - vec![Asset::ScalarOutcome(market_id, ScalarPosition::Short)], + vec![pool.assets()[1], pool.assets()[3], pool.assets()[5], pool.assets()[7]], amount_in, 0, _10 @@ -405,37 +443,45 @@ fn combo_sell_fails_on_amount_out_below_min() { #[test_case(vec![0, 1], vec![2, 1], vec![3, 4]; "buy_keep_overlap")] #[test_case(vec![0, 1], vec![2, 4], vec![3, 1]; "buy_sell_overlap")] #[test_case(vec![0, 1], vec![2, 4], vec![4, 3]; "keep_sell_overlap")] -#[test_case(vec![0, 1, 999], vec![2, 4], vec![5, 3]; "out_of_bounds_buy")] -#[test_case(vec![0, 1], vec![2, 4, 999], vec![5, 3]; "out_of_bounds_keep")] -#[test_case(vec![0, 1], vec![2, 4], vec![5, 999, 3]; "out_of_bounds_sell")] #[test_case(vec![0, 6, 1, 6], vec![2, 4], vec![5, 3]; "duplicate_buy")] #[test_case(vec![0, 1], vec![2, 2, 4], vec![5, 3]; "duplicate_keep")] #[test_case(vec![0, 1], vec![2, 4], vec![5, 3, 6, 6, 6]; "duplicate_sell")] fn combo_sell_fails_on_invalid_partition( - indices_buy: Vec, - indices_keep: Vec, - indices_sell: Vec, + buy_indices: Vec, + keep_indices: Vec, + sell_indices: Vec, ) { ExtBuilder::default().build().execute_with(|| { - let market_id = create_market_and_deploy_pool( + let (_, pool_id) = create_markets_and_deploy_combinatorial_pool( ALICE, BASE_ASSET, - MarketType::Categorical(7), - _10, - vec![_1_7, _1_7, _1_7, _1_7, _1_7, _1_7, _1_7 + 4], + vec![MarketType::Categorical(2), MarketType::Categorical(2), MarketType::Scalar(0..=1)], + _100, + vec![1_250_000_000; 8], CENT, ); - let buy = indices_buy.into_iter().map(|i| CategoricalOutcome(market_id, i)).collect(); - let keep = indices_keep.into_iter().map(|i| CategoricalOutcome(market_id, i)).collect(); - let sell = indices_sell.into_iter().map(|i| CategoricalOutcome(market_id, i)).collect(); + let pool = as PoolStorage>::get(pool_id).unwrap(); + let amount_in = _10; + assert_ok!(AssetManager::deposit(pool.assets()[0], &BOB, amount_in)); + assert_ok!(AssetManager::deposit(pool.assets()[1], &BOB, amount_in)); + assert_ok!(AssetManager::deposit(pool.assets()[2], &BOB, amount_in)); + assert_ok!(AssetManager::deposit(pool.assets()[3], &BOB, amount_in)); + assert_ok!(AssetManager::deposit(pool.assets()[4], &BOB, amount_in)); + assert_ok!(AssetManager::deposit(pool.assets()[5], &BOB, amount_in)); + assert_ok!(AssetManager::deposit(pool.assets()[6], &BOB, amount_in)); + assert_ok!(AssetManager::deposit(pool.assets()[7], &BOB, amount_in)); + + let buy: Vec<_> = buy_indices.iter().map(|&i| pool.assets()[i as usize]).collect(); + let keep: Vec<_> = keep_indices.iter().map(|&i| pool.assets()[i as usize]).collect(); + let sell: Vec<_> = sell_indices.iter().map(|&i| pool.assets()[i as usize]).collect(); // Buying 1 at price of .5 will return less than 2 outcomes due to slippage. assert_noop!( NeoSwaps::combo_sell( RuntimeOrigin::signed(BOB), - market_id, - 7, + pool_id, + 8, buy, keep, sell, @@ -451,31 +497,28 @@ fn combo_sell_fails_on_invalid_partition( #[test] fn combo_sell_fails_on_spot_price_slipping_too_low() { ExtBuilder::default().build().execute_with(|| { - let market_id = create_market_and_deploy_pool( + let (_, pool_id) = create_markets_and_deploy_combinatorial_pool( ALICE, BASE_ASSET, - MarketType::Categorical(5), - _10, - vec![_1_5, _1_5, _1_5, _1_5, _1_5], + vec![MarketType::Categorical(5)], + _100, + vec![20 * CENT; 5], CENT, ); - let amount_buy = _100; - - for i in 0..4 { - assert_ok!(AssetManager::deposit( - Asset::CategoricalOutcome(market_id, i), - &BOB, - amount_buy - )); - } + let pool = as PoolStorage>::get(pool_id).unwrap(); + let amount_buy = 1_000 * _1; - let buy = [0, 1, 2, 3].into_iter().map(|i| CategoricalOutcome(market_id, i)).collect(); - let sell = [4].into_iter().map(|i| CategoricalOutcome(market_id, i)).collect(); + let buy = pool.assets()[0..4].to_vec(); + let sell = pool.assets()[4..5].to_vec(); + + for &asset in buy.iter() { + assert_ok!(AssetManager::deposit(asset, &BOB, amount_buy)); + } assert_noop!( NeoSwaps::combo_sell( RuntimeOrigin::signed(BOB), - market_id, + pool_id, 5, buy, vec![], @@ -490,33 +533,30 @@ fn combo_sell_fails_on_spot_price_slipping_too_low() { } #[test] -fn combo_sell_fails_on_spot_price_slipping_too_high() { +fn combo_sell_fails_on_large_amount() { ExtBuilder::default().build().execute_with(|| { - let market_id = create_market_and_deploy_pool( + let (_, pool_id) = create_markets_and_deploy_combinatorial_pool( ALICE, BASE_ASSET, - MarketType::Categorical(5), - _10, - vec![_1_5, _1_5, _1_5, _1_5, _1_5], + vec![MarketType::Categorical(5)], + _100, + vec![20 * CENT; 5], CENT, ); - let amount_buy = _100; - - for i in 0..4 { - assert_ok!(AssetManager::deposit( - Asset::CategoricalOutcome(market_id, i), - &BOB, - amount_buy - )); - } + let pool = as PoolStorage>::get(pool_id).unwrap(); + let amount_buy = 100 * _100; - let buy = [0].into_iter().map(|i| CategoricalOutcome(market_id, i)).collect(); - let sell = [1, 2, 3, 4].into_iter().map(|i| CategoricalOutcome(market_id, i)).collect(); + let sell = pool.assets()[0..4].to_vec(); + let buy = pool.assets()[4..5].to_vec(); + + for &asset in buy.iter() { + assert_ok!(AssetManager::deposit(asset, &BOB, amount_buy)); + } assert_noop!( NeoSwaps::combo_sell( RuntimeOrigin::signed(BOB), - market_id, + pool_id, 5, buy, vec![], @@ -525,85 +565,80 @@ fn combo_sell_fails_on_spot_price_slipping_too_high() { 0, 0 ), - Error::::NumericalLimits(NumericalLimitsError::SpotPriceSlippedTooLow), + Error::::MathError, ); }); } -#[test] -fn combo_sell_fails_on_large_amount() { +#[test_case(vec![], 1)] +#[test_case(vec![2], _2)] +fn combo_sell_fails_on_invalid_amount_keep(keep_indices: Vec, amount_in_keep: u128) { ExtBuilder::default().build().execute_with(|| { - let market_id = create_market_and_deploy_pool( + let (_, pool_id) = create_markets_and_deploy_combinatorial_pool( ALICE, BASE_ASSET, - MarketType::Categorical(5), - _10, - vec![_1_5, _1_5, _1_5, _1_5, _1_5], + vec![MarketType::Categorical(5)], + _100, + vec![20 * CENT; 5], CENT, ); - let amount_buy = 100 * _100; - - for i in 0..4 { - assert_ok!(AssetManager::deposit( - Asset::CategoricalOutcome(market_id, i), - &BOB, - amount_buy - )); - } + let pool = as PoolStorage>::get(pool_id).unwrap(); - let buy = [0].into_iter().map(|i| CategoricalOutcome(market_id, i)).collect(); - let sell = [1, 2, 3, 4].into_iter().map(|i| CategoricalOutcome(market_id, i)).collect(); + let buy = vec![pool.assets()[1]]; + let keep: Vec<_> = keep_indices.iter().map(|&i| pool.assets()[i as usize]).collect(); + let sell = vec![pool.assets()[0]]; assert_noop!( NeoSwaps::combo_sell( RuntimeOrigin::signed(BOB), - market_id, + pool_id, 5, buy, - vec![], + keep, sell, - amount_buy, + _2, + amount_in_keep, 0, - 0 ), - Error::::MathError, + Error::::InvalidAmountKeep ); }); } -#[test_case(vec![], 1)] -#[test_case(vec![2], _2)] -fn combo_sell_fails_on_invalid_amount_keep(keep_indices: Vec, amount_in_keep: u128) { +#[test] +fn combo_sell_fails_on_invalid_pool_type() { ExtBuilder::default().build().execute_with(|| { - let spot_prices = vec![25 * CENT; 4]; - let asset_count = spot_prices.len() as u16; - let market_id = create_market_and_deploy_pool( + let pool_id = create_market_and_deploy_pool( ALICE, BASE_ASSET, - MarketType::Categorical(asset_count), + MarketType::Categorical(5), _10, - spot_prices, + vec![_1_5, _1_5, _1_5, _1_5, _1_5], CENT, ); + let pool = as PoolStorage>::get(pool_id).unwrap(); + let amount_buy = _1; - let buy = vec![Asset::CategoricalOutcome(market_id, 0)]; - let sell = vec![Asset::CategoricalOutcome(market_id, 1)]; - let keep: Vec<_> = - keep_indices.iter().map(|&i| Asset::CategoricalOutcome(market_id, i)).collect(); + let sell = pool.assets()[0..4].to_vec(); + let buy = pool.assets()[4..5].to_vec(); + + for &asset in buy.iter() { + assert_ok!(AssetManager::deposit(asset, &BOB, amount_buy)); + } assert_noop!( NeoSwaps::combo_sell( RuntimeOrigin::signed(BOB), - market_id, - asset_count, - buy.clone(), - keep.clone(), - sell.clone(), - _1, - amount_in_keep, + pool_id, + 5, + buy, + vec![], + sell, + amount_buy, 0, + 0 ), - Error::::InvalidAmountKeep + Error::::InvalidPoolType, ); }); } diff --git a/zrml/neo-swaps/src/tests/deploy_combinatorial_pool.rs b/zrml/neo-swaps/src/tests/deploy_combinatorial_pool.rs new file mode 100644 index 000000000..2638a0086 --- /dev/null +++ b/zrml/neo-swaps/src/tests/deploy_combinatorial_pool.rs @@ -0,0 +1,515 @@ +// Copyright 2023-2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use super::*; +use crate::liquidity_tree::types::Node; +use alloc::collections::BTreeMap; +use test_case::test_case; +use zeitgeist_primitives::constants::BASE; + +#[test] +fn deploy_combinatorial_pool_works_with_single_market() { + ExtBuilder::default().build().execute_with(|| { + let alice_before = AssetManager::free_balance(BASE_ASSET, &ALICE); + let amount = _10; + let asset_count = 2usize; + let spot_prices = vec![BASE / (asset_count as u128); asset_count]; + let swap_fee = CENT; + let (market_ids, pool_id) = create_markets_and_deploy_combinatorial_pool( + ALICE, + BASE_ASSET, + vec![MarketType::Categorical(2)], + amount, + spot_prices.clone(), + swap_fee, + ); + let pool = Pools::::get(pool_id).unwrap(); + let assets = pool.assets(); + let expected_liquidity = 144_269_504_089; + let buffer = AssetManager::minimum_balance(pool.collateral); + assert_approx!(pool.liquidity_parameter, expected_liquidity, 1); + assert_eq!(pool.collateral, BASE_ASSET); + assert_liquidity_tree_state!( + pool.liquidity_shares_manager, + [Node:: { + account: Some(ALICE), + stake: amount, + fees: 0u128, + descendant_stake: 0u128, + lazy_fees: 0u128, + }], + create_b_tree_map!({ ALICE => 0 }), + Vec::::new(), + ); + assert_eq!(pool.swap_fee, swap_fee); + assert_balance!(pool.account_id, pool.collateral, buffer); + + let mut reserves = BTreeMap::new(); + for (&asset, &price) in assets.iter().zip(spot_prices.iter()) { + assert_balance!(pool.account_id, asset, amount); + assert_eq!(pool.reserve_of(&asset).unwrap(), amount); + assert_eq!(pool.calculate_spot_price(asset).unwrap(), price); + assert_balance!(ALICE, asset, 0); + reserves.insert(asset, amount); + } + assert_balance!(ALICE, BASE_ASSET, alice_before - amount - buffer); + + System::assert_last_event( + Event::CombinatorialPoolDeployed { + who: ALICE, + market_ids, + pool_id, + account_id: pool.account_id, + reserves, + collateral: pool.collateral, + liquidity_parameter: pool.liquidity_parameter, + pool_shares_amount: amount, + swap_fee, + } + .into(), + ); + }); +} + +#[test] +fn deploy_combinatorial_pool_works_with_single_market_uneven_spot_prices() { + ExtBuilder::default().build().execute_with(|| { + let alice_before = AssetManager::free_balance(BASE_ASSET, &ALICE); + let amount = _10; + let spot_prices = vec![_1_4, _3_4]; + let expected_reserves = [_10, 20_751_874_964]; + let swap_fee = CENT; + let (market_ids, pool_id) = create_markets_and_deploy_combinatorial_pool( + ALICE, + BASE_ASSET, + vec![MarketType::Categorical(2)], + amount, + spot_prices.clone(), + swap_fee, + ); + let pool = Pools::::get(pool_id).unwrap(); + let assets = pool.assets(); + let expected_liquidity = 72_134_752_044; + let buffer = AssetManager::minimum_balance(pool.collateral); + assert_approx!(pool.liquidity_parameter, expected_liquidity, 1); + assert_eq!(pool.collateral, BASE_ASSET); + assert_liquidity_tree_state!( + pool.liquidity_shares_manager, + [Node:: { + account: Some(ALICE), + stake: amount, + fees: 0u128, + descendant_stake: 0u128, + lazy_fees: 0u128, + }], + create_b_tree_map!({ ALICE => 0 }), + Vec::::new(), + ); + assert_eq!(pool.swap_fee, swap_fee); + assert_balance!(pool.account_id, pool.collateral, buffer); + + let mut reserves = BTreeMap::new(); + for ((&asset, &price), &reserve) in + assets.iter().zip(spot_prices.iter()).zip(expected_reserves.iter()) + { + assert_balance!(pool.account_id, asset, reserve); + assert_eq!(pool.reserve_of(&asset).unwrap(), reserve); + assert_eq!(pool.calculate_spot_price(asset).unwrap(), price); + assert_balance!(ALICE, asset, amount - reserve); + reserves.insert(asset, reserve); + } + assert_balance!(ALICE, BASE_ASSET, alice_before - amount - buffer); + + System::assert_last_event( + Event::CombinatorialPoolDeployed { + who: ALICE, + market_ids, + pool_id, + account_id: pool.account_id, + reserves, + collateral: pool.collateral, + liquidity_parameter: pool.liquidity_parameter, + pool_shares_amount: amount, + swap_fee, + } + .into(), + ); + }); +} + +#[test] +fn deploy_combinatorial_pool_works_with_multiple_markets() { + ExtBuilder::default().build().execute_with(|| { + let alice_before = AssetManager::free_balance(BASE_ASSET, &ALICE); + let amount = _10; + let asset_count = 16usize; + let spot_prices = vec![BASE / (asset_count as u128); asset_count]; + let swap_fee = CENT; + let (market_ids, pool_id) = create_markets_and_deploy_combinatorial_pool( + ALICE, + BASE_ASSET, + vec![ + MarketType::Categorical(2), + MarketType::Categorical(4), + MarketType::Scalar(0u128..=1u128), + ], + amount, + spot_prices.clone(), + swap_fee, + ); + let pool = Pools::::get(pool_id).unwrap(); + let assets = pool.assets(); + let expected_liquidity = 36_067_376_022; + let buffer = AssetManager::minimum_balance(pool.collateral); + assert_eq!(pool.assets(), assets); + assert_approx!(pool.liquidity_parameter, expected_liquidity, 1); + assert_eq!(pool.collateral, BASE_ASSET); + assert_liquidity_tree_state!( + pool.liquidity_shares_manager, + [Node:: { + account: Some(ALICE), + stake: amount, + fees: 0u128, + descendant_stake: 0u128, + lazy_fees: 0u128, + }], + create_b_tree_map!({ ALICE => 0 }), + Vec::::new(), + ); + assert_eq!(pool.swap_fee, swap_fee); + assert_balance!(pool.account_id, pool.collateral, buffer); + + let mut reserves = BTreeMap::new(); + for (&asset, &price) in assets.iter().zip(spot_prices.iter()) { + assert_balance!(pool.account_id, asset, amount); + assert_eq!(pool.reserve_of(&asset).unwrap(), amount); + assert_eq!(pool.calculate_spot_price(asset).unwrap(), price); + assert_balance!(ALICE, asset, 0); + reserves.insert(asset, amount); + } + assert_balance!(ALICE, BASE_ASSET, alice_before - amount - buffer); + + System::assert_last_event( + Event::CombinatorialPoolDeployed { + who: ALICE, + market_ids, + pool_id, + account_id: pool.account_id, + reserves, + collateral: pool.collateral, + liquidity_parameter: pool.liquidity_parameter, + pool_shares_amount: amount, + swap_fee, + } + .into(), + ); + }); +} + +#[test] +fn deploy_combinatorial_pool_fails_on_incorrect_vec_len() { + ExtBuilder::default().build().execute_with(|| { + let market_ids = vec![ + create_market(ALICE, BASE_ASSET, MarketType::Scalar(0..=1), ScoringRule::AmmCdaHybrid), + create_market(ALICE, BASE_ASSET, MarketType::Categorical(3), ScoringRule::AmmCdaHybrid), + ]; + assert_noop!( + NeoSwaps::deploy_combinatorial_pool( + RuntimeOrigin::signed(ALICE), + market_ids, + _10, + vec![20 * CENT; 5], + CENT, + false, + ), + Error::::IncorrectVecLen + ); + }); +} + +#[test] +fn deploy_combinatorial_pool_fails_on_market_not_found() { + ExtBuilder::default().build().execute_with(|| { + let _ = + create_market(ALICE, BASE_ASSET, MarketType::Scalar(0..=1), ScoringRule::AmmCdaHybrid); + let _ = + create_market(ALICE, BASE_ASSET, MarketType::Categorical(5), ScoringRule::AmmCdaHybrid); + assert_noop!( + NeoSwaps::deploy_combinatorial_pool( + RuntimeOrigin::signed(ALICE), + vec![0, 2, 1], + _10, + vec![10 * CENT; 10], + CENT, + false, + ), + zrml_market_commons::Error::::MarketDoesNotExist, + ); + }); +} + +#[test_case(MarketStatus::Proposed)] +#[test_case(MarketStatus::Closed)] +#[test_case(MarketStatus::Reported)] +#[test_case(MarketStatus::Disputed)] +#[test_case(MarketStatus::Resolved)] +fn deploy_combinatorial_pool_fails_on_inactive_market(market_status: MarketStatus) { + ExtBuilder::default().build().execute_with(|| { + let market_ids = vec![ + create_market(ALICE, BASE_ASSET, MarketType::Scalar(0..=1), ScoringRule::AmmCdaHybrid), + create_market(ALICE, BASE_ASSET, MarketType::Categorical(5), ScoringRule::AmmCdaHybrid), + ]; + MarketCommons::mutate_market(market_ids.last().unwrap(), |market| { + market.status = market_status; + Ok(()) + }) + .unwrap(); + assert_noop!( + NeoSwaps::deploy_combinatorial_pool( + RuntimeOrigin::signed(ALICE), + market_ids, + _100, + vec![10 * CENT; 10], + CENT, + false, + ), + Error::::MarketNotActive, + ); + }); +} + +#[test] +fn deploy_combinatorial_pool_fails_on_invalid_trading_mechanism() { + ExtBuilder::default().build().execute_with(|| { + let market_ids = vec![ + create_market(ALICE, BASE_ASSET, MarketType::Scalar(0..=1), ScoringRule::AmmCdaHybrid), + create_market(ALICE, BASE_ASSET, MarketType::Categorical(5), ScoringRule::Parimutuel), + ]; + assert_noop!( + NeoSwaps::deploy_combinatorial_pool( + RuntimeOrigin::signed(ALICE), + market_ids, + _100, + vec![10 * CENT; 10], + CENT, + false, + ), + Error::::InvalidTradingMechanism + ); + }); +} + +#[test] +fn deploy_combinatorial_pool_fails_on_max_splits_exceeded() { + ExtBuilder::default().build().execute_with(|| { + // log2(MaxSplits + 1) + let market_count = u16::BITS - ::MaxSplits::get().leading_zeros(); + + let mut market_ids = vec![]; + for _ in 0..market_count { + let market_id = create_market( + ALICE, + BASE_ASSET, + MarketType::Categorical(2), + ScoringRule::AmmCdaHybrid, + ); + + market_ids.push(market_id); + } + let liquidity = 1_000 * BASE; + + let asset_count = 2u128.pow(market_count); + let mut spot_prices = vec![_1 / asset_count; asset_count as usize - 1]; + spot_prices.push(_1 - spot_prices.iter().sum::()); + + assert_noop!( + NeoSwaps::deploy_combinatorial_pool( + RuntimeOrigin::signed(ALICE), + market_ids, + liquidity, + spot_prices, + CENT, + false, + ), + Error::::MaxSplitsExceeded + ); + }); +} + +#[test] +fn deploy_combinatorial_pool_fails_on_swap_fee_below_min() { + ExtBuilder::default().build().execute_with(|| { + let market_id = + create_market(ALICE, BASE_ASSET, MarketType::Categorical(2), ScoringRule::AmmCdaHybrid); + let liquidity = _10; + assert_ok!(PredictionMarkets::buy_complete_set( + RuntimeOrigin::signed(ALICE), + market_id, + liquidity, + )); + assert_noop!( + NeoSwaps::deploy_combinatorial_pool( + RuntimeOrigin::signed(ALICE), + vec![market_id], + liquidity, + vec![_1_4, _3_4], + MIN_SWAP_FEE - 1, + false, + ), + Error::::SwapFeeBelowMin + ); + }); +} + +#[test] +fn deploy_combinatorial_pool_fails_on_swap_fee_above_max() { + ExtBuilder::default().build().execute_with(|| { + let market_id = + create_market(ALICE, BASE_ASSET, MarketType::Categorical(2), ScoringRule::AmmCdaHybrid); + let liquidity = _10; + assert_ok!(PredictionMarkets::buy_complete_set( + RuntimeOrigin::signed(ALICE), + market_id, + liquidity, + )); + assert_noop!( + NeoSwaps::deploy_combinatorial_pool( + RuntimeOrigin::signed(ALICE), + vec![market_id], + liquidity, + vec![_1_4, _3_4], + ::MaxSwapFee::get() + 1, + false, + ), + Error::::SwapFeeAboveMax + ); + }); +} + +#[test_case(vec![_1_4, _3_4 - 1])] +#[test_case(vec![_1_4 + 1, _3_4])] +fn deploy_combinatorial_pool_fails_on_invalid_spot_prices(spot_prices: Vec>) { + ExtBuilder::default().build().execute_with(|| { + let market_id = + create_market(ALICE, BASE_ASSET, MarketType::Categorical(2), ScoringRule::AmmCdaHybrid); + let liquidity = _10; + assert_noop!( + NeoSwaps::deploy_combinatorial_pool( + RuntimeOrigin::signed(ALICE), + vec![market_id], + liquidity, + spot_prices, + CENT, + false, + ), + Error::::InvalidSpotPrices + ); + }); +} + +#[test] +fn deploy_combinatorial_pool_fails_on_spot_price_below_min() { + ExtBuilder::default().build().execute_with(|| { + let market_id = + create_market(ALICE, BASE_ASSET, MarketType::Categorical(2), ScoringRule::AmmCdaHybrid); + let liquidity = _10; + let spot_price = MIN_SPOT_PRICE - 1; + assert_noop!( + NeoSwaps::deploy_combinatorial_pool( + RuntimeOrigin::signed(ALICE), + vec![market_id], + liquidity, + vec![spot_price, _1 - spot_price], + CENT, + false, + ), + Error::::SpotPriceBelowMin + ); + }); +} + +#[test] +fn deploy_combinatorial_pool_fails_on_spot_price_above_max() { + ExtBuilder::default().build().execute_with(|| { + let market_id = + create_market(ALICE, BASE_ASSET, MarketType::Categorical(2), ScoringRule::AmmCdaHybrid); + let liquidity = _10; + assert_ok!(PredictionMarkets::buy_complete_set( + RuntimeOrigin::signed(ALICE), + market_id, + liquidity, + )); + let spot_price = MAX_SPOT_PRICE + 1; + assert_noop!( + NeoSwaps::deploy_combinatorial_pool( + RuntimeOrigin::signed(ALICE), + vec![market_id], + liquidity, + vec![spot_price, _1 - spot_price], + CENT, + false, + ), + Error::::SpotPriceAboveMax + ); + }); +} + +#[test] +fn deploy_combinatorial_pool_fails_on_insufficient_funds() { + ExtBuilder::default().build().execute_with(|| { + let market_id = + create_market(ALICE, BASE_ASSET, MarketType::Categorical(2), ScoringRule::AmmCdaHybrid); + let liquidity = _10; + + #[cfg(feature = "parachain")] + let expected_error = orml_tokens::Error::::BalanceTooLow; + #[cfg(not(feature = "parachain"))] + let expected_error = orml_currencies::Error::::BalanceTooLow; + + assert_noop!( + NeoSwaps::deploy_combinatorial_pool( + RuntimeOrigin::signed(BOB), + vec![market_id], + liquidity, + vec![_3_4, _1_4], + CENT, + false, + ), + expected_error + ); + }); +} + +#[test] +fn deploy_combinatorial_pool_fails_on_liquidity_too_low() { + ExtBuilder::default().build().execute_with(|| { + let market_id = + create_market(ALICE, BASE_ASSET, MarketType::Scalar(0..=1), ScoringRule::AmmCdaHybrid); + let amount = _1_2; + assert_noop!( + NeoSwaps::deploy_combinatorial_pool( + RuntimeOrigin::signed(ALICE), + vec![market_id], + amount, + vec![_1_2, _1_2], + CENT, + false, + ), + Error::::LiquidityTooLow + ); + }); +} diff --git a/zrml/neo-swaps/src/tests/deploy_pool.rs b/zrml/neo-swaps/src/tests/deploy_pool.rs index 8d7f4fdec..c35d82c28 100644 --- a/zrml/neo-swaps/src/tests/deploy_pool.rs +++ b/zrml/neo-swaps/src/tests/deploy_pool.rs @@ -73,6 +73,7 @@ fn deploy_pool_works_with_binary_markets() { Event::PoolDeployed { who: ALICE, market_id, + pool_id: market_id, account_id: pool.account_id, reserves, collateral: pool.collateral, @@ -152,6 +153,7 @@ fn deploy_pool_works_with_scalar_marktes() { Event::PoolDeployed { who: ALICE, market_id, + pool_id: market_id, account_id: pool.account_id, reserves, collateral: pool.collateral, diff --git a/zrml/neo-swaps/src/tests/exit.rs b/zrml/neo-swaps/src/tests/exit.rs index 233b1f527..48060d1e0 100644 --- a/zrml/neo-swaps/src/tests/exit.rs +++ b/zrml/neo-swaps/src/tests/exit.rs @@ -87,7 +87,7 @@ fn exit_works( System::assert_last_event( Event::ExitExecuted { who: ALICE, - market_id, + pool_id: market_id, pool_shares_amount, amounts_out, new_liquidity_parameter, @@ -138,7 +138,7 @@ fn last_exit_destroys_pool(market_status: MarketStatus, amounts_out: Vec::contains_key(market_id)); assert_balances!(pool_account, outcomes, [0, 0]); System::assert_last_event( - Event::PoolDestroyed { who: ALICE, market_id, amounts_out }.into(), + Event::PoolDestroyed { who: ALICE, pool_id: market_id, amounts_out }.into(), ); }); } diff --git a/zrml/neo-swaps/src/tests/join.rs b/zrml/neo-swaps/src/tests/join.rs index 1e6bc0183..0600b0108 100644 --- a/zrml/neo-swaps/src/tests/join.rs +++ b/zrml/neo-swaps/src/tests/join.rs @@ -19,7 +19,7 @@ use super::*; use crate::{ helpers::create_spot_prices, liquidity_tree::{ - traits::liquidity_tree_helper::LiquidityTreeHelper, types::LiquidityTreeError, + traits::LiquidityTreeHelper, types::LiquidityTreeError, }, }; use alloc::collections::BTreeMap; @@ -65,7 +65,7 @@ fn join_works( System::assert_last_event( Event::JoinExecuted { who, - market_id, + pool_id: market_id, pool_shares_amount, amounts_in, new_liquidity_parameter, diff --git a/zrml/neo-swaps/src/tests/mod.rs b/zrml/neo-swaps/src/tests/mod.rs index 42fdbe5f2..e8f6b1da8 100644 --- a/zrml/neo-swaps/src/tests/mod.rs +++ b/zrml/neo-swaps/src/tests/mod.rs @@ -21,6 +21,7 @@ mod buy; mod buy_and_sell; mod combo_buy; mod combo_sell; +mod deploy_combinatorial_pool; mod deploy_pool; mod exit; mod join; @@ -100,6 +101,36 @@ fn create_market_and_deploy_pool( market_id } +fn create_markets_and_deploy_combinatorial_pool( + creator: AccountIdOf, + base_asset: Asset, + market_types: Vec, + amount: BalanceOf, + spot_prices: Vec>, + swap_fee: BalanceOf, +) -> (Vec, ::PoolId) { + let mut market_ids = vec![]; + + for market_type in market_types.iter() { + let market_id = + create_market(creator, base_asset, market_type.clone(), ScoringRule::AmmCdaHybrid); + + market_ids.push(market_id); + } + + let pool_id = as PoolStorage>::next_pool_id(); + assert_ok!(NeoSwaps::deploy_combinatorial_pool( + RuntimeOrigin::signed(ALICE), + market_ids.clone(), + amount, + spot_prices.clone(), + swap_fee, + false, + )); + + (market_ids, pool_id) +} + fn deposit_complete_set( market_id: MarketId, account: AccountIdOf, diff --git a/zrml/neo-swaps/src/tests/sell.rs b/zrml/neo-swaps/src/tests/sell.rs index 5c1b9dd60..c6b49bcae 100644 --- a/zrml/neo-swaps/src/tests/sell.rs +++ b/zrml/neo-swaps/src/tests/sell.rs @@ -78,7 +78,7 @@ fn sell_works() { System::assert_last_event( Event::SellExecuted { who: BOB, - market_id, + pool_id: market_id, asset_in, amount_in, amount_out: expected_amount_out_minus_fees, @@ -378,3 +378,33 @@ fn sell_fails_on_amount_out_below_min() { ); }); } + +#[test] +fn sell_fails_on_invalid_pool_type() { + ExtBuilder::default().build().execute_with(|| { + let (_, pool_id) = create_markets_and_deploy_combinatorial_pool( + ALICE, + BASE_ASSET, + vec![MarketType::Scalar(0..=1)], + _10, + vec![_1_2, _1_2], + CENT, + ); + + let pool = as PoolStorage>::get(pool_id).unwrap(); + let assets = pool.assets(); + + assert_noop!( + NeoSwaps::sell( + RuntimeOrigin::signed(BOB), + pool_id, + 2, + assets[0], + _1, + 0 + ), + Error::::InvalidPoolType, + ); + }); +} + diff --git a/zrml/neo-swaps/src/tests/withdraw_fees.rs b/zrml/neo-swaps/src/tests/withdraw_fees.rs index 55100908c..1951d49a1 100644 --- a/zrml/neo-swaps/src/tests/withdraw_fees.rs +++ b/zrml/neo-swaps/src/tests/withdraw_fees.rs @@ -74,7 +74,7 @@ fn withdraw_fees_works() { fees_remaining, ); System::assert_last_event( - Event::FeesWithdrawn { who, market_id, amount: fees_withdrawn }.into(), + Event::FeesWithdrawn { who, pool_id: market_id, amount: fees_withdrawn }.into(), ); }; test_withdraw(ALICE, _1_4, _3_4); diff --git a/zrml/neo-swaps/src/traits/mod.rs b/zrml/neo-swaps/src/traits/mod.rs index b6c796c11..b0a8e57da 100644 --- a/zrml/neo-swaps/src/traits/mod.rs +++ b/zrml/neo-swaps/src/traits/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2023 Forecasting Technologies LTD. +// Copyright 2023-2024 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // @@ -15,8 +15,10 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . -pub(crate) mod liquidity_shares_manager; -pub(crate) mod pool_operations; +mod liquidity_shares_manager; +mod pool_operations; +mod pool_storage; pub(crate) use liquidity_shares_manager::LiquiditySharesManager; pub(crate) use pool_operations::PoolOperations; +pub(crate) use pool_storage::PoolStorage; diff --git a/zrml/neo-swaps/src/traits/pool_operations.rs b/zrml/neo-swaps/src/traits/pool_operations.rs index 341ffb779..5b54bca1e 100644 --- a/zrml/neo-swaps/src/traits/pool_operations.rs +++ b/zrml/neo-swaps/src/traits/pool_operations.rs @@ -37,6 +37,9 @@ pub(crate) trait PoolOperations { /// Beware! The reserve need not coincide with the balance in the pool account. fn reserves_of(&self, assets: &[AssetOf]) -> Result>, DispatchError>; + /// Checks if the pool can be traded on. + fn is_active(&self) -> Result; + /// Perform a checked addition to the balance of `asset`. fn increase_reserve( &mut self, diff --git a/zrml/neo-swaps/src/traits/pool_storage.rs b/zrml/neo-swaps/src/traits/pool_storage.rs new file mode 100644 index 000000000..805e0d847 --- /dev/null +++ b/zrml/neo-swaps/src/traits/pool_storage.rs @@ -0,0 +1,33 @@ +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use sp_runtime::DispatchError; + +pub(crate) trait PoolStorage { + type PoolId; + type Pool; + + fn next_pool_id() -> Self::PoolId; + + fn add(pool: Self::Pool) -> Result; + + fn get(pool_id: Self::PoolId) -> Result; + + fn try_mutate_pool(pool_id: &Self::PoolId, mutator: F) -> Result + where + F: FnMut(&mut Self::Pool) -> Result; +} diff --git a/zrml/neo-swaps/src/types/decision_market_benchmark_helper.rs b/zrml/neo-swaps/src/types/decision_market_benchmark_helper.rs index 9c8b60e6e..a0a4363af 100644 --- a/zrml/neo-swaps/src/types/decision_market_benchmark_helper.rs +++ b/zrml/neo-swaps/src/types/decision_market_benchmark_helper.rs @@ -19,10 +19,10 @@ use crate::{ liquidity_tree::types::LiquidityTree, - types::{DecisionMarketOracle, Pool}, + types::{DecisionMarketOracle, Pool, PoolType}, BalanceOf, Config, MarketIdOf, Pallet, Pools, }; -use alloc::collections::BTreeMap; +use alloc::{collections::BTreeMap, vec}; use core::marker::PhantomData; use sp_runtime::{traits::Zero, Saturating}; use zeitgeist_primitives::{ @@ -40,13 +40,13 @@ where /// Creates a mocked up pool with prices so that the returned decision market oracle evaluates /// to `value`. The pool is technically in invalid state. fn create_oracle(value: bool) -> DecisionMarketOracle { - let market_id: MarketIdOf = 0u8.into(); + let pool_id: MarketIdOf = 0u8.into(); let collateral = Asset::Ztg; // Create a `reserves` map so that `positive_outcome` has a higher price if and only if // `value` is `true`. - let positive_outcome = Asset::CategoricalOutcome(market_id, 0u16); - let negative_outcome = Asset::CategoricalOutcome(market_id, 1u16); + let positive_outcome = Asset::CombinatorialToken([0u8; 32]); + let negative_outcome = Asset::CombinatorialToken([1u8; 32]); let mut reserves = BTreeMap::new(); let one: BalanceOf = ZeitgeistBase::get().unwrap(); let two: BalanceOf = one.saturating_mul(2u8.into()); @@ -58,18 +58,20 @@ where reserves.insert(negative_outcome, one); } - let account_id: T::AccountId = Pallet::::pool_account_id(&market_id); + let account_id: T::AccountId = Pallet::::pool_account_id(&pool_id); let pool = Pool { account_id: account_id.clone(), + assets: vec![positive_outcome, negative_outcome].try_into().unwrap(), reserves: reserves.try_into().unwrap(), collateral, liquidity_parameter: one, liquidity_shares_manager: LiquidityTree::new(account_id, one).unwrap(), swap_fee: Zero::zero(), + pool_type: PoolType::Standard(0u8.into()), }; - Pools::::insert(market_id, pool); + Pools::::insert(pool_id, pool); - DecisionMarketOracle::new(market_id, positive_outcome, negative_outcome) + DecisionMarketOracle::new(pool_id, positive_outcome, negative_outcome) } } diff --git a/zrml/neo-swaps/src/types/decision_market_oracle.rs b/zrml/neo-swaps/src/types/decision_market_oracle.rs index 7c8e8239d..2d27f247b 100644 --- a/zrml/neo-swaps/src/types/decision_market_oracle.rs +++ b/zrml/neo-swaps/src/types/decision_market_oracle.rs @@ -15,10 +15,7 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . -use crate::{ - traits::pool_operations::PoolOperations, weights::WeightInfoZeitgeist, AssetOf, Config, Error, - MarketIdOf, Pools, -}; +use crate::{traits::PoolOperations, weights::WeightInfoZeitgeist, AssetOf, Config, Error, Pools}; use frame_support::pallet_prelude::Weight; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; @@ -30,7 +27,7 @@ pub struct DecisionMarketOracle where T: Config, { - market_id: MarketIdOf, + pool_id: T::PoolId, positive_outcome: AssetOf, negative_outcome: AssetOf, } @@ -40,17 +37,17 @@ where T: Config, { pub fn new( - market_id: MarketIdOf, + pool_id: T::PoolId, positive_outcome: AssetOf, negative_outcome: AssetOf, ) -> Self { - Self { market_id, positive_outcome, negative_outcome } + Self { pool_id, positive_outcome, negative_outcome } } // Utility implementation that uses the question mark operator to implement a fallible version // of `evaluate`. fn try_evaluate(&self) -> Result { - let pool = Pools::::get(self.market_id) + let pool = Pools::::get(self.pool_id) .ok_or::(Error::::PoolNotFound.into())?; let positive_value = pool.calculate_spot_price(self.positive_outcome)?; diff --git a/zrml/neo-swaps/src/types/mod.rs b/zrml/neo-swaps/src/types/mod.rs index 754ba2bcb..057dcd204 100644 --- a/zrml/neo-swaps/src/types/mod.rs +++ b/zrml/neo-swaps/src/types/mod.rs @@ -20,6 +20,7 @@ mod decision_market_oracle; mod fee_distribution; mod max_assets; mod pool; +mod pool_type; #[cfg(feature = "runtime-benchmarks")] pub use decision_market_benchmark_helper::*; @@ -27,3 +28,4 @@ pub use decision_market_oracle::*; pub(crate) use fee_distribution::*; pub(crate) use max_assets::*; pub(crate) use pool::*; +pub(crate) use pool_type::*; diff --git a/zrml/neo-swaps/src/types/pool.rs b/zrml/neo-swaps/src/types/pool.rs index d6586514f..eb1d7eba9 100644 --- a/zrml/neo-swaps/src/types/pool.rs +++ b/zrml/neo-swaps/src/types/pool.rs @@ -15,6 +15,7 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . +use frame_support::BoundedVec; use crate::{ consts::EXP_NUMERICAL_LIMIT, math::{ @@ -23,7 +24,8 @@ use crate::{ }, pallet::{AssetOf, BalanceOf, Config}, traits::{LiquiditySharesManager, PoolOperations}, - Error, + types::PoolType, + Error, MarketIdOf, }; use alloc::{fmt::Debug, vec::Vec}; use frame_support::{ @@ -36,6 +38,8 @@ use sp_runtime::{ traits::{CheckedAdd, CheckedSub, Get}, DispatchError, DispatchResult, SaturatedConversion, Saturating, }; +use zeitgeist_primitives::types::MarketStatus; +use zrml_market_commons::MarketCommonsPalletApi; #[derive( CloneNoBound, Decode, Encode, Eq, MaxEncodedLen, PartialEqNoBound, RuntimeDebugNoBound, TypeInfo, @@ -48,11 +52,13 @@ where S: Get, { pub account_id: T::AccountId, + pub assets: BoundedVec, S>, pub reserves: BoundedBTreeMap, BalanceOf, S>, pub collateral: AssetOf, pub liquidity_parameter: BalanceOf, pub liquidity_shares_manager: LSM, pub swap_fee: BalanceOf, + pub pool_type: PoolType, S>, } impl PoolOperations for Pool @@ -63,7 +69,7 @@ where S: Get, { fn assets(&self) -> Vec> { - self.reserves.keys().cloned().collect() + self.assets.to_vec() } fn contains(&self, asset: &AssetOf) -> bool { @@ -78,6 +84,19 @@ where assets.iter().map(|a| self.reserve_of(a)).collect() } + /// Checks if the pool can be traded on. + fn is_active(&self) -> Result { + for market_id in self.pool_type.iter_market_ids() { + let market = T::MarketCommons::market(market_id)?; + + if market.status != MarketStatus::Active { + return Ok(false); + } + } + + Ok(true) + } + fn increase_reserve( &mut self, asset: &AssetOf, diff --git a/zrml/neo-swaps/src/types/pool_type.rs b/zrml/neo-swaps/src/types/pool_type.rs new file mode 100644 index 000000000..e29c870ea --- /dev/null +++ b/zrml/neo-swaps/src/types/pool_type.rs @@ -0,0 +1,49 @@ +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use alloc::{boxed::Box, fmt::Debug}; +use core::iter; +use frame_support::{CloneNoBound, PartialEqNoBound, RuntimeDebugNoBound}; +use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; +use sp_runtime::{traits::Get, BoundedVec}; + +#[derive( + CloneNoBound, Decode, Encode, Eq, MaxEncodedLen, PartialEqNoBound, RuntimeDebugNoBound, TypeInfo, +)] +#[scale_info(skip_type_params(MaxMarkets))] +pub(crate) enum PoolType +where + MarketId: Clone + Decode + Debug + Encode + MaxEncodedLen + PartialEq + Eq + TypeInfo, + MaxMarkets: Get, +{ + Standard(MarketId), + Combinatorial(BoundedVec), +} + +impl PoolType +where + MarketId: Clone + Decode + Debug + Encode + MaxEncodedLen + PartialEq + Eq + TypeInfo, + MaxMarkets: Get, +{ + pub fn iter_market_ids(&self) -> Box + '_> { + match self { + PoolType::Standard(market_id) => Box::new(iter::once(market_id)), + PoolType::Combinatorial(market_ids) => Box::new(market_ids.iter()), + } + } +} From 6ff1288bda5aadd41e774587a1104c595a088da1 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Wed, 30 Oct 2024 12:43:11 +0100 Subject: [PATCH 44/73] Fix formatting --- zrml/neo-swaps/src/tests/buy.rs | 9 +-------- zrml/neo-swaps/src/tests/join.rs | 4 +--- zrml/neo-swaps/src/tests/sell.rs | 10 +--------- zrml/neo-swaps/src/types/pool.rs | 3 +-- 4 files changed, 4 insertions(+), 22 deletions(-) diff --git a/zrml/neo-swaps/src/tests/buy.rs b/zrml/neo-swaps/src/tests/buy.rs index 62a7f4cf0..55107c9e5 100644 --- a/zrml/neo-swaps/src/tests/buy.rs +++ b/zrml/neo-swaps/src/tests/buy.rs @@ -363,14 +363,7 @@ fn buy_fails_on_invalid_pool_type() { let assets = pool.assets(); assert_noop!( - NeoSwaps::buy( - RuntimeOrigin::signed(BOB), - pool_id, - 2, - assets[0], - _1, - 0 - ), + NeoSwaps::buy(RuntimeOrigin::signed(BOB), pool_id, 2, assets[0], _1, 0), Error::::InvalidPoolType, ); }); diff --git a/zrml/neo-swaps/src/tests/join.rs b/zrml/neo-swaps/src/tests/join.rs index 0600b0108..f5a3994e6 100644 --- a/zrml/neo-swaps/src/tests/join.rs +++ b/zrml/neo-swaps/src/tests/join.rs @@ -18,9 +18,7 @@ use super::*; use crate::{ helpers::create_spot_prices, - liquidity_tree::{ - traits::LiquidityTreeHelper, types::LiquidityTreeError, - }, + liquidity_tree::{traits::LiquidityTreeHelper, types::LiquidityTreeError}, }; use alloc::collections::BTreeMap; use test_case::test_case; diff --git a/zrml/neo-swaps/src/tests/sell.rs b/zrml/neo-swaps/src/tests/sell.rs index c6b49bcae..559164d4b 100644 --- a/zrml/neo-swaps/src/tests/sell.rs +++ b/zrml/neo-swaps/src/tests/sell.rs @@ -395,16 +395,8 @@ fn sell_fails_on_invalid_pool_type() { let assets = pool.assets(); assert_noop!( - NeoSwaps::sell( - RuntimeOrigin::signed(BOB), - pool_id, - 2, - assets[0], - _1, - 0 - ), + NeoSwaps::sell(RuntimeOrigin::signed(BOB), pool_id, 2, assets[0], _1, 0), Error::::InvalidPoolType, ); }); } - diff --git a/zrml/neo-swaps/src/types/pool.rs b/zrml/neo-swaps/src/types/pool.rs index eb1d7eba9..bff6b77f2 100644 --- a/zrml/neo-swaps/src/types/pool.rs +++ b/zrml/neo-swaps/src/types/pool.rs @@ -15,7 +15,6 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . -use frame_support::BoundedVec; use crate::{ consts::EXP_NUMERICAL_LIMIT, math::{ @@ -29,7 +28,7 @@ use crate::{ }; use alloc::{fmt::Debug, vec::Vec}; use frame_support::{ - storage::bounded_btree_map::BoundedBTreeMap, CloneNoBound, PartialEqNoBound, + storage::bounded_btree_map::BoundedBTreeMap, BoundedVec, CloneNoBound, PartialEqNoBound, RuntimeDebugNoBound, }; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; From 6055847e1f074a40ab19cffab93619f9e4f792e8 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Wed, 30 Oct 2024 17:30:40 +0100 Subject: [PATCH 45/73] Fix coverage --- scripts/tests/coverage.sh | 4 ++-- zrml/hybrid-router/Cargo.toml | 1 + zrml/neo-swaps/Cargo.toml | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/tests/coverage.sh b/scripts/tests/coverage.sh index aeed8dbfd..71b59fa16 100755 --- a/scripts/tests/coverage.sh +++ b/scripts/tests/coverage.sh @@ -11,7 +11,7 @@ export LLVM_PROFILE_FILE="cargo-test-%p-%m.profraw" rustflags="-Cinstrument-coverage" test_package_with_feature "primitives" "std" "$rustflags" -no_runtime_benchmarks=('court' 'market-commons' 'rikiddo') +no_runtime_benchmarks=('court' 'market-commons') for package in zrml/*; do if [[ " ${no_runtime_benchmarks[*]} " != *" ${package##*/} "* ]]; then @@ -25,4 +25,4 @@ done unset CARGO_INCREMENTAL LLVM_PROFILE_FILE -grcov . --binary-path ./target/debug/deps/ -s . -t lcov --branch --ignore-not-existing --llvm --ignore '../*' --ignore "/*" -o $RUNNER_TEMP/zeitgeist-test-coverage.lcov \ No newline at end of file +grcov . --binary-path ./target/debug/deps/ -s . -t lcov --branch --ignore-not-existing --llvm --ignore '../*' --ignore "/*" -o $RUNNER_TEMP/zeitgeist-test-coverage.lcov diff --git a/zrml/hybrid-router/Cargo.toml b/zrml/hybrid-router/Cargo.toml index 62d9ac8f9..4791f9697 100644 --- a/zrml/hybrid-router/Cargo.toml +++ b/zrml/hybrid-router/Cargo.toml @@ -71,6 +71,7 @@ runtime-benchmarks = [ "frame-system/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "pallet-xcm/runtime-benchmarks", + "zrml-combinatorial-tokens/runtime-benchmarks", "zrml-prediction-markets/runtime-benchmarks", ] std = [ diff --git a/zrml/neo-swaps/Cargo.toml b/zrml/neo-swaps/Cargo.toml index 95847ed63..fcdae3976 100644 --- a/zrml/neo-swaps/Cargo.toml +++ b/zrml/neo-swaps/Cargo.toml @@ -82,6 +82,7 @@ runtime-benchmarks = [ "frame-system/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "pallet-xcm/runtime-benchmarks", + "zrml-combinatorial-tokens/runtime-benchmarks", ] std = [ "frame-benchmarking?/std", From 790906f13e55f6a57fac3c8ad85b16ffabb4cba1 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Wed, 30 Oct 2024 20:22:10 +0100 Subject: [PATCH 46/73] Mkl neo swaps benchmarks (#1390) * clean up TODO * First benchmark * . * . * . * Sceond bench * Use weights * Use asset_count in deploy_combinatorial_pool * . * . * Use weights --- zrml/neo-swaps/src/benchmarking.rs | 156 +++++++++- zrml/neo-swaps/src/lib.rs | 41 +-- .../src/tests/deploy_combinatorial_pool.rs | 51 ++- zrml/neo-swaps/src/tests/mod.rs | 5 +- zrml/neo-swaps/src/utility.rs | 34 ++ zrml/neo-swaps/src/weights.rs | 294 ++++++++++-------- 6 files changed, 415 insertions(+), 166 deletions(-) create mode 100644 zrml/neo-swaps/src/utility.rs diff --git a/zrml/neo-swaps/src/benchmarking.rs b/zrml/neo-swaps/src/benchmarking.rs index 8515ccf1c..cd0ccc29b 100644 --- a/zrml/neo-swaps/src/benchmarking.rs +++ b/zrml/neo-swaps/src/benchmarking.rs @@ -20,7 +20,7 @@ use super::*; use crate::{ liquidity_tree::{traits::LiquidityTreeHelper, types::LiquidityTree}, - traits::{LiquiditySharesManager, PoolOperations}, + traits::{LiquiditySharesManager, PoolOperations, PoolStorage}, types::DecisionMarketOracle, AssetOf, BalanceOf, MarketIdOf, Pallet as NeoSwaps, Pools, MIN_SPOT_PRICE, }; @@ -493,6 +493,160 @@ mod benchmarks { ); } + // Remark on benchmarks for combinatorial pools: Combinatorial buying, selling and deploying + // pools depends on the number of assets as well as the number of markets. But these parameters + // depend on each other (the more markets, the more assets). The benchmark parameter is the + // market count and the logarithm of the number of assets. This maximizes the number of markets + // per asset. + + #[benchmark] + fn combo_buy(n: Linear<1, 7>) { + let market_count = n; + + let alice: T::AccountId = whitelisted_caller(); + let base_asset = Asset::Ztg; + let asset_count = 2u16.pow(market_count); + + let mut market_ids = vec![]; + for _ in 0..market_count { + let market_id = create_market::(alice.clone(), base_asset, 2); + market_ids.push(market_id); + } + + let amount = _100.saturated_into(); + let total_cost = amount + T::MultiCurrency::minimum_balance(base_asset); + assert_ok!(T::MultiCurrency::deposit(base_asset, &alice, total_cost)); + assert_ok!(NeoSwaps::::deploy_combinatorial_pool( + RawOrigin::Signed(alice).into(), + asset_count, + market_ids, + amount, + create_spot_prices::(asset_count), + CENT.saturated_into(), + false, + )); + + let pool_id = 0u8.into(); + let pool = as PoolStorage>::get(pool_id).unwrap(); + let assets = pool.assets(); + + let amount_in = _1.saturated_into(); + let min_amount_out = Zero::zero(); + + // Work is maximized by having no keep indicies. + let middle = asset_count / 2; + let buy_arg = (0..middle).map(|i| assets[i as usize]).collect::>(); + let sell_arg = (middle..asset_count).map(|i| assets[i as usize]).collect::>(); + + let helper = BenchmarkHelper::::new(); + let bob = helper.accounts().next().unwrap(); + assert_ok!(T::MultiCurrency::deposit(base_asset, &bob, amount_in)); + + #[extrinsic_call] + _( + RawOrigin::Signed(bob), + pool_id, + asset_count, + buy_arg, + sell_arg, + amount_in, + min_amount_out, + ); + } + + #[benchmark] + fn combo_sell(n: Linear<1, 7>) { + let market_count = n; + + let alice: T::AccountId = whitelisted_caller(); + let base_asset = Asset::Ztg; + let asset_count = 2u16.pow(market_count); + + let mut market_ids = vec![]; + for _ in 0..market_count { + let market_id = create_market::(alice.clone(), base_asset, 2); + market_ids.push(market_id); + } + + let amount = _100.saturated_into(); + let total_cost = amount + T::MultiCurrency::minimum_balance(base_asset); + assert_ok!(T::MultiCurrency::deposit(base_asset, &alice, total_cost)); + assert_ok!(NeoSwaps::::deploy_combinatorial_pool( + RawOrigin::Signed(alice).into(), + asset_count, + market_ids, + amount, + create_spot_prices::(asset_count), + CENT.saturated_into(), + false, + )); + + let pool_id = 0u8.into(); + let pool = as PoolStorage>::get(pool_id).unwrap(); + let assets = pool.assets(); + + // Work is maximized by having as few sell indices as possible. + let buy_arg = vec![assets[0]]; + let sell_arg = vec![assets[1]]; + let keep_arg = (2..asset_count).map(|i| assets[i as usize]).collect::>(); + + let amount_buy: BalanceOf = _2.saturated_into(); + let amount_keep = if keep_arg.is_empty() { + // If n = 1; + Zero::zero() + } else { + _1.saturated_into() + }; + let min_amount_out = Zero::zero(); + + let helper = BenchmarkHelper::::new(); + let bob = helper.accounts().next().unwrap(); + + // We don't care about being precise here and just deposit a huge bunch of tokens for Bob. + for &asset in assets.iter() { + let amount_for_bob = amount_buy.max(amount_buy); + assert_ok!(T::MultiCurrency::deposit(asset, &bob, amount_for_bob)); + } + + #[extrinsic_call] + _( + RawOrigin::Signed(bob), + pool_id, + asset_count, + buy_arg, + keep_arg, + sell_arg, + amount_buy, + amount_keep, + min_amount_out, + ); + } + + #[benchmark] + fn deploy_combinatorial_pool(n: Linear<1, 7>) { + let market_count = n; + + let alice: T::AccountId = whitelisted_caller(); + let base_asset = Asset::Ztg; + let asset_count = 2u16.pow(market_count); + + let mut market_ids = vec![]; + for _ in 0..market_count { + let market_id = create_market::(alice.clone(), base_asset, 2); + market_ids.push(market_id); + } + + let amount = _100.saturated_into(); + let total_cost = amount + T::MultiCurrency::minimum_balance(base_asset); + assert_ok!(T::MultiCurrency::deposit(base_asset, &alice, total_cost)); + + let spot_prices = create_spot_prices::(asset_count); + let swap_fee = CENT.saturated_into(); + + #[extrinsic_call] + _(RawOrigin::Signed(alice), asset_count, market_ids, amount, spot_prices, swap_fee, true); + } + #[benchmark] fn decision_market_oracle_evaluate() { let alice = whitelisted_caller(); diff --git a/zrml/neo-swaps/src/lib.rs b/zrml/neo-swaps/src/lib.rs index 142005e26..2fc47f05a 100644 --- a/zrml/neo-swaps/src/lib.rs +++ b/zrml/neo-swaps/src/lib.rs @@ -34,6 +34,7 @@ mod pool_storage; mod tests; pub mod traits; pub mod types; +mod utility; pub mod weights; pub use pallet::*; @@ -46,6 +47,7 @@ mod pallet { math::{traits::MathOps, types::Math}, traits::{LiquiditySharesManager, PoolOperations, PoolStorage}, types::{FeeDistribution, MaxAssets, Pool, PoolType}, + utility::LogCeil, weights::*, }; use alloc::{ @@ -428,7 +430,7 @@ mod pallet { /// Depends on the implementation of `CompleteSetOperationsApi` and `ExternalFees`; when /// using the canonical implementations, the runtime complexity is `O(asset_count)`. #[pallet::call_index(0)] - #[pallet::weight(T::WeightInfo::buy((*asset_count).saturated_into()))] // TODO Use into() + #[pallet::weight(T::WeightInfo::buy((*asset_count).into()))] #[transactional] pub fn buy( origin: OriginFor, @@ -478,7 +480,7 @@ mod pallet { /// Depends on the implementation of `CompleteSetOperationsApi` and `ExternalFees`; when /// using the canonical implementations, the runtime complexity is `O(asset_count)`. #[pallet::call_index(1)] - #[pallet::weight(T::WeightInfo::sell((*asset_count).saturated_into()))] // TODO Use `into()` + #[pallet::weight(T::WeightInfo::sell((*asset_count).into()))] #[transactional] pub fn sell( origin: OriginFor, @@ -679,9 +681,9 @@ mod pallet { Ok(Some(T::WeightInfo::deploy_pool(spot_prices_len)).into()) } - #[allow(clippy::too_many_arguments)] // TODO Bundle `buy`/`keep`/`sell` into one arg. + #[allow(clippy::too_many_arguments)] #[pallet::call_index(6)] - #[pallet::weight(T::WeightInfo::buy((*asset_count).into()))] // TODO + #[pallet::weight(T::WeightInfo::combo_buy(asset_count.log_ceil().into()))] #[transactional] pub fn combo_buy( origin: OriginFor, @@ -691,7 +693,7 @@ mod pallet { sell: Vec>, #[pallet::compact] amount_in: BalanceOf, #[pallet::compact] min_amount_out: BalanceOf, - ) -> DispatchResultWithPostInfo { + ) -> DispatchResult { let who = ensure_signed(origin)?; let pool = ::get(pool_id)?; @@ -700,14 +702,12 @@ mod pallet { asset_count_real.try_into().map_err(|_| Error::::NarrowingConversion)?; ensure!(asset_count == asset_count_real_u16, Error::::IncorrectAssetCount); - Self::do_combo_buy(who, pool_id, buy, sell, amount_in, min_amount_out)?; - - Ok(Some(T::WeightInfo::buy(asset_count.into())).into()) // TODO + Self::do_combo_buy(who, pool_id, buy, sell, amount_in, min_amount_out) } - #[allow(clippy::too_many_arguments)] // TODO Bundle `buy`/`keep`/`sell` into one arg. + #[allow(clippy::too_many_arguments)] #[pallet::call_index(7)] - #[pallet::weight(T::WeightInfo::buy((*asset_count).saturated_into()))] // TODO + #[pallet::weight(T::WeightInfo::combo_sell(asset_count.log_ceil().into()))] #[transactional] pub fn combo_sell( origin: OriginFor, @@ -719,7 +719,7 @@ mod pallet { #[pallet::compact] amount_buy: BalanceOf, #[pallet::compact] amount_keep: BalanceOf, #[pallet::compact] min_amount_out: BalanceOf, - ) -> DispatchResultWithPostInfo { + ) -> DispatchResult { let who = ensure_signed(origin)?; let pool = ::get(pool_id)?; @@ -737,16 +737,15 @@ mod pallet { amount_buy, amount_keep, min_amount_out, - )?; - - Ok(Some(T::WeightInfo::buy(asset_count.into())).into()) // TODO + ) } #[pallet::call_index(8)] - #[pallet::weight({0})] // TODO + #[pallet::weight(T::WeightInfo::deploy_combinatorial_pool(asset_count.log_ceil().into()))] #[transactional] pub fn deploy_combinatorial_pool( origin: OriginFor, + asset_count: AssetIndexType, market_ids: Vec>, amount: BalanceOf, spot_prices: Vec>, @@ -755,6 +754,13 @@ mod pallet { ) -> DispatchResult { let who = ensure_signed(origin)?; + let mut real_asset_count = 1u16; + for market_id in market_ids.iter() { + let market = T::MarketCommons::market(market_id)?; + real_asset_count = real_asset_count.saturating_mul(market.outcomes()); + } + ensure!(asset_count == real_asset_count, Error::::IncorrectAssetCount); + Self::do_deploy_combinatorial_pool( who, market_ids, @@ -1221,7 +1227,6 @@ mod pallet { ); } - // TODO This is where the common code begins! let (liquidity_parameter, amounts_in) = Math::::calculate_reserves_from_spot_prices(amount, spot_prices)?; ensure!( @@ -1273,12 +1278,11 @@ mod pallet { Ok(()) } - #[allow(clippy::too_many_arguments)] // TODO Bundle `buy`/`keep`/`sell` into one arg. + #[allow(clippy::too_many_arguments)] #[require_transactional] pub(crate) fn do_combo_buy( who: T::AccountId, pool_id: T::PoolId, - // TODO Replace `buy`/`keep`/`sell` with a struct. buy: Vec>, sell: Vec>, amount_in: BalanceOf, @@ -1371,7 +1375,6 @@ mod pallet { }) } - // TODO Replace `buy`/`keep`/`sell` with a struct. #[allow(clippy::too_many_arguments)] #[require_transactional] pub(crate) fn do_combo_sell( diff --git a/zrml/neo-swaps/src/tests/deploy_combinatorial_pool.rs b/zrml/neo-swaps/src/tests/deploy_combinatorial_pool.rs index 2638a0086..e58ccc5a5 100644 --- a/zrml/neo-swaps/src/tests/deploy_combinatorial_pool.rs +++ b/zrml/neo-swaps/src/tests/deploy_combinatorial_pool.rs @@ -230,6 +230,7 @@ fn deploy_combinatorial_pool_fails_on_incorrect_vec_len() { assert_noop!( NeoSwaps::deploy_combinatorial_pool( RuntimeOrigin::signed(ALICE), + 6, market_ids, _10, vec![20 * CENT; 5], @@ -251,6 +252,7 @@ fn deploy_combinatorial_pool_fails_on_market_not_found() { assert_noop!( NeoSwaps::deploy_combinatorial_pool( RuntimeOrigin::signed(ALICE), + 10, vec![0, 2, 1], _10, vec![10 * CENT; 10], @@ -281,6 +283,7 @@ fn deploy_combinatorial_pool_fails_on_inactive_market(market_status: MarketStatu assert_noop!( NeoSwaps::deploy_combinatorial_pool( RuntimeOrigin::signed(ALICE), + 10, market_ids, _100, vec![10 * CENT; 10], @@ -302,6 +305,7 @@ fn deploy_combinatorial_pool_fails_on_invalid_trading_mechanism() { assert_noop!( NeoSwaps::deploy_combinatorial_pool( RuntimeOrigin::signed(ALICE), + 10, market_ids, _100, vec![10 * CENT; 10], @@ -339,6 +343,7 @@ fn deploy_combinatorial_pool_fails_on_max_splits_exceeded() { assert_noop!( NeoSwaps::deploy_combinatorial_pool( RuntimeOrigin::signed(ALICE), + 2u16.pow(market_count), market_ids, liquidity, spot_prices, @@ -356,14 +361,10 @@ fn deploy_combinatorial_pool_fails_on_swap_fee_below_min() { let market_id = create_market(ALICE, BASE_ASSET, MarketType::Categorical(2), ScoringRule::AmmCdaHybrid); let liquidity = _10; - assert_ok!(PredictionMarkets::buy_complete_set( - RuntimeOrigin::signed(ALICE), - market_id, - liquidity, - )); assert_noop!( NeoSwaps::deploy_combinatorial_pool( RuntimeOrigin::signed(ALICE), + 2, vec![market_id], liquidity, vec![_1_4, _3_4], @@ -381,14 +382,10 @@ fn deploy_combinatorial_pool_fails_on_swap_fee_above_max() { let market_id = create_market(ALICE, BASE_ASSET, MarketType::Categorical(2), ScoringRule::AmmCdaHybrid); let liquidity = _10; - assert_ok!(PredictionMarkets::buy_complete_set( - RuntimeOrigin::signed(ALICE), - market_id, - liquidity, - )); assert_noop!( NeoSwaps::deploy_combinatorial_pool( RuntimeOrigin::signed(ALICE), + 2, vec![market_id], liquidity, vec![_1_4, _3_4], @@ -410,6 +407,7 @@ fn deploy_combinatorial_pool_fails_on_invalid_spot_prices(spot_prices: Vec::IncorrectAssetCount, + ); + }); +} diff --git a/zrml/neo-swaps/src/tests/mod.rs b/zrml/neo-swaps/src/tests/mod.rs index e8f6b1da8..15ba59d97 100644 --- a/zrml/neo-swaps/src/tests/mod.rs +++ b/zrml/neo-swaps/src/tests/mod.rs @@ -110,10 +110,12 @@ fn create_markets_and_deploy_combinatorial_pool( swap_fee: BalanceOf, ) -> (Vec, ::PoolId) { let mut market_ids = vec![]; - + let mut asset_count = 1u16; for market_type in market_types.iter() { let market_id = create_market(creator, base_asset, market_type.clone(), ScoringRule::AmmCdaHybrid); + let market = ::MarketCommons::market(&market_id).unwrap(); + asset_count *= market.outcomes(); market_ids.push(market_id); } @@ -121,6 +123,7 @@ fn create_markets_and_deploy_combinatorial_pool( let pool_id = as PoolStorage>::next_pool_id(); assert_ok!(NeoSwaps::deploy_combinatorial_pool( RuntimeOrigin::signed(ALICE), + asset_count, market_ids.clone(), amount, spot_prices.clone(), diff --git a/zrml/neo-swaps/src/utility.rs b/zrml/neo-swaps/src/utility.rs new file mode 100644 index 000000000..62103304b --- /dev/null +++ b/zrml/neo-swaps/src/utility.rs @@ -0,0 +1,34 @@ +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use sp_runtime::SaturatedConversion; + +pub(crate) trait LogCeil { + fn log_ceil(&self) -> Self; +} + +impl LogCeil for u16 { + fn log_ceil(&self) -> Self { + let x = *self; + + let bits_minus_one = u16::MAX.saturating_sub(1); + let leading_zeros: u16 = x.leading_zeros().saturated_into(); + let floor_log2 = bits_minus_one.saturating_sub(leading_zeros); + + if x.is_power_of_two() { floor_log2 } else { floor_log2.saturating_add(1) } + } +} diff --git a/zrml/neo-swaps/src/weights.rs b/zrml/neo-swaps/src/weights.rs index 88801707e..4d6fa6de6 100644 --- a/zrml/neo-swaps/src/weights.rs +++ b/zrml/neo-swaps/src/weights.rs @@ -19,21 +19,21 @@ //! Autogenerated weights for zrml_neo_swaps //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2024-08-28`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2024-10-30`, STEPS: `2`, REPEAT: `0`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `zeitgeist-benchmark`, CPU: `AMD EPYC 7601 32-Core Processor` +//! HOSTNAME: `blackbird`, CPU: `` //! EXECUTION: ``, WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` // Executed Command: -// ./target/production/zeitgeist +// ./target/release/zeitgeist // benchmark // pallet // --chain=dev -// --steps=50 -// --repeat=20 +// --steps=2 +// --repeat=0 // --pallet=zrml_neo_swaps // --extrinsic=* -// --execution=wasm +// --execution=native // --wasm-execution=compiled // --heap-pages=4096 // --template=./misc/weight_template.hbs @@ -57,197 +57,231 @@ pub trait WeightInfoZeitgeist { fn exit(n: u32) -> Weight; fn withdraw_fees() -> Weight; fn deploy_pool(n: u32) -> Weight; + fn combo_buy(n: u32) -> Weight; + fn combo_sell(n: u32) -> Weight; + fn deploy_combinatorial_pool(n: u32) -> Weight; fn decision_market_oracle_evaluate() -> Weight; } /// Weight functions for zrml_neo_swaps (automatically generated) pub struct WeightInfo(PhantomData); impl WeightInfoZeitgeist for WeightInfo { - /// Storage: `MarketCommons::Markets` (r:1 w:0) - /// Proof: `MarketCommons::Markets` (`max_values`: None, `max_size`: Some(694), added: 3169, mode: `MaxEncodedLen`) /// Storage: `NeoSwaps::Pools` (r:1 w:1) - /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(144746), added: 147221, mode: `MaxEncodedLen`) + /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(152829), added: 155304, mode: `MaxEncodedLen`) + /// Storage: `MarketCommons::Markets` (r:1 w:0) + /// Proof: `MarketCommons::Markets` (`max_values`: None, `max_size`: Some(708), added: 3183, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:3 w:3) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(132), added: 2607, mode: `MaxEncodedLen`) /// Storage: `Tokens::Accounts` (r:129 w:129) - /// Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(123), added: 2598, mode: `MaxEncodedLen`) + /// Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(137), added: 2612, mode: `MaxEncodedLen`) /// Storage: `Tokens::TotalIssuance` (r:128 w:128) - /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(43), added: 2518, mode: `MaxEncodedLen`) + /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 128]`. - fn buy(n: u32) -> Weight { + fn buy(_n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `1283 + n * (163 ±0)` - // Estimated: `148211 + n * (2598 ±0)` - // Minimum execution time: 367_628 nanoseconds. - Weight::from_parts(352_364_640, 148211) - // Standard Error: 44_817 - .saturating_add(Weight::from_parts(16_342_365, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(5)) - .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(n.into()))) - .saturating_add(Weight::from_parts(0, 2598).saturating_mul(n.into())) + // Measured: `1335 + n * (183 ±0)` + // Estimated: `337938` + // Minimum execution time: 234_000 nanoseconds. + Weight::from_parts(3_918_000_000, 337938) + .saturating_add(T::DbWeight::get().reads(262)) + .saturating_add(T::DbWeight::get().writes(261)) } - /// Storage: `MarketCommons::Markets` (r:1 w:0) - /// Proof: `MarketCommons::Markets` (`max_values`: None, `max_size`: Some(694), added: 3169, mode: `MaxEncodedLen`) /// Storage: `NeoSwaps::Pools` (r:1 w:1) - /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(144746), added: 147221, mode: `MaxEncodedLen`) + /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(152829), added: 155304, mode: `MaxEncodedLen`) + /// Storage: `MarketCommons::Markets` (r:1 w:0) + /// Proof: `MarketCommons::Markets` (`max_values`: None, `max_size`: Some(708), added: 3183, mode: `MaxEncodedLen`) /// Storage: `Tokens::Accounts` (r:129 w:129) - /// Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(123), added: 2598, mode: `MaxEncodedLen`) + /// Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(137), added: 2612, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:3 w:3) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(132), added: 2607, mode: `MaxEncodedLen`) /// Storage: `Tokens::TotalIssuance` (r:128 w:128) - /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(43), added: 2518, mode: `MaxEncodedLen`) + /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 128]`. - fn sell(n: u32) -> Weight { + fn sell(_n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `1414 + n * (163 ±0)` - // Estimated: `148211 + n * (2598 ±0)` - // Minimum execution time: 290_977 nanoseconds. - Weight::from_parts(251_827_451, 148211) - // Standard Error: 26_730 - .saturating_add(Weight::from_parts(23_379_744, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(5)) - .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(n.into()))) - .saturating_add(Weight::from_parts(0, 2598).saturating_mul(n.into())) + // Measured: `1466 + n * (183 ±0)` + // Estimated: `337938` + // Minimum execution time: 206_000 nanoseconds. + Weight::from_parts(4_491_000_000, 337938) + .saturating_add(T::DbWeight::get().reads(262)) + .saturating_add(T::DbWeight::get().writes(261)) } - /// Storage: `MarketCommons::Markets` (r:1 w:0) - /// Proof: `MarketCommons::Markets` (`max_values`: None, `max_size`: Some(694), added: 3169, mode: `MaxEncodedLen`) /// Storage: `NeoSwaps::Pools` (r:1 w:1) - /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(144746), added: 147221, mode: `MaxEncodedLen`) + /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(152829), added: 155304, mode: `MaxEncodedLen`) + /// Storage: `MarketCommons::Markets` (r:1 w:0) + /// Proof: `MarketCommons::Markets` (`max_values`: None, `max_size`: Some(708), added: 3183, mode: `MaxEncodedLen`) /// Storage: `Tokens::Accounts` (r:256 w:256) - /// Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(123), added: 2598, mode: `MaxEncodedLen`) + /// Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(137), added: 2612, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:0) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(132), added: 2607, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 128]`. - fn join_in_place(n: u32) -> Weight { + fn join_in_place(_n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `139311 + n * (197 ±0)` - // Estimated: `148211 + n * (5196 ±0)` - // Minimum execution time: 280_475 nanoseconds. - Weight::from_parts(216_619_883, 148211) - // Standard Error: 169_197 - .saturating_add(Weight::from_parts(30_980_963, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(1)) - .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(n.into()))) - .saturating_add(Weight::from_parts(0, 5196).saturating_mul(n.into())) + // Measured: `139361 + n * (217 ±0)` + // Estimated: `669662` + // Minimum execution time: 247_000 nanoseconds. + Weight::from_parts(2_408_000_000, 669662) + .saturating_add(T::DbWeight::get().reads(259)) + .saturating_add(T::DbWeight::get().writes(257)) } - /// Storage: `MarketCommons::Markets` (r:1 w:0) - /// Proof: `MarketCommons::Markets` (`max_values`: None, `max_size`: Some(694), added: 3169, mode: `MaxEncodedLen`) /// Storage: `NeoSwaps::Pools` (r:1 w:1) - /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(144746), added: 147221, mode: `MaxEncodedLen`) + /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(152829), added: 155304, mode: `MaxEncodedLen`) + /// Storage: `MarketCommons::Markets` (r:1 w:0) + /// Proof: `MarketCommons::Markets` (`max_values`: None, `max_size`: Some(708), added: 3183, mode: `MaxEncodedLen`) /// Storage: `Tokens::Accounts` (r:256 w:256) - /// Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(123), added: 2598, mode: `MaxEncodedLen`) + /// Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(137), added: 2612, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:0) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(132), added: 2607, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 128]`. - fn join_reassigned(n: u32) -> Weight { + fn join_reassigned(_n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `139107 + n * (197 ±0)` - // Estimated: `148211 + n * (5196 ±0)` - // Minimum execution time: 306_517 nanoseconds. - Weight::from_parts(192_506_242, 148211) - // Standard Error: 186_645 - .saturating_add(Weight::from_parts(32_347_165, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(1)) - .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(n.into()))) - .saturating_add(Weight::from_parts(0, 5196).saturating_mul(n.into())) + // Measured: `139157 + n * (217 ±0)` + // Estimated: `669662` + // Minimum execution time: 257_000 nanoseconds. + Weight::from_parts(2_458_000_000, 669662) + .saturating_add(T::DbWeight::get().reads(259)) + .saturating_add(T::DbWeight::get().writes(257)) } - /// Storage: `MarketCommons::Markets` (r:1 w:0) - /// Proof: `MarketCommons::Markets` (`max_values`: None, `max_size`: Some(694), added: 3169, mode: `MaxEncodedLen`) /// Storage: `NeoSwaps::Pools` (r:1 w:1) - /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(144746), added: 147221, mode: `MaxEncodedLen`) + /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(152829), added: 155304, mode: `MaxEncodedLen`) + /// Storage: `MarketCommons::Markets` (r:1 w:0) + /// Proof: `MarketCommons::Markets` (`max_values`: None, `max_size`: Some(708), added: 3183, mode: `MaxEncodedLen`) /// Storage: `Tokens::Accounts` (r:256 w:256) - /// Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(123), added: 2598, mode: `MaxEncodedLen`) + /// Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(137), added: 2612, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:0) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(132), added: 2607, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 128]`. - fn join_leaf(n: u32) -> Weight { + fn join_leaf(_n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `138611 + n * (197 ±0)` - // Estimated: `148211 + n * (5196 ±0)` - // Minimum execution time: 333_727 nanoseconds. - Weight::from_parts(276_851_445, 148211) - // Standard Error: 174_609 - .saturating_add(Weight::from_parts(31_456_446, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(1)) - .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(n.into()))) - .saturating_add(Weight::from_parts(0, 5196).saturating_mul(n.into())) + // Measured: `138661 + n * (217 ±0)` + // Estimated: `669662` + // Minimum execution time: 254_000 nanoseconds. + Weight::from_parts(3_047_000_000, 669662) + .saturating_add(T::DbWeight::get().reads(259)) + .saturating_add(T::DbWeight::get().writes(257)) } - /// Storage: `MarketCommons::Markets` (r:1 w:0) - /// Proof: `MarketCommons::Markets` (`max_values`: None, `max_size`: Some(694), added: 3169, mode: `MaxEncodedLen`) /// Storage: `NeoSwaps::Pools` (r:1 w:1) - /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(144746), added: 147221, mode: `MaxEncodedLen`) + /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(152829), added: 155304, mode: `MaxEncodedLen`) + /// Storage: `MarketCommons::Markets` (r:1 w:0) + /// Proof: `MarketCommons::Markets` (`max_values`: None, `max_size`: Some(708), added: 3183, mode: `MaxEncodedLen`) /// Storage: `Tokens::Accounts` (r:256 w:256) - /// Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(123), added: 2598, mode: `MaxEncodedLen`) + /// Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(137), added: 2612, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:0) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(132), added: 2607, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 128]`. - fn exit(n: u32) -> Weight { + fn exit(_n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `139208 + n * (197 ±0)` - // Estimated: `148211 + n * (5196 ±0)` - // Minimum execution time: 325_537 nanoseconds. - Weight::from_parts(344_456_185, 148211) - // Standard Error: 201_865 - .saturating_add(Weight::from_parts(30_432_204, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(1)) - .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(n.into()))) - .saturating_add(Weight::from_parts(0, 5196).saturating_mul(n.into())) + // Measured: `139258 + n * (217 ±0)` + // Estimated: `669662` + // Minimum execution time: 264_000 nanoseconds. + Weight::from_parts(2_309_000_000, 669662) + .saturating_add(T::DbWeight::get().reads(259)) + .saturating_add(T::DbWeight::get().writes(257)) } /// Storage: `NeoSwaps::Pools` (r:1 w:1) - /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(144746), added: 147221, mode: `MaxEncodedLen`) + /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(152829), added: 155304, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(132), added: 2607, mode: `MaxEncodedLen`) fn withdraw_fees() -> Weight { // Proof Size summary in bytes: - // Measured: `137756` - // Estimated: `148211` - // Minimum execution time: 310_396 nanoseconds. - Weight::from_parts(328_797_000, 148211) + // Measured: `137883` + // Estimated: `156294` + // Minimum execution time: 177_000 nanoseconds. + Weight::from_parts(177_000_000, 156294) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: `MarketCommons::Markets` (r:1 w:0) - /// Proof: `MarketCommons::Markets` (`max_values`: None, `max_size`: Some(694), added: 3169, mode: `MaxEncodedLen`) - /// Storage: `NeoSwaps::Pools` (r:1 w:1) - /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(144746), added: 147221, mode: `MaxEncodedLen`) + /// Proof: `MarketCommons::Markets` (`max_values`: None, `max_size`: Some(708), added: 3183, mode: `MaxEncodedLen`) + /// Storage: `NeoSwaps::MarketIdToPoolId` (r:1 w:1) + /// Proof: `NeoSwaps::MarketIdToPoolId` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `Tokens::Accounts` (r:256 w:256) - /// Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(123), added: 2598, mode: `MaxEncodedLen`) + /// Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(137), added: 2612, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(132), added: 2607, mode: `MaxEncodedLen`) + /// Storage: `NeoSwaps::PoolCount` (r:1 w:1) + /// Proof: `NeoSwaps::PoolCount` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `NeoSwaps::Pools` (r:0 w:1) + /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(152829), added: 155304, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 128]`. - fn deploy_pool(n: u32) -> Weight { + fn deploy_pool(_n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `611 + n * (81 ±0)` - // Estimated: `148211 + n * (5196 ±0)` - // Minimum execution time: 161_493 nanoseconds. - Weight::from_parts(109_983_447, 148211) - // Standard Error: 44_260 - .saturating_add(Weight::from_parts(34_197_887, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(2)) - .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(n.into()))) - .saturating_add(Weight::from_parts(0, 5196).saturating_mul(n.into())) + // Measured: `593 + n * (81 ±0)` + // Estimated: `669662` + // Minimum execution time: 105_000 nanoseconds. + Weight::from_parts(2_875_000_000, 669662) + .saturating_add(T::DbWeight::get().reads(260)) + .saturating_add(T::DbWeight::get().writes(260)) + } + /// Storage: `NeoSwaps::Pools` (r:1 w:1) + /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(152829), added: 155304, mode: `MaxEncodedLen`) + /// Storage: `MarketCommons::Markets` (r:7 w:0) + /// Proof: `MarketCommons::Markets` (`max_values`: None, `max_size`: Some(708), added: 3183, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:3 w:3) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(132), added: 2607, mode: `MaxEncodedLen`) + /// Storage: `Tokens::Accounts` (r:256 w:256) + /// Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(137), added: 2612, mode: `MaxEncodedLen`) + /// Storage: `Tokens::TotalIssuance` (r:128 w:128) + /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) + /// The range of component `n` is `[1, 7]`. + fn combo_buy(_n: u32) -> Weight { + // Proof Size summary in bytes: + // Measured: `0 + n * (5868 ±0)` + // Estimated: `669662` + // Minimum execution time: 260_000 nanoseconds. + Weight::from_parts(8_787_000_000, 669662) + .saturating_add(T::DbWeight::get().reads(395)) + .saturating_add(T::DbWeight::get().writes(388)) + } + /// Storage: `NeoSwaps::Pools` (r:1 w:1) + /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(152829), added: 155304, mode: `MaxEncodedLen`) + /// Storage: `MarketCommons::Markets` (r:7 w:0) + /// Proof: `MarketCommons::Markets` (`max_values`: None, `max_size`: Some(708), added: 3183, mode: `MaxEncodedLen`) + /// Storage: `Tokens::Accounts` (r:255 w:255) + /// Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(137), added: 2612, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:3 w:3) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(132), added: 2607, mode: `MaxEncodedLen`) + /// Storage: `Tokens::TotalIssuance` (r:128 w:128) + /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) + /// The range of component `n` is `[1, 7]`. + fn combo_sell(_n: u32) -> Weight { + // Proof Size summary in bytes: + // Measured: `0 + n * (7895 ±0)` + // Estimated: `667050` + // Minimum execution time: 274_000 nanoseconds. + Weight::from_parts(13_888_000_000, 667050) + .saturating_add(T::DbWeight::get().reads(394)) + .saturating_add(T::DbWeight::get().writes(387)) + } + /// Storage: `MarketCommons::Markets` (r:7 w:0) + /// Proof: `MarketCommons::Markets` (`max_values`: None, `max_size`: Some(708), added: 3183, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(132), added: 2607, mode: `MaxEncodedLen`) + /// Storage: `Tokens::Accounts` (r:382 w:382) + /// Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(137), added: 2612, mode: `MaxEncodedLen`) + /// Storage: `Tokens::TotalIssuance` (r:254 w:254) + /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) + /// Storage: `NeoSwaps::PoolCount` (r:1 w:1) + /// Proof: `NeoSwaps::PoolCount` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `NeoSwaps::Pools` (r:0 w:1) + /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(152829), added: 155304, mode: `MaxEncodedLen`) + /// The range of component `n` is `[1, 7]`. + fn deploy_combinatorial_pool(_n: u32) -> Weight { + // Proof Size summary in bytes: + // Measured: `351 + n * (185 ±0)` + // Estimated: `998774` + // Minimum execution time: 2_053_000 nanoseconds. + Weight::from_parts(310_900_000_000, 998774) + .saturating_add(T::DbWeight::get().reads(646)) + .saturating_add(T::DbWeight::get().writes(640)) } /// Storage: `NeoSwaps::Pools` (r:1 w:0) - /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(146552), added: 149027, mode: `MaxEncodedLen`) + /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(152829), added: 155304, mode: `MaxEncodedLen`) fn decision_market_oracle_evaluate() -> Weight { // Proof Size summary in bytes: - // Measured: `365` - // Estimated: `150017` - // Minimum execution time: 44_000 nanoseconds. - Weight::from_parts(44_000_000, 150017).saturating_add(T::DbWeight::get().reads(1)) + // Measured: `492` + // Estimated: `156294` + // Minimum execution time: 49_000 nanoseconds. + Weight::from_parts(49_000_000, 156294).saturating_add(T::DbWeight::get().reads(1)) } } From 93f06635570c1959a6c20164213337c97ce010bc Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 1 Nov 2024 10:13:55 +0000 Subject: [PATCH 47/73] Add weights of new modules --- zrml/combinatorial-tokens/src/weights.rs | 132 ++++++++------ zrml/futarchy/src/weights.rs | 26 +-- zrml/neo-swaps/src/weights.rs | 210 ++++++++++++++--------- 3 files changed, 225 insertions(+), 143 deletions(-) diff --git a/zrml/combinatorial-tokens/src/weights.rs b/zrml/combinatorial-tokens/src/weights.rs index b509b599d..1edf64a0a 100644 --- a/zrml/combinatorial-tokens/src/weights.rs +++ b/zrml/combinatorial-tokens/src/weights.rs @@ -19,21 +19,21 @@ //! Autogenerated weights for zrml_combinatorial_tokens //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2024-10-24`, STEPS: `2`, REPEAT: `0`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2024-10-30`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `blackbird`, CPU: `` +//! HOSTNAME: `ztg-benchmark`, CPU: `AMD EPYC 7601 32-Core Processor` //! EXECUTION: ``, WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` // Executed Command: -// ./target/release/zeitgeist +// ./target/production/zeitgeist // benchmark // pallet // --chain=dev -// --steps=2 -// --repeat=0 +// --steps=50 +// --repeat=20 // --pallet=zrml_combinatorial_tokens // --extrinsic=* -// --execution=native +// --execution=wasm // --wasm-execution=compiled // --heap-pages=4096 // --template=./misc/weight_template.hbs @@ -71,14 +71,19 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `Tokens::TotalIssuance` (r:32 w:32) /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 32]`. - fn split_position_vertical_sans_parent(_n: u32) -> Weight { + fn split_position_vertical_sans_parent(n: u32) -> Weight { // Proof Size summary in bytes: // Measured: `441` - // Estimated: `84574` - // Minimum execution time: 1_923_000 nanoseconds. - Weight::from_parts(29_365_000_000, 84574) - .saturating_add(T::DbWeight::get().reads(66)) - .saturating_add(T::DbWeight::get().writes(65)) + // Estimated: `4173 + n * (2612 ±0)` + // Minimum execution time: 6_978_416 nanoseconds. + Weight::from_parts(102_072_603, 4173) + // Standard Error: 4_544_660 + .saturating_add(Weight::from_parts(3_565_520_352, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(1)) + .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 2612).saturating_mul(n.into())) } /// Storage: `MarketCommons::Markets` (r:1 w:0) /// Proof: `MarketCommons::Markets` (`max_values`: None, `max_size`: Some(708), added: 3183, mode: `MaxEncodedLen`) @@ -87,14 +92,19 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `Tokens::TotalIssuance` (r:33 w:33) /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 32]`. - fn split_position_vertical_with_parent(_n: u32) -> Weight { + fn split_position_vertical_with_parent(n: u32) -> Weight { // Proof Size summary in bytes: // Measured: `671` - // Estimated: `87186` - // Minimum execution time: 2_353_000 nanoseconds. - Weight::from_parts(37_193_000_000, 87186) - .saturating_add(T::DbWeight::get().reads(67)) - .saturating_add(T::DbWeight::get().writes(66)) + // Estimated: `4173 + n * (2612 ±0)` + // Minimum execution time: 8_316_377 nanoseconds. + Weight::from_parts(2_605_489, 4173) + // Standard Error: 3_965_121 + .saturating_add(Weight::from_parts(4_222_525_574, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(2)) + .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 2612).saturating_mul(n.into())) } /// Storage: `MarketCommons::Markets` (r:1 w:0) /// Proof: `MarketCommons::Markets` (`max_values`: None, `max_size`: Some(708), added: 3183, mode: `MaxEncodedLen`) @@ -103,14 +113,19 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `Tokens::TotalIssuance` (r:33 w:33) /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 32]`. - fn split_position_horizontal(_n: u32) -> Weight { + fn split_position_horizontal(n: u32) -> Weight { // Proof Size summary in bytes: // Measured: `633` - // Estimated: `87186` - // Minimum execution time: 2_773_000 nanoseconds. - Weight::from_parts(30_303_000_000, 87186) - .saturating_add(T::DbWeight::get().reads(67)) - .saturating_add(T::DbWeight::get().writes(66)) + // Estimated: `4173 + n * (2612 ±0)` + // Minimum execution time: 10_369_759 nanoseconds. + Weight::from_parts(3_384_663_612, 4173) + // Standard Error: 3_858_637 + .saturating_add(Weight::from_parts(3_549_879_971, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(2)) + .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 2612).saturating_mul(n.into())) } /// Storage: `MarketCommons::Markets` (r:1 w:0) /// Proof: `MarketCommons::Markets` (`max_values`: None, `max_size`: Some(708), added: 3183, mode: `MaxEncodedLen`) @@ -121,14 +136,19 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(132), added: 2607, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 32]`. - fn merge_position_vertical_sans_parent(_n: u32) -> Weight { + fn merge_position_vertical_sans_parent(n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `624 + n * (160 ±0)` - // Estimated: `84574` - // Minimum execution time: 1_889_000 nanoseconds. - Weight::from_parts(29_394_000_000, 84574) - .saturating_add(T::DbWeight::get().reads(66)) - .saturating_add(T::DbWeight::get().writes(65)) + // Measured: `623 + n * (159 ±0)` + // Estimated: `4173 + n * (2612 ±0)` + // Minimum execution time: 6_950_049 nanoseconds. + Weight::from_parts(7_032_940_000, 4173) + // Standard Error: 8_128_230 + .saturating_add(Weight::from_parts(3_221_583_105, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(1)) + .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 2612).saturating_mul(n.into())) } /// Storage: `MarketCommons::Markets` (r:1 w:0) /// Proof: `MarketCommons::Markets` (`max_values`: None, `max_size`: Some(708), added: 3183, mode: `MaxEncodedLen`) @@ -137,14 +157,19 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `Tokens::TotalIssuance` (r:33 w:33) /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 32]`. - fn merge_position_vertical_with_parent(_n: u32) -> Weight { + fn merge_position_vertical_with_parent(n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `518 + n * (160 ±0)` - // Estimated: `87186` - // Minimum execution time: 2_376_000 nanoseconds. - Weight::from_parts(37_564_000_000, 87186) - .saturating_add(T::DbWeight::get().reads(67)) - .saturating_add(T::DbWeight::get().writes(66)) + // Measured: `515 + n * (160 ±0)` + // Estimated: `4173 + n * (2612 ±0)` + // Minimum execution time: 8_233_017 nanoseconds. + Weight::from_parts(8_273_928_000, 4173) + // Standard Error: 9_495_570 + .saturating_add(Weight::from_parts(3_810_340_613, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(2)) + .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 2612).saturating_mul(n.into())) } /// Storage: `MarketCommons::Markets` (r:1 w:0) /// Proof: `MarketCommons::Markets` (`max_values`: None, `max_size`: Some(708), added: 3183, mode: `MaxEncodedLen`) @@ -153,14 +178,19 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `Tokens::TotalIssuance` (r:33 w:33) /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 32]`. - fn merge_position_horizontal(_n: u32) -> Weight { + fn merge_position_horizontal(n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `480 + n * (160 ±0)` - // Estimated: `87186` - // Minimum execution time: 2_760_000 nanoseconds. - Weight::from_parts(30_589_000_000, 87186) - .saturating_add(T::DbWeight::get().reads(67)) - .saturating_add(T::DbWeight::get().writes(66)) + // Measured: `478 + n * (160 ±0)` + // Estimated: `4173 + n * (2612 ±0)` + // Minimum execution time: 10_361_313 nanoseconds. + Weight::from_parts(3_250_658_133, 4173) + // Standard Error: 4_095_907 + .saturating_add(Weight::from_parts(3_565_909_886, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(2)) + .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 2612).saturating_mul(n.into())) } /// Storage: `MarketCommons::Markets` (r:1 w:0) /// Proof: `MarketCommons::Markets` (`max_values`: None, `max_size`: Some(708), added: 3183, mode: `MaxEncodedLen`) @@ -171,12 +201,14 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(132), added: 2607, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 32]`. - fn redeem_position_sans_parent(_n: u32) -> Weight { + fn redeem_position_sans_parent(n: u32) -> Weight { // Proof Size summary in bytes: // Measured: `780` // Estimated: `4173` - // Minimum execution time: 979_000 nanoseconds. - Weight::from_parts(986_000_000, 4173) + // Minimum execution time: 3_501_697 nanoseconds. + Weight::from_parts(3_567_968_354, 4173) + // Standard Error: 114_392 + .saturating_add(Weight::from_parts(159_826, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -191,8 +223,8 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `674` // Estimated: `6214` - // Minimum execution time: 1_193_000 nanoseconds. - Weight::from_parts(1_215_000_000, 6214) + // Minimum execution time: 4_123_619 nanoseconds. + Weight::from_parts(4_201_426_623, 6214) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(4)) } diff --git a/zrml/futarchy/src/weights.rs b/zrml/futarchy/src/weights.rs index db21cdab6..c63ae28cc 100644 --- a/zrml/futarchy/src/weights.rs +++ b/zrml/futarchy/src/weights.rs @@ -19,21 +19,21 @@ //! Autogenerated weights for zrml_futarchy //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2024-10-20`, STEPS: `2`, REPEAT: `0`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2024-10-30`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `blackbird`, CPU: `` +//! HOSTNAME: `ztg-benchmark`, CPU: `AMD EPYC 7601 32-Core Processor` //! EXECUTION: ``, WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` // Executed Command: -// ./target/release/zeitgeist +// ./target/production/zeitgeist // benchmark // pallet // --chain=dev -// --steps=2 -// --repeat=0 +// --steps=50 +// --repeat=20 // --pallet=zrml_futarchy // --extrinsic=* -// --execution=native +// --execution=wasm // --wasm-execution=compiled // --heap-pages=4096 // --template=./misc/weight_template.hbs @@ -62,21 +62,21 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `41` // Estimated: `7026` - // Minimum execution time: 13_000 nanoseconds. - Weight::from_parts(13_000_000, 7026) + // Minimum execution time: 18_200 nanoseconds. + Weight::from_parts(18_871_000, 7026) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `NeoSwaps::Pools` (r:1 w:0) - /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(146552), added: 149027, mode: `MaxEncodedLen`) + /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(152829), added: 155304, mode: `MaxEncodedLen`) /// Storage: `Scheduler::Agenda` (r:1 w:1) /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(109074), added: 111549, mode: `MaxEncodedLen`) fn maybe_schedule_proposal() -> Weight { // Proof Size summary in bytes: - // Measured: `368` - // Estimated: `150017` - // Minimum execution time: 55_000 nanoseconds. - Weight::from_parts(55_000_000, 150017) + // Measured: `480` + // Estimated: `156294` + // Minimum execution time: 98_532 nanoseconds. + Weight::from_parts(100_052_000, 156294) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/zrml/neo-swaps/src/weights.rs b/zrml/neo-swaps/src/weights.rs index 4d6fa6de6..ef359b861 100644 --- a/zrml/neo-swaps/src/weights.rs +++ b/zrml/neo-swaps/src/weights.rs @@ -19,21 +19,21 @@ //! Autogenerated weights for zrml_neo_swaps //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2024-10-30`, STEPS: `2`, REPEAT: `0`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2024-10-31`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `blackbird`, CPU: `` +//! HOSTNAME: `ztg-benchmark`, CPU: `AMD EPYC 7601 32-Core Processor` //! EXECUTION: ``, WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` // Executed Command: -// ./target/release/zeitgeist +// ./target/production/zeitgeist // benchmark // pallet // --chain=dev -// --steps=2 -// --repeat=0 +// --steps=50 +// --repeat=20 // --pallet=zrml_neo_swaps // --extrinsic=* -// --execution=native +// --execution=wasm // --wasm-execution=compiled // --heap-pages=4096 // --template=./misc/weight_template.hbs @@ -77,14 +77,19 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `Tokens::TotalIssuance` (r:128 w:128) /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 128]`. - fn buy(_n: u32) -> Weight { + fn buy(n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `1335 + n * (183 ±0)` - // Estimated: `337938` - // Minimum execution time: 234_000 nanoseconds. - Weight::from_parts(3_918_000_000, 337938) - .saturating_add(T::DbWeight::get().reads(262)) - .saturating_add(T::DbWeight::get().writes(261)) + // Measured: `1372 + n * (182 ±0)` + // Estimated: `156294 + n * (2612 ±0)` + // Minimum execution time: 402_539 nanoseconds. + Weight::from_parts(302_340_193, 156294) + // Standard Error: 92_254 + .saturating_add(Weight::from_parts(53_376_748, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(5)) + .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 2612).saturating_mul(n.into())) } /// Storage: `NeoSwaps::Pools` (r:1 w:1) /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(152829), added: 155304, mode: `MaxEncodedLen`) @@ -97,14 +102,19 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `Tokens::TotalIssuance` (r:128 w:128) /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 128]`. - fn sell(_n: u32) -> Weight { + fn sell(n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `1466 + n * (183 ±0)` - // Estimated: `337938` - // Minimum execution time: 206_000 nanoseconds. - Weight::from_parts(4_491_000_000, 337938) - .saturating_add(T::DbWeight::get().reads(262)) - .saturating_add(T::DbWeight::get().writes(261)) + // Measured: `1503 + n * (182 ±0)` + // Estimated: `156294 + n * (2612 ±0)` + // Minimum execution time: 337_007 nanoseconds. + Weight::from_parts(250_443_677, 156294) + // Standard Error: 116_699 + .saturating_add(Weight::from_parts(60_461_360, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(5)) + .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 2612).saturating_mul(n.into())) } /// Storage: `NeoSwaps::Pools` (r:1 w:1) /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(152829), added: 155304, mode: `MaxEncodedLen`) @@ -115,14 +125,19 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `System::Account` (r:1 w:0) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(132), added: 2607, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 128]`. - fn join_in_place(_n: u32) -> Weight { + fn join_in_place(n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `139361 + n * (217 ±0)` - // Estimated: `669662` - // Minimum execution time: 247_000 nanoseconds. - Weight::from_parts(2_408_000_000, 669662) - .saturating_add(T::DbWeight::get().reads(259)) - .saturating_add(T::DbWeight::get().writes(257)) + // Measured: `139400 + n * (216 ±0)` + // Estimated: `156294 + n * (5224 ±0)` + // Minimum execution time: 396_540 nanoseconds. + Weight::from_parts(350_023_672, 156294) + // Standard Error: 157_275 + .saturating_add(Weight::from_parts(31_812_304, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(1)) + .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 5224).saturating_mul(n.into())) } /// Storage: `NeoSwaps::Pools` (r:1 w:1) /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(152829), added: 155304, mode: `MaxEncodedLen`) @@ -133,14 +148,19 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `System::Account` (r:1 w:0) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(132), added: 2607, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 128]`. - fn join_reassigned(_n: u32) -> Weight { + fn join_reassigned(n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `139157 + n * (217 ±0)` - // Estimated: `669662` - // Minimum execution time: 257_000 nanoseconds. - Weight::from_parts(2_458_000_000, 669662) - .saturating_add(T::DbWeight::get().reads(259)) - .saturating_add(T::DbWeight::get().writes(257)) + // Measured: `139196 + n * (216 ±0)` + // Estimated: `156294 + n * (5224 ±0)` + // Minimum execution time: 405_608 nanoseconds. + Weight::from_parts(376_463_342, 156294) + // Standard Error: 158_653 + .saturating_add(Weight::from_parts(32_337_731, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(1)) + .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 5224).saturating_mul(n.into())) } /// Storage: `NeoSwaps::Pools` (r:1 w:1) /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(152829), added: 155304, mode: `MaxEncodedLen`) @@ -151,14 +171,19 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `System::Account` (r:1 w:0) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(132), added: 2607, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 128]`. - fn join_leaf(_n: u32) -> Weight { + fn join_leaf(n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `138661 + n * (217 ±0)` - // Estimated: `669662` - // Minimum execution time: 254_000 nanoseconds. - Weight::from_parts(3_047_000_000, 669662) - .saturating_add(T::DbWeight::get().reads(259)) - .saturating_add(T::DbWeight::get().writes(257)) + // Measured: `138700 + n * (216 ±0)` + // Estimated: `156294 + n * (5224 ±0)` + // Minimum execution time: 470_430 nanoseconds. + Weight::from_parts(404_406_469, 156294) + // Standard Error: 162_346 + .saturating_add(Weight::from_parts(31_654_906, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(1)) + .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 5224).saturating_mul(n.into())) } /// Storage: `NeoSwaps::Pools` (r:1 w:1) /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(152829), added: 155304, mode: `MaxEncodedLen`) @@ -169,14 +194,19 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `System::Account` (r:1 w:0) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(132), added: 2607, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 128]`. - fn exit(_n: u32) -> Weight { + fn exit(n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `139258 + n * (217 ±0)` - // Estimated: `669662` - // Minimum execution time: 264_000 nanoseconds. - Weight::from_parts(2_309_000_000, 669662) - .saturating_add(T::DbWeight::get().reads(259)) - .saturating_add(T::DbWeight::get().writes(257)) + // Measured: `139297 + n * (216 ±0)` + // Estimated: `156294 + n * (5224 ±0)` + // Minimum execution time: 433_429 nanoseconds. + Weight::from_parts(445_783_938, 156294) + // Standard Error: 148_856 + .saturating_add(Weight::from_parts(31_235_322, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(1)) + .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 5224).saturating_mul(n.into())) } /// Storage: `NeoSwaps::Pools` (r:1 w:1) /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(152829), added: 155304, mode: `MaxEncodedLen`) @@ -186,8 +216,8 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `137883` // Estimated: `156294` - // Minimum execution time: 177_000 nanoseconds. - Weight::from_parts(177_000_000, 156294) + // Minimum execution time: 319_797 nanoseconds. + Weight::from_parts(365_799_000, 156294) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -204,14 +234,19 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `NeoSwaps::Pools` (r:0 w:1) /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(152829), added: 155304, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 128]`. - fn deploy_pool(_n: u32) -> Weight { + fn deploy_pool(n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `593 + n * (81 ±0)` - // Estimated: `669662` - // Minimum execution time: 105_000 nanoseconds. - Weight::from_parts(2_875_000_000, 669662) - .saturating_add(T::DbWeight::get().reads(260)) - .saturating_add(T::DbWeight::get().writes(260)) + // Measured: `611 + n * (81 ±0)` + // Estimated: `4173 + n * (5224 ±0)` + // Minimum execution time: 159_294 nanoseconds. + Weight::from_parts(100_340_149, 4173) + // Standard Error: 67_332 + .saturating_add(Weight::from_parts(33_452_544, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(4)) + .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 5224).saturating_mul(n.into())) } /// Storage: `NeoSwaps::Pools` (r:1 w:1) /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(152829), added: 155304, mode: `MaxEncodedLen`) @@ -224,14 +259,19 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `Tokens::TotalIssuance` (r:128 w:128) /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) /// The range of component `n` is `[1, 7]`. - fn combo_buy(_n: u32) -> Weight { + fn combo_buy(n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `0 + n * (5868 ±0)` - // Estimated: `669662` - // Minimum execution time: 260_000 nanoseconds. - Weight::from_parts(8_787_000_000, 669662) - .saturating_add(T::DbWeight::get().reads(395)) - .saturating_add(T::DbWeight::get().writes(388)) + // Measured: `0 + n * (2721 ±0)` + // Estimated: `156294 + n * (38153 ±999)` + // Minimum execution time: 462_410 nanoseconds. + Weight::from_parts(465_820_000, 156294) + // Standard Error: 23_261_249 + .saturating_add(Weight::from_parts(901_905_964, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(11)) + .saturating_add(T::DbWeight::get().reads((23_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(10)) + .saturating_add(T::DbWeight::get().writes((22_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 38153).saturating_mul(n.into())) } /// Storage: `NeoSwaps::Pools` (r:1 w:1) /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(152829), added: 155304, mode: `MaxEncodedLen`) @@ -244,14 +284,19 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `Tokens::TotalIssuance` (r:128 w:128) /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) /// The range of component `n` is `[1, 7]`. - fn combo_sell(_n: u32) -> Weight { + fn combo_sell(n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `0 + n * (7895 ±0)` - // Estimated: `667050` - // Minimum execution time: 274_000 nanoseconds. - Weight::from_parts(13_888_000_000, 667050) - .saturating_add(T::DbWeight::get().reads(394)) - .saturating_add(T::DbWeight::get().writes(387)) + // Measured: `0 + n * (3627 ±0)` + // Estimated: `156294 + n * (38153 ±484)` + // Minimum execution time: 523_942 nanoseconds. + Weight::from_parts(527_472_000, 156294) + // Standard Error: 38_920_367 + .saturating_add(Weight::from_parts(1_535_695_671, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(10)) + .saturating_add(T::DbWeight::get().reads((23_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(9)) + .saturating_add(T::DbWeight::get().writes((22_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 38153).saturating_mul(n.into())) } /// Storage: `MarketCommons::Markets` (r:7 w:0) /// Proof: `MarketCommons::Markets` (`max_values`: None, `max_size`: Some(708), added: 3183, mode: `MaxEncodedLen`) @@ -266,14 +311,19 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `NeoSwaps::Pools` (r:0 w:1) /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(152829), added: 155304, mode: `MaxEncodedLen`) /// The range of component `n` is `[1, 7]`. - fn deploy_combinatorial_pool(_n: u32) -> Weight { + fn deploy_combinatorial_pool(n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `351 + n * (185 ±0)` - // Estimated: `998774` - // Minimum execution time: 2_053_000 nanoseconds. - Weight::from_parts(310_900_000_000, 998774) - .saturating_add(T::DbWeight::get().reads(646)) - .saturating_add(T::DbWeight::get().writes(640)) + // Measured: `357 + n * (185 ±0)` + // Estimated: `11438 + n * (57229 ±969)` + // Minimum execution time: 7_103_129 nanoseconds. + Weight::from_parts(7_159_730_000, 11438) + // Standard Error: 1_623_272_081 + .saturating_add(Weight::from_parts(61_965_055_407, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(10)) + .saturating_add(T::DbWeight::get().reads((37_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(10)) + .saturating_add(T::DbWeight::get().writes((37_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 57229).saturating_mul(n.into())) } /// Storage: `NeoSwaps::Pools` (r:1 w:0) /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(152829), added: 155304, mode: `MaxEncodedLen`) @@ -281,7 +331,7 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `492` // Estimated: `156294` - // Minimum execution time: 49_000 nanoseconds. - Weight::from_parts(49_000_000, 156294).saturating_add(T::DbWeight::get().reads(1)) + // Minimum execution time: 91_762 nanoseconds. + Weight::from_parts(93_152_000, 156294).saturating_add(T::DbWeight::get().reads(1)) } } From 33a736eecfb282c54f5652c2573e848b3e7c7727 Mon Sep 17 00:00:00 2001 From: Chralt Date: Thu, 30 Jan 2025 14:29:21 +0100 Subject: [PATCH 48/73] Add storage migrations for Combinatorial Tokens Upgrade (#1401) * add todos for storage migrations * remove todo for asset storage migration * add storage migration and try-runtime tests * correct migration and tests * update todos * update migration tests * add migration to runtime, fix clippy * bump storage version * remove corrupted pools * correct clippy * correct CI --- primitives/src/asset.rs | 1 - runtime/common/src/lib.rs | 4 +- zrml/neo-swaps/src/lib.rs | 2 +- zrml/neo-swaps/src/migration.rs | 398 ++++++++++++++++++++++++++++++++ 4 files changed, 402 insertions(+), 3 deletions(-) diff --git a/primitives/src/asset.rs b/primitives/src/asset.rs index dbd22367d..d3d87223a 100644 --- a/primitives/src/asset.rs +++ b/primitives/src/asset.rs @@ -55,7 +55,6 @@ pub enum Asset { ForeignAsset(u32), ParimutuelShare(MarketId, CategoryIndex), } -// TODO Needs storage migration #[cfg(feature = "runtime-benchmarks")] impl ZeitgeistAssetEnumerator for Asset { diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs index 2b26b806a..8d3426fce 100644 --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -96,11 +96,13 @@ macro_rules! decl_common_types { #[cfg(feature = "runtime-benchmarks")] use zrml_prediction_markets::types::PredictionMarketsCombinatorialTokensBenchmarkHelper; + use zrml_neo_swaps::migration::MigratePoolStorageItems; + pub type Block = generic::Block; type Address = sp_runtime::MultiAddress; - type Migrations = (); + type Migrations = (MigratePoolStorageItems); pub type Executive = frame_executive::Executive< Runtime, diff --git a/zrml/neo-swaps/src/lib.rs b/zrml/neo-swaps/src/lib.rs index 2fc47f05a..88b45bc3a 100644 --- a/zrml/neo-swaps/src/lib.rs +++ b/zrml/neo-swaps/src/lib.rs @@ -93,7 +93,7 @@ mod pallet { }; use zrml_market_commons::MarketCommonsPalletApi; - pub(crate) const STORAGE_VERSION: StorageVersion = StorageVersion::new(2); + pub(crate) const STORAGE_VERSION: StorageVersion = StorageVersion::new(3); // These should not be config parameters to avoid misconfigurations. pub(crate) const EXIT_FEE: u128 = CENT / 10; diff --git a/zrml/neo-swaps/src/migration.rs b/zrml/neo-swaps/src/migration.rs index 2e9ba478f..3ef4559d5 100644 --- a/zrml/neo-swaps/src/migration.rs +++ b/zrml/neo-swaps/src/migration.rs @@ -14,3 +14,401 @@ // // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . + +use crate::{ + traits::LiquiditySharesManager, + types::{MaxAssets, Pool, PoolType}, + AssetOf, BalanceOf, Config, LiquidityTreeOf, MarketIdOf, MarketIdToPoolId, Pallet, PoolCount, + Pools, +}; +use alloc::{fmt::Debug, vec, vec::Vec}; +use core::marker::PhantomData; +use frame_support::{ + migration::storage_key_iter, + pallet_prelude::Twox64Concat, + storage::bounded_btree_map::BoundedBTreeMap, + traits::{Get, OnRuntimeUpgrade, StorageVersion}, + weights::Weight, + CloneNoBound, PartialEqNoBound, RuntimeDebugNoBound, +}; +use log; +use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; +use sp_runtime::{SaturatedConversion, Saturating}; +use zeitgeist_primitives::math::checked_ops_res::CheckedAddRes; +use zrml_market_commons::MarketCommonsPalletApi; + +cfg_if::cfg_if! { + if #[cfg(feature = "try-runtime")] { + use alloc::{format, collections::BTreeMap}; + use sp_runtime::DispatchError; + } +} + +const NEO_SWAPS: &[u8] = b"NeoSwaps"; +const POOLS: &[u8] = b"Pools"; + +const NEO_SWAPS_REQUIRED_STORAGE_VERSION: u16 = 2; +const NEO_SWAPS_NEXT_STORAGE_VERSION: u16 = NEO_SWAPS_REQUIRED_STORAGE_VERSION + 1; + +#[derive( + CloneNoBound, Decode, Encode, Eq, MaxEncodedLen, PartialEqNoBound, RuntimeDebugNoBound, TypeInfo, +)] +#[scale_info(skip_type_params(S, T))] +pub struct OldPool +where + T: Config, + LSM: Clone + Debug + LiquiditySharesManager + PartialEq, + S: Get, +{ + pub account_id: T::AccountId, + pub reserves: BoundedBTreeMap, BalanceOf, S>, + pub collateral: AssetOf, + pub liquidity_parameter: BalanceOf, + pub liquidity_shares_manager: LSM, + pub swap_fee: BalanceOf, +} + +type OldPoolOf = OldPool, MaxAssets>; + +// https://substrate.stackexchange.com/questions/10472/pallet-storage-migration-fails-try-runtime-idempotent-test +// idempotent test fails, because of the manual storage version increment +// VersionedMigration is still an experimental feature for the currently used polkadot version +// that's why the idempotent test is ignored for this migration +pub struct MigratePoolStorageItems(PhantomData, RemovableMarketIds); + +impl OnRuntimeUpgrade for MigratePoolStorageItems +where + T: Config, + RemovableMarketIds: Get>, +{ + fn on_runtime_upgrade() -> Weight { + let mut total_weight = T::DbWeight::get().reads(1); + let neo_swaps_version = StorageVersion::get::>(); + if neo_swaps_version != NEO_SWAPS_REQUIRED_STORAGE_VERSION { + log::info!( + "MigratePoolStorageItems: neo-swaps version is {:?}, but {:?} is required", + neo_swaps_version, + NEO_SWAPS_REQUIRED_STORAGE_VERSION, + ); + return total_weight; + } + log::info!("MigratePoolStorageItems: Starting..."); + // NeoSwaps: 7de9893ad4de67f3510fd09678a13412 + // Pools: 4c72016d74b63ae83d79b02efdb5528e + // failed to decode pool with market id 880: 0x7de9893ad4de67f3510fd09678a134124c72016d74b63ae83d79b02efdb5528e00251b42e33e726f70030000000000000000000000000000 + // failed to decode pool with market id 878: 0x7de9893ad4de67f3510fd09678a134124c72016d74b63ae83d79b02efdb5528e0f7a0cea0db6ee406e030000000000000000000000000000 + // failed to decode pool with market id 882: 0x7de9893ad4de67f3510fd09678a134124c72016d74b63ae83d79b02efdb5528eb49736cf4bc6723372030000000000000000000000000000 + // failed to decode pool with market id 879: 0x7de9893ad4de67f3510fd09678a134124c72016d74b63ae83d79b02efdb5528ed857f1051e4281a76f030000000000000000000000000000 + // failed to decode pool with market id 877: 0x7de9893ad4de67f3510fd09678a134124c72016d74b63ae83d79b02efdb5528ee0edd4b43beb361f6d030000000000000000000000000000 + // The decode failure happens, because the old pool used a CampaignAsset as asset, which is not supported anymore, since the asset system overhaul has been reverted. + + let mut max_pool_id: T::PoolId = Default::default(); + for (market_id, _) in + storage_key_iter::, OldPoolOf, Twox64Concat>(NEO_SWAPS, POOLS) + { + total_weight = total_weight.saturating_add(T::DbWeight::get().reads(2)); + if T::MarketCommons::market(&market_id).is_err() { + log::error!("MigratePoolStorageItems: Market {:?} not found", market_id); + return total_weight; + }; + let pool_id = market_id; + max_pool_id = max_pool_id.max(pool_id); + } + let next_pool_count_id = if let Ok(id) = max_pool_id.checked_add_res(&1u8.into()) { + id + } else { + log::error!("MigratePoolStorageItems: Pool id overflow"); + return total_weight; + }; + let mut translated = 0u64; + Pools::::translate::, _>(|market_id, pool| { + translated.saturating_inc(); + let pool_id = market_id; + MarketIdToPoolId::::insert(pool_id, market_id); + let assets = if let Ok(market) = T::MarketCommons::market(&market_id) { + market.outcome_assets().try_into().ok()? + } else { + log::error!( + "MigratePoolStorageItems: Market {:?} not found. This should not happen, \ + because it is checked above.", + market_id + ); + pool.reserves.keys().cloned().collect::>().try_into().ok()? + }; + Some(Pool { + account_id: pool.account_id, + assets, + reserves: pool.reserves, + collateral: pool.collateral, + liquidity_parameter: pool.liquidity_parameter, + liquidity_shares_manager: pool.liquidity_shares_manager, + swap_fee: pool.swap_fee, + pool_type: PoolType::Standard(market_id), + }) + }); + PoolCount::::set(next_pool_count_id); + // Write for the PoolCount storage item + total_weight = total_weight.saturating_add(T::DbWeight::get().writes(1)); + log::info!("MigratePoolStorageItems: Upgraded {} pools.", translated); + // Reads and writes for the Pools storage item + total_weight = + total_weight.saturating_add(T::DbWeight::get().reads_writes(translated, translated)); + // Read for the market and write for the MarketIdToPoolId storage item + total_weight = + total_weight.saturating_add(T::DbWeight::get().reads_writes(translated, translated)); + + // remove pools that contain a corrupted campaign asset from the reverted asset system overhaul + let mut corrupted_pools = vec![]; + for &market_id in RemovableMarketIds::get().iter() { + let market_id = market_id.saturated_into::>(); + total_weight = total_weight.saturating_add(T::DbWeight::get().reads(2)); + let is_corrupted = + || Pools::::contains_key(market_id) && Pools::::get(market_id).is_none(); + if is_corrupted() { + total_weight = total_weight.saturating_add(T::DbWeight::get().writes(1)); + Pools::::remove(market_id); + corrupted_pools.push(market_id); + } else { + log::warn!( + "RemoveMarkets: Pool with market id {:?} was expected to be corrupted, but \ + isn't.", + market_id + ); + } + } + log::info!("RemovePools: Removed pools with market ids: {:?}.", corrupted_pools); + StorageVersion::new(NEO_SWAPS_NEXT_STORAGE_VERSION).put::>(); + total_weight = total_weight.saturating_add(T::DbWeight::get().writes(1)); + log::info!("MigratePoolStorageItems: Done!"); + total_weight + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, DispatchError> { + let old_pools = + storage_key_iter::, OldPoolOf, Twox64Concat>(NEO_SWAPS, POOLS) + .collect::>(); + Ok(old_pools.encode()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(previous_state: Vec) -> Result<(), DispatchError> { + let old_pools: BTreeMap, OldPoolOf> = + Decode::decode(&mut &previous_state[..]) + .map_err(|_| "Failed to decode state: Invalid state")?; + let new_pool_count = Pools::::iter().count(); + assert_eq!(old_pools.len(), new_pool_count); + let mut max_pool_id: T::PoolId = Default::default(); + for (market_id, new_pool) in Pools::::iter() { + let old_pool = + old_pools.get(&market_id).expect(&format!("Pool {:?} not found", market_id)[..]); + max_pool_id = max_pool_id.max(market_id); + assert_eq!(new_pool.account_id, old_pool.account_id); + let market = T::MarketCommons::market(&market_id)?; + let outcome_assets = market.outcome_assets(); + for asset in &outcome_assets { + assert!(new_pool.assets.contains(asset)); + } + assert_eq!(new_pool.assets.len(), outcome_assets.len()); + assert_eq!(new_pool.reserves, old_pool.reserves); + assert_eq!(new_pool.collateral, old_pool.collateral); + assert_eq!(new_pool.liquidity_parameter, old_pool.liquidity_parameter); + assert_eq!(new_pool.liquidity_shares_manager, old_pool.liquidity_shares_manager); + assert_eq!(new_pool.swap_fee, old_pool.swap_fee); + assert_eq!(new_pool.pool_type, PoolType::Standard(market_id)); + + assert_eq!( + MarketIdToPoolId::::get(market_id).expect("MarketIdToPoolId mapping not found"), + market_id + ); + } + let next_pool_count_id = PoolCount::::get(); + assert_eq!(next_pool_count_id, max_pool_id.checked_add_res(&1u8.into())?); + log::info!( + "MigratePoolStorageItems: Post-upgrade next pool count id is {:?}!", + next_pool_count_id + ); + for &market_id in RemovableMarketIds::get().iter() { + let market_id = market_id.saturated_into::>(); + assert!(!Pools::::contains_key(market_id)); + assert!(Pools::::try_get(market_id).is_err()); + } + log::info!("MigratePoolStorageItems: Post-upgrade pool count is {}!", new_pool_count); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + liquidity_tree::types::LiquidityTree, + mock::{ExtBuilder, MarketCommons, Runtime, ALICE, BOB}, + MarketIdOf, PoolOf, Pools, + }; + use alloc::collections::BTreeMap; + use core::fmt::Debug; + use frame_support::{migration::put_storage_value, StorageHasher, Twox64Concat}; + use parity_scale_codec::Encode; + use sp_io::storage::root as storage_root; + use sp_runtime::{Perbill, StateVersion}; + use zeitgeist_primitives::types::{ + Asset, Market, MarketCreation, MarketPeriod, MarketStatus, MarketType, ScoringRule, + }; + + struct RemovableMarketIds; + impl Get> for RemovableMarketIds { + fn get() -> Vec { + vec![] + } + } + + #[test] + fn on_runtime_upgrade_increments_the_storage_version() { + ExtBuilder::default().build().execute_with(|| { + set_up_version(); + MigratePoolStorageItems::::on_runtime_upgrade(); + assert_eq!(StorageVersion::get::>(), NEO_SWAPS_NEXT_STORAGE_VERSION); + }); + } + + #[test] + fn on_runtime_upgrade_is_noop_if_versions_are_not_correct() { + ExtBuilder::default().build().execute_with(|| { + StorageVersion::new(NEO_SWAPS_NEXT_STORAGE_VERSION).put::>(); + let (_, new_pools) = construct_old_new_tuple(); + populate_test_data::, PoolOf>( + NEO_SWAPS, POOLS, new_pools, + ); + let tmp = storage_root(StateVersion::V1); + MigratePoolStorageItems::::on_runtime_upgrade(); + assert_eq!(tmp, storage_root(StateVersion::V1)); + }); + } + + #[test] + fn on_runtime_upgrade_correctly_updates_pool_storages() { + ExtBuilder::default().build().execute_with(|| { + set_up_version(); + create_markets(3); + let (old_pools, new_pools) = construct_old_new_tuple(); + populate_test_data::, OldPoolOf>( + NEO_SWAPS, POOLS, old_pools, + ); + MigratePoolStorageItems::::on_runtime_upgrade(); + let actual = Pools::get(0u128).unwrap(); + assert_eq!(actual, new_pools[0]); + let next_pool_count_id = PoolCount::::get(); + assert_eq!(next_pool_count_id, 3u128); + assert_eq!(MarketIdToPoolId::::get(0u128).unwrap(), 0u128); + assert_eq!(MarketIdToPoolId::::get(1u128).unwrap(), 1u128); + assert_eq!(MarketIdToPoolId::::get(2u128).unwrap(), 2u128); + assert!(MarketIdToPoolId::::get(3u128).is_none()); + assert!(MarketIdToPoolId::::iter_keys().count() == 3); + }); + } + + fn set_up_version() { + StorageVersion::new(NEO_SWAPS_REQUIRED_STORAGE_VERSION).put::>(); + } + + fn create_markets(count: u8) { + for _ in 0..count { + let base_asset = Asset::Ztg; + let market = Market { + market_id: 0u8.into(), + base_asset, + creation: MarketCreation::Permissionless, + creator_fee: Perbill::zero(), + creator: ALICE, + oracle: BOB, + metadata: vec![0, 50], + market_type: MarketType::Categorical(3), + period: MarketPeriod::Block(0u32.into()..1u32.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, + }; + MarketCommons::push_market(market).unwrap(); + } + } + + fn construct_old_new_tuple() -> (Vec>, Vec>) { + let account_id = 1; + let mut reserves = BTreeMap::new(); + let asset_0 = Asset::CategoricalOutcome(0, 0); + let asset_1 = Asset::CategoricalOutcome(0, 1); + let asset_2 = Asset::CategoricalOutcome(0, 2); + reserves.insert(asset_0, 4); + reserves.insert(asset_1, 5); + reserves.insert(asset_2, 6); + let reserves: BoundedBTreeMap, BalanceOf, MaxAssets> = + reserves.clone().try_into().unwrap(); + let collateral = Asset::Ztg; + let liquidity_parameter = 5; + let swap_fee = 6; + let total_shares = 7; + let fees = 8; + + let mut liquidity_shares_manager = LiquidityTree::new(account_id, total_shares).unwrap(); + liquidity_shares_manager.nodes.get_mut(0).unwrap().fees = fees; + + let old_pool = OldPoolOf { + account_id, + reserves: reserves.clone(), + collateral, + liquidity_parameter, + liquidity_shares_manager: liquidity_shares_manager.clone(), + swap_fee, + }; + let new_pool = Pool { + account_id, + assets: vec![asset_0, asset_1, asset_2].try_into().unwrap(), + reserves, + collateral, + liquidity_parameter, + liquidity_shares_manager, + swap_fee, + pool_type: PoolType::Standard(0), + }; + ( + vec![old_pool.clone(), old_pool.clone(), old_pool.clone()], + vec![new_pool.clone(), new_pool.clone(), new_pool.clone()], + ) + } + + #[allow(unused)] + fn populate_test_data(pallet: &[u8], prefix: &[u8], data: Vec) + where + H: StorageHasher, + K: TryFrom + Encode, + V: Encode + Clone, + >::Error: Debug, + { + for (key, value) in data.iter().enumerate() { + let storage_hash = utility::key_to_hash::(K::try_from(key).unwrap()); + put_storage_value::(pallet, prefix, &storage_hash, (*value).clone()); + } + } +} + +mod utility { + use alloc::vec::Vec; + use frame_support::StorageHasher; + use parity_scale_codec::Encode; + + #[allow(unused)] + pub fn key_to_hash(key: K) -> Vec + where + H: StorageHasher, + K: Encode, + { + key.using_encoded(H::hash).as_ref().to_vec() + } +} From d70e33ae45dac3393a6a50c8258a973a467121d5 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Thu, 30 Jan 2025 14:49:58 +0100 Subject: [PATCH 49/73] Audit fixes (#1395) * (Issue No 1) - Clarify `ComboMath` documentation - Make test `combo_buy_fails_on_amount_out_below_min` more precise * Fix `log_ceil` and add extensive tests * (Issue No 7) Enable overflow checks * (Issue No 9) Fix incorrect test * (Issue No 10) Resolve FIXMEs - Won't fix FIXME asking for better error messages - Solve FIXME in neo-swaps by adding `try_mutate_exists` to `PoolStorage` * Fix formatting * (Issue No 13) Remove TODO by avoiding a migration * (Issue No 13) Remove TODO by keeping low-level types * (Issue No 13) Remove TODO by keeping low-level types * (Issue No 13) Remove already fixed TODO * (Issue No 13) Remove won't fix TODOs * (Issue No 13) Remove more TODOs, some by implementing `CheckedIncRes` * (Issue No 13) Remove code quality TODO * (Issue No 14) Define `SCHEDULE_PRIORITY` * (Issue No 14) Increase readability * (Issue No 14) Reuse `r_over_b` * (Issue No 14) Add clarifying comments * (Issue No 14) Abstract common math code away * Add missing file * (Issue No 2) Replace `force_max_work` with `fuel` parameter * (Issue 6) Replace `SubmitOrigin` with root * (Issue Nos. 5 & 6) Squashed commit of the following: commit 45175043923cee5f1bdebd93a4502e664f0d8cc4 Author: Malte Kliemann Date: Tue Dec 10 18:51:24 2024 +0100 Benchmark `DecisionMarketOracle` commit 5832f0bfcf136e22e812d412df9295969efa2789 Author: Malte Kliemann Date: Tue Dec 10 16:55:48 2024 +0100 Implement `DecisionMarketOracle` using scoreboard commit e684d473b824bbda3e6ec0316d5db9a265f1737c Author: Malte Kliemann Date: Mon Dec 9 21:24:47 2024 +0100 Add `BlockNumber` parameter to `update` commit 5f513782fe9c4eee41075811e0412d3f4bbbf294 Author: Malte Kliemann Date: Mon Dec 9 21:17:20 2024 +0100 Update oracles on each block commit 2b831fbf02b43ef0ee33fa482b778348533823f2 Author: Malte Kliemann Date: Mon Dec 9 20:40:26 2024 +0100 Implement `try_mutate_all` commit 59cb18524c8a78ddbcf79d92d0fe39d1f659fa6d Author: Malte Kliemann Date: Mon Dec 9 18:24:56 2024 +0100 Use `ProposalStorage` and `MaxProposals` commit 58afe87b6021aae3688d5ea2336a214eda50bdb5 Author: Malte Kliemann Date: Mon Dec 9 17:56:53 2024 +0100 Add `MaxProposals` value commit 22068d8578b99f018eb54ee450e9e75141c69ae7 Author: Malte Kliemann Date: Mon Dec 9 14:15:33 2024 +0100 Add `update` function to `FutarchyOracle` trait * Fix audit decompressor security miscellaneous comments (#1402) * Update arkworks-rs dependencies * Extend get_collection_id comment * Distinguish collection id generation error * Use immutable transformations * fmt * Update copyrights * Use retrieval instead of generation for collection id error * Fix misprint * fix clippy * Merge oracles into audit fixes (#1399) * Add `update` function to `FutarchyOracle` trait * Add `MaxProposals` value * Use `ProposalStorage` and `MaxProposals` * Implement `try_mutate_all` * Update oracles on each block * Add `BlockNumber` parameter to `update` * Implement `DecisionMarketOracle` using scoreboard * Benchmark `DecisionMarketOracle` --------- Co-authored-by: Chralt --- Cargo.lock | 338 ++++++++++++++---- Cargo.toml | 24 +- primitives/src/asset.rs | 3 +- primitives/src/math/checked_ops_res.rs | 17 + primitives/src/traits.rs | 2 + .../src/traits/combinatorial_tokens_api.rs | 8 +- .../src/traits/combinatorial_tokens_fuel.rs | 10 + primitives/src/traits/futarchy_oracle.rs | 5 + runtime/battery-station/src/parameters.rs | 4 +- runtime/common/src/lib.rs | 12 +- runtime/zeitgeist/src/parameters.rs | 4 +- zrml/combinatorial-tokens/src/benchmarking.rs | 68 ++-- zrml/combinatorial-tokens/src/lib.rs | 174 +++++---- zrml/combinatorial-tokens/src/mock/runtime.rs | 7 +- .../src/tests/integration.rs | 44 +-- .../src/tests/merge_position.rs | 18 +- zrml/combinatorial-tokens/src/tests/mod.rs | 1 + .../src/tests/redeem_position.rs | 46 ++- .../src/tests/split_position.rs | 28 +- .../src/traits/combinatorial_id_manager.rs | 14 +- .../src/types/collection_id_error.rs | 35 ++ .../decompressor/mod.rs | 82 +++-- .../tests/decompress_collection_id.rs | 4 +- .../decompressor/tests/decompress_hash.rs | 5 +- .../decompressor/tests/get_collection_id.rs | 8 +- .../src/types/cryptographic_id_manager/mod.rs | 50 ++- zrml/combinatorial-tokens/src/types/mod.rs | 6 +- zrml/combinatorial-tokens/src/weights.rs | 132 ++++--- zrml/futarchy/src/dispatchable_impls.rs | 8 +- zrml/futarchy/src/lib.rs | 70 ++-- zrml/futarchy/src/mock/runtime.rs | 5 +- zrml/futarchy/src/mock/types/oracle.rs | 9 +- zrml/futarchy/src/pallet_impls.rs | 6 +- zrml/futarchy/src/proposal_storage.rs | 88 +++++ zrml/futarchy/src/tests/mod.rs | 4 +- zrml/futarchy/src/tests/submit_proposal.rs | 6 +- zrml/futarchy/src/traits/mod.rs | 4 + zrml/futarchy/src/traits/proposal_storage.rs | 28 ++ zrml/futarchy/src/types/proposal.rs | 1 - zrml/hybrid-router/src/mock.rs | 3 +- zrml/neo-swaps/src/benchmarking.rs | 59 ++- zrml/neo-swaps/src/lib.rs | 53 +-- .../src/math/traits/combo_math_ops.rs | 8 + zrml/neo-swaps/src/math/types/combo_math.rs | 39 +- zrml/neo-swaps/src/math/types/common.rs | 51 +++ zrml/neo-swaps/src/math/types/math.rs | 42 +-- zrml/neo-swaps/src/math/types/mod.rs | 1 + zrml/neo-swaps/src/mock.rs | 3 +- zrml/neo-swaps/src/pool_storage.rs | 25 +- zrml/neo-swaps/src/tests/combo_buy.rs | 30 +- .../src/tests/deploy_combinatorial_pool.rs | 26 +- zrml/neo-swaps/src/tests/mod.rs | 3 +- zrml/neo-swaps/src/traits/pool_storage.rs | 9 + .../types/decision_market_benchmark_helper.rs | 11 +- .../src/types/decision_market_oracle.rs | 40 ++- .../decision_market_oracle_scoreboard.rs | 100 ++++++ zrml/neo-swaps/src/types/mod.rs | 2 + zrml/neo-swaps/src/utility.rs | 43 ++- zrml/neo-swaps/src/weights.rs | 226 +++++------- zrml/prediction-markets/src/lib.rs | 1 - 60 files changed, 1492 insertions(+), 661 deletions(-) create mode 100644 primitives/src/traits/combinatorial_tokens_fuel.rs create mode 100644 zrml/combinatorial-tokens/src/types/collection_id_error.rs create mode 100644 zrml/futarchy/src/proposal_storage.rs create mode 100644 zrml/futarchy/src/traits/proposal_storage.rs create mode 100644 zrml/neo-swaps/src/math/types/common.rs create mode 100644 zrml/neo-swaps/src/types/decision_market_oracle_scoreboard.rs diff --git a/Cargo.lock b/Cargo.lock index 45d4359cc..2d6b3d84d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -232,21 +232,22 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c775f0d12169cba7aae4caeb547bb6a50781c7449a8aa53793827c9ec4abf488" dependencies = [ - "ark-ec", - "ark-ff", - "ark-serialize", - "ark-std", + "ark-ec 0.4.2", + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", ] [[package]] name = "ark-bn254" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a22f4561524cd949590d78d7d4c5df8f592430d221f7f3c9497bbafd8972120f" +checksum = "d69eab57e8d2663efa5c63135b2af4f396d66424f88954c21104125ab6b3e6bc" dependencies = [ - "ark-ec", - "ark-ff", - "ark-std", + "ark-ec 0.5.0", + "ark-ff 0.5.0", + "ark-r1cs-std", + "ark-std 0.5.0", ] [[package]] @@ -255,10 +256,10 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba" dependencies = [ - "ark-ff", - "ark-poly", - "ark-serialize", - "ark-std", + "ark-ff 0.4.2", + "ark-poly 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", "derivative", "hashbrown 0.13.2", "itertools 0.10.5", @@ -266,6 +267,27 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ark-ec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d68f2d516162846c1238e755a7c4d131b892b70cc70c471a8e3ca3ed818fce" +dependencies = [ + "ahash 0.8.11", + "ark-ff 0.5.0", + "ark-poly 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "educe", + "fnv", + "hashbrown 0.15.2", + "itertools 0.13.0", + "num-bigint", + "num-integer", + "num-traits", + "zeroize", +] + [[package]] name = "ark-ed-on-bls12-381-bandersnatch" version = "0.4.0" @@ -273,9 +295,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9cde0f2aa063a2a5c28d39b47761aa102bda7c13c84fc118a61b87c7b2f785c" dependencies = [ "ark-bls12-381", - "ark-ec", - "ark-ff", - "ark-std", + "ark-ec 0.4.2", + "ark-ff 0.4.2", + "ark-std 0.4.0", ] [[package]] @@ -284,10 +306,10 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" dependencies = [ - "ark-ff-asm", - "ark-ff-macros", - "ark-serialize", - "ark-std", + "ark-ff-asm 0.4.2", + "ark-ff-macros 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", "derivative", "digest 0.10.7", "itertools 0.10.5", @@ -298,6 +320,26 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ark-ff" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a177aba0ed1e0fbb62aa9f6d0502e9b46dad8c2eab04c14258a1212d2557ea70" +dependencies = [ + "ark-ff-asm 0.5.0", + "ark-ff-macros 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "arrayvec 0.7.4", + "digest 0.10.7", + "educe", + "itertools 0.13.0", + "num-bigint", + "num-traits", + "paste", + "zeroize", +] + [[package]] name = "ark-ff-asm" version = "0.4.2" @@ -308,6 +350,16 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ark-ff-asm" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" +dependencies = [ + "quote", + "syn 2.0.79", +] + [[package]] name = "ark-ff-macros" version = "0.4.2" @@ -321,29 +373,86 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ark-ff-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09be120733ee33f7693ceaa202ca41accd5653b779563608f1234f78ae07c4b3" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 2.0.79", +] + [[package]] name = "ark-poly" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf" dependencies = [ - "ark-ff", - "ark-serialize", - "ark-std", + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", "derivative", "hashbrown 0.13.2", ] +[[package]] +name = "ark-poly" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579305839da207f02b89cd1679e50e67b4331e2f9294a57693e5051b7703fe27" +dependencies = [ + "ahash 0.8.11", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "educe", + "fnv", + "hashbrown 0.15.2", +] + +[[package]] +name = "ark-r1cs-std" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "941551ef1df4c7a401de7068758db6503598e6f01850bdb2cfdb614a1f9dbea1" +dependencies = [ + "ark-ec 0.5.0", + "ark-ff 0.5.0", + "ark-relations", + "ark-std 0.5.0", + "educe", + "num-bigint", + "num-integer", + "num-traits", + "tracing", +] + +[[package]] +name = "ark-relations" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec46ddc93e7af44bcab5230937635b06fb5744464dd6a7e7b083e80ebd274384" +dependencies = [ + "ark-ff 0.5.0", + "ark-std 0.5.0", + "tracing", + "tracing-subscriber", +] + [[package]] name = "ark-scale" version = "0.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49b08346a3e38e2be792ef53ee168623c9244d968ff00cd70fb9932f6fe36393" dependencies = [ - "ark-ec", - "ark-ff", - "ark-serialize", - "ark-std", + "ark-ec 0.4.2", + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", "parity-scale-codec", ] @@ -353,10 +462,10 @@ version = "0.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f69c00b3b529be29528a6f2fd5fa7b1790f8bed81b9cdca17e326538545a179" dependencies = [ - "ark-ec", - "ark-ff", - "ark-serialize", - "ark-std", + "ark-ec 0.4.2", + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", "parity-scale-codec", "scale-info", ] @@ -366,10 +475,10 @@ name = "ark-secret-scalar" version = "0.0.2" source = "git+https://github.com/w3f/ring-vrf?rev=3119f51#3119f51b54b69308abfb0671f6176cb125ae1bf1" dependencies = [ - "ark-ec", - "ark-ff", - "ark-serialize", - "ark-std", + "ark-ec 0.4.2", + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", "ark-transcript", "digest 0.10.7", "rand_core 0.6.4", @@ -382,8 +491,21 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" dependencies = [ - "ark-serialize-derive", - "ark-std", + "ark-serialize-derive 0.4.2", + "ark-std 0.4.0", + "digest 0.10.7", + "num-bigint", +] + +[[package]] +name = "ark-serialize" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7" +dependencies = [ + "ark-serialize-derive 0.5.0", + "ark-std 0.5.0", + "arrayvec 0.7.4", "digest 0.10.7", "num-bigint", ] @@ -399,6 +521,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ark-serialize-derive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + [[package]] name = "ark-std" version = "0.4.0" @@ -409,14 +542,24 @@ dependencies = [ "rand 0.8.5", ] +[[package]] +name = "ark-std" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + [[package]] name = "ark-transcript" version = "0.0.2" source = "git+https://github.com/w3f/ring-vrf?rev=3119f51#3119f51b54b69308abfb0671f6176cb125ae1bf1" dependencies = [ - "ark-ff", - "ark-serialize", - "ark-std", + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", "digest 0.10.7", "rand_core 0.6.4", "sha3", @@ -751,12 +894,12 @@ version = "0.0.1" source = "git+https://github.com/w3f/ring-vrf?rev=3119f51#3119f51b54b69308abfb0671f6176cb125ae1bf1" dependencies = [ "ark-bls12-381", - "ark-ec", + "ark-ec 0.4.2", "ark-ed-on-bls12-381-bandersnatch", - "ark-ff", + "ark-ff 0.4.2", "ark-scale 0.0.12", - "ark-serialize", - "ark-std", + "ark-serialize 0.4.2", + "ark-std 0.4.0", "dleq_vrf", "fflonk", "merlin 3.0.0", @@ -1441,11 +1584,11 @@ name = "common" version = "0.1.0" source = "git+https://github.com/w3f/ring-proof?rev=0e948f3#0e948f3c28cbacecdd3020403c4841c0eb339213" dependencies = [ - "ark-ec", - "ark-ff", - "ark-poly", - "ark-serialize", - "ark-std", + "ark-ec 0.4.2", + "ark-ff 0.4.2", + "ark-poly 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", "fflonk", "merlin 3.0.0", ] @@ -2599,12 +2742,12 @@ name = "dleq_vrf" version = "0.0.2" source = "git+https://github.com/w3f/ring-vrf?rev=3119f51#3119f51b54b69308abfb0671f6176cb125ae1bf1" dependencies = [ - "ark-ec", - "ark-ff", + "ark-ec 0.4.2", + "ark-ff 0.4.2", "ark-scale 0.0.10", "ark-secret-scalar", - "ark-serialize", - "ark-std", + "ark-serialize 0.4.2", + "ark-std 0.4.0", "ark-transcript", "arrayvec 0.7.4", "rand_core 0.6.4", @@ -2751,6 +2894,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "educe" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn 2.0.79", +] + [[package]] name = "either" version = "1.11.0" @@ -2794,6 +2949,26 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "enum-ordinalize" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea0dcfa4e54eeb516fe454635a95753ddd39acda650ce703031c6973e315dd5" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + [[package]] name = "enumflags2" version = "0.7.9" @@ -3055,11 +3230,11 @@ name = "fflonk" version = "0.1.0" source = "git+https://github.com/w3f/fflonk#1e854f35e9a65d08b11a86291405cdc95baa0a35" dependencies = [ - "ark-ec", - "ark-ff", - "ark-poly", - "ark-serialize", - "ark-std", + "ark-ec 0.4.2", + "ark-ff 0.4.2", + "ark-poly 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", "merlin 3.0.0", ] @@ -3862,6 +4037,15 @@ dependencies = [ "serde", ] +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "allocator-api2", +] + [[package]] name = "heck" version = "0.4.1" @@ -4083,7 +4267,7 @@ dependencies = [ "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows-core 0.52.0", + "windows-core", ] [[package]] @@ -4339,6 +4523,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -4623,7 +4816,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" dependencies = [ "cfg-if", - "windows-targets 0.52.5", + "windows-targets 0.48.5", ] [[package]] @@ -9644,11 +9837,11 @@ name = "ring" version = "0.1.0" source = "git+https://github.com/w3f/ring-proof?rev=0e948f3#0e948f3c28cbacecdd3020403c4841c0eb339213" dependencies = [ - "ark-ec", - "ark-ff", - "ark-poly", - "ark-serialize", - "ark-std", + "ark-ec 0.4.2", + "ark-ff 0.4.2", + "ark-poly 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", "common", "fflonk", "merlin 3.0.0", @@ -13753,7 +13946,7 @@ checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" dependencies = [ "cfg-if", "digest 0.10.7", - "rand 0.8.5", + "rand 0.7.3", "static_assertions", ] @@ -14532,7 +14725,7 @@ version = "0.51.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca229916c5ee38c2f2bc1e9d8f04df975b4bd93f9955dc69fabb5d91270045c9" dependencies = [ - "windows-core 0.51.1", + "windows-core", "windows-targets 0.48.5", ] @@ -14545,15 +14738,6 @@ dependencies = [ "windows-targets 0.48.5", ] -[[package]] -name = "windows-core" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" -dependencies = [ - "windows-targets 0.52.5", -] - [[package]] name = "windows-sys" version = "0.45.0" @@ -15194,7 +15378,7 @@ name = "zrml-combinatorial-tokens" version = "0.5.5" dependencies = [ "ark-bn254", - "ark-ff", + "ark-ff 0.5.0", "env_logger 0.10.2", "frame-benchmarking", "frame-support", diff --git a/Cargo.toml b/Cargo.toml index eac6a6327..e7b24ef66 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -276,8 +276,8 @@ url = "2.5.0" # Other (wasm) arbitrary = { version = "1.3.2", default-features = false } -ark-bn254 = { version = "0.4.0", default-features = false, features = ["curve"] } -ark-ff = { version = "0.4.0", default-features = false } +ark-bn254 = { version = "0.5.0", default-features = false, features = ["curve"] } +ark-ff = { version = "0.5.0", default-features = false } arrayvec = { version = "0.7.4", default-features = false } cfg-if = { version = "1.0.0" } fixed = { version = "=1.15.0", default-features = false, features = ["num-traits"] } @@ -291,6 +291,15 @@ rand_chacha = { version = "0.3.1", default-features = false } serde = { version = "1.0.198", default-features = false } typenum = { version = "1.17.0", default-features = false } +[profile.test] +overflow-checks = true + +[profile.test.package."*"] +overflow-checks = true + +[profile.dev] +overflow-checks = true + [profile.dev.package] blake2 = { opt-level = 3 } blake2b_simd = { opt-level = 3 } @@ -336,17 +345,28 @@ x25519-dalek = { opt-level = 3 } yamux = { opt-level = 3 } zeroize = { opt-level = 3 } +[profile.dev.package."*"] +overflow-checks = true + [profile.production] codegen-units = 1 incremental = false inherits = "release" lto = true +overflow-checks = true + +[profile.production.package."*"] +overflow-checks = true [profile.release] opt-level = 3 +overflow-checks = true # Zeitgeist runtime requires unwinding. panic = "unwind" +[profile.release.package."*"] +overflow-checks = true + # xcm-emulator incompatible block number type fixed # Commits: diff --git a/primitives/src/asset.rs b/primitives/src/asset.rs index d3d87223a..df684894e 100644 --- a/primitives/src/asset.rs +++ b/primitives/src/asset.rs @@ -48,12 +48,13 @@ use serde::{Deserialize, Serialize}; pub enum Asset { CategoricalOutcome(MarketId, CategoryIndex), ScalarOutcome(MarketId, ScalarPosition), - CombinatorialToken(CombinatorialId), + CombinatorialOutcomeLegacy, // Here to avoid having to migrate all holdings on the chain. PoolShare(PoolId), #[default] Ztg, ForeignAsset(u32), ParimutuelShare(MarketId, CategoryIndex), + CombinatorialToken(CombinatorialId), } #[cfg(feature = "runtime-benchmarks")] diff --git a/primitives/src/math/checked_ops_res.rs b/primitives/src/math/checked_ops_res.rs index 13cd420a6..38e971031 100644 --- a/primitives/src/math/checked_ops_res.rs +++ b/primitives/src/math/checked_ops_res.rs @@ -64,6 +64,13 @@ where fn checked_rem_res(&self, other: &Self) -> Result; } +pub trait CheckedIncRes +where + Self: Sized, +{ + fn checked_inc_res(&self) -> Result; +} + impl CheckedAddRes for T where T: CheckedAdd, @@ -123,3 +130,13 @@ where self.checked_rem(other).ok_or(DispatchError::Arithmetic(ArithmeticError::DivisionByZero)) } } + +impl CheckedIncRes for T +where + T: CheckedAdd + From, +{ + #[inline] + fn checked_inc_res(&self) -> Result { + self.checked_add(&1u8.into()).ok_or(DispatchError::Arithmetic(ArithmeticError::Overflow)) + } +} diff --git a/primitives/src/traits.rs b/primitives/src/traits.rs index cd36b113e..d141610f1 100644 --- a/primitives/src/traits.rs +++ b/primitives/src/traits.rs @@ -18,6 +18,7 @@ mod combinatorial_tokens_api; mod combinatorial_tokens_benchmark_helper; +mod combinatorial_tokens_fuel; mod combinatorial_tokens_unsafe_api; mod complete_set_operations_api; mod deploy_pool_api; @@ -36,6 +37,7 @@ mod zeitgeist_asset; pub use combinatorial_tokens_api::*; pub use combinatorial_tokens_benchmark_helper::*; +pub use combinatorial_tokens_fuel::*; pub use combinatorial_tokens_unsafe_api::*; pub use complete_set_operations_api::*; pub use deploy_pool_api::*; diff --git a/primitives/src/traits/combinatorial_tokens_api.rs b/primitives/src/traits/combinatorial_tokens_api.rs index 33230d703..c9953feb4 100644 --- a/primitives/src/traits/combinatorial_tokens_api.rs +++ b/primitives/src/traits/combinatorial_tokens_api.rs @@ -15,8 +15,11 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . -use crate::types::SplitPositionDispatchInfo; +use crate::{traits::CombinatorialTokensFuel, types::SplitPositionDispatchInfo}; use alloc::vec::Vec; +use core::fmt::Debug; +use parity_scale_codec::{Decode, Encode}; +use scale_info::TypeInfo; use sp_runtime::DispatchError; pub trait CombinatorialTokensApi { @@ -24,6 +27,7 @@ pub trait CombinatorialTokensApi { type Balance; type CombinatorialId; type MarketId; + type Fuel: Clone + CombinatorialTokensFuel + Debug + Decode + Encode + Eq + TypeInfo; fn split_position( who: Self::AccountId, @@ -31,6 +35,6 @@ pub trait CombinatorialTokensApi { market_id: Self::MarketId, partition: Vec>, amount: Self::Balance, - force_max_work: bool, + force_max_work: Self::Fuel, ) -> Result, DispatchError>; } diff --git a/primitives/src/traits/combinatorial_tokens_fuel.rs b/primitives/src/traits/combinatorial_tokens_fuel.rs new file mode 100644 index 000000000..8fc952a23 --- /dev/null +++ b/primitives/src/traits/combinatorial_tokens_fuel.rs @@ -0,0 +1,10 @@ +/// A trait for keeping track of a certain amount of work to be done. +pub trait CombinatorialTokensFuel { + /// Creates a `Fuel` object from a `total` value which indicates the total amount of work to be + /// done. This is usually done for benchmarking purposes. + fn from_total(total: u32) -> Self; + + /// Returns a `u32` which indicates the total amount of work to be done. Must be `O(1)` to avoid + /// excessive calculation if this call is used when calculating extrinsic weight. + fn total(&self) -> u32; +} diff --git a/primitives/src/traits/futarchy_oracle.rs b/primitives/src/traits/futarchy_oracle.rs index 0a4530a6d..37b5b3b17 100644 --- a/primitives/src/traits/futarchy_oracle.rs +++ b/primitives/src/traits/futarchy_oracle.rs @@ -18,7 +18,12 @@ use frame_support::pallet_prelude::Weight; pub trait FutarchyOracle { + type BlockNumber; + /// Evaluates the query at the current block and returns the weight consumed and a `bool` /// indicating whether the query evaluated positively. fn evaluate(&self) -> (Weight, bool); + + /// Updates the oracle's data and returns the weight consumed. + fn update(&mut self, now: Self::BlockNumber) -> Weight; } diff --git a/runtime/battery-station/src/parameters.rs b/runtime/battery-station/src/parameters.rs index 3c1757791..cf2d83d1d 100644 --- a/runtime/battery-station/src/parameters.rs +++ b/runtime/battery-station/src/parameters.rs @@ -165,9 +165,10 @@ parameter_types! { /// can lead to extrinsic with very big weight: see delegate for instance. pub const MaxVotes: u32 = 100; /// The maximum number of public proposals that can exist at any time. - pub const MaxProposals: u32 = 100; + pub const DemocracyMaxProposals: u32 = 100; // Futarchy + pub const FutarchyMaxProposals: u32 = 4; pub const MinDuration: BlockNumber = 7 * BLOCKS_PER_DAY; // Hybrid Router parameters @@ -456,6 +457,7 @@ parameter_type_with_key! { match currency_id { Asset::CategoricalOutcome(_,_) => ExistentialDeposit::get(), Asset::CombinatorialToken(_) => ExistentialDeposit::get(), + Asset::CombinatorialOutcomeLegacy => ExistentialDeposit::get(), Asset::PoolShare(_) => ExistentialDeposit::get(), Asset::ScalarOutcome(_,_) => ExistentialDeposit::get(), #[cfg(feature = "parachain")] diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs index 8d3426fce..893035caa 100644 --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -84,7 +84,7 @@ macro_rules! decl_common_types { generic, DispatchError, DispatchResult, RuntimeDebug, SaturatedConversion, }; use zeitgeist_primitives::traits::{DeployPoolApi, DistributeFees, MarketCommonsPalletApi}; - use zrml_combinatorial_tokens::types::CryptographicIdManager; + use zrml_combinatorial_tokens::types::{CryptographicIdManager, Fuel}; use zrml_neo_swaps::types::DecisionMarketOracle; #[cfg(feature = "try-runtime")] @@ -817,7 +817,7 @@ macro_rules! impl_config_traits { type PalletsOrigin = OriginCaller; type MaxVotes = MaxVotes; type WeightInfo = weights::pallet_democracy::WeightInfo; - type MaxProposals = MaxProposals; + type MaxProposals = DemocracyMaxProposals; type Preimages = Preimage; type MaxBlacklisted = ConstU32<100>; type MaxDeposits = ConstU32<100>; @@ -1162,6 +1162,7 @@ macro_rules! impl_config_traits { #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = PredictionMarketsCombinatorialTokensBenchmarkHelper; type CombinatorialIdManager = CryptographicIdManager; + type Fuel = Fuel; type MarketCommons = MarketCommons; type MultiCurrency = AssetManager; type Payout = PredictionMarkets; @@ -1200,11 +1201,11 @@ macro_rules! impl_config_traits { impl zrml_futarchy::Config for Runtime { #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = DecisionMarketBenchmarkHelper; + type MaxProposals = FutarchyMaxProposals; type MinDuration = MinDuration; type Oracle = DecisionMarketOracle; type RuntimeEvent = RuntimeEvent; type Scheduler = Scheduler; - type SubmitOrigin = EnsureRoot; type WeightInfo = zrml_futarchy::weights::WeightInfo; } @@ -2288,7 +2289,7 @@ macro_rules! create_common_tests { }; use zrml_futarchy::types::Proposal; use zrml_market_commons::types::MarketBuilder; - use zrml_neo_swaps::types::DecisionMarketOracle; + use zrml_neo_swaps::types::{DecisionMarketOracle, DecisionMarketOracleScoreboard}; #[test] fn futarchy_schedules_and_executes_call() { @@ -2362,10 +2363,13 @@ macro_rules! create_common_tests { }; let call = Preimage::bound(RuntimeCall::from(remark_dispatched_as)).unwrap(); + let scoreboard = + DecisionMarketOracleScoreboard::new(40_000, 10_000, one / 7, one); let oracle = DecisionMarketOracle::new( market_id, Asset::CategoricalOutcome(market_id, 0), Asset::CategoricalOutcome(market_id, 1), + scoreboard, ); let when = duration + 10; let proposal = Proposal { when, call, oracle }; diff --git a/runtime/zeitgeist/src/parameters.rs b/runtime/zeitgeist/src/parameters.rs index 8f89dc422..0888ba680 100644 --- a/runtime/zeitgeist/src/parameters.rs +++ b/runtime/zeitgeist/src/parameters.rs @@ -165,9 +165,10 @@ parameter_types! { /// can lead to extrinsic with very big weight: see delegate for instance. pub const MaxVotes: u32 = 100; /// The maximum number of public proposals that can exist at any time. - pub const MaxProposals: u32 = 100; + pub const DemocracyMaxProposals: u32 = 100; // Futarchy + pub const FutarchyMaxProposals: u32 = 4; pub const MinDuration: BlockNumber = 7 * BLOCKS_PER_DAY; // Hybrid Router parameters @@ -456,6 +457,7 @@ parameter_type_with_key! { match currency_id { Asset::CategoricalOutcome(_,_) => ExistentialDeposit::get(), Asset::CombinatorialToken(_) => ExistentialDeposit::get(), + Asset::CombinatorialOutcomeLegacy => ExistentialDeposit::get(), Asset::PoolShare(_) => ExistentialDeposit::get(), Asset::ScalarOutcome(_,_) => ExistentialDeposit::get(), #[cfg(feature = "parachain")] diff --git a/zrml/combinatorial-tokens/src/benchmarking.rs b/zrml/combinatorial-tokens/src/benchmarking.rs index 901821fb1..4d2bf4093 100644 --- a/zrml/combinatorial-tokens/src/benchmarking.rs +++ b/zrml/combinatorial-tokens/src/benchmarking.rs @@ -26,7 +26,7 @@ use orml_traits::MultiCurrency; use sp_runtime::{traits::Zero, Perbill}; use zeitgeist_primitives::{ math::fixed::{BaseProvider, ZeitgeistBase}, - traits::{CombinatorialTokensBenchmarkHelper, MarketCommonsPalletApi}, + traits::{CombinatorialTokensBenchmarkHelper, CombinatorialTokensFuel, MarketCommonsPalletApi}, types::{Asset, Market, MarketCreation, MarketPeriod, MarketStatus, MarketType, ScoringRule}, }; @@ -65,10 +65,11 @@ mod benchmarks { use super::*; #[benchmark] - fn split_position_vertical_sans_parent(n: Linear<2, 32>) { + fn split_position_vertical_sans_parent(n: Linear<2, 32>, m: Linear<32, 64>) { let alice: T::AccountId = whitelisted_caller(); let position_count: usize = n.try_into().unwrap(); + let total = m; let parent_collection_id = None; let market_id = create_market::(alice.clone(), position_count.try_into().unwrap()); @@ -92,7 +93,7 @@ mod benchmarks { market_id, partition.clone(), amount, - true, + T::Fuel::from_total(total), ); let collection_ids: Vec<_> = partition @@ -103,7 +104,7 @@ mod benchmarks { parent_collection_id, market_id, index_set, - false, + T::Fuel::from_total(total), ) .unwrap() }) @@ -129,10 +130,11 @@ mod benchmarks { } #[benchmark] - fn split_position_vertical_with_parent(n: Linear<2, 32>) { + fn split_position_vertical_with_parent(n: Linear<2, 32>, m: Linear<32, 64>) { let alice: T::AccountId = whitelisted_caller(); let position_count: usize = n.try_into().unwrap(); + let total = m; let parent_collection_id = None; let parent_market_id = create_market::(alice.clone(), 2); @@ -142,7 +144,7 @@ mod benchmarks { parent_collection_id, parent_market_id, vec![false, true], - false, + T::Fuel::from_total(total), ) .unwrap(); let pos_01 = Pallet::::position_from_collection_id(parent_market_id, cid_01).unwrap(); @@ -167,7 +169,7 @@ mod benchmarks { child_market_id, partition.clone(), amount, - true, + T::Fuel::from_total(total), ); let collection_ids: Vec<_> = partition @@ -178,7 +180,7 @@ mod benchmarks { Some(cid_01), child_market_id, index_set, - false, + T::Fuel::from_total(total), ) .unwrap() }) @@ -204,11 +206,12 @@ mod benchmarks { } #[benchmark] - fn split_position_horizontal(n: Linear<2, 32>) { + fn split_position_horizontal(n: Linear<2, 32>, m: Linear<32, 64>) { let alice: T::AccountId = whitelisted_caller(); let position_count: usize = n.try_into().unwrap(); let asset_count = position_count + 1; + let total = m; let parent_collection_id = None; let market_id = create_market::(alice.clone(), asset_count.try_into().unwrap()); @@ -230,7 +233,7 @@ mod benchmarks { parent_collection_id, market_id, asset_in_index_set, - false, + T::Fuel::from_total(total), ) .unwrap(); T::MultiCurrency::deposit(asset_in, &alice, amount).unwrap(); @@ -242,7 +245,7 @@ mod benchmarks { market_id, partition.clone(), amount, - true, + T::Fuel::from_total(total), ); let collection_ids: Vec<_> = partition @@ -253,7 +256,7 @@ mod benchmarks { parent_collection_id, market_id, index_set, - false, + T::Fuel::from_total(total), ) .unwrap() }) @@ -279,10 +282,11 @@ mod benchmarks { } #[benchmark] - fn merge_position_vertical_sans_parent(n: Linear<2, 32>) { + fn merge_position_vertical_sans_parent(n: Linear<2, 32>, m: Linear<32, 64>) { let alice: T::AccountId = whitelisted_caller(); let position_count: usize = n.try_into().unwrap(); + let total = m; let parent_collection_id = None; let market_id = create_market::(alice.clone(), position_count.try_into().unwrap()); @@ -304,7 +308,7 @@ mod benchmarks { parent_collection_id, market_id, index_set, - false, + T::Fuel::from_total(total), ) .unwrap() }) @@ -322,7 +326,7 @@ mod benchmarks { market_id, partition.clone(), amount, - true, + T::Fuel::from_total(total), ); let expected_event = ::RuntimeEvent::from(Event::::TokenMerged { @@ -338,10 +342,11 @@ mod benchmarks { } #[benchmark] - fn merge_position_vertical_with_parent(n: Linear<2, 32>) { + fn merge_position_vertical_with_parent(n: Linear<2, 32>, m: Linear<32, 64>) { let alice: T::AccountId = whitelisted_caller(); let position_count: usize = n.try_into().unwrap(); + let total = m; let parent_collection_id = None; let parent_market_id = create_market::(alice.clone(), 2); @@ -351,7 +356,7 @@ mod benchmarks { parent_collection_id, parent_market_id, vec![false, true], - false, + T::Fuel::from_total(total), ) .unwrap(); let pos_01 = Pallet::::position_from_collection_id(parent_market_id, cid_01).unwrap(); @@ -375,7 +380,7 @@ mod benchmarks { Some(cid_01), child_market_id, index_set, - false, + T::Fuel::from_total(total), ) .unwrap() }) @@ -392,7 +397,7 @@ mod benchmarks { child_market_id, partition.clone(), amount, - true, + T::Fuel::from_total(total), ); let expected_event = ::RuntimeEvent::from(Event::::TokenMerged { @@ -408,11 +413,12 @@ mod benchmarks { } #[benchmark] - fn merge_position_horizontal(n: Linear<2, 32>) { + fn merge_position_horizontal(n: Linear<2, 32>, m: Linear<32, 64>) { let alice: T::AccountId = whitelisted_caller(); let position_count: usize = n.try_into().unwrap(); let asset_count = position_count + 1; + let total = m; let parent_collection_id = None; let market_id = create_market::(alice.clone(), asset_count.try_into().unwrap()); @@ -435,7 +441,7 @@ mod benchmarks { parent_collection_id, market_id, index_set, - false, + T::Fuel::from_total(total), ) .unwrap() }) @@ -452,7 +458,7 @@ mod benchmarks { market_id, partition.clone(), amount, - true, + T::Fuel::from_total(total), ); let mut asset_out_index_set = vec![true; asset_count]; @@ -461,7 +467,7 @@ mod benchmarks { parent_collection_id, market_id, asset_out_index_set, - false, + T::Fuel::from_total(total), ) .unwrap(); let expected_event = ::RuntimeEvent::from(Event::::TokenMerged { @@ -477,11 +483,12 @@ mod benchmarks { } #[benchmark] - fn redeem_position_sans_parent(n: Linear<2, 32>) { + fn redeem_position_sans_parent(n: Linear<2, 32>, m: Linear<32, 64>) { let alice: T::AccountId = whitelisted_caller(); let n_u16: u16 = n.try_into().unwrap(); let asset_count = n_u16 + 1; + let total = m; // `index_set` has `n` entries that are `true`, which results in `n` iterations in the `for` // loop in `redeem_position`. @@ -499,7 +506,7 @@ mod benchmarks { parent_collection_id, market_id, index_set.clone(), - false, + T::Fuel::from_total(total), ) .unwrap(); let amount = ZeitgeistBase::get().unwrap(); @@ -512,7 +519,7 @@ mod benchmarks { parent_collection_id, market_id, index_set.clone(), - true, + T::Fuel::from_total(total), ); let expected_event = ::RuntimeEvent::from(Event::::TokenRedeemed { @@ -529,11 +536,12 @@ mod benchmarks { } #[benchmark] - fn redeem_position_with_parent(n: Linear<2, 32>) { + fn redeem_position_with_parent(n: Linear<2, 32>, m: Linear<32, 64>) { let alice: T::AccountId = whitelisted_caller(); let n_u16: u16 = n.try_into().unwrap(); let asset_count = n_u16 + 1; + let total = m; // `index_set` has `n` entries that are `true`, which results in `n` iterations in the `for` // loop in `redeem_position`. @@ -545,7 +553,7 @@ mod benchmarks { None, parent_market_id, vec![false, true], - false, + T::Fuel::from_total(total), ) .unwrap(); let pos_01 = Pallet::::position_from_collection_id(parent_market_id, cid_01).unwrap(); @@ -555,7 +563,7 @@ mod benchmarks { Some(cid_01), child_market_id, index_set.clone(), - false, + T::Fuel::from_total(total), ) .unwrap(); let amount = ZeitgeistBase::get().unwrap(); @@ -570,7 +578,7 @@ mod benchmarks { Some(cid_01), child_market_id, index_set.clone(), - true, + T::Fuel::from_total(total), ); let expected_event = ::RuntimeEvent::from(Event::::TokenRedeemed { diff --git a/zrml/combinatorial-tokens/src/lib.rs b/zrml/combinatorial-tokens/src/lib.rs index 7feb249b3..148af3f06 100644 --- a/zrml/combinatorial-tokens/src/lib.rs +++ b/zrml/combinatorial-tokens/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // @@ -39,10 +39,12 @@ pub use pallet::*; #[frame_support::pallet] mod pallet { use crate::{ - traits::CombinatorialIdManager, types::TransmutationType, weights::WeightInfoZeitgeist, + traits::CombinatorialIdManager, + types::{CollectionIdError, TransmutationType}, + weights::WeightInfoZeitgeist, }; use alloc::{vec, vec::Vec}; - use core::marker::PhantomData; + use core::{fmt::Debug, marker::PhantomData}; use frame_support::{ ensure, pallet_prelude::{DispatchResultWithPostInfo, IsType, StorageVersion}, @@ -53,6 +55,8 @@ mod pallet { pallet_prelude::{BlockNumberFor, OriginFor}, }; use orml_traits::MultiCurrency; + use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; + use scale_info::TypeInfo; use sp_runtime::{ traits::{AccountIdConversion, Get, Zero}, DispatchError, DispatchResult, SaturatedConversion, @@ -60,7 +64,8 @@ mod pallet { use zeitgeist_primitives::{ math::{checked_ops_res::CheckedAddRes, fixed::FixedMul}, traits::{ - CombinatorialTokensApi, CombinatorialTokensUnsafeApi, MarketCommonsPalletApi, PayoutApi, + CombinatorialTokensApi, CombinatorialTokensFuel, CombinatorialTokensUnsafeApi, + MarketCommonsPalletApi, PayoutApi, }, types::{Asset, CombinatorialId, SplitPositionDispatchInfo}, }; @@ -80,8 +85,19 @@ mod pallet { Asset = AssetOf, MarketId = MarketIdOf, CombinatorialId = CombinatorialId, + Fuel = Self::Fuel, >; + type Fuel: Clone + + CombinatorialTokensFuel + + Debug + + Decode + + Encode + + Eq + + MaxEncodedLen + + PartialEq + + TypeInfo; + type MarketCommons: MarketCommonsPalletApi>; type MultiCurrency: MultiCurrency>; @@ -106,6 +122,8 @@ mod pallet { <::MultiCurrency as MultiCurrency>>::Balance; pub(crate) type CombinatorialIdOf = <::CombinatorialIdManager as CombinatorialIdManager>::CombinatorialId; + pub(crate) type FuelOf = + <::CombinatorialIdManager as CombinatorialIdManager>::Fuel; pub(crate) type MarketIdOf = <::MarketCommons as MarketCommonsPalletApi>::MarketId; pub(crate) type SplitPositionDispatchInfoOf = @@ -170,6 +188,9 @@ mod pallet { #[pallet::error] pub enum Error { + /// An error for the collection ID retrieval occured. + CollectionIdRetrievalFailed(CollectionIdError), + /// Specified index set is trival, empty, or doesn't match the market's number of outcomes. InvalidIndexSet, @@ -177,9 +198,6 @@ mod pallet { /// market's number of outcomes. InvalidPartition, - /// Specified collection ID is invalid. - InvalidCollectionId, - /// Specified market is not resolved. PayoutVectorNotFound, @@ -197,21 +215,27 @@ mod pallet { impl Pallet { #[pallet::call_index(0)] #[pallet::weight( - T::WeightInfo::split_position_vertical_sans_parent(partition.len().saturated_into()) - .max(T::WeightInfo::split_position_vertical_with_parent( - partition.len().saturated_into(), - )) - .max(T::WeightInfo::split_position_horizontal(partition.len().saturated_into())) + T::WeightInfo::split_position_vertical_sans_parent( + partition.len().saturated_into(), + fuel.total(), + ) + .max(T::WeightInfo::split_position_vertical_with_parent( + partition.len().saturated_into(), + fuel.total(), + )) + .max(T::WeightInfo::split_position_horizontal( + partition.len().saturated_into(), + fuel.total(), + )) )] #[transactional] pub fn split_position( origin: OriginFor, - // TODO Abstract this into a separate type. parent_collection_id: Option>, market_id: MarketIdOf, partition: Vec>, amount: BalanceOf, - force_max_work: bool, + fuel: FuelOf, ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; @@ -221,7 +245,7 @@ mod pallet { market_id, partition, amount, - force_max_work, + fuel, )?; DispatchResultWithPostInfo::Ok(post_dispatch_info) @@ -229,11 +253,18 @@ mod pallet { #[pallet::call_index(1)] #[pallet::weight( - T::WeightInfo::merge_position_vertical_sans_parent(partition.len().saturated_into()) - .max(T::WeightInfo::merge_position_vertical_with_parent( - partition.len().saturated_into(), - )) - .max(T::WeightInfo::merge_position_horizontal(partition.len().saturated_into())) + T::WeightInfo::merge_position_vertical_sans_parent( + partition.len().saturated_into(), + fuel.total(), + ) + .max(T::WeightInfo::merge_position_vertical_with_parent( + partition.len().saturated_into(), + fuel.total(), + )) + .max(T::WeightInfo::merge_position_horizontal( + partition.len().saturated_into(), + fuel.total(), + )) )] #[transactional] pub fn merge_position( @@ -242,23 +273,22 @@ mod pallet { market_id: MarketIdOf, partition: Vec>, amount: BalanceOf, - force_max_work: bool, + fuel: FuelOf, ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; - Self::do_merge_position( - who, - parent_collection_id, - market_id, - partition, - amount, - force_max_work, - ) + Self::do_merge_position(who, parent_collection_id, market_id, partition, amount, fuel) } #[pallet::call_index(2)] #[pallet::weight( - T::WeightInfo::redeem_position_with_parent(index_set.len().saturated_into()) - .max(T::WeightInfo::redeem_position_sans_parent(index_set.len().saturated_into())) + T::WeightInfo::redeem_position_with_parent( + index_set.len().saturated_into(), + fuel.total(), + ) + .max(T::WeightInfo::redeem_position_sans_parent( + index_set.len().saturated_into(), + fuel.total() + )) )] #[transactional] pub fn redeem_position( @@ -266,16 +296,10 @@ mod pallet { parent_collection_id: Option>, market_id: MarketIdOf, index_set: Vec, - force_max_work: bool, + fuel: FuelOf, ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; - Self::do_redeem_position( - who, - parent_collection_id, - market_id, - index_set, - force_max_work, - ) + Self::do_redeem_position(who, parent_collection_id, market_id, index_set, fuel) } } @@ -287,13 +311,13 @@ mod pallet { market_id: MarketIdOf, partition: Vec>, amount: BalanceOf, - force_max_work: bool, + fuel: FuelOf, ) -> Result, DispatchError> { let (transmutation_type, position) = Self::transmutation_asset( parent_collection_id, market_id, partition.clone(), - force_max_work, + fuel.clone(), )?; // Destroy the token to be split. @@ -301,12 +325,13 @@ mod pallet { TransmutationType::VerticalWithParent => { // Split combinatorial token into higher level position. // This will fail if the market has a different collateral than the previous - // markets. FIXME A cleaner error message would be nice though... + // markets. T::MultiCurrency::ensure_can_withdraw(position, &who, amount)?; T::MultiCurrency::withdraw(position, &who, amount)?; T::WeightInfo::split_position_vertical_with_parent( partition.len().saturated_into(), + fuel.total(), ) } TransmutationType::VerticalSansParent => { @@ -317,6 +342,7 @@ mod pallet { T::WeightInfo::split_position_vertical_sans_parent( partition.len().saturated_into(), + fuel.total(), ) } TransmutationType::Horizontal => { @@ -324,7 +350,10 @@ mod pallet { T::MultiCurrency::ensure_can_withdraw(position, &who, amount)?; T::MultiCurrency::withdraw(position, &who, amount)?; - T::WeightInfo::split_position_horizontal(partition.len().saturated_into()) + T::WeightInfo::split_position_horizontal( + partition.len().saturated_into(), + fuel.total(), + ) } }; @@ -337,7 +366,7 @@ mod pallet { parent_collection_id, market_id, index_set, - force_max_work, + fuel.clone(), ) }) .collect::, _>>()?; @@ -379,13 +408,13 @@ mod pallet { market_id: MarketIdOf, partition: Vec>, amount: BalanceOf, - force_max_work: bool, + fuel: FuelOf, ) -> DispatchResultWithPostInfo { let (transmutation_type, position) = Self::transmutation_asset( parent_collection_id, market_id, partition.clone(), - force_max_work, + fuel.clone(), )?; // Destroy the old tokens. @@ -397,7 +426,7 @@ mod pallet { parent_collection_id, market_id, index_set, - force_max_work, + fuel.clone(), ) }) .collect::, _>>()?; @@ -414,6 +443,7 @@ mod pallet { T::WeightInfo::merge_position_vertical_with_parent( partition.len().saturated_into(), + fuel.total(), ) } TransmutationType::VerticalSansParent => { @@ -423,13 +453,17 @@ mod pallet { T::WeightInfo::merge_position_vertical_sans_parent( partition.len().saturated_into(), + fuel.total(), ) } TransmutationType::Horizontal => { // Horizontal merge. T::MultiCurrency::deposit(position, &who, amount)?; - T::WeightInfo::merge_position_horizontal(partition.len().saturated_into()) + T::WeightInfo::merge_position_horizontal( + partition.len().saturated_into(), + fuel.total(), + ) } }; @@ -451,7 +485,7 @@ mod pallet { parent_collection_id: Option>, market_id: MarketIdOf, index_set: Vec, - force_max_work: bool, + fuel: FuelOf, ) -> DispatchResultWithPostInfo { let payout_vector = T::Payout::payout_vector(market_id).ok_or(Error::::PayoutVectorNotFound)?; @@ -480,7 +514,7 @@ mod pallet { parent_collection_id, market_id, index_set.clone(), - force_max_work, + fuel.clone(), )?; let amount = T::MultiCurrency::free_balance(position, &who); ensure!(!amount.is_zero(), Error::::NoTokensFound); @@ -494,8 +528,10 @@ mod pallet { let position = Asset::CombinatorialToken(position_id); T::MultiCurrency::deposit(position, &who, total_payout)?; - let weight = - T::WeightInfo::redeem_position_with_parent(index_set.len().saturated_into()); + let weight = T::WeightInfo::redeem_position_with_parent( + index_set.len().saturated_into(), + fuel.total(), + ); (weight, position) } else { @@ -506,8 +542,10 @@ mod pallet { total_payout, )?; - let weight = - T::WeightInfo::redeem_position_sans_parent(index_set.len().saturated_into()); + let weight = T::WeightInfo::redeem_position_sans_parent( + index_set.len().saturated_into(), + fuel.total(), + ); (weight, collateral_token) }; @@ -563,7 +601,7 @@ mod pallet { parent_collection_id: Option>, market_id: MarketIdOf, partition: Vec>, - force_max_work: bool, + fuel: FuelOf, ) -> Result<(TransmutationType, AssetOf), DispatchError> { let market = T::MarketCommons::market(&market_id)?; let collateral_token = market.base_asset; @@ -586,7 +624,7 @@ mod pallet { parent_collection_id, market_id, remaining_index_set, - force_max_work, + fuel, )?; (TransmutationType::Horizontal, position) @@ -599,15 +637,17 @@ mod pallet { parent_collection_id: Option>, market_id: MarketIdOf, index_set: Vec, - force_max_work: bool, + fuel: FuelOf, ) -> Result, DispatchError> { T::CombinatorialIdManager::get_collection_id( parent_collection_id, market_id, index_set, - force_max_work, + fuel, ) - .ok_or(Error::::InvalidCollectionId.into()) + .map_err(|collection_id_error| { + Error::::CollectionIdRetrievalFailed(collection_id_error).into() + }) } pub(crate) fn position_from_collection_id( @@ -628,13 +668,13 @@ mod pallet { parent_collection_id: Option>, market_id: MarketIdOf, index_set: Vec, - force_max_work: bool, + fuel: FuelOf, ) -> Result, DispatchError> { let collection_id = Self::collection_id_from_parent_collection( parent_collection_id, market_id, index_set, - force_max_work, + fuel, )?; Self::position_from_collection_id(market_id, collection_id) @@ -649,6 +689,7 @@ mod pallet { type Balance = BalanceOf; type CombinatorialId = CombinatorialIdOf; type MarketId = MarketIdOf; + type Fuel = <::CombinatorialIdManager as CombinatorialIdManager>::Fuel; fn split_position( who: Self::AccountId, @@ -656,16 +697,9 @@ mod pallet { market_id: Self::MarketId, partition: Vec>, amount: Self::Balance, - force_max_work: bool, + fuel: Self::Fuel, ) -> Result, DispatchError> { - Self::do_split_position( - who, - parent_collection_id, - market_id, - partition, - amount, - force_max_work, - ) + Self::do_split_position(who, parent_collection_id, market_id, partition, amount, fuel) } } diff --git a/zrml/combinatorial-tokens/src/mock/runtime.rs b/zrml/combinatorial-tokens/src/mock/runtime.rs index ede07e829..ff39e3832 100644 --- a/zrml/combinatorial-tokens/src/mock/runtime.rs +++ b/zrml/combinatorial-tokens/src/mock/runtime.rs @@ -16,7 +16,11 @@ // along with Zeitgeist. If not, see . use crate as zrml_combinatorial_tokens; -use crate::{mock::types::MockPayout, types::CryptographicIdManager, weights::WeightInfo}; +use crate::{ + mock::types::MockPayout, + types::{cryptographic_id_manager::Fuel, CryptographicIdManager}, + weights::WeightInfo, +}; use frame_support::{construct_runtime, traits::Everything, Blake2_256}; use frame_system::mocking::MockBlock; use sp_runtime::traits::{BlakeTwo256, ConstU32, IdentityLookup}; @@ -49,6 +53,7 @@ impl zrml_combinatorial_tokens::Config for Runtime { #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = BenchmarkHelper; type CombinatorialIdManager = CryptographicIdManager; + type Fuel = Fuel; type MarketCommons = MarketCommons; type MultiCurrency = Currencies; type Payout = MockPayout; diff --git a/zrml/combinatorial-tokens/src/tests/integration.rs b/zrml/combinatorial-tokens/src/tests/integration.rs index 83a5162bd..7b6b5880a 100644 --- a/zrml/combinatorial-tokens/src/tests/integration.rs +++ b/zrml/combinatorial-tokens/src/tests/integration.rs @@ -42,7 +42,7 @@ fn split_followed_by_merge_vertical_no_parent() { market_id, partition.clone(), amount, - false, + Fuel::new(16, false), )); assert_eq!(alice.free_balance(Asset::Ztg), _99); assert_eq!(alice.free_balance(ct_001), _1); @@ -55,7 +55,7 @@ fn split_followed_by_merge_vertical_no_parent() { market_id, partition, amount, - false, + Fuel::new(16, false), )); assert_eq!(alice.free_balance(Asset::Ztg), _100); assert_eq!(alice.free_balance(ct_001), 0); @@ -96,7 +96,7 @@ fn split_followed_by_merge_vertical_with_parent() { parent_market_id, parent_partition.clone(), parent_amount, - false, + Fuel::new(16, false), )); let child_market_id = create_market(Asset::Ztg, MarketType::Categorical(4)); @@ -113,7 +113,7 @@ fn split_followed_by_merge_vertical_with_parent() { child_market_id, child_partition.clone(), child_amount, - false, + Fuel::new(16, false), )); assert_eq!(alice.free_balance(ct_001), parent_amount - child_amount); assert_eq!(alice.free_balance(ct_110), parent_amount); @@ -128,7 +128,7 @@ fn split_followed_by_merge_vertical_with_parent() { child_market_id, child_partition, child_amount, - false, + Fuel::new(16, false), )); assert_eq!(alice.free_balance(ct_001), parent_amount); assert_eq!(alice.free_balance(ct_110), parent_amount); @@ -143,7 +143,7 @@ fn split_followed_by_merge_vertical_with_parent() { parent_market_id, parent_partition, parent_amount, - false, + Fuel::new(16, false), )); assert_eq!(alice.free_balance(ct_001), 0); assert_eq!(alice.free_balance(ct_110), 0); @@ -225,7 +225,7 @@ fn split_followed_by_merge_vertical_with_parent_in_opposite_order() { market_0, partition_0.clone(), amount, - false, + Fuel::new(16, false), )); // Split C into C&(U|V) and C&(W|X). @@ -235,7 +235,7 @@ fn split_followed_by_merge_vertical_with_parent_in_opposite_order() { market_1, partition_1.clone(), amount, - false, + Fuel::new(16, false), )); // Split A|B into into (A|B)&(U|V) and (A|B)&(W|X). @@ -245,7 +245,7 @@ fn split_followed_by_merge_vertical_with_parent_in_opposite_order() { market_1, partition_1.clone(), amount, - false, + Fuel::new(16, false), )); assert_eq!(alice.free_balance(ct_001), 0); @@ -265,7 +265,7 @@ fn split_followed_by_merge_vertical_with_parent_in_opposite_order() { market_0, partition_0.clone(), amount, - false, + Fuel::new(16, false), )); assert_eq!(alice.free_balance(ct_001), 0); @@ -285,7 +285,7 @@ fn split_followed_by_merge_vertical_with_parent_in_opposite_order() { market_0, partition_0, amount, - false, + Fuel::new(16, false), )); assert_eq!(alice.free_balance(ct_001), 0); @@ -305,7 +305,7 @@ fn split_followed_by_merge_vertical_with_parent_in_opposite_order() { market_1, partition_1, amount, - false, + Fuel::new(16, false), )); assert_eq!(alice.free_balance(ct_001), 0); @@ -337,7 +337,7 @@ fn split_vertical_followed_by_horizontal_split_no_parent() { market_id, vec![vec![B0, B0, B1], vec![B1, B1, B0]], amount, - false, + Fuel::new(16, false), )); assert_ok!(CombinatorialTokens::split_position( alice.signed(), @@ -345,7 +345,7 @@ fn split_vertical_followed_by_horizontal_split_no_parent() { market_id, vec![vec![B1, B0, B0], vec![B0, B1, B0]], amount, - false, + Fuel::new(16, false), )); let ct_001 = CombinatorialToken([ @@ -372,7 +372,7 @@ fn split_vertical_followed_by_horizontal_split_no_parent() { market_id, vec![vec![B1, B0, B0], vec![B0, B1, B0], vec![B0, B0, B1]], amount, - false, + Fuel::new(16, false), )); assert_eq!(alice.free_balance(ct_001), 2 * amount); @@ -398,7 +398,7 @@ fn split_vertical_followed_by_horizontal_split_with_parent() { parent_market_id, vec![vec![B0, B0, B1], vec![B1, B1, B0]], parent_amount, - false, + Fuel::new(16, false), )); let child_market_id = create_market(Asset::Ztg, MarketType::Categorical(4)); @@ -441,7 +441,7 @@ fn split_vertical_followed_by_horizontal_split_with_parent() { child_market_id, vec![vec![B0, B0, B1, B1], vec![B1, B1, B0, B0]], child_amount_first_pass, - false, + Fuel::new(16, false), )); assert_ok!(CombinatorialTokens::split_position( alice.signed(), @@ -449,7 +449,7 @@ fn split_vertical_followed_by_horizontal_split_with_parent() { child_market_id, vec![vec![B1, B0, B0, B0], vec![B0, B1, B0, B0]], child_amount_first_pass, - false, + Fuel::new(16, false), )); assert_eq!(alice.free_balance(ct_001), parent_amount - child_amount_first_pass); @@ -469,7 +469,7 @@ fn split_vertical_followed_by_horizontal_split_with_parent() { child_market_id, vec![vec![B1, B0, B0, B0], vec![B0, B1, B0, B0], vec![B0, B0, B1, B1]], child_amount_second_pass, - false, + Fuel::new(16, false), )); let total_child_amount = child_amount_first_pass + child_amount_second_pass; @@ -507,7 +507,7 @@ fn split_horizontal_followed_by_merge_horizontal() { market_id, vec![vec![B0, B0, B1], vec![B1, B1, B0]], amount, - false, + Fuel::new(16, false), )); assert_ok!(CombinatorialTokens::split_position( @@ -516,7 +516,7 @@ fn split_horizontal_followed_by_merge_horizontal() { market_id, vec![vec![B1, B0, B0], vec![B0, B1, B0]], amount, - false, + Fuel::new(16, false), )); assert_ok!(CombinatorialTokens::merge_position( @@ -525,7 +525,7 @@ fn split_horizontal_followed_by_merge_horizontal() { market_id, vec![vec![B1, B0, B0], vec![B0, B1, B0]], amount, - false, + Fuel::new(16, false), )); assert_eq!(alice.free_balance(ct_001), _1); diff --git a/zrml/combinatorial-tokens/src/tests/merge_position.rs b/zrml/combinatorial-tokens/src/tests/merge_position.rs index 077685f29..853036df4 100644 --- a/zrml/combinatorial-tokens/src/tests/merge_position.rs +++ b/zrml/combinatorial-tokens/src/tests/merge_position.rs @@ -50,7 +50,7 @@ fn merge_position_works_no_parent( market_id, partition.clone(), amount, - false, + Fuel::new(16, false), )); assert_eq!(alice.free_balance(ct_001), 0); @@ -111,7 +111,7 @@ fn merge_position_works_parent() { market_id, partition.clone(), amount, - false, + Fuel::new(16, false), )); assert_eq!(alice.free_balance(ct_001), amount); @@ -160,7 +160,7 @@ fn merge_position_horizontal_works() { market_id, vec![vec![B0, B1, B0], vec![B1, B0, B0]], amount, - false, + Fuel::new(16, false), )); assert_eq!(alice.free_balance(ct_110), amount); @@ -181,7 +181,7 @@ fn merge_position_fails_if_market_not_found() { 0, vec![vec![B0, B0, B1], vec![B1, B1, B0]], 1, - false, + Fuel::new(16, false), ), zrml_market_commons::Error::::MarketDoesNotExist, ); @@ -204,7 +204,7 @@ fn merge_position_fails_on_invalid_partition_length() { market_id, partition, _1, - false + Fuel::new(16, false) ), Error::::InvalidPartition ); @@ -227,7 +227,7 @@ fn merge_position_fails_on_trivial_partition_member() { market_id, partition, _1, - false + Fuel::new(16, false) ), Error::::InvalidPartition ); @@ -250,7 +250,7 @@ fn merge_position_fails_on_overlapping_partition_members() { market_id, partition, _1, - false + Fuel::new(16, false) ), Error::::InvalidPartition ); @@ -272,7 +272,7 @@ fn merge_position_fails_on_insufficient_funds() { market_id, vec![vec![B1, B0, B1], vec![B0, B1, B0]], _100, - false, + Fuel::new(16, false), ), orml_tokens::Error::::BalanceTooLow ); @@ -294,7 +294,7 @@ fn merge_position_fails_on_insufficient_funds_foreign_token() { market_id, vec![vec![B1, B0, B1], vec![B0, B1, B0]], _100, - false, + Fuel::new(16, false), ), orml_tokens::Error::::BalanceTooLow ); diff --git a/zrml/combinatorial-tokens/src/tests/mod.rs b/zrml/combinatorial-tokens/src/tests/mod.rs index ebcb3d751..8b1fb8469 100644 --- a/zrml/combinatorial-tokens/src/tests/mod.rs +++ b/zrml/combinatorial-tokens/src/tests/mod.rs @@ -28,6 +28,7 @@ use crate::{ runtime::{CombinatorialTokens, Currencies, MarketCommons, Runtime, RuntimeOrigin, System}, types::MockPayout, }, + types::cryptographic_id_manager::Fuel, Error, Event, Pallet, }; use frame_support::{assert_noop, assert_ok}; diff --git a/zrml/combinatorial-tokens/src/tests/redeem_position.rs b/zrml/combinatorial-tokens/src/tests/redeem_position.rs index 64a0d9944..ffabc600d 100644 --- a/zrml/combinatorial-tokens/src/tests/redeem_position.rs +++ b/zrml/combinatorial-tokens/src/tests/redeem_position.rs @@ -25,7 +25,13 @@ fn redeem_position_fails_on_no_payout_vector() { let market_id = 0; MockPayout::set_return_value(None); assert_noop!( - CombinatorialTokens::redeem_position(alice.signed(), None, market_id, vec![], false), + CombinatorialTokens::redeem_position( + alice.signed(), + None, + market_id, + vec![], + Fuel::new(16, false) + ), Error::::PayoutVectorNotFound ); assert!(MockPayout::called_once_with(market_id)); @@ -38,7 +44,13 @@ fn redeem_position_fails_on_market_not_found() { let alice = Account::new(0).deposit(Asset::Ztg, _100).unwrap(); MockPayout::set_return_value(Some(vec![_1_2, _1_2])); assert_noop!( - CombinatorialTokens::redeem_position(alice.signed(), None, 0, vec![], false), + CombinatorialTokens::redeem_position( + alice.signed(), + None, + 0, + vec![], + Fuel::new(16, false) + ), zrml_market_commons::Error::::MarketDoesNotExist ); }); @@ -46,14 +58,20 @@ fn redeem_position_fails_on_market_not_found() { #[test_case(vec![B0, B1, B0, B1]; "incorrect_len")] #[test_case(vec![B0, B0, B0]; "all_zero")] -#[test_case(vec![B0, B0, B0]; "all_one")] +#[test_case(vec![B1, B1, B1]; "all_one")] fn redeem_position_fails_on_incorrect_index_set(index_set: Vec) { ExtBuilder::build().execute_with(|| { let alice = Account::new(0).deposit(Asset::Ztg, _100).unwrap(); MockPayout::set_return_value(Some(vec![_1_3, _1_3, _1_3])); let market_id = create_market(Asset::Ztg, MarketType::Categorical(3)); assert_noop!( - CombinatorialTokens::redeem_position(alice.signed(), None, market_id, index_set, false), + CombinatorialTokens::redeem_position( + alice.signed(), + None, + market_id, + index_set, + Fuel::new(16, false) + ), Error::::InvalidIndexSet ); }); @@ -67,7 +85,13 @@ fn redeem_position_fails_if_tokens_have_to_value() { let market_id = create_market(Asset::Ztg, MarketType::Categorical(4)); let index_set = vec![B1, B0, B0, B1]; assert_noop!( - CombinatorialTokens::redeem_position(alice.signed(), None, market_id, index_set, false), + CombinatorialTokens::redeem_position( + alice.signed(), + None, + market_id, + index_set, + Fuel::new(16, false) + ), Error::::TokenHasNoValue ); }); @@ -81,7 +105,13 @@ fn redeem_position_fails_if_user_holds_no_winning_tokens() { let market_id = create_market(Asset::Ztg, MarketType::Categorical(4)); let index_set = vec![B0, B1, B0, B1]; assert_noop!( - CombinatorialTokens::redeem_position(alice.signed(), None, market_id, index_set, false), + CombinatorialTokens::redeem_position( + alice.signed(), + None, + market_id, + index_set, + Fuel::new(16, false) + ), Error::::NoTokensFound, ); }); @@ -109,7 +139,7 @@ fn redeem_position_works_sans_parent() { parent_collection_id, market_id, index_set.clone(), - false, + Fuel::new(16, false), )); assert_eq!(alice.free_balance(ct_110), 0); @@ -166,7 +196,7 @@ fn redeem_position_works_with_parent() { parent_collection_id, market_id, index_set.clone(), - false, + Fuel::new(16, false), )); assert_eq!(alice.free_balance(ct_001_0101), 0); diff --git a/zrml/combinatorial-tokens/src/tests/split_position.rs b/zrml/combinatorial-tokens/src/tests/split_position.rs index 88873e02b..1b16a7ab4 100644 --- a/zrml/combinatorial-tokens/src/tests/split_position.rs +++ b/zrml/combinatorial-tokens/src/tests/split_position.rs @@ -34,7 +34,7 @@ fn split_position_works_vertical_no_parent() { market_id, partition.clone(), amount, - false, + Fuel::new(16, false), )); let ct_001 = CombinatorialToken([ @@ -90,7 +90,7 @@ fn split_position_works_vertical_with_parent() { parent_market_id, vec![vec![B0, B0, B1], vec![B1, B1, B0]], parent_amount, - false, + Fuel::new(16, false), )); let child_market_id = create_market(Asset::Ztg, MarketType::Categorical(4)); @@ -107,7 +107,7 @@ fn split_position_works_vertical_with_parent() { child_market_id, partition.clone(), child_amount, - false, + Fuel::new(16, false), )); // Alice is left with 1 unit of [0, 0, 1], 2 units of [1, 1, 0] and one unit of each of the @@ -183,7 +183,7 @@ fn split_position_fails_if_market_not_found() { 0, vec![vec![B0, B0, B1], vec![B1, B1, B0]], 1, - false, + Fuel::new(16, false), ), zrml_market_commons::Error::::MarketDoesNotExist, ); @@ -206,7 +206,7 @@ fn split_position_fails_on_invalid_partition_length() { market_id, partition, _1, - false, + Fuel::new(16, false), ), Error::::InvalidPartition ); @@ -229,7 +229,7 @@ fn split_position_fails_on_empty_partition_member() { market_id, partition, _1, - false + Fuel::new(16, false) ), Error::::InvalidPartition ); @@ -252,7 +252,7 @@ fn split_position_fails_on_overlapping_partition_members() { market_id, partition, _1, - false, + Fuel::new(16, false), ), Error::::InvalidPartition ); @@ -274,7 +274,7 @@ fn split_position_fails_on_trivial_partition() { market_id, partition, _1, - false + Fuel::new(16, false) ), Error::::InvalidPartition ); @@ -296,7 +296,7 @@ fn split_position_fails_on_insufficient_funds_native_token_no_parent() { market_id, vec![vec![B1, B0, B1], vec![B0, B1, B0]], _100, - false, + Fuel::new(16, false), ), orml_currencies::Error::::BalanceTooLow ); @@ -318,7 +318,7 @@ fn split_position_fails_on_insufficient_funds_foreign_token_no_parent() { market_id, vec![vec![B1, B0, B1], vec![B0, B1, B0]], _100, - false, + Fuel::new(16, false), ), orml_currencies::Error::::BalanceTooLow ); @@ -351,7 +351,7 @@ fn split_position_vertical_fails_on_insufficient_funds_combinatorial_token() { market_id, vec![vec![B1, B0, B1, B0], vec![B0, B1, B0, B1]], _100, - false, + Fuel::new(16, false), ), orml_tokens::Error::::BalanceTooLow ); @@ -363,7 +363,7 @@ fn split_position_vertical_fails_on_insufficient_funds_combinatorial_token() { market_id, vec![vec![B1, B0, B1, B0], vec![B0, B1, B0, B1]], _99, - false, + Fuel::new(16, false), )); }); } @@ -388,7 +388,7 @@ fn split_position_horizontal_fails_on_insufficient_funds_combinatorial_token() { market_id, vec![vec![B1, B0, B0], vec![B0, B1, B0]], _100, - false, + Fuel::new(16, false), ), orml_tokens::Error::::BalanceTooLow ); @@ -400,7 +400,7 @@ fn split_position_horizontal_fails_on_insufficient_funds_combinatorial_token() { market_id, vec![vec![B1, B0, B0], vec![B0, B1, B0]], _99, - false, + Fuel::new(16, false), )); }); } diff --git a/zrml/combinatorial-tokens/src/traits/combinatorial_id_manager.rs b/zrml/combinatorial-tokens/src/traits/combinatorial_id_manager.rs index 547d2f9d8..47cb9389a 100644 --- a/zrml/combinatorial-tokens/src/traits/combinatorial_id_manager.rs +++ b/zrml/combinatorial-tokens/src/traits/combinatorial_id_manager.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // @@ -22,20 +22,26 @@ // , // and has been relicensed under GPL-3.0-or-later in this repository. +use crate::types::CollectionIdError; use alloc::vec::Vec; pub trait CombinatorialIdManager { type Asset; type MarketId; type CombinatorialId; + type Fuel; - // TODO Replace `Vec` with a more effective bit mask type. + /// Calculate the collection ID obtained when splitting `parent_collection_id` over the market + /// given by `market_id` and the `index_set`. + /// + /// The `fuel` parameter specifies how much work the function will do and can be used for + /// benchmarking purposes. fn get_collection_id( parent_collection_id: Option, market_id: Self::MarketId, index_set: Vec, - force_max_work: bool, - ) -> Option; + fuel: Self::Fuel, + ) -> Result; fn get_position_id( collateral: Self::Asset, diff --git a/zrml/combinatorial-tokens/src/types/collection_id_error.rs b/zrml/combinatorial-tokens/src/types/collection_id_error.rs new file mode 100644 index 000000000..2de7ce936 --- /dev/null +++ b/zrml/combinatorial-tokens/src/types/collection_id_error.rs @@ -0,0 +1,35 @@ +// Copyright 2025 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . +// +// This file incorporates work licensed under the GNU Lesser General +// Public License 3.0 but published without copyright notice by Gnosis +// (, info@gnosis.io) in the +// conditional-tokens-contracts repository +// , +// and has been relicensed under GPL-3.0-or-later in this repository. + +use frame_support::PalletError; +use parity_scale_codec::{Decode, Encode}; +use scale_info::TypeInfo; +use sp_runtime::RuntimeDebug; + +#[derive(Decode, Encode, Eq, PartialEq, PalletError, RuntimeDebug, TypeInfo)] +pub enum CollectionIdError { + InvalidParentCollectionId, + EllipticCurvePointNotFoundWithFuel, + EllipticCurvePointXToBytesConversionFailed, +} diff --git a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/mod.rs b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/mod.rs index f5f5d0a6f..b1947d3b0 100644 --- a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/mod.rs +++ b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // @@ -26,19 +26,24 @@ mod tests; +use crate::types::{cryptographic_id_manager::Fuel, CollectionIdError}; use ark_bn254::{g1::G1Affine, Fq}; use ark_ff::{BigInteger, PrimeField}; use core::ops::Neg; use sp_runtime::traits::{One, Zero}; -use zeitgeist_primitives::types::CombinatorialId; +use zeitgeist_primitives::{traits::CombinatorialTokensFuel, types::CombinatorialId}; -/// Will return `None` if and only if `parent_collection_id` is not a valid collection ID. +/// Returns a valid collection ID from an `hash` and an optional `parent_collection_id`. +/// +/// Will return `None` if `parent_collection_id` is not a valid collection ID or +/// the decompression of the hash doesn't return a valid point of `alt_bn128` +/// (maybe insufficient `fuel` parameter) or because of a failing bytes conversion. pub(crate) fn get_collection_id( hash: CombinatorialId, parent_collection_id: Option, - force_max_work: bool, -) -> Option { - let mut u = decompress_hash(hash, force_max_work)?; + fuel: Fuel, +) -> Result { + let mut u = decompress_hash(hash, fuel)?; if let Some(pci) = parent_collection_id { let v = decompress_collection_id(pci)?; @@ -48,26 +53,30 @@ pub(crate) fn get_collection_id( // Convert back to bytes _before_ flipping, as flipping will sometimes result in numbers larger // than the base field modulus. - let mut bytes: CombinatorialId = u.x.into_bigint().to_bytes_be().try_into().ok()?; + let bytes_y_even: CombinatorialId = + u.x.into_bigint() + .to_bytes_be() + .try_into() + .map_err(|_| CollectionIdError::EllipticCurvePointXToBytesConversionFailed)?; - if u.y.into_bigint().is_odd() { - flip_second_highest_bit(&mut bytes); - } + let bytes = if u.y.into_bigint().is_odd() { + flip_second_highest_bit(&bytes_y_even) + } else { + bytes_y_even + }; - Some(bytes) + Ok(bytes) } -const DECOMPRESS_HASH_MAX_ITERS: usize = 32; - /// Decompresses a collection ID `hash` to a point of `alt_bn128`. The amount of work done can be -/// forced to be independent of the input by setting the `force_max_work` flag. +/// controlled using the `fuel` parameter. /// /// We don't have mathematical proof that the points of `alt_bn128` are distributed so that the /// required number of iterations is below the specified limit of iterations, but there's good -/// evidence that input hash requires more than `log_2(P) = 507.19338271000436` iterations. -/// -/// Provided the assumption above is correct, this function cannot return `None`. -fn decompress_hash(hash: CombinatorialId, force_max_work: bool) -> Option { +/// evidence that input hash requires more than `log_2(P) = 507.19338271000436` iterations. With a +/// `fuel.total` value of `32`, statistical evidence suggests a 1 in 500_000_000 chance that the +/// number of iterations will not be enough. +fn decompress_hash(hash: CombinatorialId, fuel: Fuel) -> Result { // Calculate `odd` first, then get congruent point `x` in `Fq`. As `hash` might represent a // larger big endian number than `field_modulus()`, the MSB of `x` might be different from the // MSB of `x_u256`. @@ -77,7 +86,7 @@ fn decompress_hash(hash: CombinatorialId, force_max_work: bool) -> Option Option Option Option { +fn decompress_collection_id(collection_id: CombinatorialId) -> Result { let odd = is_second_msb_set(&collection_id); - chop_off_two_highest_bits(&mut collection_id); - let x = Fq::from_be_bytes_mod_order(&collection_id); + let chopped_collection_id = chop_off_two_highest_bits(&collection_id); + let x = Fq::from_be_bytes_mod_order(&chopped_collection_id); // Ensure that the big-endian integer represented by `collection_id` was less than the field // modulus. Otherwise, we consider `collection_id` an invalid ID. - if x.into_bigint().to_bytes_be() != collection_id { - return None; + if x.into_bigint().to_bytes_be() != chopped_collection_id { + return Err(CollectionIdError::InvalidParentCollectionId); } - let mut y = matching_y_coordinate(x)?; // Fails if `collection_id` is not a collection ID. + // Fails if `collection_id` is not a collection ID. + let mut y = matching_y_coordinate(x).ok_or(CollectionIdError::InvalidParentCollectionId)?; // We have two options for the y-coordinate of the corresponding point: `y` and `P - y`. If // `odd` is set but `y` isn't odd, we switch to the other option. @@ -136,12 +148,14 @@ fn decompress_collection_id(mut collection_id: CombinatorialId) -> Option CombinatorialId { + let mut bytes = *bytes; bytes[0] ^= 0b01000000; + bytes } /// Checks if the most significant bit of the big-endian `bytes` is set. @@ -155,8 +169,10 @@ fn is_second_msb_set(bytes: &CombinatorialId) -> bool { } /// Zeroes out the two most significant bits off the big-endian `bytes`. -fn chop_off_two_highest_bits(bytes: &mut CombinatorialId) { +fn chop_off_two_highest_bits(bytes: &CombinatorialId) -> CombinatorialId { + let mut bytes = *bytes; bytes[0] &= 0b00111111; + bytes } /// Returns a value `y` of `Fq` so that `(x, y)` is a point on `alt_bn128` or `None` if there is no diff --git a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/decompress_collection_id.rs b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/decompress_collection_id.rs index 5764626b1..926ff1cfc 100644 --- a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/decompress_collection_id.rs +++ b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/decompress_collection_id.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // @@ -574,5 +574,5 @@ fn decompress_collection_id_works(collection_id: CombinatorialId, expected: (&st )] fn decompress_collection_id_fails_on_invalid_collection_id(collection_id: CombinatorialId) { let actual = decompress_collection_id(collection_id); - assert_eq!(actual, None); + assert_eq!(actual, Err(CollectionIdError::InvalidParentCollectionId)); } diff --git a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/decompress_hash.rs b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/decompress_hash.rs index c0de02291..f55b5e15a 100644 --- a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/decompress_hash.rs +++ b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/decompress_hash.rs @@ -763,11 +763,12 @@ use rstest::rstest; )] fn decompress_hash_works( #[case] hash: CombinatorialId, - #[values(false, true)] force_max_work: bool, + #[values(false, true)] consume_all: bool, #[case] expected: (&str, &str), ) { let x = Fq::from_hex_str(expected.0); let y = Fq::from_hex_str(expected.1); let expected = G1Affine::new(x, y); - assert_eq!(decompress_hash(hash, force_max_work).unwrap(), expected); + let actual = decompress_hash(hash, Fuel { total: 16, consume_all }).unwrap(); + assert_eq!(actual, expected); } diff --git a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/get_collection_id.rs b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/get_collection_id.rs index 26420b038..34a698a89 100644 --- a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/get_collection_id.rs +++ b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/get_collection_id.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // @@ -83,8 +83,10 @@ use rstest::rstest; fn get_collection_id_works( #[case] hash: CombinatorialId, #[case] parent_collection_id: Option, - #[values(false, true)] force_max_work: bool, + #[values(false, true)] consume_all: bool, #[case] expected: Option, ) { - assert_eq!(get_collection_id(hash, parent_collection_id, force_max_work), expected); + let actual = + get_collection_id(hash, parent_collection_id, Fuel { total: 16, consume_all }).ok(); + assert_eq!(actual, expected); } diff --git a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/mod.rs b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/mod.rs index 087498fce..f29975101 100644 --- a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/mod.rs +++ b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // @@ -25,12 +25,47 @@ mod decompressor; mod hash_tuple; +use super::CollectionIdError; use crate::traits::CombinatorialIdManager; use alloc::vec::Vec; use core::marker::PhantomData; use hash_tuple::{HashTuple, ToBytes}; -use parity_scale_codec::Encode; -use zeitgeist_primitives::types::{Asset, CombinatorialId}; +use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; +use zeitgeist_primitives::{ + traits::CombinatorialTokensFuel, + types::{Asset, CombinatorialId}, +}; + +#[derive(Clone, Debug, Decode, Encode, Eq, MaxEncodedLen, PartialEq, TypeInfo)] +pub struct Fuel { + /// The maximum number of iterations to perform in the main loop of `get_collection_id`. + total: u32, + + /// Perform `self.total` of iterations in the main loop of `get_collection_id`. Useful for + /// benchmarking purposes and should probably not be used in production. + consume_all: bool, +} + +impl Fuel { + pub fn new(total: u32, consume_all: bool) -> Self { + Fuel { total, consume_all } + } + + pub fn consume_all(&self) -> bool { + self.consume_all + } +} + +impl CombinatorialTokensFuel for Fuel { + fn from_total(total: u32) -> Fuel { + Fuel { total, consume_all: true } + } + + fn total(&self) -> u32 { + self.total + } +} pub struct CryptographicIdManager(PhantomData<(MarketId, Hasher)>); @@ -42,16 +77,18 @@ where type Asset = Asset; type CombinatorialId = CombinatorialId; type MarketId = MarketId; + type Fuel = Fuel; fn get_collection_id( parent_collection_id: Option, market_id: Self::MarketId, index_set: Vec, - force_max_work: bool, - ) -> Option { + fuel: Self::Fuel, + ) -> Result { let input = (market_id, index_set); let hash = Hasher::hash_tuple(input); - decompressor::get_collection_id(hash, parent_collection_id, force_max_work) + + decompressor::get_collection_id(hash, parent_collection_id, fuel) } fn get_position_id( @@ -59,6 +96,7 @@ where collection_id: Self::CombinatorialId, ) -> Self::CombinatorialId { let input = (collateral, collection_id); + Hasher::hash_tuple(input) } } diff --git a/zrml/combinatorial-tokens/src/types/mod.rs b/zrml/combinatorial-tokens/src/types/mod.rs index a9960976e..f23b68450 100644 --- a/zrml/combinatorial-tokens/src/types/mod.rs +++ b/zrml/combinatorial-tokens/src/types/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // @@ -15,10 +15,12 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . +mod collection_id_error; pub(crate) mod cryptographic_id_manager; pub(crate) mod hash; mod transmutation_type; -pub use cryptographic_id_manager::CryptographicIdManager; +pub use collection_id_error::CollectionIdError; +pub use cryptographic_id_manager::{CryptographicIdManager, Fuel}; pub(crate) use hash::Hash256; pub use transmutation_type::TransmutationType; diff --git a/zrml/combinatorial-tokens/src/weights.rs b/zrml/combinatorial-tokens/src/weights.rs index 1edf64a0a..f1991c6df 100644 --- a/zrml/combinatorial-tokens/src/weights.rs +++ b/zrml/combinatorial-tokens/src/weights.rs @@ -19,21 +19,21 @@ //! Autogenerated weights for zrml_combinatorial_tokens //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2024-10-30`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2024-12-05`, STEPS: `2`, REPEAT: `0`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `ztg-benchmark`, CPU: `AMD EPYC 7601 32-Core Processor` +//! HOSTNAME: `Mac`, CPU: `` //! EXECUTION: ``, WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` // Executed Command: -// ./target/production/zeitgeist +// ./target/release/zeitgeist // benchmark // pallet // --chain=dev -// --steps=50 -// --repeat=20 +// --steps=2 +// --repeat=0 // --pallet=zrml_combinatorial_tokens // --extrinsic=* -// --execution=wasm +// --execution=native // --wasm-execution=compiled // --heap-pages=4096 // --template=./misc/weight_template.hbs @@ -49,14 +49,14 @@ use frame_support::{traits::Get, weights::Weight}; /// Trait containing the required functions for weight retrival within /// zrml_combinatorial_tokens (automatically generated) pub trait WeightInfoZeitgeist { - fn split_position_vertical_sans_parent(n: u32) -> Weight; - fn split_position_vertical_with_parent(n: u32) -> Weight; - fn split_position_horizontal(n: u32) -> Weight; - fn merge_position_vertical_sans_parent(n: u32) -> Weight; - fn merge_position_vertical_with_parent(n: u32) -> Weight; - fn merge_position_horizontal(n: u32) -> Weight; - fn redeem_position_sans_parent(n: u32) -> Weight; - fn redeem_position_with_parent(n: u32) -> Weight; + fn split_position_vertical_sans_parent(n: u32, m: u32) -> Weight; + fn split_position_vertical_with_parent(n: u32, m: u32) -> Weight; + fn split_position_horizontal(n: u32, m: u32) -> Weight; + fn merge_position_vertical_sans_parent(n: u32, m: u32) -> Weight; + fn merge_position_vertical_with_parent(n: u32, m: u32) -> Weight; + fn merge_position_horizontal(n: u32, m: u32) -> Weight; + fn redeem_position_sans_parent(n: u32, m: u32) -> Weight; + fn redeem_position_with_parent(n: u32, m: u32) -> Weight; } /// Weight functions for zrml_combinatorial_tokens (automatically generated) @@ -71,14 +71,17 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `Tokens::TotalIssuance` (r:32 w:32) /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 32]`. - fn split_position_vertical_sans_parent(n: u32) -> Weight { + /// The range of component `m` is `[32, 64]`. + fn split_position_vertical_sans_parent(n: u32, m: u32) -> Weight { // Proof Size summary in bytes: // Measured: `441` // Estimated: `4173 + n * (2612 ±0)` - // Minimum execution time: 6_978_416 nanoseconds. - Weight::from_parts(102_072_603, 4173) - // Standard Error: 4_544_660 - .saturating_add(Weight::from_parts(3_565_520_352, 0).saturating_mul(n.into())) + // Minimum execution time: 3_358_000 nanoseconds. + Weight::from_parts(3_358_000_000, 4173) + // Standard Error: 397_575_770 + .saturating_add(Weight::from_parts(1_183_275_893, 0).saturating_mul(n.into())) + // Standard Error: 191_113_523 + .saturating_add(Weight::from_parts(73_290_272, 0).saturating_mul(m.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(1)) @@ -92,14 +95,17 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `Tokens::TotalIssuance` (r:33 w:33) /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 32]`. - fn split_position_vertical_with_parent(n: u32) -> Weight { + /// The range of component `m` is `[32, 64]`. + fn split_position_vertical_with_parent(n: u32, m: u32) -> Weight { // Proof Size summary in bytes: // Measured: `671` // Estimated: `4173 + n * (2612 ±0)` - // Minimum execution time: 8_316_377 nanoseconds. - Weight::from_parts(2_605_489, 4173) - // Standard Error: 3_965_121 - .saturating_add(Weight::from_parts(4_222_525_574, 0).saturating_mul(n.into())) + // Minimum execution time: 3_816_000 nanoseconds. + Weight::from_parts(3_816_000_000, 4173) + // Standard Error: 404_411_306 + .saturating_add(Weight::from_parts(1_418_273_449, 0).saturating_mul(n.into())) + // Standard Error: 194_399_346 + .saturating_add(Weight::from_parts(67_832_101, 0).saturating_mul(m.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(2)) @@ -113,14 +119,17 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `Tokens::TotalIssuance` (r:33 w:33) /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 32]`. - fn split_position_horizontal(n: u32) -> Weight { + /// The range of component `m` is `[32, 64]`. + fn split_position_horizontal(n: u32, m: u32) -> Weight { // Proof Size summary in bytes: // Measured: `633` // Estimated: `4173 + n * (2612 ±0)` - // Minimum execution time: 10_369_759 nanoseconds. - Weight::from_parts(3_384_663_612, 4173) - // Standard Error: 3_858_637 - .saturating_add(Weight::from_parts(3_549_879_971, 0).saturating_mul(n.into())) + // Minimum execution time: 4_950_000 nanoseconds. + Weight::from_parts(4_950_000_000, 4173) + // Standard Error: 407_636_194 + .saturating_add(Weight::from_parts(1_190_982_890, 0).saturating_mul(n.into())) + // Standard Error: 195_949_540 + .saturating_add(Weight::from_parts(75_730_302, 0).saturating_mul(m.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(2)) @@ -136,14 +145,17 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(132), added: 2607, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 32]`. - fn merge_position_vertical_sans_parent(n: u32) -> Weight { + /// The range of component `m` is `[32, 64]`. + fn merge_position_vertical_sans_parent(n: u32, m: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `623 + n * (159 ±0)` + // Measured: `623 + n * (160 ±0)` // Estimated: `4173 + n * (2612 ±0)` - // Minimum execution time: 6_950_049 nanoseconds. - Weight::from_parts(7_032_940_000, 4173) - // Standard Error: 8_128_230 - .saturating_add(Weight::from_parts(3_221_583_105, 0).saturating_mul(n.into())) + // Minimum execution time: 3_357_000 nanoseconds. + Weight::from_parts(3_357_000_000, 4173) + // Standard Error: 398_813_800 + .saturating_add(Weight::from_parts(1_188_250_534, 0).saturating_mul(n.into())) + // Standard Error: 191_708_641 + .saturating_add(Weight::from_parts(73_478_154, 0).saturating_mul(m.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(1)) @@ -157,14 +169,17 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `Tokens::TotalIssuance` (r:33 w:33) /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 32]`. - fn merge_position_vertical_with_parent(n: u32) -> Weight { + /// The range of component `m` is `[32, 64]`. + fn merge_position_vertical_with_parent(n: u32, m: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `515 + n * (160 ±0)` + // Measured: `520 + n * (159 ±0)` // Estimated: `4173 + n * (2612 ±0)` - // Minimum execution time: 8_233_017 nanoseconds. - Weight::from_parts(8_273_928_000, 4173) - // Standard Error: 9_495_570 - .saturating_add(Weight::from_parts(3_810_340_613, 0).saturating_mul(n.into())) + // Minimum execution time: 3_791_000 nanoseconds. + Weight::from_parts(3_791_000_000, 4173) + // Standard Error: 403_628_733 + .saturating_add(Weight::from_parts(1_426_263_672, 0).saturating_mul(n.into())) + // Standard Error: 194_023_165 + .saturating_add(Weight::from_parts(67_374_417, 0).saturating_mul(m.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(2)) @@ -178,14 +193,17 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `Tokens::TotalIssuance` (r:33 w:33) /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 32]`. - fn merge_position_horizontal(n: u32) -> Weight { + /// The range of component `m` is `[32, 64]`. + fn merge_position_horizontal(n: u32, m: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `478 + n * (160 ±0)` + // Measured: `481 + n * (159 ±0)` // Estimated: `4173 + n * (2612 ±0)` - // Minimum execution time: 10_361_313 nanoseconds. - Weight::from_parts(3_250_658_133, 4173) - // Standard Error: 4_095_907 - .saturating_add(Weight::from_parts(3_565_909_886, 0).saturating_mul(n.into())) + // Minimum execution time: 4_948_000 nanoseconds. + Weight::from_parts(4_948_000_000, 4173) + // Standard Error: 408_972_386 + .saturating_add(Weight::from_parts(1_174_151_237, 0).saturating_mul(n.into())) + // Standard Error: 196_591_844 + .saturating_add(Weight::from_parts(76_736_050, 0).saturating_mul(m.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(2)) @@ -201,14 +219,15 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(132), added: 2607, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 32]`. - fn redeem_position_sans_parent(n: u32) -> Weight { + /// The range of component `m` is `[32, 64]`. + fn redeem_position_sans_parent(n: u32, _m: u32) -> Weight { // Proof Size summary in bytes: // Measured: `780` // Estimated: `4173` - // Minimum execution time: 3_501_697 nanoseconds. - Weight::from_parts(3_567_968_354, 4173) - // Standard Error: 114_392 - .saturating_add(Weight::from_parts(159_826, 0).saturating_mul(n.into())) + // Minimum execution time: 342_000 nanoseconds. + Weight::from_parts(298_833_333, 4173) + // Standard Error: 86_602 + .saturating_add(Weight::from_parts(22_083_333, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -219,12 +238,15 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `Tokens::TotalIssuance` (r:2 w:2) /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 32]`. - fn redeem_position_with_parent(_n: u32) -> Weight { + /// The range of component `m` is `[32, 64]`. + fn redeem_position_with_parent(n: u32, _m: u32) -> Weight { // Proof Size summary in bytes: // Measured: `674` // Estimated: `6214` - // Minimum execution time: 4_123_619 nanoseconds. - Weight::from_parts(4_201_426_623, 6214) + // Minimum execution time: 572_000 nanoseconds. + Weight::from_parts(549_299_999, 6214) + // Standard Error: 202_072 + .saturating_add(Weight::from_parts(21_850_000, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(4)) } diff --git a/zrml/futarchy/src/dispatchable_impls.rs b/zrml/futarchy/src/dispatchable_impls.rs index ea786f4ef..0fd409e11 100644 --- a/zrml/futarchy/src/dispatchable_impls.rs +++ b/zrml/futarchy/src/dispatchable_impls.rs @@ -15,7 +15,7 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . -use crate::{types::Proposal, Config, Error, Event, Pallet, Proposals}; +use crate::{traits::ProposalStorage, types::Proposal, Config, Error, Event, Pallet}; use frame_support::{ensure, require_transactional, traits::Get}; use frame_system::pallet_prelude::BlockNumberFor; use sp_runtime::{DispatchResult, Saturating}; @@ -31,12 +31,10 @@ impl Pallet { let now = frame_system::Pallet::::block_number(); let to_be_scheduled_at = now.saturating_add(duration); - let try_mutate_result = Proposals::::try_mutate(to_be_scheduled_at, |proposals| { - proposals.try_push(proposal.clone()).map_err(|_| Error::::CacheFull) - }); + as ProposalStorage>::add(to_be_scheduled_at, proposal.clone())?; Self::deposit_event(Event::::Submitted { duration, proposal }); - Ok(try_mutate_result?) + Ok(()) } } diff --git a/zrml/futarchy/src/lib.rs b/zrml/futarchy/src/lib.rs index 500c61457..fca677860 100644 --- a/zrml/futarchy/src/lib.rs +++ b/zrml/futarchy/src/lib.rs @@ -42,6 +42,7 @@ mod benchmarking; mod dispatchable_impls; pub mod mock; mod pallet_impls; +mod proposal_storage; mod tests; pub mod traits; pub mod types; @@ -51,21 +52,21 @@ pub use pallet::*; #[frame_support::pallet] mod pallet { - use crate::{types::Proposal, weights::WeightInfoZeitgeist}; + use crate::{traits::ProposalStorage, types::Proposal, weights::WeightInfoZeitgeist}; use alloc::fmt::Debug; use core::marker::PhantomData; use frame_support::{ - pallet_prelude::{EnsureOrigin, IsType, StorageMap, StorageVersion, ValueQuery, Weight}, + pallet_prelude::{IsType, StorageMap, StorageValue, StorageVersion, ValueQuery, Weight}, traits::{schedule::v3::Anon as ScheduleAnon, Bounded, Hooks, OriginTrait}, transactional, Blake2_128Concat, BoundedVec, }; - use frame_system::pallet_prelude::{BlockNumberFor, OriginFor}; + use frame_system::{ + ensure_root, + pallet_prelude::{BlockNumberFor, OriginFor}, + }; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; - use sp_runtime::{ - traits::{ConstU32, Get}, - DispatchResult, - }; + use sp_runtime::{traits::Get, DispatchResult, SaturatedConversion}; use zeitgeist_primitives::traits::FutarchyOracle; #[cfg(feature = "runtime-benchmarks")] @@ -76,10 +77,13 @@ mod pallet { #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper: FutarchyBenchmarkHelper; + /// The maximum number of proposals allowed to be in flight simultaneously. + type MaxProposals: Get; + type MinDuration: Get>; // The type used to define the oracle for each proposal. - type Oracle: FutarchyOracle + type Oracle: FutarchyOracle> + Clone + Debug + Decode @@ -94,9 +98,6 @@ mod pallet { /// Scheduler interface for executing proposals. type Scheduler: ScheduleAnon, CallOf, PalletsOriginOf>; - /// The origin that is allowed to submit proposals. - type SubmitOrigin: EnsureOrigin; - type WeightInfo: WeightInfoZeitgeist; } @@ -104,23 +105,21 @@ mod pallet { #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(PhantomData); - pub(crate) type CacheSize = ConstU32<16>; pub(crate) type CallOf = ::RuntimeCall; pub(crate) type BoundedCallOf = Bounded>; pub(crate) type OracleOf = ::Oracle; pub(crate) type PalletsOriginOf = <::RuntimeOrigin as OriginTrait>::PalletsOrigin; + pub(crate) type ProposalsOf = BoundedVec, ::MaxProposals>; pub(crate) const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); #[pallet::storage] - pub type Proposals = StorageMap< - _, - Blake2_128Concat, - BlockNumberFor, - BoundedVec, CacheSize>, - ValueQuery, - >; + pub type Proposals = + StorageMap<_, Blake2_128Concat, BlockNumberFor, ProposalsOf, ValueQuery>; + + #[pallet::storage] + pub type ProposalCount = StorageValue<_, u32, ValueQuery>; #[pallet::event] #[pallet::generate_deposit(pub(crate) fn deposit_event)] @@ -148,6 +147,9 @@ mod pallet { /// The specified duration must be at least equal to `MinDuration`. DurationTooShort, + + /// This is a logic error. You shouldn't see this. + UnexpectedStorageFailure, } #[pallet::call] @@ -160,7 +162,7 @@ mod pallet { duration: BlockNumberFor, proposal: Proposal, ) -> DispatchResult { - T::SubmitOrigin::ensure_origin(origin)?; + ensure_root(origin)?; Self::do_submit_proposal(duration, proposal) } @@ -169,10 +171,34 @@ mod pallet { #[pallet::hooks] impl Hooks> for Pallet { fn on_initialize(now: BlockNumberFor) -> Weight { - let mut total_weight = Weight::zero(); // Add buffer. + let mut total_weight = Weight::zero(); + + // Update all oracles. + let mutate_all_result = + as ProposalStorage>::mutate_all(|p| p.oracle.update(now)); + if let Ok(block_to_weights) = mutate_all_result { + // We did one storage read per vector cached. Shouldn't saturate, but technically + // might. + let reads: u64 = block_to_weights.len().saturated_into(); + total_weight = total_weight.saturating_add(T::DbWeight::get().reads(reads)); + + for weights in block_to_weights.values() { + for &weight in weights.iter() { + total_weight = total_weight.saturating_add(weight); + } + } + } else { + // Unreachable! + return total_weight; + } - let proposals = Proposals::::take(now); + // total_weight = total_weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); + let proposals = if let Ok(proposals) = as ProposalStorage>::take(now) { + proposals + } else { + return total_weight; + }; for proposal in proposals.into_iter() { let weight = Self::maybe_schedule_proposal(proposal); diff --git a/zrml/futarchy/src/mock/runtime.rs b/zrml/futarchy/src/mock/runtime.rs index 87dc92f29..0fc2e69cf 100644 --- a/zrml/futarchy/src/mock/runtime.rs +++ b/zrml/futarchy/src/mock/runtime.rs @@ -21,7 +21,7 @@ use crate::{ weights::WeightInfo, }; use frame_support::{construct_runtime, parameter_types, traits::Everything}; -use frame_system::{mocking::MockBlock, EnsureRoot}; +use frame_system::mocking::MockBlock; use sp_runtime::traits::{BlakeTwo256, ConstU32, IdentityLookup}; use zeitgeist_primitives::{ constants::mock::{BlockHashCount, ExistentialDeposit, MaxLocks, MaxReserves}, @@ -33,6 +33,7 @@ use crate::mock::types::MockBenchmarkHelper; parameter_types! { // zrml-futarchy + pub const MaxProposals: u32 = 16; pub const MinDuration: BlockNumber = 10; } @@ -89,10 +90,10 @@ impl pallet_balances::Config for Runtime { impl zrml_futarchy::Config for Runtime { #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = MockBenchmarkHelper; + type MaxProposals = MaxProposals; type MinDuration = MinDuration; type Oracle = MockOracle; type RuntimeEvent = RuntimeEvent; type Scheduler = MockScheduler; - type SubmitOrigin = EnsureRoot<::AccountId>; type WeightInfo = WeightInfo; } diff --git a/zrml/futarchy/src/mock/types/oracle.rs b/zrml/futarchy/src/mock/types/oracle.rs index 4b8a7f7e4..92b5e6b79 100644 --- a/zrml/futarchy/src/mock/types/oracle.rs +++ b/zrml/futarchy/src/mock/types/oracle.rs @@ -19,7 +19,8 @@ use alloc::fmt::Debug; use frame_support::pallet_prelude::Weight; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; -use zeitgeist_primitives::traits::FutarchyOracle; +use sp_runtime::traits::Zero; +use zeitgeist_primitives::{traits::FutarchyOracle, types::BlockNumber}; #[derive(Clone, Debug, Decode, Encode, Eq, MaxEncodedLen, PartialEq, TypeInfo)] pub struct MockOracle { @@ -40,7 +41,13 @@ impl MockOracle { } impl FutarchyOracle for MockOracle { + type BlockNumber = BlockNumber; + fn evaluate(&self) -> (Weight, bool) { (self.weight, self.value) } + + fn update(&mut self, _: Self::BlockNumber) -> Weight { + Zero::zero() + } } diff --git a/zrml/futarchy/src/pallet_impls.rs b/zrml/futarchy/src/pallet_impls.rs index 9ab3fe5ce..c5019ad6d 100644 --- a/zrml/futarchy/src/pallet_impls.rs +++ b/zrml/futarchy/src/pallet_impls.rs @@ -23,6 +23,10 @@ use frame_support::{ }; use zeitgeist_primitives::traits::FutarchyOracle; +// Following Parity's implementation of pallet-democracy, we're using minimum priority for futarchy +// proposals. +const SCHEDULE_PRIORITY: u8 = 63; + impl Pallet { /// Evaluates `proposal` using the specified oracle and schedules the contained call if the /// oracle approves. @@ -33,7 +37,7 @@ impl Pallet { let result = T::Scheduler::schedule( DispatchTime::At(proposal.when), None, - 63, + SCHEDULE_PRIORITY, RawOrigin::Root.into(), proposal.call.clone(), ); diff --git a/zrml/futarchy/src/proposal_storage.rs b/zrml/futarchy/src/proposal_storage.rs new file mode 100644 index 000000000..6a0fc0a10 --- /dev/null +++ b/zrml/futarchy/src/proposal_storage.rs @@ -0,0 +1,88 @@ +use crate::{ + traits::ProposalStorage, types::Proposal, Config, Error, Pallet, ProposalCount, Proposals, + ProposalsOf, +}; +use alloc::{collections::BTreeMap, vec, vec::Vec}; +use frame_support::{ensure, require_transactional, traits::Get}; +use frame_system::pallet_prelude::BlockNumberFor; +use sp_runtime::{DispatchError, SaturatedConversion}; +use zeitgeist_primitives::math::checked_ops_res::{CheckedIncRes, CheckedSubRes}; + +impl ProposalStorage for Pallet +where + T: Config, +{ + fn count() -> u32 { + ProposalCount::::get() + } + + #[require_transactional] + fn add(block_number: BlockNumberFor, proposal: Proposal) -> Result<(), DispatchError> { + let proposal_count = ProposalCount::::get(); + ensure!(proposal_count < T::MaxProposals::get(), Error::::CacheFull); + + let new_proposal_count = proposal_count.checked_inc_res()?; + ProposalCount::::put(new_proposal_count); + + // Can't error unless state is invalid. + let mutate_result = Proposals::::try_mutate(block_number, |proposals| { + proposals.try_push(proposal).map_err(|_| Error::::CacheFull) + }); + + Ok(mutate_result?) + } + + /// Take all proposals scheduled at `block_number`. + fn take(block_number: BlockNumberFor) -> Result, DispatchError> { + let proposals = Proposals::::take(block_number); + + // Can't error unless state is invalid. + let proposal_count = ProposalCount::::get(); + let proposals_len: u32 = proposals.len().try_into().map_err(|_| Error::::CacheFull)?; + let new_proposal_count = proposal_count.checked_sub_res(&proposals_len)?; + ProposalCount::::put(new_proposal_count); + + Ok(proposals) + } + + /// Returns all proposals scheduled at `block_number`. + fn get(block_number: BlockNumberFor) -> ProposalsOf { + Proposals::::get(block_number) + } + + fn mutate_all( + mut mutator: F, + ) -> Result, Vec>, DispatchError> + where + F: FnMut(&mut Proposal) -> R, + { + // Collect keys to avoid iterating over the keys whilst modifying the map. Won't saturate + // unless `usize` has fewer bits than `u32` for some reason. + let keys: Vec<_> = + Proposals::::iter_keys().take(T::MaxProposals::get().saturated_into()).collect(); + + let mut result_map = BTreeMap::new(); + + for k in keys.into_iter() { + let proposals = Self::get(k); + + let mut results = vec![]; + + // If mutation goes out of bounds, we've clearly failed. + let proposals = proposals + .try_mutate(|v| { + for p in v.iter_mut() { + let r = mutator(p); + results.push(r); + } + }) + .ok_or(Error::::UnexpectedStorageFailure)?; + + result_map.insert(k, results); + + Proposals::::insert(k, proposals); + } + + Ok(result_map) + } +} diff --git a/zrml/futarchy/src/tests/mod.rs b/zrml/futarchy/src/tests/mod.rs index a9cdbd360..e3bc8d409 100644 --- a/zrml/futarchy/src/tests/mod.rs +++ b/zrml/futarchy/src/tests/mod.rs @@ -27,14 +27,14 @@ use crate::{ utility, }, types::Proposal, - CacheSize, Config, Error, Event, Proposals, + Config, Error, Event, Proposals, ProposalsOf, }; use frame_support::{ assert_noop, assert_ok, dispatch::RawOrigin, traits::{schedule::DispatchTime, Bounded}, }; -use sp_runtime::{traits::Get, BoundedVec, DispatchError}; +use sp_runtime::DispatchError; /// Utility struct for managing test accounts. pub(crate) struct Account { diff --git a/zrml/futarchy/src/tests/submit_proposal.rs b/zrml/futarchy/src/tests/submit_proposal.rs index 3eabcfafe..df0ece080 100644 --- a/zrml/futarchy/src/tests/submit_proposal.rs +++ b/zrml/futarchy/src/tests/submit_proposal.rs @@ -134,9 +134,9 @@ fn submit_proposal_fails_if_cache_is_full() { // Mock up a full vector of proposals. let now = System::block_number(); let to_be_scheduled_at = now + duration; - let cache_size: u32 = >>::get().unwrap(); - let proposals_vec = vec![proposal.clone(); cache_size as usize]; - let proposals: BoundedVec<_, CacheSize> = proposals_vec.try_into().unwrap(); + let max_proposals: u32 = ::MaxProposals::get(); + let proposals_vec = vec![proposal.clone(); max_proposals as usize]; + let proposals: ProposalsOf = proposals_vec.try_into().unwrap(); Proposals::::insert(to_be_scheduled_at, proposals); assert_noop!( diff --git a/zrml/futarchy/src/traits/mod.rs b/zrml/futarchy/src/traits/mod.rs index 1032ee726..d995c8264 100644 --- a/zrml/futarchy/src/traits/mod.rs +++ b/zrml/futarchy/src/traits/mod.rs @@ -14,3 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . + +mod proposal_storage; + +pub(crate) use proposal_storage::ProposalStorage; diff --git a/zrml/futarchy/src/traits/proposal_storage.rs b/zrml/futarchy/src/traits/proposal_storage.rs new file mode 100644 index 000000000..f26220bc9 --- /dev/null +++ b/zrml/futarchy/src/traits/proposal_storage.rs @@ -0,0 +1,28 @@ +use crate::{types::Proposal, Config, ProposalsOf}; +use alloc::{collections::BTreeMap, vec::Vec}; +use frame_system::pallet_prelude::BlockNumberFor; +use sp_runtime::DispatchError; + +pub(crate) trait ProposalStorage +where + T: Config, +{ + /// Returns the number of proposals currently in flight. + #[allow(dead_code)] + fn count() -> u32; + + /// Schedule `proposal` for evaluation at `block_number`. + fn add(block_number: BlockNumberFor, proposal: Proposal) -> Result<(), DispatchError>; + + /// Take all proposals scheduled at `block_number`. + fn take(block_number: BlockNumberFor) -> Result, DispatchError>; + + /// Returns all proposals scheduled at `block_number`. + #[allow(dead_code)] + fn get(block_number: BlockNumberFor) -> ProposalsOf; + + /// Mutates all scheduled proposals. + fn mutate_all(mutator: F) -> Result, Vec>, DispatchError> + where + F: FnMut(&mut Proposal) -> R; +} diff --git a/zrml/futarchy/src/types/proposal.rs b/zrml/futarchy/src/types/proposal.rs index 84ddf6748..fa69b6401 100644 --- a/zrml/futarchy/src/types/proposal.rs +++ b/zrml/futarchy/src/types/proposal.rs @@ -21,7 +21,6 @@ use frame_system::pallet_prelude::BlockNumberFor; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; -// TODO Make config a generic, keeps things simple. #[derive( CloneNoBound, Decode, Encode, Eq, MaxEncodedLen, PartialEqNoBound, RuntimeDebugNoBound, TypeInfo, )] diff --git a/zrml/hybrid-router/src/mock.rs b/zrml/hybrid-router/src/mock.rs index 99d3bc72b..36f4dbd9b 100644 --- a/zrml/hybrid-router/src/mock.rs +++ b/zrml/hybrid-router/src/mock.rs @@ -59,7 +59,7 @@ use zeitgeist_primitives::{ MarketId, Moment, }, }; -use zrml_combinatorial_tokens::types::CryptographicIdManager; +use zrml_combinatorial_tokens::types::{CryptographicIdManager, Fuel}; #[cfg(feature = "parachain")] use { @@ -281,6 +281,7 @@ impl zrml_combinatorial_tokens::Config for Runtime { #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = NoopCombinatorialTokensBenchmarkHelper; type CombinatorialIdManager = CryptographicIdManager; + type Fuel = Fuel; type MarketCommons = MarketCommons; type MultiCurrency = AssetManager; type Payout = PredictionMarkets; diff --git a/zrml/neo-swaps/src/benchmarking.rs b/zrml/neo-swaps/src/benchmarking.rs index cd0ccc29b..6a7886844 100644 --- a/zrml/neo-swaps/src/benchmarking.rs +++ b/zrml/neo-swaps/src/benchmarking.rs @@ -21,7 +21,7 @@ use super::*; use crate::{ liquidity_tree::{traits::LiquidityTreeHelper, types::LiquidityTree}, traits::{LiquiditySharesManager, PoolOperations, PoolStorage}, - types::DecisionMarketOracle, + types::{DecisionMarketOracle, DecisionMarketOracleScoreboard}, AssetOf, BalanceOf, MarketIdOf, Pallet as NeoSwaps, Pools, MIN_SPOT_PRICE, }; use alloc::{vec, vec::Vec}; @@ -40,7 +40,7 @@ use sp_runtime::{ use zeitgeist_primitives::{ constants::{base_multiples::*, CENT}, math::fixed::{BaseProvider, FixedDiv, FixedMul, ZeitgeistBase}, - traits::{CompleteSetOperationsApi, FutarchyOracle}, + traits::{CombinatorialTokensFuel, CompleteSetOperationsApi, FutarchyOracle}, types::{Asset, Market, MarketCreation, MarketPeriod, MarketStatus, MarketType, ScoringRule}, }; use zrml_market_commons::MarketCommonsPalletApi; @@ -523,7 +523,7 @@ mod benchmarks { amount, create_spot_prices::(asset_count), CENT.saturated_into(), - false, + FuelOf::::from_total(16), )); let pool_id = 0u8.into(); @@ -578,7 +578,7 @@ mod benchmarks { amount, create_spot_prices::(asset_count), CENT.saturated_into(), - false, + FuelOf::::from_total(16), )); let pool_id = 0u8.into(); @@ -623,8 +623,9 @@ mod benchmarks { } #[benchmark] - fn deploy_combinatorial_pool(n: Linear<1, 7>) { + fn deploy_combinatorial_pool(n: Linear<1, 7>, m: Linear<32, 64>) { let market_count = n; + let total = m; let alice: T::AccountId = whitelisted_caller(); let base_asset = Asset::Ztg; @@ -644,7 +645,15 @@ mod benchmarks { let swap_fee = CENT.saturated_into(); #[extrinsic_call] - _(RawOrigin::Signed(alice), asset_count, market_ids, amount, spot_prices, swap_fee, true); + _( + RawOrigin::Signed(alice), + asset_count, + market_ids, + amount, + spot_prices, + swap_fee, + FuelOf::::from_total(total), + ); } #[benchmark] @@ -662,7 +671,13 @@ mod benchmarks { let pool = Pools::::get(market_id).unwrap(); let assets = pool.assets(); - let oracle = DecisionMarketOracle::::new(market_id, assets[0], assets[1]); + let scoreboard = DecisionMarketOracleScoreboard::::new( + Zero::zero(), + Zero::zero(), + Zero::zero(), + Zero::zero(), + ); + let oracle = DecisionMarketOracle::::new(market_id, assets[0], assets[1], scoreboard); #[block] { @@ -670,6 +685,36 @@ mod benchmarks { } } + #[benchmark] + fn decision_market_oracle_update() { + let alice = whitelisted_caller(); + let base_asset = Asset::Ztg; + let asset_count = 2; + let market_id = create_market_and_deploy_pool::( + alice, + base_asset, + asset_count, + _10.saturated_into(), + ); + + let pool = Pools::::get(market_id).unwrap(); + let assets = pool.assets(); + + let scoreboard = DecisionMarketOracleScoreboard::::new( + Zero::zero(), + Zero::zero(), + Zero::zero(), + Zero::zero(), + ); + let mut oracle = + DecisionMarketOracle::::new(market_id, assets[0], assets[1], scoreboard); + + #[block] + { + let _ = oracle.update(1u8.into()); + } + } + impl_benchmark_test_suite!( NeoSwaps, crate::mock::ExtBuilder::default().build(), diff --git a/zrml/neo-swaps/src/lib.rs b/zrml/neo-swaps/src/lib.rs index 88b45bc3a..993dec1c5 100644 --- a/zrml/neo-swaps/src/lib.rs +++ b/zrml/neo-swaps/src/lib.rs @@ -17,8 +17,8 @@ #![doc = include_str!("../README.md")] #![cfg_attr(not(feature = "std"), no_std)] -#![allow(clippy::too_many_arguments)] // TODO Try to remove this later! -#![allow(clippy::type_complexity)] // TODO Try to remove this later! +#![allow(clippy::too_many_arguments)] +#![allow(clippy::type_complexity)] extern crate alloc; @@ -86,8 +86,8 @@ mod pallet { fixed::{BaseProvider, FixedDiv, FixedMul, ZeitgeistBase}, }, traits::{ - CombinatorialTokensApi, CombinatorialTokensUnsafeApi, CompleteSetOperationsApi, - DeployPoolApi, DistributeFees, HybridRouterAmmApi, + CombinatorialTokensApi, CombinatorialTokensFuel, CombinatorialTokensUnsafeApi, + CompleteSetOperationsApi, DeployPoolApi, DistributeFees, HybridRouterAmmApi, }, types::{Asset, MarketStatus, ScoringRule}, }; @@ -119,6 +119,8 @@ mod pallet { pub(crate) type BalanceOf = <::MultiCurrency as MultiCurrency>>::Balance; pub(crate) type AssetIndexType = u16; + pub(crate) type FuelOf = + <::CombinatorialTokens as CombinatorialTokensApi>::Fuel; pub(crate) type MarketIdOf = <::MarketCommons as MarketCommonsPalletApi>::MarketId; pub(crate) type LiquidityTreeOf = LiquidityTree::MaxLiquidityTreeDepth>; @@ -741,7 +743,10 @@ mod pallet { } #[pallet::call_index(8)] - #[pallet::weight(T::WeightInfo::deploy_combinatorial_pool(asset_count.log_ceil().into()))] + #[pallet::weight(T::WeightInfo::deploy_combinatorial_pool( + asset_count.log_ceil().into(), + fuel.total(), + ))] #[transactional] pub fn deploy_combinatorial_pool( origin: OriginFor, @@ -750,7 +755,7 @@ mod pallet { amount: BalanceOf, spot_prices: Vec>, swap_fee: BalanceOf, - force_max_work: bool, + fuel: FuelOf, ) -> DispatchResult { let who = ensure_signed(origin)?; @@ -761,14 +766,7 @@ mod pallet { } ensure!(asset_count == real_asset_count, Error::::IncorrectAssetCount); - Self::do_deploy_combinatorial_pool( - who, - market_ids, - amount, - spot_prices, - swap_fee, - force_max_work, - ) + Self::do_deploy_combinatorial_pool(who, market_ids, amount, spot_prices, swap_fee, fuel) } } @@ -1008,10 +1006,7 @@ mod pallet { ) -> DispatchResult { ensure!(pool_shares_amount != Zero::zero(), Error::::ZeroAmount); - // FIXME Should this also be made part of the `PoolStorage` interface? - Pools::::try_mutate_exists(pool_id, |maybe_pool| { - let pool = - maybe_pool.as_mut().ok_or::(Error::::PoolNotFound.into())?; + ::try_mutate_exists(&pool_id, |pool| { let ratio = { let mut ratio = pool_shares_amount .bdiv_floor(pool.liquidity_shares_manager.total_shares()?)?; @@ -1047,13 +1042,14 @@ mod pallet { for asset in pool.assets().iter() { withdraw_remaining(asset)?; } - *maybe_pool = None; // Delete the storage map entry. Self::deposit_event(Event::::PoolDestroyed { who: who.clone(), pool_id, amounts_out, }); - // No need to clear `MarketIdToPoolId`. + + // Delete the pool. No need to clear `MarketIdToPoolId`. + Ok(((), true)) } else { let old_liquidity_parameter = pool.liquidity_parameter; let new_liquidity_parameter = old_liquidity_parameter @@ -1083,8 +1079,9 @@ mod pallet { amounts_out, new_liquidity_parameter, }); + + Ok(((), false)) } - Ok(()) }) } @@ -1200,13 +1197,13 @@ mod pallet { amount: BalanceOf, spot_prices: Vec>, swap_fee: BalanceOf, - force_max_work: bool, + fuel: FuelOf, ) -> DispatchResult { ensure!(swap_fee >= MIN_SWAP_FEE.saturated_into(), Error::::SwapFeeBelowMin); ensure!(swap_fee <= T::MaxSwapFee::get(), Error::::SwapFeeAboveMax); let (collection_ids, position_ids, collateral) = - Self::split_markets(who.clone(), market_ids.clone(), amount, force_max_work)?; + Self::split_markets(who.clone(), market_ids.clone(), amount, fuel)?; ensure!(spot_prices.len() == collection_ids.len(), Error::::IncorrectVecLen); ensure!( @@ -1326,6 +1323,8 @@ mod pallet { let amount_out = swap_amount_out.checked_add_res(&amount_in_minus_fees)?; ensure!(amount_out >= min_amount_out, Error::::AmountOutBelowMin); + // Using unsafe API to avoid doing work. This is perfectly safe as long as + // `pool.assets()` returns a "full set" of split tokens. T::CombinatorialTokensUnsafe::split_position_unsafe( who.clone(), pool.collateral, @@ -1456,6 +1455,8 @@ mod pallet { pool.increase_reserve(&asset, &amount_keep)?; } + // Using unsafe API to avoid doing work. This is perfectly safe as long as + // `pool.assets()` returns a "full set" of split tokens. T::CombinatorialTokensUnsafe::merge_position_unsafe( pool.account_id.clone(), pool.collateral, @@ -1568,7 +1569,7 @@ mod pallet { who: T::AccountId, market_ids: Vec>, amount: BalanceOf, - force_max_work: bool, + fuel: FuelOf, ) -> Result<(Vec, Vec>, AssetOf), DispatchError> { let markets = market_ids.iter().map(T::MarketCommons::market).collect::, _>>()?; @@ -1619,7 +1620,7 @@ mod pallet { *market_id, partition.clone(), amount, - force_max_work, + fuel.clone(), )?; collection_ids.extend_from_slice(&split_position_info.collection_ids); @@ -1641,7 +1642,7 @@ mod pallet { *market_id, partition.clone(), amount, - force_max_work, + fuel.clone(), )?; new_collection_ids.extend_from_slice(&split_position_info.collection_ids); diff --git a/zrml/neo-swaps/src/math/traits/combo_math_ops.rs b/zrml/neo-swaps/src/math/traits/combo_math_ops.rs index 69148c51d..856c96202 100644 --- a/zrml/neo-swaps/src/math/traits/combo_math_ops.rs +++ b/zrml/neo-swaps/src/math/traits/combo_math_ops.rs @@ -23,6 +23,9 @@ pub(crate) trait ComboMathOps where T: Config, { + /// Calculates the amount swapped out of a pool with liquidity parameter `liquidity` when + /// swapping in `amount_in` units of assets whose reserves in the pool are `sell` and swapping + /// out assets whose reserves in the pool are `buy`. fn calculate_swap_amount_out_for_buy( buy: Vec>, sell: Vec>, @@ -30,6 +33,7 @@ where liquidity: BalanceOf, ) -> Result, DispatchError>; + /// Calculates the amount eventually held by the user after equalizing holdings. #[allow(dead_code)] fn calculate_equalize_amount( buy: Vec>, @@ -39,6 +43,10 @@ where liquidity: BalanceOf, ) -> Result, DispatchError>; + /// Calculates the amount of each asset of a pool with liquidity parameter `liquidity` held + /// in the user's wallet after equalizing positions whose reserves in the pool are `buy`, `keep` + /// and `sell`, resp. The parameters `amount_buy` and `amount_keep` refer to the user's holdings + /// of `buy` and `keep`. fn calculate_swap_amount_out_for_sell( buy: Vec>, keep: Vec>, diff --git a/zrml/neo-swaps/src/math/types/combo_math.rs b/zrml/neo-swaps/src/math/types/combo_math.rs index bfdb4e62a..585464776 100644 --- a/zrml/neo-swaps/src/math/types/combo_math.rs +++ b/zrml/neo-swaps/src/math/types/combo_math.rs @@ -16,23 +16,12 @@ // along with Zeitgeist. If not, see . use crate::{ - math::{ - traits::ComboMathOps, - transcendental::{exp, ln}, - }, + math::{traits::ComboMathOps, transcendental::ln, types::common::FixedType}, BalanceOf, Config, Error, }; use alloc::vec::Vec; use core::marker::PhantomData; -use fixed::FixedU128; use sp_runtime::{traits::Zero, DispatchError, SaturatedConversion}; -use typenum::U80; - -type Fractional = U80; -type FixedType = FixedU128; - -/// The point at which `exp` values become too large, 32.44892769177272. -const EXP_NUMERICAL_THRESHOLD: FixedType = FixedType::from_bits(0x20_72EC_ECDA_6EBE_EACC_40C7); pub(crate) struct ComboMath(PhantomData); @@ -111,14 +100,7 @@ where mod detail { use super::*; - use zeitgeist_primitives::{ - constants::DECIMALS, - math::fixed::{IntoFixedDecimal, IntoFixedFromDecimal}, - }; - - fn to_fixed(value: u128) -> Option { - value.to_fixed_from_fixed_decimal(DECIMALS).ok() - } + use crate::math::types::common::{from_fixed, protected_exp, to_fixed}; /// Converts `Vec` of fixed decimal numbers to a `Vec` of fixed point numbers; /// returns `None` if any of them fail. @@ -126,19 +108,6 @@ mod detail { vec.into_iter().map(to_fixed).collect() } - fn from_fixed(value: FixedType) -> Option - where - B: Into + From, - { - value.to_fixed_decimal(DECIMALS).ok() - } - - /// Calculates `exp(value)` but returns `None` if `value` lies outside of the numerical - /// boundaries. - fn protected_exp(value: FixedType, neg: bool) -> Option { - if value < EXP_NUMERICAL_THRESHOLD { exp(value, neg).ok() } else { None } - } - /// Returns `\sum_{r \in R} e^{-r/b}`, where `R` denotes `reserves` and `b` denotes `liquidity`. /// The result is `None` if and only if any of the `exp` calculations has failed. fn exp_sum(reserves: Vec, liquidity: FixedType) -> Option { @@ -272,7 +241,9 @@ mod detail { ) -> Option { // Ensure that either `keep` is empty and `amount_keep` is zero, or `keep` is non-empty and // `amount_keep` is non-zero. - if keep.is_empty() && !amount_keep.is_zero() || !keep.is_empty() && amount_keep.is_zero() { + if (keep.is_empty() && !amount_keep.is_zero()) + || (!keep.is_empty() && amount_keep.is_zero()) + { return None; } diff --git a/zrml/neo-swaps/src/math/types/common.rs b/zrml/neo-swaps/src/math/types/common.rs new file mode 100644 index 000000000..e336aea75 --- /dev/null +++ b/zrml/neo-swaps/src/math/types/common.rs @@ -0,0 +1,51 @@ +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use crate::math::transcendental::exp; +use fixed::FixedU128; +use typenum::U80; +use zeitgeist_primitives::{ + constants::DECIMALS, + math::fixed::{IntoFixedDecimal, IntoFixedFromDecimal}, +}; + +type Fractional = U80; +pub(crate) type FixedType = FixedU128; + +// 32.44892769177272 +pub(crate) const EXP_NUMERICAL_THRESHOLD: FixedType = + FixedType::from_bits(0x20_72EC_ECDA_6EBE_EACC_40C7); + +pub(crate) fn to_fixed(value: B) -> Option +where + B: Into + From, +{ + value.to_fixed_from_fixed_decimal(DECIMALS).ok() +} + +pub(crate) fn from_fixed(value: FixedType) -> Option +where + B: Into + From, +{ + value.to_fixed_decimal(DECIMALS).ok() +} + +/// Calculates `exp(value)` but returns `None` if `value` lies outside of the numerical +/// boundaries. +pub(crate) fn protected_exp(value: FixedType, neg: bool) -> Option { + if value < EXP_NUMERICAL_THRESHOLD { exp(value, neg).ok() } else { None } +} diff --git a/zrml/neo-swaps/src/math/types/math.rs b/zrml/neo-swaps/src/math/types/math.rs index 68cc38ac6..e816da26e 100644 --- a/zrml/neo-swaps/src/math/types/math.rs +++ b/zrml/neo-swaps/src/math/types/math.rs @@ -18,24 +18,17 @@ use crate::{ math::{ traits::MathOps, - transcendental::{exp, ln}, + transcendental::ln, + types::common::{FixedType, EXP_NUMERICAL_THRESHOLD}, }, BalanceOf, Config, Error, }; use alloc::vec::Vec; use core::marker::PhantomData; -use fixed::FixedU128; use sp_runtime::{ traits::{One, Zero}, DispatchError, SaturatedConversion, }; -use typenum::U80; - -type Fractional = U80; -type FixedType = FixedU128; - -// 32.44892769177272 -const EXP_NUMERICAL_THRESHOLD: FixedType = FixedType::from_bits(0x20_72EC_ECDA_6EBE_EACC_40C7); pub(crate) struct Math(PhantomData); @@ -136,10 +129,7 @@ where mod detail { use super::*; - use zeitgeist_primitives::{ - constants::DECIMALS, - math::fixed::{IntoFixedDecimal, IntoFixedFromDecimal}, - }; + use crate::math::types::common::{from_fixed, protected_exp, to_fixed}; /// Calculate b * ln( e^(x/b) − 1 + e^(−r_i/b) ) + r_i − x. pub(super) fn calculate_swap_amount_out_for_buy( @@ -228,26 +218,6 @@ mod detail { from_fixed(result_fixed) } - fn to_fixed(value: B) -> Option - where - B: Into + From, - { - value.to_fixed_from_fixed_decimal(DECIMALS).ok() - } - - fn from_fixed(value: FixedType) -> Option - where - B: Into + From, - { - value.to_fixed_decimal(DECIMALS).ok() - } - - /// Calculates `exp(value)` but returns `None` if `value` lies outside of the numerical - /// boundaries. - fn protected_exp(value: FixedType, neg: bool) -> Option { - if value < EXP_NUMERICAL_THRESHOLD { exp(value, neg).ok() } else { None } - } - fn calculate_swap_amount_out_for_buy_fixed( reserve: FixedType, amount_in: FixedType, @@ -317,7 +287,7 @@ mod detail { let exp_x_over_b: FixedType = protected_exp(amount_in.checked_div(liquidity)?, false)?; let r_over_b = reserve.checked_div(liquidity)?; let exp_neg_r_over_b = if r_over_b < EXP_NUMERICAL_THRESHOLD { - protected_exp(reserve.checked_div(liquidity)?, true)? + protected_exp(r_over_b, true)? } else { FixedType::checked_from_num(0)? // Underflow to zero. }; @@ -372,7 +342,9 @@ mod tests { #![allow(clippy::duplicated_attributes)] use super::*; - use crate::{mock::Runtime as MockRuntime, MAX_SPOT_PRICE, MIN_SPOT_PRICE}; + use crate::{ + math::transcendental::exp, mock::Runtime as MockRuntime, MAX_SPOT_PRICE, MIN_SPOT_PRICE, + }; use alloc::str::FromStr; use frame_support::assert_err; use test_case::test_case; diff --git a/zrml/neo-swaps/src/math/types/mod.rs b/zrml/neo-swaps/src/math/types/mod.rs index 69628b16a..4c8a92ba6 100644 --- a/zrml/neo-swaps/src/math/types/mod.rs +++ b/zrml/neo-swaps/src/math/types/mod.rs @@ -16,6 +16,7 @@ // along with Zeitgeist. If not, see . mod combo_math; +mod common; mod math; pub(crate) use combo_math::ComboMath; diff --git a/zrml/neo-swaps/src/mock.rs b/zrml/neo-swaps/src/mock.rs index 627b7db6a..75add7a3a 100644 --- a/zrml/neo-swaps/src/mock.rs +++ b/zrml/neo-swaps/src/mock.rs @@ -64,7 +64,7 @@ use zeitgeist_primitives::{ MarketId, Moment, }, }; -use zrml_combinatorial_tokens::types::CryptographicIdManager; +use zrml_combinatorial_tokens::types::{CryptographicIdManager, Fuel}; use zrml_neo_swaps::BalanceOf; #[cfg(feature = "parachain")] @@ -268,6 +268,7 @@ impl zrml_combinatorial_tokens::Config for Runtime { #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = NoopCombinatorialTokensBenchmarkHelper; type CombinatorialIdManager = CryptographicIdManager; + type Fuel = Fuel; type MarketCommons = MarketCommons; type MultiCurrency = AssetManager; type Payout = PredictionMarkets; diff --git a/zrml/neo-swaps/src/pool_storage.rs b/zrml/neo-swaps/src/pool_storage.rs index 603b4b8a2..bccf35e74 100644 --- a/zrml/neo-swaps/src/pool_storage.rs +++ b/zrml/neo-swaps/src/pool_storage.rs @@ -16,8 +16,9 @@ // along with Zeitgeist. If not, see . use crate::{traits::PoolStorage, Config, Error, Pallet, PoolCount, PoolOf, Pools}; +use frame_support::require_transactional; use sp_runtime::DispatchError; -use zeitgeist_primitives::math::checked_ops_res::CheckedAddRes; +use zeitgeist_primitives::math::checked_ops_res::CheckedIncRes; impl PoolStorage for Pallet where @@ -26,16 +27,16 @@ where type PoolId = T::PoolId; type Pool = PoolOf; - // TODO Make `PoolId` as u32. fn next_pool_id() -> T::PoolId { PoolCount::::get() } + #[require_transactional] fn add(pool: Self::Pool) -> Result { let pool_id = Self::next_pool_id(); Pools::::insert(pool_id, pool); - let pool_count = pool_id.checked_add_res(&1u8.into())?; // TODO Add CheckedInc. + let pool_count = pool_id.checked_inc_res()?; PoolCount::::set(pool_count); Ok(pool_id) @@ -47,10 +48,26 @@ where fn try_mutate_pool(pool_id: &Self::PoolId, mutator: F) -> Result where - F: FnMut(&mut PoolOf) -> Result, + F: FnMut(&mut Self::Pool) -> Result, { Pools::::try_mutate(pool_id, |maybe_pool| { maybe_pool.as_mut().ok_or(Error::::PoolNotFound.into()).and_then(mutator) }) } + + fn try_mutate_exists(pool_id: &Self::PoolId, mutator: F) -> Result + where + F: FnMut(&mut Self::Pool) -> Result<(R, bool), DispatchError>, + { + Pools::::try_mutate_exists(pool_id, |maybe_pool| { + let (result, delete) = + maybe_pool.as_mut().ok_or(Error::::PoolNotFound.into()).and_then(mutator)?; + + if delete { + *maybe_pool = None; + } + + Ok(result) + }) + } } diff --git a/zrml/neo-swaps/src/tests/combo_buy.rs b/zrml/neo-swaps/src/tests/combo_buy.rs index df1185e45..7cdd2dd4f 100644 --- a/zrml/neo-swaps/src/tests/combo_buy.rs +++ b/zrml/neo-swaps/src/tests/combo_buy.rs @@ -362,27 +362,45 @@ fn combo_buy_fails_on_amount_out_below_min() { ALICE, BASE_ASSET, vec![MarketType::Categorical(2), MarketType::Scalar(0..=1)], - _10, + 100_000_000 * _100, // Massive liquidity to keep slippage low. vec![_1_4, _1_4, _1_4, _1_4], CENT, ); let pool = as PoolStorage>::get(pool_id).unwrap(); let assets = pool.assets(); - let amount_in = _1; + + let asset_count = 4; + let buy = assets[0..1].to_vec(); + let sell = assets[1..4].to_vec(); + // amount_in is _1 / 0.97 (i.e. _1 after deducting 3% fees - 1% trading fees, 1% external + // fees _for each market_) + let amount_in = 10_309_278_350; + assert_ok!(AssetManager::deposit(BASE_ASSET, &BOB, amount_in)); - // Buying 1 at price of .25 will return less than 4 outcomes due to slippage. + // Buying for 1 at a price of .25 will return less than 4 outcomes due to slippage. assert_noop!( NeoSwaps::combo_buy( RuntimeOrigin::signed(BOB), pool_id, - 4, - vec![assets[0]], - vec![assets[1]], + asset_count, + buy.clone(), + sell.clone(), amount_in, _4, ), Error::::AmountOutBelowMin, ); + + // Post OakSecurity audit: Show that the slippage limit is tight. + assert_ok!(NeoSwaps::combo_buy( + RuntimeOrigin::signed(BOB), + pool_id, + asset_count, + buy, + sell, + amount_in, + _4 - 33, + )); }); } diff --git a/zrml/neo-swaps/src/tests/deploy_combinatorial_pool.rs b/zrml/neo-swaps/src/tests/deploy_combinatorial_pool.rs index e58ccc5a5..2037c2f3e 100644 --- a/zrml/neo-swaps/src/tests/deploy_combinatorial_pool.rs +++ b/zrml/neo-swaps/src/tests/deploy_combinatorial_pool.rs @@ -235,7 +235,7 @@ fn deploy_combinatorial_pool_fails_on_incorrect_vec_len() { _10, vec![20 * CENT; 5], CENT, - false, + Fuel::new(16, false), ), Error::::IncorrectVecLen ); @@ -257,7 +257,7 @@ fn deploy_combinatorial_pool_fails_on_market_not_found() { _10, vec![10 * CENT; 10], CENT, - false, + Fuel::new(16, false), ), zrml_market_commons::Error::::MarketDoesNotExist, ); @@ -288,7 +288,7 @@ fn deploy_combinatorial_pool_fails_on_inactive_market(market_status: MarketStatu _100, vec![10 * CENT; 10], CENT, - false, + Fuel::new(16, false), ), Error::::MarketNotActive, ); @@ -310,7 +310,7 @@ fn deploy_combinatorial_pool_fails_on_invalid_trading_mechanism() { _100, vec![10 * CENT; 10], CENT, - false, + Fuel::new(16, false), ), Error::::InvalidTradingMechanism ); @@ -348,7 +348,7 @@ fn deploy_combinatorial_pool_fails_on_max_splits_exceeded() { liquidity, spot_prices, CENT, - false, + Fuel::new(16, false), ), Error::::MaxSplitsExceeded ); @@ -369,7 +369,7 @@ fn deploy_combinatorial_pool_fails_on_swap_fee_below_min() { liquidity, vec![_1_4, _3_4], MIN_SWAP_FEE - 1, - false, + Fuel::new(16, false), ), Error::::SwapFeeBelowMin ); @@ -390,7 +390,7 @@ fn deploy_combinatorial_pool_fails_on_swap_fee_above_max() { liquidity, vec![_1_4, _3_4], ::MaxSwapFee::get() + 1, - false, + Fuel::new(16, false), ), Error::::SwapFeeAboveMax ); @@ -412,7 +412,7 @@ fn deploy_combinatorial_pool_fails_on_invalid_spot_prices(spot_prices: Vec::InvalidSpotPrices ); @@ -434,7 +434,7 @@ fn deploy_combinatorial_pool_fails_on_spot_price_below_min() { liquidity, vec![spot_price, _1 - spot_price], CENT, - false, + Fuel::new(16, false), ), Error::::SpotPriceBelowMin ); @@ -456,7 +456,7 @@ fn deploy_combinatorial_pool_fails_on_spot_price_above_max() { liquidity, vec![spot_price, _1 - spot_price], CENT, - false, + Fuel::new(16, false), ), Error::::SpotPriceAboveMax ); @@ -483,7 +483,7 @@ fn deploy_combinatorial_pool_fails_on_insufficient_funds() { liquidity, vec![_3_4, _1_4], CENT, - false, + Fuel::new(16, false), ), expected_error ); @@ -504,7 +504,7 @@ fn deploy_combinatorial_pool_fails_on_liquidity_too_low() { amount, vec![_1_2, _1_2], CENT, - false, + Fuel::new(16, false), ), Error::::LiquidityTooLow ); @@ -528,7 +528,7 @@ fn deploy_combinatorial_pool_fails_on_incorrect_asset_count() { amount, vec![_1_2, _1_2], // Incorrect, but doesn't matter! CENT, - false, + Fuel::new(16, false), ), Error::::IncorrectAssetCount, ); diff --git a/zrml/neo-swaps/src/tests/mod.rs b/zrml/neo-swaps/src/tests/mod.rs index 15ba59d97..56493b141 100644 --- a/zrml/neo-swaps/src/tests/mod.rs +++ b/zrml/neo-swaps/src/tests/mod.rs @@ -41,6 +41,7 @@ use zeitgeist_primitives::{ MarketType, MultiHash, ScalarPosition, ScoringRule, }, }; +use zrml_combinatorial_tokens::types::Fuel; use zrml_market_commons::{MarketCommonsPalletApi, Markets}; #[cfg(not(feature = "parachain"))] @@ -128,7 +129,7 @@ fn create_markets_and_deploy_combinatorial_pool( amount, spot_prices.clone(), swap_fee, - false, + Fuel::new(16, false), )); (market_ids, pool_id) diff --git a/zrml/neo-swaps/src/traits/pool_storage.rs b/zrml/neo-swaps/src/traits/pool_storage.rs index 805e0d847..6553a39d7 100644 --- a/zrml/neo-swaps/src/traits/pool_storage.rs +++ b/zrml/neo-swaps/src/traits/pool_storage.rs @@ -17,6 +17,8 @@ use sp_runtime::DispatchError; +/// Slot map interface for pool storage. Undocumented functions behave like their counterparts in +/// substrate's `StorageMap`. pub(crate) trait PoolStorage { type PoolId; type Pool; @@ -30,4 +32,11 @@ pub(crate) trait PoolStorage { fn try_mutate_pool(pool_id: &Self::PoolId, mutator: F) -> Result where F: FnMut(&mut Self::Pool) -> Result; + + /// Mutate and maybe remove the pool indexed by `pool_id`. Unlike `try_mutate_exists` in + /// `StorageMap`, the `mutator` must return a `(R, bool)`. If and only if the pool is positive, + /// the pool is removed. + fn try_mutate_exists(pool_id: &Self::PoolId, mutator: F) -> Result + where + F: FnMut(&mut Self::Pool) -> Result<(R, bool), DispatchError>; } diff --git a/zrml/neo-swaps/src/types/decision_market_benchmark_helper.rs b/zrml/neo-swaps/src/types/decision_market_benchmark_helper.rs index a0a4363af..a3841345a 100644 --- a/zrml/neo-swaps/src/types/decision_market_benchmark_helper.rs +++ b/zrml/neo-swaps/src/types/decision_market_benchmark_helper.rs @@ -19,7 +19,7 @@ use crate::{ liquidity_tree::types::LiquidityTree, - types::{DecisionMarketOracle, Pool, PoolType}, + types::{DecisionMarketOracle, DecisionMarketOracleScoreboard, Pool, PoolType}, BalanceOf, Config, MarketIdOf, Pallet, Pools, }; use alloc::{collections::BTreeMap, vec}; @@ -58,6 +58,13 @@ where reserves.insert(negative_outcome, one); } + let scoreboard = DecisionMarketOracleScoreboard::new( + Zero::zero(), + Zero::zero(), + Zero::zero(), + Zero::zero(), + ); + let account_id: T::AccountId = Pallet::::pool_account_id(&pool_id); let pool = Pool { account_id: account_id.clone(), @@ -72,6 +79,6 @@ where Pools::::insert(pool_id, pool); - DecisionMarketOracle::new(pool_id, positive_outcome, negative_outcome) + DecisionMarketOracle::new(pool_id, positive_outcome, negative_outcome, scoreboard) } } diff --git a/zrml/neo-swaps/src/types/decision_market_oracle.rs b/zrml/neo-swaps/src/types/decision_market_oracle.rs index 2d27f247b..b7650326b 100644 --- a/zrml/neo-swaps/src/types/decision_market_oracle.rs +++ b/zrml/neo-swaps/src/types/decision_market_oracle.rs @@ -15,13 +15,21 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . -use crate::{traits::PoolOperations, weights::WeightInfoZeitgeist, AssetOf, Config, Error, Pools}; +use crate::{ + traits::PoolOperations, types::DecisionMarketOracleScoreboard, weights::WeightInfoZeitgeist, + AssetOf, BalanceOf, Config, Error, Pools, +}; use frame_support::pallet_prelude::Weight; +use frame_system::pallet_prelude::BlockNumberFor; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; use sp_runtime::DispatchError; use zeitgeist_primitives::traits::FutarchyOracle; +/// Struct that implements `FutarchyOracle` using price measurements from liquidity pools. +/// +/// The oracle evaluates to `true` if and only if the `positive_outcome` is more valuable than the +/// `negative_outcome` in the liquidity pool specified by `pool_id`. #[derive(Clone, Debug, Decode, Encode, Eq, MaxEncodedLen, PartialEq, TypeInfo)] pub struct DecisionMarketOracle where @@ -30,6 +38,7 @@ where pool_id: T::PoolId, positive_outcome: AssetOf, negative_outcome: AssetOf, + scoreboard: DecisionMarketOracleScoreboard, } impl DecisionMarketOracle @@ -40,22 +49,19 @@ where pool_id: T::PoolId, positive_outcome: AssetOf, negative_outcome: AssetOf, + scoreboard: DecisionMarketOracleScoreboard, ) -> Self { - Self { pool_id, positive_outcome, negative_outcome } + Self { pool_id, positive_outcome, negative_outcome, scoreboard } } - // Utility implementation that uses the question mark operator to implement a fallible version - // of `evaluate`. - fn try_evaluate(&self) -> Result { + fn try_get_prices(&self) -> Result<(BalanceOf, BalanceOf), DispatchError> { let pool = Pools::::get(self.pool_id) .ok_or::(Error::::PoolNotFound.into())?; let positive_value = pool.calculate_spot_price(self.positive_outcome)?; let negative_value = pool.calculate_spot_price(self.negative_outcome)?; - let success = positive_value > negative_value; - - Ok(success) + Ok((positive_value, negative_value)) } } @@ -63,11 +69,21 @@ impl FutarchyOracle for DecisionMarketOracle where T: Config, { + type BlockNumber = BlockNumberFor; + fn evaluate(&self) -> (Weight, bool) { - // Err on the side of caution if the pool is not found or a calculation fails by not - // enacting the policy. - let value = self.try_evaluate().unwrap_or(false); + (T::WeightInfo::decision_market_oracle_evaluate(), self.scoreboard.evaluate()) + } + + fn update(&mut self, now: Self::BlockNumber) -> Weight { + if let Ok((positive_outcome_price, negative_outcome_price)) = self.try_get_prices() { + self.scoreboard.update(now, positive_outcome_price, negative_outcome_price); + } else { + // Err on the side of caution if the pool is not found or a calculation fails by not + // enacting the policy. + self.scoreboard.skip_update(now); + } - (T::WeightInfo::decision_market_oracle_evaluate(), value) + T::WeightInfo::decision_market_oracle_update() } } diff --git a/zrml/neo-swaps/src/types/decision_market_oracle_scoreboard.rs b/zrml/neo-swaps/src/types/decision_market_oracle_scoreboard.rs new file mode 100644 index 000000000..1f718a35c --- /dev/null +++ b/zrml/neo-swaps/src/types/decision_market_oracle_scoreboard.rs @@ -0,0 +1,100 @@ +use crate::{BalanceOf, Config}; +use frame_system::pallet_prelude::BlockNumberFor; +use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; +use sp_runtime::{traits::Zero, Saturating}; +use zeitgeist_primitives::math::fixed::FixedDiv; + +/// Records until the end of time. +#[derive(Clone, Debug, Decode, Encode, Eq, MaxEncodedLen, PartialEq, TypeInfo)] +pub struct DecisionMarketOracleScoreboard +where + T: Config, +{ + /// The block at which the oracle records its first tick. + start: BlockNumberFor, + + /// The number of ticks the positive outcome requires to have + victory_margin: u128, + + /// The absolute minimum difference in prices required for the positive outcome to receive a + /// point. + price_margin_abs: BalanceOf, + + /// The relative minimum difference in prices required for the positive outcome to receive a + /// point, specified as fractional (i.e. 0.1 represents 10%). + price_margin_rel: BalanceOf, + + /// The number of ticks for the positive outcome. + pass_score: u128, + + /// The number of ticks for the negative outcome. + reject_score: u128, +} + +impl DecisionMarketOracleScoreboard +where + T: Config, +{ + pub fn new( + start: BlockNumberFor, + victory_margin: u128, + price_margin_abs: BalanceOf, + price_margin_rel: BalanceOf, + ) -> DecisionMarketOracleScoreboard { + DecisionMarketOracleScoreboard { + start, + victory_margin, + price_margin_abs, + price_margin_rel, + pass_score: 0, + reject_score: 0, + } + } + + pub fn update( + &mut self, + now: BlockNumberFor, + positive_outcome_price: BalanceOf, + negative_outcome_price: BalanceOf, + ) { + if now < self.start { + return; + } + + // Saturation is fine as that just means that the negative outcome is more valuable than the + // positive outcome. + let margin_abs = positive_outcome_price.saturating_sub(negative_outcome_price); + // In case of error, we're using zero here as a defensive default value. + let margin_rel = margin_abs.bdiv(negative_outcome_price).unwrap_or(Zero::zero()); + + if margin_abs >= self.price_margin_abs && margin_rel >= self.price_margin_rel { + // Saturation is fine as that would mean the oracle has been collecting data for + // hundreds of years. + self.pass_score.saturating_inc(); + } else { + // Saturation is fine as that would mean the oracle has been collecting data for + // hundreds of years. + self.reject_score.saturating_inc(); + } + } + + pub fn evaluate(&self) -> bool { + // Saturating is fine as that just means that the `reject_score` is higher than `pass_score` + // anyways. + let score_margin = self.pass_score.saturating_sub(self.reject_score); + + score_margin >= self.victory_margin + } + + /// Skips update on this block and awards a point to the negative outcome. + pub fn skip_update(&mut self, now: BlockNumberFor) { + if now < self.start { + return; + } + + // Saturation is fine as that would mean the oracle has been collecting data for + // hundreds of years. + self.reject_score.saturating_inc(); + } +} diff --git a/zrml/neo-swaps/src/types/mod.rs b/zrml/neo-swaps/src/types/mod.rs index 057dcd204..5d7723ede 100644 --- a/zrml/neo-swaps/src/types/mod.rs +++ b/zrml/neo-swaps/src/types/mod.rs @@ -17,6 +17,7 @@ mod decision_market_benchmark_helper; mod decision_market_oracle; +mod decision_market_oracle_scoreboard; mod fee_distribution; mod max_assets; mod pool; @@ -25,6 +26,7 @@ mod pool_type; #[cfg(feature = "runtime-benchmarks")] pub use decision_market_benchmark_helper::*; pub use decision_market_oracle::*; +pub use decision_market_oracle_scoreboard::*; pub(crate) use fee_distribution::*; pub(crate) use max_assets::*; pub(crate) use pool::*; diff --git a/zrml/neo-swaps/src/utility.rs b/zrml/neo-swaps/src/utility.rs index 62103304b..58a0e8792 100644 --- a/zrml/neo-swaps/src/utility.rs +++ b/zrml/neo-swaps/src/utility.rs @@ -15,9 +15,13 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . -use sp_runtime::SaturatedConversion; +use sp_runtime::{ + traits::{One, Zero}, + SaturatedConversion, +}; pub(crate) trait LogCeil { + /// Calculates the ceil of the log with base 2 of `self`. fn log_ceil(&self) -> Self; } @@ -25,10 +29,45 @@ impl LogCeil for u16 { fn log_ceil(&self) -> Self { let x = *self; - let bits_minus_one = u16::MAX.saturating_sub(1); + if x.is_zero() { + return One::one(); + } + + let bits_minus_one: u16 = u16::BITS.saturating_sub(1).saturated_into(); let leading_zeros: u16 = x.leading_zeros().saturated_into(); let floor_log2 = bits_minus_one.saturating_sub(leading_zeros); if x.is_power_of_two() { floor_log2 } else { floor_log2.saturating_add(1) } } } + +#[cfg(test)] +mod tests { + use super::*; + use test_case::test_case; + + #[test_case(0, 1)] + #[test_case(1, 0)] + #[test_case(2, 1)] + #[test_case(3, 2)] + #[test_case(4, 2)] + #[test_case(5, 3)] + #[test_case(6, 3)] + #[test_case(7, 3)] + #[test_case(8, 3)] + #[test_case(9, 4)] + #[test_case(15, 4)] + #[test_case(16, 4)] + #[test_case(17, 5)] + #[test_case(1023, 10)] + #[test_case(1024, 10)] + #[test_case(1025, 11)] + #[test_case(32767, 15)] + #[test_case(32768, 15)] + #[test_case(32769, 16)] + #[test_case(65535, 16)] + fn log_ceil_works(value: u16, expected: u16) { + let actual = value.log_ceil(); + assert_eq!(actual, expected); + } +} diff --git a/zrml/neo-swaps/src/weights.rs b/zrml/neo-swaps/src/weights.rs index ef359b861..aabda3571 100644 --- a/zrml/neo-swaps/src/weights.rs +++ b/zrml/neo-swaps/src/weights.rs @@ -19,21 +19,21 @@ //! Autogenerated weights for zrml_neo_swaps //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2024-10-31`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2024-12-10`, STEPS: `2`, REPEAT: `0`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `ztg-benchmark`, CPU: `AMD EPYC 7601 32-Core Processor` +//! HOSTNAME: `Mac`, CPU: `` //! EXECUTION: ``, WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` // Executed Command: -// ./target/production/zeitgeist +// ./target/release/zeitgeist // benchmark // pallet // --chain=dev -// --steps=50 -// --repeat=20 +// --steps=2 +// --repeat=0 // --pallet=zrml_neo_swaps // --extrinsic=* -// --execution=wasm +// --execution=native // --wasm-execution=compiled // --heap-pages=4096 // --template=./misc/weight_template.hbs @@ -59,8 +59,9 @@ pub trait WeightInfoZeitgeist { fn deploy_pool(n: u32) -> Weight; fn combo_buy(n: u32) -> Weight; fn combo_sell(n: u32) -> Weight; - fn deploy_combinatorial_pool(n: u32) -> Weight; + fn deploy_combinatorial_pool(n: u32, m: u32) -> Weight; fn decision_market_oracle_evaluate() -> Weight; + fn decision_market_oracle_update() -> Weight; } /// Weight functions for zrml_neo_swaps (automatically generated) @@ -77,19 +78,14 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `Tokens::TotalIssuance` (r:128 w:128) /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 128]`. - fn buy(n: u32) -> Weight { + fn buy(_n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `1372 + n * (182 ±0)` - // Estimated: `156294 + n * (2612 ±0)` - // Minimum execution time: 402_539 nanoseconds. - Weight::from_parts(302_340_193, 156294) - // Standard Error: 92_254 - .saturating_add(Weight::from_parts(53_376_748, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(5)) - .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(n.into()))) - .saturating_add(Weight::from_parts(0, 2612).saturating_mul(n.into())) + // Measured: `1335 + n * (183 ±0)` + // Estimated: `337938` + // Minimum execution time: 231_000 nanoseconds. + Weight::from_parts(3_880_000_000, 337938) + .saturating_add(T::DbWeight::get().reads(262)) + .saturating_add(T::DbWeight::get().writes(261)) } /// Storage: `NeoSwaps::Pools` (r:1 w:1) /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(152829), added: 155304, mode: `MaxEncodedLen`) @@ -102,19 +98,14 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `Tokens::TotalIssuance` (r:128 w:128) /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 128]`. - fn sell(n: u32) -> Weight { + fn sell(_n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `1503 + n * (182 ±0)` - // Estimated: `156294 + n * (2612 ±0)` - // Minimum execution time: 337_007 nanoseconds. - Weight::from_parts(250_443_677, 156294) - // Standard Error: 116_699 - .saturating_add(Weight::from_parts(60_461_360, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(5)) - .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(n.into()))) - .saturating_add(Weight::from_parts(0, 2612).saturating_mul(n.into())) + // Measured: `1466 + n * (183 ±0)` + // Estimated: `337938` + // Minimum execution time: 207_000 nanoseconds. + Weight::from_parts(4_459_000_000, 337938) + .saturating_add(T::DbWeight::get().reads(262)) + .saturating_add(T::DbWeight::get().writes(261)) } /// Storage: `NeoSwaps::Pools` (r:1 w:1) /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(152829), added: 155304, mode: `MaxEncodedLen`) @@ -125,19 +116,14 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `System::Account` (r:1 w:0) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(132), added: 2607, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 128]`. - fn join_in_place(n: u32) -> Weight { + fn join_in_place(_n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `139400 + n * (216 ±0)` - // Estimated: `156294 + n * (5224 ±0)` - // Minimum execution time: 396_540 nanoseconds. - Weight::from_parts(350_023_672, 156294) - // Standard Error: 157_275 - .saturating_add(Weight::from_parts(31_812_304, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(1)) - .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(n.into()))) - .saturating_add(Weight::from_parts(0, 5224).saturating_mul(n.into())) + // Measured: `139361 + n * (217 ±0)` + // Estimated: `669662` + // Minimum execution time: 235_000 nanoseconds. + Weight::from_parts(2_362_000_000, 669662) + .saturating_add(T::DbWeight::get().reads(259)) + .saturating_add(T::DbWeight::get().writes(257)) } /// Storage: `NeoSwaps::Pools` (r:1 w:1) /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(152829), added: 155304, mode: `MaxEncodedLen`) @@ -148,19 +134,14 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `System::Account` (r:1 w:0) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(132), added: 2607, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 128]`. - fn join_reassigned(n: u32) -> Weight { + fn join_reassigned(_n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `139196 + n * (216 ±0)` - // Estimated: `156294 + n * (5224 ±0)` - // Minimum execution time: 405_608 nanoseconds. - Weight::from_parts(376_463_342, 156294) - // Standard Error: 158_653 - .saturating_add(Weight::from_parts(32_337_731, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(1)) - .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(n.into()))) - .saturating_add(Weight::from_parts(0, 5224).saturating_mul(n.into())) + // Measured: `139157 + n * (217 ±0)` + // Estimated: `669662` + // Minimum execution time: 230_000 nanoseconds. + Weight::from_parts(2_864_000_000, 669662) + .saturating_add(T::DbWeight::get().reads(259)) + .saturating_add(T::DbWeight::get().writes(257)) } /// Storage: `NeoSwaps::Pools` (r:1 w:1) /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(152829), added: 155304, mode: `MaxEncodedLen`) @@ -171,19 +152,14 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `System::Account` (r:1 w:0) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(132), added: 2607, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 128]`. - fn join_leaf(n: u32) -> Weight { + fn join_leaf(_n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `138700 + n * (216 ±0)` - // Estimated: `156294 + n * (5224 ±0)` - // Minimum execution time: 470_430 nanoseconds. - Weight::from_parts(404_406_469, 156294) - // Standard Error: 162_346 - .saturating_add(Weight::from_parts(31_654_906, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(1)) - .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(n.into()))) - .saturating_add(Weight::from_parts(0, 5224).saturating_mul(n.into())) + // Measured: `138661 + n * (217 ±0)` + // Estimated: `669662` + // Minimum execution time: 279_000 nanoseconds. + Weight::from_parts(2_348_000_000, 669662) + .saturating_add(T::DbWeight::get().reads(259)) + .saturating_add(T::DbWeight::get().writes(257)) } /// Storage: `NeoSwaps::Pools` (r:1 w:1) /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(152829), added: 155304, mode: `MaxEncodedLen`) @@ -194,19 +170,14 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `System::Account` (r:1 w:0) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(132), added: 2607, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 128]`. - fn exit(n: u32) -> Weight { + fn exit(_n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `139297 + n * (216 ±0)` - // Estimated: `156294 + n * (5224 ±0)` - // Minimum execution time: 433_429 nanoseconds. - Weight::from_parts(445_783_938, 156294) - // Standard Error: 148_856 - .saturating_add(Weight::from_parts(31_235_322, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(1)) - .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(n.into()))) - .saturating_add(Weight::from_parts(0, 5224).saturating_mul(n.into())) + // Measured: `139258 + n * (217 ±0)` + // Estimated: `669662` + // Minimum execution time: 235_000 nanoseconds. + Weight::from_parts(2_551_000_000, 669662) + .saturating_add(T::DbWeight::get().reads(259)) + .saturating_add(T::DbWeight::get().writes(257)) } /// Storage: `NeoSwaps::Pools` (r:1 w:1) /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(152829), added: 155304, mode: `MaxEncodedLen`) @@ -216,8 +187,8 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `137883` // Estimated: `156294` - // Minimum execution time: 319_797 nanoseconds. - Weight::from_parts(365_799_000, 156294) + // Minimum execution time: 183_000 nanoseconds. + Weight::from_parts(183_000_000, 156294) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -234,19 +205,14 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `NeoSwaps::Pools` (r:0 w:1) /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(152829), added: 155304, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 128]`. - fn deploy_pool(n: u32) -> Weight { + fn deploy_pool(_n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `611 + n * (81 ±0)` - // Estimated: `4173 + n * (5224 ±0)` - // Minimum execution time: 159_294 nanoseconds. - Weight::from_parts(100_340_149, 4173) - // Standard Error: 67_332 - .saturating_add(Weight::from_parts(33_452_544, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(4)) - .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(n.into()))) - .saturating_add(Weight::from_parts(0, 5224).saturating_mul(n.into())) + // Measured: `593 + n * (81 ±0)` + // Estimated: `669662` + // Minimum execution time: 106_000 nanoseconds. + Weight::from_parts(3_041_000_000, 669662) + .saturating_add(T::DbWeight::get().reads(260)) + .saturating_add(T::DbWeight::get().writes(260)) } /// Storage: `NeoSwaps::Pools` (r:1 w:1) /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(152829), added: 155304, mode: `MaxEncodedLen`) @@ -259,19 +225,14 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `Tokens::TotalIssuance` (r:128 w:128) /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) /// The range of component `n` is `[1, 7]`. - fn combo_buy(n: u32) -> Weight { + fn combo_buy(_n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `0 + n * (2721 ±0)` - // Estimated: `156294 + n * (38153 ±999)` - // Minimum execution time: 462_410 nanoseconds. - Weight::from_parts(465_820_000, 156294) - // Standard Error: 23_261_249 - .saturating_add(Weight::from_parts(901_905_964, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(11)) - .saturating_add(T::DbWeight::get().reads((23_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(10)) - .saturating_add(T::DbWeight::get().writes((22_u64).saturating_mul(n.into()))) - .saturating_add(Weight::from_parts(0, 38153).saturating_mul(n.into())) + // Measured: `0 + n * (5912 ±0)` + // Estimated: `669662` + // Minimum execution time: 257_000 nanoseconds. + Weight::from_parts(8_850_000_000, 669662) + .saturating_add(T::DbWeight::get().reads(395)) + .saturating_add(T::DbWeight::get().writes(388)) } /// Storage: `NeoSwaps::Pools` (r:1 w:1) /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(152829), added: 155304, mode: `MaxEncodedLen`) @@ -284,19 +245,14 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `Tokens::TotalIssuance` (r:128 w:128) /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) /// The range of component `n` is `[1, 7]`. - fn combo_sell(n: u32) -> Weight { + fn combo_sell(_n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `0 + n * (3627 ±0)` - // Estimated: `156294 + n * (38153 ±484)` - // Minimum execution time: 523_942 nanoseconds. - Weight::from_parts(527_472_000, 156294) - // Standard Error: 38_920_367 - .saturating_add(Weight::from_parts(1_535_695_671, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(10)) - .saturating_add(T::DbWeight::get().reads((23_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(9)) - .saturating_add(T::DbWeight::get().writes((22_u64).saturating_mul(n.into()))) - .saturating_add(Weight::from_parts(0, 38153).saturating_mul(n.into())) + // Measured: `0 + n * (7938 ±0)` + // Estimated: `667050` + // Minimum execution time: 298_000 nanoseconds. + Weight::from_parts(14_050_000_000, 667050) + .saturating_add(T::DbWeight::get().reads(394)) + .saturating_add(T::DbWeight::get().writes(387)) } /// Storage: `MarketCommons::Markets` (r:7 w:0) /// Proof: `MarketCommons::Markets` (`max_values`: None, `max_size`: Some(708), added: 3183, mode: `MaxEncodedLen`) @@ -311,27 +267,37 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `NeoSwaps::Pools` (r:0 w:1) /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(152829), added: 155304, mode: `MaxEncodedLen`) /// The range of component `n` is `[1, 7]`. - fn deploy_combinatorial_pool(n: u32) -> Weight { + /// The range of component `m` is `[32, 64]`. + fn deploy_combinatorial_pool(n: u32, m: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `357 + n * (185 ±0)` - // Estimated: `11438 + n * (57229 ±969)` - // Minimum execution time: 7_103_129 nanoseconds. - Weight::from_parts(7_159_730_000, 11438) - // Standard Error: 1_623_272_081 - .saturating_add(Weight::from_parts(61_965_055_407, 0).saturating_mul(n.into())) + // Measured: `351 + n * (185 ±0)` + // Estimated: `11438 + n * (156551 ±6_414)` + // Minimum execution time: 3_344_000 nanoseconds. + Weight::from_parts(3_344_000_000, 11438) + // Standard Error: 16_685_962_537 + .saturating_add(Weight::from_parts(57_843_965_765, 0).saturating_mul(n.into())) + // Standard Error: 1_759_383_308 + .saturating_add(Weight::from_parts(108_277_083, 0).saturating_mul(m.into())) .saturating_add(T::DbWeight::get().reads(10)) - .saturating_add(T::DbWeight::get().reads((37_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().reads((101_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(10)) - .saturating_add(T::DbWeight::get().writes((37_u64).saturating_mul(n.into()))) - .saturating_add(Weight::from_parts(0, 57229).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().writes((100_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 156551).saturating_mul(n.into())) + } + fn decision_market_oracle_evaluate() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 0 nanoseconds. + Weight::from_parts(0, 0) } /// Storage: `NeoSwaps::Pools` (r:1 w:0) /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(152829), added: 155304, mode: `MaxEncodedLen`) - fn decision_market_oracle_evaluate() -> Weight { + fn decision_market_oracle_update() -> Weight { // Proof Size summary in bytes: // Measured: `492` // Estimated: `156294` - // Minimum execution time: 91_762 nanoseconds. - Weight::from_parts(93_152_000, 156294).saturating_add(T::DbWeight::get().reads(1)) + // Minimum execution time: 47_000 nanoseconds. + Weight::from_parts(47_000_000, 156294).saturating_add(T::DbWeight::get().reads(1)) } } diff --git a/zrml/prediction-markets/src/lib.rs b/zrml/prediction-markets/src/lib.rs index 8d924151e..5623cbd04 100644 --- a/zrml/prediction-markets/src/lib.rs +++ b/zrml/prediction-markets/src/lib.rs @@ -3063,7 +3063,6 @@ mod pallet { type MarketId = MarketIdOf; fn payout_vector(market_id: Self::MarketId) -> Option> { - // TODO Abstract into separate function so we don't have to litter this with ok() calls. let market = >::market(&market_id).ok()?; if market.status != MarketStatus::Resolved || !market.is_redeemable() { From 57e9348d1f71956daac8143edf4f829ed5d34c79 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Mon, 10 Feb 2025 10:55:50 +0000 Subject: [PATCH 50/73] Add docs (#1391) * Add some docs and remove TODOs * More documentation * . * update docs to use fuel parameter * Update zrml/combinatorial-tokens/README.md * Update zrml/combinatorial-tokens/README.md * fix comment * add decision market oracle scoreboard to futarchy module doc * update decision market oracle doc to include scoreboard * add fuel parameter doc to complexity of deploy combinatorial pool * correct the futarchy module documentation to use root origin for proposal --------- Co-authored-by: Chralt98 Co-authored-by: Chralt --- README.md | 4 + .../src/traits/combinatorial_tokens_api.rs | 2 + .../combinatorial_tokens_benchmark_helper.rs | 2 + .../traits/combinatorial_tokens_unsafe_api.rs | 6 + zrml/combinatorial-tokens/README.md | 39 ++++++- zrml/combinatorial-tokens/src/lib.rs | 66 +++++++++++ .../src/traits/combinatorial_id_manager.rs | 3 + zrml/futarchy/README.md | 45 ++++++++ zrml/futarchy/src/lib.rs | 7 +- zrml/futarchy/src/types/proposal.rs | 5 + zrml/neo-swaps/src/lib.rs | 104 ++++++++++++++++++ .../src/types/decision_market_oracle.rs | 4 +- 12 files changed, 283 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e2dffb8b8..cf6899b58 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,12 @@ decentralized court. ## Modules - [authorized](./zrml/authorized) - Offers authorized resolution of disputes. +- [combinatorial-tokens](./zrml/combinatorial-tokens) - The module responsible + for generating Zeitgeist 2.0 outcome tokens. - [court](./zrml/court) - An implementation of a court mechanism used to resolve disputes in a decentralized fashion. +- [futarchy](./zrml/futarchy) - A novel on-chain governance mechanism using + prediction markets. - [global-disputes](./zrml-global-disputes) - Global disputes sets one out of multiple outcomes with the most locked ZTG tokens as the canonical outcome. This is the default process if a dispute mechanism fails to resolve. diff --git a/primitives/src/traits/combinatorial_tokens_api.rs b/primitives/src/traits/combinatorial_tokens_api.rs index c9953feb4..48a0e1a45 100644 --- a/primitives/src/traits/combinatorial_tokens_api.rs +++ b/primitives/src/traits/combinatorial_tokens_api.rs @@ -22,6 +22,8 @@ use parity_scale_codec::{Decode, Encode}; use scale_info::TypeInfo; use sp_runtime::DispatchError; +/// Trait that can be used to expose the internal functionality of zrml-combinatorial-tokens to +/// other pallets. pub trait CombinatorialTokensApi { type AccountId; type Balance; diff --git a/primitives/src/traits/combinatorial_tokens_benchmark_helper.rs b/primitives/src/traits/combinatorial_tokens_benchmark_helper.rs index dbb8cf889..b1c3b3212 100644 --- a/primitives/src/traits/combinatorial_tokens_benchmark_helper.rs +++ b/primitives/src/traits/combinatorial_tokens_benchmark_helper.rs @@ -18,6 +18,8 @@ use alloc::vec::Vec; use sp_runtime::DispatchResult; +/// Trait used for setting up benchmarks of zrml-combinatorial-tokens. Must not be used in +/// production. pub trait CombinatorialTokensBenchmarkHelper { type Balance; type MarketId; diff --git a/primitives/src/traits/combinatorial_tokens_unsafe_api.rs b/primitives/src/traits/combinatorial_tokens_unsafe_api.rs index 4aca7c69f..317dca26b 100644 --- a/primitives/src/traits/combinatorial_tokens_unsafe_api.rs +++ b/primitives/src/traits/combinatorial_tokens_unsafe_api.rs @@ -26,6 +26,9 @@ pub trait CombinatorialTokensUnsafeApi { type Balance; type MarketId; + /// Transfers `amount` units of collateral from the user to the pallet's reserve and mints + /// `amount` units of each asset in `assets`. Can break the reserve or result in loss of funds + /// if the value of the elements in `assets` don't add up to exactly 1. fn split_position_unsafe( who: Self::AccountId, collateral: Asset, @@ -33,6 +36,9 @@ pub trait CombinatorialTokensUnsafeApi { amount: Self::Balance, ) -> DispatchResult; + /// Transfers `amount` units of collateral from the pallet's reserve to the user and burns + /// `amount` units of each asset in `assets`. Can break the reserve or result in loss of funds + /// if the value of the elements in `assets` don't add up to exactly 1. fn merge_position_unsafe( who: Self::AccountId, collateral: Asset, diff --git a/zrml/combinatorial-tokens/README.md b/zrml/combinatorial-tokens/README.md index 1e0c36716..2630fc506 100644 --- a/zrml/combinatorial-tokens/README.md +++ b/zrml/combinatorial-tokens/README.md @@ -1,3 +1,38 @@ -# Combo Module +# Combinatorial Tokens Module -The Combo module implements combinatorial tokens in substrate. +The combinatorial-tokens module implements modern Zeitgeist's method of +creating and destroying outcome tokens. + +## Overview + +In a categorical or scalar prediction market, one unit of a complete set (i.e. one unit of each outcome token of the market) always redeems for one unit of collateral. + +In a Yes/No market, for instance, holding `x` units of Yes and `x` units of No means that, when the market resolves, you will always receive `x` units of collateral. In a scalar market, on the other hand, `x` units of Long and `x` units of Short will always redeem to a total of `x` units of collateral, as well. + +This means that buying and selling collateral for complete sets should be allowed. For example, `x` units of collateral should fetch `x` units of complete set, and vice versa. Buying complete sets can be thought of as splitting collateral into outcome tokens, while selling complete sets can be thought of as merging outcome tokens back into collateral. + +The combinatorial-tokens module generalizes this approach to not only allow splitting and merging into collateral, but also splitting and merging into outcome tokens of multiple different markets. This allows us to create outcome tokens that combine multiple events. They are called _combinatorial tokens_. + +For example, splitting an `A` token from one categorical market using another categorical market with two outcomes `X` and `Y` yields `A & X` and `A & Y` tokens. They represent the event that `A` and `X` (resp. `Y`) occur. Splitting a Yes token from a binary market using a scalar market will give `Yes & Long` and `Yes & Short` tokens. They represent Long/Short tokens contingent on `Yes` occurring. + +In addition to splitting and merging, combinatorial tokens can be redeemed if one of the markets involved in creating them has been resolved. For example, if the `XY` market above resolves to `X`, then every unit of `X & A` redeems for a unit of `A` and `Y & A` is worthless. If the scalar market above resolves so that `Long` is valued at `.4` and `Short` at `.6`, then every unit of `Yes & Long` redeems for `.4` units of `Yes` and every unit of `Yes & Short` redeems for `.6`. + +An important distinction which we've so far neglected to make is the distinction between an abstract _collection_ like `X & A` or `Yes & Short` and a concrete _position_, which is a collection together with a collateral token against which it is valued. Collections are purely abstract and used in the implementation. Positions are actual tokens on the chain. + +Collections and positions are identified using their IDs. When using the standard combinatorial ID Manager, this ID is a 256 bit value. The position ID of a certain token can be calculated using the collection ID and the collateral. + +### Terminology + +- _Combinatorial token_: Any instance of `zeitgeist_primitives::Asset::CombinatorialToken`. +- _Complete set (of a prediction market)_: An abstract set containing every outcome of a particular prediction market. One unit of a complete set is one unit of each outcome token from the market in question. After the market resolves, a complete set always redeems for exactly one unit of collateral. +- _Merge_: The process of exchanging multiple tokens for a single token of equal value. +- _Split_: The process of exchanging a token for more complicated tokens of equal value. + +### Combinatorial ID Manager + +Calculating + +alt_bn128 + +combinatorial tokens, as [defined by +Gnosis](https://docs.gnosis.io/conditionaltokens/) in Substrate. diff --git a/zrml/combinatorial-tokens/src/lib.rs b/zrml/combinatorial-tokens/src/lib.rs index 148af3f06..331b9a1e9 100644 --- a/zrml/combinatorial-tokens/src/lib.rs +++ b/zrml/combinatorial-tokens/src/lib.rs @@ -81,6 +81,7 @@ mod pallet { MarketId = MarketIdOf, >; + /// Interface for calculating collection and position IDs. type CombinatorialIdManager: CombinatorialIdManager< Asset = AssetOf, MarketId = MarketIdOf, @@ -102,6 +103,7 @@ mod pallet { type MultiCurrency: MultiCurrency>; + /// Interface for acquiring the payout vector by market ID. type Payout: PayoutApi, MarketId = MarketIdOf>; type RuntimeEvent: From> + IsType<::RuntimeEvent>; @@ -213,6 +215,32 @@ mod pallet { #[pallet::call] impl Pallet { + /// Split `amount` units of the position specified by `parent_collection_id` over the market + /// with ID `market_id` according to the given `partition`. + /// + /// The `partition` is specified as a vector whose elements are equal-length `Vec`. A + /// `true` entry at the `i`th index of a partition element means that the `i`th outcome + /// token of the market is contained in this element of the partition. + /// + /// For each element `b` of the partition, the split mints a new outcome token which is made + /// up of the position to be split and the conjunction `(x|...|z)` where `x, ..., z` are the + /// items of `b`. The position to be split, in turn, is burned or transferred into the + /// pallet account, depending on whether or not it is a true combinatorial token or + /// collateral. + /// + /// If the `parent_collection_id` is `None`, then the position split is the collateral of the + /// market given by `market_id`. + /// + /// If the `parent_collection_id` is `Some(pid)`, then there are two cases: vertical and + /// horizontal split. If `partition` is complete (i.e. there is no index `i` so that `b[i]` + /// is `false` for all `b` in `partition`), the position split is the position obtained by + /// combining `pid` with the collateral of the market given by `market_id`. If `partition` + /// is not complete, the position split is the position made up of the + /// `parent_collection_id` and the conjunction `(x|...|z)` where `x, ..., z` are the items + /// covered by `partition`. + /// + /// The `fuel` parameter specifies how much work the cryptographic id manager will do + /// and can be used for benchmarking purposes. #[pallet::call_index(0)] #[pallet::weight( T::WeightInfo::split_position_vertical_sans_parent( @@ -251,6 +279,31 @@ mod pallet { DispatchResultWithPostInfo::Ok(post_dispatch_info) } + /// Merge `amount` units of the tokens obtained by splitting `parent_collection_id` using + /// `partition` into the position specified by `parent_collection_id` (vertical split) or + /// the position obtained by splitting `parent_collection_id` according to `partiton` over + /// the market with ID `market_id` (horizontal; see below for details). + /// + /// The `partition` is specified as a vector whose elements are equal-length `Vec`. A + /// `true` entry at the `i`th index of a partition element means that the `i`th outcome + /// token of the market is contained in this element of the partition. + /// + /// For each element `b` of the partition, the split burns the outcome tokens which are made + /// up of the position to be split and the conjunction `(x|...|z)` where `x, ..., z` are the + /// items of `b`. The position given by `parent_collection_id` is + /// + /// If the `parent_collection_id` is `None`, then the position split is the collateral of the + /// market given by `market_id`. + /// + /// If the `parent_collection_id` is `Some(pid)`, then there are two cases: vertical and + /// horizontal merge. If `partition` is complete (i.e. there is no index `i` so that `b[i]` + /// is `false` for all `b` in `partition`), the the result of the merge is the position + /// defined by `parent_collection_id`. If `partition` is not complete, the result of the + /// merge is the position made up of the `parent_collection_id` and the conjunction + /// `(x|...|z)` where `x, ..., z` are the items covered by `partition`. + /// + /// The `fuel` parameter specifies how much work the cryptographic id manager will do + /// and can be used for benchmarking purposes. #[pallet::call_index(1)] #[pallet::weight( T::WeightInfo::merge_position_vertical_sans_parent( @@ -279,6 +332,19 @@ mod pallet { Self::do_merge_position(who, parent_collection_id, market_id, partition, amount, fuel) } + /// (Partially) redeems a position if part of it belongs to a resolved market given by + /// `market_id`. + /// + /// The position to be redeemed is the position obtained by combining the position given by + /// `parent_collection_id` and `collateral` with the conjunction `(x|...|z)` where `x, ... + /// z` are the outcome tokens of the market `market_id` given by `partition`. + /// + /// The position to be redeemed is completely removed from the origin's wallet. According to + /// how much the conjunction `(x|...|z)` is valued, the user is paid in the position defined + /// by `parent_collection_id` and `collateral`. + /// + /// The `fuel` parameter specifies how much work the cryptographic id manager will do + /// and can be used for benchmarking purposes. #[pallet::call_index(2)] #[pallet::weight( T::WeightInfo::redeem_position_with_parent( diff --git a/zrml/combinatorial-tokens/src/traits/combinatorial_id_manager.rs b/zrml/combinatorial-tokens/src/traits/combinatorial_id_manager.rs index 47cb9389a..dcee9a12d 100644 --- a/zrml/combinatorial-tokens/src/traits/combinatorial_id_manager.rs +++ b/zrml/combinatorial-tokens/src/traits/combinatorial_id_manager.rs @@ -25,6 +25,7 @@ use crate::types::CollectionIdError; use alloc::vec::Vec; +/// Handles calculations of combinatorial IDs. pub trait CombinatorialIdManager { type Asset; type MarketId; @@ -43,6 +44,8 @@ pub trait CombinatorialIdManager { fuel: Self::Fuel, ) -> Result; + /// Calculate the position ID belonging to the `collection_id` combined with `collateral` as + /// collateral. fn get_position_id( collateral: Self::Asset, collection_id: Self::CombinatorialId, diff --git a/zrml/futarchy/README.md b/zrml/futarchy/README.md index 19f6917fb..a41fb2199 100644 --- a/zrml/futarchy/README.md +++ b/zrml/futarchy/README.md @@ -1 +1,46 @@ # Futarchy Module + +The futarchy module provides a straightforward, "no bells and whistles" +implementation of the +[futarchy governance system](https://docs.zeitgeist.pm/docs/learn/futarchy). + +## Overview + +The futarchy module is essentially an oracle based governance system: When a +proposal is submitted, an oracle is specified which evaluates whether the +proposal should be executed. The type of the oracle is configured using the +associated type `Oracle`, which must implement `FutarchyOracle`. + +The typical oracle implementation for futarchy is the `DecisionMarketOracle` +implementation exposed by the neo-swaps module, which allows making decisions +based on prices in prediction markets. A `DecisionMarketOracle` is defined by +providing a pool ID and two outcomes, the _positive_ and _negative_ outcome. The +oracle evaluates positively (meaning that it will allow the proposal to pass) if +and only if the positive outcome is more valuable than the negative outcome over +a period of time for a certain absolute and relative threshold determined by a +`DecisionMarketOracleScoreboard`. + +The standard governance flow is the following: + +- The root origin submits a proposal to be approved or rejected via futarchy by + running a governance proposal through + [pallet-democracy](https://github.com/paritytech/polkadot-sdk/tree/master/substrate/frame/democracy) + and calling into this pallet's sole extrinsic `submit_proposal`. Assuming that + the thesis of futarchy is correct and the market used to evaluate the proposal + is well-configured and sufficiently liquid, submitting a proposal to futarchy + rather than pallet-democracy gives a stronger guarantee on the efficacy of the + proposal. +- Wait until the `duration` specified in `submit_proposal` has passed. The + oracle will be automatically evaluated and will either schedule + `proposal.call` at `proposal.when` where `proposal` is the proposal specified + in `submit_proposal`. + +### Terminology + +- _Call_: Refers to an on-chain extrinsic call. +- _Oracle_: A means of making a decision about a proposal. At any block, an + oracle evaluates to `true` (proposal is accepted) or `false` (proposal is + rejected). +- _Proposal_: Consists of a call, an oracle and a time of execution. If and only + if the proposal is accepted, the call is scheduled for the specified time of + execution. diff --git a/zrml/futarchy/src/lib.rs b/zrml/futarchy/src/lib.rs index fca677860..f37c5fb70 100644 --- a/zrml/futarchy/src/lib.rs +++ b/zrml/futarchy/src/lib.rs @@ -80,9 +80,10 @@ mod pallet { /// The maximum number of proposals allowed to be in flight simultaneously. type MaxProposals: Get; + /// The minimum allowed duration between the creation of a proposal and its evaluation. type MinDuration: Get>; - // The type used to define the oracle for each proposal. + /// The type used to define the oracle for a proposal. type Oracle: FutarchyOracle> + Clone + Debug @@ -154,6 +155,10 @@ mod pallet { #[pallet::call] impl Pallet { + /// Submits a `proposal` for evaluation in `duration` blocks. + /// + /// If, after `duration` blocks, the oracle `proposal.oracle` is evaluated positively, the + /// proposal is scheduled for execution at `proposal.when`. #[pallet::call_index(0)] #[transactional] #[pallet::weight(T::WeightInfo::submit_proposal())] diff --git a/zrml/futarchy/src/types/proposal.rs b/zrml/futarchy/src/types/proposal.rs index fa69b6401..3576be99c 100644 --- a/zrml/futarchy/src/types/proposal.rs +++ b/zrml/futarchy/src/types/proposal.rs @@ -29,7 +29,12 @@ pub struct Proposal where T: Config, { + /// The time at which the proposal will be enacted. pub when: BlockNumberFor, + + /// The proposed call. pub call: BoundedCallOf, + + /// The oracle that evaluates if the proposal should be enacted. pub oracle: OracleOf, } diff --git a/zrml/neo-swaps/src/lib.rs b/zrml/neo-swaps/src/lib.rs index 993dec1c5..d034b2455 100644 --- a/zrml/neo-swaps/src/lib.rs +++ b/zrml/neo-swaps/src/lib.rs @@ -129,8 +129,10 @@ mod pallet { #[pallet::config] pub trait Config: frame_system::Config { + /// Type of combinatorial ID used by the combinatorial tokens APIs. type CombinatorialId: Clone; + /// API used for calculating splits of tokens when creating combinatorial pools. type CombinatorialTokens: CombinatorialTokensApi< AccountId = Self::AccountId, Balance = BalanceOf, @@ -138,6 +140,7 @@ mod pallet { MarketId = MarketIdOf, >; + /// API for fast creation of tokens when buying or selling combinatorial tokens. type CombinatorialTokensUnsafe: CombinatorialTokensUnsafeApi< AccountId = Self::AccountId, Balance = BalanceOf, @@ -683,6 +686,34 @@ mod pallet { Ok(Some(T::WeightInfo::deploy_pool(spot_prices_len)).into()) } + /// Make a combinatorial bet on the specified pool. + /// + /// The `amount_in` is paid in collateral. The transaction fails if the amount of outcome + /// tokens received is smaller than `min_amount_out`. The user must correctly specify the + /// number of outcomes for benchmarking reasons. + /// + /// The user's collateral is used to mint complete sets of the combinatorial tokens in the + /// pool. The parameters `buy` and `sell` are used to specify which of these tokens the user + /// wants and doesn't want: The assets in `sell` are sold to buy more of `buy` from the + /// pool. The assets not contained in either of these will remain in the users wallet + /// unchanged. + /// + /// The function will error if certain numerical constraints are violated. + /// + /// # Parameters + /// + /// - `origin`: The origin account making the purchase. + /// - `pool_id`: Identifier for the pool used to trade on. + /// - `asset_count`: Number of assets in the pool. + /// - `buy`: The assets that the user want to have more of. Must not be empty. + /// - `sell`: The assets that the user doesn't want any of. Must not be empty. + /// - `amount_in`: Amount of collateral paid by the user. + /// - `min_amount_out`: Minimum number of outcome tokens the user expects to receive. + /// + /// # Complexity + /// + /// Depends on the implementation of `CombinatorialTokensUnsafeApi` and `ExternalFees`; when + /// using the canonical implementations, the runtime complexity is `O(asset_count)`. #[allow(clippy::too_many_arguments)] #[pallet::call_index(6)] #[pallet::weight(T::WeightInfo::combo_buy(asset_count.log_ceil().into()))] @@ -707,6 +738,36 @@ mod pallet { Self::do_combo_buy(who, pool_id, buy, sell, amount_in, min_amount_out) } + /// Cancel a combinatorial bet on the specified pool. + /// + /// The `buy`, `keep` and `sell` parameters are used to specify the amounts of the bet the + /// user wishes to cancel. The user must hold `amount_buy` units of each asset in `buy` and + /// `amount_keep` of each asset in `keep` in their wallet. If `keep` is empty, then + /// `amount_keep` must be zero. + /// + /// The transaction fails if the amount of outcome tokens received is smaller than + /// `min_amount_out`. The user must correctly specify the number of outcomes for + /// benchmarking reasons. + /// + /// The function will error if certain numerical constraints are violated. + /// + /// # Parameters + /// + /// - `origin`: The origin account making the purchase. + /// - `pool_id`: Identifier for the pool used to trade on. + /// - `asset_count`: Number of assets in the pool. + /// - `buy`: The `buy` of the bet that the user wishes to cancel. Must not be empty. + /// - `keep`: The tokens not contained in `buy` or `sell` of the bet that the user wishes to + /// cancel. May be empty. + /// - `sell`: The `sell` of the bet that the user wishes to cancel. Must not be empty. + /// - `amount_buy`: Amount of tokens in `buy` the user wishes to let go. + /// - `amount_keep`: Amount of tokens in `keep` the user wishes to let go. + /// - `min_amount_out`: Minimum number of outcome tokens the user expects to receive. + /// + /// # Complexity + /// + /// Depends on the implementation of `CombinatorialTokensUnsafeApi` and `ExternalFees`; when + /// using the canonical implementations, the runtime complexity is `O(asset_count)`. #[allow(clippy::too_many_arguments)] #[pallet::call_index(7)] #[pallet::weight(T::WeightInfo::combo_sell(asset_count.log_ceil().into()))] @@ -742,6 +803,49 @@ mod pallet { ) } + /// Deploy a combinatorial pool for the specified markets and provide liquidity. + /// + /// The tokens of each of the markets specified by `market_ids` are split into atoms. For + /// each combination of outcome tokens `x, ..., z` from the markets, there is one + /// combinatorial token `x & ... & z` in the pool. + /// + /// The pool's assets are ordered by lexicographical order, using the ordering of tokens of + /// each individual market provided by the `MarketCommonsApi`. For example, if three markets + /// with outcomes `x_1, x_2`, `y_1, y_2` and `z_1, z_2` are involved, the outcomes of the + /// pool are (in order): + /// + /// x_1 & y_1 & z_1 + /// x_1 & y_1 & z_2 + /// x_1 & y_2 & z_1 + /// x_1 & y_2 & z_2 + /// x_2 & y_1 & z_1 + /// x_2 & y_1 & z_2 + /// x_2 & y_2 & z_1 + /// x_2 & y_2 & z_2 + /// + /// The sender specifies a vector of `spot_prices` for the assets of the new pool, in the + /// order as described above. + /// + /// Depending on the values in the `spot_prices`, the transaction will transfer different + /// amounts of each outcome to the pool. The sender specifies a maximum `amount` of outcome + /// tokens to spend. + /// + /// Unlike in the `deploy_pool` extrinsic, the sender need not acquire the outcome tokens + /// themselves. Instead, all they need is `amount` units of collateral. + /// + /// Deploying the pool will cost the signer an additional fee to the tune of the + /// collateral's existential deposit. This fee is placed in the pool account and ensures + /// that swap fees can be stored in the pool account without triggering dusting or failed + /// transfers. + /// + /// The `fuel` parameter specifies how much work the cryptographic id manager will do + /// and can be used for benchmarking purposes. + /// + /// # Complexity + /// + /// `O(n)` where `n` is the number of splits required to create the pool. + /// The `fuel` parameter specifies how much work the cryptographic id manager will do + /// and can be used for benchmarking purposes. #[pallet::call_index(8)] #[pallet::weight(T::WeightInfo::deploy_combinatorial_pool( asset_count.log_ceil().into(), diff --git a/zrml/neo-swaps/src/types/decision_market_oracle.rs b/zrml/neo-swaps/src/types/decision_market_oracle.rs index b7650326b..3347fae0f 100644 --- a/zrml/neo-swaps/src/types/decision_market_oracle.rs +++ b/zrml/neo-swaps/src/types/decision_market_oracle.rs @@ -29,7 +29,9 @@ use zeitgeist_primitives::traits::FutarchyOracle; /// Struct that implements `FutarchyOracle` using price measurements from liquidity pools. /// /// The oracle evaluates to `true` if and only if the `positive_outcome` is more valuable than the -/// `negative_outcome` in the liquidity pool specified by `pool_id`. +/// `negative_outcome` in the liquidity pool specified by `pool_id` over +/// a period of time for a certain absolute and relative threshold determined by a +/// [`DecisionMarketOracleScoreboard`]. #[derive(Clone, Debug, Decode, Encode, Eq, MaxEncodedLen, PartialEq, TypeInfo)] pub struct DecisionMarketOracle where From 23267920ef38ecc5bce85ecf74020cb3809ae6d1 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Mon, 10 Feb 2025 12:32:09 +0000 Subject: [PATCH 51/73] Add fuzz tests for combinatorial tokens, pool and futarchy (#1392) * Implement fuzz tests for futarchy * Minor fixes * add first fuzz test for combo tokens * Add test to script * Add fuzz test for merge position * . * Add redeem fuzz test * Add combinatorial pool fuzz tests * Add neo-swaps fuzz to script * combo buy * Minor fixes, add to script * Add combo sell * use fuel parameter for fuzz tests --------- Co-authored-by: Chralt98 --- Cargo.lock | 47 +++++ Cargo.toml | 3 + .../src/traits/market_commons_pallet_api.rs | 2 +- scripts/tests/fuzz.sh | 10 + zrml/combinatorial-tokens/fuzz/Cargo.toml | 38 ++++ zrml/combinatorial-tokens/fuzz/common.rs | 35 ++++ .../fuzz/merge_position.rs | 120 ++++++++++++ .../fuzz/redeem_position.rs | 119 ++++++++++++ .../fuzz/split_position.rs | 101 ++++++++++ zrml/combinatorial-tokens/src/lib.rs | 25 ++- zrml/combinatorial-tokens/src/mock/mod.rs | 4 +- .../src/mock/types/mod.rs | 2 +- zrml/futarchy/Cargo.toml | 6 + zrml/futarchy/fuzz/Cargo.toml | 26 +++ zrml/futarchy/fuzz/submit_proposal.rs | 44 +++++ zrml/futarchy/src/mock/mod.rs | 4 +- zrml/futarchy/src/mock/types/oracle.rs | 16 ++ zrml/futarchy/src/types/proposal.rs | 28 +++ zrml/neo-swaps/fuzz/Cargo.toml | 39 ++++ zrml/neo-swaps/fuzz/combo_buy.rs | 173 ++++++++++++++++++ zrml/neo-swaps/fuzz/combo_sell.rs | 149 +++++++++++++++ zrml/neo-swaps/fuzz/common.rs | 35 ++++ .../fuzz/deploy_combinatorial_pool.rs | 121 ++++++++++++ zrml/neo-swaps/src/lib.rs | 35 ++-- 24 files changed, 1148 insertions(+), 34 deletions(-) create mode 100644 zrml/combinatorial-tokens/fuzz/Cargo.toml create mode 100644 zrml/combinatorial-tokens/fuzz/common.rs create mode 100644 zrml/combinatorial-tokens/fuzz/merge_position.rs create mode 100644 zrml/combinatorial-tokens/fuzz/redeem_position.rs create mode 100644 zrml/combinatorial-tokens/fuzz/split_position.rs create mode 100644 zrml/futarchy/fuzz/Cargo.toml create mode 100644 zrml/futarchy/fuzz/submit_proposal.rs create mode 100644 zrml/neo-swaps/fuzz/Cargo.toml create mode 100644 zrml/neo-swaps/fuzz/combo_buy.rs create mode 100644 zrml/neo-swaps/fuzz/combo_sell.rs create mode 100644 zrml/neo-swaps/fuzz/common.rs create mode 100644 zrml/neo-swaps/fuzz/deploy_combinatorial_pool.rs diff --git a/Cargo.lock b/Cargo.lock index 2d6b3d84d..51d7ee5e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15399,6 +15399,21 @@ dependencies = [ "zrml-market-commons", ] +[[package]] +name = "zrml-combinatorial-tokens-fuzz" +version = "0.5.5" +dependencies = [ + "arbitrary", + "frame-support", + "frame-system", + "libfuzzer-sys", + "orml-traits", + "rand 0.8.5", + "sp-runtime", + "zeitgeist-primitives", + "zrml-combinatorial-tokens", +] + [[package]] name = "zrml-court" version = "0.5.5" @@ -15430,6 +15445,7 @@ dependencies = [ name = "zrml-futarchy" version = "0.5.5" dependencies = [ + "arbitrary", "env_logger 0.10.2", "frame-benchmarking", "frame-support", @@ -15437,6 +15453,7 @@ dependencies = [ "pallet-balances", "parity-scale-codec", "scale-info", + "sp-core", "sp-io", "sp-runtime", "test-case", @@ -15444,6 +15461,21 @@ dependencies = [ "zrml-futarchy", ] +[[package]] +name = "zrml-futarchy-fuzz" +version = "0.5.5" +dependencies = [ + "arbitrary", + "frame-support", + "frame-system", + "libfuzzer-sys", + "orml-traits", + "rand 0.8.5", + "sp-runtime", + "zeitgeist-primitives", + "zrml-futarchy", +] + [[package]] name = "zrml-global-disputes" version = "0.5.5" @@ -15566,6 +15598,21 @@ dependencies = [ "zrml-prediction-markets-runtime-api", ] +[[package]] +name = "zrml-neo-swaps-fuzz" +version = "0.5.5" +dependencies = [ + "arbitrary", + "frame-support", + "frame-system", + "libfuzzer-sys", + "orml-traits", + "rand 0.8.5", + "sp-runtime", + "zeitgeist-primitives", + "zrml-neo-swaps", +] + [[package]] name = "zrml-orderbook" version = "0.5.5" diff --git a/Cargo.toml b/Cargo.toml index e7b24ef66..d34f44cf0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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", diff --git a/primitives/src/traits/market_commons_pallet_api.rs b/primitives/src/traits/market_commons_pallet_api.rs index a24eecefa..38c9ccb0f 100644 --- a/primitives/src/traits/market_commons_pallet_api.rs +++ b/primitives/src/traits/market_commons_pallet_api.rs @@ -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 = Market< +pub type MarketOf = Market< ::AccountId, ::Balance, ::BlockNumber, diff --git a/scripts/tests/fuzz.sh b/scripts/tests/fuzz.sh index beff95f26..c70909ac1 100755 --- a/scripts/tests/fuzz.sh +++ b/scripts/tests/fuzz.sh @@ -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 diff --git a/zrml/combinatorial-tokens/fuzz/Cargo.toml b/zrml/combinatorial-tokens/fuzz/Cargo.toml new file mode 100644 index 000000000..32d76bf87 --- /dev/null +++ b/zrml/combinatorial-tokens/fuzz/Cargo.toml @@ -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 diff --git a/zrml/combinatorial-tokens/fuzz/common.rs b/zrml/combinatorial-tokens/fuzz/common.rs new file mode 100644 index 000000000..b4a0e297d --- /dev/null +++ b/zrml/combinatorial-tokens/fuzz/common.rs @@ -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( + market_id: MarketIdOf, + base_asset: AssetOf, + market_type: MarketType, +) -> MarketOf<::MarketCommons> +where + T: 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, + } +} diff --git a/zrml/combinatorial-tokens/fuzz/merge_position.rs b/zrml/combinatorial-tokens/fuzz/merge_position.rs new file mode 100644 index 000000000..2a0a32f44 --- /dev/null +++ b/zrml/combinatorial-tokens/fuzz/merge_position.rs @@ -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::{CombinatorialTokensFuel, MarketCommonsPalletApi}, + types::{Asset, MarketType}, +}; +use zrml_combinatorial_tokens::{ + mock::{ + ext_builder::ExtBuilder, + runtime::{CombinatorialTokens, Runtime, RuntimeOrigin}, + }, + AccountIdOf, BalanceOf, CombinatorialIdOf, Config, FuelOf, MarketIdOf, +}; + +#[derive(Debug)] +struct MergePositionFuzzParams { + account_id: AccountIdOf, + parent_collection_id: Option>, + market_id: MarketIdOf, + partition: Vec>, + amount: BalanceOf, + fuel: FuelOf, +} + +impl<'a> Arbitrary<'a> for MergePositionFuzzParams { + fn arbitrary(u: &mut Unstructured<'a>) -> ArbitraryResult { + let account_id = u128::arbitrary(u)?; + let parent_collection_id = Arbitrary::arbitrary(u)?; + let market_id = 0u8.into(); + let amount = Arbitrary::arbitrary(u)?; + let fuel = FuelOf::::from_total(u.int_in_range(1..=100)?); + + // 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::>>()?; + + let params = MergePositionFuzzParams { + account_id, + parent_collection_id, + market_id, + partition, + amount, + fuel, + }; + + 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::( + params.market_id, + collateral, + MarketType::Categorical(asset_count), + ); + <::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, + FuelOf::::from_total(16), + ) + }) + .collect::, _>>() + .unwrap(); + for &position in positions.iter() { + <::MultiCurrency>::deposit( + position, + ¶ms.account_id, + params.amount, + ) + .unwrap(); + } + + // Is not required if `parent_collection_id.is_some()`, but we're doing it anyways. + <::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.fuel, + ); + }); + + let _ = ext.commit_all(); +}); diff --git a/zrml/combinatorial-tokens/fuzz/redeem_position.rs b/zrml/combinatorial-tokens/fuzz/redeem_position.rs new file mode 100644 index 000000000..566202b10 --- /dev/null +++ b/zrml/combinatorial-tokens/fuzz/redeem_position.rs @@ -0,0 +1,119 @@ +#![no_main] + +mod common; + +use arbitrary::{Arbitrary, Result as ArbitraryResult, Unstructured}; +use libfuzzer_sys::fuzz_target; +use orml_traits::currency::MultiCurrency; +use zeitgeist_primitives::{ + constants::base_multiples::*, + traits::{CombinatorialTokensFuel, MarketCommonsPalletApi}, + types::{Asset, MarketType}, +}; +use zrml_combinatorial_tokens::{ + mock::{ + ext_builder::ExtBuilder, + runtime::{CombinatorialTokens, Runtime, RuntimeOrigin}, + types::MockPayout, + }, + traits::CombinatorialIdManager, + AccountIdOf, BalanceOf, CombinatorialIdOf, Config, FuelOf, MarketIdOf, +}; + +#[derive(Debug)] +struct RedeemPositionFuzzParams { + account_id: AccountIdOf, + parent_collection_id: Option>, + market_id: MarketIdOf, + index_set: Vec, + fuel: FuelOf, + payout_vector: Option>>, + amount: BalanceOf, +} + +impl<'a> Arbitrary<'a> for RedeemPositionFuzzParams { + fn arbitrary(u: &mut Unstructured<'a>) -> ArbitraryResult { + let account_id = u128::arbitrary(u)?; + let parent_collection_id = Arbitrary::arbitrary(u)?; + let market_id = 0u8.into(); + let amount = Arbitrary::arbitrary(u)?; + let fuel = FuelOf::::from_total(u.int_in_range(1..=100)?); + + let min_len = 2; + let max_len = 1000; + let len = u.int_in_range(0..=max_len)?; + let index_set = + (min_len..len).map(|_| bool::arbitrary(u)).collect::>>()?; + + // Clamp every value of the payout vector to [0..1]. That doesn't ensure that the payout + // vector is valid, but it's valid enough to avoid most overflows. + let payout_vector = Some( + (min_len..len) + .map(|_| Ok(u128::arbitrary(u)? % _1)) + .collect::>>()?, + ); + + let params = RedeemPositionFuzzParams { + account_id, + parent_collection_id, + market_id, + index_set, + fuel, + payout_vector, + amount, + }; + + Ok(params) + } +} + +fuzz_target!(|params: RedeemPositionFuzzParams| { + let mut ext = ExtBuilder::build(); + + ext.execute_with(|| { + // We create a market and equip the user with the tokens they require to make the + // `redeem_position` call meaningful. We also provide the pallet account with collateral in + // case it's required. + let collateral = Asset::Ztg; + let asset_count = params.index_set.len() as u16; + let market = common::market::( + params.market_id, + collateral, + MarketType::Categorical(asset_count), + ); + <::MarketCommons as MarketCommonsPalletApi>::push_market(market) + .unwrap(); + + let position = if let Some(pci) = params.parent_collection_id { + let position_id = + ::CombinatorialIdManager::get_position_id(collateral, pci); + + Asset::CombinatorialToken(position_id) + } else { + Asset::Ztg + }; + <::MultiCurrency>::deposit(position, ¶ms.account_id, params.amount) + .unwrap(); + + // Is not required if `parent_collection_id.is_some()`, but we're doing it anyways. + <::MultiCurrency>::deposit( + collateral, + &CombinatorialTokens::account_id(), + params.amount * asset_count as u128, + ) + .unwrap(); + + // Mock up the payout vector. + MockPayout::set_return_value(params.payout_vector); + + let _ = CombinatorialTokens::redeem_position( + RuntimeOrigin::signed(params.account_id), + params.parent_collection_id, + params.market_id, + params.index_set, + params.fuel, + ); + }); + + let _ = ext.commit_all(); +}); diff --git a/zrml/combinatorial-tokens/fuzz/split_position.rs b/zrml/combinatorial-tokens/fuzz/split_position.rs new file mode 100644 index 000000000..4f733cd8f --- /dev/null +++ b/zrml/combinatorial-tokens/fuzz/split_position.rs @@ -0,0 +1,101 @@ +#![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::{CombinatorialTokensFuel, MarketCommonsPalletApi}, + types::{Asset, MarketType}, +}; +use zrml_combinatorial_tokens::{ + mock::{ + ext_builder::ExtBuilder, + runtime::{CombinatorialTokens, Runtime, RuntimeOrigin}, + }, + traits::CombinatorialIdManager, + AccountIdOf, BalanceOf, CombinatorialIdOf, Config, FuelOf, MarketIdOf, +}; + +#[derive(Debug)] +struct SplitPositionFuzzParams { + account_id: AccountIdOf, + parent_collection_id: Option>, + market_id: MarketIdOf, + partition: Vec>, + amount: BalanceOf, + fuel: FuelOf, +} + +impl<'a> Arbitrary<'a> for SplitPositionFuzzParams { + fn arbitrary(u: &mut Unstructured<'a>) -> ArbitraryResult { + let account_id = u128::arbitrary(u)?; + let parent_collection_id = Arbitrary::arbitrary(u)?; + let market_id = 0u8.into(); + let amount = Arbitrary::arbitrary(u)?; + let fuel = FuelOf::::from_total(u.int_in_range(1..=100)?); + + // 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::>>()?; + + let params = SplitPositionFuzzParams { + account_id, + parent_collection_id, + market_id, + partition, + amount, + fuel, + }; + + Ok(params) + } +} + +fuzz_target!(|params: SplitPositionFuzzParams| { + let mut ext = ExtBuilder::build(); + + ext.execute_with(|| { + // We create a market and equip the user with the tokens they require to make the + // `split_position` call meaningful. + 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::( + params.market_id, + collateral, + MarketType::Categorical(asset_count), + ); + <::MarketCommons as MarketCommonsPalletApi>::push_market(market) + .unwrap(); + + let position = if let Some(pci) = params.parent_collection_id { + let position_id = + ::CombinatorialIdManager::get_position_id(collateral, pci); + + Asset::CombinatorialToken(position_id) + } else { + Asset::Ztg + }; + <::MultiCurrency>::deposit(position, ¶ms.account_id, params.amount) + .unwrap(); + + let _ = CombinatorialTokens::split_position( + RuntimeOrigin::signed(params.account_id), + params.parent_collection_id, + params.market_id, + params.partition, + params.amount, + params.fuel, + ); + }); + + let _ = ext.commit_all(); +}); diff --git a/zrml/combinatorial-tokens/src/lib.rs b/zrml/combinatorial-tokens/src/lib.rs index 331b9a1e9..2530195e9 100644 --- a/zrml/combinatorial-tokens/src/lib.rs +++ b/zrml/combinatorial-tokens/src/lib.rs @@ -30,7 +30,7 @@ extern crate alloc; mod benchmarking; pub mod mock; mod tests; -mod traits; +pub mod traits; pub mod types; pub mod weights; @@ -118,16 +118,15 @@ mod pallet { #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(PhantomData); - pub(crate) type AccountIdOf = ::AccountId; - pub(crate) type AssetOf = Asset>; - pub(crate) type BalanceOf = + pub type AccountIdOf = ::AccountId; + pub type AssetOf = Asset>; + pub type BalanceOf = <::MultiCurrency as MultiCurrency>>::Balance; - pub(crate) type CombinatorialIdOf = + pub type CombinatorialIdOf = <::CombinatorialIdManager as CombinatorialIdManager>::CombinatorialId; - pub(crate) type FuelOf = + pub type MarketIdOf = <::MarketCommons as MarketCommonsPalletApi>::MarketId; + pub type FuelOf = <::CombinatorialIdManager as CombinatorialIdManager>::Fuel; - pub(crate) type MarketIdOf = - <::MarketCommons as MarketCommonsPalletApi>::MarketId; pub(crate) type SplitPositionDispatchInfoOf = SplitPositionDispatchInfo, MarketIdOf>; @@ -239,7 +238,7 @@ mod pallet { /// `parent_collection_id` and the conjunction `(x|...|z)` where `x, ..., z` are the items /// covered by `partition`. /// - /// The `fuel` parameter specifies how much work the cryptographic id manager will do + /// The `fuel` parameter specifies how much work the cryptographic id manager will do /// and can be used for benchmarking purposes. #[pallet::call_index(0)] #[pallet::weight( @@ -302,7 +301,7 @@ mod pallet { /// merge is the position made up of the `parent_collection_id` and the conjunction /// `(x|...|z)` where `x, ..., z` are the items covered by `partition`. /// - /// The `fuel` parameter specifies how much work the cryptographic id manager will do + /// The `fuel` parameter specifies how much work the cryptographic id manager will do /// and can be used for benchmarking purposes. #[pallet::call_index(1)] #[pallet::weight( @@ -343,7 +342,7 @@ mod pallet { /// how much the conjunction `(x|...|z)` is valued, the user is paid in the position defined /// by `parent_collection_id` and `collateral`. /// - /// The `fuel` parameter specifies how much work the cryptographic id manager will do + /// The `fuel` parameter specifies how much work the cryptographic id manager will do /// and can be used for benchmarking purposes. #[pallet::call_index(2)] #[pallet::weight( @@ -630,7 +629,7 @@ mod pallet { Ok(Some(weight).into()) } - pub(crate) fn account_id() -> T::AccountId { + pub fn account_id() -> T::AccountId { T::PalletId::get().into_account_truncating() } @@ -730,7 +729,7 @@ mod pallet { Ok(asset) } - pub(crate) fn position_from_parent_collection( + pub fn position_from_parent_collection( parent_collection_id: Option>, market_id: MarketIdOf, index_set: Vec, diff --git a/zrml/combinatorial-tokens/src/mock/mod.rs b/zrml/combinatorial-tokens/src/mock/mod.rs index f2933dea7..b29cf33f0 100644 --- a/zrml/combinatorial-tokens/src/mock/mod.rs +++ b/zrml/combinatorial-tokens/src/mock/mod.rs @@ -19,5 +19,5 @@ pub(crate) mod consts; pub mod ext_builder; -pub(crate) mod runtime; -pub(crate) mod types; +pub mod runtime; +pub mod types; diff --git a/zrml/combinatorial-tokens/src/mock/types/mod.rs b/zrml/combinatorial-tokens/src/mock/types/mod.rs index 6f3afbeaf..0eb340eff 100644 --- a/zrml/combinatorial-tokens/src/mock/types/mod.rs +++ b/zrml/combinatorial-tokens/src/mock/types/mod.rs @@ -21,4 +21,4 @@ mod payout; #[cfg(feature = "runtime-benchmarks")] pub(crate) use benchmark_helper::BenchmarkHelper; -pub(crate) use payout::MockPayout; +pub use payout::MockPayout; diff --git a/zrml/futarchy/Cargo.toml b/zrml/futarchy/Cargo.toml index ab9b7ceff..1fd6f7bfd 100644 --- a/zrml/futarchy/Cargo.toml +++ b/zrml/futarchy/Cargo.toml @@ -13,12 +13,18 @@ env_logger = { workspace = true, optional = true } pallet-balances = { workspace = true, optional = true } sp-io = { workspace = true, optional = true } +# fuzz + +arbitrary = { workspace = true, features = ["derive"], optional = true } +sp-core = { workspace = true, optional = true } + [dev-dependencies] test-case = { workspace = true } zrml-futarchy = { workspace = true, features = ["default", "mock"] } [features] default = ["std"] +fuzzing = ["arbitrary", "sp-core"] mock = [ "env_logger/default", "sp-io/default", diff --git a/zrml/futarchy/fuzz/Cargo.toml b/zrml/futarchy/fuzz/Cargo.toml new file mode 100644 index 000000000..e12990f80 --- /dev/null +++ b/zrml/futarchy/fuzz/Cargo.toml @@ -0,0 +1,26 @@ +[[bin]] +doc = false +name = "submit_proposal" +path = "submit_proposal.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-futarchy = { workspace = true, features = ["default", "fuzzing", "mock"] } + +[package] +authors = ["Forecasting Technologies Ltd"] +edition.workspace = true +name = "zrml-futarchy-fuzz" +publish = false +version = "0.5.5" + +[package.metadata] +cargo-fuzz = true diff --git a/zrml/futarchy/fuzz/submit_proposal.rs b/zrml/futarchy/fuzz/submit_proposal.rs new file mode 100644 index 000000000..22a0c0bbd --- /dev/null +++ b/zrml/futarchy/fuzz/submit_proposal.rs @@ -0,0 +1,44 @@ +#![no_main] + +use arbitrary::{Arbitrary, Result as ArbitraryResult, Unstructured}; +use frame_system::pallet_prelude::{BlockNumberFor, OriginFor}; +use libfuzzer_sys::fuzz_target; +use zrml_futarchy::{ + mock::{ + ext_builder::ExtBuilder, + runtime::{Futarchy, Runtime, RuntimeOrigin}, + }, + types::Proposal, +}; + +#[derive(Debug)] +struct SubmitProposalParams { + origin: OriginFor, + duration: BlockNumberFor, + proposal: Proposal, +} + +impl<'a> Arbitrary<'a> for SubmitProposalParams { + fn arbitrary(u: &mut Unstructured<'a>) -> ArbitraryResult { + let account_id = u128::arbitrary(u)?; + let origin = RuntimeOrigin::signed(account_id); + + let duration = Arbitrary::arbitrary(u)?; + + let proposal = Arbitrary::arbitrary(u)?; + + let params = SubmitProposalParams { origin, duration, proposal }; + + Ok(params) + } +} + +fuzz_target!(|params: SubmitProposalParams| { + let mut ext = ExtBuilder::build(); + + ext.execute_with(|| { + let _ = Futarchy::submit_proposal(params.origin, params.duration, params.proposal); + }); + + let _ = ext.commit_all(); +}); diff --git a/zrml/futarchy/src/mock/mod.rs b/zrml/futarchy/src/mock/mod.rs index 546789d7e..6c3be09e1 100644 --- a/zrml/futarchy/src/mock/mod.rs +++ b/zrml/futarchy/src/mock/mod.rs @@ -18,6 +18,6 @@ #![cfg(feature = "mock")] pub mod ext_builder; -pub(crate) mod runtime; -pub(crate) mod types; +pub mod runtime; +pub mod types; pub mod utility; diff --git a/zrml/futarchy/src/mock/types/oracle.rs b/zrml/futarchy/src/mock/types/oracle.rs index 92b5e6b79..fd0f226d3 100644 --- a/zrml/futarchy/src/mock/types/oracle.rs +++ b/zrml/futarchy/src/mock/types/oracle.rs @@ -22,6 +22,9 @@ use scale_info::TypeInfo; use sp_runtime::traits::Zero; use zeitgeist_primitives::{traits::FutarchyOracle, types::BlockNumber}; +#[cfg(feature = "fuzzing")] +use arbitrary::{Arbitrary, Unstructured, Result as ArbitraryResult}; + #[derive(Clone, Debug, Decode, Encode, Eq, MaxEncodedLen, PartialEq, TypeInfo)] pub struct MockOracle { weight: Weight, @@ -51,3 +54,16 @@ impl FutarchyOracle for MockOracle { Zero::zero() } } + +#[cfg(feature = "fuzzing")] +impl<'a> Arbitrary<'a> for MockOracle { + fn arbitrary(u: &mut Unstructured<'a>) -> ArbitraryResult { + let ref_time = u64::arbitrary(u)?; + let proof_size = u64::arbitrary(u)?; + let weight = Weight::from_parts(ref_time, proof_size); + + let value = bool::arbitrary(u)?; + + Ok(MockOracle::new(weight, value)) + } +} diff --git a/zrml/futarchy/src/types/proposal.rs b/zrml/futarchy/src/types/proposal.rs index 3576be99c..bb117c33f 100644 --- a/zrml/futarchy/src/types/proposal.rs +++ b/zrml/futarchy/src/types/proposal.rs @@ -21,6 +21,14 @@ use frame_system::pallet_prelude::BlockNumberFor; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; +#[cfg(feature = "fuzzing")] +use { + arbitrary::{Arbitrary, Result as ArbitraryResult, Unstructured}, + frame_support::traits::{Bounded}, + sp_core::H256, +}; + +// TODO Make config a generic, keeps things simple. #[derive( CloneNoBound, Decode, Encode, Eq, MaxEncodedLen, PartialEqNoBound, RuntimeDebugNoBound, TypeInfo, )] @@ -38,3 +46,23 @@ where /// The oracle that evaluates if the proposal should be enacted. pub oracle: OracleOf, } + +#[cfg(feature = "fuzzing")] +impl<'a, T> Arbitrary<'a> for Proposal +where + OracleOf: Arbitrary<'a>, + T: Config, +{ + fn arbitrary(u: &mut Unstructured<'a>) -> ArbitraryResult { + let when = u32::arbitrary(u)?.into(); + + let raw: [u8; 32] = Arbitrary::arbitrary(u)?; + let hash = H256(raw); + let len = u32::arbitrary(u)?; + let call = Bounded::Lookup { hash, len }; + + let oracle = Arbitrary::arbitrary(u)?; + + Ok(Proposal { when, call, oracle }) + } +} diff --git a/zrml/neo-swaps/fuzz/Cargo.toml b/zrml/neo-swaps/fuzz/Cargo.toml new file mode 100644 index 000000000..7b5c6dfd0 --- /dev/null +++ b/zrml/neo-swaps/fuzz/Cargo.toml @@ -0,0 +1,39 @@ +[[bin]] +doc = false +name = "deploy_combinatorial_pool" +path = "deploy_combinatorial_pool.rs" +test = false + +[[bin]] +doc = false +name = "combo_buy" +path = "combo_buy.rs" +test = false + +[[bin]] +doc = false +name = "combo_sell" +path = "combo_sell.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-neo-swaps = { workspace = true, features = ["default", "mock"] } + +[package] +authors = ["Forecasting Technologies Ltd"] +edition.workspace = true +name = "zrml-neo-swaps-fuzz" +publish = false +version = "0.5.5" + +[package.metadata] +cargo-fuzz = true + diff --git a/zrml/neo-swaps/fuzz/combo_buy.rs b/zrml/neo-swaps/fuzz/combo_buy.rs new file mode 100644 index 000000000..f1212bef5 --- /dev/null +++ b/zrml/neo-swaps/fuzz/combo_buy.rs @@ -0,0 +1,173 @@ +#![no_main] + +mod common; + +use arbitrary::{Arbitrary, Result as ArbitraryResult, Unstructured}; +use libfuzzer_sys::fuzz_target; +use orml_traits::currency::MultiCurrency; +use rand::seq::SliceRandom; +use sp_runtime::traits::Zero; +use zeitgeist_primitives::{ + constants::base_multiples::*, + traits::{CombinatorialTokensFuel, MarketCommonsPalletApi}, + types::{Asset, MarketType}, +}; +use zrml_neo_swaps::{ + mock::{ExtBuilder, NeoSwaps, Runtime, RuntimeOrigin}, + AccountIdOf, BalanceOf, Config, FuelOf, MarketIdOf, MAX_SPOT_PRICE, MIN_SPOT_PRICE, + MIN_SWAP_FEE, +}; + +#[derive(Debug)] +struct ComboBuyFuzzParams { + account_id: AccountIdOf, + pool_id: ::PoolId, + market_ids: Vec>, + spot_prices: Vec>, + swap_fee: BalanceOf, + category_counts: Vec, + asset_count: u16, + buy: Vec, + keep: Vec, + sell: Vec, + amount_buy: BalanceOf, + amount_keep: BalanceOf, + min_amount_out: BalanceOf, +} + +impl<'a> Arbitrary<'a> for ComboBuyFuzzParams { + fn arbitrary(u: &mut Unstructured<'a>) -> ArbitraryResult { + let account_id = u128::arbitrary(u)?; + let pool_id = 0; + let market_ids = vec![0, 1, 2]; + + let min_category_count = 2; + let max_category_count = 16; + let mut category_counts = vec![]; + for _ in market_ids.iter() { + // We're just assuming three markets here! + let category_count = u.int_in_range(min_category_count..=max_category_count)? as u16; + category_counts.push(category_count); + } + + let asset_count = category_counts.iter().product(); + let asset_count_usize = asset_count as usize; + + // Create arbitrary spot price vector by creating a vector of `MinSpotPrice` and then adding + // value to them in increments until a total spot price of one is reached. It's possible + // that this results in invalid spot prices, for example if `total_assets` is too large. + let mut spot_prices = vec![MIN_SPOT_PRICE; asset_count_usize]; + let increment = MIN_SPOT_PRICE; + while spot_prices.iter().sum::() < _1 { + let index = u.int_in_range(0..=asset_count_usize - 1)?; + if spot_prices[index] < MAX_SPOT_PRICE { + spot_prices[index] += increment; + } + } + + let swap_fee = u.int_in_range(MIN_SWAP_FEE..=::MaxSwapFee::get())?; + + // Shuffle 0..asset_count_usize and then obtain `buy` and `sell` from the result. + let mut indices: Vec = (0..asset_count_usize).collect(); + for i in (1..indices.len()).rev() { + let j = u.int_in_range(0..=i)?; + indices.swap(i, j); + } + + // This isn't perfectly random, but biased towards producing larger `buy` sets. + let buy_len = u.int_in_range(1..=asset_count_usize - 1)?; + let keep_len = u.int_in_range(0..=asset_count_usize - 1 - buy_len)?; + let buy = indices[0..buy_len].to_vec(); + let keep = indices[buy_len..buy_len + keep_len].to_vec(); + let sell = indices[buy_len + keep_len..asset_count_usize].to_vec(); + + let amount_buy = u.int_in_range(_1..=_100)?; + let amount_keep = + if keep.is_empty() { Zero::zero() } else { u.int_in_range(_1..=amount_buy)? }; + + let min_amount_out = Arbitrary::arbitrary(u)?; + + let params = ComboBuyFuzzParams { + account_id, + pool_id, + market_ids, + spot_prices, + swap_fee, + category_counts, + asset_count, + buy, + keep, + sell, + amount_buy, + amount_keep, + min_amount_out, + }; + + Ok(params) + } +} + +fuzz_target!(|params: ComboBuyFuzzParams| { + let mut ext = ExtBuilder::default().build(); + + ext.execute_with(|| { + // We create the required markets and deposit collateral in the user's account. + let collateral = Asset::Ztg; + for (market_id, &category_count) in params.category_counts.iter().enumerate() { + let market = common::market::( + market_id as u128, + collateral, + MarketType::Categorical(category_count), + ); + <::MarketCommons as MarketCommonsPalletApi>::push_market(market) + .unwrap(); + } + <::MultiCurrency>::deposit( + collateral, + ¶ms.account_id, + 100 * params.amount_buy, + ) + .unwrap(); + + // Create a pool to trade on. + NeoSwaps::deploy_combinatorial_pool( + RuntimeOrigin::signed(params.account_id), + params.asset_count, + params.market_ids, + 10 * params.amount_buy, + params.spot_prices, + params.swap_fee, + FuelOf::::from_total(16), + ) + .unwrap(); + + // Convert indices to assets an deposit funds for the user. + let assets = NeoSwaps::assets(params.pool_id).unwrap(); + for &asset in assets.iter() { + <::MultiCurrency>::deposit( + asset, + ¶ms.account_id, + params.amount_buy, + ) + .unwrap(); + } + + let buy = params.buy.into_iter().map(|i| assets[i]).collect(); + let keep = params.keep.into_iter().map(|i| assets[i]).collect(); + let sell = params.sell.into_iter().map(|i| assets[i]).collect(); + + let _ = NeoSwaps::combo_sell( + RuntimeOrigin::signed(params.account_id), + params.pool_id, + params.asset_count, + buy, + keep, + sell, + params.amount_buy, + params.amount_keep, + params.min_amount_out, + ); + }); + + let _ = ext.commit_all(); +}); diff --git a/zrml/neo-swaps/fuzz/combo_sell.rs b/zrml/neo-swaps/fuzz/combo_sell.rs new file mode 100644 index 000000000..69e1a16ab --- /dev/null +++ b/zrml/neo-swaps/fuzz/combo_sell.rs @@ -0,0 +1,149 @@ +#![no_main] + +mod common; + +use arbitrary::{Arbitrary, Result as ArbitraryResult, Unstructured}; +use libfuzzer_sys::fuzz_target; +use orml_traits::currency::MultiCurrency; +use rand::seq::SliceRandom; +use zeitgeist_primitives::{ + constants::base_multiples::*, + traits::{CombinatorialTokensFuel, MarketCommonsPalletApi}, + types::{Asset, MarketType}, +}; +use zrml_neo_swaps::{ + mock::{ExtBuilder, NeoSwaps, Runtime, RuntimeOrigin}, + AccountIdOf, BalanceOf, Config, FuelOf, MarketIdOf, MAX_SPOT_PRICE, MIN_SPOT_PRICE, + MIN_SWAP_FEE, +}; + +#[derive(Debug)] +struct ComboBuyFuzzParams { + account_id: AccountIdOf, + pool_id: ::PoolId, + market_ids: Vec>, + spot_prices: Vec>, + swap_fee: BalanceOf, + category_counts: Vec, + asset_count: u16, + buy: Vec, + sell: Vec, + amount_in: BalanceOf, + min_amount_out: BalanceOf, +} + +impl<'a> Arbitrary<'a> for ComboBuyFuzzParams { + fn arbitrary(u: &mut Unstructured<'a>) -> ArbitraryResult { + let account_id = u128::arbitrary(u)?; + let pool_id = 0; + let market_ids = vec![0, 1, 2]; + + let min_category_count = 2; + let max_category_count = 16; + let mut category_counts = vec![]; + for _ in market_ids.iter() { + // We're just assuming three markets here! + let category_count = u.int_in_range(min_category_count..=max_category_count)? as u16; + category_counts.push(category_count); + } + + let asset_count = category_counts.iter().product(); + let asset_count_usize = asset_count as usize; + + // Create arbitrary spot price vector by creating a vector of `MinSpotPrice` and then adding + // value to them in increments until a total spot price of one is reached. It's possible + // that this results in invalid spot prices, for example if `total_assets` is too large. + let mut spot_prices = vec![MIN_SPOT_PRICE; asset_count_usize]; + let increment = MIN_SPOT_PRICE; + while spot_prices.iter().sum::() < _1 { + let index = u.int_in_range(0..=asset_count_usize - 1)?; + if spot_prices[index] < MAX_SPOT_PRICE { + spot_prices[index] += increment; + } + } + + let swap_fee = u.int_in_range(MIN_SWAP_FEE..=::MaxSwapFee::get())?; + + // Shuffle 0..asset_count_usize and then obtain `buy` and `sell` from the result. + let mut indices: Vec = (0..asset_count_usize).collect(); + for i in (1..indices.len()).rev() { + let j = u.int_in_range(0..=i)?; + indices.swap(i, j); + } + let buy_len = u.int_in_range(1..=asset_count_usize - 1)?; + let buy = indices[0..buy_len].to_vec(); + let sell = indices[buy_len..asset_count_usize].to_vec(); + + let amount_in = u.int_in_range(_1..=_100)?; + let min_amount_out = Arbitrary::arbitrary(u)?; + + let params = ComboBuyFuzzParams { + account_id, + pool_id, + market_ids, + spot_prices, + swap_fee, + category_counts, + asset_count, + buy, + sell, + amount_in, + min_amount_out, + }; + + Ok(params) + } +} + +fuzz_target!(|params: ComboBuyFuzzParams| { + let mut ext = ExtBuilder::default().build(); + + ext.execute_with(|| { + // We create the required markets and deposit enough funds for the user. + let collateral = Asset::Ztg; + for (market_id, &category_count) in params.category_counts.iter().enumerate() { + let market = common::market::( + market_id as u128, + collateral, + MarketType::Categorical(category_count), + ); + <::MarketCommons as MarketCommonsPalletApi>::push_market(market) + .unwrap(); + } + <::MultiCurrency>::deposit( + collateral, + ¶ms.account_id, + 100 * params.amount_in, + ) + .unwrap(); + + // Create a pool to trade on. + NeoSwaps::deploy_combinatorial_pool( + RuntimeOrigin::signed(params.account_id), + params.asset_count, + params.market_ids, + 10 * params.amount_in, + params.spot_prices, + params.swap_fee, + FuelOf::::from_total(16), + ) + .unwrap(); + + // Convert indices to assets. + let assets = NeoSwaps::assets(params.pool_id).unwrap(); + let buy = params.buy.into_iter().map(|i| assets[i]).collect(); + let sell = params.sell.into_iter().map(|i| assets[i]).collect(); + + let _ = NeoSwaps::combo_buy( + RuntimeOrigin::signed(params.account_id), + params.pool_id, + params.asset_count, + buy, + sell, + params.amount_in, + params.min_amount_out, + ); + }); + + let _ = ext.commit_all(); +}); diff --git a/zrml/neo-swaps/fuzz/common.rs b/zrml/neo-swaps/fuzz/common.rs new file mode 100644 index 000000000..583dc430c --- /dev/null +++ b/zrml/neo-swaps/fuzz/common.rs @@ -0,0 +1,35 @@ +use zeitgeist_primitives::{ + traits::MarketOf, + types::{Market, MarketCreation, MarketPeriod, MarketStatus, MarketType, ScoringRule}, +}; +use zrml_neo_swaps::{AssetOf, Config, MarketIdOf}; + +pub(crate) fn market( + market_id: MarketIdOf, + base_asset: AssetOf, + market_type: MarketType, +) -> MarketOf<::MarketCommons> +where + T: 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, + } +} diff --git a/zrml/neo-swaps/fuzz/deploy_combinatorial_pool.rs b/zrml/neo-swaps/fuzz/deploy_combinatorial_pool.rs new file mode 100644 index 000000000..355ce40ac --- /dev/null +++ b/zrml/neo-swaps/fuzz/deploy_combinatorial_pool.rs @@ -0,0 +1,121 @@ +#![no_main] + +mod common; + +use arbitrary::{Arbitrary, Result as ArbitraryResult, Unstructured}; +use libfuzzer_sys::fuzz_target; +use orml_traits::currency::MultiCurrency; +use zeitgeist_primitives::{ + constants::base_multiples::*, + traits::{CombinatorialTokensFuel, MarketCommonsPalletApi}, + types::{Asset, MarketType}, +}; +use zrml_neo_swaps::{ + mock::{ExtBuilder, NeoSwaps, Runtime, RuntimeOrigin}, + AccountIdOf, BalanceOf, Config, FuelOf, MarketIdOf, COMBO_MAX_SPOT_PRICE, COMBO_MIN_SPOT_PRICE, + MIN_SWAP_FEE, +}; + +#[derive(Debug)] +struct DeployCombinatorialPoolFuzzParams { + account_id: AccountIdOf, + asset_count: u16, + market_ids: Vec>, + category_counts: Vec, + amount: BalanceOf, + spot_prices: Vec>, + swap_fee: BalanceOf, + fuel: FuelOf, +} + +impl<'a> Arbitrary<'a> for DeployCombinatorialPoolFuzzParams { + fn arbitrary(u: &mut Unstructured<'a>) -> ArbitraryResult { + let account_id = u128::arbitrary(u)?; + + let min_market_ids = 1; + let max_market_ids = 16; + let market_ids_len: usize = u.int_in_range(min_market_ids..=max_market_ids)?; + let market_ids = + (0..market_ids_len).map(|x| (x as u32).into()).collect::>>(); + + let min_category_count = 2; + let max_category_count = 16; + let mut category_counts = vec![]; + for _ in market_ids.iter() { + let category_count = u.int_in_range(min_category_count..=max_category_count)? as u16; + category_counts.push(category_count); + } + + let amount = Arbitrary::arbitrary(u)?; + + let asset_count: u16 = category_counts.iter().product(); + let asset_count_usize = asset_count as usize; + + // Create arbitrary spot price vector by creating a vector of `MinSpotPrice` and then adding + // value to them in increments until a total spot price of one is reached. It's possible + // that this results in invalid spot prices, for example if `total_assets` is too large. + let mut spot_prices = vec![COMBO_MIN_SPOT_PRICE; asset_count_usize]; + let increment = COMBO_MIN_SPOT_PRICE; + while spot_prices.iter().sum::() < _1 { + let index = u.int_in_range(0..=asset_count_usize - 1)?; + if spot_prices[index] < COMBO_MAX_SPOT_PRICE { + spot_prices[index] += increment; + } + } + + let swap_fee = u.int_in_range(MIN_SWAP_FEE..=::MaxSwapFee::get())?; + + let fuel = FuelOf::::from_total(u.int_in_range(1..=100)?); + + let params = DeployCombinatorialPoolFuzzParams { + account_id, + asset_count: asset_count as u16, + market_ids, + category_counts, + amount, + spot_prices, + swap_fee, + fuel, + }; + + Ok(params) + } +} + +fuzz_target!(|params: DeployCombinatorialPoolFuzzParams| { + let mut ext = ExtBuilder::default().build(); + + ext.execute_with(|| { + // We create the required markets and deposit enough funds for the user. + let collateral = Asset::Ztg; + for (&market_id, &category_count) in + params.market_ids.iter().zip(params.category_counts.iter()) + { + let market = common::market::( + market_id, + collateral, + MarketType::Categorical(category_count), + ); + <::MarketCommons as MarketCommonsPalletApi>::push_market(market) + .unwrap(); + } + <::MultiCurrency>::deposit( + collateral, + ¶ms.account_id, + params.amount, + ) + .unwrap(); + + let _ = NeoSwaps::deploy_combinatorial_pool( + RuntimeOrigin::signed(params.account_id), + params.asset_count, + params.market_ids, + params.amount, + params.spot_prices, + params.swap_fee, + params.fuel, + ); + }); + + let _ = ext.commit_all(); +}); diff --git a/zrml/neo-swaps/src/lib.rs b/zrml/neo-swaps/src/lib.rs index d034b2455..10529d50e 100644 --- a/zrml/neo-swaps/src/lib.rs +++ b/zrml/neo-swaps/src/lib.rs @@ -29,7 +29,7 @@ mod liquidity_tree; mod macros; mod math; pub mod migration; -mod mock; +pub mod mock; mod pool_storage; mod tests; pub mod traits; @@ -99,30 +99,28 @@ mod pallet { pub(crate) const EXIT_FEE: u128 = CENT / 10; /// The minimum allowed swap fee. Hardcoded to avoid misconfigurations which may lead to /// exploits. - pub(crate) const MIN_SWAP_FEE: u128 = BASE / 1_000; // 0.1%. + pub const MIN_SWAP_FEE: u128 = BASE / 1_000; // 0.1%. /// The maximum allowed spot price when creating a pool. - pub(crate) const MAX_SPOT_PRICE: u128 = BASE - CENT / 2; + pub const MAX_SPOT_PRICE: u128 = BASE - CENT / 2; /// The minimum allowed spot price when creating a pool. - pub(crate) const MIN_SPOT_PRICE: u128 = CENT / 2; + pub const MIN_SPOT_PRICE: u128 = CENT / 2; /// The maximum value the spot price is allowed to take in a combinatorial market. - pub(crate) const COMBO_MAX_SPOT_PRICE: u128 = BASE - CENT / 10; + pub const COMBO_MAX_SPOT_PRICE: u128 = BASE - CENT / 10; /// The minimum value the spot price is allowed to take in a combinatorial market. - pub(crate) const COMBO_MIN_SPOT_PRICE: u128 = CENT / 10; + pub const COMBO_MIN_SPOT_PRICE: u128 = CENT / 10; /// The minimum vallowed value of a pool's liquidity parameter. pub(crate) const MIN_LIQUIDITY: u128 = BASE; /// The minimum percentage each new LP position must increase the liquidity by, represented as /// fractional (0.0139098411 represents 1.39098411%). pub(crate) const MIN_RELATIVE_LP_POSITION_VALUE: u128 = 139098411; // 1.39098411% - pub(crate) type AccountIdOf = ::AccountId; - pub(crate) type AssetOf = Asset>; - pub(crate) type BalanceOf = + pub type AccountIdOf = ::AccountId; + pub type AssetOf = Asset>; + pub type BalanceOf = <::MultiCurrency as MultiCurrency>>::Balance; + pub type MarketIdOf = <::MarketCommons as MarketCommonsPalletApi>::MarketId; + pub type FuelOf = <::CombinatorialTokens as CombinatorialTokensApi>::Fuel; pub(crate) type AssetIndexType = u16; - pub(crate) type FuelOf = - <::CombinatorialTokens as CombinatorialTokensApi>::Fuel; - pub(crate) type MarketIdOf = - <::MarketCommons as MarketCommonsPalletApi>::MarketId; pub(crate) type LiquidityTreeOf = LiquidityTree::MaxLiquidityTreeDepth>; pub(crate) type PoolOf = Pool, MaxAssets>; pub(crate) type AmmTradeOf = AmmTrade>; @@ -838,13 +836,13 @@ mod pallet { /// that swap fees can be stored in the pool account without triggering dusting or failed /// transfers. /// - /// The `fuel` parameter specifies how much work the cryptographic id manager will do + /// The `fuel` parameter specifies how much work the cryptographic id manager will do /// and can be used for benchmarking purposes. /// /// # Complexity /// /// `O(n)` where `n` is the number of splits required to create the pool. - /// The `fuel` parameter specifies how much work the cryptographic id manager will do + /// The `fuel` parameter specifies how much work the cryptographic id manager will do /// and can be used for benchmarking purposes. #[pallet::call_index(8)] #[pallet::weight(T::WeightInfo::deploy_combinatorial_pool( @@ -1633,6 +1631,13 @@ mod pallet { T::PalletId::get().into_sub_account_truncating((*pool_id).saturated_into::()) } + /// Returns the assets contained in the pool given by `pool_id`. + pub fn assets(pool_id: T::PoolId) -> Result>, DispatchError> { + let pool = ::get(pool_id)?; + + Ok(pool.assets.into_inner()) + } + /// Distribute swap fees and external fees and returns the remaining amount. /// /// # Arguments From f0fcb2ce260794cdf85ac680621ec09204ea98c1 Mon Sep 17 00:00:00 2001 From: Chralt98 Date: Mon, 10 Feb 2025 15:42:52 +0100 Subject: [PATCH 52/73] update comments in neo-swaps --- zrml/neo-swaps/src/lib.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/zrml/neo-swaps/src/lib.rs b/zrml/neo-swaps/src/lib.rs index 10529d50e..d69484ea2 100644 --- a/zrml/neo-swaps/src/lib.rs +++ b/zrml/neo-swaps/src/lib.rs @@ -518,7 +518,7 @@ mod pallet { /// /// # Parameters /// - /// - `pool`: Identifier for the pool to add liquidity to. + /// - `pool_id`: Identifier for the pool to add liquidity to. /// - `pool_shares_amount`: The number of new pool shares the LP will receive. /// - `max_amounts_in`: Vector of the maximum amounts of each outcome token the LP is /// willing to deposit (with outcomes specified in the order of `MarketCommonsApi`). @@ -576,7 +576,7 @@ mod pallet { /// /// # Parameters /// - /// - `poold_id`: Identifier for the pool to withdraw liquidity from. + /// - `pool_id`: Identifier for the pool to withdraw liquidity from. /// - `pool_shares_amount_out`: The number of pool shares the LP will relinquish. /// - `min_amounts_out`: Vector of the minimum amounts of each outcome token the LP expects /// to withdraw (with outcomes specified in the order given by `MarketCommonsApi`). @@ -907,8 +907,12 @@ mod pallet { ); let buy = vec![asset_out]; let sell = pool.assets_complement(&buy); + // `swap_amount_out` is the amount of assets in sell (S) that are sold for more + // assets of buy (B). In the reference documentation it's called `y(x)` let swap_amount_out = pool.calculate_swap_amount_out_for_buy(buy, sell, amount_in_minus_fees)?; + // The following is the buy complete set amount plus the additional amount + // that was received through the sale of the unwanted outcomes in the sell. let amount_out = swap_amount_out.checked_add_res(&amount_in_minus_fees)?; ensure!(amount_out >= min_amount_out, Error::::AmountOutBelowMin); // Instead of letting `who` buy the complete sets and then transfer almost all of @@ -970,6 +974,10 @@ mod pallet { Error::::NumericalLimits(NumericalLimitsError::MaxAmountExceeded), ); + // `asset_in` is sold in order to get the amount of full sets of all possible + // outcomes, the `amount_out` is calculated in which all other assets are sold to + // get an equal amount of each possible asset back, + // which can then be burned for collateral let buy = vec![asset_in]; let keep = vec![]; let sell = pool.assets_complement(&buy); From 80cdd849182b17dd26c7a0acaeca709312a092da Mon Sep 17 00:00:00 2001 From: Chralt98 Date: Mon, 10 Feb 2025 15:54:32 +0100 Subject: [PATCH 53/73] remove duplicated numerical limits check --- zrml/neo-swaps/src/lib.rs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/zrml/neo-swaps/src/lib.rs b/zrml/neo-swaps/src/lib.rs index d69484ea2..095fe073a 100644 --- a/zrml/neo-swaps/src/lib.rs +++ b/zrml/neo-swaps/src/lib.rs @@ -1604,19 +1604,6 @@ mod pallet { ); } - // Ensure that numerical limits of all prices are respected. - for &asset in pool.assets().iter() { - let spot_price = pool.calculate_spot_price(asset)?; - ensure!( - spot_price >= COMBO_MIN_SPOT_PRICE.saturated_into(), - Error::::NumericalLimits(NumericalLimitsError::SpotPriceSlippedTooLow) - ); - ensure!( - spot_price <= COMBO_MAX_SPOT_PRICE.saturated_into(), - Error::::NumericalLimits(NumericalLimitsError::SpotPriceSlippedTooHigh) - ); - } - Self::deposit_event(Event::::ComboSellExecuted { who: who.clone(), pool_id, From 6d565866898d01120b0651f094f2f7e75b5da567 Mon Sep 17 00:00:00 2001 From: Chralt98 Date: Mon, 10 Feb 2025 16:37:36 +0100 Subject: [PATCH 54/73] correct comments in split_position.rs --- zrml/combinatorial-tokens/src/tests/split_position.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/zrml/combinatorial-tokens/src/tests/split_position.rs b/zrml/combinatorial-tokens/src/tests/split_position.rs index 1b16a7ab4..2303e2ae2 100644 --- a/zrml/combinatorial-tokens/src/tests/split_position.rs +++ b/zrml/combinatorial-tokens/src/tests/split_position.rs @@ -110,7 +110,7 @@ fn split_position_works_vertical_with_parent() { Fuel::new(16, false), )); - // Alice is left with 1 unit of [0, 0, 1], 2 units of [1, 1, 0] and one unit of each of the + // Alice is left with 2 units of [0, 0, 1], 3 units of [1, 1, 0] and one unit of each of the // two new tokens. let ct_001 = CombinatorialToken([ 207, 168, 160, 93, 238, 221, 197, 1, 171, 102, 28, 24, 18, 107, 205, 231, 227, 98, 220, @@ -286,7 +286,7 @@ fn split_position_fails_on_insufficient_funds_native_token_no_parent() { ExtBuilder::build().execute_with(|| { let alice = Account::new(0).deposit(Asset::Ztg, _99).unwrap(); - // Market has three outcomes, but there's an element in the partition of size two. + let market_id = create_market(Asset::Ztg, MarketType::Categorical(3)); assert_noop!( @@ -308,7 +308,7 @@ fn split_position_fails_on_insufficient_funds_foreign_token_no_parent() { ExtBuilder::build().execute_with(|| { let alice = Account::new(0).deposit(Asset::ForeignAsset(1), _99).unwrap(); - // Market has three outcomes, but there's an element in the partition of size two. + let market_id = create_market(Asset::Ztg, MarketType::Categorical(3)); assert_noop!( From ca389c6dca6563f8df5b21301ba7e39d170d9a9d Mon Sep 17 00:00:00 2001 From: Chralt98 Date: Mon, 10 Feb 2025 16:39:38 +0100 Subject: [PATCH 55/73] fix comments in merge_position --- zrml/combinatorial-tokens/src/tests/merge_position.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/zrml/combinatorial-tokens/src/tests/merge_position.rs b/zrml/combinatorial-tokens/src/tests/merge_position.rs index 853036df4..a4582746d 100644 --- a/zrml/combinatorial-tokens/src/tests/merge_position.rs +++ b/zrml/combinatorial-tokens/src/tests/merge_position.rs @@ -216,7 +216,7 @@ fn merge_position_fails_on_trivial_partition_member() { ExtBuilder::build().execute_with(|| { let alice = Account::new(0).deposit(Asset::Ztg, _100).unwrap(); - // Market has three outcomes, but there's an element in the partition of size two. + let market_id = create_market(Asset::Ztg, MarketType::Categorical(3)); let partition = vec![vec![B1, B0, B1], vec![B0, B0, B0]]; @@ -239,7 +239,7 @@ fn merge_position_fails_on_overlapping_partition_members() { ExtBuilder::build().execute_with(|| { let alice = Account::new(0).deposit(Asset::Ztg, _100).unwrap(); - // Market has three outcomes, but there's an element in the partition of size two. + let market_id = create_market(Asset::Ztg, MarketType::Categorical(3)); let partition = vec![vec![B1, B0, B1], vec![B0, B0, B1]]; @@ -262,7 +262,7 @@ fn merge_position_fails_on_insufficient_funds() { ExtBuilder::build().execute_with(|| { let alice = Account::new(0).deposit(Asset::Ztg, _99).unwrap(); - // Market has three outcomes, but there's an element in the partition of size two. + let market_id = create_market(Asset::Ztg, MarketType::Categorical(3)); assert_noop!( @@ -284,7 +284,7 @@ fn merge_position_fails_on_insufficient_funds_foreign_token() { ExtBuilder::build().execute_with(|| { let alice = Account::new(0).deposit(Asset::ForeignAsset(1), _99).unwrap(); - // Market has three outcomes, but there's an element in the partition of size two. + let market_id = create_market(Asset::Ztg, MarketType::Categorical(3)); assert_noop!( From f81cefbd563b5369efeb1905220791d90c78ee18 Mon Sep 17 00:00:00 2001 From: Chralt98 Date: Mon, 10 Feb 2025 16:41:34 +0100 Subject: [PATCH 56/73] correct redeem_position test function name --- zrml/combinatorial-tokens/src/tests/redeem_position.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zrml/combinatorial-tokens/src/tests/redeem_position.rs b/zrml/combinatorial-tokens/src/tests/redeem_position.rs index ffabc600d..359629db4 100644 --- a/zrml/combinatorial-tokens/src/tests/redeem_position.rs +++ b/zrml/combinatorial-tokens/src/tests/redeem_position.rs @@ -78,7 +78,7 @@ fn redeem_position_fails_on_incorrect_index_set(index_set: Vec) { } #[test] -fn redeem_position_fails_if_tokens_have_to_value() { +fn redeem_position_fails_if_tokens_have_no_value() { ExtBuilder::build().execute_with(|| { let alice = Account::new(0).deposit(Asset::Ztg, _100).unwrap(); MockPayout::set_return_value(Some(vec![0, _1_2, _1_2, 0])); From d7f9fbb52779c42c03b8ba864b05fbd2ede44620 Mon Sep 17 00:00:00 2001 From: Chralt98 Date: Mon, 10 Feb 2025 16:43:08 +0100 Subject: [PATCH 57/73] use HARD_DEADLINE scheduler priority --- zrml/futarchy/src/pallet_impls.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/zrml/futarchy/src/pallet_impls.rs b/zrml/futarchy/src/pallet_impls.rs index c5019ad6d..601eb27c7 100644 --- a/zrml/futarchy/src/pallet_impls.rs +++ b/zrml/futarchy/src/pallet_impls.rs @@ -19,14 +19,10 @@ use crate::{types::Proposal, weights::WeightInfoZeitgeist, Config, Event, Pallet use frame_support::{ dispatch::RawOrigin, pallet_prelude::Weight, - traits::schedule::{v3::Anon, DispatchTime}, + traits::schedule::{v3::Anon, DispatchTime, HARD_DEADLINE}, }; use zeitgeist_primitives::traits::FutarchyOracle; -// Following Parity's implementation of pallet-democracy, we're using minimum priority for futarchy -// proposals. -const SCHEDULE_PRIORITY: u8 = 63; - impl Pallet { /// Evaluates `proposal` using the specified oracle and schedules the contained call if the /// oracle approves. @@ -37,7 +33,7 @@ impl Pallet { let result = T::Scheduler::schedule( DispatchTime::At(proposal.when), None, - SCHEDULE_PRIORITY, + HARD_DEADLINE, RawOrigin::Root.into(), proposal.call.clone(), ); From 22385d32a38f26467da7017fd060ec8381219174 Mon Sep 17 00:00:00 2001 From: Chralt98 Date: Tue, 11 Feb 2025 12:59:56 +0100 Subject: [PATCH 58/73] fix futarchy benchmarks and fmt --- zrml/combinatorial-tokens/src/lib.rs | 3 +- .../src/tests/merge_position.rs | 4 -- .../src/tests/split_position.rs | 2 - zrml/futarchy/src/benchmarking.rs | 30 +++++++++++- zrml/futarchy/src/lib.rs | 7 ++- zrml/futarchy/src/mock/types/oracle.rs | 2 +- zrml/futarchy/src/types/proposal.rs | 2 +- zrml/futarchy/src/weights.rs | 49 +++++++++++++------ zrml/neo-swaps/src/lib.rs | 2 +- 9 files changed, 71 insertions(+), 30 deletions(-) diff --git a/zrml/combinatorial-tokens/src/lib.rs b/zrml/combinatorial-tokens/src/lib.rs index 2530195e9..56283f2ea 100644 --- a/zrml/combinatorial-tokens/src/lib.rs +++ b/zrml/combinatorial-tokens/src/lib.rs @@ -125,8 +125,7 @@ mod pallet { pub type CombinatorialIdOf = <::CombinatorialIdManager as CombinatorialIdManager>::CombinatorialId; pub type MarketIdOf = <::MarketCommons as MarketCommonsPalletApi>::MarketId; - pub type FuelOf = - <::CombinatorialIdManager as CombinatorialIdManager>::Fuel; + pub type FuelOf = <::CombinatorialIdManager as CombinatorialIdManager>::Fuel; pub(crate) type SplitPositionDispatchInfoOf = SplitPositionDispatchInfo, MarketIdOf>; diff --git a/zrml/combinatorial-tokens/src/tests/merge_position.rs b/zrml/combinatorial-tokens/src/tests/merge_position.rs index a4582746d..f3a4d1d15 100644 --- a/zrml/combinatorial-tokens/src/tests/merge_position.rs +++ b/zrml/combinatorial-tokens/src/tests/merge_position.rs @@ -216,7 +216,6 @@ fn merge_position_fails_on_trivial_partition_member() { ExtBuilder::build().execute_with(|| { let alice = Account::new(0).deposit(Asset::Ztg, _100).unwrap(); - let market_id = create_market(Asset::Ztg, MarketType::Categorical(3)); let partition = vec![vec![B1, B0, B1], vec![B0, B0, B0]]; @@ -239,7 +238,6 @@ fn merge_position_fails_on_overlapping_partition_members() { ExtBuilder::build().execute_with(|| { let alice = Account::new(0).deposit(Asset::Ztg, _100).unwrap(); - let market_id = create_market(Asset::Ztg, MarketType::Categorical(3)); let partition = vec![vec![B1, B0, B1], vec![B0, B0, B1]]; @@ -262,7 +260,6 @@ fn merge_position_fails_on_insufficient_funds() { ExtBuilder::build().execute_with(|| { let alice = Account::new(0).deposit(Asset::Ztg, _99).unwrap(); - let market_id = create_market(Asset::Ztg, MarketType::Categorical(3)); assert_noop!( @@ -284,7 +281,6 @@ fn merge_position_fails_on_insufficient_funds_foreign_token() { ExtBuilder::build().execute_with(|| { let alice = Account::new(0).deposit(Asset::ForeignAsset(1), _99).unwrap(); - let market_id = create_market(Asset::Ztg, MarketType::Categorical(3)); assert_noop!( diff --git a/zrml/combinatorial-tokens/src/tests/split_position.rs b/zrml/combinatorial-tokens/src/tests/split_position.rs index 2303e2ae2..57e7b4f7a 100644 --- a/zrml/combinatorial-tokens/src/tests/split_position.rs +++ b/zrml/combinatorial-tokens/src/tests/split_position.rs @@ -286,7 +286,6 @@ fn split_position_fails_on_insufficient_funds_native_token_no_parent() { ExtBuilder::build().execute_with(|| { let alice = Account::new(0).deposit(Asset::Ztg, _99).unwrap(); - let market_id = create_market(Asset::Ztg, MarketType::Categorical(3)); assert_noop!( @@ -308,7 +307,6 @@ fn split_position_fails_on_insufficient_funds_foreign_token_no_parent() { ExtBuilder::build().execute_with(|| { let alice = Account::new(0).deposit(Asset::ForeignAsset(1), _99).unwrap(); - let market_id = create_market(Asset::Ztg, MarketType::Categorical(3)); assert_noop!( diff --git a/zrml/futarchy/src/benchmarking.rs b/zrml/futarchy/src/benchmarking.rs index 23de777c8..53b0039d0 100644 --- a/zrml/futarchy/src/benchmarking.rs +++ b/zrml/futarchy/src/benchmarking.rs @@ -17,7 +17,7 @@ #![cfg(feature = "runtime-benchmarks")] -use crate::{types::Proposal, Call, Config, Event, Pallet}; +use crate::{traits::ProposalStorage, types::Proposal, Call, Config, Event, Pallet, Proposals}; use alloc::vec; use frame_benchmarking::v2::*; use frame_support::{ @@ -42,6 +42,14 @@ mod benchmarks { oracle, }; + let now = System::::block_number(); + let to_be_scheduled_at = now + duration; + let mut proposals = Proposals::::get(to_be_scheduled_at); + for _ in 0..(T::MaxProposals::get() - 1) { + proposals.try_push(proposal.clone()).unwrap(); + } + Proposals::::insert(to_be_scheduled_at, proposals); + #[extrinsic_call] _(RawOrigin::Root, duration, proposal.clone()); @@ -66,6 +74,26 @@ mod benchmarks { System::::assert_last_event(expected_event.into()); } + #[benchmark] + fn take_proposals(n: Linear<1, 4>) { + let when = u32::MAX.into(); + let oracle = T::BenchmarkHelper::create_oracle(true); + let proposal = + Proposal { when, call: Bounded::Inline(vec![7u8; 128].try_into().unwrap()), oracle }; + + let now = System::::block_number(); + let mut proposals = Proposals::::get(now); + for _ in 0..n { + proposals.try_push(proposal.clone()).unwrap(); + } + Proposals::::insert(now, proposals); + + #[block] + { + let _ = as ProposalStorage>::take(now); + } + } + impl_benchmark_test_suite!( Pallet, crate::mock::ext_builder::ExtBuilder::build(), diff --git a/zrml/futarchy/src/lib.rs b/zrml/futarchy/src/lib.rs index f37c5fb70..7d03b0063 100644 --- a/zrml/futarchy/src/lib.rs +++ b/zrml/futarchy/src/lib.rs @@ -197,11 +197,14 @@ mod pallet { return total_weight; } - // - total_weight = total_weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); let proposals = if let Ok(proposals) = as ProposalStorage>::take(now) { + total_weight = total_weight + .saturating_add(T::WeightInfo::take_proposals(proposals.len() as u32)); proposals } else { + // assumes the worst case scenario + total_weight = total_weight + .saturating_add(T::WeightInfo::take_proposals(T::MaxProposals::get())); return total_weight; }; diff --git a/zrml/futarchy/src/mock/types/oracle.rs b/zrml/futarchy/src/mock/types/oracle.rs index fd0f226d3..0d379ddc1 100644 --- a/zrml/futarchy/src/mock/types/oracle.rs +++ b/zrml/futarchy/src/mock/types/oracle.rs @@ -23,7 +23,7 @@ use sp_runtime::traits::Zero; use zeitgeist_primitives::{traits::FutarchyOracle, types::BlockNumber}; #[cfg(feature = "fuzzing")] -use arbitrary::{Arbitrary, Unstructured, Result as ArbitraryResult}; +use arbitrary::{Arbitrary, Result as ArbitraryResult, Unstructured}; #[derive(Clone, Debug, Decode, Encode, Eq, MaxEncodedLen, PartialEq, TypeInfo)] pub struct MockOracle { diff --git a/zrml/futarchy/src/types/proposal.rs b/zrml/futarchy/src/types/proposal.rs index bb117c33f..78f8c19d1 100644 --- a/zrml/futarchy/src/types/proposal.rs +++ b/zrml/futarchy/src/types/proposal.rs @@ -24,7 +24,7 @@ use scale_info::TypeInfo; #[cfg(feature = "fuzzing")] use { arbitrary::{Arbitrary, Result as ArbitraryResult, Unstructured}, - frame_support::traits::{Bounded}, + frame_support::traits::Bounded, sp_core::H256, }; diff --git a/zrml/futarchy/src/weights.rs b/zrml/futarchy/src/weights.rs index c63ae28cc..0235ca85e 100644 --- a/zrml/futarchy/src/weights.rs +++ b/zrml/futarchy/src/weights.rs @@ -19,13 +19,13 @@ //! Autogenerated weights for zrml_futarchy //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2024-10-30`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2025-02-11`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `ztg-benchmark`, CPU: `AMD EPYC 7601 32-Core Processor` +//! HOSTNAME: `msi-pro-b650-s`, CPU: `AMD Ryzen 9 7950X3D 16-Core Processor` //! EXECUTION: ``, WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` // Executed Command: -// ./target/production/zeitgeist +// ./target/release/zeitgeist // benchmark // pallet // --chain=dev @@ -51,32 +51,49 @@ use frame_support::{traits::Get, weights::Weight}; pub trait WeightInfoZeitgeist { fn submit_proposal() -> Weight; fn maybe_schedule_proposal() -> Weight; + fn take_proposals(n: u32) -> Weight; } /// Weight functions for zrml_futarchy (automatically generated) pub struct WeightInfo(PhantomData); impl WeightInfoZeitgeist for WeightInfo { + /// Storage: `Futarchy::ProposalCount` (r:1 w:1) + /// Proof: `Futarchy::ProposalCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `Futarchy::Proposals` (r:1 w:1) - /// Proof: `Futarchy::Proposals` (`max_values`: None, `max_size`: Some(3561), added: 6036, mode: `MaxEncodedLen`) + /// Proof: `Futarchy::Proposals` (`max_values`: None, `max_size`: Some(1261), added: 3736, mode: `MaxEncodedLen`) fn submit_proposal() -> Weight { // Proof Size summary in bytes: - // Measured: `41` - // Estimated: `7026` - // Minimum execution time: 18_200 nanoseconds. - Weight::from_parts(18_871_000, 7026) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) + // Measured: `122` + // Estimated: `4726` + // Minimum execution time: 14_000 nanoseconds. + Weight::from_parts(14_500_000, 4726) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `NeoSwaps::Pools` (r:1 w:0) - /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(152829), added: 155304, mode: `MaxEncodedLen`) /// Storage: `Scheduler::Agenda` (r:1 w:1) /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(109074), added: 111549, mode: `MaxEncodedLen`) fn maybe_schedule_proposal() -> Weight { // Proof Size summary in bytes: - // Measured: `480` - // Estimated: `156294` - // Minimum execution time: 98_532 nanoseconds. - Weight::from_parts(100_052_000, 156294) + // Measured: `3` + // Estimated: `112539` + // Minimum execution time: 10_120 nanoseconds. + Weight::from_parts(10_550_000, 112539) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Futarchy::Proposals` (r:1 w:1) + /// Proof: `Futarchy::Proposals` (`max_values`: None, `max_size`: Some(1261), added: 3736, mode: `MaxEncodedLen`) + /// Storage: `Futarchy::ProposalCount` (r:1 w:0) + /// Proof: `Futarchy::ProposalCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// The range of component `n` is `[1, 4]`. + fn take_proposals(n: u32) -> Weight { + // Proof Size summary in bytes: + // Measured: `94 + n * (309 ±0)` + // Estimated: `4726` + // Minimum execution time: 5_610 nanoseconds. + Weight::from_parts(5_757_222, 4726) + // Standard Error: 3_602 + .saturating_add(Weight::from_parts(274_609, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/zrml/neo-swaps/src/lib.rs b/zrml/neo-swaps/src/lib.rs index 095fe073a..a1f278f8e 100644 --- a/zrml/neo-swaps/src/lib.rs +++ b/zrml/neo-swaps/src/lib.rs @@ -976,7 +976,7 @@ mod pallet { // `asset_in` is sold in order to get the amount of full sets of all possible // outcomes, the `amount_out` is calculated in which all other assets are sold to - // get an equal amount of each possible asset back, + // get an equal amount of each possible asset back, // which can then be burned for collateral let buy = vec![asset_in]; let keep = vec![]; From cf6a9d27de3a62e70b57278ce71637b76315a58a Mon Sep 17 00:00:00 2001 From: Chralt98 Date: Tue, 11 Feb 2025 13:21:12 +0100 Subject: [PATCH 59/73] add comments for combo buy --- zrml/neo-swaps/src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/zrml/neo-swaps/src/lib.rs b/zrml/neo-swaps/src/lib.rs index a1f278f8e..ee8d28fdb 100644 --- a/zrml/neo-swaps/src/lib.rs +++ b/zrml/neo-swaps/src/lib.rs @@ -1425,11 +1425,15 @@ mod pallet { swap_fees: swap_fee_amount, external_fees: external_fee_amount, } = Self::distribute_fees(pool, &who, amount_in)?; + // `swap_amount_out` is the amount of assets in sell (S) that are sold for more + // assets of buy (B). In the reference documentation it's called `y(x)` let swap_amount_out = pool.calculate_swap_amount_out_for_buy( buy.clone(), sell.clone(), amount_in_minus_fees, )?; + // The following is the buy complete set amount plus the additional amount + // that was received through the sale of the unwanted outcomes in the sell. let amount_out = swap_amount_out.checked_add_res(&amount_in_minus_fees)?; ensure!(amount_out >= min_amount_out, Error::::AmountOutBelowMin); From a9860b6246289f29cc9033565a193860cf0b532d Mon Sep 17 00:00:00 2001 From: Chralt98 Date: Tue, 11 Feb 2025 13:23:43 +0100 Subject: [PATCH 60/73] correct combo buy test --- zrml/neo-swaps/src/tests/combo_buy.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zrml/neo-swaps/src/tests/combo_buy.rs b/zrml/neo-swaps/src/tests/combo_buy.rs index 7cdd2dd4f..4cbdaee2d 100644 --- a/zrml/neo-swaps/src/tests/combo_buy.rs +++ b/zrml/neo-swaps/src/tests/combo_buy.rs @@ -136,7 +136,8 @@ fn combo_buy_works() { vec![1, 3, 4], 5_208_333_333_333, 6_576_234_413_778, - 5_000_000_000_000, + // keep_indices vector is empty anyways, so `expected_amount_out_keep` amount has no effect + 0, vec![ 8_423_765_586_223, 1500 * _1 + 1, From 7cb8cf4cbbcdc0f8b11df4e0d839adb28584071a Mon Sep 17 00:00:00 2001 From: Chralt98 Date: Tue, 11 Feb 2025 13:24:36 +0100 Subject: [PATCH 61/73] rename test function --- zrml/neo-swaps/src/tests/combo_buy.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zrml/neo-swaps/src/tests/combo_buy.rs b/zrml/neo-swaps/src/tests/combo_buy.rs index 4cbdaee2d..07501c232 100644 --- a/zrml/neo-swaps/src/tests/combo_buy.rs +++ b/zrml/neo-swaps/src/tests/combo_buy.rs @@ -256,7 +256,7 @@ fn combo_buy_fails_on_incorrect_asset_count() { } #[test] -fn combo_buy_fails_on_market_not_found() { +fn combo_buy_fails_on_pool_not_found() { ExtBuilder::default().build().execute_with(|| { let (_, pool_id) = create_markets_and_deploy_combinatorial_pool( ALICE, From 4d0078d62168b62ac02f61cfe335adfb0b492079 Mon Sep 17 00:00:00 2001 From: Chralt98 Date: Tue, 11 Feb 2025 13:28:50 +0100 Subject: [PATCH 62/73] add test comment --- zrml/neo-swaps/src/tests/deploy_combinatorial_pool.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/zrml/neo-swaps/src/tests/deploy_combinatorial_pool.rs b/zrml/neo-swaps/src/tests/deploy_combinatorial_pool.rs index 2037c2f3e..dd5970ff6 100644 --- a/zrml/neo-swaps/src/tests/deploy_combinatorial_pool.rs +++ b/zrml/neo-swaps/src/tests/deploy_combinatorial_pool.rs @@ -223,6 +223,7 @@ fn deploy_combinatorial_pool_works_with_multiple_markets() { #[test] fn deploy_combinatorial_pool_fails_on_incorrect_vec_len() { ExtBuilder::default().build().execute_with(|| { + // The following markets will produce 6 collections: LONG & 0, LONG & 1, LONG & 2, SHORT & 0, SHORT & 1, SHORT & 2 let market_ids = vec![ create_market(ALICE, BASE_ASSET, MarketType::Scalar(0..=1), ScoringRule::AmmCdaHybrid), create_market(ALICE, BASE_ASSET, MarketType::Categorical(3), ScoringRule::AmmCdaHybrid), @@ -233,6 +234,7 @@ fn deploy_combinatorial_pool_fails_on_incorrect_vec_len() { 6, market_ids, _10, + // Here it's five spot prices although the above market ids will have 6 spot prices. vec![20 * CENT; 5], CENT, Fuel::new(16, false), From f139fc9da1b7ff774dbb351e3397c6b70eb7012c Mon Sep 17 00:00:00 2001 From: Chralt98 Date: Tue, 11 Feb 2025 13:31:25 +0100 Subject: [PATCH 63/73] add test comment --- zrml/neo-swaps/src/tests/deploy_combinatorial_pool.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/zrml/neo-swaps/src/tests/deploy_combinatorial_pool.rs b/zrml/neo-swaps/src/tests/deploy_combinatorial_pool.rs index dd5970ff6..b936a9292 100644 --- a/zrml/neo-swaps/src/tests/deploy_combinatorial_pool.rs +++ b/zrml/neo-swaps/src/tests/deploy_combinatorial_pool.rs @@ -479,6 +479,7 @@ fn deploy_combinatorial_pool_fails_on_insufficient_funds() { assert_noop!( NeoSwaps::deploy_combinatorial_pool( + // BOB doesn't have enough funds RuntimeOrigin::signed(BOB), 2, vec![market_id], From f4c4ac6cf0ad9ec0eee9e89d523f405a12a8a62e Mon Sep 17 00:00:00 2001 From: Chralt98 Date: Tue, 11 Feb 2025 13:33:27 +0100 Subject: [PATCH 64/73] correct neo-swaps benchmark --- zrml/neo-swaps/src/benchmarking.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zrml/neo-swaps/src/benchmarking.rs b/zrml/neo-swaps/src/benchmarking.rs index 6a7886844..433adfd80 100644 --- a/zrml/neo-swaps/src/benchmarking.rs +++ b/zrml/neo-swaps/src/benchmarking.rs @@ -604,7 +604,7 @@ mod benchmarks { // We don't care about being precise here and just deposit a huge bunch of tokens for Bob. for &asset in assets.iter() { - let amount_for_bob = amount_buy.max(amount_buy); + let amount_for_bob = amount_buy; assert_ok!(T::MultiCurrency::deposit(asset, &bob, amount_for_bob)); } From 86552c34e222e5674f79acb0cfece8df8d2dd434 Mon Sep 17 00:00:00 2001 From: Chralt98 Date: Tue, 11 Feb 2025 13:36:30 +0100 Subject: [PATCH 65/73] add reminder from the documentation --- zrml/neo-swaps/src/math/types/combo_math.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/zrml/neo-swaps/src/math/types/combo_math.rs b/zrml/neo-swaps/src/math/types/combo_math.rs index 585464776..c336db042 100644 --- a/zrml/neo-swaps/src/math/types/combo_math.rs +++ b/zrml/neo-swaps/src/math/types/combo_math.rs @@ -199,6 +199,7 @@ mod detail { let exp_of_minus_amount_in: FixedType = protected_exp(amount_in_div_liquidity, true)?; let exp_of_minus_amount_in_times_exp_sum_sell = exp_of_minus_amount_in.checked_mul(exp_sum_sell)?; + // Reminder from the documentation: `exp_sum_buy + exp_sum_sell = 1 - exp_sum_keep` let numerator = exp_sum_buy .checked_add(exp_sum_sell)? .checked_sub(exp_of_minus_amount_in_times_exp_sum_sell)?; From eb08fc5e6a4f2e4cb5a45f870afe910d7ad3a298 Mon Sep 17 00:00:00 2001 From: Chralt98 Date: Tue, 11 Feb 2025 13:52:31 +0100 Subject: [PATCH 66/73] update gnosis doc references --- .../decompressor/tests/get_collection_id.rs | 6 +++++- zrml/neo-swaps/docs/docs.tex | 2 +- zrml/neo-swaps/src/math/types/combo_math.rs | 2 +- zrml/neo-swaps/src/math/types/math.rs | 2 +- zrml/neo-swaps/src/tests/buy.rs | 2 +- zrml/neo-swaps/src/tests/combo_buy.rs | 2 +- 6 files changed, 10 insertions(+), 6 deletions(-) diff --git a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/get_collection_id.rs b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/get_collection_id.rs index 34a698a89..b19615caa 100644 --- a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/get_collection_id.rs +++ b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/get_collection_id.rs @@ -18,15 +18,17 @@ use super::*; use rstest::rstest; -// Gnosis test cases using mocked keccak256 results, found here: https://docs.gnosis.io/conditionaltokens/docs/devguide05 +// Gnosis test cases using mocked keccak256 results, found here: https://gnosis-conditional-tokens.readthedocs.io/en/latest/developer-guide.html# #[rstest] #[case( + // 0x52ff54f0f5616e34a2d4f56fb68ab4cc636bf0d92111de74d1ec99040a8da118 [ 0x52, 0xFF, 0x54, 0xF0, 0xF5, 0x61, 0x6E, 0x34, 0xA2, 0xD4, 0xF5, 0x6F, 0xB6, 0x8A, 0xB4, 0xCC, 0x63, 0x6B, 0xF0, 0xD9, 0x21, 0x11, 0xDE, 0x74, 0xD1, 0xEC, 0x99, 0x04, 0x0A, 0x8D, 0xA1, 0x18, ], None, + // 0x229b067e142fce0aea84afb935095c6ecbea8647b8a013e795cc0ced3210a3d5 Some([ 0x22, 0x9B, 0x06, 0x7E, 0x14, 0x2F, 0xCE, 0x0A, 0xEA, 0x84, 0xAF, 0xB9, 0x35, 0x09, 0x5C, 0x6E, 0xCB, 0xEA, 0x86, 0x47, 0xB8, 0xA0, 0x13, 0xE7, 0x95, 0xCC, 0x0C, 0xED, 0x32, 0x10, @@ -40,6 +42,7 @@ use rstest::rstest; 0x7C, 0xDC, ], None, + // 0x560ae373ed304932b6f424c8a243842092c117645533390a3c1c95ff481587c2 Some([ 0x56, 0x0A, 0xE3, 0x73, 0xED, 0x30, 0x49, 0x32, 0xB6, 0xF4, 0x24, 0xC8, 0xA2, 0x43, 0x84, 0x20, 0x92, 0xC1, 0x17, 0x64, 0x55, 0x33, 0x39, 0x0A, 0x3C, 0x1C, 0x95, 0xFF, 0x48, 0x15, @@ -57,6 +60,7 @@ use rstest::rstest; 0x6E, 0xCB, 0xEA, 0x86, 0x47, 0xB8, 0xA0, 0x13, 0xE7, 0x95, 0xCC, 0x0C, 0xED, 0x32, 0x10, 0xA3, 0xD5, ]), + // 0x6f722aa250221af2eba9868fc9d7d43994794177dd6fa7766e3e72ba3c111909 Some([ 0x6F, 0x72, 0x2A, 0xA2, 0x50, 0x22, 0x1A, 0xF2, 0xEB, 0xA9, 0x86, 0x8F, 0xC9, 0xD7, 0xD4, 0x39, 0x94, 0x79, 0x41, 0x77, 0xDD, 0x6F, 0xA7, 0x76, 0x6E, 0x3E, 0x72, 0xBA, 0x3C, 0x11, diff --git a/zrml/neo-swaps/docs/docs.tex b/zrml/neo-swaps/docs/docs.tex index 3b18abca4..3733af17f 100644 --- a/zrml/neo-swaps/docs/docs.tex +++ b/zrml/neo-swaps/docs/docs.tex @@ -36,7 +36,7 @@ \section{Introduction} -This document provides the mathematical and technical details for zrml-neo-swaps. The automatic market maker (AMM) implemented by zrml-neo-swaps is a variant of the Logarithmic Market Scoring Rule (LMSR; \cite{hanson_2003}) which was first developed by Gnosis (see \url{https://docs.gnosis.io/conditionaltokens/docs/introduction3/}). We often refer to it as AMM 2.0. +This document provides the mathematical and technical details for zrml-neo-swaps. The automatic market maker (AMM) implemented by zrml-neo-swaps is a variant of the Logarithmic Market Scoring Rule (LMSR; \cite{hanson_2003}) which was first developed by Gnosis (see \url{https://gnosis-conditional-tokens.readthedocs.io/en/latest/developer-guide.html#}). We often refer to it as AMM 2.0. Unlike the typical implementation using a cost function (see \cite{chen_vaughan_2010}), this implementation of LMSR is a \emph{constant-function market maker} (CFMM), similar to the classical constant product market maker, which allows us to implement \emph{dynamic liquidity}. In other words, liquidity providers (LPs) can come and go as they please, allowing the market to self-regulate how much price resistance the AMM should provide. diff --git a/zrml/neo-swaps/src/math/types/combo_math.rs b/zrml/neo-swaps/src/math/types/combo_math.rs index c336db042..ef3a8206d 100644 --- a/zrml/neo-swaps/src/math/types/combo_math.rs +++ b/zrml/neo-swaps/src/math/types/combo_math.rs @@ -310,7 +310,7 @@ mod tests { type MockMath = ComboMath; // Example taken from - // https://docs.gnosis.io/conditionaltokens/docs/introduction3/#an-example-with-lmsr + // https://github.com/gnosis/conditional-tokens-docs/blob/e73aa18ab82446049bca61df31fc88efd3cdc5cc/docs/intro3.md?plain=1#L78-L88 #[test_case(vec![_10], vec![_10], _10, 144_269_504_088, 58_496_250_072)] #[test_case(vec![_1], vec![4_586_751_453], _1, _1, 7_353_256_641)] #[test_case(vec![_2], vec![9_173_502_907], _2, _2, 14_706_513_281; "positive ln")] diff --git a/zrml/neo-swaps/src/math/types/math.rs b/zrml/neo-swaps/src/math/types/math.rs index e816da26e..67713d9c9 100644 --- a/zrml/neo-swaps/src/math/types/math.rs +++ b/zrml/neo-swaps/src/math/types/math.rs @@ -354,7 +354,7 @@ mod tests { type MockMath = Math; // Example taken from - // https://docs.gnosis.io/conditionaltokens/docs/introduction3/#an-example-with-lmsr + // https://github.com/gnosis/conditional-tokens-docs/blob/e73aa18ab82446049bca61df31fc88efd3cdc5cc/docs/intro3.md?plain=1#L78-L88 #[test_case(_10, _10, 144_269_504_088, 58_496_250_072)] #[test_case(_1, _1, _1, 7_353_256_641)] #[test_case(_2, _2, _2, 14_706_513_281; "positive ln")] diff --git a/zrml/neo-swaps/src/tests/buy.rs b/zrml/neo-swaps/src/tests/buy.rs index 55107c9e5..3d24becbc 100644 --- a/zrml/neo-swaps/src/tests/buy.rs +++ b/zrml/neo-swaps/src/tests/buy.rs @@ -21,7 +21,7 @@ use sp_runtime::{DispatchError, TokenError}; use test_case::test_case; // Example taken from -// https://docs.gnosis.io/conditionaltokens/docs/introduction3/#an-example-with-lmsr +// https://github.com/gnosis/conditional-tokens-docs/blob/e73aa18ab82446049bca61df31fc88efd3cdc5cc/docs/intro3.md?plain=1#L78-L88 #[test] fn buy_works() { ExtBuilder::default().build().execute_with(|| { diff --git a/zrml/neo-swaps/src/tests/combo_buy.rs b/zrml/neo-swaps/src/tests/combo_buy.rs index 07501c232..0394eb8e4 100644 --- a/zrml/neo-swaps/src/tests/combo_buy.rs +++ b/zrml/neo-swaps/src/tests/combo_buy.rs @@ -19,7 +19,7 @@ use super::*; use test_case::test_case; // Example taken from -// https://docs.gnosis.io/conditionaltokens/docs/introduction3/#an-example-with-lmsr +// https://github.com/gnosis/conditional-tokens-docs/blob/e73aa18ab82446049bca61df31fc88efd3cdc5cc/docs/intro3.md?plain=1#L78-L88 #[test] fn combo_buy_works() { ExtBuilder::default().build().execute_with(|| { From 6863f8481885b92e72a65db114277b91115f0ddb Mon Sep 17 00:00:00 2001 From: Chralt98 Date: Tue, 11 Feb 2025 14:16:52 +0100 Subject: [PATCH 67/73] update gnosis doc reference --- zrml/combinatorial-tokens/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zrml/combinatorial-tokens/README.md b/zrml/combinatorial-tokens/README.md index 2630fc506..43e88d9b0 100644 --- a/zrml/combinatorial-tokens/README.md +++ b/zrml/combinatorial-tokens/README.md @@ -35,4 +35,4 @@ Calculating alt_bn128 combinatorial tokens, as [defined by -Gnosis](https://docs.gnosis.io/conditionaltokens/) in Substrate. +Gnosis](https://gnosis-conditional-tokens.readthedocs.io/en/latest/developer-guide.html#) in Substrate. From 1edf274e94cbe39afa179017d5133a30f856ef7d Mon Sep 17 00:00:00 2001 From: Chralt98 Date: Mon, 24 Feb 2025 12:04:34 +0100 Subject: [PATCH 68/73] update copyrights --- primitives/src/asset.rs | 2 +- primitives/src/constants.rs | 2 +- primitives/src/constants/base_multiples.rs | 2 +- primitives/src/constants/mock.rs | 2 +- primitives/src/math/checked_ops_res.rs | 2 +- primitives/src/traits.rs | 2 +- .../src/traits/combinatorial_tokens_api.rs | 2 +- .../combinatorial_tokens_benchmark_helper.rs | 2 +- .../src/traits/combinatorial_tokens_fuel.rs | 17 +++++++++++++++++ .../traits/combinatorial_tokens_unsafe_api.rs | 2 +- .../src/traits/futarchy_benchmark_helper.rs | 2 +- primitives/src/traits/futarchy_oracle.rs | 2 +- .../src/traits/market_commons_pallet_api.rs | 2 +- primitives/src/traits/payout_api.rs | 2 +- primitives/src/types.rs | 2 +- runtime/battery-station/src/parameters.rs | 2 +- runtime/zeitgeist/src/parameters.rs | 2 +- zrml/combinatorial-tokens/fuzz/common.rs | 2 +- .../combinatorial-tokens/fuzz/merge_position.rs | 2 +- .../fuzz/redeem_position.rs | 2 +- .../combinatorial-tokens/fuzz/split_position.rs | 2 +- zrml/combinatorial-tokens/src/benchmarking.rs | 2 +- zrml/combinatorial-tokens/src/mock/consts.rs | 2 +- .../src/mock/ext_builder.rs | 2 +- zrml/combinatorial-tokens/src/mock/mod.rs | 2 +- zrml/combinatorial-tokens/src/mock/runtime.rs | 2 +- .../src/mock/types/benchmark_helper.rs | 2 +- zrml/combinatorial-tokens/src/mock/types/mod.rs | 2 +- .../src/mock/types/payout.rs | 2 +- .../src/tests/integration.rs | 2 +- .../src/tests/merge_position.rs | 2 +- zrml/combinatorial-tokens/src/tests/mod.rs | 2 +- .../src/tests/redeem_position.rs | 2 +- .../src/tests/split_position.rs | 2 +- zrml/combinatorial-tokens/src/traits/mod.rs | 2 +- .../decompressor/tests/decompress_hash.rs | 2 +- .../decompressor/tests/matching_y_coordinate.rs | 2 +- .../decompressor/tests/mod.rs | 2 +- .../decompressor/tests/pow_magic_number.rs | 2 +- .../cryptographic_id_manager/hash_tuple.rs | 2 +- zrml/combinatorial-tokens/src/types/hash.rs | 2 +- .../src/types/transmutation_type.rs | 2 +- zrml/combinatorial-tokens/src/weights.rs | 2 +- zrml/futarchy/fuzz/submit_proposal.rs | 2 +- zrml/futarchy/src/benchmarking.rs | 2 +- zrml/futarchy/src/dispatchable_impls.rs | 2 +- zrml/futarchy/src/lib.rs | 2 +- zrml/futarchy/src/mock/ext_builder.rs | 2 +- zrml/futarchy/src/mock/mod.rs | 2 +- zrml/futarchy/src/mock/runtime.rs | 2 +- .../futarchy/src/mock/types/benchmark_helper.rs | 2 +- zrml/futarchy/src/mock/types/mod.rs | 2 +- zrml/futarchy/src/mock/types/oracle.rs | 2 +- zrml/futarchy/src/mock/types/scheduler.rs | 2 +- zrml/futarchy/src/mock/utility.rs | 2 +- zrml/futarchy/src/pallet_impls.rs | 2 +- zrml/futarchy/src/proposal_storage.rs | 2 +- zrml/futarchy/src/tests/mod.rs | 2 +- zrml/futarchy/src/tests/submit_proposal.rs | 2 +- zrml/futarchy/src/traits/mod.rs | 2 +- zrml/futarchy/src/traits/proposal_storage.rs | 2 +- zrml/futarchy/src/types/mod.rs | 2 +- zrml/futarchy/src/types/proposal.rs | 2 +- zrml/futarchy/src/weights.rs | 2 +- zrml/hybrid-router/src/lib.rs | 2 +- zrml/hybrid-router/src/mock.rs | 2 +- zrml/hybrid-router/src/tests/buy.rs | 2 +- zrml/hybrid-router/src/tests/sell.rs | 2 +- zrml/neo-swaps/fuzz/combo_buy.rs | 2 +- zrml/neo-swaps/fuzz/combo_sell.rs | 2 +- zrml/neo-swaps/fuzz/common.rs | 2 +- .../neo-swaps/fuzz/deploy_combinatorial_pool.rs | 2 +- zrml/neo-swaps/src/benchmarking.rs | 2 +- zrml/neo-swaps/src/lib.rs | 2 +- zrml/neo-swaps/src/liquidity_tree/tests/mod.rs | 2 +- zrml/neo-swaps/src/liquidity_tree/traits/mod.rs | 2 +- zrml/neo-swaps/src/macros.rs | 2 +- zrml/neo-swaps/src/math/mod.rs | 2 +- .../neo-swaps/src/math/traits/combo_math_ops.rs | 2 +- zrml/neo-swaps/src/math/traits/math_ops.rs | 2 +- zrml/neo-swaps/src/math/traits/mod.rs | 2 +- zrml/neo-swaps/src/math/transcendental.rs | 2 +- zrml/neo-swaps/src/math/types/combo_math.rs | 2 +- zrml/neo-swaps/src/math/types/common.rs | 2 +- zrml/neo-swaps/src/math/types/math.rs | 2 +- zrml/neo-swaps/src/math/types/mod.rs | 2 +- zrml/neo-swaps/src/migration.rs | 2 +- zrml/neo-swaps/src/mock.rs | 2 +- zrml/neo-swaps/src/pool_storage.rs | 2 +- zrml/neo-swaps/src/tests/buy.rs | 2 +- zrml/neo-swaps/src/tests/buy_and_sell.rs | 2 +- zrml/neo-swaps/src/tests/combo_buy.rs | 2 +- zrml/neo-swaps/src/tests/combo_sell.rs | 2 +- .../src/tests/deploy_combinatorial_pool.rs | 2 +- zrml/neo-swaps/src/tests/deploy_pool.rs | 2 +- zrml/neo-swaps/src/tests/exit.rs | 2 +- zrml/neo-swaps/src/tests/join.rs | 2 +- zrml/neo-swaps/src/tests/mod.rs | 2 +- zrml/neo-swaps/src/tests/sell.rs | 2 +- zrml/neo-swaps/src/tests/withdraw_fees.rs | 2 +- zrml/neo-swaps/src/traits/mod.rs | 2 +- zrml/neo-swaps/src/traits/pool_operations.rs | 2 +- zrml/neo-swaps/src/traits/pool_storage.rs | 2 +- .../types/decision_market_benchmark_helper.rs | 2 +- .../src/types/decision_market_oracle.rs | 2 +- .../types/decision_market_oracle_scoreboard.rs | 2 +- zrml/neo-swaps/src/types/mod.rs | 2 +- zrml/neo-swaps/src/types/pool.rs | 2 +- zrml/neo-swaps/src/types/pool_type.rs | 2 +- zrml/neo-swaps/src/utility.rs | 2 +- zrml/neo-swaps/src/weights.rs | 2 +- zrml/prediction-markets/src/lib.rs | 2 +- zrml/prediction-markets/src/tests/mod.rs | 2 +- .../src/tests/payout_vector.rs | 2 +- .../combinatorial_tokens_benchmark_helper.rs | 2 +- zrml/prediction-markets/src/types/mod.rs | 2 +- 116 files changed, 132 insertions(+), 115 deletions(-) diff --git a/primitives/src/asset.rs b/primitives/src/asset.rs index df684894e..f432123c8 100644 --- a/primitives/src/asset.rs +++ b/primitives/src/asset.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2024 Forecasting Technologies LTD. +// Copyright 2022-2025 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. diff --git a/primitives/src/constants.rs b/primitives/src/constants.rs index 7b13887d8..821ab446f 100644 --- a/primitives/src/constants.rs +++ b/primitives/src/constants.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2024 Forecasting Technologies LTD. +// Copyright 2022-2025 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. diff --git a/primitives/src/constants/base_multiples.rs b/primitives/src/constants/base_multiples.rs index b7827c8c6..f8e245ab1 100644 --- a/primitives/src/constants/base_multiples.rs +++ b/primitives/src/constants/base_multiples.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/primitives/src/constants/mock.rs b/primitives/src/constants/mock.rs index d33f5386e..e16de65d1 100644 --- a/primitives/src/constants/mock.rs +++ b/primitives/src/constants/mock.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2024 Forecasting Technologies LTD. +// Copyright 2022-2025 Forecasting Technologies LTD. // Copyright 2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. diff --git a/primitives/src/math/checked_ops_res.rs b/primitives/src/math/checked_ops_res.rs index 38e971031..66f0a3c67 100644 --- a/primitives/src/math/checked_ops_res.rs +++ b/primitives/src/math/checked_ops_res.rs @@ -1,4 +1,4 @@ -// Copyright 2023-2024 Forecasting Technologies LTD. +// Copyright 2023-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/primitives/src/traits.rs b/primitives/src/traits.rs index d141610f1..f39fc9dee 100644 --- a/primitives/src/traits.rs +++ b/primitives/src/traits.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2024 Forecasting Technologies LTD. +// Copyright 2022-2025 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. diff --git a/primitives/src/traits/combinatorial_tokens_api.rs b/primitives/src/traits/combinatorial_tokens_api.rs index 48a0e1a45..3024ae33b 100644 --- a/primitives/src/traits/combinatorial_tokens_api.rs +++ b/primitives/src/traits/combinatorial_tokens_api.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/primitives/src/traits/combinatorial_tokens_benchmark_helper.rs b/primitives/src/traits/combinatorial_tokens_benchmark_helper.rs index b1c3b3212..23906395d 100644 --- a/primitives/src/traits/combinatorial_tokens_benchmark_helper.rs +++ b/primitives/src/traits/combinatorial_tokens_benchmark_helper.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/primitives/src/traits/combinatorial_tokens_fuel.rs b/primitives/src/traits/combinatorial_tokens_fuel.rs index 8fc952a23..4d939f0af 100644 --- a/primitives/src/traits/combinatorial_tokens_fuel.rs +++ b/primitives/src/traits/combinatorial_tokens_fuel.rs @@ -1,3 +1,20 @@ +// Copyright 2024-2025 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + /// A trait for keeping track of a certain amount of work to be done. pub trait CombinatorialTokensFuel { /// Creates a `Fuel` object from a `total` value which indicates the total amount of work to be diff --git a/primitives/src/traits/combinatorial_tokens_unsafe_api.rs b/primitives/src/traits/combinatorial_tokens_unsafe_api.rs index 317dca26b..9893ddd18 100644 --- a/primitives/src/traits/combinatorial_tokens_unsafe_api.rs +++ b/primitives/src/traits/combinatorial_tokens_unsafe_api.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/primitives/src/traits/futarchy_benchmark_helper.rs b/primitives/src/traits/futarchy_benchmark_helper.rs index 625a7f5a9..e439253a0 100644 --- a/primitives/src/traits/futarchy_benchmark_helper.rs +++ b/primitives/src/traits/futarchy_benchmark_helper.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/primitives/src/traits/futarchy_oracle.rs b/primitives/src/traits/futarchy_oracle.rs index 37b5b3b17..9309cea6b 100644 --- a/primitives/src/traits/futarchy_oracle.rs +++ b/primitives/src/traits/futarchy_oracle.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/primitives/src/traits/market_commons_pallet_api.rs b/primitives/src/traits/market_commons_pallet_api.rs index 38c9ccb0f..e55b3c825 100644 --- a/primitives/src/traits/market_commons_pallet_api.rs +++ b/primitives/src/traits/market_commons_pallet_api.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2024 Forecasting Technologies LTD. +// Copyright 2022-2025 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. diff --git a/primitives/src/traits/payout_api.rs b/primitives/src/traits/payout_api.rs index 675ef411f..4f03a84da 100644 --- a/primitives/src/traits/payout_api.rs +++ b/primitives/src/traits/payout_api.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/primitives/src/types.rs b/primitives/src/types.rs index d354399a5..c30aef1c4 100644 --- a/primitives/src/types.rs +++ b/primitives/src/types.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2024 Forecasting Technologies LTD. +// Copyright 2022-2025 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. diff --git a/runtime/battery-station/src/parameters.rs b/runtime/battery-station/src/parameters.rs index faf7cba39..e3d02f7bb 100644 --- a/runtime/battery-station/src/parameters.rs +++ b/runtime/battery-station/src/parameters.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2024 Forecasting Technologies LTD. +// Copyright 2022-2025 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. diff --git a/runtime/zeitgeist/src/parameters.rs b/runtime/zeitgeist/src/parameters.rs index 9f5736573..adc3ded53 100644 --- a/runtime/zeitgeist/src/parameters.rs +++ b/runtime/zeitgeist/src/parameters.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2024 Forecasting Technologies LTD. +// Copyright 2022-2025 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. diff --git a/zrml/combinatorial-tokens/fuzz/common.rs b/zrml/combinatorial-tokens/fuzz/common.rs index b4a0e297d..13f853b86 100644 --- a/zrml/combinatorial-tokens/fuzz/common.rs +++ b/zrml/combinatorial-tokens/fuzz/common.rs @@ -1,4 +1,4 @@ -use zeitgeist_primitives::{ +// Copyright 2025 Forecasting Technologies LTD. traits::MarketOf, types::{Market, MarketCreation, MarketPeriod, MarketStatus, MarketType, ScoringRule}, }; diff --git a/zrml/combinatorial-tokens/fuzz/merge_position.rs b/zrml/combinatorial-tokens/fuzz/merge_position.rs index 2a0a32f44..89c18b2bd 100644 --- a/zrml/combinatorial-tokens/fuzz/merge_position.rs +++ b/zrml/combinatorial-tokens/fuzz/merge_position.rs @@ -1,4 +1,4 @@ -#![no_main] +// Copyright 2025 Forecasting Technologies LTD. mod common; diff --git a/zrml/combinatorial-tokens/fuzz/redeem_position.rs b/zrml/combinatorial-tokens/fuzz/redeem_position.rs index 566202b10..f7fef9a06 100644 --- a/zrml/combinatorial-tokens/fuzz/redeem_position.rs +++ b/zrml/combinatorial-tokens/fuzz/redeem_position.rs @@ -1,4 +1,4 @@ -#![no_main] +// Copyright 2025 Forecasting Technologies LTD. mod common; diff --git a/zrml/combinatorial-tokens/fuzz/split_position.rs b/zrml/combinatorial-tokens/fuzz/split_position.rs index 4f733cd8f..180817793 100644 --- a/zrml/combinatorial-tokens/fuzz/split_position.rs +++ b/zrml/combinatorial-tokens/fuzz/split_position.rs @@ -1,4 +1,4 @@ -#![no_main] +// Copyright 2025 Forecasting Technologies LTD. mod common; diff --git a/zrml/combinatorial-tokens/src/benchmarking.rs b/zrml/combinatorial-tokens/src/benchmarking.rs index 4d2bf4093..68fe22a97 100644 --- a/zrml/combinatorial-tokens/src/benchmarking.rs +++ b/zrml/combinatorial-tokens/src/benchmarking.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/combinatorial-tokens/src/mock/consts.rs b/zrml/combinatorial-tokens/src/mock/consts.rs index d614e0775..58eb0a3d1 100644 --- a/zrml/combinatorial-tokens/src/mock/consts.rs +++ b/zrml/combinatorial-tokens/src/mock/consts.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/combinatorial-tokens/src/mock/ext_builder.rs b/zrml/combinatorial-tokens/src/mock/ext_builder.rs index ddd2d2e10..d9340607b 100644 --- a/zrml/combinatorial-tokens/src/mock/ext_builder.rs +++ b/zrml/combinatorial-tokens/src/mock/ext_builder.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/combinatorial-tokens/src/mock/mod.rs b/zrml/combinatorial-tokens/src/mock/mod.rs index b29cf33f0..5c7e91fc5 100644 --- a/zrml/combinatorial-tokens/src/mock/mod.rs +++ b/zrml/combinatorial-tokens/src/mock/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/combinatorial-tokens/src/mock/runtime.rs b/zrml/combinatorial-tokens/src/mock/runtime.rs index ff39e3832..518c3e5d9 100644 --- a/zrml/combinatorial-tokens/src/mock/runtime.rs +++ b/zrml/combinatorial-tokens/src/mock/runtime.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/combinatorial-tokens/src/mock/types/benchmark_helper.rs b/zrml/combinatorial-tokens/src/mock/types/benchmark_helper.rs index ea3f3309d..0a42c3a0b 100644 --- a/zrml/combinatorial-tokens/src/mock/types/benchmark_helper.rs +++ b/zrml/combinatorial-tokens/src/mock/types/benchmark_helper.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/combinatorial-tokens/src/mock/types/mod.rs b/zrml/combinatorial-tokens/src/mock/types/mod.rs index 0eb340eff..40663b2a0 100644 --- a/zrml/combinatorial-tokens/src/mock/types/mod.rs +++ b/zrml/combinatorial-tokens/src/mock/types/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/combinatorial-tokens/src/mock/types/payout.rs b/zrml/combinatorial-tokens/src/mock/types/payout.rs index f3fbd8d2a..447bb445d 100644 --- a/zrml/combinatorial-tokens/src/mock/types/payout.rs +++ b/zrml/combinatorial-tokens/src/mock/types/payout.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/combinatorial-tokens/src/tests/integration.rs b/zrml/combinatorial-tokens/src/tests/integration.rs index 7b6b5880a..0b7012c60 100644 --- a/zrml/combinatorial-tokens/src/tests/integration.rs +++ b/zrml/combinatorial-tokens/src/tests/integration.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/combinatorial-tokens/src/tests/merge_position.rs b/zrml/combinatorial-tokens/src/tests/merge_position.rs index f3a4d1d15..d124776e8 100644 --- a/zrml/combinatorial-tokens/src/tests/merge_position.rs +++ b/zrml/combinatorial-tokens/src/tests/merge_position.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/combinatorial-tokens/src/tests/mod.rs b/zrml/combinatorial-tokens/src/tests/mod.rs index 8b1fb8469..2e3ef1b01 100644 --- a/zrml/combinatorial-tokens/src/tests/mod.rs +++ b/zrml/combinatorial-tokens/src/tests/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/combinatorial-tokens/src/tests/redeem_position.rs b/zrml/combinatorial-tokens/src/tests/redeem_position.rs index 359629db4..2f191b999 100644 --- a/zrml/combinatorial-tokens/src/tests/redeem_position.rs +++ b/zrml/combinatorial-tokens/src/tests/redeem_position.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/combinatorial-tokens/src/tests/split_position.rs b/zrml/combinatorial-tokens/src/tests/split_position.rs index 57e7b4f7a..331acda6e 100644 --- a/zrml/combinatorial-tokens/src/tests/split_position.rs +++ b/zrml/combinatorial-tokens/src/tests/split_position.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/combinatorial-tokens/src/traits/mod.rs b/zrml/combinatorial-tokens/src/traits/mod.rs index 5ef0075ef..2b9aa08a3 100644 --- a/zrml/combinatorial-tokens/src/traits/mod.rs +++ b/zrml/combinatorial-tokens/src/traits/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/decompress_hash.rs b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/decompress_hash.rs index f55b5e15a..ed5c4220f 100644 --- a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/decompress_hash.rs +++ b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/decompress_hash.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/matching_y_coordinate.rs b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/matching_y_coordinate.rs index 93c2a46a5..3176126d0 100644 --- a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/matching_y_coordinate.rs +++ b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/matching_y_coordinate.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/mod.rs b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/mod.rs index 242d06093..efb7710b7 100644 --- a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/mod.rs +++ b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/pow_magic_number.rs b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/pow_magic_number.rs index f09adce59..65ba7b609 100644 --- a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/pow_magic_number.rs +++ b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/tests/pow_magic_number.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/hash_tuple.rs b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/hash_tuple.rs index 15ae10f3d..b86f93c68 100644 --- a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/hash_tuple.rs +++ b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/hash_tuple.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/combinatorial-tokens/src/types/hash.rs b/zrml/combinatorial-tokens/src/types/hash.rs index 115239fc5..396e2722a 100644 --- a/zrml/combinatorial-tokens/src/types/hash.rs +++ b/zrml/combinatorial-tokens/src/types/hash.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/combinatorial-tokens/src/types/transmutation_type.rs b/zrml/combinatorial-tokens/src/types/transmutation_type.rs index 2be4c2f5a..a0aa0ae89 100644 --- a/zrml/combinatorial-tokens/src/types/transmutation_type.rs +++ b/zrml/combinatorial-tokens/src/types/transmutation_type.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/combinatorial-tokens/src/weights.rs b/zrml/combinatorial-tokens/src/weights.rs index f1991c6df..0196556e9 100644 --- a/zrml/combinatorial-tokens/src/weights.rs +++ b/zrml/combinatorial-tokens/src/weights.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2024 Forecasting Technologies LTD. +// Copyright 2022-2025 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. diff --git a/zrml/futarchy/fuzz/submit_proposal.rs b/zrml/futarchy/fuzz/submit_proposal.rs index 22a0c0bbd..f12d5fb90 100644 --- a/zrml/futarchy/fuzz/submit_proposal.rs +++ b/zrml/futarchy/fuzz/submit_proposal.rs @@ -1,4 +1,4 @@ -#![no_main] +// Copyright 2025 Forecasting Technologies LTD. use arbitrary::{Arbitrary, Result as ArbitraryResult, Unstructured}; use frame_system::pallet_prelude::{BlockNumberFor, OriginFor}; diff --git a/zrml/futarchy/src/benchmarking.rs b/zrml/futarchy/src/benchmarking.rs index 53b0039d0..b33aceeda 100644 --- a/zrml/futarchy/src/benchmarking.rs +++ b/zrml/futarchy/src/benchmarking.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/futarchy/src/dispatchable_impls.rs b/zrml/futarchy/src/dispatchable_impls.rs index 0fd409e11..13ff4574d 100644 --- a/zrml/futarchy/src/dispatchable_impls.rs +++ b/zrml/futarchy/src/dispatchable_impls.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/futarchy/src/lib.rs b/zrml/futarchy/src/lib.rs index 7d03b0063..8e3eb40e1 100644 --- a/zrml/futarchy/src/lib.rs +++ b/zrml/futarchy/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/futarchy/src/mock/ext_builder.rs b/zrml/futarchy/src/mock/ext_builder.rs index 333b8dcb1..fd3825657 100644 --- a/zrml/futarchy/src/mock/ext_builder.rs +++ b/zrml/futarchy/src/mock/ext_builder.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/futarchy/src/mock/mod.rs b/zrml/futarchy/src/mock/mod.rs index 6c3be09e1..698dc06e8 100644 --- a/zrml/futarchy/src/mock/mod.rs +++ b/zrml/futarchy/src/mock/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/futarchy/src/mock/runtime.rs b/zrml/futarchy/src/mock/runtime.rs index 0fc2e69cf..6027963f6 100644 --- a/zrml/futarchy/src/mock/runtime.rs +++ b/zrml/futarchy/src/mock/runtime.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/futarchy/src/mock/types/benchmark_helper.rs b/zrml/futarchy/src/mock/types/benchmark_helper.rs index 5ba2c2238..ae5dd53e3 100644 --- a/zrml/futarchy/src/mock/types/benchmark_helper.rs +++ b/zrml/futarchy/src/mock/types/benchmark_helper.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/futarchy/src/mock/types/mod.rs b/zrml/futarchy/src/mock/types/mod.rs index 104e8b8ef..585eba939 100644 --- a/zrml/futarchy/src/mock/types/mod.rs +++ b/zrml/futarchy/src/mock/types/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/futarchy/src/mock/types/oracle.rs b/zrml/futarchy/src/mock/types/oracle.rs index 0d379ddc1..045c46de3 100644 --- a/zrml/futarchy/src/mock/types/oracle.rs +++ b/zrml/futarchy/src/mock/types/oracle.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/futarchy/src/mock/types/scheduler.rs b/zrml/futarchy/src/mock/types/scheduler.rs index 9d3dc92ab..499d2678d 100644 --- a/zrml/futarchy/src/mock/types/scheduler.rs +++ b/zrml/futarchy/src/mock/types/scheduler.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/futarchy/src/mock/utility.rs b/zrml/futarchy/src/mock/utility.rs index 75f501821..fe2d8a16c 100644 --- a/zrml/futarchy/src/mock/utility.rs +++ b/zrml/futarchy/src/mock/utility.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/futarchy/src/pallet_impls.rs b/zrml/futarchy/src/pallet_impls.rs index 601eb27c7..c53dbfd3d 100644 --- a/zrml/futarchy/src/pallet_impls.rs +++ b/zrml/futarchy/src/pallet_impls.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/futarchy/src/proposal_storage.rs b/zrml/futarchy/src/proposal_storage.rs index 6a0fc0a10..39c34d301 100644 --- a/zrml/futarchy/src/proposal_storage.rs +++ b/zrml/futarchy/src/proposal_storage.rs @@ -1,4 +1,4 @@ -use crate::{ +// Copyright 2025 Forecasting Technologies LTD. traits::ProposalStorage, types::Proposal, Config, Error, Pallet, ProposalCount, Proposals, ProposalsOf, }; diff --git a/zrml/futarchy/src/tests/mod.rs b/zrml/futarchy/src/tests/mod.rs index e3bc8d409..ea615c72b 100644 --- a/zrml/futarchy/src/tests/mod.rs +++ b/zrml/futarchy/src/tests/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/futarchy/src/tests/submit_proposal.rs b/zrml/futarchy/src/tests/submit_proposal.rs index df0ece080..7f687902c 100644 --- a/zrml/futarchy/src/tests/submit_proposal.rs +++ b/zrml/futarchy/src/tests/submit_proposal.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/futarchy/src/traits/mod.rs b/zrml/futarchy/src/traits/mod.rs index d995c8264..f092719ce 100644 --- a/zrml/futarchy/src/traits/mod.rs +++ b/zrml/futarchy/src/traits/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/futarchy/src/traits/proposal_storage.rs b/zrml/futarchy/src/traits/proposal_storage.rs index f26220bc9..770c3953b 100644 --- a/zrml/futarchy/src/traits/proposal_storage.rs +++ b/zrml/futarchy/src/traits/proposal_storage.rs @@ -1,4 +1,4 @@ -use crate::{types::Proposal, Config, ProposalsOf}; +// Copyright 2025 Forecasting Technologies LTD. use alloc::{collections::BTreeMap, vec::Vec}; use frame_system::pallet_prelude::BlockNumberFor; use sp_runtime::DispatchError; diff --git a/zrml/futarchy/src/types/mod.rs b/zrml/futarchy/src/types/mod.rs index 0bce47392..db9310023 100644 --- a/zrml/futarchy/src/types/mod.rs +++ b/zrml/futarchy/src/types/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/futarchy/src/types/proposal.rs b/zrml/futarchy/src/types/proposal.rs index 78f8c19d1..722b058b9 100644 --- a/zrml/futarchy/src/types/proposal.rs +++ b/zrml/futarchy/src/types/proposal.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/futarchy/src/weights.rs b/zrml/futarchy/src/weights.rs index 0235ca85e..5a944e28c 100644 --- a/zrml/futarchy/src/weights.rs +++ b/zrml/futarchy/src/weights.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2024 Forecasting Technologies LTD. +// Copyright 2022-2025 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. diff --git a/zrml/hybrid-router/src/lib.rs b/zrml/hybrid-router/src/lib.rs index 49f4145cc..4dcd61b02 100644 --- a/zrml/hybrid-router/src/lib.rs +++ b/zrml/hybrid-router/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/hybrid-router/src/mock.rs b/zrml/hybrid-router/src/mock.rs index 36f4dbd9b..3b3460849 100644 --- a/zrml/hybrid-router/src/mock.rs +++ b/zrml/hybrid-router/src/mock.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/hybrid-router/src/tests/buy.rs b/zrml/hybrid-router/src/tests/buy.rs index d642110f3..8b8bade2a 100644 --- a/zrml/hybrid-router/src/tests/buy.rs +++ b/zrml/hybrid-router/src/tests/buy.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/hybrid-router/src/tests/sell.rs b/zrml/hybrid-router/src/tests/sell.rs index 260a10dd7..3034e657f 100644 --- a/zrml/hybrid-router/src/tests/sell.rs +++ b/zrml/hybrid-router/src/tests/sell.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/neo-swaps/fuzz/combo_buy.rs b/zrml/neo-swaps/fuzz/combo_buy.rs index f1212bef5..8531ab3e9 100644 --- a/zrml/neo-swaps/fuzz/combo_buy.rs +++ b/zrml/neo-swaps/fuzz/combo_buy.rs @@ -1,4 +1,4 @@ -#![no_main] +// Copyright 2025 Forecasting Technologies LTD. mod common; diff --git a/zrml/neo-swaps/fuzz/combo_sell.rs b/zrml/neo-swaps/fuzz/combo_sell.rs index 69e1a16ab..e3a2f7eb4 100644 --- a/zrml/neo-swaps/fuzz/combo_sell.rs +++ b/zrml/neo-swaps/fuzz/combo_sell.rs @@ -1,4 +1,4 @@ -#![no_main] +// Copyright 2025 Forecasting Technologies LTD. mod common; diff --git a/zrml/neo-swaps/fuzz/common.rs b/zrml/neo-swaps/fuzz/common.rs index 583dc430c..348572b17 100644 --- a/zrml/neo-swaps/fuzz/common.rs +++ b/zrml/neo-swaps/fuzz/common.rs @@ -1,4 +1,4 @@ -use zeitgeist_primitives::{ +// Copyright 2025 Forecasting Technologies LTD. traits::MarketOf, types::{Market, MarketCreation, MarketPeriod, MarketStatus, MarketType, ScoringRule}, }; diff --git a/zrml/neo-swaps/fuzz/deploy_combinatorial_pool.rs b/zrml/neo-swaps/fuzz/deploy_combinatorial_pool.rs index 355ce40ac..2c89ccab1 100644 --- a/zrml/neo-swaps/fuzz/deploy_combinatorial_pool.rs +++ b/zrml/neo-swaps/fuzz/deploy_combinatorial_pool.rs @@ -1,4 +1,4 @@ -#![no_main] +// Copyright 2025 Forecasting Technologies LTD. mod common; diff --git a/zrml/neo-swaps/src/benchmarking.rs b/zrml/neo-swaps/src/benchmarking.rs index e7001bb2b..35a5b5e6d 100644 --- a/zrml/neo-swaps/src/benchmarking.rs +++ b/zrml/neo-swaps/src/benchmarking.rs @@ -1,4 +1,4 @@ -// Copyright 2023-2024 Forecasting Technologies LTD. +// Copyright 2023-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/neo-swaps/src/lib.rs b/zrml/neo-swaps/src/lib.rs index ee8d28fdb..b86d24d21 100644 --- a/zrml/neo-swaps/src/lib.rs +++ b/zrml/neo-swaps/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2023-2024 Forecasting Technologies LTD. +// Copyright 2023-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/neo-swaps/src/liquidity_tree/tests/mod.rs b/zrml/neo-swaps/src/liquidity_tree/tests/mod.rs index 1839f12e5..bb4e287bc 100644 --- a/zrml/neo-swaps/src/liquidity_tree/tests/mod.rs +++ b/zrml/neo-swaps/src/liquidity_tree/tests/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2023-2024 Forecasting Technologies LTD. +// Copyright 2023-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/neo-swaps/src/liquidity_tree/traits/mod.rs b/zrml/neo-swaps/src/liquidity_tree/traits/mod.rs index 76780562c..78fc4b648 100644 --- a/zrml/neo-swaps/src/liquidity_tree/traits/mod.rs +++ b/zrml/neo-swaps/src/liquidity_tree/traits/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2023-2024 Forecasting Technologies LTD. +// Copyright 2023-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/neo-swaps/src/macros.rs b/zrml/neo-swaps/src/macros.rs index 8dbe2f10a..3e5e63109 100644 --- a/zrml/neo-swaps/src/macros.rs +++ b/zrml/neo-swaps/src/macros.rs @@ -1,4 +1,4 @@ -// Copyright 2023-2024 Forecasting Technologies LTD. +// Copyright 2023-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/neo-swaps/src/math/mod.rs b/zrml/neo-swaps/src/math/mod.rs index 0c9c3a0cc..f0f7bc594 100644 --- a/zrml/neo-swaps/src/math/mod.rs +++ b/zrml/neo-swaps/src/math/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2023-2024 Forecasting Technologies LTD. +// Copyright 2023-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/neo-swaps/src/math/traits/combo_math_ops.rs b/zrml/neo-swaps/src/math/traits/combo_math_ops.rs index 856c96202..5c0f790c3 100644 --- a/zrml/neo-swaps/src/math/traits/combo_math_ops.rs +++ b/zrml/neo-swaps/src/math/traits/combo_math_ops.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/neo-swaps/src/math/traits/math_ops.rs b/zrml/neo-swaps/src/math/traits/math_ops.rs index a11922468..904f07a5c 100644 --- a/zrml/neo-swaps/src/math/traits/math_ops.rs +++ b/zrml/neo-swaps/src/math/traits/math_ops.rs @@ -1,4 +1,4 @@ -// Copyright 2023-2024 Forecasting Technologies LTD. +// Copyright 2023-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/neo-swaps/src/math/traits/mod.rs b/zrml/neo-swaps/src/math/traits/mod.rs index b8a86228d..df48ce479 100644 --- a/zrml/neo-swaps/src/math/traits/mod.rs +++ b/zrml/neo-swaps/src/math/traits/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/neo-swaps/src/math/transcendental.rs b/zrml/neo-swaps/src/math/transcendental.rs index 1d3d97122..6768083ab 100644 --- a/zrml/neo-swaps/src/math/transcendental.rs +++ b/zrml/neo-swaps/src/math/transcendental.rs @@ -1,4 +1,4 @@ -// Copyright 2023-2024 Forecasting Technologies LTD. +// Copyright 2023-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/neo-swaps/src/math/types/combo_math.rs b/zrml/neo-swaps/src/math/types/combo_math.rs index ef3a8206d..bb553d211 100644 --- a/zrml/neo-swaps/src/math/types/combo_math.rs +++ b/zrml/neo-swaps/src/math/types/combo_math.rs @@ -1,4 +1,4 @@ -// Copyright 2023-2024 Forecasting Technologies LTD. +// Copyright 2023-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/neo-swaps/src/math/types/common.rs b/zrml/neo-swaps/src/math/types/common.rs index e336aea75..0f632d196 100644 --- a/zrml/neo-swaps/src/math/types/common.rs +++ b/zrml/neo-swaps/src/math/types/common.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/neo-swaps/src/math/types/math.rs b/zrml/neo-swaps/src/math/types/math.rs index 67713d9c9..6660ed0b3 100644 --- a/zrml/neo-swaps/src/math/types/math.rs +++ b/zrml/neo-swaps/src/math/types/math.rs @@ -1,4 +1,4 @@ -// Copyright 2023-2024 Forecasting Technologies LTD. +// Copyright 2023-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/neo-swaps/src/math/types/mod.rs b/zrml/neo-swaps/src/math/types/mod.rs index 4c8a92ba6..8de08770f 100644 --- a/zrml/neo-swaps/src/math/types/mod.rs +++ b/zrml/neo-swaps/src/math/types/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/neo-swaps/src/migration.rs b/zrml/neo-swaps/src/migration.rs index 3ef4559d5..b2d9857a7 100644 --- a/zrml/neo-swaps/src/migration.rs +++ b/zrml/neo-swaps/src/migration.rs @@ -1,4 +1,4 @@ -// Copyright 2023-2024 Forecasting Technologies LTD. +// Copyright 2023-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/neo-swaps/src/mock.rs b/zrml/neo-swaps/src/mock.rs index 75add7a3a..cac90375f 100644 --- a/zrml/neo-swaps/src/mock.rs +++ b/zrml/neo-swaps/src/mock.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2024 Forecasting Technologies LTD. +// Copyright 2022-2025 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. diff --git a/zrml/neo-swaps/src/pool_storage.rs b/zrml/neo-swaps/src/pool_storage.rs index bccf35e74..843ffd565 100644 --- a/zrml/neo-swaps/src/pool_storage.rs +++ b/zrml/neo-swaps/src/pool_storage.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/neo-swaps/src/tests/buy.rs b/zrml/neo-swaps/src/tests/buy.rs index 3d24becbc..ac3bde7c8 100644 --- a/zrml/neo-swaps/src/tests/buy.rs +++ b/zrml/neo-swaps/src/tests/buy.rs @@ -1,4 +1,4 @@ -// Copyright 2023-2024 Forecasting Technologies LTD. +// Copyright 2023-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/neo-swaps/src/tests/buy_and_sell.rs b/zrml/neo-swaps/src/tests/buy_and_sell.rs index 8b596e725..710315c1c 100644 --- a/zrml/neo-swaps/src/tests/buy_and_sell.rs +++ b/zrml/neo-swaps/src/tests/buy_and_sell.rs @@ -1,4 +1,4 @@ -// Copyright 2023-2024 Forecasting Technologies LTD. +// Copyright 2023-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/neo-swaps/src/tests/combo_buy.rs b/zrml/neo-swaps/src/tests/combo_buy.rs index 0394eb8e4..ad8de0630 100644 --- a/zrml/neo-swaps/src/tests/combo_buy.rs +++ b/zrml/neo-swaps/src/tests/combo_buy.rs @@ -1,4 +1,4 @@ -// Copyright 2023-2024 Forecasting Technologies LTD. +// Copyright 2023-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/neo-swaps/src/tests/combo_sell.rs b/zrml/neo-swaps/src/tests/combo_sell.rs index 8bb6cb5e8..67f261af4 100644 --- a/zrml/neo-swaps/src/tests/combo_sell.rs +++ b/zrml/neo-swaps/src/tests/combo_sell.rs @@ -1,4 +1,4 @@ -// Copyright 2023-2024 Forecasting Technologies LTD. +// Copyright 2023-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/neo-swaps/src/tests/deploy_combinatorial_pool.rs b/zrml/neo-swaps/src/tests/deploy_combinatorial_pool.rs index b936a9292..dc204abb9 100644 --- a/zrml/neo-swaps/src/tests/deploy_combinatorial_pool.rs +++ b/zrml/neo-swaps/src/tests/deploy_combinatorial_pool.rs @@ -1,4 +1,4 @@ -// Copyright 2023-2024 Forecasting Technologies LTD. +// Copyright 2023-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/neo-swaps/src/tests/deploy_pool.rs b/zrml/neo-swaps/src/tests/deploy_pool.rs index c35d82c28..cc78873a6 100644 --- a/zrml/neo-swaps/src/tests/deploy_pool.rs +++ b/zrml/neo-swaps/src/tests/deploy_pool.rs @@ -1,4 +1,4 @@ -// Copyright 2023-2024 Forecasting Technologies LTD. +// Copyright 2023-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/neo-swaps/src/tests/exit.rs b/zrml/neo-swaps/src/tests/exit.rs index 48060d1e0..87a309f23 100644 --- a/zrml/neo-swaps/src/tests/exit.rs +++ b/zrml/neo-swaps/src/tests/exit.rs @@ -1,4 +1,4 @@ -// Copyright 2023-2024 Forecasting Technologies LTD. +// Copyright 2023-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/neo-swaps/src/tests/join.rs b/zrml/neo-swaps/src/tests/join.rs index f5a3994e6..325434b35 100644 --- a/zrml/neo-swaps/src/tests/join.rs +++ b/zrml/neo-swaps/src/tests/join.rs @@ -1,4 +1,4 @@ -// Copyright 2023-2024 Forecasting Technologies LTD. +// Copyright 2023-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/neo-swaps/src/tests/mod.rs b/zrml/neo-swaps/src/tests/mod.rs index 56493b141..3b79b8548 100644 --- a/zrml/neo-swaps/src/tests/mod.rs +++ b/zrml/neo-swaps/src/tests/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2023-2024 Forecasting Technologies LTD. +// Copyright 2023-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/neo-swaps/src/tests/sell.rs b/zrml/neo-swaps/src/tests/sell.rs index 559164d4b..d6aa370e2 100644 --- a/zrml/neo-swaps/src/tests/sell.rs +++ b/zrml/neo-swaps/src/tests/sell.rs @@ -1,4 +1,4 @@ -// Copyright 2023-2024 Forecasting Technologies LTD. +// Copyright 2023-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/neo-swaps/src/tests/withdraw_fees.rs b/zrml/neo-swaps/src/tests/withdraw_fees.rs index 1951d49a1..768b0f20c 100644 --- a/zrml/neo-swaps/src/tests/withdraw_fees.rs +++ b/zrml/neo-swaps/src/tests/withdraw_fees.rs @@ -1,4 +1,4 @@ -// Copyright 2023-2024 Forecasting Technologies LTD. +// Copyright 2023-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/neo-swaps/src/traits/mod.rs b/zrml/neo-swaps/src/traits/mod.rs index b0a8e57da..f28df2a92 100644 --- a/zrml/neo-swaps/src/traits/mod.rs +++ b/zrml/neo-swaps/src/traits/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2023-2024 Forecasting Technologies LTD. +// Copyright 2023-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/neo-swaps/src/traits/pool_operations.rs b/zrml/neo-swaps/src/traits/pool_operations.rs index 5b54bca1e..b6126fcee 100644 --- a/zrml/neo-swaps/src/traits/pool_operations.rs +++ b/zrml/neo-swaps/src/traits/pool_operations.rs @@ -1,4 +1,4 @@ -// Copyright 2023-2024 Forecasting Technologies LTD. +// Copyright 2023-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/neo-swaps/src/traits/pool_storage.rs b/zrml/neo-swaps/src/traits/pool_storage.rs index 6553a39d7..a5b73275f 100644 --- a/zrml/neo-swaps/src/traits/pool_storage.rs +++ b/zrml/neo-swaps/src/traits/pool_storage.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/neo-swaps/src/types/decision_market_benchmark_helper.rs b/zrml/neo-swaps/src/types/decision_market_benchmark_helper.rs index a3841345a..a57857422 100644 --- a/zrml/neo-swaps/src/types/decision_market_benchmark_helper.rs +++ b/zrml/neo-swaps/src/types/decision_market_benchmark_helper.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/neo-swaps/src/types/decision_market_oracle.rs b/zrml/neo-swaps/src/types/decision_market_oracle.rs index 3347fae0f..f2f105e7f 100644 --- a/zrml/neo-swaps/src/types/decision_market_oracle.rs +++ b/zrml/neo-swaps/src/types/decision_market_oracle.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/neo-swaps/src/types/decision_market_oracle_scoreboard.rs b/zrml/neo-swaps/src/types/decision_market_oracle_scoreboard.rs index 1f718a35c..2498b51af 100644 --- a/zrml/neo-swaps/src/types/decision_market_oracle_scoreboard.rs +++ b/zrml/neo-swaps/src/types/decision_market_oracle_scoreboard.rs @@ -1,4 +1,4 @@ -use crate::{BalanceOf, Config}; +// Copyright 2025 Forecasting Technologies LTD. use frame_system::pallet_prelude::BlockNumberFor; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; diff --git a/zrml/neo-swaps/src/types/mod.rs b/zrml/neo-swaps/src/types/mod.rs index 5d7723ede..7d77f9e62 100644 --- a/zrml/neo-swaps/src/types/mod.rs +++ b/zrml/neo-swaps/src/types/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2023-2024 Forecasting Technologies LTD. +// Copyright 2023-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/neo-swaps/src/types/pool.rs b/zrml/neo-swaps/src/types/pool.rs index bff6b77f2..a174c77c0 100644 --- a/zrml/neo-swaps/src/types/pool.rs +++ b/zrml/neo-swaps/src/types/pool.rs @@ -1,4 +1,4 @@ -// Copyright 2023-2024 Forecasting Technologies LTD. +// Copyright 2023-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/neo-swaps/src/types/pool_type.rs b/zrml/neo-swaps/src/types/pool_type.rs index e29c870ea..65c652b1f 100644 --- a/zrml/neo-swaps/src/types/pool_type.rs +++ b/zrml/neo-swaps/src/types/pool_type.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/neo-swaps/src/utility.rs b/zrml/neo-swaps/src/utility.rs index 58a0e8792..5e16e0b48 100644 --- a/zrml/neo-swaps/src/utility.rs +++ b/zrml/neo-swaps/src/utility.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/neo-swaps/src/weights.rs b/zrml/neo-swaps/src/weights.rs index aabda3571..7df6e6c38 100644 --- a/zrml/neo-swaps/src/weights.rs +++ b/zrml/neo-swaps/src/weights.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2024 Forecasting Technologies LTD. +// Copyright 2022-2025 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. diff --git a/zrml/prediction-markets/src/lib.rs b/zrml/prediction-markets/src/lib.rs index 5623cbd04..e677aed77 100644 --- a/zrml/prediction-markets/src/lib.rs +++ b/zrml/prediction-markets/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2024 Forecasting Technologies LTD. +// Copyright 2022-2025 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. diff --git a/zrml/prediction-markets/src/tests/mod.rs b/zrml/prediction-markets/src/tests/mod.rs index ee0c93570..05bb82cc6 100644 --- a/zrml/prediction-markets/src/tests/mod.rs +++ b/zrml/prediction-markets/src/tests/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2024 Forecasting Technologies LTD. +// Copyright 2022-2025 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. diff --git a/zrml/prediction-markets/src/tests/payout_vector.rs b/zrml/prediction-markets/src/tests/payout_vector.rs index 94a4d0ddb..a7e57f3b6 100644 --- a/zrml/prediction-markets/src/tests/payout_vector.rs +++ b/zrml/prediction-markets/src/tests/payout_vector.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/prediction-markets/src/types/combinatorial_tokens_benchmark_helper.rs b/zrml/prediction-markets/src/types/combinatorial_tokens_benchmark_helper.rs index 49e649453..1f5920e1d 100644 --- a/zrml/prediction-markets/src/types/combinatorial_tokens_benchmark_helper.rs +++ b/zrml/prediction-markets/src/types/combinatorial_tokens_benchmark_helper.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // diff --git a/zrml/prediction-markets/src/types/mod.rs b/zrml/prediction-markets/src/types/mod.rs index 0325d79b5..1eb94a82e 100644 --- a/zrml/prediction-markets/src/types/mod.rs +++ b/zrml/prediction-markets/src/types/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // From 929df7d20724790d40c0d4a098ae37444f217d37 Mon Sep 17 00:00:00 2001 From: Chralt98 Date: Mon, 24 Feb 2025 12:19:08 +0100 Subject: [PATCH 69/73] fix copyrights --- zrml/combinatorial-tokens/fuzz/common.rs | 19 ++++++++++++++++++- .../fuzz/merge_position.rs | 19 ++++++++++++++++++- .../fuzz/redeem_position.rs | 19 ++++++++++++++++++- .../fuzz/split_position.rs | 19 ++++++++++++++++++- zrml/futarchy/fuzz/submit_proposal.rs | 19 ++++++++++++++++++- zrml/futarchy/src/proposal_storage.rs | 19 ++++++++++++++++++- zrml/futarchy/src/traits/proposal_storage.rs | 19 ++++++++++++++++++- zrml/neo-swaps/fuzz/combo_buy.rs | 19 ++++++++++++++++++- zrml/neo-swaps/fuzz/combo_sell.rs | 19 ++++++++++++++++++- zrml/neo-swaps/fuzz/common.rs | 19 ++++++++++++++++++- .../fuzz/deploy_combinatorial_pool.rs | 19 ++++++++++++++++++- .../decision_market_oracle_scoreboard.rs | 19 ++++++++++++++++++- 12 files changed, 216 insertions(+), 12 deletions(-) diff --git a/zrml/combinatorial-tokens/fuzz/common.rs b/zrml/combinatorial-tokens/fuzz/common.rs index 13f853b86..a1a5cd138 100644 --- a/zrml/combinatorial-tokens/fuzz/common.rs +++ b/zrml/combinatorial-tokens/fuzz/common.rs @@ -1,4 +1,21 @@ -// Copyright 2025 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use zeitgeist_primitives::{ traits::MarketOf, types::{Market, MarketCreation, MarketPeriod, MarketStatus, MarketType, ScoringRule}, }; diff --git a/zrml/combinatorial-tokens/fuzz/merge_position.rs b/zrml/combinatorial-tokens/fuzz/merge_position.rs index 89c18b2bd..97e9696de 100644 --- a/zrml/combinatorial-tokens/fuzz/merge_position.rs +++ b/zrml/combinatorial-tokens/fuzz/merge_position.rs @@ -1,4 +1,21 @@ -// Copyright 2025 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +#![no_main] mod common; diff --git a/zrml/combinatorial-tokens/fuzz/redeem_position.rs b/zrml/combinatorial-tokens/fuzz/redeem_position.rs index f7fef9a06..67f2c199e 100644 --- a/zrml/combinatorial-tokens/fuzz/redeem_position.rs +++ b/zrml/combinatorial-tokens/fuzz/redeem_position.rs @@ -1,4 +1,21 @@ -// Copyright 2025 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +#![no_main] mod common; diff --git a/zrml/combinatorial-tokens/fuzz/split_position.rs b/zrml/combinatorial-tokens/fuzz/split_position.rs index 180817793..9cf2e1f8f 100644 --- a/zrml/combinatorial-tokens/fuzz/split_position.rs +++ b/zrml/combinatorial-tokens/fuzz/split_position.rs @@ -1,4 +1,21 @@ -// Copyright 2025 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +#![no_main] mod common; diff --git a/zrml/futarchy/fuzz/submit_proposal.rs b/zrml/futarchy/fuzz/submit_proposal.rs index f12d5fb90..1c39ead34 100644 --- a/zrml/futarchy/fuzz/submit_proposal.rs +++ b/zrml/futarchy/fuzz/submit_proposal.rs @@ -1,4 +1,21 @@ -// Copyright 2025 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +#![no_main] use arbitrary::{Arbitrary, Result as ArbitraryResult, Unstructured}; use frame_system::pallet_prelude::{BlockNumberFor, OriginFor}; diff --git a/zrml/futarchy/src/proposal_storage.rs b/zrml/futarchy/src/proposal_storage.rs index 39c34d301..da7ce7e09 100644 --- a/zrml/futarchy/src/proposal_storage.rs +++ b/zrml/futarchy/src/proposal_storage.rs @@ -1,4 +1,21 @@ -// Copyright 2025 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use crate::{ traits::ProposalStorage, types::Proposal, Config, Error, Pallet, ProposalCount, Proposals, ProposalsOf, }; diff --git a/zrml/futarchy/src/traits/proposal_storage.rs b/zrml/futarchy/src/traits/proposal_storage.rs index 770c3953b..127c3dd91 100644 --- a/zrml/futarchy/src/traits/proposal_storage.rs +++ b/zrml/futarchy/src/traits/proposal_storage.rs @@ -1,4 +1,21 @@ -// Copyright 2025 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use crate::{types::Proposal, Config, ProposalsOf}; use alloc::{collections::BTreeMap, vec::Vec}; use frame_system::pallet_prelude::BlockNumberFor; use sp_runtime::DispatchError; diff --git a/zrml/neo-swaps/fuzz/combo_buy.rs b/zrml/neo-swaps/fuzz/combo_buy.rs index 8531ab3e9..121807b6a 100644 --- a/zrml/neo-swaps/fuzz/combo_buy.rs +++ b/zrml/neo-swaps/fuzz/combo_buy.rs @@ -1,4 +1,21 @@ -// Copyright 2025 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +#![no_main] mod common; diff --git a/zrml/neo-swaps/fuzz/combo_sell.rs b/zrml/neo-swaps/fuzz/combo_sell.rs index e3a2f7eb4..5c44c19b7 100644 --- a/zrml/neo-swaps/fuzz/combo_sell.rs +++ b/zrml/neo-swaps/fuzz/combo_sell.rs @@ -1,4 +1,21 @@ -// Copyright 2025 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +#![no_main] mod common; diff --git a/zrml/neo-swaps/fuzz/common.rs b/zrml/neo-swaps/fuzz/common.rs index 348572b17..8ee4e7a18 100644 --- a/zrml/neo-swaps/fuzz/common.rs +++ b/zrml/neo-swaps/fuzz/common.rs @@ -1,4 +1,21 @@ -// Copyright 2025 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use zeitgeist_primitives::{ traits::MarketOf, types::{Market, MarketCreation, MarketPeriod, MarketStatus, MarketType, ScoringRule}, }; diff --git a/zrml/neo-swaps/fuzz/deploy_combinatorial_pool.rs b/zrml/neo-swaps/fuzz/deploy_combinatorial_pool.rs index 2c89ccab1..738b2f1e5 100644 --- a/zrml/neo-swaps/fuzz/deploy_combinatorial_pool.rs +++ b/zrml/neo-swaps/fuzz/deploy_combinatorial_pool.rs @@ -1,4 +1,21 @@ -// Copyright 2025 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +#![no_main] mod common; diff --git a/zrml/neo-swaps/src/types/decision_market_oracle_scoreboard.rs b/zrml/neo-swaps/src/types/decision_market_oracle_scoreboard.rs index 2498b51af..64e745762 100644 --- a/zrml/neo-swaps/src/types/decision_market_oracle_scoreboard.rs +++ b/zrml/neo-swaps/src/types/decision_market_oracle_scoreboard.rs @@ -1,4 +1,21 @@ -// Copyright 2025 Forecasting Technologies LTD. +// Copyright 2024-2025 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use crate::{BalanceOf, Config}; use frame_system::pallet_prelude::BlockNumberFor; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; From ad3a4b116d26a4cb71a464c93185a3673020af57 Mon Sep 17 00:00:00 2001 From: Chralt98 Date: Mon, 24 Feb 2025 12:54:52 +0100 Subject: [PATCH 70/73] taplo fmt --- zrml/neo-swaps/fuzz/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/zrml/neo-swaps/fuzz/Cargo.toml b/zrml/neo-swaps/fuzz/Cargo.toml index 7b5c6dfd0..cf5683e08 100644 --- a/zrml/neo-swaps/fuzz/Cargo.toml +++ b/zrml/neo-swaps/fuzz/Cargo.toml @@ -36,4 +36,3 @@ version = "0.5.5" [package.metadata] cargo-fuzz = true - From 3f9be1c962fe3f695a187daef1430f01cc12bbd7 Mon Sep 17 00:00:00 2001 From: Chralt98 Date: Mon, 24 Feb 2025 15:39:01 +0100 Subject: [PATCH 71/73] fix neo-swaps benchmarks after existential deposit bump --- zrml/neo-swaps/src/benchmarking.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/zrml/neo-swaps/src/benchmarking.rs b/zrml/neo-swaps/src/benchmarking.rs index 35a5b5e6d..651d71bb1 100644 --- a/zrml/neo-swaps/src/benchmarking.rs +++ b/zrml/neo-swaps/src/benchmarking.rs @@ -513,7 +513,7 @@ mod benchmarks { market_ids.push(market_id); } - let amount = _100.saturated_into(); + let amount = (100 * _100).saturated_into(); let total_cost = amount + T::MultiCurrency::minimum_balance(base_asset); assert_ok!(T::MultiCurrency::deposit(base_asset, &alice, total_cost)); assert_ok!(NeoSwaps::::deploy_combinatorial_pool( @@ -568,7 +568,7 @@ mod benchmarks { market_ids.push(market_id); } - let amount = _100.saturated_into(); + let amount = (100 * _100).saturated_into(); let total_cost = amount + T::MultiCurrency::minimum_balance(base_asset); assert_ok!(T::MultiCurrency::deposit(base_asset, &alice, total_cost)); assert_ok!(NeoSwaps::::deploy_combinatorial_pool( @@ -590,7 +590,7 @@ mod benchmarks { let sell_arg = vec![assets[1]]; let keep_arg = (2..asset_count).map(|i| assets[i as usize]).collect::>(); - let amount_buy: BalanceOf = _2.saturated_into(); + let amount_buy: BalanceOf = (100 * _2).saturated_into(); let amount_keep = if keep_arg.is_empty() { // If n = 1; Zero::zero() From 169fb0721ec07aa1172eb96d83a70088f1e14873 Mon Sep 17 00:00:00 2001 From: Chralt98 Date: Mon, 24 Feb 2025 15:45:26 +0100 Subject: [PATCH 72/73] fix deploy combinatorial pool benchmark --- zrml/neo-swaps/src/benchmarking.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zrml/neo-swaps/src/benchmarking.rs b/zrml/neo-swaps/src/benchmarking.rs index 651d71bb1..12e87b399 100644 --- a/zrml/neo-swaps/src/benchmarking.rs +++ b/zrml/neo-swaps/src/benchmarking.rs @@ -637,7 +637,7 @@ mod benchmarks { market_ids.push(market_id); } - let amount = _100.saturated_into(); + let amount = (100 * _100).saturated_into(); let total_cost = amount + T::MultiCurrency::minimum_balance(base_asset); assert_ok!(T::MultiCurrency::deposit(base_asset, &alice, total_cost)); From d3bf2b2b12bf093b01727b9d9809c7d5c0421d16 Mon Sep 17 00:00:00 2001 From: Chralt98 Date: Mon, 24 Feb 2025 15:59:09 +0100 Subject: [PATCH 73/73] fix decision market oracle benchmarks --- zrml/neo-swaps/src/benchmarking.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zrml/neo-swaps/src/benchmarking.rs b/zrml/neo-swaps/src/benchmarking.rs index 12e87b399..795090e98 100644 --- a/zrml/neo-swaps/src/benchmarking.rs +++ b/zrml/neo-swaps/src/benchmarking.rs @@ -665,7 +665,7 @@ mod benchmarks { alice, base_asset, asset_count, - _10.saturated_into(), + (100 * _100).saturated_into(), ); let pool = Pools::::get(market_id).unwrap(); @@ -694,7 +694,7 @@ mod benchmarks { alice, base_asset, asset_count, - _10.saturated_into(), + (100 * _100).saturated_into(), ); let pool = Pools::::get(market_id).unwrap();