Commit f6c20be2 authored by Karl Floersch's avatar Karl Floersch

Add deletion to ring buffer

parent 1a14b811
...@@ -9,6 +9,7 @@ struct TimeboundRingBuffer { ...@@ -9,6 +9,7 @@ struct TimeboundRingBuffer {
bytes32 context; bytes32 context;
uint32 maxSize; uint32 maxSize;
uint32 maxSizeIncrementAmount; uint32 maxSizeIncrementAmount;
uint32 deletionOffset;
uint firstElementTimestamp; uint firstElementTimestamp;
uint timeout; uint timeout;
} }
...@@ -31,7 +32,7 @@ library Lib_TimeboundRingBuffer { ...@@ -31,7 +32,7 @@ library Lib_TimeboundRingBuffer {
_self.firstElementTimestamp = block.timestamp; _self.firstElementTimestamp = block.timestamp;
} }
function push( function pushAppendOnly(
TimeboundRingBuffer storage _self, TimeboundRingBuffer storage _self,
bytes32 _ele, bytes32 _ele,
bytes28 _extraData bytes28 _extraData
...@@ -50,7 +51,20 @@ library Lib_TimeboundRingBuffer { ...@@ -50,7 +51,20 @@ library Lib_TimeboundRingBuffer {
_self.context = makeContext(uint32(length+1), _extraData); _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, TimeboundRingBuffer storage _self,
bytes32 _ele1, bytes32 _ele1,
bytes32 _ele2, bytes32 _ele2,
...@@ -72,6 +86,20 @@ library Lib_TimeboundRingBuffer { ...@@ -72,6 +86,20 @@ library Lib_TimeboundRingBuffer {
_self.context = makeContext(uint32(length+2), _extraData); _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( function makeContext(
uint32 _length, uint32 _length,
bytes28 _extraData bytes28 _extraData
...@@ -106,8 +134,8 @@ library Lib_TimeboundRingBuffer { ...@@ -106,8 +134,8 @@ library Lib_TimeboundRingBuffer {
uint32 uint32
) )
{ {
bytes32 lengthMask = 0x00000000000000000000000000000000000000000000000000000000ffffffff; // Length is the last 4 bytes
return uint32(uint256(context & lengthMask)); return uint32(uint256(context & 0x00000000000000000000000000000000000000000000000000000000ffffffff));
} }
function getExtraData( function getExtraData(
...@@ -119,8 +147,8 @@ library Lib_TimeboundRingBuffer { ...@@ -119,8 +147,8 @@ library Lib_TimeboundRingBuffer {
bytes28 bytes28
) )
{ {
bytes32 extraDataMask = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000; // Extra Data is the first 28 bytes
return bytes28(_self.context & extraDataMask); return bytes28(_self.context & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000);
} }
function get( function get(
...@@ -135,7 +163,26 @@ library Lib_TimeboundRingBuffer { ...@@ -135,7 +163,26 @@ library Lib_TimeboundRingBuffer {
{ {
uint length = _getLength(_self.context); uint length = _getLength(_self.context);
require(_index < length, "Index too large."); 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]; 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 { ...@@ -34,8 +34,12 @@ contract TestLib_TimeboundRingBuffer {
list.push2(_ele1, _ele2, _extraData); list.push2(_ele1, _ele2, _extraData);
} }
function get(uint32 index) public view returns(bytes32) { function get(uint32 _index) public view returns(bytes32) {
return list.get(index); return list.get(_index);
}
function deleteElementsAfter(uint32 _index, bytes28 _extraData) public {
return list.deleteElementsAfter(_index, _extraData);
} }
function getLength() public view returns(uint32) { function getLength() public view returns(uint32) {
......
...@@ -29,6 +29,8 @@ describe('Lib_TimeboundRingBuffer', () => { ...@@ -29,6 +29,8 @@ describe('Lib_TimeboundRingBuffer', () => {
let Lib_TimeboundRingBuffer: Contract let Lib_TimeboundRingBuffer: Contract
const NON_NULL_BYTES28 = makeHexString('01', 28) 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', () => { describe('push with no timeout', () => {
beforeEach(async () => { beforeEach(async () => {
...@@ -77,7 +79,6 @@ describe('Lib_TimeboundRingBuffer', () => { ...@@ -77,7 +79,6 @@ describe('Lib_TimeboundRingBuffer', () => {
describe('push with timeout', () => { describe('push with timeout', () => {
const startSize = 2 const startSize = 2
const pushNum = (num: Number) => Lib_TimeboundRingBuffer.push(numToBytes32(num), NON_NULL_BYTES28)
beforeEach(async () => { beforeEach(async () => {
Lib_TimeboundRingBuffer = await ( Lib_TimeboundRingBuffer = await (
...@@ -118,15 +119,12 @@ describe('Lib_TimeboundRingBuffer', () => { ...@@ -118,15 +119,12 @@ describe('Lib_TimeboundRingBuffer', () => {
describe('push2 with timeout', () => { describe('push2 with timeout', () => {
const startSize = 2 const startSize = 2
const push2Nums = (num1: Number, num2: Number) => Lib_TimeboundRingBuffer.push2(numToBytes32(num1), numToBytes32(num2), NON_NULL_BYTES28)
beforeEach(async () => { beforeEach(async () => {
Lib_TimeboundRingBuffer = await ( Lib_TimeboundRingBuffer = await (
await ethers.getContractFactory('TestLib_TimeboundRingBuffer') await ethers.getContractFactory('TestLib_TimeboundRingBuffer')
).deploy(startSize, 1, 10_000) ).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 () => { it('should push a single value which extends the array', async () => {
await push2Nums(0, 1) await push2Nums(0, 1)
...@@ -156,4 +154,42 @@ describe('Lib_TimeboundRingBuffer', () => { ...@@ -156,4 +154,42 @@ describe('Lib_TimeboundRingBuffer', () => {
expect(await Lib_TimeboundRingBuffer.getExtraData()).to.equal(NON_NULL_BYTES28) 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