code4rena-2024-03-dittoeth-m07
[M-07] Using cached price to create a proposal reduce the efficacity of redemptions for asset peg
Summary
캐시한 오라클 데이터가 최신 데이터인지 확인하지 않고 사용한다. 잘못된 가격을 기반으로 청산 대상을 판단하므로 청산 대상이 아닌 숏을 청산시킬 수 있다.
Keyword
price oracle, staled oracle
Vulnerability
proposeRedemption 에서는 캐시된 오라클 가격을 사용하여 담보 비율을 계산한다. 이 때 캐시된 가격 정보가 오래된 것인지 확인하지 않고 사용한다. 잘못된 가격을 사용하여 담보를 계산한다면 실제로는 청산 대상이 아닌 숏을 청산시킬 수 있다.
function proposeRedemption(
address asset,
MTypes.ProposalInput[] calldata proposalInput,
uint88 redemptionAmount,
uint88 maxRedemptionFee
) external isNotFrozen(asset) nonReentrant {
if (proposalInput.length > type(uint8).max) revert Errors.TooManyProposals();
MTypes.ProposeRedemption memory p;
p.asset = asset;
STypes.AssetUser storage redeemerAssetUser = s.assetUser[p.asset][msg.sender];
uint256 minShortErc = LibAsset.minShortErc(p.asset);
if (redemptionAmount < minShortErc) revert Errors.RedemptionUnderMinShortErc();
if (redeemerAssetUser.ercEscrowed < redemptionAmount) revert Errors.InsufficientERCEscrowed();
// @dev redeemerAssetUser.SSTORE2Pointer gets reset to address(0) after actual redemption
if (redeemerAssetUser.SSTORE2Pointer != address(0)) revert Errors.ExistingProposedRedemptions();
@> p.oraclePrice = LibOracle.getPrice(p.asset); // no price update before use this cached price
bytes memory slate;
for (uint8 i = 0; i < proposalInput.length; i++) {
...
@> p.currentCR = currentSR.getCollateralRatio(p.oraclePrice);
...
@> p.colRedeemed = p.oraclePrice.mulU88(p.amountProposed);
if (p.colRedeemed > currentSR.collateral) {
p.colRedeemed = currentSR.collateral;
}
...
}
...
}Impact
잘못된 가격을 사용하여 담보를 계산한다면 실제로는 청산 대상이 아닌 숏을 청산시킬 수 있다.
Mitigation
캐시가 오래 되면 다시 오라클에 쿼리하여 캐시를 업데이트 하는 getSavedOrSpotOraclePrice 함수를 이용한다.
function proposeRedemption(
address asset,
MTypes.ProposalInput[] calldata proposalInput,
uint88 redemptionAmount,
uint88 maxRedemptionFee
) external isNotFrozen(asset) nonReentrant {
if (proposalInput.length > type(uint8).max) revert Errors.TooManyProposals();
MTypes.ProposeRedemption memory p;
p.asset = asset;
STypes.AssetUser storage redeemerAssetUser = s.assetUser[p.asset][msg.sender];
uint256 minShortErc = LibAsset.minShortErc(p.asset);
if (redemptionAmount < minShortErc) revert Errors.RedemptionUnderMinShortErc();
if (redeemerAssetUser.ercEscrowed < redemptionAmount) revert Errors.InsufficientERCEscrowed();
// @dev redeemerAssetUser.SSTORE2Pointer gets reset to address(0) after actual redemption
if (redeemerAssetUser.SSTORE2Pointer != address(0)) revert Errors.ExistingProposedRedemptions();
- p.oraclePrice = LibOracle.getPrice(p.asset);
+ p.oraclePrice = LibOracle.getSavedOrSpotOraclePrice(p.asset);
...
}tags: bughunting, dittoeth, smart contract, solidity, price oracle, staled oracle, severity medium