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
User lacks slippage protection if the swap fails #14
Comments
dmvt marked the issue as duplicate of #7 |
dmvt marked the issue as not a duplicate |
dmvt marked the issue as low quality report |
Slippage protection exists. User passes the value of quote in params. |
dmvt marked the issue as unsatisfactory: |
I think the issue was miss-judged I do not agree with the label of low quality report since it explains the issue of having to set @dmvt you mentioned that a slippage exists as a param, but the issue clearly describes that This issue is a duplicate of #232 Quoting issue 232 "Medium. The current design forces the user to set _minReturnedTokens to zero and disables any type of check over the amount of minted tokens." |
dmvt marked the issue as satisfactory |
dmvt marked the issue as duplicate of #232 |
You're correct. Good catch. |
Lines of code
https://github.com/code-423n4/2023-05-juicebox/blob/9a36e5c8d0588f0f262a0cd1c08e34b2184d8f4d/juice-buyback/contracts/JBXBuybackDelegate.sol#L205
Vulnerability details
Impact
There exists an inconsistency on the
JBPayoutRedemptionPaymentTerminal3_1.sol
contract when managing slippage of minted project tokens when theBuybackDelegate.sol
contract is also used.The
pay()
function at the terminal contract has a parameter named_minReturnedTokens
where a user can specify the minimum project tokens to get minted, if not, it will revert. This slippage is important since in Ethereum you do not have guarantees when your tx will get confirmed.The problem is that in order to take advantage of the feature of
BuybackDelegate.sol
thepay()
function must be called with_minReturnedTokens
as zero, so the user now does not have slippage protection on the potentially minted tokens, and theBuybackDelegate.sol
does have slippage protection for the swap, but it does not have slippage protection if the swap fails and the user gets minted tokens instead.Proof of Concept
Imagine a scenario where a user wants to contribute 1 ETH to the JX project. The front-end (or the user if he does not want to use a front end) reads the quote of the current Uniswap V3 pool and sees that it has a better quote (10,500 tokens for example) than the current
weight
(10,000 tokens for example). So, it crafts a transaction for thepay()
function, it puts the quote and the swap slippage (minimum 10,001 tokens) on the metadata and sets_minReturnedTokens
to zero.It happens that the user tx does not get executed right away because of network congestion, or because the tx was held maliciously. During this time a lot can happen for example that
weight
has decreased (from 10,000 to 9,000 for example) because of a re-configuration or just the standard decrease after a funding cycle, also the quote of the uniswap pool can change (from 10,500 to 9,500 for example), regarding the uniswap pool more things can happen, for example liquidity decrement is also a possibility. When the user tx gets finally executed, the swap will fail because the slippage was set to get at least 10,001 tokens no 9,500. So, the user will get 9,000 minted tokens.The problem is that the user never got the chance to specify a slippage of minted tokens in case the swap fails due to the non-determinism of transaction execution on Ethereum. Maybe he would not have contributed for only 9,000 tokens.
I wrote a PoC to show the behaviour described, it is important to note that on the test the user "pay()" transaction was sent after the changes on the
weight
and to the univ3 pool, because the tests are sequential, but in reality what can happen is that the tx was sent before but not executed if not after the changes described.1.- Under the
DelegateE2E.t.sol
test add the following state variables:2.- On the
setUp()
function write the following when minting the liquidity:(liquidityNFT, initialLiquidity, , ) = INonfungiblePositionManager(POSITION_MANAGER).mint(params);
3.- Copy and paste the following test:
4.- run:
forge test --match-contract TestIntegrationJBXBuybackDelegate --match-test test_noSlippageProtection
Tools Used
Manual Review
Recommended Mitigation Steps
Allow users set a minimum amount of tokens to get minted if the swap fails.
Assessed type
Other
The text was updated successfully, but these errors were encountered: