code-423n4 / 2023-05-juicebox-findings

1 stars 1 forks source link

`_minReturnedTokens` in `pay()` will always have to be set to 0 which in some cases can result in unexpected return for the payer. #36

Closed code423n4 closed 1 year ago

code423n4 commented 1 year ago

Lines of code

https://github.com/code-423n4/2023-05-juicebox/blob/9d0458282511ff269b3b35b5b082b56d5cc08663/juice-buyback/contracts/JBXBuybackDelegate.sol#L166 https://github.com/jbx-protocol/juice-contracts-v3/blob/12d852f28d372dd44987586f8009c56b0fe247a9/contracts/JBSingleTokenPaymentTerminalStore.sol#L409 https://github.com/jbx-protocol/juice-contracts-v3/blob/12d852f28d372dd44987586f8009c56b0fe247a9/contracts/abstract/JBPayoutRedemptionPaymentTerminal.sol#L1385 https://github.com/code-423n4/2023-05-juicebox/blob/9d0458282511ff269b3b35b5b082b56d5cc08663/juice-buyback/contracts/test/DelegateUnit.t.sol#L193

Vulnerability details

Impact

Why does _minReturnedTokens = 0 :

There is a check in the _pay() function of JBPayoutRedemptionPaymentTerminal.sol, that requires beneficiaryTokenCount to be higher or equal than _minReturnedTokens (https://github.com/jbx-protocol/juice-contracts-v3/blob/12d852f28d372dd44987586f8009c56b0fe247a9/contracts/abstract/JBPayoutRedemptionPaymentTerminal.sol#L1373-L1385C11).

JuiceBoxBuybackDelegate can return weight = 0 in payParams() if the swap path is chosen over the minting.

If using it as a delegate and data source, when weight = 0 then recordPaymentFrom() will return tokenCount = 0 which will then cause beneficiaryTokenCount to be 0.

See: https://github.com/code-423n4/2023-05-juicebox/blob/9d0458282511ff269b3b35b5b082b56d5cc08663/juice-buyback/contracts/JBXBuybackDelegate.sol#L166 and https://github.com/jbx-protocol/juice-contracts-v3/blob/12d852f28d372dd44987586f8009c56b0fe247a9/contracts/JBSingleTokenPaymentTerminalStore.sol#L409.

Because of that the only way to pass this check is to use _minReturnedTokens = 0

This is validated by the tests files (https://github.com/code-423n4/2023-05-juicebox/blob/9d0458282511ff269b3b35b5b082b56d5cc08663/juice-buyback/contracts/test/DelegateUnit.t.sol#L193).

Why is it an issue :

_minReturnedTokens is here to make sure the user will always get a minimum return when calling pay().

Because the returns on user's investement are determined by the cycle params, if these params where to change while the user transaction is in the MEM pool, the returns would get impacted.

Fortunately it seems like cycle's params are fixed for the cycle duration, protecting the user in most cases.

But if a cycle was to end and a new one to start while the transaction is being mined, the returns for the user could change if the new cycle's params are different.

Proof of Concept

Tools Used

Manual review

Recommended Mitigation Steps

A _minReturnedTokensDelegate variable could be passed in the metadata variable and then be checked in the JuiceBoxBuybackDelegate contract and revert if tokenCount is inferior.

Assessed type

Timing

c4-pre-sort commented 1 year ago

dmvt marked the issue as primary issue

simon-something commented 1 year ago

2 things:

The scenario of "the transaction is pending then current fc rolls over and the beneficiary receive less token" looks a bit super edge case (esp for a med risk)...

Agreed it should be more documented tho, as it might be confusing rn

c4-sponsor commented 1 year ago

drgorillamd marked the issue as disagree with severity

c4-judge commented 1 year ago

dmvt marked issue #232 as primary and marked this issue as a duplicate of 232

dmvt commented 1 year ago

I'm going to leave this in place as a medium. The code functions in unexpected ways based on external factors and it's actual function is very different than a user could reasonably expect. If the variable should always be zero, consider removing it. I don't agree with the assertion that including dead arguments which may be used in the future is future-proof.

Also, see here wrt protecting users from themselves.

c4-judge commented 1 year ago

dmvt marked the issue as satisfactory