USDT0 engaged Guardian to review the security of their USDT0 multihop contract. From the 6th of February to the 10th of February, a team of 2 auditors reviewed the source code in scope.
- Published
- Review window
- February 6 to 10, 2025
- Language
- Solidity
- Chains
- Ethereum, Arbitrum, Ink, Hyperliquid, Polygon, Monad, Solana, Stellar
- Sector
- Stablecoins
- 0 Critical
- 0 High
- 1 Medium
- 8 Low
- 16 Informational
Scope
Overview
USDT0 engaged Guardian to review the security of their USDT0 multihop contract. From the 6th of February to the 10th of February, a team of 2 auditors reviewed the source code in scope.
Findings 25
-
M-01 Medium Funds Trapped On Ethereum DoS Resolved
Description
The
MultiHopComposerV1contract uses an interface which returns a boolean value for theapprovefunction, however USDT on Ethereum does not return a boolean from theapprovefunction.This results in a revert upon approval which will cause the compose message to be un-executable, thereby trapping the USDT in the
MultiHopComposerV1contract.Recommendation
For
MultiHopComposerV1deployment on Ethereum mainnet use anIERC20interface which does not include a boolean return value for theapprovefunction.Resolution
USDT0 Team: Resolved.
-
L-01 Low retrySend Is Unusable For Users With Multiple Failures Unexpected Behavior Acknowledged
Description
The
retrySendfunction forces the user to send their entireamountOwedbalances in a singleoft.sendcall.However the user may have had multiple failed hops, in which case the
amountNativewould be too large for a singleoft.sendcall and theamountTokenamount could be larger than the total amount the user wishes to send to any single receiving address.For example:
- Bob initiates a composed hop A which is to be sent to Alice on Chain 1
- Bob initiates a composed hop B which is to be sent to Carol on Chain 2
- Both hop A and B fail and are stored within the
amountOwedmapping - It is impossible for Bob to retry both of these hops individually since they are grouped into the same
amountOwedentryRecommendation
Consider allowing users to specify what amount of both tokens they would like to use from their
amountOwedmapping entry for theretrySendcall.Resolution
USDT0 Team: Acknowledged.
-
L-02 Low Excess Ether Cannot Be Easily Retrieved Unexpected Behavior Resolved
Description
In the
lzComposefunction the refund address in theoft.sendcall is assigned as theMultiHopComposerV1contract. As a result excessmsg.valuesent to thelzComposefunction remains in theMultiHopComposerV1contract.There is a function for the owner to reclaim native assets from the
MultiHopComposerV1contract, however this function only allows sweeping the entire native balance which includes refunds for failed OFT transfers which should be claimable by those users.As a result it is unwieldy for the owner to claim this excess Ether.
Recommendation
Consider sending the excess value to a dedicated refund receiver address, or implement logic to track the totality of pending user native refunds and implement a function which allows the owner to withdraw only the excess native tokens which were refunded from the
oft.sendcall.Resolution
USDT0 Team: Resolved.
-
L-03 Low Force Retry With Insufficient Message Value Censoring Acknowledged
Description
The
lzComposefunction in theLayerZero EndpointV2contract can be called by anyone and simply validates that the message contents are the same as those which were sent by theOApp.Therefore any arbitrary address can force a retry by executing the compose message with a value less than the
MessagingFeespecifies, causing aNotEnoughNativerevert and_handleErrorto be triggered.A malicious actor can observe that a composed message to the
MultiHopComposerV1contract has been posted, or force it to be posted themselves by executing the parentlzReceivemessage, and then use their own maliciouslzComposeinvocation to force theOft.sendcall to fail.Recommendation
Ensure the
msg.valuematches the fee.Resolution
USDT0 Team: Acknowledged.
-
L-04 Low Force Retry With Insufficient Gas Censoring Acknowledged
Description
The
lzComposefunction in theLayerZero EndpointV2contract can be called by anyone and simply validates that the message contents are the same as those which were sent by theOApp.Therefore any arbitrary address may invoke the
lzComposefunction for aMultiHopComposerV1compose call with an amount of gas such that theoft.sendfunction reverts with out of gas, but the rest of the execution completes due to thereservedGas.A malicious actor can observe that a composed message to the
MultiHopComposerV1contract has been posted, or force it to be posted themselves by executing the parentlzReceivemessage, and then use their own maliciouslzComposeinvocation to force theOft.sendcall to fail.Recommendation
Consider adding validation to the
lzComposefunction that requires that a sufficient amount of gas has been provided by the caller to successfully execute theoft.sendcall.Resolution
USDT0 Team: Acknowledged.
-
L-05 Low Blacklisted Addresses May Retrieve Funds Unexpected Behavior Acknowledged
Description
Through the
lzComposefunction USDT tokens may be credited to an arbitraryevmRefundAddresswith funds in theamountOwed. TheevmRefundAddressmay be blacklisted at the time of thelzComposeexecution or may be blacklisted in the future.In either case, the blacklisted address is able to reclaim their funds through either the
retrieveFundsorretrySendfunctions.Recommendation
Consider adding validation to require that the user is not blacklisted in the
retrieveFundsandretrySendfunctions.Resolution
USDT0 Team: Acknowledged.
-
L-06 Low Reentrancy Checks Abused For Censoring Censoring Acknowledged
Description
The
sendfunction in theStarGatePoolcontract invokes thesendTokenfunction which uses thenonReentrantAndNotPausedmodifier.If any functions in the
StarGatePoolcontract with thenonReentrantAndNotPausedmodifier have been entered into then any subsequent calls tosendwill revert due to the reentrancy check.This behavior can be leveraged to censor composed hops by causing them to fail on the
Oft.sendfunction.A malicious actor can observe that a composed message to the
MultiHopComposerV1contract has been posted, or force it to be posted themselves by executing the parentlzReceivemessage, and then use their own maliciouslzComposeinvocation to force theOft.sendcall to fail.The malicious actor can first invoke their own dummy
StarGatePool.sendinvocation to trigger the reentrancy guard, and then inside of that function call, during the native refund callback to their address.The malicious actor can invoke the
lzComposefunction on theEndpointV2contract to trigger theMultiHopComposerV1.lzComposefunction which will then make a call to theStarGatePool.sendfunction that fails due to a reentrant call.The user’s send is then censored and they are forced to retry their send with the
retrySendfunction.Recommendation
If the target
oftaddress is theStarGatePool, consider checking if the publicstatusvalue isENTERED, and if so reverting thelzComposetransaction rather than censoring the transaction for the user. Otherwise thelzComposefunction can validate that the executor is a trusted executor.Resolution
USDT0 Team: Acknowledged. 16
-
L-07 Low Unexpected Zero Amount Oft Sends Validation Acknowledged
Description
The
retrySendfunction does not validate that the sender has a nonzero token balance in theamountOwedmapping before triggering the OFT send. As a result any user may call theretrySendfunction and trigger an OFT send from theMultiHopComposerV1contract with a zero token amount.This may be unexpected, particularly since the user can send composed messages from the
MultiHopComposerV1along with this zero token amount.Recommendation
Consider validating that the user has a nonzero
amountTokenin theretrySendfunction.Resolution
USDT0 Team: Acknowledged.
-
L-08 Low Insufficient Reserved Gas Warning Resolved
Description
With additional logic inside of the
forceApprovefunction and the added gas for an external token approval call, thereservedGasis not sufficient for the_handleErrorfunction and forced approval to occur in the event that the OFT send runs out of gas.Recommendation
Consider increasing the
reservedGasby a minor amount to 42,000.Resolution
USDT0 Team: Resolved.
-
I-01 Informational Unused Import Imports Resolved
Description
In the
MultiHopComposerV1theIOAppCoreinterface is imported but not used in the contract code.Recommendation
Remove the extraneous
IOAppCoreinterface import.Resolution
USDT0 Team: Resolved.
-
I-02 Informational Missing Event Events Resolved
Description
In the
retrieveFundsfunction there is an event to indicate the retrieval of the underlying token with theLogRetrieveFundsevent, but no event nor data entry in theLogRetrieveFundsevent to indicate that native value was retrieved from the contract.Similarly there is no indication of native value retrieved in the
retrySendfunction.Recommendation
Consider either adding a native value field to the
LogRetrieveFundsevent or introducing an event to indicate the retrieval of native funds in theretrieveFundsandretrySendfunction.Resolution
USDT0 Team: Resolved.
-
I-03 Informational Unused Events Superfluous Code Resolved
Description
The
MultiHopComposerV1contract contains theLogTooHighSendAmountandSwappedevents which are declared but never used in the contract.Recommendation
Implement the use case for the
LogTooHighSendAmountorSwappedevents or remove them from the contract.Resolution
USDT0 Team: Resolved.
-
I-04 Informational Lacking Zero Address Checks Validation Resolved
Description
The constructor for the
MultiHopFactoryV1contract performs no zero address validation on the_endPointaddress.Recommendation
Consider performing validation on the
_endPointparameter to ensure it is not the zero address.Resolution
USDT0 Team: Resolved.
-
I-05 Informational Outdated Documentation Documentation Partially resolved
Description
The documentation for the
constructorof theMultiHopComposerV1contract references theStableComposercontract as the contract which it constructs. However theMultiHopComposerV1is the contract which is constructed.Additionally, the documentation for the contract indicates that
The contract is intended to unwrapUSDT0 into native gas tokens. However this is not the current functionality of the contract.Recommendation
Update the comment for the
constructorto reflect that it is theMultiHopComposerV1contract which is being constructed. And update the documentation for the contract to reflect its current intended behavior.Resolution
USDT0 Team: Partially Resolved.
-
I-06 Informational Zero Transfer Attempted Superfluous Code Acknowledged
Description
Within the
retrieveFundsfunction, ifretrieveNativeis set to true but theamountTokenhas already been retrieved, the function will still attempt to transferamountTokeneven if it is a zero amount.For USDT and USDT0 implementations this is not a large concern as they do not revert on zero amount transfers, however if the
MultiHopComposerV1contract were to be used with a token that reverts on zero transfers this would prevent claiming of native funds.Recommendation
Consider only executing the token transfer if the
amountTokenis larger than zero as an optimization.Resolution
USDT0 Team: Acknowledged.
-
I-07 Informational Arbitrary Oft Address Best Practices Acknowledged
Description
The
MultiHopComposerV1contract accepts an arbitraryoftaddress as a part of the composed_messagein thelzComposeandretrySendfunctions.While no exploit has been identified with the call to an arbitrary
oftaddress, out of an abundance of caution it may be best to limit the attack surface by requiring that the providedoftaddress is explicitly whitelisted.It may be noteworthy that an untrusted oft contract can:
- Remove the approved amount of USDT from the
MultiHopComposerV1contract - Revert on purpose, causing a
_handleErrorinvocation - Consume an unexpected amount of gas
- Return malformed
returndata, causing a revert of thelzComposefunction - Re-enter into
MultiHopComposerV1functions as well as other related OFT/LayerZero systems.
Recommendation
Consider introducing a
whitelistedOftsmapping and validating theoftaddresses used against this.Resolution
USDT0 Team: Acknowledged.
- Remove the approved amount of USDT from the
-
I-08 Informational Malformed Composed Messages Trap USDT Trapped Funds Acknowledged
Description
In the
lzComposefunction in the event that theOft.sendfunction reverts a refund is stored for users with the_handleErrorfunction. However in the event that a revert occurs outside of theOft.sendfunction the user will not be able to retrieve their funds from theMultiHopComposerV1contract.Specifically, if the composed
_messageis malformed and does not contain the expectedevmRefundAddress,oft,sendParam, andmessageingFeetypes with no dirty upper bits then thelzComposewill chronically revert and the composed action can never be executed.Recommendation
Be aware of this risk and warn users and integrators to verify the correctness of their composed message structure.
Resolution
USDT0 Team: Acknowledged.
-
I-09 Informational Arbitrary Oapp Deployed Through Factory Warning Partially resolved
Description
Function
MultiHopComposerFactoryV1.createMultiHopComposertakes an arbitrary oApp address. Therefore, users can populate thecomposersmapping with a composer that uses a maliciousOappand lead to user interaction with an undesired contract and token within theMultiHopComposer.There is also risk with a block reorg that a user may believe they are interacting with a composer with a safe
Oapp, but within the context of the reorg a composer with a maliciousOAppis deployed to that address first leading to users interacting with a malicious contract unexpectedly.This is because the CREATE opcode relies solely on the sender address and nonce. Consider the following example:
(1) Alice deploys a composer with a safe Oapp A to address A. (2) Bob deploys a composer with a malicious Oapp B to address B. (3) A reorg occurs, such that Bob’s tx is included first and the malicious composer is now deployed to address A, since the sender address and nonce are the same as when Alice created a composer initially. (4) Users who thought they were interacting with Alice’s safe composer at address A now interact with Bob’s malicious composer.
Recommendation
Consider enforcing a whitelist for the
oAppor document this risk.Resolution
USDT0 Team: Partially resolved by using CREATE2 to mitigate re-org risk.
Guardian Team: Arbitrary addresses are still allowed to create arbitrary multihop contracts with 27 arbitrary OApps, this is acknowledged by the team.
-
I-10 Informational SendParam Can Differ From Initial lzCompose Warning Acknowledged
Description
The SendParam initially used for the
lzComposecan differ from the one passed by users duringretrySend.Recommendation
Be aware of this behavior.
Resolution
USDT0 Team: Acknowledged.
-
I-11 Informational Stargate Only Allows Same-Asset Transfers Documentation Acknowledged
Description
Stargate currently does not support Bera/INK USDT0 to be transferred to USDT that is on other chains such as Polygon. Therefore, users should be aware that for most USDT0 transfers Arbitrum will be the intermediate step.
Recommendation
Make users aware that these pathways must be implemented on StarGate and cannot immediately be supported.
Resolution
USDT0 Team: Acknowledged.
-
I-12 Informational Refunds Are Not Supplied Documentation Acknowledged
Description
When invoking the
oft.sendfunction throughlzComposethe refund address is theMultiHopComposer. In contrast, when sending throughretrySendthe refund address is themsg.sender.This asymmetry makes it preferable for a user to attempt a retry to ensure unused native fee is returned to them rather than donated to the
MultiHopComposerto be swept by the owner at a later time.Recommendation
It may be unpreferred to allow the user to specify a refund address for the
oft.sendinvocation given the address can make arbitrary actions, expend an unexpected amount of gas, or simply revert.Instead, the USDT0 team may consider implementing
amountOwedlogic in the receive function to credit thefromaddress of the current compose message, but only when anlzComposeinvocation is in progress.Resolution
USDT0 Team: Acknowledged.
-
I-13 Informational Arguments Can Be Calldata Optimization Acknowledged
Description
The
messagingFeefunction of theretrySendfunction is not modified and can therefore be declared ascalldata.Recommendation
Consider declaring the
messagingFeeparameter ascalldatainstead ofmemory.Resolution
USDT0 Team: Acknowledged.
-
I-14 Informational Lacking setReservedGas Validation Validation Acknowledged
Description
The
setReservedGasfunction does not implement any validation which prevents the owner from assigning thereservedGasvalue to an errantly high amount.If the
reservedGasis assigned too high it will cause underflow reverts and can potentially trap USDT funds in theMultiHopComposerV1contract.Recommendation
Consider adding maximum configurable value validation for the
setReservedGasfunction.Resolution
USDT0 Team: Acknowledged.
-
I-15 Informational retrySend DoS DoS Resolved
Description
The
oft.sendfunction requires that the providedmsg.valueis exactly the same as thenativeFeespecified by theMessagingFeeparameter.Since the value sent to the
oft.sendfunction is based uponamountNativestored in theamountOwedmapping, it is possible for a malicious actor to frontrun an attempt toretrySendand increase theamountNativeentry to DoS theretrySendinvocation.The malicious actor can achieve this with a composed message waiting in the
EndpointV2which uses the target victim address as theevmRefundAddressand sends to an invalid eid to force a send revert.The malicious actor can then frontrun the
retrySendinvocation to execute their own compose message and increment theamountNativeby even just 1 wei.Recommendation
Be aware of this frontrunning DoS vector and document it for users and integrators of the
MultiHopComposerV1.Resolution
USDT0 Team: Resolved.
-
I-16 Informational Smart Contract Claimers Incompatibility Compatibility Resolved
Description
In the
retrySendfunction therefundReceiveris hardcoded as themsg.sender. As a result smart contract addresses which do not implement a receive function cannot accept the refund and therefore cause a revert when additional native fees are provided.Users may provide an
evmRefundAddresswhich can accept native funds to avoid this, however if the providedevmRefundAddressis not compatible then the user will be forced to redeem their funds with theretrieveFundsfunction to arecipientaddress.Recommendation
Consider allowing the user to specify a
refundRecipientaddress in theretrySendfunction.Resolution
USDT0 Team: Resolved.
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.
