-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Users can use the protocol freely without paying any fees by calling the DecentEthRouter::bridgeWithPayload()
function directly.
#647
Comments
raymondfam marked the issue as sufficient quality report |
raymondfam marked the issue as duplicate of #15 |
raymondfam marked the issue as not a duplicate |
raymondfam marked the issue as duplicate of #51 |
This report details a clear flow on how bridge dodging can be used to avoid fees. |
raymondfam marked the issue as high quality report |
alex-ppg marked the issue as selected for report |
The Warden has demonstrated that it is possible to bypass Decent fees when performing bridging operations by directly interacting with the I believe a severity of medium is more appropriate given that uncaptured profit is solely affected. |
alex-ppg marked the issue as satisfactory |
alex-ppg changed the severity to 2 (Med Risk) |
Hi @alex-ppg, thank you for the detailed response. This issue is invalid since the decent bridge does not charge any bridge fees in the first place. If we look at the modifier retrieveAndCollectFees, it uses the We can see this expected behaviour here. When the bridgedAmount() function is called on the adapter, the decentBridgeAdapter just returns the same amount without any bridge fees applied (see here), while the stargateBridgeAdapter applies the bridgeFee correctly here. Please consider re-evaluating this issue. Thank you. |
That doesn't make any sense. In the bypassed retrieveAndCollectFees function natspec, it is clearly saying: "Transfers fees from the sender to UTB, and finally to the Fee Collector" Additionally, if we look at the docs, here is it's saying: Do you understand from all this that there are no fees being collected? |
Hi @stalinMacias, thank you for the comment. The struct FeeStructure {
uint bridgeFee;
address feeToken;
uint feeAmount;
} The decent bridge not taking bridge fees is something that the sponsor had confirmed as well (although in my private thread). It would be good if the judge clarifies this separately with the sponsor since it would be against the code of conduct for me to introduce information from the private thread (though I'm willing to invite anyone to it for proof). |
@mcgrathcoutinho Let's break this down.
@mcgrathcoutinho I hope this explanation clears any doubts you may had about the report. |
Hey @mcgrathcoutinho and @stalinMacias, thank you for contributing to this thread. Regardless of what the Sponsor shared in private discussions, all material in the documentation as well as the code of the project points to a fee being charged. The entire presence of the Based on the above, my original ruling on this issue stands. |
wkantaros (sponsor) confirmed |
Lines of code
https://github.com/decentxyz/decent-bridge/blob/7f90fd4489551b69c20d11eeecb17a3f564afb18/src/DecentEthRouter.sol#L148-L194
Vulnerability details
The execution flow of
bridgeAndExecute
functionTo understand the vulnerability, we need to understand the execution flow of the
bridgeAndExecute()
function, at least a small portion of it.When the user wants to bridge tokens of him and execute an action on another chain, he will need to execute the
UTB::bridgeAndExecute()
function.Suppose the user exists in Polygon, he has USDC and he wants to mint an NFT in Optimism which costs 1000 DAI. What will happen is that the protocol will first, in polygon, swap the user's USDC with WETH, then bridge the WETH to Optimism, then swap the WETH with DAI and then execute the arbitrary call the user wants to execute, which will be to mint the NFT in exchange for the resulting 1000 DAI from the post-bridge swap operation.
When this function is called, the following will happen:
Step 1: When the user calls the
UTB::bridgeAndExecute()
function, it will do three things: first, it will collect the fees by calling theUTBFeeCollector:collectFees()
function, secondly, it will conduct the pre-bridge swap operation (occurs in the source destination), it will swap the user's USDC to WETH. thirdly, it will modify theswapInstructions
which the user supplied to prepare for the post-bridge swap. Then after all of the 3 operations take place, it will invoke theUTB::callBridge()
function.Step 2: In the
UTB::callBridge()
function, some approvals are granted to theDecentBridgeAdapter
contract, and then the it will invoke the functionDecentBridgeAdapter::bridge()
in theDecentBridgeAdapter
contract.Step 3: In the
DecentBridgeAdapter::bridge()
function, some data like the post-bridge swap payload and bridge payload (what to execute when the TX reaches destination) will be encoded, then it will reach out to theDecentEthRouter
contract and invoke the functionDecentEthRouter::bridgeWithPayload
Step 4: When the execution reaches the
DecentEthRouter::bridgeWithPayload
function, an internal function containing the actual logic, with the same name will also be called:DecentEthRouter::_bridgeWithPayload
Note: Notice that the `DecentEthRouter::bridgeWithPayload() function isn't protected by any modifiers, any body can call it directly
Step 5: When the execution gets inside the
DecentEthRouter::_bridgeWithPayload
function, the function will prepare theLzCallParams
for the layerzero call and the actual bridging will happen when thedcntEth::sendAndCall
function is actually invoked.Step 6: The bridging process kickstarts and the execution flow is continued in the destination chain.
Here is a graph of the execution flow

The vulnerability & PoC
The vulnerability is that any user can conduct the pre-bridge swap operation using uniswap by himself, prepare the right calldata and call the function
DecentEthRouter::bridgeWithPayload
directly (since anybody can call it), doing so will allow the user to bypass the fee collection process completely.The fee collection as mentioned in the previously detailed execution flow, happens when the user first calls the
bridgeAndExecute()
function. But as I mentioned, nothing really forces him to start execution from there. All that function does is collect the fees, conduct the pre-bridge swap operation and prepare the proper call data. The user can conduct the pre-bridge swap operation himself, prepare the proper calldata and talk directly to theDecentEthRouter::bridgeWithPayload
function, effecitvely bypassing fee collection.Anybody can call the
DecentEthRouter::bridgeWithPayload
directly, and the protocol has no mechanism of determining whether or not the user is using the protocol with or without paying fees.Impact
Users can use the protocol without paying any fees.
Tools Used
Manual Audit
Recommended Mitigation Steps
Tighten up the access control on the
DecentEthRouter::bridgeWithPayload
function. Allow only the DecentBridgeAdapter to call the bridgeWithPayload() function.Found & reported by: sin1st3r__
Team: NPCsCorp
Assessed type
Access Control
The text was updated successfully, but these errors were encountered: