-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
telome
committed
Sep 25, 2023
1 parent
38a06e2
commit 80b1a40
Showing
2 changed files
with
197 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
// SPDX-FileCopyrightText: © 2023 Dai Foundation <www.daifoundation.org> | ||
// SPDX-License-Identifier: AGPL-3.0-or-later | ||
// | ||
// This program is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU Affero General Public License as published by | ||
// the Free Software Foundation, either version 3 of the License, or | ||
// (at your option) any later version. | ||
// | ||
// This program is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU Affero General Public License for more details. | ||
// | ||
// You should have received a copy of the GNU Affero General Public License | ||
// along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
pragma solidity ^0.8.16; | ||
|
||
interface PipLike { | ||
function read() external view returns (uint128); | ||
} | ||
|
||
contract StickyOracle { | ||
mapping (address => uint256) public wards; | ||
mapping (address => uint256) public buds; // Whitelisted feed readers | ||
mapping (uint16 => uint256) accumulators; // daily (eod) sticky oracle price accumulators | ||
|
||
PipLike public immutable pip; | ||
|
||
uint96 public slope = uint96(RAY); // maximum allowable price growth rate from center of TWAP window to now (in RAY) | ||
uint8 public lo; // how many days ago should the TWAP window start (exclusive) | ||
uint8 public hi; // how many days ago should the TWAP window end (inclusive) | ||
|
||
uint128 val; // last poked price | ||
uint32 public age; // time of last poke | ||
|
||
struct Accumulator { | ||
uint256 acc; // accumulator value at the end of the day | ||
} | ||
|
||
event Rely(address indexed usr); | ||
event Deny(address indexed usr); | ||
event Kiss(address indexed usr); | ||
event Diss(address indexed usr); | ||
event File(bytes32 indexed what, uint256 data); | ||
|
||
constructor( | ||
address _pip | ||
) { | ||
pip = PipLike(_pip); | ||
|
||
wards[msg.sender] = 1; | ||
emit Rely(msg.sender); | ||
} | ||
|
||
modifier auth { | ||
require(wards[msg.sender] == 1, "StickyOracle/not-authorized"); | ||
_; | ||
} | ||
|
||
modifier toll { | ||
require(buds[msg.sender] == 1, "StickyOracle/not-whitelisted"); | ||
_; | ||
} | ||
|
||
uint256 internal constant RAY = 10 ** 27; | ||
|
||
function rely(address usr) external auth { wards[usr] = 1; emit Rely(usr); } | ||
function deny(address usr) external auth { wards[usr] = 0; emit Deny(usr); } | ||
function kiss(address usr) external auth { buds[usr] = 1; emit Kiss(usr); } | ||
function diss(address usr) external auth { buds[usr] = 0; emit Diss(usr); } | ||
|
||
function file(bytes32 what, uint256 data) external auth { | ||
if (what == "slope") slope = uint96(data); | ||
if (what == "lo") lo = uint8(data); | ||
if (what == "hi") hi = uint8(data); | ||
else revert("StickyOracle/file-unrecognized-param"); | ||
emit File(what, data); | ||
} | ||
|
||
function _getCap() internal view returns (uint256 cap) { | ||
uint16 today = uint16(block.timestamp / 1 days); | ||
(uint96 slope_, uint16 lo_, uint16 hi_) = (slope, lo, hi); | ||
uint256 acc_lo = accumulators[today - lo_]; | ||
uint256 acc_hi = accumulators[today - hi_]; | ||
|
||
if (acc_lo == 0 || acc_hi == 0) return pip.read(); // TODO: do something smarter (use partial window or extrapolate missing daily accs) | ||
return (acc_hi - acc_lo) * slope_ / (RAY * (lo_ - hi_) * 1 days); | ||
} | ||
|
||
|
||
function poke() public { | ||
uint128 cur = pip.read(); | ||
uint16 today = uint16(block.timestamp / 1 days); | ||
uint256 acc = accumulators[today]; | ||
(uint128 val_, uint32 age_) = (val, age); | ||
uint256 newAcc; | ||
if (acc == 0) { // first poke of the day | ||
if (age_ > 0) { | ||
uint16 prevDay = uint16(age_ / 1 days); | ||
uint256 prevAcc = accumulators[prevDay]; | ||
newAcc = prevAcc + ( | ||
val_ * (today - prevDay - 1) + // account for possibly missing daily pokes | ||
cur // optimistically assume this will be the last poke of the day | ||
) * 1 days; | ||
} else { | ||
newAcc = cur * 1 days; // optimistically assume this will be the last poke of the day | ||
} | ||
} else { // not the first poke of the day | ||
uint256 eod = (today + 1) * 1 days; // TODO: check if not off-by-one | ||
uint256 off = eod - age_; // period during which the accumulator value needs to be adjusted | ||
newAcc = acc + cur * off - val_ * off; | ||
} | ||
accumulators[today] = newAcc; | ||
val = cur; | ||
age = uint32(block.timestamp); | ||
} | ||
|
||
function read() external view toll returns (uint256) { | ||
uint128 cur = pip.read(); | ||
uint256 cap = _getCap(); | ||
return cur < cap ? cur : cap; | ||
} | ||
|
||
function peek() external view toll returns (uint256, bool) { | ||
uint128 cur = pip.read(); | ||
uint256 cap = _getCap(); | ||
return (cur < cap ? cur : cap, cur > 0); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
// SPDX-FileCopyrightText: © 2023 Dai Foundation <www.daifoundation.org> | ||
// SPDX-License-Identifier: AGPL-3.0-or-later | ||
// | ||
// This program is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU Affero General Public License as published by | ||
// the Free Software Foundation, either version 3 of the License, or | ||
// (at your option) any later version. | ||
// | ||
// This program is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU Affero General Public License for more details. | ||
// | ||
// You should have received a copy of the GNU Affero General Public License | ||
// along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
pragma solidity ^0.8.16; | ||
|
||
import "forge-std/Test.sol"; | ||
|
||
import { StickyOracle } from "src/StickyOracle.sol"; | ||
|
||
interface ChainlogLike { | ||
function getAddress(bytes32) external view returns (address); | ||
} | ||
|
||
interface PipLike { | ||
function read() external view returns (uint256); | ||
function kiss(address) external; | ||
} | ||
|
||
contract StickyOracleTest is Test { | ||
|
||
PipLike public medianizer; | ||
StickyOracle public oracle; | ||
uint256 public initialMedianizerPrice; | ||
|
||
uint256 constant RAY = 10 ** 27; | ||
address constant LOG = 0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F; | ||
|
||
address PAUSE_PROXY; | ||
address PIP_MKR; | ||
|
||
function setUp() public { | ||
vm.createSelectFork(vm.envString("ETH_RPC_URL")); | ||
|
||
PAUSE_PROXY = ChainlogLike(LOG).getAddress("MCD_PAUSE_PROXY"); | ||
PIP_MKR = ChainlogLike(LOG).getAddress("PIP_MKR"); | ||
|
||
medianizer = PipLike(PIP_MKR); | ||
|
||
vm.startPrank(PAUSE_PROXY); | ||
|
||
oracle = new StickyOracle(PIP_MKR); | ||
oracle.kiss(address(this)); | ||
medianizer.kiss(address(oracle)); | ||
medianizer.kiss(address(this)); | ||
|
||
vm.stopPrank(); | ||
|
||
initialMedianizerPrice = uint256(medianizer.read()); | ||
assertGt(initialMedianizerPrice, 0); | ||
} | ||
|
||
function testPoke() public { | ||
} | ||
} |