Skip to content

Commit cdfa268

Browse files
Merge pull request #42 from hashed-io/fix/unsafe-arithmetics
Fix/unsafe arithmetics
2 parents 37de4d9 + 240f3f8 commit cdfa268

File tree

8 files changed

+174
-16
lines changed

8 files changed

+174
-16
lines changed

pallets/afloat/src/functions.rs

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,16 @@ use frame_support::pallet_prelude::*;
44
use frame_system::{pallet_prelude::*, RawOrigin};
55
use pallet_fruniques::types::{Attributes, CollectionDescription, FruniqueRole, ParentInfo};
66
use pallet_gated_marketplace::types::{Marketplace, MarketplaceRole};
7-
use sp_runtime::{traits::StaticLookup, Permill};
87
// use frame_support::traits::OriginTrait;
98
use core::convert::TryInto;
109
use frame_support::traits::Time;
1110
use pallet_rbac::types::{IdOrVec, RoleBasedAccessControl, RoleId};
1211
use scale_info::prelude::vec;
1312
use sp_io::hashing::blake2_256;
1413
use sp_runtime::{
14+
Permill,
1515
sp_std::{str, vec::Vec},
16-
traits::Zero,
16+
traits::{Zero, StaticLookup, CheckedMul},
1717
};
1818

1919
impl<T: Config> Pallet<T> {
@@ -473,19 +473,19 @@ impl<T: Config> Pallet<T> {
473473
offer.tax_credit_amount_remaining >= tax_credit_amount,
474474
Error::<T>::NotEnoughTaxCreditsAvailable
475475
);
476+
477+
let price_per_credit: T::Balance = offer.price_per_credit.into();
478+
let total_price: T::Balance = Self::safe_multiply_offer(price_per_credit, tax_credit_amount)?;
476479
//ensure user has enough afloat balance
477480
ensure!(
478-
Self::do_get_afloat_balance(who.clone())? >=
479-
offer.price_per_credit * tax_credit_amount.into(),
481+
Self::do_get_afloat_balance(who.clone())? >= total_price,
480482
Error::<T>::NotEnoughAfloatBalanceAvailable
481483
);
482484
let zero_balance: T::Balance = Zero::zero();
483485
//ensure tax credit amount is greater than zero
484486
ensure!(tax_credit_amount > zero_balance, Error::<T>::Underflow);
485487

486488
let creation_date: u64 = T::Timestamp::now().into();
487-
let price_per_credit: T::Balance = offer.price_per_credit.into();
488-
let total_price: T::Balance = price_per_credit * tax_credit_amount;
489489
let fee: Option<T::Balance> = None;
490490
let tax_credit_id: <T as pallet_uniques::Config>::ItemId = offer.tax_credit_id;
491491
let seller_id: T::AccountId = offer.creator_id;
@@ -531,6 +531,8 @@ impl<T: Config> Pallet<T> {
531531
Ok(())
532532
}
533533

534+
535+
534536
/// Confirms a sell transaction.
535537
///
536538
/// # Arguments
@@ -933,6 +935,14 @@ impl<T: Config> Pallet<T> {
933935
})
934936
}
935937

938+
fn safe_multiply_offer(
939+
factor1: T::Balance,
940+
factor2: T::Balance
941+
) -> Result<T::Balance, DispatchError> {
942+
let result = factor1.checked_mul(&factor2).ok_or_else(|| Error::<T>::ArithmeticOverflow)?;
943+
Ok(result)
944+
}
945+
936946
fn get_all_roles_for_user(account_id: T::AccountId) -> Result<Vec<AfloatRole>, DispatchError> {
937947
let roles_storage = <T as pallet::Config>::Rbac::get_roles_by_user(
938948
account_id.clone(),

pallets/afloat/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,8 @@ pub mod pallet {
146146
ChildOfferIdNotFound,
147147
// Tax credit amount underflow
148148
Underflow,
149+
// Arithmetic multiplication overflow
150+
ArithmeticOverflow,
149151
// Afloat marketplace label too long
150152
LabelTooLong,
151153
// Afloat asset has not been set

pallets/afloat/src/mock.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ impl pallet_rbac::Config for Test {
187187
type MaxPermissionsPerRole = MaxPermissionsPerRole;
188188
type MaxRolesPerUser = MaxRolesPerUser;
189189
type MaxUsersPerRole = MaxUsersPerRole;
190+
type WeightInfo = ();
190191
}
191192

192193
impl pallet_timestamp::Config for Test {

pallets/afloat/src/tests.rs

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use super::*;
2-
use crate::{mock::*, types::*, Error};
2+
use crate::{mock::*, types::*, AfloatOffers, Error};
33
use frame_support::{assert_noop, assert_ok, traits::Currency, BoundedVec};
44
use frame_system::RawOrigin;
55

@@ -16,6 +16,61 @@ fn dummy_description() -> BoundedVec<u8, StringLimit> {
1616
//buy_fee = 2%
1717
//sell_fee = 4%
1818

19+
#[test]
20+
fn replicate_overflow_for_start_take_sell_order() {
21+
TestExternalitiesBuilder::new().initialize_all().build().execute_with(|| {
22+
let user = new_account(3);
23+
let other_user = new_account(4);
24+
let item_id = 0;
25+
26+
Balances::make_free_balance_be(&user, 100);
27+
Balances::make_free_balance_be(&other_user, 100);
28+
29+
let args = SignUpArgs::BuyerOrSeller {
30+
cid: ShortString::try_from(b"cid".to_vec()).unwrap(),
31+
cid_creator: ShortString::try_from(b"cid_creator".to_vec()).unwrap(),
32+
group: ShortString::try_from(b"Group".to_vec()).unwrap(),
33+
};
34+
35+
assert_ok!(Afloat::sign_up(RawOrigin::Signed(user.clone()).into(), args.clone()));
36+
assert_ok!(Afloat::sign_up(RawOrigin::Signed(other_user.clone()).into(), args.clone()));
37+
38+
assert_ok!(Afloat::set_afloat_balance(
39+
RuntimeOrigin::signed(1),
40+
other_user.clone(),
41+
100000
42+
));
43+
assert_eq!(Afloat::do_get_afloat_balance(other_user.clone()).unwrap(), 100000);
44+
45+
assert_ok!(Afloat::create_tax_credit(
46+
RawOrigin::Signed(user.clone()).into(),
47+
dummy_description(),
48+
None,
49+
None,
50+
));
51+
52+
assert_ok!(Afloat::create_offer(
53+
RawOrigin::Signed(user.clone()).into(),
54+
CreateOfferArgs::Sell {
55+
tax_credit_id: item_id,
56+
price_per_credit: 18446744073709551615,
57+
tax_credit_amount: 10,
58+
expiration_date: 1000
59+
}
60+
));
61+
62+
let (offer_id, _offer) = AfloatOffers::<Test>::iter().next().unwrap().clone();
63+
assert_noop!(
64+
Afloat::start_take_sell_order(
65+
RawOrigin::Signed(other_user.clone()).into(),
66+
offer_id,
67+
10
68+
),
69+
Error::<Test>::ArithmeticOverflow
70+
);
71+
});
72+
}
73+
1974
#[test]
2075
fn sign_up_works() {
2176
TestExternalitiesBuilder::new().initialize_all().build().execute_with(|| {

pallets/fund-admin/src/functions.rs

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2008,8 +2008,8 @@ impl<T: Config> Pallet<T> {
20082008

20092009
// Create revenue transaction id
20102010
let revenue_transaction_id =
2011-
(revenue_id, job_eligible_id, project_id, timestamp).using_encoded(blake2_256);
2012-
2011+
(revenue_id, revenue_amount, job_eligible_id, project_id, timestamp)
2012+
.using_encoded(blake2_256);
20132013
// Ensure revenue transaction id doesn't exist
20142014
ensure!(
20152015
!RevenueTransactionsInfo::<T>::contains_key(revenue_transaction_id),
@@ -2031,11 +2031,6 @@ impl<T: Config> Pallet<T> {
20312031
};
20322032

20332033
// Insert revenue transaction data into RevenueTransactionsInfo
2034-
// Ensure revenue transaction id doesn't exist
2035-
ensure!(
2036-
!RevenueTransactionsInfo::<T>::contains_key(revenue_transaction_id),
2037-
Error::<T>::RevenueTransactionIdAlreadyExists
2038-
);
20392034
<RevenueTransactionsInfo<T>>::insert(revenue_transaction_id, revenue_transaction_data);
20402035

20412036
// Insert revenue transaction id into TransactionsByRevenue
@@ -3125,6 +3120,14 @@ impl<T: Config> Pallet<T> {
31253120
Ok(())
31263121
}
31273122

3123+
fn safe_add(
3124+
a: u64,
3125+
b: u64
3126+
) -> Result<u64, DispatchError> {
3127+
let result = a.checked_add(b).ok_or_else(|| Error::<T>::ArithmeticOverflow)?;
3128+
Ok(result)
3129+
}
3130+
31283131
fn do_calculate_drawdown_total_amount(
31293132
project_id: [u8; 32],
31303133
drawdown_id: [u8; 32],
@@ -3147,7 +3150,7 @@ impl<T: Config> Pallet<T> {
31473150
.ok_or(Error::<T>::TransactionNotFound)?;
31483151

31493152
// Add transaction amount to drawdown total amount
3150-
drawdown_total_amount += transaction_data.amount;
3153+
drawdown_total_amount = Self::safe_add(drawdown_total_amount, transaction_data.amount)?;
31513154
}
31523155
}
31533156

@@ -3299,7 +3302,7 @@ impl<T: Config> Pallet<T> {
32993302
.ok_or(Error::<T>::RevenueTransactionNotFound)?;
33003303

33013304
// Add revenue transaction amount to revenue total amount
3302-
revenue_total_amount += revenue_transaction_data.amount;
3305+
revenue_total_amount = Self::safe_add(revenue_total_amount, revenue_transaction_data.amount)?;
33033306
}
33043307
}
33053308

pallets/fund-admin/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -694,6 +694,8 @@ pub mod pallet {
694694
AdminHasNoFreeBalance,
695695
/// Administrator account has insuficiente balance to register a new user
696696
InsufficientFundsToTransfer,
697+
// Arithmetic addition overflow
698+
ArithmeticOverflow,
697699
}
698700

699701
// E X T R I N S I C S

pallets/fund-admin/src/mock.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ impl pallet_rbac::Config for Test {
152152
type MaxRolesPerUser = MaxRolesPerUser;
153153
type MaxUsersPerRole = MaxUsersPerRole;
154154
type RemoveOrigin = EnsureRoot<Self::AccountId>;
155+
type WeightInfo = ();
155156
}
156157

157158
// Build genesis storage according to the mock runtime.

pallets/fund-admin/src/tests.rs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4174,6 +4174,50 @@ fn drawdowns_a_user_submits_a_drawdown_for_approval_works() {
41744174
});
41754175
}
41764176

4177+
#[test]
4178+
fn replicate_overflow_for_a_drawdown_submission() {
4179+
new_test_ext().execute_with(|| {
4180+
assert_ok!(make_default_full_project());
4181+
let project_id = ProjectsInfo::<Test>::iter_keys().next().unwrap();
4182+
4183+
let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1);
4184+
let expenditure_id = get_budget_expenditure_id(
4185+
project_id,
4186+
make_field_name("Expenditure Test 1"),
4187+
ExpenditureType::HardCost,
4188+
);
4189+
4190+
let transaction_data =
4191+
make_transaction(Some(expenditure_id), Some(1000), CUDAction::Create, None);
4192+
4193+
assert_ok!(FundAdmin::submit_drawdown(
4194+
RuntimeOrigin::signed(2),
4195+
project_id,
4196+
drawdown_id,
4197+
Some(transaction_data),
4198+
false,
4199+
));
4200+
4201+
let second_transaction_data = make_transaction(
4202+
Some(expenditure_id),
4203+
Some(18446744073709551615),
4204+
CUDAction::Create,
4205+
None,
4206+
);
4207+
4208+
assert_noop!(
4209+
FundAdmin::submit_drawdown(
4210+
RuntimeOrigin::signed(2),
4211+
project_id,
4212+
drawdown_id,
4213+
Some(second_transaction_data),
4214+
true,
4215+
),
4216+
Error::<Test>::ArithmeticOverflow
4217+
);
4218+
});
4219+
}
4220+
41774221
#[test]
41784222
fn drawdowns_a_user_submits_a_draft_drawdown_for_approval_works() {
41794223
new_test_ext().execute_with(|| {
@@ -5790,6 +5834,46 @@ fn revenues_after_a_revenue_is_submitted_the_next_one_is_generated_automaticaly_
57905834
});
57915835
}
57925836

5837+
#[test]
5838+
fn replicate_overflow_for_a_revenue_submission() {
5839+
new_test_ext().execute_with(|| {
5840+
assert_ok!(make_default_full_project());
5841+
let project_id = ProjectsInfo::<Test>::iter_keys().next().unwrap();
5842+
5843+
let revenue_id = get_revenue_id(project_id, 1);
5844+
let job_eligible_id = get_job_eligible_id(project_id, make_field_name("Job Eligible Test"));
5845+
5846+
let revenue_transaction_data =
5847+
make_revenue_transaction(Some(job_eligible_id), Some(10000), CUDAction::Create, None);
5848+
5849+
assert_ok!(FundAdmin::submit_revenue(
5850+
RuntimeOrigin::signed(2),
5851+
project_id,
5852+
revenue_id,
5853+
Some(revenue_transaction_data),
5854+
false,
5855+
));
5856+
5857+
let second_revenue_transaction_data = make_revenue_transaction(
5858+
Some(job_eligible_id),
5859+
Some(18446744073709551615),
5860+
CUDAction::Create,
5861+
None,
5862+
);
5863+
5864+
assert_noop!(
5865+
FundAdmin::submit_revenue(
5866+
RuntimeOrigin::signed(2),
5867+
project_id,
5868+
revenue_id,
5869+
Some(second_revenue_transaction_data),
5870+
true,
5871+
),
5872+
Error::<Test>::ArithmeticOverflow
5873+
);
5874+
});
5875+
}
5876+
57935877
#[test]
57945878
fn revenues_an_administrator_rejects_a_given_revenue_works() {
57955879
new_test_ext().execute_with(|| {

0 commit comments

Comments
 (0)