Skip to content

Fix: Logic checks ethBalance < BigInt(fee) but ignores gas costs.#124

Open
aniket866 wants to merge 3 commits intoStabilityNexus:mainfrom
aniket866:fixing-checkBalance
Open

Fix: Logic checks ethBalance < BigInt(fee) but ignores gas costs.#124
aniket866 wants to merge 3 commits intoStabilityNexus:mainfrom
aniket866:fixing-checkBalance

Conversation

@aniket866
Copy link

@aniket866 aniket866 commented Feb 18, 2026

Addressed Issues:

#123

Checklist

  • My PR addresses a single issue, fixes a single bug or makes a single improvement.
  • My code follows the project's code style and conventions.
  • If applicable, I have made corresponding changes or additions to the documentation.
  • If applicable, I have made corresponding changes or additions to tests.
  • My changes generate no new warnings or errors.
  • I have joined the Stability Nexus's Discord server and I will share a link to this PR with the project maintainers there.
  • I have read the Contribution Guidelines.
  • Once I submit my PR, CodeRabbit AI will automatically review it and I will address CodeRabbit's comments.

⚠️ AI Notice - Important!

We encourage contributors to use AI tools responsibly when creating Pull Requests. While AI can be a valuable aid, it is essential to ensure that your contributions meet the task requirements, build successfully, include relevant tests, and pass all linters. Submissions that do not meet these standards may be closed without warning to maintain the quality and integrity of the project. Please take the time to understand the changes you are proposing and their impact.

Summary by CodeRabbit

  • New Features

    • Invoice payment flows now include automatic gas cost estimation to prevent failed transactions.
  • Improvements

    • Balance validation now comprehensively checks available funds for both transaction fees and gas costs before payment processing.
    • Enhanced support for validating balances across both native ETH and ERC20 token payments.
    • Updated error messages to clarify when insufficient funds exist for fees and gas costs.

@coderabbitai
Copy link

coderabbitai bot commented Feb 18, 2026

📝 Walkthrough

Walkthrough

This PR adds comprehensive test coverage for batch invoice operations and fee withdrawal functionality while enhancing the frontend payment flow with gas cost estimation and ERC20 token balance validation before payment processing.

Changes

Cohort / File(s) Summary
Test Suite Expansion
contracts/test/Chainvoice.t.sol
Added 5 new test functions covering batch invoice operations (batch creation and payment), batch size validation, fuzz testing for invoice creation, and fee withdrawal. Increased initial Ether balances for test accounts from 10 to 100. Updated license header format.
Frontend Payment Flow Enhancement
frontend/src/page/ReceivedInvoice.jsx
Integrated gas estimation and gas cost calculations into balance validation logic. Added ERC20 token balance verification with decimals calculation. Updated error messages to reflect gas-inclusive ETH requirements for both native and token payment paths.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • adityabhattad2021

Poem

🐰 Hop along the test paths bright,
With batches stacked and gas in sight,
Tokens checked, fees withdrawn true,
Invoices dance in batches new! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title directly and specifically addresses the main change: adding gas cost considerations to the balance check logic that previously only checked fees.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
frontend/src/page/ReceivedInvoice.jsx (2)

428-428: ⚠️ Potential issue | 🟡 Minor

Use toast.info instead of toast.success for an intermediate step

toast.success at line 428 implies the payment succeeded. At this point only the pre-check passed; the transaction hasn't been submitted. A subsequent approval or on-chain failure will still surface an error, creating a confusing "success → error" sequence for the user.

✏️ Proposed fix
- toast.success("Balance check passed! Processing payment...");
+ toast.info("Balance check passed. Processing payment...");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/page/ReceivedInvoice.jsx` at line 428, The toast shown after the
balance pre-check in ReceivedInvoice.jsx currently uses toast.success and
misleads users into thinking payment completed; change the call to toast.info
(or another neutral status like toast.pending) in the function where
toast.success("Balance check passed! Processing payment...") is invoked so it
communicates an intermediate state (e.g., "Balance check passed — processing
payment...") instead of a final success; update any related tests/messages
expecting the success toast accordingly.

238-296: ⚠️ Potential issue | 🟠 Major

checkBalance uses a fixed single-invoice fee for batch payments — balance check will underestimate required ETH

BigInt(fee) (the component state, a single-invoice fee) is used at both line 259 (ETH path) and line 285 (ERC20 ETH path). However, the actual batch transaction at line 577 sends feePerInvoice * BigInt(invoiceIds.length). For a batch of N invoices, the check only requires 1×fee worth of ETH, while the transaction requires N×fee. The check can pass and the on-chain transaction will then fail for any batch with N > 1 when the user holds exactly the minimum checked amount.

checkBalance also captures fee via closure over the component state instead of the locally-fetched fee from payInvoice (line 421's const fee = await contract.fee() shadows the state but does not reach the closure). If the protocol fee changes between page load and payment, the pre-check and the actual value: argument diverge.

🐛 Proposed fix: add an `invoiceCount` parameter and pass it from the batch caller
- const checkBalance = async (tokenAddress, amount, symbol, signer) => {
+ const checkBalance = async (tokenAddress, amount, symbol, signer, invoiceCount = 1) => {
    const userAddress = await signer.getAddress();
    const provider = signer.provider;

    const feeData = await provider.getFeeData();
    const gasPrice = feeData.gasPrice || feeData.maxFeePerGas;

    if (!gasPrice) {
      throw new Error("Unable to fetch gas price");
    }

    const estimatedGasLimit = BigInt(300000);
    const estimatedGasCost = gasPrice * estimatedGasLimit;

    if (tokenAddress === ethers.ZeroAddress) {
      const balance = await provider.getBalance(userAddress);
      const invoiceAmount = ethers.parseUnits(amount.toString(), 18);
      const totalRequired =
-       invoiceAmount + BigInt(fee) + estimatedGasCost;
+       invoiceAmount + BigInt(fee) * BigInt(invoiceCount) + estimatedGasCost;
      // ...
    } else {
      // ...
-     const totalEthRequired = BigInt(fee) + estimatedGasCost;
+     const totalEthRequired = BigInt(fee) * BigInt(invoiceCount) + estimatedGasCost;
      // ...
    }
  };

And in handleBatchPayment, pass the count:

  await checkBalance(
    group.tokenAddress,
    group.totalAmount,
    group.symbol,
    signer,
+   group.invoices.length
  );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/page/ReceivedInvoice.jsx` around lines 238 - 296, The balance
check under checkBalance currently uses the component state fee and assumes a
single invoice; update checkBalance to accept an invoiceCount (and/or
feePerInvoice) parameter and use the actual fee used by the on‑chain call
(multiply feePerInvoice * BigInt(invoiceCount)) when computing
totalRequired/totalEthRequired in both the ETH branch (tokenAddress ===
ethers.ZeroAddress) and the ERC20 branch, and have the batch caller
(handleBatchPayment) pass the invoice count and the locally-fetched fee (the fee
read in payInvoice/handleBatchPayment) into checkBalance so the pre-check
matches the actual value: argument names to look for are checkBalance,
handleBatchPayment, and the local fee fetch (const fee = await contract.fee()).
🧹 Nitpick comments (1)
frontend/src/page/ReceivedInvoice.jsx (1)

250-252: Hardcoded 300,000 gas limit doesn't scale with batch size

estimatedGasLimit = BigInt(300000) is a constant regardless of how many invoices are in the batch. For a batch of 50 invoices, the actual gas cost is orders of magnitude higher. Consider accepting an invoiceCount parameter (consistent with the fee fix above) and scaling the gas buffer proportionally, or using provider.estimateGas against the actual call data for a more accurate bound.

♻️ Suggested approach
- const estimatedGasLimit = BigInt(300000); // adjust if needed
+ // Scale gas estimate proportionally with batch size (rough buffer per invoice)
+ const gasPerInvoice = BigInt(150000);
+ const estimatedGasLimit = gasPerInvoice * BigInt(invoiceCount);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/page/ReceivedInvoice.jsx` around lines 250 - 252, The hardcoded
gas buffer in ReceivedInvoice.jsx uses estimatedGasLimit = BigInt(300000) which
doesn't scale with batch size; update the logic in the area that computes
estimatedGasLimit/estimatedGasCost to accept an invoiceCount parameter (or
derive count from the batch) and scale the gas limit proportionally (e.g.,
per-invoice gas * invoiceCount), or replace the constant by calling
provider.estimateGas with the actual transaction call data to compute a more
accurate bound; adjust uses of estimatedGasLimit and estimatedGasCost
accordingly so downstream code (the send/fee calculation path) uses the
scaled/estimated value.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@contracts/test/Chainvoice.t.sol`:
- Line 1: Replace the block-style SPDX header comment with the canonical
line-comment form so downstream tools recognize it: change the current "/*
SPDX-License-Identifier: Unlicense */" in contracts/test/Chainvoice.t.sol to a
single-line comment "// SPDX-License-Identifier: Unlicense" as the very first
line of the file.
- Around line 200-213: The contract is inconsistent: createInvoicesBatch
enforces non-zero amounts but createInvoice does not; either add the same
validation in the contract or make the test defensive. Fix option A: in the
createInvoice function add a require(amount > 0, "Amount zero") (mirror the
check used in createInvoicesBatch) so createInvoice and createInvoicesBatch
behave consistently. Fix option B: if you prefer to allow zero amounts, update
the fuzz test testFuzz_CreateInvoice to assume(amount > 0) (replace or add
vm.assume(amount > 0)) so the test only supplies valid inputs. Ensure you modify
the createInvoice function name or test function name referenced above
accordingly.

---

Outside diff comments:
In `@frontend/src/page/ReceivedInvoice.jsx`:
- Line 428: The toast shown after the balance pre-check in ReceivedInvoice.jsx
currently uses toast.success and misleads users into thinking payment completed;
change the call to toast.info (or another neutral status like toast.pending) in
the function where toast.success("Balance check passed! Processing payment...")
is invoked so it communicates an intermediate state (e.g., "Balance check passed
— processing payment...") instead of a final success; update any related
tests/messages expecting the success toast accordingly.
- Around line 238-296: The balance check under checkBalance currently uses the
component state fee and assumes a single invoice; update checkBalance to accept
an invoiceCount (and/or feePerInvoice) parameter and use the actual fee used by
the on‑chain call (multiply feePerInvoice * BigInt(invoiceCount)) when computing
totalRequired/totalEthRequired in both the ETH branch (tokenAddress ===
ethers.ZeroAddress) and the ERC20 branch, and have the batch caller
(handleBatchPayment) pass the invoice count and the locally-fetched fee (the fee
read in payInvoice/handleBatchPayment) into checkBalance so the pre-check
matches the actual value: argument names to look for are checkBalance,
handleBatchPayment, and the local fee fetch (const fee = await contract.fee()).

---

Nitpick comments:
In `@frontend/src/page/ReceivedInvoice.jsx`:
- Around line 250-252: The hardcoded gas buffer in ReceivedInvoice.jsx uses
estimatedGasLimit = BigInt(300000) which doesn't scale with batch size; update
the logic in the area that computes estimatedGasLimit/estimatedGasCost to accept
an invoiceCount parameter (or derive count from the batch) and scale the gas
limit proportionally (e.g., per-invoice gas * invoiceCount), or replace the
constant by calling provider.estimateGas with the actual transaction call data
to compute a more accurate bound; adjust uses of estimatedGasLimit and
estimatedGasCost accordingly so downstream code (the send/fee calculation path)
uses the scaled/estimated value.

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.

1 participant