Platform: Code4rena
Start Date: 16/01/2024
Pot Size: $80,000 USDC
Total HM: 37
Participants: 178
Period: 14 days
Judge: Picodes
Total Solo HM: 4
Id: 320
League: ETH
Rank: 6/178
Findings: 9
Award: $2,252.86
π Selected for report: 4
π Solo Findings: 0
π Selected for report: 00xSEV
Also found by: Banditx0x, CongZhang-CertiK, J4X, Myrault, OMEN, jesjupyter, linmiaomiao, miaowu, n1punp
264.1611 USDC - $264.16
Liquidations will be won by liquidators who manipulate the Salty reserve oracle to unfairly liquidate "almost-unhealthy" borrowers.
Liquidation is a race, where there is strong incentive for liquidators to be the first to liquidate a position. This means that if an oracle used for liquidation is even slightly manipulatable in a block, then this can be maliciously used to push a position which is near liquidation but not-yet-unhealthy into liquidation range.
The aggregated Oracle uses 3 price sources - Chainlink, Uniswap TWAP and Salty Reserves. It then takes the average of the two oracle prices that are closer together.
Chainlink and Uniswap TWAP Reserves are impractically expensive/risky to manipulate for this scenario. However, there price sources often return values which are +/- 0-5% of each other as the Uniswap TWAP is slightly time-lagged. It incorperates data up to 30 minutes from the past.
The salty reserves are very easy to manipulate, especially for the small % required to unfairly to manipulate almost-unhealty users.
For example:
Uniswap TWAP returns price (exchange rate) of $102
Chainlink oracle returns price of $100
Salty Reserves Oracle also returns price of $100
The current aggregated oracle returns average of 2 closest values which are both 100, so the result is 100.
Attacker manipulates Salty Reserves Oracle to $103
. Now, the two closest prices are $102
and $103
so the aggregated oracle now returns $102.5
Note that this scenario happens ALL THE TIME. Let's say the value of a LP position is gradually decreasing. The TWAP oracle will lag behind the chainlink price oracle as it has the time delay. Therefore the liquidation can always be won by a liquidator who manipulates the salty reserves.
Manual Review
Using 3 oracles is still recommended as if 1 oracle returns an outlier value, it can be ignored by averaging the other 2 oracles. However, an oracle other than the salty reserves should be used.
Oracle
#0 - c4-judge
2024-02-02T18:10:34Z
Picodes marked the issue as duplicate of #609
#1 - c4-judge
2024-02-21T16:20:41Z
Picodes changed the severity to 3 (High Risk)
#2 - c4-judge
2024-02-21T16:20:59Z
Picodes marked the issue as satisfactory
π Selected for report: 0xbepresent
Also found by: 00xSEV, 0xAlix2, 0xAsen, 0xBinChook, 0xCiphky, 0xRobocop, 0xanmol, 0xlemon, 0xpiken, Arz, Audinarey, Auditwolf, Aymen0909, Banditx0x, CaeraDenoir, DanielArmstrong, Draiakoo, HALITUS, Infect3d, J4X, Jorgect, Kalyan-Singh, KingNFT, Krace, PENGUN, Toshii, Udsen, ayden, b0g0, c0pp3rscr3w3r, developerjordy, djxploit, erosjohn, holydevoti0n, iamandreiski, israeladelaja, juancito, klau5, lanrebayode77, memforvik, mussucal, n0kto, novodelta, pkqs90, solmaxis69, stackachu, twcctop, zhaojie, zhaojohnson
0.7809 USDC - $0.78
Liquidations can be prevented by a user increasing or decreasing their share amount to reset the cooldown for decreasing shares.
In the liquidateUser
function, the call to _decreaseUserShare
passes in true
for the useCooldown
parameter. This means that any calls to liquidate will revert when the user's position is in cooldown.
if ( useCooldown ) if ( msg.sender != address(exchangeConfig.dao()) ) // DAO doesn't use the cooldown { require( block.timestamp >= user.cooldownExpiration, "Must wait for the cooldown to expire" ); user.cooldownExpiration = block.timestamp + stakingConfig.modificationCooldown(); }
A user can easily trigger the cooldownExpiration by calling depositCollateralAndIncreaseShare
. A user can deposit liquidity even if their position is currently liquidatable. The cooldownExpiration
for increasing and decreasing shares is the same, so this affects liquidations at all. Therefore the user can prevent liquidations on their insolvent positions indefinitely.
Manual review
The fix is simple: the bool value false
rather than true
should be used when calling the _decreaseUserShare()
function within liquidateUser
DoS
#0 - c4-judge
2024-01-31T22:46:31Z
Picodes marked the issue as duplicate of #891
#1 - c4-judge
2024-02-21T16:52:19Z
Picodes marked the issue as satisfactory
#2 - thebrittfactor
2024-02-21T21:55:34Z
For transparency, the judge confirmed issue should be marked as duplicate-312
.
π Selected for report: Udsen
Also found by: 0xfave, Banditx0x, DanielArmstrong, Draiakoo, J4X, Jorgect, ether_sky, santiellena, stackachu
79.2483 USDC - $79.25
Users get slightly more rewards than they are entitled to, especially if they make multiple withdrawals. This can cause the last withdrawal to revert as there are not enough rewards to pay them out, causing the subtraction of rewards to underflow.
Rewards are tracked from the beginning of time, and then virtual rewards are tracked for a user during the time of their deposit and then subtracted from the rewards they get when their share decreases.
Since virtualRewardsToRemove
are subtracted from a user's rewards, then a smaller reward virtualRewardsToRemove
value is in favor of the user.
We can see this error in this code snippet:
uint256 virtualRewardsToRemove = (user.virtualRewards * decreaseShareAmount) / user.userShare;
Therefore users get more rewards than they are entitled to. If all users try to withdraw, there wouldn't be enough rewards to pay out the last withdrawer.
Manual Review
The correct rounding direction for all calculations of a users virtual rewards is to round up. Use divCeil
so virtualRewardsToRemove
rounds up.
Under/Overflow
#0 - c4-judge
2024-02-03T15:17:17Z
Picodes marked the issue as primary issue
#1 - c4-judge
2024-02-06T16:28:21Z
Picodes marked issue #464 as primary and marked this issue as a duplicate of 464
#2 - c4-judge
2024-02-18T16:47:29Z
Picodes marked the issue as partial-50
#3 - c4-judge
2024-02-18T16:48:06Z
Picodes marked the issue as satisfactory
#4 - c4-sponsor
2024-02-20T03:56:36Z
othernet-global (sponsor) confirmed
#5 - othernet-global
2024-02-20T03:56:42Z
virtualRewards now rounded up on _decreaseUserShare
https://github.com/othernet-global/salty-io/commit/b3b8cb955db2b9f0e47a4964e1e4f833a447a72d
484.6392 USDC - $484.64
The minimum collateral is enforced to prevent a well known vulnerability - there is no incentive to liquidate small loans. Therefore the impact of this check being bypassed is the same - small loans will not be liquidated which can lead to bad debt in the protocol.
The minimum collateral is enforced in the when borrowing USDS, during the call to the maxBorrowableUSDS
function. However this check is not applied when the user withdraws collateral. Therefore, a loan with small collateral can be created by:
Why small loans are an issue:
The liquidation reward is based off a flat 5% of the collateral being liquidated. There is no minimum cap to the liquidation fee.
Let's have an example where gas costs are $5
. The collateral being liquidated needs to be $100 USDS
or more for the liquidation costs to be worth the reward.
However, a loan that is worth $100
at liquidation is actually worth far more than that upon time of opening. For example, opened at the minimum collateral factor of 200%, the loan is actually worth slightly less than $200
when it was opened, and then decreased to $100
. With even lower-leverage positions with 400% CF, it is reasonable that position sizes are $400
or more upon time of opening.
Manual Review
The minimum collateral should be enforced on withdrawals whenever a user has an active USDS loan.
Invalid Validation
#0 - c4-judge
2024-02-02T15:46:07Z
Picodes marked the issue as duplicate of #1010
#1 - c4-judge
2024-02-14T07:13:29Z
Picodes marked the issue as satisfactory
#2 - c4-judge
2024-02-14T07:13:36Z
Picodes marked the issue as selected for report
#3 - Picodes
2024-02-14T07:49:03Z
To me medium severity is more appropriate here under "leak value with a hypothetical attack path with stated assumptions, but external requirements" and "broken functionality".
Indeed this could lead to bad debt but the attack is hardly profitable at any point in time as the gas costs to setup small loans is expensive (you need to add collateral, borrow, repay, withdraw) and unless the oracle is flawed you can't have a guarantee that the attack will profitable.
#4 - c4-judge
2024-02-14T07:49:12Z
Picodes changed the severity to 2 (Med Risk)
#5 - c4-sponsor
2024-02-14T08:39:22Z
othernet-global (sponsor) acknowledged
#6 - othernet-global
2024-02-23T03:39:52Z
The stablecoin framework: /stablecoin, /price_feed, WBTC/WETH collateral, PriceAggregator, price feeds and USDS have been removed: https://github.com/othernet-global/salty-io/commit/88b7fd1f3f5e037a155424a85275efd79f3e9bf9
552.2953 USDC - $552.30
Stakers get slightly less weekly rewards than they should when performUpkeep
is called multiple times in a week.
The SALT rewards emitted are correct when Upkeep is called exactly every week. However the Upkeep function can be called by anybody and can be called multiple times a week.
When upkeep is called multiple times during a week, the first call distributes the correct amount of rewards (corresponding to 0.5% of rewards balance since start of the week). However, subsequent calls now use the reduced balanceOf(salt)
meaning that less rewards are distributed than there should be.
An intuitive explanation of this is that the rewards are negatively compounding per call to upkeep
. Every subsequent call of upkeep gives less rewards per unit of time.
Manual Review
The upkeep should distribute rewards based on the balance during the first call of each week to ensure that the rewards remain consistent to match emissionsWeeklyPercent
no matter how many times Upkeep is called during the week.
Math
#0 - c4-judge
2024-02-06T23:52:09Z
Picodes marked the issue as primary issue
#1 - c4-sponsor
2024-02-08T08:53:17Z
othernet-global (sponsor) acknowledged
#2 - c4-judge
2024-02-21T14:56:15Z
Picodes marked issue #243 as primary and marked this issue as a duplicate of 243
#3 - c4-judge
2024-02-21T14:56:19Z
Picodes marked the issue as satisfactory
57.772 USDC - $57.77
https://github.com/code-423n4/2024-01-salty/blob/53516c2cdfdfacb662cdea6417c52f23c94d5b5b/src/dao/DAO.sol#L316 https://github.com/code-423n4/2024-01-salty/blob/53516c2cdfdfacb662cdea6417c52f23c94d5b5b/src/dao/DAO.sol#L360
formPOL can be vulnerable to a well known liquidity deposit sandwich attack causing loss of funds for the protocol.
In formPOL
, there are no slippage parameters when calling depositLiquidityAndIncreaseShare
All liquidity deposits in a Uniswap v2 style pool can be frontrun by this sequence of steps:
Liquidity withdrawals in a x * y = k
pool are actually rarely profitable to frontrun attack (it requires certain edge cases), as the same sequence actually loses money if it is a liquidity withdrawals, which is why withdrawPOL
wasn't explicitly mentioned. However, this issue also applies to withdrawPOL
and adding slippage protection in that function could be an extra safety measure.
Manual review
formPOL
should have some form of slippage protection. This could be based off a maximum deviation off some other price feed such a Uniswap v3 TWAP or Chainlink pricefeed. If the pool reserves deviates too much from the expected ratio from the price feed, formPOL
should revert.
MEV
#0 - c4-judge
2024-02-02T10:47:06Z
Picodes marked the issue as primary issue
#1 - othernet-global
2024-02-08T08:13:19Z
The automatic arbitrage on the DEX provides some built in protection against front running as the attacker is not able to move in and out without experiencing friction from the arbitrage itself.
Simulations (see Sandwich.t.sol) show that when sandwich attacks are used on Salty, the arbitrage earned by the protocol sometimes exceeds any amount lost due to the sandwich attack itself. The actual swap loss (taking arbitrage profits generated by the sandwich swaps into account) is dependent on the multiple pool reserves involved in the arbitrage (which are encouraged by rewards distribution to create more reasonable arbitrage opportunities).
#2 - c4-sponsor
2024-02-08T10:47:26Z
othernet-global (sponsor) acknowledged
#3 - c4-judge
2024-02-19T14:12:21Z
Picodes changed the severity to 2 (Med Risk)
#4 - c4-judge
2024-02-21T15:28:09Z
Picodes marked the issue as satisfactory
#5 - c4-judge
2024-02-21T15:28:44Z
Picodes marked the issue as selected for report
#6 - Picodes
2024-02-21T15:39:13Z
Regrouping issues about missing slippage checks here as the root cause is the same - the assumption that AAA is enough to prevent MEV bots from sandwiching maintenance transactions doesn't always hold
#7 - othernet-global
2024-02-23T02:46:16Z
POL has been removed from the protocol
eaf40ef0fa27314c6e674db6830990df68e5d70e https://github.com/othernet-global/salty-io/commit/8e3231d3f444e9851881d642d6dd03021fade5ed
π Selected for report: Banditx0x
Also found by: Arz, Infect3d, Kalyan-Singh, PENGUN, Toshii, a3yip6, israeladelaja, jasonxiale, linmiaomiao, zhaojohnson
84.2914 USDC - $84.29
An attacker can inflate the value of liquidity through reserve ratio manipulation (even without manipulating the aggregated oracle) and take out a undercollateralized USDS loan.
The value of a liquidity position in Salty is determined by:
The only invariant enforced by the Uniswap v2 style AMM is $x * y = k$. When the ratio of the tokens deviates from the "correct" ratio (where the ratio corresponds exactly to the correct value of the tokens), the liquidity will always be worth more , as calculated by the USD value of the individual tokens. There is the well known frontrunning attack on liquidity deposits which works based on this fact.
Mathematical Example
Let's go through a simple example. The pool is USDT-DAI, which is selected for simplicity as they are both tokens with 18 decimals and the oracle always returns a value of $1 for both tokens.
Currently there is 1e18 tokens in each pool, and let's say that the constant k
is 1e36
Invariant formula: $x * y = k$
Let's do a swap which doubles the x
variable and recalculate the corresponding y value:
Now if we apply the correct oracle price of the tokens, which is $1
per 1e18 wei, the value of the LP position is now worth (0.5 + 2) = $2.5
, which is more , than the correct value of $2
.
An intuitive explanation of value inflation
We know that large trades that push the reserve ratio away from the correct ratio loses money for traders (this money is gained by LP's). Trades that move the reserves towards the correct ratio gain money for traders (denominated by the "real value" of the tokens). This is the basis for AMM arbitrage and slippage attacks.
Pushing the price far from the correct reserve ratio can thus be seen as a temporary inflation of the price of the LP token when the token reserves are valued by their USD denominated price. This inflation comes from the manipulating trader losing a large amount of USD-denominated value.
Using this intuitive analogy, we can see why it's only possible to inflate the LP price and not deflate it. Pushing the reserve ratio towards the correct ratio is the method of deflating the USD-denominated value, however, the non-manipulated value should always approximately match the correct price, so it is not possible!
Escalating to an Attack
The LP token is used as collateral to borrow USDS, so we can inflate the LP price to borrow more than the liquidity position's real worth. The attacker can then dump the USDS and the whole USDS stablecoin will be undercollateralized and collapse.
Does Automated Arbitrage Prevent Pool Manipulation?
There is potential difficulty in manipulating the Salty pool due to the automated arbitrage. If there is enough liquidity in the pools in the arbitragePath
, then the protocol will swap WETH to more WETH through the path which will rebalance the pool.
Note that this requires "enough liquidity" in the pool path. One could simply deposit a large amount liquidity in the pool being manipulated such that it holds far more token value than the other pools. Therefore, the WETH arbitrage will leave the pools imbalanced due to causing alot of price slippage in the smaller pools to create only a small price correction in the large pool.
Note that the potential profit of the attacker, which is to borrow all the USDS available at a massively discounted rate, is huge. Therefore the loss of losing to the WETH arbitrage would be negligible compared to how much they gain.
Manual Review
Valuing LP positions is tricky. Salty's current method is to "trust" the untrustworthy pool reserves. Instead, you could value the liquidity as if the ratio was at the correct ratio, rather than the current pool ratio. Here is an example of a protocol implementing this solution for Uniswap v3 positions:
https://github.com/arcadia-finance/accounts-v2/blob/main/src/asset-modules/UniswapV3/UniswapV3AM.sol
Uniswap
#0 - c4-judge
2024-02-02T17:39:05Z
Picodes marked the issue as duplicate of #945
#1 - c4-judge
2024-02-17T18:09:20Z
Picodes marked the issue as selected for report
#2 - c4-judge
2024-02-17T18:09:47Z
Picodes marked the issue as satisfactory
#3 - Picodes
2024-02-17T18:13:29Z
The sponsor's answer to this issue can be found here: https://github.com/code-423n4/2024-01-salty-findings/issues/945#issuecomment-1939769047
As shown by this report, this attack seems valid but if arbitrage paths are properly configured the cost of attack is greater than usual. You'd need to either increase the size of the pool or manipulate the price of all the pools in the arbitrage paths at once to prevent arbitrages from happening. As it's still possible, the correct severity seems to be Medium under " leak value with a hypothetical attack path with stated assumptions, but external requirements.", the external requirements being that the cost of attack is smaller than the value extractable by borrowing USDS.
#4 - c4-sponsor
2024-02-17T22:43:48Z
othernet-global marked the issue as disagree with severity
#5 - othernet-global
2024-02-17T22:43:57Z
USDS has been removed from the exchange.
https://github.com/othernet-global/salty-io/commit/f3ff64a21449feb60a60c0d60721cfe2c24151c1
#6 - c4-judge
2024-02-18T17:41:40Z
Picodes changed the severity to 2 (Med Risk)
#7 - c4-sponsor
2024-02-18T21:34:33Z
othernet-global (sponsor) confirmed
#8 - othernet-global
2024-02-23T03:40:32Z
The stablecoin framework: /stablecoin, /price_feed, WBTC/WETH collateral, PriceAggregator, price feeds and USDS have been removed: https://github.com/othernet-global/salty-io/commit/88b7fd1f3f5e037a155424a85275efd79f3e9bf9
π Selected for report: Banditx0x
Also found by: 0xMango, jasonxiale
717.9839 USDC - $717.98
The first depositor in the AMM can lose the majority of their tokens to a frontrunning attack even if they set the correct minShares slippage parameters.
Summary:
By using $x + y$ instead of $sqrt(x * y)$ as the formula for determining the initial liquidity shares, minshares is no longer a effective slippage parameter for the first deposit as front running can change the shares minted even when the token ratio is the same.
Explanation:
Salty is a Uniswap style pool which maintains an $x * y = k$ invaraint. However, the equation determining the initial liquidity amount is different from Uniswap v2
Here is Uniswap's:
Here is Salty's (implemented in the _addLiquidity
function):
Here is a quote from Uniswap's whitepaper:
"Uniswap v2 initially mints shares equal to the geometric mean of the amounts, liquidity = sqrt(xy). This formula ensures that the value of a liquidity pool share at any time is essentially independent of the ratio at which liquidity was initially deposited⦠The above formula ensures that a liquidity pool share will never be worth less than the geometric mean of the reserves in that pool."
Uniswap's liquidity formula is path-independent. As long as we know the x
and y
values in a deposit, we know that the liquidity will conform to the $L = sqrt(x * y)$ formula.
Salty's formula is not path independent. The number of shares minted for a token deposit is affected by the initial liquidity deposit.
This is the basis for this attack.
The only slippage parameter used for depositing into the AMM is minShares
. However, during the first deposit, an attacker can exploit the difference in the initial deposit liquidity equation $x + y$ and the invariant $x * y$ to inflate the number of shares minted per token, and then use the inflated shares to set the AMM at an incorrect reserve ratio such that it can be arbitraged.
First let's examine a base case, where a user makes an initial deposit with correct minShares
parameters:
User deposits 10000 DAI and 10000 USDT at a 1-1 reserve ratio. This is correct as they are both tokens with 18 decimals and value of $1
.
Let's calculate the minShares slippage parameters they should set. During the first deposit they are depositing 10000 * 1e18 wei of each token, so 1e22 wei. Liquidity minted is the sum of the 2 amounts (1e22 + 1e22). So liquidity minted = 2e22
Now let's examine how a frontrunner can inflate the shares so that the slippage parameter is no longer effective.
Frontrunning Case, Step by step:
x * y = 1e20
1e30
shares! In the example without frontrunning, they only minted 1e22
sharesminShares
parameter.Manual Review
Consider using minAmount0
and minAmount1
deposited into the AMM. This is how Uniswap V3 implements their slippage parameters.
Alternatively, using the same liquidity formula as Uniswap v2 - $L = sqrt(x * y)$ will prevent this attack.
MEV
#0 - c4-judge
2024-02-02T15:53:39Z
Picodes marked the issue as duplicate of #527
#1 - c4-judge
2024-02-02T15:53:51Z
Picodes marked the issue as selected for report
#2 - c4-sponsor
2024-02-08T08:07:07Z
othernet-global (sponsor) confirmed
#3 - othernet-global
2024-02-15T02:46:37Z
minAddedAmountA and minAddedAmountB are now used.
Fixed in: https://github.com/othernet-global/salty-io/commit/0bb763cc67e6a30a97d8b157f7e5954692b3dd68
#4 - c4-judge
2024-02-18T19:18:29Z
Picodes marked the issue as satisfactory
#5 - 0xRobocop
2024-02-22T00:58:01Z
I think this issue was miss-judged and its invalid.
First, I would like to say that this issue (if valid) can be demonstrated clearly with a PoC which is also important given the impact.
The values given in the report cannot be reached, for example starting with reserves (1e2, 1e18) it is impossible to reach reserves of (1e10, 1e10) by making "swaps", it is important to note that the absolute value of reserves is important on the computation of the amount of liquidity to mint.
Finally, the report is pretty vague on demonstrating (even symbolically) how the first depositor can be attacked (if he set the slippage correctly).
#6 - t0x1cC0de
2024-02-22T11:45:50Z
Won't comment on the current report since I've not completely gone through it, but one can see a working coded PoC in my report: #143 . The judge has given it only partial credits though.
I personally believe a report complete with a coded PoC makes for a better choice as selected for report
.
#7 - Picodes
2024-02-26T06:35:20Z
@t0x1cC0de I gave partial credit to #143 but to me it's close to being invalid. In the provided PoC the minLiquidityReceived
is set to 0 so the report is only saying "if the user doesn't set the slippage parameter he can be sandwiched" which is obvious. Here the root of the issue is that the slippage protection isn't working because it is not path-independant so front-running a transaction could lead to more shares being minted and would make the slippage parameter useless. Then, an attacker could use #143 to extract funds.
#8 - Picodes
2024-02-26T06:39:01Z
@0xRobocop Although there is no PoC to me this report does a great job at explaining what the issue is. It's clear that adding liquidity at an incorrect price could lead to being arbed and it's clear as well by this report how the initial slippage is not path-independent so doesn't work.
#9 - Picodes
2024-02-26T07:47:16Z
Actually, #143 is a duplicate of #937
#10 - 0xRobocop
2024-02-26T15:53:30Z
Hi @Picodes, thanks for taking the time for reviewing this.
The problem is not only the lack of the coded PoC. But is that neither the written description is demonstrating if the attack is possible, for example, if you try the reproduce the attack based on the steps description it does not make sense:
-> Step 1: Attacker deposits (1e2, 1e18) -> Step 2: Attacker makes some swaps to the reserves become (1e10, 1e10). Here is the first issue, is not possible by starting on reserves (1e2, 1e18) to end in (1e10, 1e10). -> Step 3: Attacker manipulate the exchange rate to 1 DAI = 5 USDT. Here is another issue that is not clear, why step 2 was needed then?, what are the reserves values? (the absolute values are important for liquidity computation).
It's clear that adding liquidity at an incorrect price could lead to being arbed and it's clear as well by this report how the initial slippage is not path-independent so doesn't work.
Yeah, the report is describing that, but it is failing to demonstrate that it actually ends in the victim losing funds. Even the written description fails to give concrete steps and values that can be verified.
#11 - Picodes
2024-02-27T16:57:28Z
@0xRobocop just to understand your point, for you the report correctly points to a high-severity vulnerability in the code but fails to show how to exploit it clearly?
#12 - Picodes
2024-02-27T16:58:08Z
Like are you disagreeing with the severity of the underlying issue or the quality of this specific report? If it's about this report, what do you think of https://github.com/code-423n4/2024-01-salty-findings/issues/120?
#13 - 0xRobocop
2024-02-27T17:57:18Z
Both.
For some context, I think the issue is invalid (it does not exists), I had the same idea during the contest, but could not find a way to exploit it, which of course does not mean that it does not exist.
But I am yet to see a report that provides a clear PoC either coded or with concrete values for the deposits, swaps, etc. This issue if valid involves 2-3 steps without external conditions (it is very easy to proof and to validate). I could not make the PoC of #120 to run, but from reading the test it is tweaking the slippage parameter instead of just passing the sum of the deposited amounts (as the first depositor should do).
It seems that the report acknowledge that the issue does not exists if the correct slippage is used:
Also, this vulnerability can be mitigated by using the entire sum for liquidity deposits which are expected to be the first provision.
@Picodes
#14 - Picodes
2024-02-27T21:51:09Z
@0xRobocop changing a bit the amounts why would it be impossible?
Like:
Am I missing something?
#15 - piken
2024-02-28T04:53:38Z
Hi @Picodes
I agree with @0xRobocop that this report is quite vague. It's very hard to understand the attack vector and there is no PoC in the report. However, I also believe that the mitigation effectively resolves the issue, even if its description is not very detailed. From my understanding the attack should be like this:
However, this attack can only impact the initial deposit. I can't see how it would affect subsequent deposits beyond the first one. Hence the first depositor can mitigate the consequences of the attack by depositing a small amount of tokens.
Would it be more appropriate to lower the severity rating to Medium?
#16 - Picodes
2024-02-28T10:14:03Z
So you're agreeing with the scenario of the report and its validity. I do agree with your argument though - this applies only to the first deposit which should remain minimal in most cases. I'll downgrade to Medium. Thanks everyone for your comments
#17 - c4-judge
2024-02-28T10:14:11Z
Picodes changed the severity to 2 (Med Risk)
π Selected for report: juancito
Also found by: 0x3b, 0xBinChook, 0xCiphky, 0xHelium, 0xMango, 0xOmer, 0xRobocop, 0xSmartContractSamurai, 0xWaitress, 0xbepresent, 0xpiken, 7ashraf, Arz, Audinarey, Banditx0x, Bauchibred, Draiakoo, IceBear, J4X, Jorgect, Kaysoft, KingNFT, Rhaydden, Rolezn, The-Seraphs, Tigerfrake, Topmark, Tripathi, Udsen, ZanyBonzy, a3yip6, b0g0, chaduke, codeslide, csanuragjain, dharma09, djxploit, eta, ether_sky, grearlake, inzinko, jasonxiale, jesjupyter, josephdara, lanrebayode77, linmiaomiao, lsaudit, niroh, oakcobalt, peanuts, pina, sivanesh_808, slvDev, t0x1c, vnavascues, wangxx2026
11.6897 USDC - $11.69
The amount swapped in a zap call is not accurately calculated, leading to less liquidity being deposited and extra slippage loss incurred by users.
During the Zap where tokens are swapped, the _determineZapSwapAmount
function calculates the exact number of tokens to swap to balance the amounts in to match the reserve ratio after the swap.
However, this makes an incorrect assumption that after the swap takes place, the liquidity deposit happens with no changes to the AMM pool occuring in between. However, the protocol actually does the in-protocol arbitrage during the swap, and this changes the reserve ratio of the underlying pool:
The call chain to the arbitrage is:
_dualZapInLiquidity
-> depositSwapWithdraw
-> _adjustReservesForSwapAndAttemptArbitrage
Manual Review
The liquidity deposit should take into account the protocol arbitrage that will take place during the swap to calculate the correct amount of tokens to swap.
Math
#0 - c4-judge
2024-02-03T09:40:24Z
Picodes marked the issue as duplicate of #250
#1 - c4-judge
2024-02-19T13:48:39Z
Picodes changed the severity to QA (Quality Assurance)
#2 - c4-judge
2024-02-21T17:11:24Z
Picodes marked the issue as grade-b