- August 17, 2026
OpenZeppelin Security
OpenZeppelin Security
Security Audits
Summary
Type: DeFi
Timeline: 2026-07-27 → 2026-07-28
Languages: Solidity, TypeScript
Findings
Total issues: 1 (1 resolved)
Critical: 0 (0 resolved) · High: 0 (0 resolved) · Medium: 0 (0 resolved) · Low: 0 (0 resolved)
Notes & Additional Information
1 note raised (1 resolved)
Client Reported Issues
0 reported issues (0 resolved)
Table of Contents
Scope
OpenZeppelin conducted a diff audit of the across-protocol/contracts repository, covering the changes between base commit fed107d and head commit b5ba27e.
In scope were the following files:
contracts
├── periphery
│ ├── SpokePoolPeriphery.sol
│ └── Tron_SpokePoolPeriphery.sol
└── tron
└── TronPeripheryImports.sol
System Overview
Across is a cross-chain bridge in which users deposit tokens into a SpokePool contract on an origin chain and relayers front the corresponding tokens to the recipient on a destination chain, later claiming a refund from a central HubPool on Ethereum. The SpokePoolPeriphery contract is an optional entry point placed in front of a SpokePool. It allows a depositor to swap one token for another immediately before depositing, and it accepts deposits authorized by an off-chain signature so that a third party can submit the transaction on the depositor's behalf. The periphery holds no liquidity of its own and does not participate in bundle validation or relayer refunds.
Two contracts implement this entry point.
-
SpokePoolPeriphery: receives the input tokens, either transferred directly by the caller or pulled from a signer using EIP-2612 permits, Permit2 witness transfers, or ERC-3009 authorizations. It optionally pays a submission fee out of the pulled amount, then approves the
SpokePooland opens the deposit. -
SwapProxy: executes the swap. The periphery transfers the input tokens to this contract, which transfers them to an exchange (either by approval, direct transfer, or Permit2 approval), calls the exchange with caller-supplied router calldata, and returns its entire output token balance to the periphery. Performing the exchange call from a separate contract keeps token approvals and swap execution separated from the contract that pulls user funds.
The changes under review adapt this entry point to Tron. The USDT contract deployed on Tron returns false from transfer even when the transfer succeeds, which causes the return value check in the OpenZeppelin SafeERC20 library to treat every such transfer as a failure. Any periphery flow that pushes USDT therefore reverts on Tron. The same behavior had already been addressed for the spoke pool, the multicall handler, and the counterfactual deposit contracts through TronTransferLib, a library that implements a function which determines the success of a transfer by measuring the change in the recipient's balance, rather than by reading the return value.
Two mechanisms are introduced to extend that approach to the periphery. The four outbound token transfers in SpokePoolPeriphery and SwapProxy are routed through an overridable _safeTransfer hook inherited from the SafeTransferERC20 mixin (a contract inherited solely for the ability to override a specific functionality), and the using directives in both contracts are narrowed so that the underlying library function is no longer reachable at those call sites. Separately, the periphery constructor now obtains its swap proxy from an overridable _deploySwapProxy function. The new Tron_SpokePoolPeriphery and Tron_SwapProxy contracts inherit their mainline counterparts and override the transfer hook, delegating to the balance-delta check in TronTransferLib, with the periphery variant additionally overriding the deployment function so that its constructor produces the Tron swap proxy. Pull-side operations are left unchanged, since transferFrom and approve have standard behavior on Tron USDT. The Tron entry point file for the Foundry build profile is updated to compile the new contracts.
Security Model and Trust Assumptions
While the audited diff does not alter any privileged roles, the following actors are relevant to the design and trust assumptions of the SpokePoolPeriphery and SwapProxy system, and its Tron adaptation.
Depositor and Signature Owner: supplies the deposit parameters, either as a direct caller or as the signer of a payload submitted by someone else. These parameters include the exchange address, the router calldata passed to it, the minimum acceptable swap output, the submission fee, and its recipient. Neither contract restricts which exchange may be named or what calldata is sent to it, so the signer bears responsibility for the exchange it selects and for the slippage bound it sets. Replay is constrained by an internal nonce and by the EIP-712 domain separator, which commits to the chain identifier and to the verifying contract address. The Tron variants inherit the mainline domain name, so signatures remain bound to a single chain and deployment.
Submitter: for the signature-authorized entry points, any address may submit a signed payload and collect the submission fee, which is drawn from the tokens pulled from the signer. This is not an on-chain role and no allowlist governs it. A submitter cannot alter the signed parameters, but it does choose whether and when to submit, so a signed payload that is never submitted simply expires against its deadline.
Exchange: an arbitrary external contract invoked with arbitrary calldata during a swap. It receives the input tokens or an allowance over them and is trusted only to the extent that the resulting output is checked against the signer's minimum expected amount before the deposit proceeds.
Tether as USDT Issuer on Tron: owns the token contract that motivates these changes and retains the ability to blacklist addresses, pause transfers, and enable a transfer fee. The balance-delta approach determines success by requiring the recipient balance to increase by exactly the transferred amount, which assumes that no such fee is active. The repository documents this assumption for the spoke pool variant.
Deployer: selects which implementation is deployed to each chain. The Tron behavior is addressed by deploying the variant contracts rather than the mainline ones, so a Tron deployment of the mainline artifacts would reintroduce the reverting transfers. The deployer also supplies the Permit2 address, which the constructor requires to be non-zero and to contain code, and which is not otherwise validated against a known deployment.
Additional Considerations
The swap proxy is created by the periphery constructor rather than passed in, so each periphery deployment is bound to the swap proxy it produced and the two addresses are established together. Because the Tron variants change the contract creation code, their deployment addresses differ from those of the mainline contracts on any chain.
Tron builds use a separate Foundry profile with its own compiler and artifact directory, and deployment proceeds from those artifacts through dedicated scripts. Correct behavior on Tron therefore depends on the deployment tooling selecting the variant artifacts, which is a build and operational property rather than one enforced on-chain.
Notes & Additional Information
Change in Revert Data for Failed Token Transfers
SpokePoolPeriphery.sol imports IERC20, SafeERC20, EIP712, and ReentrancyGuard from OpenZeppelin Contracts v4, while the new _safeTransfer hook is inherited from the SafeTransferERC20 mixin, which uses SafeERC20 from OpenZeppelin Contracts v5. The default transfer path of SwapProxy and SpokePoolPeriphery therefore executes v5 code on every chain, not only on Tron. Both versions accept and reject the same set of token return values, so no transfer outcome changes. However, a transfer that fails the return-value check now reverts with the v5 custom error SafeERC20FailedOperation rather than the v4 string "SafeERC20: ERC20 operation did not succeed".
No code within the repository matches on the former string, but off-chain consumers that classify periphery failures by revert data, such as relayers, quoting services, and simulation tooling, would observe this change on every chain where the periphery is deployed. Drawing transfer semantics from two major library versions within a single contract also makes those semantics harder to review, although SpokePool.sol already inherits the same v5 mixin while itself using v4. This also makes inaccurate the documentation in TronPeripheryImports that states that these contracts (meaning SpokePoolPeriphery and SwapProxy) use OZ v4, since SpokePoolPeriphery now uses both v4 and v5.5.
Consider documenting this change in revert data so that integrators can be notified, or adding a v4-based variant of the SafeTransferERC20 mixin for contracts that otherwise use OpenZeppelin Contracts v4, so that each contract draws its transfer semantics from a single library version.
Update: Resolved in pull request #1505.
Conclusion
The SpokePoolPeriphery contract provides an optional entry point to Across deposits, combining a token swap and a signature-authorized deposit within a single transaction. OpenZeppelin audited the changes that adapt this entry point, together with the SwapProxy contract it relies on for swap execution, to the Tron network, where the deployed USDT implementation reports failure from a transfer that has in fact succeeded and so defeats success checks based on return values.
The adaptation is confined to the transfer path. Rather than duplicating logic across contracts, the change introduces a single overridable transfer hook in the mainline contracts and narrows the library attachments around it, so that outbound transfers cannot bypass the hook at the affected call sites. The Tron variants add no state, entry points, or privileged functions of their own, and the pull side of the flow is left unchanged on the basis that the affected token behaves conventionally there. The approach follows the one already applied to the spoke pool, the multicall handler, and the counterfactual deposit contracts, so the periphery is now aligned with a pattern established elsewhere in the repository.
Substituting a measured balance change for a return value makes success detection depend on token behavior that the contracts do not govern, so the correctness of the Tron path rests on assumptions about the token rather than on checks performed on-chain. Those assumptions are documented for the spoke pool variant and hold for the current configuration of USDT on Tron, but they describe a third-party contract whose owner retains the ability to change it. Correct behavior on Tron further depends on the deployment tooling selecting the variant artifacts, as the two variants expose identical function selectors and neither asserts anything about the chain on which it is deployed. Both properties merit operational tracking as additional tokens and flows are routed through Tron. Overall, the audited changes are minimal, well-implemented, and consistent with the patterns previously established in the codebase.
We thank the Across team for their cooperation throughout this engagement, and look forward to supporting their work in the future.
Appendix
Issue Classification
OpenZeppelin classifies smart contract vulnerabilities on a 5-level scale:
- Critical
- High
- Medium
- Low
- Note/Information
Critical Severity
This classification is applied when the issue’s impact is catastrophic, threatening extensive damage to the client's reputation and/or causing severe financial loss to the client or users. The likelihood of exploitation can be high, warranting a swift response. Critical issues typically involve significant risks such as the permanent loss or locking of a large volume of users' sensitive assets or the failure of core system functionalities without viable mitigations. These issues demand immediate attention due to their potential to compromise system integrity or user trust significantly.
High Severity
These issues are characterized by the potential to substantially impact the client’s reputation and/or result in considerable financial losses. The likelihood of exploitation is significant, warranting a swift response. Such issues might include temporary loss or locking of a significant number of users' sensitive assets or disruptions to critical system functionalities, albeit with potential, yet limited, mitigations available. The emphasis is on the significant but not always catastrophic effects on system operation or asset security, necessitating prompt and effective remediation.
Medium Severity
Issues classified as being of medium severity can lead to a noticeable negative impact on the client's reputation and/or moderate financial losses. Such issues, if left unattended, have a moderate likelihood of being exploited or may cause unwanted side effects in the system. These issues are typically confined to a smaller subset of users' sensitive assets or might involve deviations from the specified system design that, while not directly financial in nature, compromise system integrity or user experience. The focus here is on issues that pose a real but contained risk, warranting timely attention to prevent escalation.
Low Severity
Low-severity issues are those that have a low impact on the client's operations and/or reputation. These issues may represent minor risks or inefficiencies to the client's specific business model. They are identified as areas for improvement that, while not urgent, could enhance the security and quality of the codebase if addressed.
Notes & Additional Information Severity
This category is reserved for issues that, despite having a minimal impact, are still important to resolve. Addressing these issues contributes to the overall security posture and code quality improvement but does not require immediate action. It reflects a commitment to maintaining high standards and continuous improvement, even in areas that do not pose immediate risks.
Looking for a security partner?