Commit f6c20be2 authored by Karl Floersch's avatar Karl Floersch

Add deletion to ring buffer

parent 1a14b811
......@@ -9,6 +9,7 @@ struct TimeboundRingBuffer {
bytes32 context;
uint32 maxSize;
uint32 maxSizeIncrementAmount;
uint32 deletionOffset;
uint firstElementTimestamp;
uint timeout;
}
......@@ -31,7 +32,7 @@ library Lib_TimeboundRingBuffer {
_self.firstElementTimestamp = block.timestamp;
}
function push(
function pushAppendOnly(
TimeboundRingBuffer storage _self,
bytes32 _ele,
bytes28 _extraData
......@@ -50,7 +51,20 @@ library Lib_TimeboundRingBuffer {
_self.context = makeContext(uint32(length+1), _extraData);
}
function push2(
function push(
TimeboundRingBuffer storage _self,
bytes32 _ele,
bytes28 _extraData
)
internal
{
pushAppendOnly(_self, _ele, _extraData);
if (_self.deletionOffset != 0) {
_self.deletionOffset += 1;
}
}
function push2AppendOnly(
TimeboundRingBuffer storage _self,
bytes32 _ele1,
bytes32 _ele2,
......@@ -72,6 +86,20 @@ library Lib_TimeboundRingBuffer {
_self.context = makeContext(uint32(length+2), _extraData);
}
function push2(
TimeboundRingBuffer storage _self,
bytes32 _ele1,
bytes32 _ele2,
bytes28 _extraData
)
internal
{
push2AppendOnly(_self, _ele1, _ele2, _extraData);
if (_self.deletionOffset != 0) {
_self.deletionOffset = _self.deletionOffset == 1 ? 0 : _self.deletionOffset - 2;
}
}
function makeContext(
uint32 _length,
bytes28 _extraData
......@@ -106,8 +134,8 @@ library Lib_TimeboundRingBuffer {
uint32
)
{
bytes32 lengthMask = 0x00000000000000000000000000000000000000000000000000000000ffffffff;
return uint32(uint256(context & lengthMask));
// Length is the last 4 bytes
return uint32(uint256(context & 0x00000000000000000000000000000000000000000000000000000000ffffffff));
}
function getExtraData(
......@@ -119,8 +147,8 @@ library Lib_TimeboundRingBuffer {
bytes28
)
{
bytes32 extraDataMask = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000;
return bytes28(_self.context & extraDataMask);
// Extra Data is the first 28 bytes
return bytes28(_self.context & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000);
}
function get(
......@@ -135,7 +163,26 @@ library Lib_TimeboundRingBuffer {
{
uint length = _getLength(_self.context);
require(_index < length, "Index too large.");
require(length - _index <= _self.maxSize, "Index too old & has been overridden.");
require(length - _index <= _self.maxSize - _self.deletionOffset, "Index too old & has been overridden.");
return _self.elements[_index % _self.maxSize];
}
function deleteElementsAfter(
TimeboundRingBuffer storage _self,
uint32 _index,
bytes28 _extraData
)
internal
{
uint32 length = _getLength(_self.context);
uint32 deletionStartingIndex = _index + 1;
uint32 numDeletedElements = length - deletionStartingIndex;
uint32 newDeletionOffset = _self.deletionOffset + numDeletedElements;
require(deletionStartingIndex < length, "Index too large.");
require(newDeletionOffset <= _self.maxSize, "Attempting to delete too many elements.");
_self.deletionOffset = newDeletionOffset;
_self.context = makeContext(deletionStartingIndex, _extraData);
}
}
\ No newline at end of file
......@@ -34,8 +34,12 @@ contract TestLib_TimeboundRingBuffer {
list.push2(_ele1, _ele2, _extraData);
}
function get(uint32 index) public view returns(bytes32) {
return list.get(index);
function get(uint32 _index) public view returns(bytes32) {
return list.get(_index);
}
function deleteElementsAfter(uint32 _index, bytes28 _extraData) public {
return list.deleteElementsAfter(_index, _extraData);
}
function getLength() public view returns(uint32) {
......
......@@ -29,6 +29,8 @@ describe('Lib_TimeboundRingBuffer', () => {
let Lib_TimeboundRingBuffer: Contract
const NON_NULL_BYTES28 = makeHexString('01', 28)
const pushNum = (num: Number) => Lib_TimeboundRingBuffer.push(numToBytes32(num), NON_NULL_BYTES28)
const push2Nums = (num1: Number, num2: Number) => Lib_TimeboundRingBuffer.push2(numToBytes32(num1), numToBytes32(num2), NON_NULL_BYTES28)
describe('push with no timeout', () => {
beforeEach(async () => {
......@@ -77,7 +79,6 @@ describe('Lib_TimeboundRingBuffer', () => {
describe('push with timeout', () => {
const startSize = 2
const pushNum = (num: Number) => Lib_TimeboundRingBuffer.push(numToBytes32(num), NON_NULL_BYTES28)
beforeEach(async () => {
Lib_TimeboundRingBuffer = await (
......@@ -118,15 +119,12 @@ describe('Lib_TimeboundRingBuffer', () => {
describe('push2 with timeout', () => {
const startSize = 2
const push2Nums = (num1: Number, num2: Number) => Lib_TimeboundRingBuffer.push2(numToBytes32(num1), numToBytes32(num2), NON_NULL_BYTES28)
beforeEach(async () => {
Lib_TimeboundRingBuffer = await (
await ethers.getContractFactory('TestLib_TimeboundRingBuffer')
).deploy(startSize, 1, 10_000)
})
// it('should push two values which extends the array', async () => {
// })
it('should push a single value which extends the array', async () => {
await push2Nums(0, 1)
......@@ -156,4 +154,42 @@ describe('Lib_TimeboundRingBuffer', () => {
expect(await Lib_TimeboundRingBuffer.getExtraData()).to.equal(NON_NULL_BYTES28)
})
})
describe('deleteElementsAfter', () => {
// [0,1,2,3] -> [0,1,-,-]
beforeEach(async () => {
Lib_TimeboundRingBuffer = await (
await ethers.getContractFactory('TestLib_TimeboundRingBuffer')
).deploy(4, 1, 0)
for (let i = 0; i < 4; i++) {
pushNum(i)
}
})
it('should disallow deletions which are too old', async () => {
push2Nums(4, 5)
await expect(Lib_TimeboundRingBuffer.deleteElementsAfter(0, NON_NULL_BYTES28)).to.be.revertedWith("Attempting to delete too many elements.")
})
it('should not allow get to be called on an old value even after deletion', async () => {
pushNum(4)
expect(await Lib_TimeboundRingBuffer.getMaxSize()).to.equal(4)
await expect(Lib_TimeboundRingBuffer.get(0)).to.be.revertedWith("Index too old & has been overridden.")
Lib_TimeboundRingBuffer.deleteElementsAfter(3, NON_NULL_BYTES28)
await expect(Lib_TimeboundRingBuffer.get(0)).to.be.revertedWith("Index too old & has been overridden.")
await expect(Lib_TimeboundRingBuffer.get(4)).to.be.revertedWith("Index too large.")
expect(await Lib_TimeboundRingBuffer.get(1)).to.equal(numToBytes32(1))
expect(await Lib_TimeboundRingBuffer.get(3)).to.equal(numToBytes32(3))
})
it('should not reduce the overall size of the buffer', async () => {
pushNum(4)
expect(await Lib_TimeboundRingBuffer.get(1)).to.equal(numToBytes32(1))
// We expect that we can still access `1` because the deletionOffset
// will have reduced by 1 after we pushed.
Lib_TimeboundRingBuffer.deleteElementsAfter(3, NON_NULL_BYTES28)
expect(await Lib_TimeboundRingBuffer.get(1)).to.equal(numToBytes32(1))
})
})
})
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment