Skip to content

fix(smart-contracts): address stranded balance issues for ERC20#1741

Merged
yomarion merged 6 commits into
masterfrom
smart-contracts/batch-conversion-0.2-0-stranded-balance
Jun 23, 2026
Merged

fix(smart-contracts): address stranded balance issues for ERC20#1741
yomarion merged 6 commits into
masterfrom
smart-contracts/batch-conversion-0.2-0-stranded-balance

Conversation

@yomarion

@yomarion yomarion commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Description of the changes

Fixes https://github.com/RequestNetwork/private-issues/issues/23

The BatchConversionContract Refunds ERC20 tokens not based on his balance but based on what was topped up by the payer at the beginning of the transaction.

New method for the contract owner: rescueERC20Funds.

  • New method for the contract owner to drain a balance of ERC20.

@greptile-apps

greptile-apps Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a stranded-balance bug in BatchConversionPayments where pre-existing ERC20 tokens on the batch contract were incorrectly refunded to the current payer at the end of a conversion batch. It also adds a new owner-only rescueERC20Funds function for draining accidentally stranded balances.

  • Core fix (BatchConversionPayments.sol): the refund calculation now subtracts a tareBalance (snapshot of balanceOf(address(this)) taken in getUTokens before any tokens are pulled in) from the post-payment balance, ensuring only the current payer's unused tokens are returned.
  • Token struct (BatchNoConversionPayments.sol): gains a tareBalance field; getUTokens is changed from pure to view to read on-chain balances; a new rescueERC20Funds admin function lets the owner drain any stranded ERC20 balance to a recipient.
  • Tests: a dedicated stranded-balance regression test (exercised for both fee-limit and no-limit code paths) plus happy-path and access-control tests for rescueERC20Funds.

Confidence Score: 5/5

Safe to merge — the tare-balance snapshot is taken at the right point in the call sequence, the refund and batch-fee arithmetic remain correct, and the new rescue function is owner-gated with a proper success check.

The change is surgical: a single subtraction in the refund formula, a new struct field, and an admin helper. The core logic is well-tested with a dedicated regression test that covers both the fee-limit and no-limit code paths. The only gap is a missing zero-address guard on the rescue function's recipient parameter, which is a low-risk omission in an owner-only function.

packages/smart-contracts/src/contracts/BatchNoConversionPayments.sol — the rescueERC20Funds function is missing a zero-address check on recipientAddress.

Important Files Changed

Filename Overview
packages/smart-contracts/src/contracts/BatchConversionPayments.sol Core bug fix: subtracts tareBalance from balanceOf(address(this)) when computing excessAmount, so pre-existing stranded tokens are no longer refunded to the payer. Logic and ordering look correct.
packages/smart-contracts/src/contracts/BatchNoConversionPayments.sol Adds tareBalance field to Token struct and records it in getUTokens (changed from pure to view). Also adds new rescueERC20Funds admin function — missing address(0) guard on recipientAddress.
packages/smart-contracts/test/contracts/BatchConversionPayments.test.ts Adds two new test suites: a stranded-balance regression test (run for both applyLimit values) and rescueERC20Funds tests for the happy path and non-owner revert.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Payer
    participant BatchConversion
    participant ConversionProxy
    participant FeeAddress

    Note over BatchConversion: getUTokens() called<br/>tareBalance = balanceOf(this) recorded here
    Payer->>BatchConversion: transferFrom(maxToSpend)
    BatchConversion->>ConversionProxy: transferFromWithReferenceAndFee(...)
    ConversionProxy-->>BatchConversion: (spends reallySpent tokens)
    Note over BatchConversion: excessAmount = balanceOf(this) - tareBalance<br/>(stranded tokens excluded)
    BatchConversion->>Payer: safeTransfer(excessAmount)
    Payer->>FeeAddress: transferFrom(batchFee on reallySpent)
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Payer
    participant BatchConversion
    participant ConversionProxy
    participant FeeAddress

    Note over BatchConversion: getUTokens() called<br/>tareBalance = balanceOf(this) recorded here
    Payer->>BatchConversion: transferFrom(maxToSpend)
    BatchConversion->>ConversionProxy: transferFromWithReferenceAndFee(...)
    ConversionProxy-->>BatchConversion: (spends reallySpent tokens)
    Note over BatchConversion: excessAmount = balanceOf(this) - tareBalance<br/>(stranded tokens excluded)
    BatchConversion->>Payer: safeTransfer(excessAmount)
    Payer->>FeeAddress: transferFrom(batchFee on reallySpent)
Loading

Reviews (6): Last reviewed commit: "Update packages/smart-contracts/test/con..." | Re-trigger Greptile

Comment thread packages/smart-contracts/test/contracts/BatchConversionPayments.test.ts Outdated
@github-actions

Copy link
Copy Markdown

❌ Echidna Fuzzing Results

Mode: ( test sequences)
Status: Property Violations Found

Property Test Results

Status Count
✅ Passed 0
❌ Failed 0
Total 0
Pass Rate 0%

📄 Full report and corpus available in workflow artifacts.

ℹ️ About Echidna Fuzzing

Echidna is a property-based fuzzer that generates random sequences of transactions
to test invariants (properties that should always hold true).

Properties tested:

  • Fee calculation bounds
  • Access control enforcement
  • Amount constraints
  • No duplicate payments
  • Zero address validation
  • Integer overflow protection

@github-actions

Copy link
Copy Markdown

✅ Slither Security Analysis

Status: Passed

Findings Summary

Severity Count Status
✅ High 0 Pass
🟡 Medium 2 Review Recommended
🔵 Low 0 Info
ℹ️ Informational 1 Info

⚠️ Please review the findings in the Security tab or download the artifacts.

📄 Full report available in workflow artifacts.
🔍 View detailed findings in the Security tab.

@github-actions

Copy link
Copy Markdown

✅ Slither Security Analysis

Status: Passed

Findings Summary

Severity Count Status
✅ High 0 Pass
🟡 Medium 2 Review Recommended
🔵 Low 0 Info
ℹ️ Informational 1 Info

⚠️ Please review the findings in the Security tab or download the artifacts.

📄 Full report available in workflow artifacts.
🔍 View detailed findings in the Security tab.

@github-actions

Copy link
Copy Markdown

✅ Echidna Fuzzing Results

Mode: ci (50000 test sequences)
Status: All Properties Passed

Property Test Results

Status Count
✅ Passed 16
❌ Failed 0
Total 16
Pass Rate 100.0%

📄 Full report and corpus available in workflow artifacts.

ℹ️ About Echidna Fuzzing

Echidna is a property-based fuzzer that generates random sequences of transactions
to test invariants (properties that should always hold true).

Properties tested:

  • Fee calculation bounds
  • Access control enforcement
  • Amount constraints
  • No duplicate payments
  • Zero address validation
  • Integer overflow protection

@yomarion yomarion marked this pull request as ready for review June 19, 2026 16:19
@github-actions

Copy link
Copy Markdown

✅ Slither Security Analysis

Status: Passed

Findings Summary

Severity Count Status
✅ High 0 Pass
🟡 Medium 2 Review Recommended
🔵 Low 0 Info
ℹ️ Informational 1 Info

⚠️ Please review the findings in the Security tab or download the artifacts.

📄 Full report available in workflow artifacts.
🔍 View detailed findings in the Security tab.

@github-actions

Copy link
Copy Markdown

✅ Echidna Fuzzing Results

Mode: ci (50000 test sequences)
Status: All Properties Passed

Property Test Results

Status Count
✅ Passed 16
❌ Failed 0
Total 16
Pass Rate 100.0%

📄 Full report and corpus available in workflow artifacts.

ℹ️ About Echidna Fuzzing

Echidna is a property-based fuzzer that generates random sequences of transactions
to test invariants (properties that should always hold true).

Properties tested:

  • Fee calculation bounds
  • Access control enforcement
  • Amount constraints
  • No duplicate payments
  • Zero address validation
  • Integer overflow protection

@rodrigopavezi rodrigopavezi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

hey, nice fix! the tareBalance approach is clean and the test coverage looks solid.

quick note — I checked BatchNoConversionPayments and it doesn't have excess refund logic, so the same stranded-balance bug doesn't apply there. the tareBalance field is currently only consumed by BatchConversionPayments, which is fine.

a few things I noticed inline:

Comment thread packages/smart-contracts/src/contracts/BatchNoConversionPayments.sol Outdated
Comment thread packages/smart-contracts/src/contracts/BatchNoConversionPayments.sol Outdated
Comment thread packages/smart-contracts/test/contracts/BatchConversionPayments.test.ts Outdated
Comment thread packages/smart-contracts/test/contracts/BatchConversionPayments.test.ts Outdated
@github-actions

Copy link
Copy Markdown

✅ Slither Security Analysis

Status: Passed

Findings Summary

Severity Count Status
✅ High 0 Pass
🟡 Medium 2 Review Recommended
🔵 Low 0 Info
ℹ️ Informational 1 Info

⚠️ Please review the findings in the Security tab or download the artifacts.

📄 Full report available in workflow artifacts.
🔍 View detailed findings in the Security tab.

@yomarion

Copy link
Copy Markdown
Contributor Author

hey, nice fix! the tareBalance approach is clean and the test coverage looks solid.

quick note — I checked BatchNoConversionPayments and it doesn't have excess refund logic, so the same stranded-balance bug doesn't apply there. the tareBalance field is currently only consumed by BatchConversionPayments, which is fine.

a few things I noticed inline:

Thanks @rodrigopavezi for your in-depth review. I addressed all your feedback.

About the choice for implementing rescueERC20Funds in the NoConversion contract, my chain of thought was: exactly the same implementation costs and could (very hypothetically) prevent a mistake in the future.

@yomarion yomarion requested a review from rodrigopavezi June 22, 2026 14:03
@github-actions

Copy link
Copy Markdown

✅ Echidna Fuzzing Results

Mode: ci (50000 test sequences)
Status: All Properties Passed

Property Test Results

Status Count
✅ Passed 16
❌ Failed 0
Total 16
Pass Rate 100.0%

📄 Full report and corpus available in workflow artifacts.

ℹ️ About Echidna Fuzzing

Echidna is a property-based fuzzer that generates random sequences of transactions
to test invariants (properties that should always hold true).

Properties tested:

  • Fee calculation bounds
  • Access control enforcement
  • Amount constraints
  • No duplicate payments
  • Zero address validation
  • Integer overflow protection

@github-actions

Copy link
Copy Markdown

✅ Slither Security Analysis

Status: Passed

Findings Summary

Severity Count Status
✅ High 0 Pass
🟡 Medium 2 Review Recommended
🔵 Low 0 Info
ℹ️ Informational 1 Info

⚠️ Please review the findings in the Security tab or download the artifacts.

📄 Full report available in workflow artifacts.
🔍 View detailed findings in the Security tab.

@github-actions

Copy link
Copy Markdown

✅ Echidna Fuzzing Results

Mode: ci (50000 test sequences)
Status: All Properties Passed

Property Test Results

Status Count
✅ Passed 16
❌ Failed 0
Total 16
Pass Rate 100.0%

📄 Full report and corpus available in workflow artifacts.

ℹ️ About Echidna Fuzzing

Echidna is a property-based fuzzer that generates random sequences of transactions
to test invariants (properties that should always hold true).

Properties tested:

  • Fee calculation bounds
  • Access control enforcement
  • Amount constraints
  • No duplicate payments
  • Zero address validation
  • Integer overflow protection

@MantisClone MantisClone left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Optimistically Approved 👍 pending comment resolution 🚧

Nice work. The stranded-balance fix makes sense to me, and the rescue path looks scoped correctly.

I left one small test comment: the sanity check should flip the payer-balance subtraction so it actually proves the spend was below maxToSpend. Once that is fixed, this looks good to me.

Comment thread packages/smart-contracts/test/contracts/BatchConversionPayments.test.ts Outdated
…s.test.ts

Co-authored-by: MantisClone <david.huntmateo@request.network>
@yomarion yomarion enabled auto-merge (squash) June 23, 2026 09:16
@github-actions

Copy link
Copy Markdown

✅ Slither Security Analysis

Status: Passed

Findings Summary

Severity Count Status
✅ High 0 Pass
🟡 Medium 2 Review Recommended
🔵 Low 0 Info
ℹ️ Informational 1 Info

⚠️ Please review the findings in the Security tab or download the artifacts.

📄 Full report available in workflow artifacts.
🔍 View detailed findings in the Security tab.

@greptile-apps

greptile-apps Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Want your agent to iterate on Greptile's feedback? Try greploops.

@github-actions

Copy link
Copy Markdown

✅ Echidna Fuzzing Results

Mode: ci (50000 test sequences)
Status: All Properties Passed

Property Test Results

Status Count
✅ Passed 16
❌ Failed 0
Total 16
Pass Rate 100.0%

📄 Full report and corpus available in workflow artifacts.

ℹ️ About Echidna Fuzzing

Echidna is a property-based fuzzer that generates random sequences of transactions
to test invariants (properties that should always hold true).

Properties tested:

  • Fee calculation bounds
  • Access control enforcement
  • Amount constraints
  • No duplicate payments
  • Zero address validation
  • Integer overflow protection

@yomarion yomarion merged commit c12cc9b into master Jun 23, 2026
13 checks passed
@yomarion yomarion deleted the smart-contracts/batch-conversion-0.2-0-stranded-balance branch June 23, 2026 09:30
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.

4 participants