Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Audit fixes #1395

Merged
merged 24 commits into from
Jan 30, 2025
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
1bf57a0
(Issue No 1)
maltekliemann Dec 2, 2024
965f378
Fix `log_ceil` and add extensive tests
maltekliemann Dec 2, 2024
cbbdf73
(Issue No 7) Enable overflow checks
maltekliemann Dec 2, 2024
7258b52
(Issue No 9) Fix incorrect test
maltekliemann Dec 2, 2024
e226c42
(Issue No 10) Resolve FIXMEs
maltekliemann Dec 2, 2024
bc61a30
Fix formatting
maltekliemann Dec 2, 2024
4b090cc
(Issue No 13) Remove TODO by avoiding a migration
maltekliemann Dec 3, 2024
60583ac
(Issue No 13) Remove TODO by keeping low-level types
maltekliemann Dec 3, 2024
3c9a691
(Issue No 13) Remove TODO by keeping low-level types
maltekliemann Dec 3, 2024
b899ed4
(Issue No 13) Remove already fixed TODO
maltekliemann Dec 3, 2024
13dac24
(Issue No 13) Remove won't fix TODOs
maltekliemann Dec 3, 2024
ad3089b
(Issue No 13) Remove more TODOs, some by implementing `CheckedIncRes`
maltekliemann Dec 3, 2024
d15ab87
(Issue No 13) Remove code quality TODO
maltekliemann Dec 3, 2024
1acd939
(Issue No 14) Define `SCHEDULE_PRIORITY`
maltekliemann Dec 3, 2024
22ce8a2
(Issue No 14) Increase readability
maltekliemann Dec 3, 2024
499aab6
(Issue No 14) Reuse `r_over_b`
maltekliemann Dec 3, 2024
10f67cd
(Issue No 14) Add clarifying comments
maltekliemann Dec 3, 2024
53cb4d1
(Issue No 14) Abstract common math code away
maltekliemann Dec 3, 2024
f77898f
Add missing file
maltekliemann Dec 3, 2024
f664336
(Issue No 2) Replace `force_max_work` with `fuel` parameter
maltekliemann Dec 5, 2024
41d8752
(Issue 6) Replace `SubmitOrigin` with root
maltekliemann Dec 9, 2024
2ce829b
(Issue Nos. 5 & 6) Squashed commit of the following:
maltekliemann Dec 10, 2024
4f44902
Fix audit decompressor security miscellaneous comments (#1402)
Chralt98 Jan 30, 2025
75e5387
Merge oracles into audit fixes (#1399)
maltekliemann Jan 30, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,15 @@ rand_chacha = { version = "0.3.1", default-features = false }
serde = { version = "1.0.198", default-features = false }
typenum = { version = "1.17.0", default-features = false }

[profile.test]
overflow-checks = true

[profile.test.package."*"]
overflow-checks = true

[profile.dev]
overflow-checks = true

[profile.dev.package]
blake2 = { opt-level = 3 }
blake2b_simd = { opt-level = 3 }
Expand Down Expand Up @@ -336,17 +345,28 @@ x25519-dalek = { opt-level = 3 }
yamux = { opt-level = 3 }
zeroize = { opt-level = 3 }

[profile.dev.package."*"]
overflow-checks = true

[profile.production]
codegen-units = 1
incremental = false
inherits = "release"
lto = true
overflow-checks = true

[profile.production.package."*"]
overflow-checks = true

[profile.release]
opt-level = 3
overflow-checks = true
# Zeitgeist runtime requires unwinding.
panic = "unwind"

[profile.release.package."*"]
overflow-checks = true


# xcm-emulator incompatible block number type fixed
# Commits:
Expand Down
4 changes: 2 additions & 2 deletions primitives/src/asset.rs
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't necessary since there is no storage value that includes the asset with index "2" (CombinatorialOutcome). For more, see here.

Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,14 @@ use serde::{Deserialize, Serialize};
pub enum Asset<MarketId> {
CategoricalOutcome(MarketId, CategoryIndex),
ScalarOutcome(MarketId, ScalarPosition),
CombinatorialToken(CombinatorialId),
CombinatorialOutcomeLegacy, // Here to avoid having to migrate all holdings on the chain.
PoolShare(PoolId),
#[default]
Ztg,
ForeignAsset(u32),
ParimutuelShare(MarketId, CategoryIndex),
CombinatorialToken(CombinatorialId),
}
// TODO Needs storage migration

#[cfg(feature = "runtime-benchmarks")]
impl<MarketId: MaxEncodedLen> ZeitgeistAssetEnumerator<MarketId> for Asset<MarketId> {
Expand Down
17 changes: 17 additions & 0 deletions primitives/src/math/checked_ops_res.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,13 @@ where
fn checked_rem_res(&self, other: &Self) -> Result<Self, DispatchError>;
}

pub trait CheckedIncRes
where
Self: Sized,
{
fn checked_inc_res(&self) -> Result<Self, DispatchError>;
}

impl<T> CheckedAddRes for T
where
T: CheckedAdd,
Expand Down Expand Up @@ -123,3 +130,13 @@ where
self.checked_rem(other).ok_or(DispatchError::Arithmetic(ArithmeticError::DivisionByZero))
}
}

impl<T> CheckedIncRes for T
where
T: CheckedAdd + From<u8>,
{
#[inline]
fn checked_inc_res(&self) -> Result<Self, DispatchError> {
self.checked_add(&1u8.into()).ok_or(DispatchError::Arithmetic(ArithmeticError::Overflow))
}
}
1 change: 1 addition & 0 deletions runtime/battery-station/src/parameters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,7 @@ parameter_type_with_key! {
match currency_id {
Asset::CategoricalOutcome(_,_) => ExistentialDeposit::get(),
Asset::CombinatorialToken(_) => ExistentialDeposit::get(),
Asset::CombinatorialOutcomeLegacy => ExistentialDeposit::get(),
Asset::PoolShare(_) => ExistentialDeposit::get(),
Asset::ScalarOutcome(_,_) => ExistentialDeposit::get(),
#[cfg(feature = "parachain")]
Expand Down
1 change: 1 addition & 0 deletions runtime/zeitgeist/src/parameters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,7 @@ parameter_type_with_key! {
match currency_id {
Asset::CategoricalOutcome(_,_) => ExistentialDeposit::get(),
Asset::CombinatorialToken(_) => ExistentialDeposit::get(),
Asset::CombinatorialOutcomeLegacy => ExistentialDeposit::get(),
Asset::PoolShare(_) => ExistentialDeposit::get(),
Asset::ScalarOutcome(_,_) => ExistentialDeposit::get(),
#[cfg(feature = "parachain")]
Expand Down
3 changes: 1 addition & 2 deletions zrml/combinatorial-tokens/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,6 @@ mod pallet {
#[transactional]
pub fn split_position(
origin: OriginFor<T>,
// TODO Abstract this into a separate type.
parent_collection_id: Option<CombinatorialIdOf<T>>,
market_id: MarketIdOf<T>,
partition: Vec<Vec<bool>>,
Expand Down Expand Up @@ -301,7 +300,7 @@ mod pallet {
TransmutationType::VerticalWithParent => {
// Split combinatorial token into higher level position.
// This will fail if the market has a different collateral than the previous
// markets. FIXME A cleaner error message would be nice though...
// markets.
T::MultiCurrency::ensure_can_withdraw(position, &who, amount)?;
T::MultiCurrency::withdraw(position, &who, amount)?;

Expand Down
2 changes: 1 addition & 1 deletion zrml/combinatorial-tokens/src/tests/redeem_position.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ fn redeem_position_fails_on_market_not_found() {

#[test_case(vec![B0, B1, B0, B1]; "incorrect_len")]
#[test_case(vec![B0, B0, B0]; "all_zero")]
#[test_case(vec![B0, B0, B0]; "all_one")]
#[test_case(vec![B1, B1, B1]; "all_one")]
fn redeem_position_fails_on_incorrect_index_set(index_set: Vec<bool>) {
ExtBuilder::build().execute_with(|| {
let alice = Account::new(0).deposit(Asset::Ztg, _100).unwrap();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ pub trait CombinatorialIdManager {
type MarketId;
type CombinatorialId;

// TODO Replace `Vec<bool>` with a more effective bit mask type.
fn get_collection_id(
parent_collection_id: Option<Self::CombinatorialId>,
market_id: Self::MarketId,
Expand Down
6 changes: 5 additions & 1 deletion zrml/futarchy/src/pallet_impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ use frame_support::{
};
use zeitgeist_primitives::traits::FutarchyOracle;

// Following Parity's implementation of pallet-democracy, we're using minimum priority for futarchy
// proposals.
const SCHEDULE_PRIORITY: u8 = 63;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use HARD_DEADLINE from frame_support::traits::schedule::HARD_DEADLINE; like here.


impl<T: Config> Pallet<T> {
/// Evaluates `proposal` using the specified oracle and schedules the contained call if the
/// oracle approves.
Expand All @@ -33,7 +37,7 @@ impl<T: Config> Pallet<T> {
let result = T::Scheduler::schedule(
DispatchTime::At(proposal.when),
None,
63,
SCHEDULE_PRIORITY,
RawOrigin::Root.into(),
proposal.call.clone(),
);
Expand Down
1 change: 0 additions & 1 deletion zrml/futarchy/src/types/proposal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ use frame_system::pallet_prelude::BlockNumberFor;
use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
use scale_info::TypeInfo;

// TODO Make config a generic, keeps things simple.
#[derive(
CloneNoBound, Decode, Encode, Eq, MaxEncodedLen, PartialEqNoBound, RuntimeDebugNoBound, TypeInfo,
)]
Expand Down
17 changes: 8 additions & 9 deletions zrml/neo-swaps/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@

#![doc = include_str!("../README.md")]
#![cfg_attr(not(feature = "std"), no_std)]
#![allow(clippy::too_many_arguments)] // TODO Try to remove this later!
#![allow(clippy::type_complexity)] // TODO Try to remove this later!
#![allow(clippy::too_many_arguments)]
#![allow(clippy::type_complexity)]

extern crate alloc;

Expand Down Expand Up @@ -1008,10 +1008,7 @@ mod pallet {
) -> DispatchResult {
ensure!(pool_shares_amount != Zero::zero(), Error::<T>::ZeroAmount);

// FIXME Should this also be made part of the `PoolStorage` interface?
Pools::<T>::try_mutate_exists(pool_id, |maybe_pool| {
let pool =
maybe_pool.as_mut().ok_or::<DispatchError>(Error::<T>::PoolNotFound.into())?;
<Self as PoolStorage>::try_mutate_exists(&pool_id, |pool| {
let ratio = {
let mut ratio = pool_shares_amount
.bdiv_floor(pool.liquidity_shares_manager.total_shares()?)?;
Expand Down Expand Up @@ -1047,13 +1044,14 @@ mod pallet {
for asset in pool.assets().iter() {
withdraw_remaining(asset)?;
}
*maybe_pool = None; // Delete the storage map entry.
Self::deposit_event(Event::<T>::PoolDestroyed {
who: who.clone(),
pool_id,
amounts_out,
});
// No need to clear `MarketIdToPoolId`.

// Delete the pool. No need to clear `MarketIdToPoolId`.
Ok(((), true))
} else {
let old_liquidity_parameter = pool.liquidity_parameter;
let new_liquidity_parameter = old_liquidity_parameter
Expand Down Expand Up @@ -1083,8 +1081,9 @@ mod pallet {
amounts_out,
new_liquidity_parameter,
});

Ok(((), false))
}
Ok(())
})
}

Expand Down
8 changes: 8 additions & 0 deletions zrml/neo-swaps/src/math/traits/combo_math_ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,17 @@ pub(crate) trait ComboMathOps<T>
where
T: Config,
{
/// Calculates the amount swapped out of a pool with liquidity parameter `liquidity` when
/// swapping in `amount_in` units of assets whose reserves in the pool are `sell` and swapping
/// out assets whose reserves in the pool are `buy`.
fn calculate_swap_amount_out_for_buy(
buy: Vec<BalanceOf<T>>,
sell: Vec<BalanceOf<T>>,
amount_in: BalanceOf<T>,
liquidity: BalanceOf<T>,
) -> Result<BalanceOf<T>, DispatchError>;

/// Calculates the amount eventually held by the user after equalizing holdings.
#[allow(dead_code)]
fn calculate_equalize_amount(
buy: Vec<BalanceOf<T>>,
Expand All @@ -39,6 +43,10 @@ where
liquidity: BalanceOf<T>,
) -> Result<BalanceOf<T>, DispatchError>;

/// Calculates the amount of each asset of a pool with liquidity parameter `liquidity` held
/// in the user's wallet after equalizing positions whose reserves in the pool are `buy`, `keep`
/// and `sell`, resp. The parameters `amount_buy` and `amount_keep` refer to the user's holdings
/// of `buy` and `keep`.
fn calculate_swap_amount_out_for_sell(
buy: Vec<BalanceOf<T>>,
keep: Vec<BalanceOf<T>>,
Expand Down
22 changes: 19 additions & 3 deletions zrml/neo-swaps/src/pool_storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

use crate::{traits::PoolStorage, Config, Error, Pallet, PoolCount, PoolOf, Pools};
use sp_runtime::DispatchError;
use zeitgeist_primitives::math::checked_ops_res::CheckedAddRes;
use zeitgeist_primitives::math::checked_ops_res::CheckedIncRes;

impl<T> PoolStorage for Pallet<T>
where
Expand All @@ -35,7 +35,7 @@ where
let pool_id = Self::next_pool_id();
Pools::<T>::insert(pool_id, pool);

let pool_count = pool_id.checked_add_res(&1u8.into())?; // TODO Add CheckedInc.
let pool_count = pool_id.checked_inc_res()?;
PoolCount::<T>::set(pool_count);

Ok(pool_id)
Expand All @@ -47,10 +47,26 @@ where

fn try_mutate_pool<R, F>(pool_id: &Self::PoolId, mutator: F) -> Result<R, DispatchError>
where
F: FnMut(&mut PoolOf<T>) -> Result<R, DispatchError>,
F: FnMut(&mut Self::Pool) -> Result<R, DispatchError>,
{
Pools::<T>::try_mutate(pool_id, |maybe_pool| {
maybe_pool.as_mut().ok_or(Error::<T>::PoolNotFound.into()).and_then(mutator)
})
}

fn try_mutate_exists<R, F>(pool_id: &Self::PoolId, mutator: F) -> Result<R, DispatchError>
where
F: FnMut(&mut Self::Pool) -> Result<(R, bool), DispatchError>,
{
Pools::<T>::try_mutate_exists(pool_id, |maybe_pool| {
let (result, delete) =
maybe_pool.as_mut().ok_or(Error::<T>::PoolNotFound.into()).and_then(mutator)?;

if delete {
*maybe_pool = None;
}

Ok(result)
})
}
}
30 changes: 24 additions & 6 deletions zrml/neo-swaps/src/tests/combo_buy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -362,27 +362,45 @@ fn combo_buy_fails_on_amount_out_below_min() {
ALICE,
BASE_ASSET,
vec![MarketType::Categorical(2), MarketType::Scalar(0..=1)],
_10,
100_000_000 * _100, // Massive liquidity to keep slippage low.
vec![_1_4, _1_4, _1_4, _1_4],
CENT,
);
let pool = <Pallet<Runtime> as PoolStorage>::get(pool_id).unwrap();
let assets = pool.assets();
let amount_in = _1;

let asset_count = 4;
let buy = assets[0..1].to_vec();
let sell = assets[1..4].to_vec();
// amount_in is _1 / 0.97 (i.e. _1 after deducting 3% fees - 1% trading fees, 1% external
// fees _for each market_)
let amount_in = 10_309_278_350;

assert_ok!(AssetManager::deposit(BASE_ASSET, &BOB, amount_in));
// Buying 1 at price of .25 will return less than 4 outcomes due to slippage.
// Buying for 1 at a price of .25 will return less than 4 outcomes due to slippage.
assert_noop!(
NeoSwaps::combo_buy(
RuntimeOrigin::signed(BOB),
pool_id,
4,
vec![assets[0]],
vec![assets[1]],
asset_count,
buy.clone(),
sell.clone(),
amount_in,
_4,
),
Error::<Runtime>::AmountOutBelowMin,
);

// Post OakSecurity audit: Show that the slippage limit is tight.
assert_ok!(NeoSwaps::combo_buy(
RuntimeOrigin::signed(BOB),
pool_id,
asset_count,
buy,
sell,
amount_in,
_4 - 33,
));
});
}

Expand Down
9 changes: 9 additions & 0 deletions zrml/neo-swaps/src/traits/pool_storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

use sp_runtime::DispatchError;

/// Slot map interface for pool storage. Undocumented functions behave like their counterparts in
/// substrate's `StorageMap`.
pub(crate) trait PoolStorage {
type PoolId;
type Pool;
Expand All @@ -30,4 +32,11 @@ pub(crate) trait PoolStorage {
fn try_mutate_pool<R, F>(pool_id: &Self::PoolId, mutator: F) -> Result<R, DispatchError>
where
F: FnMut(&mut Self::Pool) -> Result<R, DispatchError>;

/// Mutate and maybe remove the pool indexed by `pool_id`. Unlike `try_mutate_exists` in
/// `StorageMap`, the `mutator` must return a `(R, bool)`. If and only if the pool is positive,
/// the pool is removed.
fn try_mutate_exists<R, F>(pool_id: &Self::PoolId, mutator: F) -> Result<R, DispatchError>
where
F: FnMut(&mut Self::Pool) -> Result<(R, bool), DispatchError>;
}
Loading