Skip to content

feat: tron batch payments contracts and tests#1724

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

feat: tron batch payments contracts and tests#1724
LeoSlrRf wants to merge 10 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 ERC20BatchPayments, an ERC20-specific version of the BatchPayments contract.
This version offers better performance and increased security on Tron.

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.

A shared helpers.js module provides utilities, 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.

The Trondeployments scripts have been updated to support the deployment of ERC20BatchPayments

Contracts Scope

This is the same contract as the BatchPayments contract from EVM without th following features:

  • The dedicated batch fees
  • The EthFeeProxy methods
  • The ownership

Security Consideration

The ERC20BatchPayment contract no longer has an owner compared to the original version, reducing the attack surface.

Gas Consideration

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

Deloyment Information

Nile: https://nile.tronscan.org/#/contract/TDnU5eY8Et3QdZRWMSTvoXQnxQeMxF7CE4
Tron: https://tronscan.org/#/contract/TUdcGd29QpV65MkbqgBLWJKbTG3UL7PuQB

Both contracts are verified ✅

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 ERC20BatchPayments, a Tron-specific batch payment contract that routes ERC20 payments through ERC20FeeProxy, along with deployment scripts, artifact registration, and a comprehensive test suite using a shared helpers.js module.

  • Contract (ERC20BatchPayments.sol): Strips ownership and ETH-path logic from the EVM BatchPayments baseline; the uniqueTokens deduplication in batchERC20PaymentsMultiTokensWithReference is correctly guarded by Solidity 0.8 checked arithmetic, and the SafeERC20 wrapper handles non-standard TRC20 tokens.
  • Artifact/deployment records: The previously mismatched nile address in index.ts is now consistent with nile.json (both point to TDnU5eY8Et3QdZRWMSTvoXQnxQeMxF7CE4).
  • Deployment scripts: deploy-mainnet.js correctly merges into the existing mainnet.json and sources the proxy address from it; deploy-nile.js unconditionally deploys a fresh ERC20FeeProxy on every run, unlike the mainnet script, which could leave index.ts and nile.json out of sync after a re-run.

Confidence Score: 5/5

The batch contract logic is sound and the deployed addresses in the artifact registry are now consistent; the only open item is a testnet-script convenience concern.

The contract's token-aggregation and payment-dispatch paths are correct, the custom SafeERC20 wrapper handles non-standard tokens safely, and all previously flagged issues (artifact name mismatch, threw undefined, nile address mismatch) are resolved in this revision. The single remaining note — the nile deployment script unconditionally redeploys ERC20FeeProxy — is a developer-tooling concern that does not affect the on-chain contract or the mainnet deployment path.

packages/smart-contracts/scripts/tron/deploy-nile.js — lacks the selective-deploy guard that the mainnet script uses, so a re-run would overwrite nile.json with a fresh proxy address.

Important Files Changed

Filename Overview
packages/smart-contracts/tron/contracts/ERC20BatchPayments.sol Core batch contract — single-token and multi-token paths look correct; approvePaymentProxyToSpend is public (matching the EVM BatchPayments.sol precedent); uniqueTokens sentinel logic is sound for Solidity 0.8 checked arithmetic.
packages/smart-contracts/test/tron/ERC20BatchPayments.test.js Comprehensive test suite; previous naming issues (BatchPaymentsTronSimplified) are resolved; error-case tests correctly delegate to expectRevertOrNoBalanceChange given Tron's non-throwing revert semantics.
packages/smart-contracts/test/tron/helpers.js threw reference bug from earlier reviews is gone; expectNonOwnerReverts now compares before/after state correctly; all exported utilities look correct.
packages/smart-contracts/src/lib/artifacts/ERC20BatchPayments/index.ts Nile address (TDnU5eY8Et3QdZRWMSTvoXQnxQeMxF7CE4, block 67775288) now matches nile.json; mainnet address matches mainnet.json; previous mismatch was fixed.
packages/smart-contracts/scripts/tron/deploy-mainnet.js Reads existing mainnet.json to source the ERC20FeeProxy address and merges new contracts into the existing deployment record; minor: creationBlockNumber is the current block at call time rather than the block containing the deployment tx.
packages/smart-contracts/scripts/tron/deploy-nile.js Always deploys a fresh ERC20FeeProxy on every run; lacks the selective-deploy guard present in the mainnet script, risking stale addresses in index.ts after a redeploy.
package.json Yarn 1 workspaces object form with correct nohoist pattern prevents @openzeppelin hoisting for the smart-contracts package.
packages/smart-contracts/tron/contracts/lib/SafeERC20.sol Low-level call wrappers for transferFrom, approve, and transfer; correctly handles non-standard ERC20s that return no data.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Caller invokes batch function] --> B{Validate array lengths}
    B -->|Mismatch| C[Revert]
    B -->|OK| D[Compute total per token]
    D --> E{For each unique token}
    E --> F{Check allowance}
    F -->|Insufficient| C
    F -->|OK| G{Check balance}
    G -->|Insufficient| C
    G -->|OK| H[Pull tokens into batch contract]
    H --> I{Proxy allowance enough?}
    I -->|No| J[Approve proxy for max]
    I -->|Yes| K[Dispatch payments via ERC20FeeProxy]
    J --> K
    K --> L[Done]
Loading

Reviews (7): Last reviewed commit: "update nile ERC20BatchPAyments deploymen..." | 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
Copy link
Copy Markdown
Contributor

MantisClone commented May 21, 2026

@LeoSlrRf is this still true? If not, please update the PR description.

> Currently, both contracts provide methods for managing the underlying proxies.

@LeoSlrRf
Copy link
Copy Markdown
Contributor Author

@MantisClone It wasn't the case. I just pushed the ownership removal and updated the PR description

uint256[] calldata _amounts,
bytes[] calldata _paymentReferences,
uint256[] calldata _feeAmounts,
address _feeAddress
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.

I noticed that all batch payment contracts enforce a single feeAddress for all payments. I don't see why that would be the case.

Given that we won't use the fee parameters, I'm wondering if it's worth updating to an array for Tron only.

Copy link
Copy Markdown
Contributor

@MantisClone MantisClone left a comment

Choose a reason for hiding this comment

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

Nice work! 💯

@MantisClone
Copy link
Copy Markdown
Contributor

Post-approval optional hardening note:

  • Guard constructor against a zero proxy address.
  • Make paymentErc20FeeProxy immutable.
  • Make approvePaymentProxyToSpend() non-public since it is only used internally.

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