-
Notifications
You must be signed in to change notification settings - Fork 11
Expand file tree
/
Copy pathPriceOracle.sol
More file actions
210 lines (171 loc) · 7.42 KB
/
Copy pathPriceOracle.sol
File metadata and controls
210 lines (171 loc) · 7.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity ^0.8.19;
import "@openzeppelin/contracts/access/AccessControl.sol";
import "./interface/IPostageStamp.sol";
/**
* @title PriceOracle contract.
* @author The Swarm Authors.
* @dev The price oracle contract emits a price feed using events.
*/
contract PriceOracle is AccessControl {
// ----------------------------- State variables ------------------------------
// The address of the linked PostageStamp contract
IPostageStamp public postageStamp;
uint16 targetRedundancy = 4;
uint16 maxConsideredExtraRedundancy = 4;
// When the contract is paused, price changes are not effective
bool public isPaused = false;
// The number of the last round price adjusting happend
uint64 public lastAdjustedRound;
// The minimum price allowed
uint32 public minimumPriceUpscaled = 24000 << 10; // we upscale it by 2^10
// The priceBase to modulate the price
uint32 public priceBase = 1048576;
uint64 public currentPriceUpScaled = minimumPriceUpscaled;
// Constants used to modulate the price, see below usage
uint32[9] public changeRate = [1049417, 1049206, 1048996, 1048786, 1048576, 1048366, 1048156, 1047946, 1047736];
// Role allowed to update price
bytes32 public immutable PRICE_UPDATER_ROLE;
// The length of a round in blocks.
uint8 private constant ROUND_LENGTH = 152;
// ----------------------------- Events ------------------------------
/**
*@dev Emitted on every price update.
*/
event PriceUpdate(uint256 price);
event StampPriceUpdateFailed(uint256 attemptedPrice);
// ----------------------------- Custom Errors ------------------------------
error CallerNotAdmin(); // Caller is not the admin
error CallerNotPriceUpdater(); // Caller is not a price updater
error PriceAlreadyAdjusted(); // Price already adjusted in this round
error UnexpectedZero(); // Redundancy needs to be higher then 0
// ----------------------------- CONSTRUCTOR ------------------------------
constructor(address _postageStamp) {
_setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
postageStamp = IPostageStamp(_postageStamp);
lastAdjustedRound = currentRound();
PRICE_UPDATER_ROLE = keccak256("PRICE_UPDATER_ROLE");
emit PriceUpdate(currentPrice());
}
////////////////////////////////////////
// STATE SETTING //
////////////////////////////////////////
/**
* @notice Manually set the price.
* @dev Can only be called by the admin role.
* @param _price The new price.
*/ function setPrice(uint32 _price) external returns (bool) {
if (!hasRole(DEFAULT_ADMIN_ROLE, msg.sender)) {
revert CallerNotAdmin();
}
uint64 _currentPriceUpScaled = _price << 10;
uint64 _minimumPriceUpscaled = minimumPriceUpscaled;
// Enforce minimum price
if (_currentPriceUpScaled < _minimumPriceUpscaled) {
_currentPriceUpScaled = _minimumPriceUpscaled;
}
currentPriceUpScaled = _currentPriceUpScaled;
// Check if the setting of price in postagestamp succeded
(bool success, ) = address(postageStamp).call(
abi.encodeWithSignature("setPrice(uint256)", uint256(currentPrice()))
);
if (!success) {
emit StampPriceUpdateFailed(currentPrice());
return false;
}
emit PriceUpdate(currentPrice());
return true;
}
function adjustPrice(uint16 redundancy) external returns (bool) {
if (isPaused == false) {
if (!hasRole(PRICE_UPDATER_ROLE, msg.sender)) {
revert CallerNotPriceUpdater();
}
uint16 usedRedundancy = redundancy;
uint64 currentRoundNumber = currentRound();
// Price can only be adjusted once per round
if (currentRoundNumber <= lastAdjustedRound) {
revert PriceAlreadyAdjusted();
}
// Redundancy may not be zero
if (redundancy == 0) {
revert UnexpectedZero();
}
// Enforce maximum considered extra redundancy
uint16 maxConsideredRedundancy = targetRedundancy + maxConsideredExtraRedundancy;
if (redundancy > maxConsideredRedundancy) {
usedRedundancy = maxConsideredRedundancy;
}
uint64 _currentPriceUpScaled = currentPriceUpScaled;
uint64 _minimumPriceUpscaled = minimumPriceUpscaled;
uint32 _priceBase = priceBase;
// Set the number of rounds that were skipped, we substract 1 as lastAdjustedRound is set below and default result is 1
uint64 skippedRounds = currentRoundNumber - lastAdjustedRound - 1;
// We first apply the increase/decrease rate for the current round
uint32 _changeRate = changeRate[usedRedundancy];
_currentPriceUpScaled = (_changeRate * _currentPriceUpScaled) / _priceBase;
// If previous rounds were skipped, use MAX price increase for the previous rounds
if (skippedRounds > 0) {
_changeRate = changeRate[0];
for (uint64 i = 0; i < skippedRounds; i++) {
_currentPriceUpScaled = (_changeRate * _currentPriceUpScaled) / _priceBase;
}
}
// Enforce minimum price
if (_currentPriceUpScaled < _minimumPriceUpscaled) {
_currentPriceUpScaled = _minimumPriceUpscaled;
}
currentPriceUpScaled = _currentPriceUpScaled;
lastAdjustedRound = currentRoundNumber;
// Check if the price set in postagestamp succeded
(bool success, ) = address(postageStamp).call(
abi.encodeWithSignature("setPrice(uint256)", uint256(currentPrice()))
);
if (!success) {
emit StampPriceUpdateFailed(currentPrice());
return false;
}
emit PriceUpdate(currentPrice());
return true;
}
return false;
}
function pause() external {
if (!hasRole(DEFAULT_ADMIN_ROLE, msg.sender)) {
revert CallerNotAdmin();
}
isPaused = true;
}
function unPause() external {
if (!hasRole(DEFAULT_ADMIN_ROLE, msg.sender)) {
revert CallerNotAdmin();
}
isPaused = false;
}
////////////////////////////////////////
// STATE READING //
////////////////////////////////////////
/**
* @notice Return the number of the current round.
*/
function currentRound() public view returns (uint64) {
// We downcasted to uint64 as uint64 has 18,446,744,073,709,551,616 places
// as each round is 152 x 5 = 760, each day has around 113 rounds which is 41245 in a year
// it results 4.4724801e+14 years to run this game
return uint64(block.number / uint256(ROUND_LENGTH));
}
/**
* @notice Return the price downscaled
*/
function currentPrice() public view returns (uint32) {
// We downcasted to uint32 and bitshift it by 2^10
return uint32((currentPriceUpScaled) >> 10);
}
/**
* @notice Return the price downscaled
*/
function minimumPrice() public view returns (uint32) {
// We downcasted to uint32 and bitshift it by 2^10
return uint32((minimumPriceUpscaled) >> 10);
}
}