SKIP TO CONTENT

Demo build — mock protocol data. No chain, no ABIs yet.

  • SYNCING: ……

K401Oracle.sol

Fail-closed TWAP on the canonical 401K/USDG pair. Valid window 30 minutes to 4 hours, else StaleOracle().

148 lines5.3 KBSolidity
Source of src/K401Oracle.sol, 148 lines of Solidity
1// SPDX-License-Identifier: MIT
2pragma solidity ^0.8.24;
3 
4import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
5import {IERC20Like, IUniswapV2Pair} from "./interfaces/IK401Interfaces.sol";
6 
7/**
8 * @title K401Oracle — fail-closed Uniswap V2 cumulative-price TWAP
9 * @notice Reads the canonical 401K/USDG pair. Spot reserves are NEVER used for pricing;
10 * only `price{0,1}CumulativeLast` deltas over a >= 30 minute window are.
11 *
12 * Fail-closed: `consult()` reverts with `StaleOracle()` whenever the stored observation
13 * is younger than a full 30 minute window or older than 4 hours. Every consumer
14 * (Distributor, BondDepository, Buyback, StockDesk) routes through it and therefore
15 * halts rather than trading on a bad number.
16 */
17contract K401Oracle {
18 uint256 public constant MIN_PERIOD = 30 minutes;
19 uint256 public constant MAX_AGE = 4 hours;
20 uint256 public constant WAD = 1e18;
21 
22 IUniswapV2Pair public immutable pair;
23 address public immutable k401;
24 address public immutable usdg;
25 bool public immutable k401IsToken0;
26 uint8 public immutable usdgDecimals;
27 
28 uint256 public lastCumulative;
29 uint256 public lastCheckpointTs;
30 /// @notice Length of the window backing the stored price, in seconds.
31 uint256 public twapPeriod;
32 /// @notice USDG (18-dec normalised) per 1e18 of 401K.
33 uint256 public priceWad;
34 bool public initialized;
35 
36 event Checkpoint(uint256 priceWad, uint256 period, uint256 timestamp);
37 
38 error StaleOracle();
39 error PeriodTooShort();
40 error BadPair();
41 
42 constructor(address pair_, address k401_, address usdg_) {
43 pair = IUniswapV2Pair(pair_);
44 k401 = k401_;
45 usdg = usdg_;
46 address t0 = IUniswapV2Pair(pair_).token0();
47 address t1 = IUniswapV2Pair(pair_).token1();
48 if (t0 == k401_ && t1 == usdg_) {
49 k401IsToken0 = true;
50 } else if (t1 == k401_ && t0 == usdg_) {
51 k401IsToken0 = false;
52 } else {
53 revert BadPair();
54 }
55 usdgDecimals = IERC20Like(usdg_).decimals();
56 
57 (uint256 cum,) = _currentCumulative();
58 lastCumulative = cum;
59 lastCheckpointTs = block.timestamp;
60 }
61 
62 /*//////////////////////////////////////////////////////////////
63 CHECKPOINTING
64 //////////////////////////////////////////////////////////////*/
65 
66 /// @notice Permissionless. Rolls the TWAP window forward. Min 30 minutes apart.
67 function checkpoint() external {
68 uint256 elapsed = block.timestamp - lastCheckpointTs;
69 if (elapsed < MIN_PERIOD) revert PeriodTooShort();
70 
71 (uint256 cum,) = _currentCumulative();
72 uint256 delta;
73 unchecked {
74 delta = cum - lastCumulative; // cumulative prices are allowed to wrap
75 }
76 uint256 avgQ112 = delta / elapsed;
77 
78 // avgQ112 is UQ112x112. Convert to a wad price, then normalise USDG decimals.
79 uint256 p = Math.mulDiv(avgQ112, WAD, 1 << 112);
80 if (usdgDecimals < 18) {
81 p *= 10 ** (18 - usdgDecimals);
82 } else if (usdgDecimals > 18) {
83 p /= 10 ** (usdgDecimals - 18);
84 }
85 
86 priceWad = p;
87 twapPeriod = elapsed;
88 lastCumulative = cum;
89 lastCheckpointTs = block.timestamp;
90 initialized = true;
91 
92 emit Checkpoint(p, elapsed, block.timestamp);
93 }
94 
95 /*//////////////////////////////////////////////////////////////
96 READING
97 //////////////////////////////////////////////////////////////*/
98 
99 function isValid() public view returns (bool) {
100 if (!initialized) return false;
101 if (priceWad == 0) return false;
102 if (twapPeriod < MIN_PERIOD) return false;
103 if (block.timestamp - lastCheckpointTs > MAX_AGE) return false;
104 return true;
105 }
106 
107 /// @notice Fail-closed price read.
108 function consult() external view returns (uint256) {
109 if (!isValid()) revert StaleOracle();
110 return priceWad;
111 }
112 
113 /// @notice Non-reverting variant for view aggregation / UI.
114 function peek() external view returns (uint256, bool) {
115 if (!isValid()) return (0, false);
116 return (priceWad, true);
117 }
118 
119 function age() external view returns (uint256) {
120 return block.timestamp - lastCheckpointTs;
121 }
122 
123 /*//////////////////////////////////////////////////////////////
124 INTERNAL
125 //////////////////////////////////////////////////////////////*/
126 
127 /// @dev Uniswap V2 counterfactual cumulative price for the 401K -> USDG direction.
128 function _currentCumulative() internal view returns (uint256 cum, uint32 ts) {
129 ts = uint32(block.timestamp % 2 ** 32);
130 (uint112 r0, uint112 r1, uint32 last) = pair.getReserves();
131 cum = k401IsToken0 ? pair.price0CumulativeLast() : pair.price1CumulativeLast();
132 if (last != ts && r0 != 0 && r1 != 0) {
133 uint32 elapsed;
134 unchecked {
135 elapsed = ts - last;
136 }
137 uint224 q = k401IsToken0 ? _uq112(r1, r0) : _uq112(r0, r1);
138 unchecked {
139 cum += uint256(q) * elapsed;
140 }
141 }
142 }
143 
144 function _uq112(uint112 numerator, uint112 denominator) private pure returns (uint224) {
145 return uint224((uint256(numerator) << 112) / uint256(denominator));
146 }
147}
148 

Click any line number to deep-link to it — the target line highlights on load.