Skip to content

Commit 5f0b75e

Browse files
rflechtnerAd96el
andauthored
refactor: bonded coins audit fixes (#873)
Changes to pallet-bonded-coins to address findings in the audit. --- Co-authored-by: Adel Golghalyani <48685760+Ad96el@users.noreply.github.com>
1 parent 8721e6a commit 5f0b75e

25 files changed

Lines changed: 1301 additions & 274 deletions

File tree

pallets/pallet-bonded-coins/README.md

Lines changed: 190 additions & 40 deletions
Large diffs are not rendered by default.

pallets/pallet-bonded-coins/src/benchmarking.rs

Lines changed: 63 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
// If you feel like getting in touch with us, you can do so at <hello@kilt.io>
1818
use frame_benchmarking::v2::*;
1919
use frame_support::traits::fungibles::roles::Inspect as InspectRoles;
20+
use scale_info::prelude::format;
2021
use sp_core::U256;
2122
use sp_std::{
2223
ops::{AddAssign, BitOrAssign, ShlAssign},
@@ -27,9 +28,11 @@ use substrate_fixed::traits::{Fixed, FixedSigned, FixedUnsigned, ToFixed};
2728
use crate::{
2829
curves::{
2930
lmsr::{LMSRParameters, LMSRParametersInput},
31+
polynomial::PolynomialParameters,
3032
square_root::{SquareRootParameters, SquareRootParametersInput},
3133
Curve, CurveInput,
3234
},
35+
types::BondedCurrenciesSettings,
3336
Call, CollateralAssetIdOf, CollateralBalanceOf, Config, CurveParameterTypeOf, FungiblesAssetIdOf,
3437
FungiblesBalanceOf, Pallet,
3538
};
@@ -63,6 +66,13 @@ where
6366
fn set_native_balance(_account: &<T>::AccountId, _amount: u128) {}
6467
}
6568

69+
fn get_2nd_order_polynomial_curve<Float: FixedSigned>() -> Curve<Float> {
70+
let m = Float::from_num(0.01);
71+
let n = Float::from_num(2);
72+
let o = Float::from_num(3);
73+
Curve::Polynomial(PolynomialParameters { m, n, o })
74+
}
75+
6676
fn get_square_root_curve<Float: FixedSigned>() -> Curve<Float> {
6777
let m = Float::from_num(3);
6878
let n = Float::from_num(2);
@@ -220,10 +230,13 @@ mod benchmarks {
220230
owner,
221231
state,
222232
collateral,
223-
denomination,
224233
bonded_currencies: BoundedVec::truncate_from(bonded_coin_ids.clone()),
225-
transferable: true,
226-
min_operation_balance: 1u128.saturated_into(),
234+
currencies_settings: BondedCurrenciesSettings {
235+
denomination,
236+
transferable: true,
237+
allow_reset_team: true,
238+
min_operation_balance: 1u128.saturated_into(),
239+
},
227240
deposit: Pallet::<T>::calculate_pool_deposit(bonded_coin_ids.len()),
228241
};
229242
Pools::<T>::insert(&pool_id, pool_details);
@@ -233,11 +246,11 @@ mod benchmarks {
233246

234247
fn generate_token_metadata<T: Config>(c: u32) -> BoundedVec<TokenMetaOf<T>, T::MaxCurrenciesPerPool> {
235248
let mut token_meta = Vec::new();
236-
for _ in 1..=c {
249+
for i in 1..=c {
237250
token_meta.push(TokenMetaOf::<T> {
238251
min_balance: 1u128.saturated_into(),
239-
name: BoundedVec::try_from(b"BTC".to_vec()).expect("Failed to create BoundedVec"),
240-
symbol: BoundedVec::try_from(b"BTC".to_vec()).expect("Failed to create BoundedVec"),
252+
name: BoundedVec::try_from(format!("Coin_{}", &i).into_bytes()).expect("Failed to create BoundedVec"),
253+
symbol: BoundedVec::try_from(format!("BTC_{}", &i).into_bytes()).expect("Failed to create BoundedVec"),
241254
})
242255
}
243256
BoundedVec::try_from(token_meta).expect("creating bounded Vec should not fail")
@@ -263,9 +276,12 @@ mod benchmarks {
263276
curve,
264277
collateral_id,
265278
currencies,
266-
10,
267-
true,
268-
1,
279+
BondedCurrenciesSettings {
280+
denomination: 10,
281+
allow_reset_team: true,
282+
transferable: true,
283+
min_operation_balance: 1u128.saturated_into(),
284+
},
269285
);
270286

271287
// Verify
@@ -301,9 +317,12 @@ mod benchmarks {
301317
curve,
302318
collateral_id,
303319
currencies,
304-
10,
305-
true,
306-
1,
320+
BondedCurrenciesSettings {
321+
denomination: 10,
322+
allow_reset_team: true,
323+
transferable: true,
324+
min_operation_balance: 1u128.saturated_into(),
325+
},
307326
);
308327

309328
// Verify
@@ -313,7 +332,7 @@ mod benchmarks {
313332
Curve::SquareRoot(_) => {
314333
assert_eq!(id, expected_pool_id);
315334
}
316-
_ => panic!("pool.curve is not a Polynomial function"),
335+
_ => panic!("pool.curve is not a SquareRoot function"),
317336
}
318337
}
319338

@@ -338,9 +357,12 @@ mod benchmarks {
338357
curve,
339358
collateral_id,
340359
currencies,
341-
10,
342-
true,
343-
1,
360+
BondedCurrenciesSettings {
361+
denomination: 10,
362+
allow_reset_team: true,
363+
transferable: true,
364+
min_operation_balance: 1u128.saturated_into(),
365+
},
344366
);
345367

346368
// Verify
@@ -350,30 +372,27 @@ mod benchmarks {
350372
Curve::Lmsr(_) => {
351373
assert_eq!(id, expected_pool_id);
352374
}
353-
_ => panic!("pool.curve is not a Polynomial function"),
375+
_ => panic!("pool.curve is not a LSMR curve!"),
354376
}
355377
}
356378

357379
#[benchmark]
358-
fn reset_team() {
380+
fn reset_team(c: Linear<1, { T::MaxCurrenciesPerPool::get() }>) {
359381
let origin = T::DefaultOrigin::try_successful_origin().expect("creating origin should not fail");
360382
let account_origin = origin
361383
.clone()
362384
.into_signer()
363385
.expect("generating account_id from origin should not fail");
364386
make_free_for_deposit::<T>(&account_origin);
365387

366-
let bonded_coin_id = T::BenchmarkHelper::calculate_bonded_asset_id(0);
367-
create_bonded_asset::<T>(bonded_coin_id.clone());
388+
let bonded_currencies = create_bonded_currencies_in_range::<T>(c, false);
368389

369390
let curve = get_linear_bonding_curve::<CurveParameterTypeOf<T>>();
370-
let pool_id = create_pool::<T>(
371-
curve,
372-
[bonded_coin_id.clone()].to_vec(),
373-
Some(account_origin),
374-
None,
375-
None,
376-
);
391+
let pool_id = create_pool::<T>(curve, bonded_currencies.clone(), Some(account_origin), None, None);
392+
393+
// Although these would rarely happen in practice, for benchmarking we assume
394+
// the worst case where the owner must be changed as well
395+
assert!(T::Fungibles::owner(bonded_currencies[0].clone()) != Some(pool_id.clone().into()));
377396

378397
let admin: AccountIdOf<T> = account("admin", 0, 0);
379398
let freezer: AccountIdOf<T> = account("freezer", 0, 0);
@@ -382,12 +401,24 @@ mod benchmarks {
382401
freezer: freezer.clone(),
383402
};
384403

404+
let pool_id_for_call = pool_id.clone();
405+
406+
let max_currencies = T::MaxCurrenciesPerPool::get();
385407
#[extrinsic_call]
386-
_(origin as T::RuntimeOrigin, pool_id, fungibles_team, 0);
408+
_(
409+
origin as T::RuntimeOrigin,
410+
pool_id_for_call,
411+
fungibles_team,
412+
max_currencies,
413+
);
387414

388415
// Verify
389-
assert_eq!(T::Fungibles::admin(bonded_coin_id.clone()), Some(admin));
390-
assert_eq!(T::Fungibles::freezer(bonded_coin_id), Some(freezer));
416+
bonded_currencies.iter().for_each(|asset_id| {
417+
assert_eq!(T::Fungibles::admin(asset_id.clone()), Some(admin.clone()));
418+
assert_eq!(T::Fungibles::freezer(asset_id.clone()), Some(freezer.clone()));
419+
assert_eq!(T::Fungibles::owner(asset_id.clone()), Some(pool_id.clone().into()));
420+
assert_eq!(T::Fungibles::issuer(asset_id.clone()), Some(pool_id.clone().into()));
421+
});
391422
}
392423

393424
#[benchmark]
@@ -475,7 +506,7 @@ mod benchmarks {
475506
make_free_for_deposit::<T>(&account_origin);
476507
set_collateral_balance::<T>(collateral_id.clone(), &account_origin, 10000u128);
477508

478-
let curve = get_linear_bonding_curve::<CurveParameterTypeOf<T>>();
509+
let curve = get_2nd_order_polynomial_curve::<CurveParameterTypeOf<T>>();
479510
let bonded_currencies = create_bonded_currencies_in_range::<T>(c, false);
480511

481512
let pool_id = create_pool::<T>(curve, bonded_currencies.clone(), None, None, Some(0));
@@ -605,7 +636,7 @@ mod benchmarks {
605636
let start_balance = 100u128;
606637
set_fungible_balance::<T>(target_asset_id.clone(), &account_origin, start_balance);
607638

608-
let curve = get_linear_bonding_curve::<CurveParameterTypeOf<T>>();
639+
let curve = get_2nd_order_polynomial_curve::<CurveParameterTypeOf<T>>();
609640

610641
let pool_id = create_pool::<T>(curve, bonded_currencies, None, None, Some(0));
611642
let pool_account = pool_id.clone().into();

pallets/pallet-bonded-coins/src/curves/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ pub fn balance_to_fixed<Balance, FixedType: Fixed>(
166166
round_kind: Round,
167167
) -> Result<FixedType, ArithmeticError>
168168
where
169-
FixedType::Bits: TryFrom<U256>, // TODO: make large integer type configurable in runtime
169+
FixedType::Bits: TryFrom<U256>,
170170
Balance: TryInto<U256>,
171171
{
172172
let decimals = U256::from(10u8)

pallets/pallet-bonded-coins/src/curves/polynomial.rs

Lines changed: 40 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,19 @@
3434
/// ### Antiderivative
3535
/// The indefinite integral of the cost function is:
3636
/// ```text
37-
/// C(s) = (m / 3) * s^3 + (n / 2) * s^2 + o * s
37+
/// C(s) = (m / 3) * s^3 + (n / 2) * s^2 + o * s = M * s^3 + N * s^2 + O * s
3838
/// ```
3939
/// Where:
4040
/// - `m` is the coefficient for the quadratic term,
4141
/// - `n` is the coefficient for the linear term,
42-
/// - `o` is the constant term.
42+
/// - `o` is the constant term,
43+
/// - `M` is the coefficient of the first (cubic) term in the antiderivative,
44+
/// - `N` is the coefficient of the second (quadratic) term in the
45+
/// antiderivative,
46+
/// - `O=o` is the coefficient of the third (linear) term in the antiderivative.
4347
///
48+
/// Coefficients of the antiderivative `N`, `M` & `O` are used to parametrize
49+
/// functions in this module.
4450
///
4551
/// `C(s)` represents the accumulated cost of purchasing or selling assets up to
4652
/// the current supply `s`. The integral between two supply points, `s*`
@@ -81,9 +87,11 @@ use crate::PassiveSupply;
8187
///
8288
/// For a polynomial cost function `c(s) = 3 * s^2 + 2 * s + 2`
8389
///
84-
/// which is resulting into the antiderivative
85-
/// `C(s) = (3 / 3) * s^3 + (2 / 2) * s^2 + 2 * s`
86-
/// the input parameters would be:
90+
/// which results in the antiderivative
91+
/// `C(s) = (3 / 3) * s^3 + (2 / 2) * s^2 + 2 * s = 1 * s^3 + 1 * s^2 + 2 * s`
92+
///
93+
/// the input parameters `M`, `N` & `O` (coefficients of the antiderivative)
94+
/// would be:
8795
/// ```rust, ignore
8896
/// PolynomialParametersInput {
8997
/// m: 1,
@@ -152,27 +160,33 @@ where
152160
.checked_add(accumulated_passive_issuance)
153161
.ok_or(ArithmeticError::Overflow)?;
154162

155-
// Calculate high - low
156-
let delta_x = high.checked_sub(low).ok_or(ArithmeticError::Underflow)?;
157-
158-
let high_low_mul = high.checked_mul(low).ok_or(ArithmeticError::Overflow)?;
159-
let high_square = square(high)?;
160-
let low_square = square(low)?;
161-
162-
// Factorized cubic term: (high^2 + high * low + low^2)
163-
let cubic_term = high_square
164-
.checked_add(high_low_mul)
165-
.ok_or(ArithmeticError::Overflow)?
166-
.checked_add(low_square)
167-
.ok_or(ArithmeticError::Overflow)?;
168-
169163
// Calculate m * (high^2 + high * low + low^2)
170-
let term1 = self.m.checked_mul(cubic_term).ok_or(ArithmeticError::Overflow)?;
171-
172-
let high_plus_low = high.checked_add(low).ok_or(ArithmeticError::Overflow)?;
164+
let term1 = if self.m == Coefficient::from_num(0u8) {
165+
// if m is 0 the product is 0
166+
Ok(self.m)
167+
} else {
168+
let high_low_mul = high.checked_mul(low).ok_or(ArithmeticError::Overflow)?;
169+
let high_square = square(high)?;
170+
let low_square = square(low)?;
171+
172+
// Factorized cubic term: (high^2 + high * low + low^2)
173+
let cubic_term = high_square
174+
.checked_add(high_low_mul)
175+
.ok_or(ArithmeticError::Overflow)?
176+
.checked_add(low_square)
177+
.ok_or(ArithmeticError::Overflow)?;
178+
179+
self.m.checked_mul(cubic_term).ok_or(ArithmeticError::Overflow)
180+
}?;
173181

174182
// Calculate n * (high + low)
175-
let term2 = self.n.checked_mul(high_plus_low).ok_or(ArithmeticError::Overflow)?;
183+
let term2 = if self.n == Coefficient::from_num(0u8) {
184+
// if n is 0 the product is 0
185+
Ok(self.n)
186+
} else {
187+
let high_plus_low = high.checked_add(low).ok_or(ArithmeticError::Overflow)?;
188+
self.n.checked_mul(high_plus_low).ok_or(ArithmeticError::Overflow)
189+
}?;
176190

177191
// Final calculation with factored (high - low)
178192
let result = term1
@@ -181,6 +195,9 @@ where
181195
.checked_add(self.o)
182196
.ok_or(ArithmeticError::Overflow)?;
183197

198+
// Calculate high - low
199+
let delta_x = high.checked_sub(low).ok_or(ArithmeticError::Underflow)?;
200+
184201
result.checked_mul(delta_x).ok_or(ArithmeticError::Overflow)
185202
}
186203
}

pallets/pallet-bonded-coins/src/curves/square_root.rs

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,19 @@
3333
/// ### Antiderivative
3434
/// The indefinite integral of the cost function is:
3535
/// ```text
36-
/// C(s) = (2/3) * m * s^(3/2) + n * s
36+
/// C(s) = (2/3) * m * s^(3/2) + n * s = M * s^(3/2) + N * s
3737
/// ```
3838
/// Where:
3939
/// - `s` is the supply of assets,
4040
/// - `m` is the coefficient for the square root term,
41-
/// - `n` is the coefficient for the linear term.
41+
/// - `n` is the coefficient for the constant term,
42+
/// - `M` is the coefficient for the first (fractional power) term in the
43+
/// antiderivative,
44+
/// - `N=n` is the coefficient of the second (linear) term in the
45+
/// antiderivative.
46+
///
47+
/// Coefficients of the antiderivative `N` & `M` are used to parametrize
48+
/// functions in this module.
4249
///
4350
/// `C(s)` represents the total cost of purchasing or selling assets up to the
4451
/// current supply `s`. To calculate the incremental cost of a transaction, use
@@ -72,14 +79,19 @@ use crate::{PassiveSupply, Precision};
7279
/// bonding curve. This struct is used to convert the input parameters to the
7380
/// correct fixed-point type.
7481
///
75-
/// The input struct assumes that the coefficients are precomputed according to
76-
/// the integral rules of the square root function./// ### Example
82+
/// The input struct expects coefficients of terms in the antiderivative of the
83+
/// square root function, which must be precomputed according to the integral
84+
/// rules of the square root function.
85+
///
86+
/// ### Example
87+
///
88+
/// For a square root cost function `c(s) = 3 * s^1/2 + 2`
7789
///
78-
/// For a square root cost function `c(s) = 3 * s^1/2 + 2
90+
/// which results in the antiderivative
91+
/// `C(s) = (2 / 3) * 3 * s^(3/2) + 2 * s = 2s^(3/2) + 2s`
7992
///
80-
/// which is resulting into the antiderivative
81-
/// `C(s) = (6 / 3) * s^(1/2) + 2 * s`
82-
/// the input parameters would be:
93+
/// the input parameters `M` & `N` (coefficients of the antiderivative) would
94+
/// be:
8395
/// ```rust, ignore
8496
/// SquareRootParametersInput {
8597
/// m: 2,

0 commit comments

Comments
 (0)