Skip to content

Conversation

bertllll
Copy link
Contributor

No description provided.

Copy link

cursor bot commented Sep 25, 2025

You have run out of free Bugbot PR reviews for this billing cycle. This will reset on October 14.

To receive reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

};
let transaction_fee_margin = PurePercentage::try_from(15).unwrap();
transaction_fee_margin.add_percent_to(gas_limit_dev_chain * gas_price_major)
transaction_fee_margin.increase_by_percent_for(gas_limit_dev_chain * gas_price_major)
Copy link
Collaborator

Choose a reason for hiding this comment

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

You don't need to keep the word _for in the functions's name. Please eliminate it.

};
let analyzed_accounts =
convert_qualified_into_analyzed_payables_in_test(unadjusted_qualified_accounts.clone());
convert_qualified_p_into_analyzed_p(unadjusted_qualified_accounts.clone());
Copy link
Collaborator

Choose a reason for hiding this comment

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

This shorthand is not helping here, maybe even introducing jargon. I'd suggest if you use the name convert_qualified_payable_into_analyzed_payable, or if you want shorter than qualified_to_analyzed_payable.

"ERROR: {test_name}: Payable scanner is blocked from preparing instructions for payments. \
The cause appears to be in competence of the user."
"ERROR: {test_name}: Payable scanner is unable to generate payment instructions. \
It looks like only the user can resolve this issue."
Copy link
Collaborator

@utkarshg6 utkarshg6 Sep 29, 2025

Choose a reason for hiding this comment

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

Instead of saying, "It looks like...", we can just direct the user by saying, "The user will have to resolve the issue manually."

use crate::accountant::payment_adjuster::miscellaneous::data_structures::UnconfirmedAdjustment;
use crate::accountant::payment_adjuster::test_utils::local_utils::{
make_meaningless_weighed_account, make_non_guaranteed_unconfirmed_adjustment,
make_meaningless_unconfirmed_adjustment, make_meaningless_weighed_account,
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think the word, meaningless is unnecessary, unless you have an alternate fn which says, meaningful, the prefix word make is enough to signify that it's meant for the test, and the developer should consider that it's fields were generated for testing purposes.

I mean this for both make_meaningless_unconfirmed_adjustment and make_meaningless_weighed_account.

.iter()
.filter(|wallet| wallet.address() == wallet_3)
.collect_vec();
assert_eq!(wallets_same_as_wallet_3.len(), 1);
Copy link
Collaborator

Choose a reason for hiding this comment

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

I've no idea why you decided to remove this... I thought you prefer stronger tests.

use web3::types::Address;

pub fn thriving_competitor_found_diagnostics(
pub fn diagnostics_for_accounts_above_disqualification_limit(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Does it mean that these accounts are disqualified or qualified? I'd suggest you either add it as a comment, or possibly rename the fn to make it clear.


pub enum AccountsByFinalization {
Unexhausted(Vec<AdjustedAccountBeforeFinalization>),
Finalized(Vec<PayableAccount>),
Copy link
Collaborator

Choose a reason for hiding this comment

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

What does these enum variants? Their name doesn't appear to be mutually exclusive. For example, if you're using Unexhausted, then shouldn't the other be Exhausted? Similarly, if it's Finalized, then shouldn't the other variant be Incomplete?

Since they aren't mutually exclusive, I find them very confusing.

|tx_fee_check_err_opt: Option<TransactionFeeImmoderateInsufficiency>,
service_fee_check_err_opt: Option<ServiceFeeImmoderateInsufficiency>| {
PaymentAdjusterError::AbsolutelyInsufficientBalance {
PaymentAdjusterError::AbsoluteFeeInsufficiency {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'd prefer the older name. That's clearer.

let number_of_accounts = current_set_of_accounts.len();
PaymentAdjusterError::AbsolutelyInsufficientServiceFeeBalancePostTxFeeAdjustment {
original_number_of_accounts: self.original_number_of_accounts,
PaymentAdjusterError::AbsoluteFeeInsufficiency {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can't it be simply "InsufficientBalance"?

)
.unwrap();
assert_eq!(adjustment_is_needed_actual, *adjustment_is_needed_expected);
});
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nicely done! 👏

assert_eq!(
result,
LateServiceFeeSingleTxErrorFactory {
LaterServiceFeeErrorFactory {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Shouldn't this be LateServiceFeeErrorFactory instead? I'm implying to use the word Late instead of Later.

below_disq_limit.push(current)
}
(above_or_even_to_disq_limit, below_disq_limit)
};
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think this could be further refactored like this:

let fold_guts = |(mut above_limit, mut below_limit): (Vec<_>, Vec<_>), current: UnconfirmedAdjustment| {
    let disqualification_limit = current.disqualification_limit_minor();
    
    if current.proposed_adjusted_balance_minor >= disqualification_limit {
        diagnostics_for_accounts_above_disqualification_limit(&current, disqualification_limit);
        
        let adjusted = UnconfirmedAdjustment {
            proposed_adjusted_balance_minor: disqualification_limit,
            ..current
        };
        above_limit.push(adjusted);
    } else {
        below_limit.push(current);
    }
    
    (above_limit, below_limit)
};

"Balance wei: {}\n\
Threshold: {}",
payable.balance_wei, threshold
);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Remove this...

Copy link
Collaborator

@utkarshg6 utkarshg6 left a comment

Choose a reason for hiding this comment

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

Continue with node/src/accountant/payment_adjuster/non_unit_tests/mod.rs

// missing. This is what the numbers like 99% and 90% illustrate. That said, the letter account
// comes across as it should take precedence for its expectedly larger weight and gain at the
// expanse of the other, but the percents speak otherwise. Yet it's correct. The interpretation
// is the key. (Caution: this test displays its output with those accounts sorted).
Copy link
Collaborator

@utkarshg6 utkarshg6 Oct 1, 2025

Choose a reason for hiding this comment

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

Bert, this explanation is not clear and concise.

Here's an output from ChatGPT that I found easy to understand. It took me few iterations to get the right output. I request you to pick an inspiration from this text and add it accordingly.

// This is a fuzz test for the PaymentAdjuster. It generates a large number of different 
// scenarios to see how well the PaymentAdjuster can handle various situations that might 
// occur in real life. The goal is to ensure it works reliably without causing problems in Node.

// The test has an exo-parameter, which is the number of scenarios to generate. This number 
// is flexible because many scenarios will be discarded during setup if they don’t meet 
// certain requirements, like whether the accounts involved are valid for payment adjustments.

// Some scenarios will be thrown out early if the PaymentAdjuster finds mistakes, ensuring 
// that only valid scenarios are tested. This helps identify corner cases that may not have 
// been considered during the design phase.

// When the test is finished, the results are saved in a file located at:
// node/generated/test/payment_adjuster/tests/home/loading_test_output.txt. This file 
// contains important numbers and statistics that show how well the PaymentAdjuster performed.

// The output may include confusing results, especially regarding percentages that indicate 
// how much of a debt is covered. These percentages might not match the account balances, 
// which can be tricky to understand. The adjustment depends on a concept called the 
// "disqualification limit," which affects how much of the total amount is required for 
// payment. This can lead to discrepancies in reported percentages.

// It’s important to interpret the results carefully. The way payments are adjusted can 
// depend on these limits, which may cause differences in the percentages reported for 
// different accounts.

)
})
.collect();
let (test_overall_output_collector, scenario_adjustment_results) =
Copy link
Collaborator

Choose a reason for hiding this comment

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

Let's just call these arguments as output_collector and results.


(t_o_c_i_f, scenario_results)
},
);
Copy link
Collaborator

@utkarshg6 utkarshg6 Oct 1, 2025

Choose a reason for hiding this comment

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

Please use this refactored code:

    let output_collector_init = first_stage_output.output_collector;
    let (output_collector, results) = second_stage_scenarios.into_iter().fold(
        (output_collector_init, vec![]),
        |(collector, mut results), scenario| {
            let adjustment = scenario.prepared_adjustment;

            let account_info =
                preserve_account_infos(&adjustment.adjustment_analysis.accounts, now);
            let adjustment_type = adjustment.adjustment_analysis.adjustment.clone();
            let wallet_balance = adjustment.agent.service_fee_balance_minor();

            let result = subject.adjust_payments(adjustment);

            let (updated_collector, single_result) = administrate_single_scenario_result(
                collector,
                result,
                account_info,
                scenario.applied_thresholds,
                adjustment_type,
                wallet_balance,
            );

            results.push(single_result);
            (updated_collector, results)
        },
    );

    render_results_to_file_and_attempt_basic_assertions(
        results,
        number_of_requested_scenarios,
        output_collector,
    )

To reduce the number of arguments passed in the fn administrate_single_scenario_result you may use this new structure.

    
// Create a struct to group scenario data
struct ScenarioData {
    applied_thresholds: AppliedThresholds,
    adjustment_type: Adjustment,
    wallet_balance: u128,
    account_info: Vec<AccountInfo>,
}

// Then your function call becomes:
let scenario_data = ScenarioData {
    applied_thresholds: scenario.applied_thresholds,
    adjustment_type,
    wallet_balance,
    account_info,
};

let (updated_collector, single_result) = administrate_single_scenario_result(
    collector,
    result,
    scenario_data,
);

let mut generate_u128 = || generate_non_zero_usize(gn, 100) as u128;
// This seems overcomplicated, damn. Yet it's a result of good simple intentions. I wanted
// to ensure the occurrence of accounts with balances of different magnitudes to be generated
// for a single scenario. This was crucial to me so much that I didn't stop myself from writing
Copy link
Collaborator

Choose a reason for hiding this comment

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

The second sentence will be clearer if you write it like this:

I wanted to make sure that accounts with balances of different sizes are generated for a single scenario.


// The remaining assets are later allocated to the accounts based on the order by their weights,
// exhausting them fully one account after another up their 100% allocation until there is any
// money that can be distributed.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Few grammatical changes:

based on the order by their weights -> based on the order of their weights
up their 100% allocation -> up to their 100% allocation
until there is any money that can be distributed -> until there is no money left to be distributed

Either<Vec<AdjustedAccountBeforeFinalization>, Vec<PayableAccount>>,
PaymentAdjusterError,
> {
) -> Result<AccountsByFinalization, PaymentAdjusterError> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

That's good!

Ok(AccountsByFinalization::Finalized(
accounts_not_needing_adjustment,
))
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Optional (If you're too excited): I think this fn is too long, maybe refactor the if-else block to an outboard method.

// number_of_accounts: usize,
// original_total_service_fee_required_minor: u128,
// cw_service_fee_balance_minor: u128,
// },
Copy link
Collaborator

Choose a reason for hiding this comment

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

Remove these commented out lines.

// We haven't needed to worry about this matter, yet this is rather a future alarm that
// will draw attention after somebody adds a possibility for an error not necessarily
// implying that an insolvency was detected before. At the moment, each error occurs
// only alongside an actual insolvency. (Hint: There might be consequences for
Copy link
Collaborator

@utkarshg6 utkarshg6 Oct 3, 2025

Choose a reason for hiding this comment

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

Some rephrasing for clarity:

"We haven't needed to worry about this matter" -> "We have not had to worry about this issue."

"yet this is rather a future alarm" should be "but this is rather a future alarm"

"whose forming takes place back out" should be "whose formation takes place outside."

.into_iter()
.for_each(|(error, expected_msg)| assert_eq!(error.to_string(), expected_msg));
assert_eq!(inputs_count, PaymentAdjusterError::VARIANT_COUNT + 2)
assert_eq!(inputs_count, PaymentAdjusterError::VARIANT_COUNT + 3)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why did you increase it by one?

.collect::<Vec<_>>();
assert_eq!(results, vec![true, true, true, true, true]);
assert_eq!(inputs_count, PaymentAdjusterError::VARIANT_COUNT + 2)
assert_eq!(inputs_count, PaymentAdjusterError::VARIANT_COUNT + 3)
Copy link
Collaborator

Choose a reason for hiding this comment

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

here too

Copy link
Collaborator

@utkarshg6 utkarshg6 left a comment

Choose a reason for hiding this comment

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

Continue with masq_lib/src/percentage.rs

fn __derive_rounding_increment(remainder: N) -> N {
let is_negative = remainder < N::try_from(0).expect("Each integer has 0");
let is_minor =
Self::__abs(remainder, is_negative) < N::try_from(50).expect("Each integer has 50");
Copy link
Collaborator

Choose a reason for hiding this comment

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

This part is hard to understand, why's that? Specifically the number 50, it's origin.

let percents_of_base = base * percents;
let (percents_of_remainder, nearly_lost_tail) =
base_and_rem_for_ensured_i16(remainder, percents);
let final_rounding_element = Self::__derive_rounding_increment(nearly_lost_tail);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Let's call it rounding_adjustment instead.

{
let hundred = N::try_from(100i8).expect("Each integer has 100");
let modulo = num % hundred;
(num / hundred, modulo)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm noticing a pattern here, maybe it's time to create a new structure for this, what about QuotientAndRemainder?

// This is a wider type that allows to specify cumulative percents of more than only 100.
// The expected use of this would look like requesting percents meaning possibly multiples of 100%,
// roughly, of a certain base number. Similarly to the PurePercentage type, also signed numbers
// would be accepted.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Some grammatical corrections:

"cumulative percents of more than only 100" -> "cumulative percents greater than 100"

"requesting percents meaning possibly multiples of 100%" -> "requesting percents that may represent multiples of 100%"

"Similarly to the PurePercentage type, also signed numbers would be accepted." -> "Similar to the PurePercentage type, signed numbers would also be accepted."

Copy link
Collaborator

@utkarshg6 utkarshg6 left a comment

Choose a reason for hiding this comment

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

Continue at masq_lib/src/percentage.rs:232

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LoosePercentage {
multiplier_of_100_percent: u32,
degrees_from_remainder: PurePercentage,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Instead of calling multiplier_of_100_percent you can simply call it quotient and degrees_from_remainder to remainder.

let multiples_of_100_percent = percents / 100;
let remainder = (percents % 100) as u8;
let degrees_from_remainder =
PurePercentage::try_from(remainder).expect("Should never happen.");
Copy link
Collaborator

Choose a reason for hiding this comment

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

You may just call these values for clearer expression:

multiples_of_100_percent -> quotient
remainder -> remainder_value
degrees_from_remainder -> remainder

<N as TryFrom<i8>>::Error: Debug,
<N as TryFrom<u32>>::Error: Debug,
i16: TryFrom<N>,
<i16 as TryFrom<N>>::Error: Debug,
Copy link
Collaborator

@utkarshg6 utkarshg6 Oct 6, 2025

Choose a reason for hiding this comment

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

Over here, please reorder them such that the error types are together, and the core and type conversions are separate, even separating with the blank lines are sufficient or you can add comments and do something like this:

        // Core
        N: PercentageInteger + TryFrom<u32>,
        // Error
        <N as TryFrom<i8>>::Error: Debug,
        <N as TryFrom<u32>>::Error: Debug,
        <i16 as TryFrom<N>>::Error: Debug,
        // Reverse Conversion
        i16: TryFrom<N>,

Apparently, you could create a trait:

pub trait ExtendedPercentageInteger: 
    PercentageInteger + TryFrom<u32>
where
        // Error
        <Self as TryFrom<i8>>::Error: Debug,
        <Self as TryFrom<u32>>::Error: Debug,
        <i16 as TryFrom<Self>>::Error: Debug,
        // Reverse Conversion
        i16: TryFrom<Self>,
{
}


let remainder = self.degrees_from_remainder.of(num);

match wholes.checked_add(&remainder) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Optional: You may save the result in the variable and then return that variable from this fn.

// Note that functions like 'add_percents_to' or 'subtract_percents_from' don't need to be
// implemented here, even though they are at the 'PurePercentage'. You can substitute them
// simply by querying 100 + <your desired addition in percents> or 100 - <your desired
// subtraction in percents in the interval (1..=99) >
Copy link
Collaborator

Choose a reason for hiding this comment

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

Instead of the second sentence, you can directly provide usage examples:

    // - To increase by X%: use `LoosePercentage::new(100 + X)`
    // - To decrease by X%: use `LoosePercentage::new(100 - X)` where X is in range 1..=99

Copy link
Collaborator

@utkarshg6 utkarshg6 left a comment

Choose a reason for hiding this comment

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

Continue at masq_lib/src/percentage.rs:345


let trivially_calculated_half = num / N::try_from(2).unwrap();
// Widening the bounds to compensate the extra rounding
let one = N::try_from(1).unwrap();
Copy link
Collaborator

Choose a reason for hiding this comment

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

Let's call this variable rounding_tolerance.

{
assert_against_literal_value(subject, num, expected_literal_num);

let trivially_calculated_half = num / N::try_from(2).unwrap();
Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe just call it: half_of_input 🤷

let half = num / N::try_from(2).unwrap();
assert_against_literal_value(subject, num, expected_literal_num);

let trivially_calculated_half = num / N::try_from(2).unwrap();
Copy link
Collaborator

Choose a reason for hiding this comment

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

Similarly, you can do the same here.

(51, 1),
(5, 0),
(99,1),
(0,0)
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is simple, yet so effective in explaining what's going on... nicely done @bertllll.

pub type StimulateConsumingNodePayments = fn(&mut MASQNodeCluster, &MASQRealNode, &WholesomeConfig);

pub type StartServingNodesAndLetThemPerformReceivablesCheck =
pub type StartServingNodesAndLetThemActivateTheirAccountancy =
Copy link
Collaborator

@utkarshg6 utkarshg6 Oct 6, 2025

Choose a reason for hiding this comment

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

This name is too long, prefer this ActivateServingNodesWithAccountancy or maybe NodeActivationHandler. Please change this name, for god's sake, that's toooooooooooo long.

stimulate_consuming_node_to_pay: StimulateConsumingNodePayments,
start_serving_nodes_and_activate_their_accountancy: StartServingNodesAndLetThemPerformReceivablesCheck,
stimulate_consuming_node_payments: StimulateConsumingNodePayments,
start_serving_nodes_and_let_them_activate_their_accountancy: StartServingNodesAndLetThemActivateTheirAccountancy,
Copy link
Collaborator

Choose a reason for hiding this comment

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

You can call this argument name as serving_nodes_activator.

wholesome_config.assert_payments_via_direct_blockchain_scanning(&assertions_values);

let _ = start_serving_nodes_and_activate_their_accountancy(
let _ = start_serving_nodes_and_let_them_activate_their_accountancy(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please do the same here

Copy link
Collaborator

@utkarshg6 utkarshg6 left a comment

Choose a reason for hiding this comment

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

Continue at multinode_integration_tests/tests/verify_bill_payment_utils/utils.rs:282

.eth()
.transaction_receipt(tx_hash)
.wait()
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Code in this file is well written. 👏


pub fn establish_test_frame(
test_inputs: TestInputs,
test_inputs: TestInput,
Copy link
Collaborator

Choose a reason for hiding this comment

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

You can make the arg name singular too for consistency.

Copy link
Collaborator

@utkarshg6 utkarshg6 left a comment

Choose a reason for hiding this comment

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

Finished! 👏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants