165 lines
5.7 KiB
Solidity
165 lines
5.7 KiB
Solidity
pragma solidity 0.5.12;
|
|
|
|
import "../interfaces/IUniswapV2.sol";
|
|
|
|
import "../libraries/SafeMath256.sol";
|
|
|
|
contract Oracle {
|
|
using SafeMath256 for uint256;
|
|
|
|
enum OracleStates { NeedsInitialization, NeedsActivation, Active }
|
|
|
|
struct TokenData {
|
|
uint128 token0;
|
|
uint128 token1;
|
|
}
|
|
|
|
struct TimeData {
|
|
uint128 blockNumber;
|
|
uint128 blockTimestamp;
|
|
}
|
|
|
|
address public exchange;
|
|
uint128 constant period = 1 days;
|
|
|
|
OracleStates private state = OracleStates.NeedsInitialization;
|
|
|
|
TokenData private reservesCumulative;
|
|
TokenData private reservesCumulativeOverflows;
|
|
|
|
TokenData private currentPrice;
|
|
|
|
TimeData private updateLast;
|
|
|
|
constructor(address _exchange) public {
|
|
exchange = _exchange;
|
|
}
|
|
|
|
function getReservesCumulative() private view returns (TokenData memory, TokenData memory) {
|
|
(
|
|
uint128 reservesCumulativeToken0,
|
|
uint128 reservesCumulativeToken1,
|
|
uint128 reservesCumulativeOverflowsToken0,
|
|
uint128 reservesCumulativeOverflowsToken1
|
|
) = IUniswapV2(exchange).getReservesCumulative();
|
|
return (
|
|
TokenData(reservesCumulativeToken0, reservesCumulativeToken1),
|
|
TokenData(reservesCumulativeOverflowsToken0, reservesCumulativeOverflowsToken1)
|
|
);
|
|
}
|
|
|
|
function getNow() private view returns (TimeData memory) {
|
|
return TimeData(block.number.downcast128(), block.timestamp.downcast128());
|
|
}
|
|
|
|
function reset() private {
|
|
delete(reservesCumulative);
|
|
delete(reservesCumulativeOverflows);
|
|
delete(currentPrice);
|
|
delete(updateLast);
|
|
state = OracleStates.NeedsInitialization;
|
|
}
|
|
|
|
function initialize() external {
|
|
require(state == OracleStates.NeedsInitialization, "Oracle: DOES_NOT_NEED_INITIALIZATION");
|
|
|
|
(reservesCumulative, reservesCumulativeOverflows) = getReservesCumulative();
|
|
updateLast = getNow();
|
|
|
|
state = OracleStates.NeedsActivation;
|
|
}
|
|
|
|
function activate() external {
|
|
require(state == OracleStates.NeedsActivation, "Oracle: DOES_NOT_NEED_ACTIVATION");
|
|
|
|
// get the current time, ensure it's been >=1 blocks since the last update
|
|
TimeData memory _now = getNow();
|
|
uint128 blocksElapsed = _now.blockNumber - updateLast.blockNumber;
|
|
require(blocksElapsed > 0, "Oracle: INSUFFICIENT_BLOCKS_PASSED");
|
|
|
|
// get the current cumulative reserves and overflows
|
|
TokenData memory reservesCumulativeNext;
|
|
TokenData memory reservesCumulativeOverflowsNext;
|
|
(reservesCumulativeNext, reservesCumulativeOverflowsNext) = getReservesCumulative();
|
|
|
|
// reset if there's been an overflow
|
|
if (
|
|
reservesCumulativeOverflows.token0 != reservesCumulativeOverflowsNext.token0 ||
|
|
reservesCumulativeOverflows.token1 != reservesCumulativeOverflowsNext.token1
|
|
) {
|
|
reset();
|
|
require(false, "Oracle: OVERFLOW");
|
|
}
|
|
|
|
// calculate the deltas, and record the new values
|
|
TokenData memory deltas = TokenData({
|
|
token0: reservesCumulativeNext.token0 - reservesCumulative.token0,
|
|
token1: reservesCumulativeNext.token1 - reservesCumulative.token1
|
|
});
|
|
reservesCumulative = reservesCumulativeNext;
|
|
|
|
// get the average price over the period and set it to the current price
|
|
currentPrice = TokenData({
|
|
token0: deltas.token0 / blocksElapsed,
|
|
token1: deltas.token1 / blocksElapsed
|
|
});
|
|
|
|
updateLast = _now;
|
|
|
|
state = OracleStates.Active;
|
|
}
|
|
|
|
function update() external {
|
|
require(state == OracleStates.Active, "Oracle: INACTIVE");
|
|
|
|
// get the current time, ensure it's been >=1 blocks since the last update
|
|
TimeData memory _now = getNow();
|
|
uint128 blocksElapsed = _now.blockNumber - updateLast.blockNumber;
|
|
require(blocksElapsed > 0, "Oracle: INSUFFICIENT_BLOCKS_PASSED");
|
|
uint128 timeElapsed = _now.blockTimestamp - updateLast.blockTimestamp;
|
|
|
|
// get the current cumulative reserves and overflows
|
|
TokenData memory reservesCumulativeNext;
|
|
TokenData memory reservesCumulativeOverflowsNext;
|
|
(reservesCumulativeNext, reservesCumulativeOverflowsNext) = getReservesCumulative();
|
|
|
|
// reset if there's been an overflow
|
|
if (
|
|
reservesCumulativeOverflows.token0 != reservesCumulativeOverflowsNext.token0 ||
|
|
reservesCumulativeOverflows.token1 != reservesCumulativeOverflowsNext.token1
|
|
) {
|
|
reset();
|
|
require(false, "Oracle: OVERFLOW");
|
|
}
|
|
|
|
// calculate the deltas, and record the new values
|
|
TokenData memory deltas = TokenData({
|
|
token0: reservesCumulativeNext.token0 - reservesCumulative.token0,
|
|
token1: reservesCumulativeNext.token1 - reservesCumulative.token1
|
|
});
|
|
reservesCumulative = reservesCumulativeNext;
|
|
|
|
// get the average price over the period
|
|
TokenData memory averages = TokenData({
|
|
token0: deltas.token0 / blocksElapsed,
|
|
token1: deltas.token1 / blocksElapsed
|
|
});
|
|
|
|
// update the current price with this information
|
|
if (timeElapsed < period) {
|
|
currentPrice = TokenData({
|
|
token0: (currentPrice.token0 * (period - timeElapsed) + averages.token0 * timeElapsed) / period,
|
|
token1: (currentPrice.token1 * (period - timeElapsed) + averages.token1 * timeElapsed) / period
|
|
});
|
|
} else {
|
|
currentPrice = averages;
|
|
}
|
|
|
|
updateLast = _now;
|
|
}
|
|
|
|
function getCurrentPrice() external view returns (uint128, uint128) {
|
|
return (currentPrice.token0, currentPrice.token1);
|
|
}
|
|
}
|