Skip to content

Commit e8244e8

Browse files
committed
- Added a safe addition helper function, safe_add, to the Fund Admin Pallet to mitigate risks from arithmetic overflow. This function checks if the addition of two numbers results in an overflow and, if so, throws the ArithmeticOverflow error. This check has been incorporated into the do_calculate_drawdown_total_amount and do_calculate_revenue_total_amount functions to ensure safe addition operation and provide user feedback.
- Defined a new `ArithmeticOverflow` error in the Fund Admin Pallet to provide feedback for operations that can potentially cause arithmetic overflow. This helps increase the robustness of our numeric operations. - Introduced two new test cases `replicate_overflow_for_a_drawdown_submission` and `replicate_overflow_for_a_revenue_submission` in the Fund Admin Pallet test suite to ensure accurate arithmetic overflow detection. These tests validate that the system correctly rejects drawdown and revenue submissions which would result in arithmetic overflow conditions. This expands the coverage of our testing and ensures the safe operation of financial transactions.
1 parent 69418f7 commit e8244e8

File tree

4 files changed

+99
-7
lines changed

4 files changed

+99
-7
lines changed

pallets/fund-admin/src/functions.rs

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2008,13 +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-
2013-
// Ensure revenue transaction id doesn't exist
2014-
ensure!(
2015-
!RevenueTransactionsInfo::<T>::contains_key(revenue_transaction_id),
2016-
Error::<T>::RevenueTransactionIdAlreadyExists
2017-
);
2011+
(revenue_id, revenue_amount, job_eligible_id, project_id, timestamp)
2012+
.using_encoded(blake2_256);
20182013

20192014
// Create revenue transaction data
20202015
let revenue_transaction_data = RevenueTransactionData {
@@ -3125,6 +3120,11 @@ impl<T: Config> Pallet<T> {
31253120
Ok(())
31263121
}
31273122

3123+
fn safe_add(a: u64, b: u64) -> DispatchResult {
3124+
a.checked_add(b).ok_or_else(|| Error::<T>::ArithmeticOverflow)?;
3125+
Ok(())
3126+
}
3127+
31283128
fn do_calculate_drawdown_total_amount(
31293129
project_id: [u8; 32],
31303130
drawdown_id: [u8; 32],
@@ -3145,6 +3145,8 @@ impl<T: Config> Pallet<T> {
31453145
// Get transaction data
31463146
let transaction_data = TransactionsInfo::<T>::get(transaction_id)
31473147
.ok_or(Error::<T>::TransactionNotFound)?;
3148+
// Check arithmetic overflow
3149+
Self::safe_add(drawdown_total_amount, transaction_data.amount)?;
31483150

31493151
// Add transaction amount to drawdown total amount
31503152
drawdown_total_amount += transaction_data.amount;
@@ -3298,6 +3300,9 @@ impl<T: Config> Pallet<T> {
32983300
RevenueTransactionsInfo::<T>::get(revenue_transaction_id)
32993301
.ok_or(Error::<T>::RevenueTransactionNotFound)?;
33003302

3303+
// Check arithmetic overflow
3304+
Self::safe_add(revenue_total_amount, revenue_transaction_data.amount)?;
3305+
33013306
// Add revenue transaction amount to revenue total amount
33023307
revenue_total_amount += revenue_transaction_data.amount;
33033308
}

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)