Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
N
nebula
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
exchain
nebula
Commits
dcfd3df9
Commit
dcfd3df9
authored
Oct 13, 2020
by
Kelvin Fichter
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Cleaned up CTC and added some tests
parent
fba4de0d
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
1125 additions
and
291 deletions
+1125
-291
OVM_BaseChain.sol
...contracts/optimistic-ethereum/OVM/chain/OVM_BaseChain.sol
+1
-1
OVM_CanonicalTransactionChain.sol
...stic-ethereum/OVM/chain/OVM_CanonicalTransactionChain.sol
+326
-220
iOVM_CanonicalTransactionChain.sol
...ic-ethereum/iOVM/chain/iOVM_CanonicalTransactionChain.sol
+88
-6
OVM_CanonicalTransactionChain.spec.ts
...contracts/OVM/chain/OVM_CanonicalTransactionChain.spec.ts
+670
-50
Lib_TimeboundRingBuffer.spec.ts
...contracts/libraries/utils/Lib_TimeboundRingBuffer.spec.ts
+34
-13
eth-time.ts
packages/contracts/test/helpers/utils/eth-time.ts
+4
-0
tslint.json
packages/contracts/tslint.json
+2
-1
No files found.
packages/contracts/contracts/optimistic-ethereum/OVM/chain/OVM_BaseChain.sol
View file @
dcfd3df9
...
...
@@ -45,8 +45,8 @@ contract OVM_BaseChain is iOVM_BaseChain {
* @return _totalElements Total submitted elements.
*/
function getTotalElements()
override
virtual
override
public
view
returns (
...
...
packages/contracts/contracts/optimistic-ethereum/OVM/chain/OVM_CanonicalTransactionChain.sol
View file @
dcfd3df9
...
...
@@ -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)
{
sequencer
Address
= 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 == sequencer
Address
,
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
}
packages/contracts/contracts/optimistic-ethereum/iOVM/chain/iOVM_CanonicalTransactionChain.sol
View file @
dcfd3df9
...
...
@@ -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;
}
packages/contracts/test/contracts/OVM/chain/OVM_CanonicalTransactionChain.spec.ts
View file @
dcfd3df9
...
...
@@ -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
)
})
})
}
})
})
packages/contracts/test/contracts/libraries/utils/Lib_TimeboundRingBuffer.spec.ts
View file @
dcfd3df9
...
...
@@ -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
})
packages/contracts/test/helpers/utils/eth-time.ts
View file @
dcfd3df9
...
...
@@ -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
}
packages/contracts/tslint.json
View file @
dcfd3df9
...
...
@@ -31,7 +31,8 @@
},
"linterOptions"
:
{
"exclude"
:
[
"**/node_modules/**/*"
"**/node_modules/**/*"
,
"bin/**/*"
]
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment