c9639c3860
* tests: add solidity test suite * tests: remove require strings * Update tests-solidity/init-test-node.sh * Update tests-solidity/init-test-node.sh Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com>
156 lines
6.4 KiB
Solidity
156 lines
6.4 KiB
Solidity
pragma solidity ^0.5.17;
|
|
|
|
|
|
/**
|
|
* @title Checkpointing - Library to handle a historic set of numeric values
|
|
*/
|
|
library Checkpointing {
|
|
uint256 private constant MAX_UINT192 = uint256(uint192(-1));
|
|
|
|
string private constant ERROR_VALUE_TOO_BIG = "CHECKPOINT_VALUE_TOO_BIG";
|
|
string private constant ERROR_CANNOT_ADD_PAST_VALUE = "CHECKPOINT_CANNOT_ADD_PAST_VALUE";
|
|
|
|
/**
|
|
* @dev To specify a value at a given point in time, we need to store two values:
|
|
* - `time`: unit-time value to denote the first time when a value was registered
|
|
* - `value`: a positive numeric value to registered at a given point in time
|
|
*
|
|
* Note that `time` does not need to refer necessarily to a timestamp value, any time unit could be used
|
|
* for it like block numbers, terms, etc.
|
|
*/
|
|
struct Checkpoint {
|
|
uint64 time;
|
|
uint192 value;
|
|
}
|
|
|
|
/**
|
|
* @dev A history simply denotes a list of checkpoints
|
|
*/
|
|
struct History {
|
|
Checkpoint[] history;
|
|
}
|
|
|
|
/**
|
|
* @dev Add a new value to a history for a given point in time. This function does not allow to add values previous
|
|
* to the latest registered value, if the value willing to add corresponds to the latest registered value, it
|
|
* will be updated.
|
|
* @param self Checkpoints history to be altered
|
|
* @param _time Point in time to register the given value
|
|
* @param _value Numeric value to be registered at the given point in time
|
|
*/
|
|
function add(History storage self, uint64 _time, uint256 _value) internal {
|
|
require(_value <= MAX_UINT192, ERROR_VALUE_TOO_BIG);
|
|
_add192(self, _time, uint192(_value));
|
|
}
|
|
|
|
/**
|
|
* TODO
|
|
*/
|
|
function lastUpdate(History storage self) internal view returns (uint256) {
|
|
uint256 length = self.history.length;
|
|
|
|
if (length > 0) {
|
|
return uint256(self.history[length - 1].time);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @dev Fetch the latest registered value of history, it will return zero if there was no value registered
|
|
* @param self Checkpoints history to be queried
|
|
*/
|
|
function getLast(History storage self) internal view returns (uint256) {
|
|
uint256 length = self.history.length;
|
|
if (length > 0) {
|
|
return uint256(self.history[length - 1].value);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @dev Fetch the most recent registered past value of a history based on a given point in time that is not known
|
|
* how recent it is beforehand. It will return zero if there is no registered value or if given time is
|
|
* previous to the first registered value.
|
|
* It uses a binary search.
|
|
* @param self Checkpoints history to be queried
|
|
* @param _time Point in time to query the most recent registered past value of
|
|
*/
|
|
function get(History storage self, uint64 _time) internal view returns (uint256) {
|
|
return _binarySearch(self, _time);
|
|
}
|
|
|
|
/**
|
|
* @dev Private function to add a new value to a history for a given point in time. This function does not allow to
|
|
* add values previous to the latest registered value, if the value willing to add corresponds to the latest
|
|
* registered value, it will be updated.
|
|
* @param self Checkpoints history to be altered
|
|
* @param _time Point in time to register the given value
|
|
* @param _value Numeric value to be registered at the given point in time
|
|
*/
|
|
function _add192(History storage self, uint64 _time, uint192 _value) private {
|
|
uint256 length = self.history.length;
|
|
if (length == 0 || self.history[self.history.length - 1].time < _time) {
|
|
// If there was no value registered or the given point in time is after the latest registered value,
|
|
// we can insert it to the history directly.
|
|
self.history.push(Checkpoint(_time, _value));
|
|
} else {
|
|
// If the point in time given for the new value is not after the latest registered value, we must ensure
|
|
// we are only trying to update the latest value, otherwise we would be changing past data.
|
|
Checkpoint storage currentCheckpoint = self.history[length - 1];
|
|
require(_time == currentCheckpoint.time, ERROR_CANNOT_ADD_PAST_VALUE);
|
|
currentCheckpoint.value = _value;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @dev Private function execute a binary search to find the most recent registered past value of a history based on
|
|
* a given point in time. It will return zero if there is no registered value or if given time is previous to
|
|
* the first registered value. Note that this function will be more suitable when don't know how recent the
|
|
* time used to index may be.
|
|
* @param self Checkpoints history to be queried
|
|
* @param _time Point in time to query the most recent registered past value of
|
|
*/
|
|
function _binarySearch(History storage self, uint64 _time) private view returns (uint256) {
|
|
// If there was no value registered for the given history return simply zero
|
|
uint256 length = self.history.length;
|
|
if (length == 0) {
|
|
return 0;
|
|
}
|
|
|
|
// If the requested time is equal to or after the time of the latest registered value, return latest value
|
|
uint256 lastIndex = length - 1;
|
|
if (_time >= self.history[lastIndex].time) {
|
|
return uint256(self.history[lastIndex].value);
|
|
}
|
|
|
|
// If the requested time is previous to the first registered value, return zero to denote missing checkpoint
|
|
if (_time < self.history[0].time) {
|
|
return 0;
|
|
}
|
|
|
|
// Execute a binary search between the checkpointed times of the history
|
|
uint256 low = 0;
|
|
uint256 high = lastIndex;
|
|
|
|
while (high > low) {
|
|
// No need for SafeMath: for this to overflow array size should be ~2^255
|
|
uint256 mid = (high + low + 1) / 2;
|
|
Checkpoint storage checkpoint = self.history[mid];
|
|
uint64 midTime = checkpoint.time;
|
|
|
|
if (_time > midTime) {
|
|
low = mid;
|
|
} else if (_time < midTime) {
|
|
// No need for SafeMath: high > low >= 0 => high >= 1 => mid >= 1
|
|
high = mid - 1;
|
|
} else {
|
|
return uint256(checkpoint.value);
|
|
}
|
|
}
|
|
|
|
return uint256(self.history[low].value);
|
|
}
|
|
}
|