Attempting to swap a larger amount of tokens than what is available in the V3 pool, with didPay(), will result in loss of funds #114
Labels
2 (Med Risk)
Assets not at direct risk, but function/availability of the protocol could be impacted or leak value
bug
Something isn't working
downgraded by judge
Judge downgraded the risk level of this issue
duplicate-162
edited-by-warden
satisfactory
satisfies C4 submission criteria; eligible for awards
Lines of code
https://github.com/code-423n4/2023-05-juicebox/blob/9d0458282511ff269b3b35b5b082b56d5cc08663/juice-buyback/contracts/JBXBuybackDelegate.sol#L183
Vulnerability details
Impact
If a user sends ether, to
didPay()
, to buy more tokens than what is available in the underlying UnicornV3Pool, the difference between the eth used to buy all the available tokens in the pool, and the amount sent, will be locked in theJBXBuybackDelegate
contract. Resulting in a loss of funds for the user.Since both to project's token and the V3 pool, are controlled by the owner of the project and/or other external factors, no assumptions on the price and/or availability of the token can be made. Therefor an estimation on the amount of eth necessary for this to happen, or on the likelyhood in general, cannot be made.
Proof of Concept
The
didPay(JBDidPayData calldata _data)
functions is invoked by theJBPayoutRedemptionPaymentTerminal3_1
with the following line of code:(https://github.com/jbx-protocol/juice-contracts-v3/blob/12d852f28d372dd44987586f8009c56b0fe247a9/contracts/abstract/JBPayoutRedemptionPaymentTerminal3_1.sol#L1532).
Where
_payableValue
is the eth being send with thepay()
function by the user, to pay for the tokens.If
didPay
ends up calling into the_mint()
function available in the same contract. This eth will be sent back to the terminal as payment for minting the tokens.If
didPay
performs a swap in the V3 pool to acquire the projectTokens, theuniswapV3SwapCallback
function will convert the eth requested by the pool, into wEth, and than transfers it as payment. (https://github.com/code-423n4/2023-05-juicebox/blob/9d0458282511ff269b3b35b5b082b56d5cc08663/juice-buyback/contracts/JBXBuybackDelegate.sol#L231). No other actions will be performed on the value sent by the user along with thepay()
request. Therefor any eth unused by theuniswapV3SwapCallback
function will be locked in the contract.If the amount of token available in the pool is lesser than the amount requested, uniswap will send all the available tokens to the caller. And the
uniswapV3SwapCallback
, will only spend the amount of eth necessary to pay for them. The difference between what is being paid for the swap and what has been sent, will be locked in the contract.Here is a foundry test that you can run. It will check weather the eth balance of
JBXBuybackDelegate
is equal to 0 before the transaction, and greater than zero after the user attempts to buy a large amount of tokens (since no assumption can be made on the availability and price of the tokens can be made, I've used unrealistic numbers in this script for simplicity and clarity).If you run this test with the
-vvv
option it will log the following content to the console:Recommended Mitigation Steps
Although the user can adjust the splippage, the contract should send back any leftover eth after it finishes all it's operations.
Assessed type
ETH-Transfer
The text was updated successfully, but these errors were encountered: