sherlock-2024-01-flat-money-m08
[M-08] Oracle can return different prices in same transaction
Summary
가격 오라클로 Pyth network 의 오라클 컨트랙트를 사용한다. 이 오라클은 동일한 트랜잭션에서 서로 다른 두 가지 가격을 제출하고 읽을 수 있다. 이를 위험 부담 없이 수익을 창출할 수 있는 차익 거래 기회를 만드는 데 사용할 수 있다.
Keyword
price oracle, pyth network, oracle manipulation
Vulnerability
OracleModule 컨트랙트는 Pyth network 의 가격 오라클을 기본으로 사용한다. Pyth의 가격 오라클은 다음과 같이 동작한다.
- 전용 네트워크에서 타임스탬프와 함께 최신 가격을 추적한다.
- 이 데이터는 오프체인에서 쿼리되어 온체인 오라클 컨트랙트에 등록된다.
- 제출된 데이터가 유효한지 확인하고 새로운 가격 데이터를 저장한다.
- 이제 최신 가격에 대한 새로운 요청은 더 최신 가격이 제출될 때까지 제출된 데이터를 반환한다.
Pyth 가격 오라클에 최신 데이터를 등록하기 위해서는 updatePriceFeeds 함수를 호출해야 한다. 오프체인(전용 네트워크)에서 읽은 가격 정보를 수수료와 함께 제출한다. 이를 위해 OracleModule 컨트랙트에는 updatePythPrice 함수가 존재한다.
function updatePythPrice(address sender, bytes[] calldata priceUpdateData) external payable nonReentrant {
// Get fee amount to pay to Pyth
@> uint256 fee = offchainOracle.oracleContract.getUpdateFee(priceUpdateData);
// Update the price data (and pay the fee)
@> offchainOracle.oracleContract.updatePriceFeeds{value: fee}(priceUpdateData);
if (msg.value - fee > 0) {
// Need to refund caller. Try to return unused value, or revert if failed
(bool success, ) = sender.call{value: msg.value - fee}("");
if (success == false) revert FlatcoinErrors.RefundFailed();
}
}한 가지 주목할 점은 Pyth network가 지속적으로 최신 가격을 업데이트(400ms마다)하기 때문에 새로운 가격이 온체인에 제출될 때 반드시 가장 최신 가격일 필요는 없다는 점이다. 그렇지 않으면 오프체인에서 데이터를 쿼리하고 트랜잭션을 구축한 후 온체인에 제출하는 과정을 400ms 미만의 지연 시간으로 수행해야 하는데, 이는 불가능하기 때문이다.
따라서 Pyth network의 오라클 컨트랙트에 동일한 트랜잭션에서 두 개의 다른 가격을 제출할 수 있으며, 따라서 동일한 트랜잭션에서 두 개의 다른 가격을 가져올 수 있다.
이를 통해 위험 부담 없이 차익거래 기회를 만들 수 있다.
- 작은 레버리지 포지션을 생성한다.
- 포지션의 크기를 늘리기 위해 레버리지 조정 주문을 등록한다.
- 같은 블록에서 지정가 청산 주문을 등록한다.
- 최소 체결 시간이 경과한 후 등록한 주문을 실행할 것이다. 먼저 Pyth 오라클(오프체인)에서 두 번째 가격이 첫 번째 가격보다 높은 두 개의 가격을 찾는다.
- 첫 번째 가격을 등록하고 2에서 등록한 조정 주문을 실행한다.
- 동일 트랜잭션에서, 두 번째 가격을 등록하고 3에서 등록한 지정가 청산 주문을 실행한다.
청산 주문을 실행할 때, 오른 가격을 기반으로 하기 때문에 차익거래로 수익을 얻을 수 있다.
Impact
동일 트랜잭션에서 다른 오라클 가격을 가져올 수 있으며, 이를 통해 위험 부담 없이 차익거래 수익을 얻을 수 있다.
Mitigation
동일 트랜잭션에서 가격 오라클을 재 업데이트 할 수 없도록 한다.
File: OracleModule.sol
FlatcoinStructs.OffchainOracle public offchainOracle; // Offchain Pyth network oracle
+ uint256 public lastOffchainUpdate;
(...)
function updatePythPrice(address sender, bytes[] calldata priceUpdateData) external payable nonReentrant {
+ if (lastOffchainUpdate >= block.timestamp) return;
+ lastOffchainUpdate = block.timestamp;
+
// Get fee amount to pay to Pyth
uint256 fee = offchainOracle.oracleContract.getUpdateFee(priceUpdateData);tags: bughunting, flat money, smart contract, solidity, solo issue, price oracle, pyth oracle integration, oracle manipulation, severity medium