Commit 3ec7c358 authored by Karl Floersch's avatar Karl Floersch

Implement first pass of ring buffer

parent 51e6bbbb
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0;
/* Logging */
import { console } from "@nomiclabs/buidler/console.sol";
struct TimeboundRingBuffer {
mapping(uint=>bytes32) elements;
bytes32 context;
uint32 maxSize;
uint32 maxSizeIncrementAmount;
uint firstElementTimestamp;
uint timeout;
}
/**
* @title Lib_TimeboundRingBuffer
*/
library Lib_TimeboundRingBuffer {
function init(
TimeboundRingBuffer storage _self,
uint32 _startingSize,
uint32 _maxSizeIncrementAmount,
uint _timeout
)
internal
{
_self.maxSize = _startingSize;
_self.maxSizeIncrementAmount = _maxSizeIncrementAmount;
_self.timeout = _timeout;
_self.firstElementTimestamp = block.timestamp;
}
function push(
TimeboundRingBuffer storage _self,
bytes32 _ele,
bytes28 _extraData
)
internal
{
uint length = _getLength(_self.context);
uint maxSize = _self.maxSize;
if (length == maxSize) {
if (block.timestamp < _self.firstElementTimestamp + _self.timeout) {
_self.maxSize += _self.maxSizeIncrementAmount;
maxSize = _self.maxSize;
}
}
_self.elements[length % maxSize] = _ele;
_self.context = makeContext(uint32(length+1), _extraData);
}
function makeContext(
uint32 _length,
bytes28 _extraData
)
internal
pure
returns(
bytes32
)
{
return bytes32(bytes4(_length));
}
function getLength(
TimeboundRingBuffer storage _self
)
internal
view
returns(
uint32
)
{
return _getLength(_self.context);
}
function _getLength(
bytes32 context
)
internal
pure
returns(
uint32
)
{
return uint32(bytes4(context));
}
function get(
TimeboundRingBuffer storage _self,
uint32 _index
)
internal
view
returns(
bytes32
)
{
uint length = _getLength(_self.context);
require(_index < length, "Index too large.");
require(length - _index <= _self.maxSize, "Index too old & has been overridden.");
return _self.elements[_index % _self.maxSize];
}
}
\ No newline at end of file
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
/* Logging */
import { console } from "@nomiclabs/buidler/console.sol";
/* Library Imports */
import { TimeboundRingBuffer, Lib_TimeboundRingBuffer } from "../../optimistic-ethereum/libraries/utils/Lib_TimeboundRingBuffer.sol";
/**
* @title TestLib_TimeboundRingBuffer
*/
contract TestLib_TimeboundRingBuffer {
using Lib_TimeboundRingBuffer for TimeboundRingBuffer;
TimeboundRingBuffer public list;
constructor (
uint32 _startingSize,
uint32 _maxSizeIncrementAmount,
uint _timeout
)
public
{
list.init(_startingSize, _maxSizeIncrementAmount, _timeout);
}
function push(bytes32 _ele, bytes28 _extraData) public {
list.push(_ele, _extraData);
}
function get(uint32 index) public view returns(bytes32) {
return list.get(index);
}
function getLength() public view returns(uint32) {
return list.getLength();
}
function getMaxSize() public view returns(uint32) {
return list.maxSize;
}
function getMaxSizeIncrementAmount() public view returns(uint32) {
return list.maxSizeIncrementAmount;
}
function getFirstElementTimestamp() public view returns(uint) {
return list.firstElementTimestamp;
}
function getTimeout() public view returns(uint) {
return list.timeout;
}
}
\ No newline at end of file
/* tslint:disable:no-empty */
import { expect } from '../../../setup'
/* External Imports */
import { ethers } from '@nomiclabs/buidler'
import { Contract, Signer } from 'ethers'
/* Internal Imports */
import {
NON_NULL_BYTES32,
makeHexString,
fromHexString,
getHexSlice,
increaseEthTime
} from '../../../helpers'
const numToBytes32 = (num: Number) => {
if (num < 0 || num > 255) {
throw new Error('Unsupported number.')
}
const strNum = (num < 16) ? '0' + num.toString(16) : num.toString(16)
return '0x' + '00'.repeat(31) + strNum
}
describe.only('Lib_TimeboundRingBuffer', () => {
let signer: Signer
before(async () => {
;[signer] = await ethers.getSigners()
})
let Lib_TimeboundRingBuffer: Contract
const NON_NULL_BYTES28 = makeHexString('01', 28)
describe('[0,1,2,3] with no timeout', () => {
beforeEach(async () => {
Lib_TimeboundRingBuffer = await (
await ethers.getContractFactory('TestLib_TimeboundRingBuffer')
).deploy(4, 1, 0)
for (let i = 0; i < 4; i++) {
await Lib_TimeboundRingBuffer.push(numToBytes32(i), NON_NULL_BYTES28)
}
})
it('should push a single value which increases the length', async () => {
expect(await Lib_TimeboundRingBuffer.getLength()).to.equal(4)
await Lib_TimeboundRingBuffer.push(NON_NULL_BYTES32, NON_NULL_BYTES28)
expect(await Lib_TimeboundRingBuffer.getLength()).to.equal(5)
})
it('should overwrite old values:[0,1,2,3] -> [4,5,2,3]', async () => {
expect(await Lib_TimeboundRingBuffer.get(0)).to.equal(numToBytes32(0))
await Lib_TimeboundRingBuffer.push(numToBytes32(4), NON_NULL_BYTES28)
expect(await Lib_TimeboundRingBuffer.get(4)).to.equal(numToBytes32(4))
await Lib_TimeboundRingBuffer.push(numToBytes32(5), NON_NULL_BYTES28)
expect(await Lib_TimeboundRingBuffer.get(5)).to.equal(numToBytes32(5))
})
})
describe('push with timeout', () => {
const startSize = 2
beforeEach(async () => {
Lib_TimeboundRingBuffer = await (
await ethers.getContractFactory('TestLib_TimeboundRingBuffer')
).deploy(startSize, 1, 10_000)
for (let i = 0; i < startSize; i++) {
await Lib_TimeboundRingBuffer.push(numToBytes32(i), NON_NULL_BYTES28)
}
})
const pushNum = (num: Number) => Lib_TimeboundRingBuffer.push(numToBytes32(num), NON_NULL_BYTES28)
const pushJunk = () => Lib_TimeboundRingBuffer.push(NON_NULL_BYTES32, NON_NULL_BYTES28)
it('should push a single value which extends the array', async () => {
await Lib_TimeboundRingBuffer.push(numToBytes32(2), NON_NULL_BYTES28)
const increasedSize = startSize + 1
expect(await Lib_TimeboundRingBuffer.getMaxSize()).to.equal(increasedSize)
await increaseEthTime(ethers.provider, 20_000)
await Lib_TimeboundRingBuffer.push(numToBytes32(3), NON_NULL_BYTES28)
expect(await Lib_TimeboundRingBuffer.getMaxSize()).to.equal(increasedSize) // Shouldn't increase the size this time
expect(await Lib_TimeboundRingBuffer.get(2)).to.equal(numToBytes32(2))
expect(await Lib_TimeboundRingBuffer.get(3)).to.equal(numToBytes32(3))
})
it('should NOT extend the array if the time is not up and extend it when it is', async () => {
await pushJunk()
const increasedSize = startSize + 1
expect(await Lib_TimeboundRingBuffer.getMaxSize()).to.equal(increasedSize)
await increaseEthTime(ethers.provider, 20_000)
// Push the time forward and verify that the time doesn't increment
for (let i = 0; i < increasedSize + 1; i++) {
await pushJunk()
}
expect(await Lib_TimeboundRingBuffer.getMaxSize()).to.equal(increasedSize)
})
})
})
\ 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