Skip to content

feat: tron batch payments contracts and tests#1724

Open
LeoSlrRf wants to merge 5 commits into
masterfrom
feat/batch-tron
Open

feat: tron batch payments contracts and tests#1724
LeoSlrRf wants to merge 5 commits into
masterfrom
feat/batch-tron

Conversation

@LeoSlrRf
Copy link
Copy Markdown
Contributor

@LeoSlrRf LeoSlrRf commented May 20, 2026

Description of the changes

Adds Tron-specific batch payment contracts and test infrastructure to the smart-contracts package.

This PR introduces the existing EVM BatchPayment contract on Tron, and introduces ERC20BatchPayments, a version for better performance and stripped down of unused methods on Tron

Two Tron-targeted test suites are added:

  • ERC20BatchPayments.test.js covers ERC20BatchPayments, including happy-path single-token and multi-token payments, zero-amount payments, zero-fee payments, BadTRC20 token handling, and error cases such as insufficient funds, missing allowance, and mismatched input arrays.
  • BatchPayments.test.js covers the main BatchPayments contract on Tron, adding batch fee computation and verification, dynamic batch fee updates, admin access control for proxy and fee setters, and a check that batchEthPaymentsWithReference reverts when no EthFeeProxy is configured.

A shared helpers.js module provides utilities used by both test files, including token deployment, approval helpers, balance diffing, batch fee computation, and revert/no-balance-change assertion helpers.

The root package.json workspace configuration is updated to prevent hoisting of @openzeppelin dependencies for the smart-contracts package, ensuring the Tron build resolves its own copy of those contracts.

Contracts Scope

BatchPayments.sol

Same contract as the one on EVM.
We don't have the EthFeeProxy on Tron, so the associated address is defined as the zero address. Calls to the associated method fail as expected.

ERC20BatchPayment.sol

This is the same contract as above, with existing BatchPayments without:

  • The dedicated batch fees
  • The EthFeeProxy methods

Security Consideration

Currently, both contracts provide methods for managing the underlying proxies. For better security, we could hardcode them at deployment time, since they are unlikely to change anytime soon, especially since we don't own a multisig on Tron.

The ERC20BatchPayment contract also benefits from not exposing unused methods, thereby reducing the attack surface. Given that there is no batch fee, it makes it easier to completely remove the notion of contract owner

Gas Consideration

ERC20BatchPayment has a lower gas footprint because it does not include batch-fee-related logic.

Initially, I created a third version in which the feeAmounts and feeRecipients were removed from the interface and were always set to zero within the contract. This didn't reduce the gas cost compared to a call where we would pass the zero values directly.

Copy link
Copy Markdown
Contributor Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@LeoSlrRf LeoSlrRf marked this pull request as ready for review May 20, 2026 11:22
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 20, 2026

Greptile Summary

This PR introduces Tron-specific batch payment contracts and tests to the smart-contracts package: a symlink alias of the existing BatchPayments.sol for Tron deployment, a new stripped-down ERC20BatchPayments.sol (no ETH proxy, no batch fee), two corresponding Mocha test suites, a shared helpers.js, and a package.json nohoist change to keep @openzeppelin local to the smart-contracts package.

  • ERC20BatchPayments.sol removes batch-fee logic and ETH proxy methods from the EVM contract, reducing attack surface and gas cost on Tron.
  • BatchPayments.sol is deployed as-is on Tron with the ETH proxy set to the zero address, with a test confirming the ETH payment path reverts.
  • Tests adapt to Tron's behavior where reverts do not throw JS exceptions, using balance-diff assertions instead of try/catch + threw checks.

Confidence Score: 4/5

Safe to merge with minor caveats in the new ERC20BatchPayments contract.

The contract logic is sound: tokens are pulled from the payer before proxy calls, Solidity 0.8 overflow protection prevents integer wrapping, and the batch-fee formula matches the test helper exactly. The two items worth attention are both non-critical: the zero-address sentinel collision in the multi-token aggregation loop is a confusing-but-safe degenerate input, and the public visibility of approvePaymentProxyToSpend is consistent with the parent contract but at odds with the stated goal of reducing attack surface. Neither can cause fund loss given normal usage.

packages/smart-contracts/tron/contracts/ERC20BatchPayments.sol — specifically the unique-token aggregation loop and the visibility of approvePaymentProxyToSpend.

Important Files Changed

Filename Overview
packages/smart-contracts/tron/contracts/ERC20BatchPayments.sol New Tron-only batch ERC20 contract. The unique-token aggregation loop in batchERC20PaymentsMultiTokensWithReference uses address(0) as a sentinel for uninitialized slots, which collides when a caller passes address(0) as a token address. approvePaymentProxyToSpend is public, allowing anyone to reset token approvals to type(uint256).max.
packages/smart-contracts/tron/contracts/BatchPayments.sol Symlink to the existing EVM BatchPayments.sol; no new contract logic introduced. Deployed on Tron with zero-address EthFeeProxy as intended.
packages/smart-contracts/test/tron/helpers.js Shared test utilities. The threw-variable ReferenceError flagged in previous reviews is now resolved; expectNonOwnerReverts uses state-comparison instead. expectRevertOrNoBalanceChange intentionally swallows errors to accommodate Tron's no-exception revert behavior.
packages/smart-contracts/test/tron/BatchPayments.test.js Integration tests for BatchPayments on Tron. Covers happy-path single-token and multi-token payments, batch fee updates, and admin access control. Deploying BatchPayments with TRON_ZERO_ADDRESS as EthProxy and asserting the ETH path reverts is well-tested.
packages/smart-contracts/test/tron/ERC20BatchPayments.test.js Integration tests for ERC20BatchPayments. Comprehensive coverage of single-token, multi-token, zero-amount, and error cases. The artifact name now correctly matches the deployed contract name ERC20BatchPayments.
package.json Adds Yarn workspace nohoist for @openzeppelin under @requestnetwork/smart-contracts to ensure the Tron compiler resolves its own copy of those contracts. Pattern and syntax look correct.

Sequence Diagram

sequenceDiagram
    participant Payer
    participant Batch as ERC20BatchPayments
    participant Proxy as ERC20FeeProxy
    participant ERC20
    participant Payee
    participant Fee as FeeAddress

    Payer->>Batch: batchERC20PaymentsWithReference(token, payees, amounts, refs, fees, feeAddr)
    Batch->>Batch: validate array lengths
    Batch->>Batch: sum total amountAndFee
    Batch->>ERC20: check allowance and balance
    Batch->>ERC20: safeTransferFrom(payer, batch, totalAmountAndFee)
    Batch->>ERC20: approve(proxy, MAX) if needed
    loop for each payment
        Batch->>Proxy: transferFromWithReferenceAndFee(token, payee, amount, ref, fee, feeAddr)
        Proxy->>ERC20: transferFrom(batch, payee, amount)
        Proxy->>ERC20: transferFrom(batch, feeAddr, fee)
    end
    Batch-->>Payer: success or full revert
Loading

Reviews (4): Last reviewed commit: "fix: remove return statement" | Re-trigger Greptile

Comment thread packages/smart-contracts/test/tron/ERC20BatchPayments.test.js Outdated
Comment thread packages/smart-contracts/test/tron/ERC20BatchPayments.test.js Outdated
Comment thread packages/smart-contracts/test/tron/ERC20BatchPayments.test.js Outdated
Comment on lines +84 to +93
const expectRevertOrNoBalanceChange = async (fn, getBalances) => {
const before = await getBalances();
try {
await fn();
} catch (_error) {}
await waitForConfirmation(2000);
const after = await getBalances();
const unchanged = before.every((value, index) => value === after[index]);
return { unchanged };
};
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.

P2 Silent error swallowing can mask real bugs

expectRevertOrNoBalanceChange catches errors without asserting that a revert actually occurred. If the transaction unexpectedly succeeds and the balance check happens to track the wrong account, a funds leak would be silently accepted. The expectNonOwnerReverts helper in the same file correctly asserts threw === true; applying the same pattern here would make failure detection explicit.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

On Tron, when a revert occurs the transaction call does not fail.

See this transaction for example.

Comment thread packages/smart-contracts/test/tron/helpers.js Outdated
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