USDT0 engaged Guardian to review the security of their Tether OFT token on Ink. From the 2nd of January to the 7th of January, a team of 3 auditors reviewed the source code in scope.
- Published
- Review window
- January 2 to 7, 2025
- Language
- Solidity
- Chains
- Ink
- Sector
- Stablecoins
- 0 Critical
- 0 High
- 0 Medium
- 7 Low
- 7 Informational
Scope
Overview
USDT0 engaged Guardian to review the security of their Tether OFT token on Ink. From the 2nd of January to the 7th of January, a team of 3 auditors reviewed the source code in scope.
Findings 14
-
L-01 Low Transfers To Tether Contract Block Messages Validation Resolved
Description
The Tether token does not allow transfers or mints to the token address in the
_beforeTokenTransferfunction. However in theOUpgradeablecontract there is no validation which prevents users from initiating cross-chain OFT transfers which would send tokens to the destination chain Tether token contract.As a result any initiated OFT transfers with the to address as the Tether token on the destination chain will be stuck in the destination chain inbox and be un-executable, effectively trapping the users funds.
Recommendation
This may be resolved by storing the address of the Tether token contract on each corresponding
dstEidin a mapping and validating that thetoaddress is not the destination’s Tether contract by overriding thesendfunction.Alternatively, the
_creditfunction could include logic to send the bridged tokens to a trusted holding address if thetoaddress is the Tether token contract.If no code changes should be implemented for this issue, be sure to clearly document and warn users of this risk.
Resolution
USDT0 Team: Fixed with frontend validation.
-
L-02 Low OFT Transfers Avoid Fees Warning Acknowledged
Description
In the default
_debitimplementation for theOFTAdapterUpgradeabletheamountReceivedLDis the same as theamountSentLDwhich is transferred from the user.However if fees are enabled for the Tether token implementation at
0xdac17f958d2ee523a2206206994597c13d831ec7on Ethereum then the amount received by the Adapter contract on Ethereum will be less than theamountReceivedLDwhich the user is credited with on the destination chain.Firstly, this allows users to avoid the transfer tax on Ethereum. Secondly this can create balance underflow issues for users transferring their Tether back to Ethereum.
Consider the following scenario:
- The transfer tax is assigned as 1% on Ethereum
- User A uses OFT send to transfer 100 USDT from Ethereum to Arbitrum
- The Adapter contract receives 99 USDT and the 1 USDT fee is sent to the Tether owner address
- The Adapter contract balance is now 99 USDT
- User A receives 100 USDT on Arbitrum
- User A sends their 100 USDT back to Ethereum
- User A’s message execution on Ethereum reverts and is stuck in the message channel because the
Adapter contract only holds 99 USDT
- User A’s 100 USDT are effectively lost until more USDT is added to the Adapter contract allowing
them to execute the message
Recommendation
Consider overriding the
_debitand_creditfunctions of theOFTAdapterUpgradeableto account for the Tether token transfer fees if they are enabled by using pre and post transfer balance checks. Otherwise ensure that the fees are never enabled for the USDT token on Ethereum.Resolution
USDT0 Team: Acknowledged.
-
L-03 Low EOA Signatures Unexpectedly Become Invalid Unexpected Behavior Acknowledged
Description
In the
isValidSignatureNowfunction of theSignatureCheckerlibrary anisContractbytecode check is performed to determine whether a normal EOA ECDSA signature validation should occur withECRECOVERor a ERC1271 contract signature validation should be performed.However in the future, with the implementation of eip 7702 or eip 7377 it may be possible for EOAs to house bytecode, in which case the EOA ECDSA signature validation can no longer be performed for these EOAs.
It is likely that many EOAs which opt to house bytecode will not have functionality to support ERC1271 contract signatures and are therefore rendered unable to sign messages for the USDT token.
Furthermore, existing signatures that have been issued from EOAs and remain unused become unexpectedly invalid as soon as bytecode is written to them.
Recommendation
Consider always performing the ECDSA signature validation and opting to check the ERC1271 signature if the ECDSA signature verification fails, similarly to the
OpenZeppelin SignatureCheckerlibrary.Resolution
USDT0 Team: Acknowledged.
-
L-04 Low Incorrect Decimal Configuration Allowed Best Practices Resolved
Description
In the
OUpgradeablecontract the constructor accepts a decimals parameter which determines the conversion from shared decimals to local chain decimals during the reception of tokens on the destination chain.There is no validation which ensures that the configured token has matching decimals to the one provided.
If a misconfiguration were to occur during deployment then a Critical issue could arise where USDT amounts are delivered on the affected destination chain at orders of magnitude higher than the amount sent.
Recommendation
Consider querying the token decimals directly instead of specifying them in the constructor similar to how it is done in the
OFTAdapterUpgradeable LayerZerocontract withIERC20Metadata(_token).decimals().Resolution
USDT0 Team: The issue was resolved in commit 7dd2007.
-
L-05 Low Burned OFTExtension Tokens Leaves USDT In Adapter Unexpected Behavior Acknowledged
Description
The
OFTExtensiontoken has methods to burn token amounts outside of OFT transfers such as theredeemanddestroyBlockedFunds onlyOwnerfunctions.When
OFTExtensiontokens are burned on remote chains, the corresponding USDT tokens which back these burned tokens remain in theOAdapterUpgradeablecontract on Ethereum.This behavior can be misleading as the
totalSupplyof USDT on Ethereum can misrepresent all of the USDT which is able to be bridged back to Ethereum.Recommendation
If this behavior is not desired than be sure to burn the corresponding amount of USDT from the
OAdapterUpgradeablecontract on Ethereum when burningOFTExtensionamounts on remote chains.Otherwise be aware of this discrepancy when relying on the
totalSupplyof USDT or reading the balance of theOAdapterUpgradeablecontract on Ethereum.Resolution
USDT0 Team: Acknowledged.
-
L-06 Low Msg.value Is Lost With lzReceive Unexpected Behavior Acknowledged
Description
If users specify
msg.valuein the message execution options, this value will be passed by the executor while callinglzReceiveon the destination chain. The value will be lost as it just ends up as a balance ofOUpgradeableorOAdapterUpgradeablewith no way of retrieving it.Recommendation
The balance of
OUpgradeableorOAdapterUpgradeablecan be retrieved by adding an admin function to transfer the balance to the owner. Otherwise, make sure to document this behavior in the docs.Resolution
USDT0 Team: Acknowledged.
-
L-07 Low USDT Pause Causes Cross-chain Messages To Fail Unexpected Behavior Acknowledged
Description
The USDT contract on Ethereum can pause transfers. If this happens, any in-flight messages will fail due to the inability to transfer USDT from the
OAdapterUpgradeablecontract.Recommendation
If USDT is paused on Ethereum, cross-chain messages and bridged USDT should be paused on all the connected chains.
Resolution
USDT0 Team: Acknowledged.
-
I-01 Informational Unnecessary Imports Best Practices Resolved
Description
The
OUpgradeablecontract file includes several unnecessary imports from the@layerzerolabs/oft-evm-upgradeablepackage which are currently unused:MessagingFeeSendParamOFTReceipt
Recommendation
Remove these unnecessary imports.
Resolution
USDT0 Team: The issue was resolved in commit 85787a7.
-
I-02 Informational Missing disableInitializers Best Practices Resolved
Description
In the
OFTAdapterUpgradeableandOUpgradeablecontracts the constructors do not include calls to_disableInitializersto disable theinitializerfunctions from being called on the implementation contract directly.This is not a significant risk as the upgradeable contracts are not UUPS proxies, however it is a best practice to disable the initializers for upgradeable contracts.
Recommendation
Consider including a call to the
_disableInitializersfunction in the constructor for theOFTAdapterUpgradeableandOUpgradeablecontracts.Resolution
USDT0 Team: The issue was resolved in commit 18c2b4d.
-
I-03 Informational Memory Arguments Can Be Calldata Optimization Acknowledged
Description
The
updateNameAndSymbolfunction accepts twostring memoryparameters which are never modified and only written to storage. These parameters do not need to be copied into memory and can instead remain as references to calldata.Recommendation
Consider using the
calldatalocation for the_nameand_stringparameters in theupdateNameAndSymbolfunction.Resolution
USDT0 Team: Acknowledged.
-
I-04 Informational Style Inconsistencies Informational Resolved
Description
Indentation of functions in the
TetherTokenOFTExtensioncontract is inconsistent. Inconsistent parameter naming convention inTetherTokenOFTExtensioncontract:mintfunction uses_destinationwhileburnusesfromwithout underscore.Recommendation
Fix the style inconsistencies by formatting the code properly and having a consistent naming convention.
Resolution
USDT0 Team: The issue was resolved in commit f241b88.
-
I-05 Informational ERC20PermitUpgradeable Draft Used Best Practices Acknowledged
Description
The version of
ERC20PermitUpgradeableused for theTetherTokencontract is an outdated draft state version fromOpenZeppelin.Though no issues have been identified with this draft version of the
ERC20PermitUpgradeablecontract, the most up to date non-draft version of theOpenZeppelin ERC20PermitUpgradeablecontract should ideally be used, accompanied by a Solidity version bump to a more recent version of Solidity.Recommendation
Consider updating the version of the
OpenZeppelin ERC20PermitUpgradeablecontract that is used as well as the version of Solidity that is used.Resolution
USDT0 Team: Acknowledged.
-
I-06 Informational Enforced Execution Options Recommendations Best Practices Acknowledged
Description
LayerZero configuration files set the
gasvalue forExecutorOptionType.LZ_RECEIVEandExecutorOptionType.COMPOSEoptions to 80k.msgTypeequal to1with option typeExecutorOptionType.LZ_RECEIVEis the case whensendComposeis not called on the destination chain and will have lower gas requirements than themsgTypeequal to2with option typeExecutorOptionType.LZ_RECEIVEwhich does callsendCompose.The maximum message bytes size in the default LayerZero send library is
10_000bytes. The worst case scenario is whensendComposeis called on the destination chain within thelzReceivefunction with the maximum composed message size -- which is9980bytes as each message encodes theamount,msg.senderandreceiver. This will add significantly to the total gas due to manipulating bytes in memory and saving the messages in the LayerZero contracts for later execution.With very long messages, the total gas requirements for
lzReceivecan be as high as ~230k gas.msgTypeequal to2with option typeExecutorOptionType.COMPOSEis the case whenlzComposeis called on the destination chain in a separate transaction. Anyone is free to define their own contract that implementslzComposeand setting gas to80kmight be too high if the logic insidelzComposeis very simple.Recommendation
Benchmark the gas requirements for each option and message type on mainnet and enforce the gas limit to ensure there are no failed messages. The 80k gas limit for
ExecutorOptionType.LZ_RECEIVEwith composed messages is insufficient, as testing showed longer messages can exceed this threshold. Consider increasing the limit while taking into account the trade-off between ensuring there are no pending messages and the increased gas costs for users that are sending short messages for compose.For example, with 100 bytes of data for a composed message the
lzReceivefor the non-adapter OFT was measured at over 83,000 gas in testing. Considering that it may be common for users to include messages of around or over 100 bytes with composed messages, you might consider adjusting the enforced gas requirement for composed messages to be 100,000 units to be safest and cover longer composed messages by default. -
I-07 Informational OFTExtension Minted Can Be Unbacked Best Practices Acknowledged
Description
The system was reviewed with the assumption that
USDTtokens get locked on Ethereum and are bridged to other chains, Ink and Berachain.If we assume that bridging is allowed in all directions the system should work fine as every
USDTon Ink/Berachain must have originated from EthereumUSDTbeing locked first.If there is bridging between Ink/Berachain, tokens are burned on source and minted on destination.
As the admin of the
OFTExtensioncontract can set any address to mint its tokens, there is a risk of minting tokens that are not backed byUSDTon Ethereum.Recommendation
Make sure that the
OFTExtensioncontract only mints tokens that are backed by USDT on Ethereum.Resolution
USDT0 Team: Acknowledged.
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.
