Skip to content

Commit

Permalink
Add clear history function
Browse files Browse the repository at this point in the history
  • Loading branch information
DimaStebaev committed Nov 17, 2023
1 parent 2fa275c commit 5b1195f
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 11 deletions.
29 changes: 28 additions & 1 deletion contracts/Paymaster.sol
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ contract Paymaster is AccessManagedUpgradeable, IPaymaster {
TimelineLibrary.Timeline private _totalRewards;
SequenceLibrary.Sequence private _totalNodesHistory;

error ImportantDataRemoving();

constructor(address initialAuthority) initializer {
__AccessManaged_init(initialAuthority);
}
Expand Down Expand Up @@ -174,6 +176,31 @@ contract Paymaster is AccessManagedUpgradeable, IPaymaster {
skaleToken = token;
}

function clearHistory(Timestamp before) external override restricted {
uint256 schainsAmount = _schainHashes.length();
for (uint256 i = 0; i < schainsAmount; ++i) {
SchainHash schainHash = SchainHash.wrap(_schainHashes.at(i));
Schain storage schain = _getSchain(schainHash);
if (schain.paidUntil < before) {
revert ImportantDataRemoving();
}
}

uint256 validatorsAmount = _validatorIds.length();
for (uint256 i = 0; i < validatorsAmount; ++i) {
ValidatorId validatorId = ValidatorId.wrap(_validatorIds.at(i));
Validator storage validator = _getValidator(validatorId);
if (validator.claimedUntil < before) {
revert ImportantDataRemoving();
}
validator.nodesHistory.clear(before);
}

_totalRewards.process(before);
_totalRewards.clear(before);
_totalNodesHistory.clear(before);
}

function pay(SchainHash schainHash, Months duration) external override {
if (duration > maxReplenishmentPeriod) {
revert ReplenishmentPeriodIsTooBig();
Expand Down Expand Up @@ -209,7 +236,7 @@ contract Paymaster is AccessManagedUpgradeable, IPaymaster {
}
}

function claim(address to) external restricted override {
function claim(address to) external override {
Validator storage validator = _getValidatorByAddress(_msgSender());
claimFor(validator.id, to);
}
Expand Down
19 changes: 17 additions & 2 deletions contracts/Sequence.sol
Original file line number Diff line number Diff line change
Expand Up @@ -147,13 +147,23 @@ library SequenceLibrary {
uint256 length = sequence.ids.length();
for (uint256 i = 0; i < length; ++i) {
Node storage node = _getNodeByIndex(sequence, i);
node.timestamp = Timestamp.wrap(0); // similar to operator delete
delete node.value;
_deleteNode(node);
}
sequence.ids.clear();
sequence.freeNodeId = NodeId.wrap(0);
}

function clear(Sequence storage sequence, Timestamp before) internal {
// It's important to store the most right value
for (uint256 nodesAmount = sequence.ids.length(); nodesAmount > 1; --nodesAmount) {
if (before <= _getNodeByIndex(sequence, 0).timestamp) {
break;
}
NodeId nodeId = sequence.ids.popFront();
_deleteNode(sequence.nodes[nodeId]);
}
}

// This function is a workaround to allow slither to analyze the code
// because current version fails on
// SequenceLibrary.NodeId.unwrap(value)
Expand All @@ -177,6 +187,11 @@ library SequenceLibrary {
sequence.freeNodeId = NodeId.wrap(NodeId.unwrap(newNodeId) + 1);
}

function _deleteNode(Node storage node) private {
node.timestamp = Timestamp.wrap(0); // similar to operator delete
delete node.value;
}

function _getNode(Sequence storage sequence, NodeId nodeId) private view returns (Node storage node) {
return sequence.nodes[nodeId];
}
Expand Down
49 changes: 42 additions & 7 deletions contracts/Timeline.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,24 +23,24 @@ pragma solidity ^0.8.19;

// cspell:words deque structs

import {DoubleEndedQueue} from "@openzeppelin/contracts/utils/structs/DoubleEndedQueue.sol";

import {PriorityQueueLibrary} from "./structs/PriorityQueue.sol";
import {TypedDoubleEndedQueue} from "./structs/TypedDoubleEndedQueue.sol";
import {DateTimeUtils, Seconds, Timestamp} from "./DateTimeUtils.sol";


library TimelineLibrary {
type ChangeId is uint256;
type ValueId is bytes32;

using DoubleEndedQueue for DoubleEndedQueue.Bytes32Deque;
using TypedDoubleEndedQueue for TypedDoubleEndedQueue.ValueIdDeque;
using PriorityQueueLibrary for PriorityQueueLibrary.PriorityQueue;

error CannotSetValueInThePast();
error TimeIntervalIsNotProcessed();
error TimeIntervalIsAlreadyProcessed();
error IncorrectTimeInterval();
error TimestampIsOutOfValues();
error ClearUnprocessed();

struct Timeline {
Timestamp processedUntil;
Expand All @@ -51,7 +51,7 @@ library TimelineLibrary {

ValueId valuesEnd;
mapping (ValueId => Value) values;
DoubleEndedQueue.Bytes32Deque valuesQueue;
TypedDoubleEndedQueue.ValueIdDeque valuesQueue;
}
struct Change {
Timestamp timestamp;
Expand Down Expand Up @@ -128,6 +128,36 @@ library TimelineLibrary {
_addChange(timeline, to, 0, rate + reminder);
}

function clear(Timeline storage timeline, Timestamp before) internal {
if (timeline.processedUntil < before) {
revert ClearUnprocessed();
}

for (uint256 valuesAmount = timeline.valuesQueue.length(); valuesAmount > 0; --valuesAmount) {
if (before <= _getValueByIndex(timeline, 0).timestamp) {
break;
}
ValueId valueId = timeline.valuesQueue.popFront();
_deleteValue(timeline.values[valueId]);
}
}

// This function is a workaround to allow slither to analyze the code
// because current version fails on
// TimelineLibrary.ValueId.unwrap(value)
// TODO: remove the function after slither fix the issue
function unwrapValueId(ValueId value) internal pure returns (bytes32 unwrappedValue) {
return ValueId.unwrap(value);
}

// This function is a workaround to allow slither to analyze the code
// because current version fails on
// TimelineLibrary.ValueId.wrap(value)
// TODO: remove the function after slither fix the issue
function wrapValueId(bytes32 unwrappedValue) internal pure returns (ValueId wrappedValue) {
return ValueId.wrap(unwrappedValue);
}

// Private

function _hasFutureChanges(Timeline storage timeline) private view returns (bool hasChanges) {
Expand Down Expand Up @@ -174,12 +204,17 @@ library TimelineLibrary {
delete timeline.futureChanges[changeId];
}

function _deleteValue(Value storage value) private {
value.timestamp = Timestamp.wrap(0);
delete value.value;
}

function _getCurrentValue(Timeline storage timeline) private view returns (Value storage value) {
return timeline.values[ValueId.wrap(timeline.valuesQueue.back())];
return timeline.values[timeline.valuesQueue.back()];
}

function _getValueByIndex(Timeline storage timeline, uint256 index) private view returns (Value storage value) {
return timeline.values[ValueId.wrap(timeline.valuesQueue.at(index))];
return timeline.values[timeline.valuesQueue.at(index)];
}

function _getLowerBoundIndex(Timeline storage timeline, Timestamp timestamp) private view returns (uint256 index) {
Expand Down Expand Up @@ -208,7 +243,7 @@ library TimelineLibrary {
}
ValueId valuesEnd = timeline.valuesEnd;
timeline.values[valuesEnd] = value;
timeline.valuesQueue.pushBack(ValueId.unwrap(valuesEnd));
timeline.valuesQueue.pushBack(valuesEnd);
timeline.valuesEnd = _getNextValueId(valuesEnd);
}

Expand Down
3 changes: 2 additions & 1 deletion contracts/interfaces/IPaymaster.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ pragma solidity ^0.8.18;

import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol";

import {Months} from "../DateTimeUtils.sol";
import {Months, Timestamp} from "../DateTimeUtils.sol";
import {USD} from "../types/Usd.sol";


Expand All @@ -43,6 +43,7 @@ interface IPaymaster {
function setSchainPrice(USD price) external;
function setSklPrice(USD price) external;
function setSkaleToken(IERC20 token) external;
function clearHistory(Timestamp before) external;
function pay(SchainHash schainHash, Months duration) external;
function claim(address to) external;
function claimFor(ValidatorId validatorId, address to) external;
Expand Down
75 changes: 75 additions & 0 deletions contracts/structs/TypedDoubleEndedQueue.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ pragma solidity ^0.8.20;
import {DoubleEndedQueue} from "@openzeppelin/contracts/utils/structs/DoubleEndedQueue.sol";

import {SequenceLibrary} from "../Sequence.sol";
import {TimelineLibrary} from "../Timeline.sol";


library TypedDoubleEndedQueue {
Expand All @@ -34,6 +35,12 @@ library TypedDoubleEndedQueue {
DoubleEndedQueue.Bytes32Deque inner;
}

struct ValueIdDeque {
DoubleEndedQueue.Bytes32Deque inner;
}

// NodeIdDeque - internal

/**
* @dev Inserts an item at the end of the queue.
*
Expand All @@ -43,6 +50,15 @@ library TypedDoubleEndedQueue {
DoubleEndedQueue.pushBack(deque.inner, bytes32(SequenceLibrary.unwrapNodeId(value)));
}

/**
* @dev Removes the item at the beginning of the queue and returns it.
*
* Reverts with `QueueEmpty` if the queue is empty.
*/
function popFront(NodeIdDeque storage deque) internal returns (SequenceLibrary.NodeId value) {
return SequenceLibrary.wrapNodeId(uint256(DoubleEndedQueue.popFront(deque.inner)));
}

/**
* @dev Resets the queue back to being empty.
*
Expand All @@ -53,6 +69,28 @@ library TypedDoubleEndedQueue {
DoubleEndedQueue.clear(deque.inner);
}

// ValueIdDeque - internal

/**
* @dev Inserts an item at the end of the queue.
*
* Reverts with {QueueFull} if the queue is full.
*/
function pushBack(ValueIdDeque storage deque, TimelineLibrary.ValueId valueId) internal {
DoubleEndedQueue.pushBack(deque.inner, TimelineLibrary.unwrapValueId(valueId));
}

/**
* @dev Removes the item at the beginning of the queue and returns it.
*
* Reverts with `QueueEmpty` if the queue is empty.
*/
function popFront(ValueIdDeque storage deque) internal returns (TimelineLibrary.ValueId valueId) {
return TimelineLibrary.wrapValueId(DoubleEndedQueue.popFront(deque.inner));
}

// NodeIdDeque - internal view

/**
* @dev Return the item at a position in the queue given by `index`, with the first item at 0 and last item at
* `length(deque) - 1`.
Expand Down Expand Up @@ -80,4 +118,41 @@ library TypedDoubleEndedQueue {
function empty(NodeIdDeque storage deque) internal view returns (bool isEmpty) {
return DoubleEndedQueue.empty(deque.inner);
}

// ValueIdDeque - internal view

/**
* @dev Returns true if the queue is empty.
*/
function empty(ValueIdDeque storage deque) internal view returns (bool isEmpty) {
return DoubleEndedQueue.empty(deque.inner);
}

/**
* @dev Returns the number of items in the queue.
*/
function length(ValueIdDeque storage deque) internal view returns (uint256 lengthValue) {
return DoubleEndedQueue.length(deque.inner);
}

/**
* @dev Returns the item at the end of the queue.
*
* Reverts with `QueueEmpty` if the queue is empty.
*/
function back(ValueIdDeque storage deque) internal view returns (TimelineLibrary.ValueId valueId) {
return TimelineLibrary.wrapValueId(DoubleEndedQueue.back(deque.inner));
}

/**
* @dev Return the item at a position in the queue given by `index`, with the first item at 0 and last item at
* `length(deque) - 1`.
*
* Reverts with `QueueOutOfBounds` if the index is out of bounds.
*/
function at(ValueIdDeque storage deque, uint256 index) internal view returns (TimelineLibrary.ValueId valueId) {
return TimelineLibrary.wrapValueId(
DoubleEndedQueue.at(deque.inner, index)
);
}
}

0 comments on commit 5b1195f

Please sign in to comment.