sherlock-2024-01-ubiquity-m01
[M-01] LibUbiquityPool::mintDollar/redeemDollar reliance on outdated TWAP oracle may be inefficient for preventing depeg
Summary
uAD를 발행/소각하기 위해 토큰 가격을 Curve TWAP 오라클에서 얻는다. 이 때, TWAP 오라클은 오래된(outdated) 상태일 수 있다. 왜냐하면 Ubiquity 풀을 호출할 때, 기반하는 metapool을 최신화하지 않고 사용하기 때문이다. 메타풀의 가격 정보는 유동성을 공급했거나 스왑을 하는 등의 인터렉션이 발생했을 때 업데이트 된다. 즉, 인터렉션이 없었다면 가격이 오랫동안 업데이트되지 않을 수 있다.
Keyword
curve oracle, price oracle, staled oracle
Vulnerability
uAD를 발행 또는 소각할 때 먼저 가격 정보를 최신 상태로 유지하는 LibTWAPOracle.update 함수를 호출한다.
function mintDollar(
uint256 collateralIndex,
uint256 dollarAmount,
uint256 dollarOutMin,
uint256 maxCollateralIn
)
internal
collateralEnabled(collateralIndex)
returns (uint256 totalDollarMint, uint256 collateralNeeded)
{
...
// update Dollar price from Curve's Dollar Metapool
@> LibTWAPOracle.update();
...
ubiquityDollarToken.mint(msg.sender, totalDollarMint);
}
function redeemDollar(
uint256 collateralIndex,
uint256 dollarAmount,
uint256 collateralOutMin
)
internal
collateralEnabled(collateralIndex)
returns (uint256 collateralOut)
{
...
// update Dollar price from Curve's Dollar Metapool
@> LibTWAPOracle.update();
...
@> ubiquityDollarToken.burnFrom(msg.sender, dollarAmount);
}LibTWAPOracle.update 에서는 기반하는 메타풀의 update 함수를 호출하지 않고 메타풀의 가격 정보를 조회한다. 따라서 메타풀이 리턴하는 가격 정보는 오래되었을 수 있다.
function update() internal {
TWAPOracleStorage storage ts = twapOracleStorage();
(
uint256[2] memory priceCumulative,
uint256 blockTimestamp
@> ) = currentCumulativePrices(); // @audit-info 메타풀에서 최신 가격 정보를 가져옴
if (blockTimestamp - ts.pricesBlockTimestampLast > 0) {
// get the balances between now and the last price cumulative snapshot
uint256[2] memory twapBalances = IMetaPool(ts.pool)
.get_twap_balances(
ts.priceCumulativeLast,
priceCumulative,
blockTimestamp - ts.pricesBlockTimestampLast
);
// price to exchange amountIn Ubiquity Dollar to 3CRV based on TWAP
ts.price0Average = IMetaPool(ts.pool).get_dy(
0,
1,
1 ether,
twapBalances
);
// price to exchange amountIn 3CRV to Ubiquity Dollar based on TWAP
ts.price1Average = IMetaPool(ts.pool).get_dy(
1,
0,
1 ether,
twapBalances
);
// we update the priceCumulative
ts.priceCumulativeLast = priceCumulative;
ts.pricesBlockTimestampLast = blockTimestamp;
}
}
function currentCumulativePrices()
internal
view
returns (uint256[2] memory priceCumulative, uint256 blockTimestamp)
{
address metapool = twapOracleStorage().pool;
@> priceCumulative = IMetaPool(metapool).get_price_cumulative_last(); // @audit-info 메타풀에서 데이터 가져옴 (최신 상태가 아닐 수 있음)
@> blockTimestamp = IMetaPool(metapool).block_timestamp_last();
}Impact
악의적인 사용자가 오래된 가격을 이용하여 uAD를 대량으로 발행/소각하면 토큰의 가치를 더 떨어뜨릴 수 있다.
Mitigation
메타풀의 가격 정보는 유동성을 공급했거나 스왑을 하는 등의 인터렉션이 발생했을 때 업데이트 된다. 즉, 인터렉션이 없었다면 가격이 오랫동안 업데이트되지 않을 수 있다. 가격을 조회하기 전, 메타풀에 remove_liquidity 를 파라미터 0과 함께 호출하여 가격을 최신화시킨다.
def remove_liquidity(
_burn_amount: uint256,
_min_amounts: uint256[N_COINS],
_receiver: address = msg.sender
)tags: bughunting, ubiquity, smart contract, solidity, curve oracle, price oracle, staled oracle, severity medium