Guardian's review of Polygon Upgrade for USDT0, published June 2025. The report records 25 findings across 2 review rounds, including 5 low and 20 informational.
- Published
- Review window
- May 12 to 29, 2025
- Rounds
- Main Review, Remediation Review
- Language
- Solidity
- Chains
- Ethereum, Arbitrum, Ink, Hyperliquid, Polygon, Monad, Solana, Stellar
- Sector
- Stablecoins
- 0 Critical
- 0 High
- 0 Medium
- 5 Low
- 20 Informational
Scope
15 files in scope · 563 nSLOC
| File | nSLOC | Lines |
|---|---|---|
contracts/common/AccessControlMixin.sol | 15 | 18 |
contracts/common/ContextMixin.sol | 18 | 28 |
contracts/common/EIP712Base.sol | 50 | 70 |
contracts/common/Initializable.sol | 12 | 15 |
contracts/common/NativeMetaTransaction.sol | 69 | 96 |
contracts/common/Proxy/IERCProxy.sol | 3 | 4 |
contracts/common/Proxy/Proxy.sol | 29 | 38 |
contracts/common/Proxy/UpgradableProxy.sol | 81 | 102 |
contracts/child/ChildToken/IChildToken.sol | 3 | 4 |
contracts/child/ChildToken/UpgradeableChildERC20/ERC20.sol | 100 | 328 |
contracts/child/ChildToken/UpgradeableChildERC20/UChildERC20.sol | 32 | 43 |
contracts/child/ChildToken/UpgradeableChildERC20/UChildERC20Proxy.sol | 8 | 10 |
contracts/child/ChildToken/DappTokens/IERC7802.sol | 5 | 20 |
contracts/child/ChildToken/DappTokens/UChildUSDT0.sol | 115 | 165 |
contracts/child/ChildToken/DappTokens/WithBlockedList.sol | 23 | 51 |
Findings 25
Main Review
24 findings · May 12 to 19, 2025-
L-01 Low Old Signatures Become Invalid Informational Acknowledged
Description
With the USDT0 upgrade, the token name and domain separator will be updated. As a result, signed meta txs according to the old domain separators, will become invalid immediately after the update.
This could cause UX issues for users utilizing DEXes for gasless swaps, for example.
If DEXes are not made aware of this domain separator change, they may continue using the old one, resulting in failed swaps for users.
Recommendation
Consider documenting this behavior and informing users that they must sign new permits after the USDT0 upgrade.
-
L-02 Low Missing ERC-165 Support Best Practices Resolved
Description
The EIP 7802 states:
The inclusion of ERC-165 provides an additional security check for integrators. By providing the interface identifier through the supportsInterface method, callers can programmatically confirm that the token adheres to the IERC7802 interface.Current
UChildUSDT0implementation inherits from IERC7802, supporting crosschain ERC20 transfers. However, this interface does not enforce IERC165, different from the current OFT extension deployments in other chains.Recommendation
Consider adding the
supportsInterfacefunction to correctly signal the EIP support, similar to the current deployments:function supportsInterface(bytes4 interfaceId) external pure override returns (bool) { return interfaceId == type(IERC7802).interfaceId || interfaceId == type(IERC165).interfaceId; } -
L-03 Low Use Of Vanilla ecrecover Allows Signature Malleability Validation Acknowledged
Description
USDT uses vanilla
ecrecoverfor signer recovery, which is susceptible to malleability due to signature variations.This does not cause any immediate damage since the signed permit itself does not change. However, this should still be considered for improvement.
Recommendation
Consider using OpenZeppelin's
ECDSAlibrary for signer recovery to mitigate malleability risks -
L-04 Low UChildUSDT0 Not EIP-2612 Compliant Logical Error Resolved
Description
According to the EIP-2612, compliant contracts must implement 3 new functions in addition to EIP-20:
function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external function nonces(address owner) external view returns (uint) function DOMAIN_SEPARATOR() external view returns (bytes32)However,
UChildUSDT0has anoncesinternal mapping, and uses thegetNoncepublic getter instead. Consequently, the expectedpermitfunctionality will be prevented for many wallets and tools that rely on the standardnonces(address)getter.Recommendation
Consider making the
noncesmapping public to comply with the EIP. -
L-05 Low Lack Of Smart Wallet Support In
permitLogical Error AcknowledgedDescription
The
permitfunction usesecrecoverfor signature verification, which only supports Externally Owned Accounts (EOAs). This excludes smart contract wallets (e.g., Gnosis Safe, DAOs) that rely on ERC-1271 for validating signatures.USDT0’s deployments on Arbitrum and Optimism already support ERC-1271 by using OpenZeppelin’s SignatureChecker, which delegates to
isValidERC1271SignatureNowwhen the signer is a contract. The Polygon version has not adopted this approach.This discrepancy breaks cross-chain UX. Smart wallet users who can interact with USDT via
permiton Arbitrum or Optimism are blocked when bridging to Polygon, where the same flow fails.As account abstraction adoption grows (e.g., EIP-7702 in Pectra), supporting contract wallets is critical. The current implementation limits composability and prevents protocols like CoW Swap from offering gasless approvals to smart wallet users.
Note: Using SignatureChecker also makes the contract EIP-7377 safe, should it be approved in future. See discussion and commit
Recommendation
Update the
permitfunction to useSignatureChecker, as done on Arbitrum and Optimism, to enable ERC-1271 compatibility and ensure cross-chain consistency. -
I-01 Informational Inconsistent Use Of Revert Messages Best Practices Resolved
Description
UChildUSDT0contains several error messages starting withUChildUSDT0:while some others useTetherToken:(i.e. multiTransfer) andUSD₮0:(AccessControlMixin). This may create some confusion to offchain services, as it might imply it's a different contract.Recommendation
Consider maintaining a consistent error message structure throughout the contract
-
I-02 Informational Missing Event On setOFTContract Update Best Practices Resolved
Description
The
setOFTContractfunction updates the OFT contract address, a critical change that should be clearly recorded on-chain. However, no dedicated event is emitted to signal this update. The only emitted event isRoleGranted, which is generic and does not specifically indicate that the OFT contract has changed.For comparison, USDT0 implementations on other chains like Arbitrum emit a dedicated
LogSetOFTContractevent when this change is made.Recommendation
Emit a specific event (e.g.,
LogSetOFTContract) in thesetOFTContractfunction to explicitly record the new OFT contract address for better transparency and traceability. -
I-03 Informational Typo In EIP712Base Best Practices Resolved
Description
The comment on line 23 of the EIP712Base contract contains a typo: "... the contractsa that inherits ..." should be corrected to "... the contracts that inherit ...".
Additionally, the comment format is not consistent with the rest of the codebase, which uses the NatSpec style with
/**and*/.Recommendation
Update the comment.
-
I-04 Informational Missing SPDX-License Identifier Best Practices Acknowledged
Description
All contracts in the scope, except
IERC7802.sol,ERC20.sol, andWithBlockedList.sol, are missing an SPDX-License Identifier.Recommendation
Consider adding SPDX license identifiers to these contracts.
-
I-05 Informational
isContractProbably Soon Redundant Informational AcknowledgedDescription
The
isContractcheck used in theUpgradableProxy.updateImplementationis probably soon redundant with the Pectra update as EOAs can contain code with EIP-7702.Currently, there is a proposal to implement Pectra on Polygon although not accepted yet: https://forum.polygon.technology/t/pip-61-pectra-eips/20783
Recommendation
As the issue is present in the proxy contract, it can't be updated. If Polygon implements Pectra, extra care should be taken when updating to a new implementation, making sure the correct address is set, as the
isContractcheck will not work as expected. -
I-06 Informational Unnecessary Looping Best Practices Acknowledged
Description
As per our discussions with the USDT team, only one assignment is expected to hold true for
DEPOSITOR_ROLETherefore, loops in
_setOFTContractoroftContractare unnecessary and could be replaced with a single revoke and assignment.Recommendation
Consider removing the loop and adding a single revoke and assignment to improve code readability and eliminate unnecessary complexity.
-
I-07 Informational No Initialization Protection Validation Resolved
Description
upgradeToUSDT0is not access-controlled, meaning anyone can call it.Since an equivalent to
disableInitializersis not implemented, anyone could call the method on the implementation contract itself and create a parallel identity of the USDT0 token.For the real USDT0 token, this is not an issue, as the context is via the proxy. However, given the scale of USDT operations, the possibility of such ambiguity should be addressed.
Recommendation
Consider adding a
disableInitializersequivalent toUChildUSDT0to prevent misuse of the implementation contract. -
I-08 Informational Removal Of Function Can Break Integrations Informational Acknowledged
Description
The update of the USDT contract removes the function
withdrawwhich was previously used to bridge out of Polygon. This may cause unexpected behavior for wallets or applications which relied on calling this function in their smart contracts.During the review, a check on all addresses that have previously called
withdrawwas performed and no smart contracts were found.Recommendation
Consider documenting this risk for users.
-
I-09 Informational Misleading Comment In
crosschainMintBest Practices ResolvedDescription
The
crosschainMintfunction includes a comment stating: "Make sure minting is done only by this function."However, there is also a separate
mintfunction that can be called by the admin, allowing them to mint any amount of USDT0 tokens to any address. This contradicts the comment above.Recommendation
If the
crosschainMintfunction is intended to be the sole method for minting tokens, the admin-only mint function should be removed.If that's not the case, the comment in
crosschainMintshould be updated to accurately reflect the intended behavior. -
I-10 Informational Missing Storage Gaps Informational Resolved
Description
The
UChildUSDT0contract inherits from bothUChildERC20andWithBlockedList. However, neither of these parent contracts defines a storage gap. This omission poses a risk for future upgrades.If additional storage variables are introduced in
UChildERC20orWithBlockedList, they may overwrite existing storage slots, potentially colliding with variables such asUSDT0_VERSIONdeclared inUChildUSDT0.Recommendation
To ensure safe and upgradeable contract design, add storage gaps (e.g.,
uint256[50] private __gap;) to bothUChildERC20andWithBlockedList. -
I-11 Informational Grouped Issues: Inherited Rom Polygon's
pos-portalContracts Informational AcknowledgedDescription
These issues are present in contracts inherited or utilized by USDT from Polygon’s
pos-portalsuite. We advise the USDT team to review and address them based on their prioritization.Note that issues in
UpgradableProxy.solcannot be fixed without changing the deployment address and are included for awareness only. Other implementation contracts can be modified if deemed important.UpgradableProxy.sol
- Missing Double-Step Ownership Transfer Ownership transfer lacks a secure two-step pattern. Recommendation: Document current behavior and validate new owner address thoroughly before transfer.
- No Return Value Forwarding in
updateAndCallThe function does not forward return data from the delegate call. Recommendation: Be aware that return values from called functions will be discarded. updateAndCallAltersmsg.senderContext Typically,upgradeToAndCall-style functions directly delegatecall the implementation. However,UpgradableProxyof pos-portal performs a regularcallto itself, which routes through the fallback and only then delegatecalls the implementation. This changesmsg.senderin the implementation to the proxy contract itself (address(this)), not the proxy owner. Many meta-transaction patterns treatmsg.sender == address(this)as an indicator of a meta-tx and attempt to extract the real sender (msgSender) from calldata. SinceupdateAndCalldoes not append any such data,msgSenderresolves to the zero address. Recommendation: Be aware of this behavior when usingupdateAndCall. If the implementation expects meta-tx structure, it may misinterpret the sender asaddress(0).
EIP712Base.sol
- Typo in
_setDomainSeperatorThe word “Seperator” is misspelled; should be “Separator.” Recommendation: Rename to_setDomainSeparator. As it's internal, this change won't break external dependencies.
NativeMetaTransaction.sol
- Unnecessary
payableModifierexecuteMetaTransactionis markedpayablebut doesn’t handle native currency. Recommendation: Removepayableto prevent accidental loss of native tokens. If retained for gas saving reasons, clearly document the behavior. - Mismatch Between Comment and Code The function comment states that relayer address is appended to calldata, but this isn’t reflected in the implementation. Recommendation: Either update the comment to match current behavior or modify the code accordingly.
- No Deadline or Expiry on Meta Transactions Meta-transactions that fail do not increment the user's nonce, leaving it unused and publicly available. Since there's no deadline parameter (like in permit), any party can replay this transaction indefinitely as long as the nonce is unchanged. to calldata, but this isn’t reflected in the implementation. Recommendation: Consider adding a deadline parameter to limit replayability and mitigate long-term replay risks.
Recommendation
NA
-
I-12 Informational OpenZeppelin v3.4.0 Recommendation Informational Acknowledged
Description
The current codebase uses Solidity version 0.6.6 and OpenZeppelin Contracts v3.1.0. To improve security and stability, we refer to the official recommendation from OpenZeppelin, which states that version 3.4.0 is the latest and most appropriate version for use with Solidity 0.6.x.
Recommendation
Update OpenZeppelin Contracts from v3.1.0 to v3.4.0.
-
I-13 Informational Unnecessary String Concatenation Gas Optimization Resolved
Description
The
upgradeToUSDT0updates some configurations with the newUSD₮0name, including the revert message for role access control:_setupContractId(string(abi.encodePacked("USD₮0")));However, this line was used in the
UChildERC20to concatenate "Child" string with the token symbol:_setupContractId(string(abi.encodePacked("Child", symbol_)));As there is no concatenation needed, the plain string can be passed as an argument to this function.
Recommendation
Remove the encoding and send plain "USD₮0" string as parameter:
_setupContractId("USD₮0"); -
I-14 Informational Recover Tokens Stuck In USDT Contract Informational Acknowledged
Description
Approximately $800,000 worth of tokens are currently stuck in the USDT contract, likely due to users mistakenly transferring tokens directly to the contract address. Since the contract does not include a mechanism to recover these funds, they remain irretrievable.
Recommendation
Consider implementing a recovery mechanism to allow retrieval of mistakenly sent tokens. This could follow the approach taken by USDC, which includes a controlled
recoverERC20function. -
I-15 Informational Invalid Test Should Be Removed Informational Resolved
Description
In
UChildUSDT0Fork.t.sol, the testtestInitializeRevertsfails to compile because theUChildUSDT0contract does not implement the functioninitialize.Recommendation
Remove the test
testInitializeReverts. -
I-16 Informational Warning About In Flight Bridges Informational Acknowledged
Description
In the current implementation, Ethereum to Polygon bridges are completed when the deposit function is called by the
childChainManager. After the update, bridges will be completed using thecrosschainMintfunction.Bridges cannot be completed on Polygon if the update occurs while there are in-transit bridge requests. The update should only be performed once all requests have been processed.
Recommendation
It has been stated that the Polygon team will handle the migration process and ensure that all previous messages are settled.
USDT Team should also consider consulting with Polygon team over unlocking of USDT locked in root manager on mainnet.
-
I-17 Informational Floating Pragma Informational Acknowledged
Description
The
IERC7802contract uses Solidity version ^0.6.0, while all the other contracts in the codebase use the fixed 0.6.6 version.Recommendation
Consider setting the pragma version to 0.6.6 in the IERC7802 contract, similar to the rest of the codebase.
-
I-18 Informational Missing Validation During Upgrade Validation Partially resolved
Description
The
upgradeToUSDT0address parameters are crucial for the correct migration to USDT0. Although this function is executed along with the implementation update and called by the proxy owner, some minimal validations should be implemented to guarantee no mistakes are made:- oftContract = address(0)
- newAdmin = address(0)
- decimals() = oft.sharedDecimals()
Keep in mind the
ArbitrumExtensionV2also has zero address check for the OFT address, so these validations will be consistent with other migrations.Recommendation
Consider adding the minimal validations when executing the upgrade function.
-
I-19 Informational LZ Config Block Confirmations Is Too Low Configuration Acknowledged
Description
The current Layer Zero configuration requires a minimum amount of block confirmations the OApp needs to wait in order to send or receive messaged. In case of USDT0, this value will be set to 256 blocks.
However, this value seems low with the fact that there have been 157 block reorgs in the past in Polygon. Additionally, other protocols like Stargate and Abracadabra have this value set to 512 blocks.
Recommendation
Consider increasing the Layer Zero block confirmation value to 512.
Remediation Review
1 finding · May 29, 2025-
I-01 Informational Events Should Be Grouped Together Acknowledged
Description
The
LogSetOFTContractwas recently added to the UChildUSDT0 contract. However, it was placed at the top of the contract (Line 12) while the rest of events are at the bottom of the contract.Recommendation
Group the events together at the top of the contract where events are typically found.
No findings match.
More from USDT0
All 20 reportsPut your code through the same review.
This review started with a conversation about scope. Tell us what you are building and we will plan yours with you.
