tests: add test coverage for the queue contract (#33)

* tests: add 100% test coverage for the queue contract

* lint: run prettier on js tests and scripts

* feature: allow Queue to efficiently operate on N items

* chore: update to use USD in gas reports

* chore: enable solidity optimizer with many runs
buddies-main-deployment
James Prestwich 4 years ago committed by James Prestwich
parent 9c5980ee09
commit 3e00a306db
No known key found for this signature in database
GPG Key ID: 7CC174C250AD83AD
  1. 4
      solidity/.gitignore
  2. 8
      solidity/.prettierrc
  3. 3
      solidity/.solcover.js
  4. 90
      solidity/contracts/Queue.sol
  5. 53
      solidity/contracts/test/TestQueue.sol
  6. 24
      solidity/hardhat.config.js
  7. 23330
      solidity/package-lock.json
  8. 13
      solidity/package.json
  9. 10
      solidity/scripts/deploy.js
  10. 2
      solidity/scripts/index.js
  11. 70
      solidity/test/Queue.test.js

@ -1,3 +1,5 @@
node_modules/
cache/
artifacts/
artifacts/
coverage/
coverage.json

@ -1,5 +1,13 @@
{
"overrides": [
{
"files": "*.js",
"options": {
"tabWidth": 2,
"singleQuote": true,
"trailingComma": "all"
}
},
{
"files": "*.sol",
"options": {

@ -0,0 +1,3 @@
module.exports = {
skipFiles: ["test"],
};

@ -3,8 +3,8 @@ pragma solidity >=0.6.11;
library QueueLib {
struct Queue {
uint256 first;
uint256 last;
uint128 first;
uint128 last;
mapping(uint256 => bytes32) queue;
}
@ -14,6 +14,63 @@ library QueueLib {
}
}
function enqueue(Queue storage _q, bytes32 _item)
internal
returns (uint128 _last)
{
_last = _q.last + 1;
_q.last = _last;
if (_item != bytes32(0)) {
// saves gas if we're queueing 0
_q.queue[_last] = _item;
}
}
function dequeue(Queue storage _q) internal returns (bytes32 _item) {
uint128 _last = _q.last;
uint128 _first = _q.first;
require(_length(_last, _first) != 0, "Empty");
_item = _q.queue[_first];
if (_item != bytes32(0)) {
// saves gas if we're dequeuing 0
delete _q.queue[_first];
}
_q.first = _first + 1;
}
function enqueue(Queue storage _q, bytes32[] memory _items)
internal
returns (uint128 _last)
{
_last = _q.last;
for (uint i = 0; i<_items.length; i+=1) {
_last+= 1;
bytes32 _item = _items[i];
if (_item != bytes32(0)) {
_q.queue[_last] = _item;
}
}
_q.last = _last;
}
function dequeue(Queue storage _q, uint256 _number) internal returns (bytes32[] memory) {
uint128 _last = _q.last;
uint128 _first = _q.first;
// Cannot underflow unless state is corrupted
require(_length(_last, _first) >= _number, "Insufficient");
bytes32[] memory _items = new bytes32[](_number);
for (uint i = 0; i < _number; i++) {
_items[i] = _q.queue[_first];
delete _q.queue[_first];
_first++;
}
_q.first = _first;
return _items;
}
// NB: this is unfortunately expensive
function contains(Queue storage _q, bytes32 _item)
internal
@ -33,36 +90,23 @@ library QueueLib {
}
function peek(Queue storage _q) internal view returns (bytes32 _item) {
require(_q.last >= _q.first, "Empty");
require(!isEmpty(_q), "Empty");
_item = _q.queue[_q.first];
}
function enqueue(Queue storage _q, bytes32 _item)
internal
returns (uint256 _last)
{
_last = _q.last + 1;
_q.last = _last;
if (_item != bytes32(0)) {
// saves gas if we're queueing 0
_q.queue[_last] = _item;
}
function isEmpty(Queue storage _q) internal view returns (bool) {
return _q.last < _q.first;
}
function dequeue(Queue storage _q) internal returns (bytes32 _item) {
uint256 _first = _q.first;
require(_q.last >= _first, "Empty");
_item = _q.queue[_first];
if (_item != bytes32(0)) {
// saves gas if we're dequeuing 0
delete _q.queue[_first];
}
_q.first = _first + 1;
function _length(uint128 _last, uint128 _first) internal pure returns (uint256) {
return uint256(_last + 1 - _first);
}
function length(Queue storage _q) internal view returns (uint256) {
uint128 _last = _q.last;
uint128 _first = _q.first;
// Cannot underflow unless state is corrupted
return _q.first - _q.last - 1;
return _length(_last, _first);
}
}

@ -0,0 +1,53 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.6.11;
import "../Queue.sol";
contract TestQueue is QueueManager {
using QueueLib for QueueLib.Queue;
constructor() QueueManager() {}
// NB: this is unfortunately expensive
function contains(bytes32 _item) external view returns (bool) {
return queue.contains(_item);
}
function lastItem() external view returns (bytes32) {
return queue.lastItem();
}
function peek() external view returns (bytes32 _item) {
return queue.peek();
}
function enqueue(bytes32 _item) external returns (uint256 _last) {
return queue.enqueue(_item);
}
function dequeue() external returns (bytes32 _item) {
return queue.dequeue();
}
function enqueueMany(bytes32[] calldata _items) external returns (uint256 _last) {
return queue.enqueue(_items);
}
function dequeueMany(uint256 _number) external returns (bytes32[] memory _items) {
return queue.dequeue(_number);
}
function length() external view returns (uint256) {
return queue.length();
}
function drain() external {
while (queue.length() != 0) {
queue.dequeue();
}
}
function initAgain() external {
queue.init();
}
}

@ -1,18 +1,30 @@
require("@nomiclabs/hardhat-waffle");
require("@nomiclabs/hardhat-ethers");
require("hardhat-gas-reporter");
require('@nomiclabs/hardhat-waffle');
require('hardhat-gas-reporter');
require('solidity-coverage');
require("./scripts");
require('./scripts');
/**
* @type import('hardhat/config').HardhatUserConfig
*/
module.exports = {
solidity: "0.7.6",
solidity: {
version: '0.7.6',
settings: {
optimizer: {
enabled: true,
runs: 999999,
},
},
},
gasReporter: {
currency: 'USD',
},
networks: {
localhost: {
url: "http://localhost:8545",
url: 'http://localhost:8545',
},
},
};

File diff suppressed because it is too large Load Diff

@ -5,12 +5,14 @@
"@nomiclabs/hardhat-waffle": "^2.0.1",
"@openzeppelin/contracts": "^3.3.0",
"@summa-tx/memview-sol": "^1.1.1",
"ethereum-waffle": "^3.2.1",
"ethers": "^5.0.25",
"hardhat": "^2.0.7",
"chai": "^4.2.0",
"ethereum-waffle": "^3.2.2",
"ethers": "^5.0.26",
"hardhat": "^2.0.8",
"hardhat-gas-reporter": "^1.0.4",
"prettier": "2.2.1",
"prettier-plugin-solidity": "^1.0.0-beta.3"
"prettier-plugin-solidity": "^1.0.0-beta.3",
"solidity-coverage": "^0.7.14"
},
"version": "0.0.0",
"description": "Optimistic Interchain Communications",
@ -20,8 +22,9 @@
},
"dependencies": {},
"scripts": {
"prettier": "prettier --write 'contracts/**/*.sol'",
"prettier": "prettier --write 'contracts/**/*.sol' ./test ./scripts",
"compile": "hardhat compile && ./scripts/update_abis.sh",
"coverage": "hardhat coverage",
"test": "hardhat test"
},
"author": "James Prestwich",

@ -1,7 +1,7 @@
const { types } = require("hardhat/config");
const { types } = require('hardhat/config');
task("deploy-home")
.addParam("slip44", "The origin chain SLIP44 ID", undefined, types.int)
.addParam("updater", "The origin chain updater", undefined, types.string)
.addParam("currentRoot", "The current root")
task('deploy-home')
.addParam('slip44', 'The origin chain SLIP44 ID', undefined, types.int)
.addParam('updater', 'The origin chain updater', undefined, types.string)
.addParam('currentRoot', 'The current root')
.setAction(async (args) => {});

@ -1 +1 @@
require("./deploy.js");
require('./deploy.js');

@ -0,0 +1,70 @@
/* global describe before it */
const { waffle, ethers } = require('hardhat');
const { expect } = require('chai');
describe('Queue', async () => {
let queue;
// create a proper hex encoded bytes32 filled with number. e.g 0x01010101...
const bytes32 = (num) => `0x${Buffer.alloc(32, num).toString('hex')}`;
before(async () => {
const Queue = await ethers.getContractFactory('TestQueue');
queue = await Queue.deploy();
await queue.deployed();
});
it('should function as a queue', async () => {
// we put this here for coverage to check that init properly does nothing
queue.initAgain();
const items = Array.from(new Array(10).keys()).map((i) => bytes32(i));
for (const [idx, item] of items.entries()) {
await queue.enqueue(item);
const length = await queue.length();
expect(length).to.equal(idx + 1);
}
// last item
const last = await queue.lastItem();
expect(last).to.equal(items[items.length - 1]);
// contains
expect(await queue.contains(bytes32(3))).to.be.true;
expect(await queue.contains(bytes32(0xff))).to.be.false;
for (const [idx, item] of items.entries()) {
// peek and dequeue
const dequeued = await queue.peek();
await queue.dequeue();
expect(dequeued).to.equal(item);
// length
const length = await queue.length();
expect(length).to.equal(items.length - idx - 1);
}
// reverts
await expect(queue.dequeue()).to.be.revertedWith('Empty');
await expect(queue.peek()).to.be.revertedWith('Empty');
// Multi-enq
await queue.enqueueMany(items);
let length = await queue.length();
expect(length).to.equal(items.length);
// Multi-deq static call to check ret val
let deqed = await queue.callStatic.dequeueMany(items.length);
items.forEach((item, idx) => {
expect(item).to.equal(deqed[idx]);
});
// Multi-deq tx to check function
await queue.dequeueMany(items.length);
length = await queue.length();
expect(length).to.equal(0);
});
});
Loading…
Cancel
Save