The following report is a result of a security review from n-Var, requested by Warp to audit the changes in the system after the attack. 36 hours were spent in total during that engagement. This is not a full security audit of the system. This review's main focus was targeted on fixes of the hack and changes in price oracles. The review was done in multiple steps with continuous code changes.
The recent attack was based on the wrong price evaluation of LP tokens. According to the old formula, when the price of tokens in a Uniswap token pair is not correct, LP token price increases. Thus, manipulating the price in one of the Uniswap pair led to the artificial increase of the LP token (collateral) price and allowed to lend more stable coins from the protocol than it should have. That can lead to draining the system's funds.
The attack and the math of a fix for the system are described here: https://cmichel.io/pricing-lp-tokens/. In this review, we double-checked the math in the new formula and the implementation of the fix. No issues were found in that solution.
The initial round of the review was performed on a codebase with a following commit hash: 8a384d50be099a5b4f5b6b08a0bdce762f86f8a9
. 2.5 days were spent to review the code and help with the fixes.
The following issues were found during that round:
-
High. The price is not updated in the
consult
function when called for one of the tokens.In the
consult
function, the token price should be updated before it's returned. But it's only happening if the price of thetoken0
is requested. In the case oftoken1
, the price remains the same.Note: That issue was fixed by switching to Chainlink oracles.
-
High. Creation of the oracle may cause
division by zero
error.When an oracle is created, the average price is updated instantly (the
update
function is called in the constructor). The average price is calculated between the current price and the last updated price in Uniswap (which are the same prices). So the average price is actually going to be the latest. The error occurs if a Uniswap trade happened before in the same block. In that case, thetimeElapsed
value is going to be zero, and we'll have adivision by zero
error.To fix that issue, the oracle instance should be inactive for the first hour and do the first
update
call after that period.Note: That issue was fixed by switching to Chainlink oracles.
-
High. A lot of duplicated code is present to have
view/get
functions.Many functions in the system are duplicated and are present in 2 versions:
get
andview
version. The code is completely the same except for one part. For theget
version, the prices of the tokens are updated. Theview
functions are showing outdated values. In some parts of the system, the outdated price is used, while the price is updated in other parts. That creates inconsistency.A general recommendation would be to either remove the
view
part completely or optimize the code, so it's not duplicated. And also make the usage of view/get mechanisms consistent.Note: The
view
part of the codebase was removed. -
Medium. The aggregation period is not fixed if not updated frequently.
The aggregation period is assumed to be 1 hour, but in practice, it depends on how often the
update
function is called. So if the price hasn't been updated for a day, it will show an average price for that day.Note: That issue was fixed by switching to Chainlink oracles.
-
Medium. Amount==0 is used as a special case in the
getPriceOfToken
functionWhen calculating a token price, the
amount
parameter is passed. When it's zero, the function returns the trice of OneToken(token). This may be misleading when the amount is zero, and the price zero is expected.Note: That issue was fixed by removing that feature.
After many fixes in the first round, the architecture of the whole oracle was reconsidered again. A decision was made to switch from Uniswap oracles to Chainlink. 8 hours were spent to review these changes and help with the fixes. One Critical issue was found during that review.
-
High. Decimals are wrong in LP token price calculation
When calculating the LP token price, each token's price in the pair is used (
p0
,p1
) in the formula. Herep0
andp1
correspond to the price ofOneToken(token0)
andOneToken(token1)
amounts of tokens accordingly.These amounts depend on the
decimals
value of each token. Usually, there are 18 decimals, but for some tokens, it's different. For example, USDC has 6 decimals. On the other hand, the formula expectsp0
andp1
to be prices of the same amount of tokens. For example, for the USDC-ETH pair, the price of LP tokens will be calculated wrong.To fix that, it's recommended to pass the prices of 10^18 amount of each token. In that case, the result will also contain the price of 10^18 LP tokens.
Note: That issue was fixed as recommended.
-
Centralization. The owner can manipulate the price to drain the funds.
The
owner
can manually add/edit oracles. That allows setting any price of the collateral token and lend any amount of stable coins. It also allows to set low prices and liquidate other accounts.Note: That issue was fixed by removing the functions that can add/edit oracles.
There are also some findings and recommendations that are not necessarily issues but should be mentioned:
- The format of Chainlink return values is not very clear from the official reference. There are some assumptions regarding that format, but it should be thoroughly checked, especially when a new oracle is added.
- There's no re-entrancy protection in the system while there are external calls. In the current configuration, all the external calls initially look safe from re-entrancy attacks. If new tokens are added to the system, they may be the cause of re-entrancy.
- Gas limitations. During some calls, there are iterations over all stable coins and collateral tokens. For each of them, external calls are happening. If the number of allowed tokens increases, some function calls may run out of gas. Since the
liquidate
function looks the most expensive, liquidation may become impossible.
Hello...
Nice connecting with you here on Telegram am a listing coordinator at Bitfinex The World's Leading Assets Platform. In order to bring top performing blockchain projects to our community we are interested in listing your product on our exchange.We are reaching out to top team members Is your project open for such offers..? i would like us to discuss further...****