From ff21a047fb016a01450c158e3fe678d5a1799a01 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Fri, 13 Sep 2024 15:15:58 +0200 Subject: [PATCH 01/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] . --- 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/47] 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/47] . --- 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] . --- 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/47] . --- 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/47] . --- 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/47] 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/47] . --- 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/47] . --- 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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)) } }