Commit dcfd3df9 authored by Kelvin Fichter's avatar Kelvin Fichter

Cleaned up CTC and added some tests

parent fba4de0d
......@@ -45,8 +45,8 @@ contract OVM_BaseChain is iOVM_BaseChain {
* @return _totalElements Total submitted elements.
*/
function getTotalElements()
override
virtual
override
public
view
returns (
......
......@@ -8,65 +8,43 @@ import { Lib_AddressResolver } from "../../libraries/resolver/Lib_AddressResolve
import { Lib_MerkleUtils } from "../../libraries/utils/Lib_MerkleUtils.sol";
import { Lib_MerkleRoot } from "../../libraries/utils/Lib_MerkleRoot.sol";
import { TimeboundRingBuffer, Lib_TimeboundRingBuffer } from "../../libraries/utils/Lib_TimeboundRingBuffer.sol";
import { console } from "@nomiclabs/buidler/console.sol";
/* Interface Imports */
import { iOVM_BaseChain } from "../../iOVM/chain/iOVM_BaseChain.sol";
import { iOVM_CanonicalTransactionChain } from "../../iOVM/chain/iOVM_CanonicalTransactionChain.sol";
/* Contract Imports */
import { OVM_BaseChain } from "./OVM_BaseChain.sol";
/* Logging Imports */
import { console } from "@nomiclabs/buidler/console.sol";
/**
* @title OVM_CanonicalTransactionChain
*/
contract OVM_CanonicalTransactionChain is OVM_BaseChain, Lib_AddressResolver { // TODO: re-add iOVM_CanonicalTransactionChain
contract OVM_CanonicalTransactionChain is iOVM_CanonicalTransactionChain, OVM_BaseChain, Lib_AddressResolver {
/**********
* Events *
*********/
event queueTransactionAppended(bytes _queueTransaction, bytes32 timestampAndBlockNumber);
event chainBatchAppended(uint _startingQueueIndex, uint _numQueueElements);
/*************
* Constants *
*************/
uint256 constant public MIN_ROLLUP_TX_GAS = 20000;
uint256 constant public MAX_ROLLUP_TX_SIZE = 10000;
uint256 constant public L2_GAS_DISCOUNT_DIVISOR = 10;
/*************************************************
* Contract Variables: Transaction Restrinctions *
*************************************************/
uint constant MAX_ROLLUP_TX_SIZE = 10000;
uint constant L2_GAS_DISCOUNT_DIVISOR = 10;
/*************
* Variables *
*************/
uint256 internal forceInclusionPeriodSeconds;
uint256 internal lastOVMTimestamp;
address internal sequencer;
using Lib_TimeboundRingBuffer for TimeboundRingBuffer;
TimeboundRingBuffer internal queue;
TimeboundRingBuffer internal chain;
struct BatchContext {
uint numSequencedTransactions;
uint numSubsequentQueueTransactions;
uint timestamp;
uint blockNumber;
}
struct TransactionChainElement {
bool isSequenced;
uint queueIndex; // QUEUED TX ONLY
uint timestamp; // SEQUENCER TX ONLY
uint blockNumber; // SEQUENCER TX ONLY
bytes txData; // SEQUENCER TX ONLY
}
/*******************************************
* Contract Variables: Contract References *
*******************************************/
/*******************************************
* Contract Variables: Internal Accounting *
*******************************************/
uint256 internal forceInclusionPeriodSeconds;
uint256 internal lastOVMTimestamp;
address internal sequencerAddress;
/***************
* Constructor *
......@@ -82,281 +60,409 @@ contract OVM_CanonicalTransactionChain is OVM_BaseChain, Lib_AddressResolver { /
)
Lib_AddressResolver(_libAddressManager)
{
sequencerAddress = resolve("OVM_Sequencer");
sequencer = resolve("OVM_Sequencer");
forceInclusionPeriodSeconds = _forceInclusionPeriodSeconds;
queue.init(100, 50, 10000000000); // TODO: Update once we have arbitrary condition
batches.init(100, 50, 10000000000); // TODO: Update once we have arbitrary condition
}
/***************************************
* Public Functions: Transaction Queue *
**************************************/
/********************
* Public Functions *
********************/
function getTotalElements()
override(OVM_BaseChain, iOVM_BaseChain)
public
view
returns (
uint256 _totalElements
)
{
(uint40 totalElements,) = _getLatestBatchContext();
return uint256(totalElements);
}
/**
* @inheritdoc iOVM_CanonicalTransactionChain
*/
function getQueueElement(
uint256 _index
)
override
public
view
returns (
Lib_OVMCodec.QueueElement memory _element
)
{
uint32 trueIndex = uint32(_index * 2);
bytes32 queueRoot = queue.get(trueIndex);
bytes32 timestampAndBlockNumber = queue.get(trueIndex + 1);
uint40 elementTimestamp;
uint32 elementBlockNumber;
assembly {
elementTimestamp := and(timestampAndBlockNumber, 0x000000000000000000000000000000000000000000000000000000ffffffffff)
elementBlockNumber := shr(40, and(timestampAndBlockNumber, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000))
}
return Lib_OVMCodec.QueueElement({
queueRoot: queueRoot,
timestamp: elementTimestamp,
blockNumber: elementBlockNumber
});
}
/**
* Adds a transaction to the queue.
* @param _target Target contract to send the transaction to.
* @param _gasLimit Gas limit for the given transaction.
* @param _data Transaction data.
* @inheritdoc iOVM_CanonicalTransactionChain
*/
function enqueue(
address _target,
uint256 _gasLimit,
bytes memory _data
)
override
public
{
require(
_data.length <= MAX_ROLLUP_TX_SIZE,
"Transaction exceeds maximum rollup data size."
);
require(_gasLimit >= 20000, "Layer 2 gas limit too low to enqueue.");
// Consume l1 gas rate limit queued transactions
uint gasToConsume = _gasLimit/L2_GAS_DISCOUNT_DIVISOR;
uint startingGas = gasleft();
uint i;
while(startingGas - gasleft() > gasToConsume) {
i++; // TODO: Replace this dumb work with minting gas token. (not today)
require(
_gasLimit >= MIN_ROLLUP_TX_GAS,
"Layer 2 gas limit too low to enqueue."
);
uint256 gasToConsume = _gasLimit/L2_GAS_DISCOUNT_DIVISOR;
uint256 startingGas = gasleft();
// Although this check is not necessary (burn below will run out of gas if not true), it
// gives the user an explicit reason as to why the enqueue attempt failed.
require(
startingGas > gasToConsume,
"Insufficient gas for L2 rate limiting burn."
);
// We need to consume some amount of L1 gas in order to rate limit transactions going into
// L2. However, L2 is cheaper than L1 so we only need to burn some small proportion of the
// provided L1 gas.
//
// Here we do some "dumb" work in order to burn gas, although we should probably replace
// this with something like minting gas token later on.
uint256 i;
while(startingGas - gasleft() < gasToConsume) {
i++;
}
bytes memory queueTx = abi.encode(
bytes memory transaction = abi.encode(
msg.sender,
_target,
_gasLimit,
_data
);
bytes32 queueRoot = keccak256(queueTx);
// bytes is left aligned, uint is right aligned - use this to encode them together
bytes32 timestampAndBlockNumber = bytes32(bytes4(uint32(block.number))) | bytes32(uint256(uint40(block.timestamp)));
// bytes32 timestampAndBlockNumber = bytes32(bytes4(uint32(999))) | bytes32(uint256(uint40(777)));
queue.push2(queueRoot, timestampAndBlockNumber, bytes28(0));
emit queueTransactionAppended(queueTx, timestampAndBlockNumber);
}
function getQueueElement(uint queueIndex) public view returns(Lib_OVMCodec.QueueElement memory) {
uint32 trueIndex = uint32(queueIndex * 2);
bytes32 queueRoot = queue.get(trueIndex);
bytes32 timestampAndBlockNumber = queue.get(trueIndex + 1);
uint40 timestamp = uint40(uint256(timestampAndBlockNumber & 0x000000000000000000000000000000000000000000000000000000ffffffffff));
uint32 blockNumber = uint32(bytes4(timestampAndBlockNumber & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000));
return Lib_OVMCodec.QueueElement({
queueRoot: queueRoot,
timestamp: timestamp,
blockNumber: blockNumber
});
}
bytes32 transactionHash = keccak256(transaction);
bytes32 timestampAndBlockNumber;
assembly {
timestampAndBlockNumber := or(timestamp(), shl(40, number()))
}
function getLatestBatchContext() public view returns(uint40 totalElements, uint32 nextQueueIndex) {
bytes28 extraData = batches.getExtraData();
totalElements = uint40(uint256(uint224(extraData & 0x0000000000000000000000000000000000000000000000ffffffffff)));
nextQueueIndex = uint32(bytes4(extraData & 0xffffffffffffffffffffffffffffffffffffffffffffff0000000000));
return (totalElements, nextQueueIndex);
}
queue.push2(transactionHash, timestampAndBlockNumber, bytes28(0));
function makeLatestBatchContext(uint40 totalElements, uint32 nextQueueIndex) public view returns(bytes28) {
bytes28 totalElementsAndNextQueueIndex = bytes28(bytes4(uint32(nextQueueIndex))) | bytes28(uint224(uint40(totalElements)));
return totalElementsAndNextQueueIndex;
emit QueueTransactionAppended(transaction, timestampAndBlockNumber);
}
/****************************************
* Public Functions: Batch Manipulation *
****************************************/
/**
* Appends a sequencer batch.
* @inheritdoc iOVM_CanonicalTransactionChain
*/
function appendQueueBatch(uint numQueuedTransactions)
function appendQueueBatch(
uint _numQueuedTransactions
)
override
public
{
// Get all of the leaves
(uint40 totalElements, uint32 nextQueueIndex) = getLatestBatchContext();
bytes32[] memory leaves = new bytes32[](numQueuedTransactions);
for (uint i = 0; i < numQueuedTransactions; i++) {
require(
_numQueuedTransactions > 0,
"Must append more than zero transactions."
);
(uint40 totalElements, uint32 nextQueueIndex) = _getLatestBatchContext();
bytes32[] memory leaves = new bytes32[](_numQueuedTransactions);
for (uint i = 0; i < _numQueuedTransactions; i++) {
leaves[i] = _getQueueLeafHash(nextQueueIndex);
nextQueueIndex++;
}
bytes32 root = _getRoot(leaves);
_appendBatch(
root,
numQueuedTransactions,
numQueuedTransactions
);
}
function _getQueueLeafHash(
uint queueIndex
)
internal
view
returns(bytes32)
{
// TODO: Improve this require statement (the `queueIndex*2` is ugly)
require(queueIndex*2 != queue.getLength(), "Queue index too large.");
TransactionChainElement memory element = TransactionChainElement({
isSequenced: false,
queueIndex: queueIndex,
timestamp: 0,
blockNumber: 0,
txData: hex""
});
require(
msg.sender == sequencerAddress || element.timestamp + forceInclusionPeriodSeconds <= block.timestamp,
"Message sender does not have permission to append this batch"
Lib_MerkleRoot.getMerkleRoot(leaves),
_numQueuedTransactions,
_numQueuedTransactions
);
return _hashTransactionChainElement(element);
}
function _appendBatch(
bytes32 transactionRoot,
uint batchSize,
uint numQueuedTransactions
)
internal
{
(uint40 totalElements, uint32 nextQueueIndex) = getLatestBatchContext();
Lib_OVMCodec.ChainBatchHeader memory header = Lib_OVMCodec.ChainBatchHeader({
batchIndex: batches.getLength(),
batchRoot: transactionRoot,
batchSize: batchSize,
prevTotalElements: totalElements,
extraData: hex""
});
bytes32 batchHeaderHash = _hashBatchHeader(header);
bytes28 latestBatchContext = makeLatestBatchContext(
totalElements + uint40(header.batchSize),
nextQueueIndex + uint32(numQueuedTransactions)
emit ChainBatchAppended(
nextQueueIndex - _numQueuedTransactions,
_numQueuedTransactions
);
batches.push(batchHeaderHash, latestBatchContext);
}
/**
* Appends a sequencer batch.
* @inheritdoc iOVM_CanonicalTransactionChain
*/
function appendSequencerBatch(
bytes[] memory _rawTransactions, // 2 byte prefix for how many elements, per element 3 byte prefix.
BatchContext[] memory _batchContexts, // 2 byte prefix for how many elements, fixed size elements
uint256 _shouldStartAtBatch, // 6 bytes
uint _totalElementsToAppend // 2 btyes
bytes[] memory _transactions,
BatchContext[] memory _contexts,
uint256 _shouldStartAtBatch,
uint _totalElementsToAppend
)
// override
public // TODO: can we make external? Hopefully so
override
public
{
require(
_shouldStartAtBatch == getTotalBatches(),
"Batch submission failed: chain length has become larger than expected"
"Actual batch start index does not match expected start index."
);
require(
msg.sender == sequencerAddress,
msg.sender == sequencer,
"Function can only be called by the Sequencer."
);
// Initialize an array which will contain the leaves of the merkle tree commitment
require(
_contexts.length > 0,
"Must provide at least one batch context."
);
require(
_totalElementsToAppend > 0,
"Must append at least one element."
);
bytes32[] memory leaves = new bytes32[](_totalElementsToAppend);
uint32 transactionIndex = 0;
uint32 numSequencerTransactionsProcessed = 0;
(, uint32 nextQueueIndex) = getLatestBatchContext();
for (uint32 batchContextIndex = 0; batchContextIndex < _batchContexts.length; batchContextIndex++) {
//////////////////// Process Sequencer Transactions \\\\\\\\\\\\\\\\\\\\
BatchContext memory curContext = _batchContexts[batchContextIndex];
_validateBatchContext(curContext, nextQueueIndex);
uint numSequencedTransactions = curContext.numSequencedTransactions;
for (uint32 i = 0; i < numSequencedTransactions; i++) {
leaves[transactionIndex] = keccak256(abi.encode(
false,
0,
curContext.timestamp,
curContext.blockNumber,
_rawTransactions[numSequencerTransactionsProcessed]
));
(, uint32 nextQueueIndex) = _getLatestBatchContext();
for (uint32 i = 0; i < _contexts.length; i++) {
BatchContext memory context = _contexts[i];
_validateBatchContext(context, nextQueueIndex);
for (uint32 i = 0; i < context.numSequencedTransactions; i++) {
leaves[transactionIndex] = _hashTransactionChainElement(
TransactionChainElement({
isSequenced: true,
queueIndex: 0,
timestamp: context.timestamp,
blockNumber: context.blockNumber,
txData: _transactions[numSequencerTransactionsProcessed]
})
);
numSequencerTransactionsProcessed++;
transactionIndex++;
}
//////////////////// Process Queue Transactions \\\\\\\\\\\\\\\\\\\\
uint numQueuedTransactions = curContext.numSubsequentQueueTransactions;
for (uint i = 0; i < numQueuedTransactions; i++) {
for (uint32 i = 0; i < context.numSubsequentQueueTransactions; i++) {
leaves[transactionIndex] = _getQueueLeafHash(nextQueueIndex);
nextQueueIndex++;
transactionIndex++;
}
}
// Make sure the correct number of leaves were calculated
require(transactionIndex == _totalElementsToAppend, "Not enough transactions supplied!");
require(
transactionIndex == _totalElementsToAppend,
"Actual transaction index does not match expected total elements to append."
);
bytes32 root = _getRoot(leaves);
uint numQueuedTransactions = _totalElementsToAppend - numSequencerTransactionsProcessed;
uint256 numQueuedTransactions = _totalElementsToAppend - numSequencerTransactionsProcessed;
_appendBatch(
root,
Lib_MerkleRoot.getMerkleRoot(leaves),
_totalElementsToAppend,
numQueuedTransactions
);
emit chainBatchAppended(nextQueueIndex-numQueuedTransactions, numQueuedTransactions);
emit ChainBatchAppended(
nextQueueIndex - numQueuedTransactions,
numQueuedTransactions
);
}
/**********************
* Internal Functions *
**********************/
/**
* Parses the batch context from the extra data.
* @return _totalElements Total number of elements submitted.
* @return _nextQueueIndex Index of the next queue element.
*/
function _getLatestBatchContext()
internal
view
returns (
uint40 _totalElements,
uint32 _nextQueueIndex
)
{
bytes28 extraData = batches.getExtraData();
uint40 totalElements;
uint32 nextQueueIndex;
assembly {
totalElements := and(shr(32, extraData), 0x000000000000000000000000000000000000000000000000000000ffffffffff)
nextQueueIndex := shr(40, and(shr(32, extraData), 0xffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000))
}
return (totalElements, nextQueueIndex);
}
/**
* Encodes the batch context for the extra data.
* @param _totalElements Total number of elements submitted.
* @param _nextQueueIndex Index of the next queue element.
* @return _context Encoded batch context.
*/
function _makeLatestBatchContext(
uint40 _totalElements,
uint32 _nextQueueIndex
)
internal
view
returns (
bytes28 _context
)
{
bytes28 totalElementsAndNextQueueIndex;
assembly {
totalElementsAndNextQueueIndex := shl(32, or(_totalElements, shl(40, _nextQueueIndex)))
}
return totalElementsAndNextQueueIndex;
}
/**
* Retrieves the hash of a queue element.
* @param _index Index of the queue element to retrieve a hash for.
* @return _queueLeafHash Hash of the queue element.
*/
function _getQueueLeafHash(
uint _index
)
internal
view
returns (
bytes32 _queueLeafHash
)
{
Lib_OVMCodec.QueueElement memory element = getQueueElement(_index);
require(
msg.sender == sequencer
|| element.timestamp + forceInclusionPeriodSeconds <= block.timestamp,
"Queue transactions cannot be submitted during the sequencer inclusion period."
);
return _hashTransactionChainElement(
TransactionChainElement({
isSequenced: false,
queueIndex: _index,
timestamp: 0,
blockNumber: 0,
txData: hex""
})
);
}
/**
* Inserts a batch into the chain of batches.
* @param _transactionRoot Root of the transaction tree for this batch.
* @param _batchSize Number of elements in the batch.
* @param _numQueuedTransactions Number of queue transactions in the batch.
*/
function _appendBatch(
bytes32 _transactionRoot,
uint _batchSize,
uint _numQueuedTransactions
)
internal
{
(uint40 totalElements, uint32 nextQueueIndex) = _getLatestBatchContext();
Lib_OVMCodec.ChainBatchHeader memory header = Lib_OVMCodec.ChainBatchHeader({
batchIndex: batches.getLength(),
batchRoot: _transactionRoot,
batchSize: _batchSize,
prevTotalElements: totalElements,
extraData: hex""
});
bytes32 batchHeaderHash = _hashBatchHeader(header);
bytes28 latestBatchContext = _makeLatestBatchContext(
totalElements + uint40(header.batchSize),
nextQueueIndex + uint32(_numQueuedTransactions)
);
batches.push(batchHeaderHash, latestBatchContext);
}
function _validateBatchContext(BatchContext memory context, uint32 nextQueueIndex) internal {
if (nextQueueIndex == 0) {
/**
* Checks that a given batch context is valid.
* @param _context Batch context to validate.
* @param _nextQueueIndex Index of the next queue element to process.
*/
function _validateBatchContext(
BatchContext memory _context,
uint32 _nextQueueIndex
)
internal
{
if (queue.getLength() == 0) {
return;
}
Lib_OVMCodec.QueueElement memory nextQueueElement = getQueueElement(nextQueueIndex);
Lib_OVMCodec.QueueElement memory nextQueueElement = getQueueElement(_nextQueueIndex);
require(
block.timestamp < nextQueueElement.timestamp + forceInclusionPeriodSeconds,
"Older queue batches must be processed before a new sequencer batch."
);
require(
context.timestamp <= nextQueueElement.timestamp,
"Sequencer transactions timestamp too high"
_context.timestamp <= nextQueueElement.timestamp,
"Sequencer transactions timestamp too high."
);
require(
context.blockNumber <= nextQueueElement.blockNumber,
"Sequencer transactions blockNumber too high"
_context.blockNumber <= nextQueueElement.blockNumber,
"Sequencer transactions blockNumber too high."
);
}
function getTotalElements()
override
public
view
returns (
uint256 _totalElements
)
{
(uint40 totalElements, uint32 nextQueueIndex) = getLatestBatchContext();
return uint256(totalElements);
}
/******************************************
* Internal Functions: Batch Manipulation *
******************************************/
// TODO docstring
/**
* Hashes a transaction chain element.
* @param _element Chain element to hash.
* @return _hash Hash of the chain element.
*/
function _hashTransactionChainElement(
TransactionChainElement memory _element
)
internal
pure
returns(bytes32)
returns (
bytes32 _hash
)
{
return keccak256(abi.encode(
_element.isSequenced,
_element.queueIndex,
_element.timestamp,
_element.blockNumber,
_element.txData
));
}
function _getRoot(bytes32[] memory leaves) internal returns(bytes32) {
// TODO: Require that leaves is even (if not this lib doesn't work maybe?)
return Lib_MerkleRoot.getMerkleRoot(leaves);
return keccak256(
abi.encode(
_element.isSequenced,
_element.queueIndex,
_element.timestamp,
_element.blockNumber,
_element.txData
)
);
}
}
\ No newline at end of file
}
......@@ -2,6 +2,9 @@
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
/* Library Imports */
import { Lib_OVMCodec } from "../../libraries/codec/Lib_OVMCodec.sol";
/* Interface Imports */
import { iOVM_BaseChain } from "./iOVM_BaseChain.sol";
......@@ -10,11 +13,90 @@ import { iOVM_BaseChain } from "./iOVM_BaseChain.sol";
*/
interface iOVM_CanonicalTransactionChain is iOVM_BaseChain {
/****************************************
* Public Functions: Batch Manipulation *
****************************************/
/**********
* Events *
**********/
event QueueTransactionAppended(
bytes _transaction,
bytes32 _timestampAndBlockNumber
);
event ChainBatchAppended(
uint256 _startingQueueIndex,
uint256 _numQueueElements
);
/***********
* Structs *
***********/
struct BatchContext {
uint256 numSequencedTransactions;
uint256 numSubsequentQueueTransactions;
uint256 timestamp;
uint256 blockNumber;
}
struct TransactionChainElement {
bool isSequenced;
uint256 queueIndex; // QUEUED TX ONLY
uint256 timestamp; // SEQUENCER TX ONLY
uint256 blockNumber; // SEQUENCER TX ONLY
bytes txData; // SEQUENCER TX ONLY
}
/********************
* Public Functions *
********************/
/**
* Gets the queue element at a particular index.
* @param _index Index of the queue element to access.
* @return _element Queue element at the given index.
*/
function getQueueElement(
uint256 _index
)
external
view
returns (
Lib_OVMCodec.QueueElement memory _element
);
/**
* Adds a transaction to the queue.
* @param _target Target contract to send the transaction to.
* @param _gasLimit Gas limit for the given transaction.
* @param _data Transaction data.
*/
function enqueue(
address _target,
uint256 _gasLimit,
bytes memory _data
) external;
/**
* Appends a given number of queued transactions as a single batch.
* @param _numQueuedTransactions Number of transactions to append.
*/
function appendQueueBatch(
uint256 _numQueuedTransactions
) external;
function enqueue(address _target, uint256 _gasLimit, bytes memory _data) external;
function appendQueueBatch() external;
function appendSequencerBatch(bytes[] calldata _batch, uint256 _timestamp) external;
/**
* Allows the sequencer to append a batch of transactions.
* @param _transactions Array of raw transaction data.
* @param _contexts Array of batch contexts.
* @param _shouldStartAtBatch Specific batch we expect to start appending to.
* @param _totalElementsToAppend Total number of batch elements we expect to append.
*/
function appendSequencerBatch(
bytes[] memory _transactions,
BatchContext[] memory _contexts,
uint256 _shouldStartAtBatch,
uint _totalElementsToAppend
) external;
}
......@@ -2,18 +2,25 @@ import { expect } from '../../../setup'
/* External Imports */
import { ethers } from '@nomiclabs/buidler'
import { Signer, ContractFactory, Contract } from 'ethers'
import { Signer, ContractFactory, Contract, BigNumber } from 'ethers'
import { smockit, MockContract } from '@eth-optimism/smock'
import _ from 'lodash'
/* Internal Imports */
import {
makeAddressManager,
setProxyTarget,
FORCE_INCLUSION_PERIOD_SECONDS,
setEthTime,
NON_ZERO_ADDRESS,
remove0x,
getEthTime,
getNextBlockNumber,
increaseEthTime,
// NON_NULL_BYTES32,
// ZERO_ADDRESS,
} from '../../../helpers'
import { defaultAbiCoder, keccak256 } from 'ethers/lib/utils'
interface sequencerBatchContext {
numSequencedTransactions: Number
......@@ -22,16 +29,81 @@ interface sequencerBatchContext {
blockNumber: Number
}
const ELEMENT_TEST_SIZES = [1, 2, 4, 8, 16]
const getQueueElementHash = (queueIndex: number): string => {
return getChainElementHash(false, queueIndex, 0, 0, '0x')
}
const getSequencerElementHash = (
timestamp: number,
blockNumber: number,
txData: string
): string => {
return getChainElementHash(true, 0, timestamp, blockNumber, txData)
}
const getChainElementHash = (
isSequenced: boolean,
queueIndex: number,
timestamp: number,
blockNumber: number,
txData: string
): string => {
return keccak256(
defaultAbiCoder.encode(
['bool', 'uint256', 'uint256', 'uint256', 'bytes'],
[isSequenced, queueIndex, timestamp, blockNumber, txData]
)
)
}
const getTransactionHash = (
sender: string,
target: string,
gasLimit: number,
data: string
): string => {
return keccak256(encodeQueueTransaction(sender, target, gasLimit, data))
}
const encodeQueueTransaction = (
sender: string,
target: string,
gasLimit: number,
data: string
): string => {
return defaultAbiCoder.encode(
['address', 'address', 'uint256', 'bytes'],
[sender, target, gasLimit, data]
)
}
const encodeTimestampAndBlockNumber = (
timestamp: number,
blockNumber: number
): string => {
return (
'0x' +
remove0x(BigNumber.from(blockNumber).toHexString()).padStart(54, '0') +
remove0x(BigNumber.from(timestamp).toHexString()).padStart(10, '0')
)
}
describe('OVM_CanonicalTransactionChain', () => {
let signer: Signer
let sequencer: Signer
before(async () => {
;[signer] = await ethers.getSigners()
;[signer, sequencer] = await ethers.getSigners()
})
let AddressManager: Contract
before(async () => {
AddressManager = await makeAddressManager()
await AddressManager.setAddress('OVM_Sequencer', await signer.getAddress())
await AddressManager.setAddress(
'OVM_Sequencer',
await sequencer.getAddress()
)
})
let Mock__OVM_L1ToL2TransactionQueue: MockContract
......@@ -63,66 +135,614 @@ describe('OVM_CanonicalTransactionChain', () => {
})
describe('enqueue', () => {
it('should store queued elements correctly', async () => {
await OVM_CanonicalTransactionChain.enqueue('0x' + '01'.repeat(20), 25000, '0x1234')
const firstQueuedElement = await OVM_CanonicalTransactionChain.getQueueElement(0)
// Sanity check that the blockNumber is non-zero
expect(firstQueuedElement.blockNumber).to.not.equal(0)
const target = NON_ZERO_ADDRESS
const gasLimit = 500_000
const data = '0x' + '12'.repeat(1234)
it('should revert when trying to input more data than the max data size', async () => {
const MAX_ROLLUP_TX_SIZE = await OVM_CanonicalTransactionChain.MAX_ROLLUP_TX_SIZE()
const data = '0x' + '12'.repeat(MAX_ROLLUP_TX_SIZE + 1)
await expect(
OVM_CanonicalTransactionChain.enqueue(target, gasLimit, data)
).to.be.revertedWith('Transaction exceeds maximum rollup data size.')
})
it('should append queued elements correctly', async () => {
await OVM_CanonicalTransactionChain.enqueue('0x' + '01'.repeat(20), 25000, '0x1234')
// Increase the time to ensure we can append the queued tx
await increaseEthTime(ethers.provider, 100000000)
await OVM_CanonicalTransactionChain.appendQueueBatch(1)
// Sanity check that the batch was appended
expect(await OVM_CanonicalTransactionChain.getTotalElements()).to.equal(1)
it('should revert if gas limit parameter is not at least MIN_ROLLUP_TX_GAS', async () => {
const MIN_ROLLUP_TX_GAS = await OVM_CanonicalTransactionChain.MIN_ROLLUP_TX_GAS()
const gasLimit = MIN_ROLLUP_TX_GAS / 2
await expect(
OVM_CanonicalTransactionChain.enqueue(target, gasLimit, data)
).to.be.revertedWith('Layer 2 gas limit too low to enqueue.')
})
it('should append multiple queued elements correctly', async () => {
await OVM_CanonicalTransactionChain.enqueue('0x' + '01'.repeat(20), 25000, '0x1234')
await OVM_CanonicalTransactionChain.enqueue('0x' + '01'.repeat(20), 25000, '0x1234')
// Increase the time to ensure we can append the queued tx
await increaseEthTime(ethers.provider, 100000000)
await OVM_CanonicalTransactionChain.appendQueueBatch(2)
// Sanity check that the two elements were appended
expect(await OVM_CanonicalTransactionChain.getTotalElements()).to.equal(2)
it('should revert if transaction gas limit does not cover rollup burn', async () => {
const L2_GAS_DISCOUNT_DIVISOR = await OVM_CanonicalTransactionChain.L2_GAS_DISCOUNT_DIVISOR()
await expect(
OVM_CanonicalTransactionChain.enqueue(target, gasLimit, data, {
gasLimit: gasLimit / L2_GAS_DISCOUNT_DIVISOR - 1,
})
).to.be.revertedWith('Insufficient gas for L2 rate limiting burn.')
})
describe('with valid input parameters', () => {
it('should emit a QueueTransactionAppended event', async () => {
const timestamp = (await getEthTime(ethers.provider)) + 100
const blockNumber = await getNextBlockNumber(ethers.provider)
await setEthTime(ethers.provider, timestamp)
const encodedTimestampAndBlockNumber = encodeTimestampAndBlockNumber(
timestamp,
blockNumber
)
const encodedQueueTransaction = encodeQueueTransaction(
await signer.getAddress(),
target,
gasLimit,
data
)
await expect(
OVM_CanonicalTransactionChain.enqueue(target, gasLimit, data)
)
.to.emit(OVM_CanonicalTransactionChain, 'QueueTransactionAppended')
.withArgs(encodedQueueTransaction, encodedTimestampAndBlockNumber)
})
describe('when enqueing multiple times', () => {
for (const size of ELEMENT_TEST_SIZES) {
it(`should be able to enqueue ${size} elements`, async () => {
for (let i = 0; i < size; i++) {
await expect(
OVM_CanonicalTransactionChain.enqueue(target, gasLimit, data)
).to.not.be.reverted
}
})
}
})
})
})
describe('appendSequencerBatch', () => {
it('should append a batch with just one batch', async () => {
// Try out appending
const testBatchContext: sequencerBatchContext = {
numSequencedTransactions: 1,
numSubsequentQueueTransactions: 0,
timestamp: 0,
blockNumber: 0
describe('getQueueElement', () => {
it('should revert when accessing a non-existent element', async () => {
await expect(
OVM_CanonicalTransactionChain.getQueueElement(0)
).to.be.revertedWith('Index too large')
})
describe('when the requested element exists', () => {
const target = NON_ZERO_ADDRESS
const gasLimit = 500_000
const data = '0x' + '12'.repeat(1234)
describe('when getting the first element', () => {
for (const size of ELEMENT_TEST_SIZES) {
it(`gets the element when ${size} + 1 elements exist`, async () => {
const timestamp = (await getEthTime(ethers.provider)) + 100
const blockNumber = await getNextBlockNumber(ethers.provider)
await setEthTime(ethers.provider, timestamp)
const queueRoot = getTransactionHash(
await signer.getAddress(),
target,
gasLimit,
data
)
await OVM_CanonicalTransactionChain.enqueue(target, gasLimit, data)
for (let i = 0; i < size; i++) {
await OVM_CanonicalTransactionChain.enqueue(
target,
gasLimit,
'0x' + '12'.repeat(i + 1)
)
}
expect(
_.toPlainObject(
await OVM_CanonicalTransactionChain.getQueueElement(0)
)
).to.deep.include({
queueRoot,
timestamp,
blockNumber,
})
})
}
})
describe('when getting the middle element', () => {
for (const size of ELEMENT_TEST_SIZES) {
it(`gets the element when ${size} elements exist`, async () => {
let timestamp: number
let blockNumber: number
let queueRoot: string
const middleIndex = Math.floor(size / 2)
for (let i = 0; i < size; i++) {
if (i === middleIndex) {
timestamp = (await getEthTime(ethers.provider)) + 100
blockNumber = await getNextBlockNumber(ethers.provider)
await setEthTime(ethers.provider, timestamp)
queueRoot = getTransactionHash(
await signer.getAddress(),
target,
gasLimit,
data
)
await OVM_CanonicalTransactionChain.enqueue(
target,
gasLimit,
data
)
} else {
await OVM_CanonicalTransactionChain.enqueue(
target,
gasLimit,
'0x' + '12'.repeat(i + 1)
)
}
}
expect(
_.toPlainObject(
await OVM_CanonicalTransactionChain.getQueueElement(middleIndex)
)
).to.deep.include({
queueRoot,
timestamp,
blockNumber,
})
})
}
})
describe('when getting the last element', () => {
for (const size of ELEMENT_TEST_SIZES) {
it(`gets the element when ${size} elements exist`, async () => {
let timestamp: number
let blockNumber: number
let queueRoot: string
for (let i = 0; i < size; i++) {
if (i === size - 1) {
timestamp = (await getEthTime(ethers.provider)) + 100
blockNumber = await getNextBlockNumber(ethers.provider)
await setEthTime(ethers.provider, timestamp)
queueRoot = getTransactionHash(
await signer.getAddress(),
target,
gasLimit,
data
)
await OVM_CanonicalTransactionChain.enqueue(
target,
gasLimit,
data
)
} else {
await OVM_CanonicalTransactionChain.enqueue(
target,
gasLimit,
'0x' + '12'.repeat(i + 1)
)
}
}
expect(
_.toPlainObject(
await OVM_CanonicalTransactionChain.getQueueElement(size - 1)
)
).to.deep.include({
queueRoot,
timestamp,
blockNumber,
})
})
}
})
})
})
describe('appendQueueBatch', () => {
it('should revert if trying to append zero transactions', async () => {
await expect(
OVM_CanonicalTransactionChain.appendQueueBatch(0)
).to.be.revertedWith('Must append more than zero transactions.')
})
it('should revert if the queue is empty', async () => {
await expect(
OVM_CanonicalTransactionChain.appendQueueBatch(1)
).to.be.revertedWith('Index too large.')
})
describe('when the queue is not empty', () => {
const target = NON_ZERO_ADDRESS
const gasLimit = 500_000
const data = '0x' + '12'.repeat(1234)
for (const size of ELEMENT_TEST_SIZES) {
describe(`when the queue has ${size} elements`, () => {
beforeEach(async () => {
for (let i = 0; i < size; i++) {
await OVM_CanonicalTransactionChain.enqueue(
target,
gasLimit,
data
)
}
})
describe('when the sequencer inclusion period has not passed', () => {
it('should revert if not called by the sequencer', async () => {
await expect(
OVM_CanonicalTransactionChain.connect(signer).appendQueueBatch(
1
)
).to.be.revertedWith(
'Queue transactions cannot be submitted during the sequencer inclusion period.'
)
})
it('should succeed if called by the sequencer', async () => {
await expect(
OVM_CanonicalTransactionChain.connect(
sequencer
).appendQueueBatch(1)
)
.to.emit(OVM_CanonicalTransactionChain, 'ChainBatchAppended')
.withArgs(0, 1)
})
})
describe('when the sequencer inclusion period has passed', () => {
beforeEach(async () => {
await increaseEthTime(
ethers.provider,
FORCE_INCLUSION_PERIOD_SECONDS * 2
)
})
it('should be able to append a single element', async () => {
await expect(OVM_CanonicalTransactionChain.appendQueueBatch(1))
.to.emit(OVM_CanonicalTransactionChain, 'ChainBatchAppended')
.withArgs(0, 1)
})
it(`should be able to append ${size} elements`, async () => {
await expect(OVM_CanonicalTransactionChain.appendQueueBatch(size))
.to.emit(OVM_CanonicalTransactionChain, 'ChainBatchAppended')
.withArgs(0, size)
})
it(`should revert if appending ${size} + 1 elements`, async () => {
await expect(
OVM_CanonicalTransactionChain.appendQueueBatch(size + 1)
).to.be.revertedWith('Index too large.')
})
})
})
}
})
})
await OVM_CanonicalTransactionChain.appendSequencerBatch(
['0x1212'],
[testBatchContext],
0,
describe('appendSequencerBatch', () => {
beforeEach(() => {
OVM_CanonicalTransactionChain = OVM_CanonicalTransactionChain.connect(
sequencer
)
})
it('should revert if expected start does not match current total batches', async () => {
await expect(
OVM_CanonicalTransactionChain.appendSequencerBatch(
['0x1234'],
[
{
numSequencedTransactions: 0,
numSubsequentQueueTransactions: 0,
timestamp: 0,
blockNumber: 0,
},
],
1234,
1
)
).to.be.revertedWith(
'Actual batch start index does not match expected start index.'
)
expect(await OVM_CanonicalTransactionChain.getTotalElements()).to.equal(1)
})
it('should append a batch with 1 sequencer tx and a queue tx', async () => {
const testBatchContext: sequencerBatchContext = {
numSequencedTransactions: 1,
numSubsequentQueueTransactions: 1,
timestamp: 3,
blockNumber: 3
}
await OVM_CanonicalTransactionChain.enqueue('0x' + '01'.repeat(20), 25000, '0x1234')
await OVM_CanonicalTransactionChain.appendSequencerBatch(
['0x1212'],
[testBatchContext],
it('should revert if not called by the sequencer', async () => {
await expect(
OVM_CanonicalTransactionChain.connect(signer).appendSequencerBatch(
['0x1234'],
[
{
numSequencedTransactions: 0,
numSubsequentQueueTransactions: 0,
timestamp: 0,
blockNumber: 0,
},
],
0,
2
)
expect(await OVM_CanonicalTransactionChain.getTotalElements()).to.equal(2)
1
)
).to.be.revertedWith('Function can only be called by the Sequencer.')
})
it('should revert if no contexts are provided', async () => {
await expect(
OVM_CanonicalTransactionChain.appendSequencerBatch(['0x1234'], [], 0, 1)
).to.be.revertedWith('Must provide at least one batch context.')
})
it('should revert if total elements to append is zero', async () => {
await expect(
OVM_CanonicalTransactionChain.appendSequencerBatch(
['0x1234'],
[
{
numSequencedTransactions: 0,
numSubsequentQueueTransactions: 0,
timestamp: 0,
blockNumber: 0,
},
],
0,
0
)
).to.be.revertedWith('Must append at least one element.')
})
for (const size of ELEMENT_TEST_SIZES) {
describe(`when appending ${size} sequencer transactions`, () => {
const target = NON_ZERO_ADDRESS
const gasLimit = 500_000
const data = '0x' + '12'.repeat(1234)
beforeEach(async () => {
await OVM_CanonicalTransactionChain.enqueue(target, gasLimit, data)
})
it('should revert if a queue element needs to be processed', async () => {
await increaseEthTime(
ethers.provider,
FORCE_INCLUSION_PERIOD_SECONDS * 2
)
await expect(
OVM_CanonicalTransactionChain.appendSequencerBatch(
['0x1234'],
[
{
numSequencedTransactions: 0,
numSubsequentQueueTransactions: 0,
timestamp: 0,
blockNumber: 0,
},
],
0,
1
)
).to.be.revertedWith(
'Older queue batches must be processed before a new sequencer batch.'
)
})
it('should revert if the context timestamp is <= the head queue element timestamp', async () => {
const timestamp = (await getEthTime(ethers.provider)) + 1000
await expect(
OVM_CanonicalTransactionChain.appendSequencerBatch(
['0x1234'],
[
{
numSequencedTransactions: 0,
numSubsequentQueueTransactions: 0,
timestamp: timestamp,
blockNumber: 0,
},
],
0,
1
)
).to.be.revertedWith('Sequencer transactions timestamp too high.')
})
it('should revert if the context block number is <= the head queue element block number', async () => {
const timestamp = (await getEthTime(ethers.provider)) - 100
const blockNumber = (await getNextBlockNumber(ethers.provider)) + 100
await expect(
OVM_CanonicalTransactionChain.appendSequencerBatch(
['0x1234'],
[
{
numSequencedTransactions: 0,
numSubsequentQueueTransactions: 0,
timestamp: timestamp,
blockNumber: blockNumber,
},
],
0,
1
)
).to.be.revertedWith('Sequencer transactions blockNumber too high.')
})
describe('when not inserting queue elements in between', () => {
describe('when using a single batch context', () => {
let contexts: any[]
let transactions: any[]
beforeEach(async () => {
const timestamp = (await getEthTime(ethers.provider)) - 100
const blockNumber =
(await getNextBlockNumber(ethers.provider)) - 10
contexts = [
{
numSequencedTransactions: size,
numSubsequentQueueTransactions: 0,
timestamp: timestamp,
blockNumber: blockNumber,
},
]
transactions = [...Array(size)].map((el, idx) => {
return '0x' + '12' + '34'.repeat(idx)
})
})
it('should append the given number of transactions', async () => {
await expect(
OVM_CanonicalTransactionChain.appendSequencerBatch(
transactions,
contexts,
0,
size
)
)
.to.emit(OVM_CanonicalTransactionChain, 'ChainBatchAppended')
.withArgs(0, 0)
})
})
})
describe('when inserting queue elements in between', () => {
beforeEach(async () => {
for (let i = 0; i < size; i++) {
await OVM_CanonicalTransactionChain.enqueue(
target,
gasLimit,
data
)
}
})
describe('between every other sequencer transaction', () => {
let contexts: any[]
let transactions: any[]
beforeEach(async () => {
const timestamp = (await getEthTime(ethers.provider)) - 100
const blockNumber =
(await getNextBlockNumber(ethers.provider)) - 50
contexts = [...Array(size)].map(() => {
return {
numSequencedTransactions: 1,
numSubsequentQueueTransactions: 1,
timestamp: timestamp,
blockNumber: Math.max(blockNumber, 0),
}
})
transactions = [...Array(size)].map((el, idx) => {
return '0x' + '12' + '34'.repeat(idx)
})
})
it('should append the batch', async () => {
await expect(
OVM_CanonicalTransactionChain.appendSequencerBatch(
transactions,
contexts,
0,
size * 2
)
)
.to.emit(OVM_CanonicalTransactionChain, 'ChainBatchAppended')
.withArgs(0, size)
})
})
describe(`between every ${Math.max(
Math.floor(size / 8),
1
)} sequencer transaction`, () => {
const spacing = Math.max(Math.floor(size / 8), 1)
let contexts: any[]
let transactions: any[]
beforeEach(async () => {
const timestamp = (await getEthTime(ethers.provider)) - 100
const blockNumber =
(await getNextBlockNumber(ethers.provider)) - 50
contexts = [...Array(spacing)].map(() => {
return {
numSequencedTransactions: size / spacing,
numSubsequentQueueTransactions: 1,
timestamp: timestamp,
blockNumber: Math.max(blockNumber, 0),
}
})
transactions = [...Array(size)].map((el, idx) => {
return '0x' + '12' + '34'.repeat(idx)
})
})
it('should append the batch', async () => {
await expect(
OVM_CanonicalTransactionChain.appendSequencerBatch(
transactions,
contexts,
0,
size + spacing
)
)
.to.emit(OVM_CanonicalTransactionChain, 'ChainBatchAppended')
.withArgs(0, spacing)
})
})
})
})
}
})
describe('getTotalElements', () => {
it('should return zero when no elements exist', async () => {
expect(await OVM_CanonicalTransactionChain.getTotalElements()).to.equal(0)
})
for (const size of ELEMENT_TEST_SIZES) {
describe(`when the sequencer inserts a batch of ${size} elements`, () => {
beforeEach(async () => {
const timestamp = (await getEthTime(ethers.provider)) - 100
const blockNumber = (await getNextBlockNumber(ethers.provider)) - 10
const contexts = [
{
numSequencedTransactions: size,
numSubsequentQueueTransactions: 0,
timestamp: timestamp,
blockNumber: Math.max(blockNumber, 0),
},
]
const transactions = [...Array(size)].map((el, idx) => {
return '0x' + '12' + '34'.repeat(idx)
})
await OVM_CanonicalTransactionChain.connect(
sequencer
).appendSequencerBatch(transactions, contexts, 0, size)
})
it(`should return ${size}`, async () => {
expect(
await OVM_CanonicalTransactionChain.getTotalElements()
).to.equal(size)
})
})
}
})
})
......@@ -9,14 +9,14 @@ import { Contract, Signer } from 'ethers'
import {
NON_NULL_BYTES32,
makeHexString,
increaseEthTime
increaseEthTime,
} from '../../../helpers'
const numToBytes32 = (num: Number): string => {
if (num < 0 || num > 255) {
throw new Error('Unsupported number.')
}
const strNum = (num < 16) ? '0' + num.toString(16) : num.toString(16)
const strNum = num < 16 ? '0' + num.toString(16) : num.toString(16)
return '0x' + '00'.repeat(31) + strNum
}
......@@ -29,8 +29,14 @@ 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)
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 () => {
......@@ -69,11 +75,15 @@ describe('Lib_TimeboundRingBuffer', () => {
})
it('should revert if index is too old', async () => {
await expect(Lib_TimeboundRingBuffer.get(0)).to.be.revertedWith("Index too old & has been overridden.")
await expect(Lib_TimeboundRingBuffer.get(0)).to.be.revertedWith(
'Index too old & has been overridden.'
)
})
it('should revert if index is greater than length', async () => {
await expect(Lib_TimeboundRingBuffer.get(5)).to.be.revertedWith("Index too large.")
await expect(Lib_TimeboundRingBuffer.get(5)).to.be.revertedWith(
'Index too large.'
)
})
})
......@@ -89,7 +99,8 @@ describe('Lib_TimeboundRingBuffer', () => {
}
})
const pushJunk = () => Lib_TimeboundRingBuffer.push(NON_NULL_BYTES32, 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 pushNum(2)
......@@ -151,7 +162,9 @@ describe('Lib_TimeboundRingBuffer', () => {
it('should return the expected extra data', async () => {
await Lib_TimeboundRingBuffer.push(NON_NULL_BYTES32, NON_NULL_BYTES28)
expect(await Lib_TimeboundRingBuffer.getExtraData()).to.equal(NON_NULL_BYTES28)
expect(await Lib_TimeboundRingBuffer.getExtraData()).to.equal(
NON_NULL_BYTES28
)
})
})
......@@ -168,17 +181,25 @@ describe('Lib_TimeboundRingBuffer', () => {
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.")
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.")
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.")
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))
})
......@@ -192,4 +213,4 @@ describe('Lib_TimeboundRingBuffer', () => {
expect(await Lib_TimeboundRingBuffer.get(1)).to.equal(numToBytes32(1))
})
})
})
\ No newline at end of file
})
......@@ -24,3 +24,7 @@ export const getBlockTime = async (
await provider.send('evm_mine', [])
return (await provider.getBlock(block)).timestamp
}
export const getNextBlockNumber = async (provider: any): Promise<number> => {
return (await provider.getBlock('latest')).number + 1
}
......@@ -31,7 +31,8 @@
},
"linterOptions": {
"exclude": [
"**/node_modules/**/*"
"**/node_modules/**/*",
"bin/**/*"
]
}
}
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