Overview This article analyzes the incident affecting Sentiment that took place at 1750 UTC on 4th April 2023. This postmortem is a result of discussions with Zach, Alex, WatchPug, Sherlock and builds upon their analyses. We thank them for their support throughout the process. Root Cause Analysis The root cause is a view-only reentrancy bug exposed in Balancer pools when removing liquidity with one of the return tokens being ETH. Since the entry point is a non-mutating view call it could not be protected by a reentrancy guard allowing the caller to take control of execution and run arbitrary code. During liquidity withdrawal operations ("exit pool") Balancer first burns the LP Tokens (BPTs) and transfers funds to the user before updating its balances. Since this is not CEI-conformant it results in an intermediate state where the totalSupply() of the BPTs is reduced and some of the tokens could have been transferred, but internal asset balances are yet to be updated. Typically the intermediate state described above is harmless because there's no way to exploit it. Unfortunately, this is not the case with when one of the return tokens is ETH. Balancer uses a low level call() to transfer ETH to the caller which allows them to reenter and take control of execution in the intermediate state with inaccurate internal balances as described above.
4/9/2023Root Cause Analysis Sentiment uses a custom oracle to price Curve Tricrypto LP Tokens throughout the protocol. The oracle used Curve's recommended method to fetch the price of an LP Token: function getPrice(address) external view returns (uint) { return curveTriCryptoOracle.lp_price() * 1e18 / pool.price_oracle(1); } curveTriCryptoOracle.lp_price() returns the USD price of the LP token. Since sentiment prices all tokens in terms of ETH we need to divide this USD price by the current ETH/USD price to compute the ETH-denomiated price of the token. We divide the price returned by the oracle by pool.price_oracle(1), the price of ETH in the curve pool and this snippet forms the root cause of this bug. pool.price_oracle(1) reflects the internal price of ETH in the pool and is susceptible to large changes over time. In this case we saw the following price changes of pool.price_oracle(1) jump from $1216.55 to $1379.44 (+13.38%) in less than 1 sec between consecutive blocks 47637853 and 47637854. Ahead of the liquidation in block 47637889, the price of ETH was $1389.50 which represents a cumulative increase of 14.21% over the span of 12 seconds. There was no corresponding change in the market price of ETH during this duration.
4/7/2023or
By clicking below, you agree to our terms of service.
New to HackMD? Sign up