forked from OpenZeppelin/openzeppelin-contracts
-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathCheckpoints.sol
More file actions
258 lines (222 loc) · 8.03 KB
/
Checkpoints.sol
File metadata and controls
258 lines (222 loc) · 8.03 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
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (utils/Checkpoints.sol)
pragma solidity ^0.8.0;
import "./math/Math.sol";
import "./math/SafeCast.sol";
/**
* @dev This library defines the `History` struct, for checkpointing values as they change at different points in
* time, and later looking up past values by block number. See {Votes} as an example.
*
* To create a history of checkpoints define a variable type `Checkpoints.History` in your contract, and store a new
* checkpoint for the current transaction block using the {push} function.
*
* _Available since v4.5._
*/
library Checkpoints {
struct Checkpoint224 {
uint32 _key;
uint224 _value;
}
function latest(Checkpoint224[] storage self) internal view returns (uint224) {
uint256 pos = self.length;
return pos == 0 ? 0 : self[pos - 1]._value;
}
function push(
Checkpoint224[] storage self,
uint32 key,
uint224 value
) internal returns (uint224, uint224) {
uint256 pos = self.length;
if (pos > 0) {
// Use of memory is important here.
Checkpoint224 memory last = self[pos - 1];
// Checkpoints keys must be increassing.
require(last._key <= key, "Checkpoint: invalid key");
// Update or push new checkpoint
if (last._key == key) {
self[pos - 1]._value = value;
} else {
self.push(Checkpoint224({_key: key, _value: value}));
}
return (last._value, value);
} else {
self.push(Checkpoint224({_key: key, _value: value}));
return (0, value);
}
}
function lowerLookup(Checkpoint224[] storage self, uint32 key) internal view returns (uint224) {
uint256 length = self.length;
uint256 pos = _lowerDichotomicLookup(self, key, 0, length);
return pos == length ? 0 : self[pos]._value;
}
function upperLookup(Checkpoint224[] storage self, uint32 key) internal view returns (uint224) {
uint256 length = self.length;
uint256 pos = _upperDichotomicLookup(self, key, 0, length);
return pos == 0 ? 0 : self[pos - 1]._value;
}
function upperLookupRecent(Checkpoint224[] storage self, uint32 key) internal view returns (uint224) {
uint256 length = self.length;
uint256 offset = 1;
while (offset <= length && self[length - offset]._key > key) {
offset <<= 1;
}
uint256 low = offset < length ? length - offset : 0;
uint256 high = length - (offset >> 1);
uint256 pos = _upperDichotomicLookup(self, key, low, high);
return pos == 0 ? 0 : self[pos - 1]._value;
}
function _upperDichotomicLookup(
Checkpoint224[] storage self,
uint32 key,
uint256 low,
uint256 high
) private view returns (uint256) {
while (low < high) {
uint256 mid = Math.average(low, high);
if (self[mid]._key > key) {
high = mid;
} else {
low = mid + 1;
}
}
return high;
}
function _lowerDichotomicLookup(
Checkpoint224[] storage self,
uint32 key,
uint256 low,
uint256 high
) private view returns (uint256) {
while (low < high) {
uint256 mid = Math.average(low, high);
if (self[mid]._key < key) {
low = mid + 1;
} else {
high = mid;
}
}
return high;
}
struct Checkpoint160 {
uint96 _key;
uint160 _value;
}
function latest(Checkpoint160[] storage self) internal view returns (uint160) {
uint256 pos = self.length;
return pos == 0 ? 0 : self[pos - 1]._value;
}
function push(
Checkpoint160[] storage self,
uint96 key,
uint160 value
) internal returns (uint160, uint160) {
uint256 pos = self.length;
if (pos > 0) {
// Use of memory is important here.
Checkpoint160 memory last = self[pos - 1];
// Checkpoints keys must be increassing.
require(last._key <= key, "Checkpoint: invalid key");
// Update or push new checkpoint
if (last._key == key) {
self[pos - 1]._value = value;
} else {
self.push(Checkpoint160({_key: key, _value: value}));
}
return (last._value, value);
} else {
self.push(Checkpoint160({_key: key, _value: value}));
return (0, value);
}
}
function lowerLookup(Checkpoint160[] storage self, uint96 key) internal view returns (uint160) {
uint256 length = self.length;
uint256 pos = _lowerDichotomicLookup(self, key, 0, length);
return pos == length ? 0 : self[pos]._value;
}
function upperLookup(Checkpoint160[] storage self, uint96 key) internal view returns (uint160) {
uint256 length = self.length;
uint256 pos = _upperDichotomicLookup(self, key, 0, length);
return pos == 0 ? 0 : self[pos - 1]._value;
}
function upperLookupRecent(Checkpoint160[] storage self, uint96 key) internal view returns (uint224) {
uint256 length = self.length;
uint256 offset = 1;
while (offset <= length && self[length - offset]._key > key) {
offset <<= 1;
}
uint256 low = offset < length ? length - offset : 0;
uint256 high = length - (offset >> 1);
uint256 pos = _upperDichotomicLookup(self, key, low, high);
return pos == 0 ? 0 : self[pos - 1]._value;
}
function _upperDichotomicLookup(
Checkpoint160[] storage self,
uint96 key,
uint256 low,
uint256 high
) private view returns (uint256) {
while (low < high) {
uint256 mid = Math.average(low, high);
if (self[mid]._key > key) {
high = mid;
} else {
low = mid + 1;
}
}
return high;
}
function _lowerDichotomicLookup(
Checkpoint160[] storage self,
uint96 key,
uint256 low,
uint256 high
) private view returns (uint256) {
while (low < high) {
uint256 mid = Math.average(low, high);
if (self[mid]._key < key) {
low = mid + 1;
} else {
high = mid;
}
}
return high;
}
struct History {
Checkpoint224[] _checkpoints;
}
/**
* @dev Returns the value in the latest checkpoint, or zero if there are no checkpoints.
*/
function latest(History storage self) internal view returns (uint256) {
return latest(self._checkpoints);
}
/**
* @dev Returns the value at a given block number. If a checkpoint is not available at that block, the closest one
* before it is returned, or zero otherwise.
*/
function getAtBlock(History storage self, uint256 blockNumber) internal view returns (uint256) {
require(blockNumber < block.number, "Checkpoints: block not yet mined");
return upperLookupRecent(self._checkpoints, SafeCast.toUint32(blockNumber));
}
/**
* @dev Pushes a value onto a History so that it is stored as the checkpoint for the current block.
*
* Returns previous value and new value.
*/
function push(History storage self, uint256 value) internal returns (uint256, uint256) {
return push(self._checkpoints, SafeCast.toUint32(block.number), SafeCast.toUint224(value));
}
/**
* @dev Pushes a value onto a History, by updating the latest value using binary operation `op`. The new value will
* be set to `op(latest, delta)`.
*
* Returns previous value and new value.
*/
function push(
History storage self,
function(uint256, uint256) view returns (uint256) op,
uint256 delta
) internal returns (uint256, uint256) {
return push(self, op(latest(self), delta));
}
}