[M-01] Loss Of UXD And UXP Tokens During Cross-Chain Bridging
Summary
The UXD and UXP are Omnichain Fungible Tokens (OFTs), allowing users to bridge the tokens from a source chain to a destination chain through the LayerZero protocol.
I found that the bridging-related contracts used by the UXD protocol to interact with the LayerZero protocol lack proper sanitization checks on the target address (_toAddress), the address that is the token receiver on the destination chain.
As a result, this vulnerability can cause users to lose their UXD and UXP tokens during the cross-chain bridging processes. Moreover, this vulnerability also causes the state inconsistency issue in the UXDToken contract.
Vulnerability Detail
The UXD protocol employs a set of bridging contracts, including LzApp, NonblockingLzApp, OFTCore, and OFT, to bridge UXD and UXP tokens from a source chain to a destination chain.
The below snippet illustrates an execution flow of bridging-related functions when a user on a source chain triggers the cross-chain token bridging process.
The snippet below shows the OFTCore.sendFrom() function (L31 - 33). This function is the entry point invoked by a user on a source chain.
The function parameter that is the root cause of this vulnerability is _toAddress, encoded in bytes.
Later, the OFTCore.sendFrom() function will execute the internal function OFTCore._send() (L32). The _send() function (L53 - 62) encodes the lzPayload (L58) by executing the following statement:
As you can see, the _toAddress parameter is encoded into the lzPayload without validation. If the address(0) encoded in bytes is passed to the _toAddress parameter, the bridging-related contracts on a source chain would not be detectable.
Furthermore, the _send() function also invokes the OFT._debitFrom(_from, _dstChainId, _toAddress, _amount) function (L56). Finally, the _send() function will invoke the LzApp._lzSend() function (L59) to trigger the lzEndpoint, the relayer of a destination chain.
After the bridging transaction on a source chain is successfully executed, the lzEndpoint, a bridge relayer of the destination chain, will be triggered.
The lzEndpoint invokes the LzApp.lzReceive() function to initiate the OFT token minting process on the destination chain.
The below snippet depicts an execution flow of bridging-related functions triggered by the lzEndpoint on the destination chain.
The below snippet shows the OFTCore._sendAck() function, one of the bridging-related functions on the destination chain.
The OFTCore._sendAck() function will decode the payload previously encoded at the source chain (L65 - 67). At this point, the target address (to) indicating the token receiver's address is decoded (L67).
Later, the OFT._creditTo(_srcChainId, to, amount) function will be executed (L69).
The following snippet shows the OFT._creditTo() function. This function would invoke the ERC20._mint(_toAddress, _amount) function (L34) to mint the OFT tokens on the destination chain.
The snippet below presents the ERC20._mint() function. At this point, the address(0) would be passed to the account parameter, making the bridging transaction on the destination chain revert in L258.
This vulnerability causes the OFT token minting transaction to revert on a destination chain. In other words, the token receiver would not receive any token from the bridge. Whereas the OFT tokens on a source chain would be burned, making the token sender lose their tokens permanently.
Furthermore, since the UXP is the UXD protocol's governance token, the loss of UXP tokens can also cause a negative impact on the decentralization of the protocol.
Moreover, in the case of the UXD token bridging, I discovered that this vulnerability also causes the state inconsistency issue in the UXDToken contract. Specifically, since the UXD tokens on a source chain would be unexpectedly burned permanently, I noticed that the state variable localMintAmount of the UXDToken contract would not be updated (decreasing) as expected. This could eventually affect the UXD token mint accounting process.
Even if the likelihood is considered LOW, nevertheless, it is possible due to several factors, such as users' mistakes, UXD protocol's front-end or back-end errors, or errors from other protocols that utilize the UXD and UXP tokens.
For the impact of this vulnerability is considered HIGH. Therefore, the risk of this vulnerability is MEDIUM.
I recommend adding a sanitization check in the OFTCore.sendFrom() function (the entry function on the source chain) like L32 - 33 in the snippet below.
SNIPPET: 8
FILE: https://github.com/sherlock-audit/2023-01-uxd/blob/main/contracts/external/layer-zero/token/oft/OFTCore.sol
LOCATIONS: L32 - 33
31: function sendFrom(address _from, uint16 _dstChainId, bytes calldata _toAddress, uint _amount, address payable _refundAddress, address _zroPaymentAddress, bytes calldata _adapterParams) public payable virtual override {
32: * address to = _toAddress.toAddress(0);
33: * require(to != address(0), "LzApp: send to the zero address");
34:
35: _send(_from, _dstChainId, _toAddress, _amount, _refundAddress, _zroPaymentAddress, _adapterParams);
36: }
serial-coder
medium
[M-01] Loss Of UXD And UXP Tokens During Cross-Chain Bridging
Summary
The
UXD
andUXP
are Omnichain Fungible Tokens (OFTs), allowing users to bridge the tokens from a source chain to a destination chain through the LayerZero protocol.I found that the bridging-related contracts used by the UXD protocol to interact with the LayerZero protocol lack proper sanitization checks on the target address (
_toAddress
), the address that is the token receiver on the destination chain.As a result, this vulnerability can cause users to lose their
UXD
andUXP
tokens during the cross-chain bridging processes. Moreover, this vulnerability also causes the state inconsistency issue in theUXDToken
contract.Vulnerability Detail
The UXD protocol employs a set of bridging contracts, including
LzApp
,NonblockingLzApp
,OFTCore
, andOFT
, to bridgeUXD
andUXP
tokens from a source chain to a destination chain.The below snippet illustrates an execution flow of bridging-related functions when a user on a source chain triggers the cross-chain token bridging process.
The snippet below shows the
OFTCore.sendFrom()
function (L31 - 33). This function is the entry point invoked by a user on a source chain.The function parameter that is the root cause of this vulnerability is
_toAddress
, encoded inbytes
.Later, the
OFTCore.sendFrom()
function will execute the internal functionOFTCore._send()
(L32). The_send()
function (L53 - 62) encodes thelzPayload
(L58) by executing the following statement:lzPayload = abi.encode(PT_SEND, _toAddress, amount);
As you can see, the
_toAddress
parameter is encoded into thelzPayload
without validation. If theaddress(0)
encoded inbytes
is passed to the_toAddress
parameter, the bridging-related contracts on a source chain would not be detectable.Furthermore, the
_send()
function also invokes theOFT._debitFrom(_from, _dstChainId, _toAddress, _amount)
function (L56). Finally, the_send()
function will invoke theLzApp._lzSend()
function (L59) to trigger thelzEndpoint
, the relayer of a destination chain.The below snippet presents the
OFT._debitFrom()
function. This function will burn the user's OFT tokens (i.e., UXD and UXP) on a source chain (L29).After the bridging transaction on a source chain is successfully executed, the
lzEndpoint
, a bridge relayer of the destination chain, will be triggered.The
lzEndpoint
invokes theLzApp.lzReceive()
function to initiate the OFT token minting process on the destination chain.The below snippet depicts an execution flow of bridging-related functions triggered by the
lzEndpoint
on the destination chain.The below snippet shows the
OFTCore._sendAck()
function, one of the bridging-related functions on the destination chain.The
OFTCore._sendAck()
function will decode the payload previously encoded at the source chain (L65 - 67). At this point, the target address (to
) indicating the token receiver's address is decoded (L67).Later, the
OFT._creditTo(_srcChainId, to, amount)
function will be executed (L69).The following snippet shows the
OFT._creditTo()
function. This function would invoke theERC20._mint(_toAddress, _amount)
function (L34) to mint the OFT tokens on the destination chain.The snippet below presents the
ERC20._mint()
function. At this point, theaddress(0)
would be passed to theaccount
parameter, making the bridging transaction on the destination chain revert in L258.Impact
This vulnerability causes the OFT token minting transaction to revert on a destination chain. In other words, the token receiver would not receive any token from the bridge. Whereas the OFT tokens on a source chain would be burned, making the token sender lose their tokens permanently.
Furthermore, since the
UXP
is the UXD protocol's governance token, the loss ofUXP
tokens can also cause a negative impact on the decentralization of the protocol.Moreover, in the case of the
UXD
token bridging, I discovered that this vulnerability also causes the state inconsistency issue in theUXDToken
contract. Specifically, since theUXD
tokens on a source chain would be unexpectedly burned permanently, I noticed that the state variablelocalMintAmount
of theUXDToken
contract would not be updated (decreasing) as expected. This could eventually affect theUXD
token mint accounting process.Even if the likelihood is considered LOW, nevertheless, it is possible due to several factors, such as users' mistakes, UXD protocol's front-end or back-end errors, or errors from other protocols that utilize the
UXD
andUXP
tokens.For the impact of this vulnerability is considered HIGH. Therefore, the risk of this vulnerability is MEDIUM.
Code Snippet
https://github.com/sherlock-audit/2023-01-uxd/blob/main/contracts/external/layer-zero/token/oft/OFTCore.sol#L31-L33
https://github.com/sherlock-audit/2023-01-uxd/blob/main/contracts/external/layer-zero/token/oft/OFTCore.sol#L53-L62
https://github.com/sherlock-audit/2023-01-uxd/blob/main/contracts/external/layer-zero/token/oft/OFT.sol#L26-L31
https://github.com/sherlock-audit/2023-01-uxd/blob/main/contracts/external/layer-zero/token/oft/OFTCore.sol#L64-L71
https://github.com/sherlock-audit/2023-01-uxd/blob/main/contracts/external/layer-zero/token/oft/OFT.sol#L33-L36
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/6a8d977d2248cf1c115497fccfd7a2da3f86a58f/contracts/token/ERC20/ERC20.sol#L260
Tool used
Manual Review
Recommendation
I recommend adding a sanitization check in the
OFTCore.sendFrom()
function (the entry function on the source chain) like L32 - 33 in the snippet below.