Refactor Heap.sol to remove index and lookup (#5190)
Co-authored-by: Ernesto García <ernestognw@gmail.com>
This commit is contained in:
@ -1,5 +1,4 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// This file was procedurally generated from scripts/generate/templates/Heap.t.js.
|
||||
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
@ -14,14 +13,8 @@ contract Uint256HeapTest is Test {
|
||||
Heap.Uint256Heap internal heap;
|
||||
|
||||
function _validateHeap(function(uint256, uint256) view returns (bool) comp) internal {
|
||||
for (uint32 i = 0; i < heap.length(); ++i) {
|
||||
// lookups
|
||||
assertEq(i, heap.data[heap.data[i].index].lookup);
|
||||
assertEq(i, heap.data[heap.data[i].lookup].index);
|
||||
|
||||
// ordering: each node has a value bigger then its parent
|
||||
if (i > 0)
|
||||
assertFalse(comp(heap.data[heap.data[i].index].value, heap.data[heap.data[(i - 1) / 2].index].value));
|
||||
for (uint32 i = 1; i < heap.length(); ++i) {
|
||||
assertFalse(comp(heap.tree[i], heap.tree[(i - 1) / 2]));
|
||||
}
|
||||
}
|
||||
|
||||
@ -79,75 +72,3 @@ contract Uint256HeapTest is Test {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contract Uint208HeapTest is Test {
|
||||
using Heap for Heap.Uint208Heap;
|
||||
|
||||
Heap.Uint208Heap internal heap;
|
||||
|
||||
function _validateHeap(function(uint256, uint256) view returns (bool) comp) internal {
|
||||
for (uint32 i = 0; i < heap.length(); ++i) {
|
||||
// lookups
|
||||
assertEq(i, heap.data[heap.data[i].index].lookup);
|
||||
assertEq(i, heap.data[heap.data[i].lookup].index);
|
||||
|
||||
// ordering: each node has a value bigger then its parent
|
||||
if (i > 0)
|
||||
assertFalse(comp(heap.data[heap.data[i].index].value, heap.data[heap.data[(i - 1) / 2].index].value));
|
||||
}
|
||||
}
|
||||
|
||||
function testFuzz(uint208[] calldata input) public {
|
||||
vm.assume(input.length < 0x20);
|
||||
assertEq(heap.length(), 0);
|
||||
|
||||
uint256 min = type(uint256).max;
|
||||
for (uint256 i = 0; i < input.length; ++i) {
|
||||
heap.insert(input[i]);
|
||||
assertEq(heap.length(), i + 1);
|
||||
_validateHeap(Comparators.lt);
|
||||
|
||||
min = Math.min(min, input[i]);
|
||||
assertEq(heap.peek(), min);
|
||||
}
|
||||
|
||||
uint256 max = 0;
|
||||
for (uint256 i = 0; i < input.length; ++i) {
|
||||
uint208 top = heap.peek();
|
||||
uint208 pop = heap.pop();
|
||||
assertEq(heap.length(), input.length - i - 1);
|
||||
_validateHeap(Comparators.lt);
|
||||
|
||||
assertEq(pop, top);
|
||||
assertGe(pop, max);
|
||||
max = pop;
|
||||
}
|
||||
}
|
||||
|
||||
function testFuzzGt(uint208[] calldata input) public {
|
||||
vm.assume(input.length < 0x20);
|
||||
assertEq(heap.length(), 0);
|
||||
|
||||
uint256 max = 0;
|
||||
for (uint256 i = 0; i < input.length; ++i) {
|
||||
heap.insert(input[i], Comparators.gt);
|
||||
assertEq(heap.length(), i + 1);
|
||||
_validateHeap(Comparators.gt);
|
||||
|
||||
max = Math.max(max, input[i]);
|
||||
assertEq(heap.peek(), max);
|
||||
}
|
||||
|
||||
uint256 min = type(uint256).max;
|
||||
for (uint256 i = 0; i < input.length; ++i) {
|
||||
uint208 top = heap.peek();
|
||||
uint208 pop = heap.pop(Comparators.gt);
|
||||
assertEq(heap.length(), input.length - i - 1);
|
||||
_validateHeap(Comparators.gt);
|
||||
|
||||
assertEq(pop, top);
|
||||
assertLe(pop, min);
|
||||
min = pop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,8 +3,6 @@ const { expect } = require('chai');
|
||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
const { PANIC_CODES } = require('@nomicfoundation/hardhat-chai-matchers/panic');
|
||||
|
||||
const { TYPES } = require('../../../scripts/generate/templates/Heap.opts');
|
||||
|
||||
async function fixture() {
|
||||
const mock = await ethers.deployContract('$Heap');
|
||||
return { mock };
|
||||
@ -15,117 +13,101 @@ describe('Heap', function () {
|
||||
Object.assign(this, await loadFixture(fixture));
|
||||
});
|
||||
|
||||
for (const { struct, valueType } of TYPES) {
|
||||
describe(struct, function () {
|
||||
const popEvent = `return$pop_Heap_${struct}`;
|
||||
const replaceEvent = `return$replace_Heap_${struct}_${valueType}`;
|
||||
describe('Uint256Heap', function () {
|
||||
it('starts empty', async function () {
|
||||
expect(await this.mock.$length(0)).to.equal(0n);
|
||||
});
|
||||
|
||||
beforeEach(async function () {
|
||||
this.helper = {
|
||||
clear: (...args) => this.mock[`$clear_Heap_${struct}`](0, ...args),
|
||||
insert: (...args) => this.mock[`$insert(uint256,${valueType})`](0, ...args),
|
||||
replace: (...args) => this.mock[`$replace(uint256,${valueType})`](0, ...args),
|
||||
length: (...args) => this.mock[`$length_Heap_${struct}`](0, ...args),
|
||||
pop: (...args) => this.mock[`$pop_Heap_${struct}`](0, ...args),
|
||||
peek: (...args) => this.mock[`$peek_Heap_${struct}`](0, ...args),
|
||||
};
|
||||
});
|
||||
it('peek, pop and replace from empty', async function () {
|
||||
await expect(this.mock.$peek(0)).to.be.revertedWithPanic(PANIC_CODES.ARRAY_ACCESS_OUT_OF_BOUNDS);
|
||||
await expect(this.mock.$pop(0)).to.be.revertedWithPanic(PANIC_CODES.POP_ON_EMPTY_ARRAY);
|
||||
await expect(this.mock.$replace(0, 0n)).to.be.revertedWithPanic(PANIC_CODES.POP_ON_EMPTY_ARRAY);
|
||||
});
|
||||
|
||||
it('starts empty', async function () {
|
||||
expect(await this.helper.length()).to.equal(0n);
|
||||
});
|
||||
it('clear', async function () {
|
||||
await this.mock.$insert(0, 42n);
|
||||
|
||||
it('peek, pop and replace from empty', async function () {
|
||||
await expect(this.helper.peek()).to.be.revertedWithPanic(PANIC_CODES.ARRAY_ACCESS_OUT_OF_BOUNDS);
|
||||
await expect(this.helper.pop()).to.be.revertedWithPanic(PANIC_CODES.POP_ON_EMPTY_ARRAY);
|
||||
await expect(this.helper.replace(0n)).to.be.revertedWithPanic(PANIC_CODES.POP_ON_EMPTY_ARRAY);
|
||||
});
|
||||
expect(await this.mock.$length(0)).to.equal(1n);
|
||||
expect(await this.mock.$peek(0)).to.equal(42n);
|
||||
|
||||
it('clear', async function () {
|
||||
await this.helper.insert(42n);
|
||||
await this.mock.$clear(0);
|
||||
|
||||
expect(await this.helper.length()).to.equal(1n);
|
||||
expect(await this.helper.peek()).to.equal(42n);
|
||||
expect(await this.mock.$length(0)).to.equal(0n);
|
||||
await expect(this.mock.$peek(0)).to.be.revertedWithPanic(PANIC_CODES.ARRAY_ACCESS_OUT_OF_BOUNDS);
|
||||
});
|
||||
|
||||
await this.helper.clear();
|
||||
it('support duplicated items', async function () {
|
||||
expect(await this.mock.$length(0)).to.equal(0n);
|
||||
|
||||
expect(await this.helper.length()).to.equal(0n);
|
||||
await expect(this.helper.peek()).to.be.revertedWithPanic(PANIC_CODES.ARRAY_ACCESS_OUT_OF_BOUNDS);
|
||||
});
|
||||
// insert 5 times
|
||||
await this.mock.$insert(0, 42n);
|
||||
await this.mock.$insert(0, 42n);
|
||||
await this.mock.$insert(0, 42n);
|
||||
await this.mock.$insert(0, 42n);
|
||||
await this.mock.$insert(0, 42n);
|
||||
|
||||
it('support duplicated items', async function () {
|
||||
expect(await this.helper.length()).to.equal(0n);
|
||||
// pop 5 times
|
||||
await expect(this.mock.$pop(0)).to.emit(this.mock, 'return$pop').withArgs(42n);
|
||||
await expect(this.mock.$pop(0)).to.emit(this.mock, 'return$pop').withArgs(42n);
|
||||
await expect(this.mock.$pop(0)).to.emit(this.mock, 'return$pop').withArgs(42n);
|
||||
await expect(this.mock.$pop(0)).to.emit(this.mock, 'return$pop').withArgs(42n);
|
||||
await expect(this.mock.$pop(0)).to.emit(this.mock, 'return$pop').withArgs(42n);
|
||||
|
||||
// insert 5 times
|
||||
await this.helper.insert(42n);
|
||||
await this.helper.insert(42n);
|
||||
await this.helper.insert(42n);
|
||||
await this.helper.insert(42n);
|
||||
await this.helper.insert(42n);
|
||||
// popping a 6th time panics
|
||||
await expect(this.mock.$pop(0)).to.be.revertedWithPanic(PANIC_CODES.POP_ON_EMPTY_ARRAY);
|
||||
});
|
||||
|
||||
// pop 5 times
|
||||
await expect(this.helper.pop()).to.emit(this.mock, popEvent).withArgs(42n);
|
||||
await expect(this.helper.pop()).to.emit(this.mock, popEvent).withArgs(42n);
|
||||
await expect(this.helper.pop()).to.emit(this.mock, popEvent).withArgs(42n);
|
||||
await expect(this.helper.pop()).to.emit(this.mock, popEvent).withArgs(42n);
|
||||
await expect(this.helper.pop()).to.emit(this.mock, popEvent).withArgs(42n);
|
||||
|
||||
// popping a 6th time panics
|
||||
await expect(this.helper.pop()).to.be.revertedWithPanic(PANIC_CODES.POP_ON_EMPTY_ARRAY);
|
||||
});
|
||||
|
||||
it('insert, pop and replace', async function () {
|
||||
const heap = [];
|
||||
for (const { op, value } of [
|
||||
{ op: 'insert', value: 712 }, // [712]
|
||||
{ op: 'insert', value: 20 }, // [20, 712]
|
||||
{ op: 'insert', value: 4337 }, // [20, 712, 4437]
|
||||
{ op: 'pop' }, // 20, [712, 4437]
|
||||
{ op: 'insert', value: 1559 }, // [712, 1559, 4437]
|
||||
{ op: 'insert', value: 165 }, // [165, 712, 1559, 4437]
|
||||
{ op: 'insert', value: 155 }, // [155, 165, 712, 1559, 4437]
|
||||
{ op: 'insert', value: 7702 }, // [155, 165, 712, 1559, 4437, 7702]
|
||||
{ op: 'pop' }, // 155, [165, 712, 1559, 4437, 7702]
|
||||
{ op: 'replace', value: 721 }, // 165, [712, 721, 1559, 4437, 7702]
|
||||
{ op: 'pop' }, // 712, [721, 1559, 4437, 7702]
|
||||
{ op: 'pop' }, // 721, [1559, 4437, 7702]
|
||||
{ op: 'pop' }, // 1559, [4437, 7702]
|
||||
{ op: 'pop' }, // 4437, [7702]
|
||||
{ op: 'pop' }, // 7702, []
|
||||
{ op: 'pop' }, // panic
|
||||
{ op: 'replace', value: '1363' }, // panic
|
||||
]) {
|
||||
switch (op) {
|
||||
case 'insert':
|
||||
await this.helper.insert(value);
|
||||
it('insert, pop and replace', async function () {
|
||||
const heap = [];
|
||||
for (const { op, value } of [
|
||||
{ op: 'insert', value: 712 }, // [712]
|
||||
{ op: 'insert', value: 20 }, // [20, 712]
|
||||
{ op: 'insert', value: 4337 }, // [20, 712, 4437]
|
||||
{ op: 'pop' }, // 20, [712, 4437]
|
||||
{ op: 'insert', value: 1559 }, // [712, 1559, 4437]
|
||||
{ op: 'insert', value: 165 }, // [165, 712, 1559, 4437]
|
||||
{ op: 'insert', value: 155 }, // [155, 165, 712, 1559, 4437]
|
||||
{ op: 'insert', value: 7702 }, // [155, 165, 712, 1559, 4437, 7702]
|
||||
{ op: 'pop' }, // 155, [165, 712, 1559, 4437, 7702]
|
||||
{ op: 'replace', value: 721 }, // 165, [712, 721, 1559, 4437, 7702]
|
||||
{ op: 'pop' }, // 712, [721, 1559, 4437, 7702]
|
||||
{ op: 'pop' }, // 721, [1559, 4437, 7702]
|
||||
{ op: 'pop' }, // 1559, [4437, 7702]
|
||||
{ op: 'pop' }, // 4437, [7702]
|
||||
{ op: 'pop' }, // 7702, []
|
||||
{ op: 'pop' }, // panic
|
||||
{ op: 'replace', value: '1363' }, // panic
|
||||
]) {
|
||||
switch (op) {
|
||||
case 'insert':
|
||||
await this.mock.$insert(0, value);
|
||||
heap.push(value);
|
||||
heap.sort((a, b) => a - b);
|
||||
break;
|
||||
case 'pop':
|
||||
if (heap.length == 0) {
|
||||
await expect(this.mock.$pop(0)).to.be.revertedWithPanic(PANIC_CODES.POP_ON_EMPTY_ARRAY);
|
||||
} else {
|
||||
await expect(this.mock.$pop(0)).to.emit(this.mock, 'return$pop').withArgs(heap.shift());
|
||||
}
|
||||
break;
|
||||
case 'replace':
|
||||
if (heap.length == 0) {
|
||||
await expect(this.mock.$replace(0, value)).to.be.revertedWithPanic(PANIC_CODES.POP_ON_EMPTY_ARRAY);
|
||||
} else {
|
||||
await expect(this.mock.$replace(0, value)).to.emit(this.mock, 'return$replace').withArgs(heap.shift());
|
||||
heap.push(value);
|
||||
heap.sort((a, b) => a - b);
|
||||
break;
|
||||
case 'pop':
|
||||
if (heap.length == 0) {
|
||||
await expect(this.helper.pop()).to.be.revertedWithPanic(PANIC_CODES.POP_ON_EMPTY_ARRAY);
|
||||
} else {
|
||||
await expect(this.helper.pop()).to.emit(this.mock, popEvent).withArgs(heap.shift());
|
||||
}
|
||||
break;
|
||||
case 'replace':
|
||||
if (heap.length == 0) {
|
||||
await expect(this.helper.replace(value)).to.be.revertedWithPanic(PANIC_CODES.POP_ON_EMPTY_ARRAY);
|
||||
} else {
|
||||
await expect(this.helper.replace(value)).to.emit(this.mock, replaceEvent).withArgs(heap.shift());
|
||||
heap.push(value);
|
||||
heap.sort((a, b) => a - b);
|
||||
}
|
||||
break;
|
||||
}
|
||||
expect(await this.helper.length()).to.equal(heap.length);
|
||||
if (heap.length == 0) {
|
||||
await expect(this.helper.peek()).to.be.revertedWithPanic(PANIC_CODES.ARRAY_ACCESS_OUT_OF_BOUNDS);
|
||||
} else {
|
||||
expect(await this.helper.peek()).to.equal(heap[0]);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
expect(await this.mock.$length(0)).to.equal(heap.length);
|
||||
if (heap.length == 0) {
|
||||
await expect(this.mock.$peek(0)).to.be.revertedWithPanic(PANIC_CODES.ARRAY_ACCESS_OUT_OF_BOUNDS);
|
||||
} else {
|
||||
expect(await this.mock.$peek(0)).to.equal(heap[0]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user