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
Show 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 {
...
@@ -45,8 +45,8 @@ contract OVM_BaseChain is iOVM_BaseChain {
* @return _totalElements Total submitted elements.
* @return _totalElements Total submitted elements.
*/
*/
function getTotalElements()
function getTotalElements()
override
virtual
virtual
override
public
public
view
view
returns (
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
...
@@ -8,65 +8,43 @@ import { Lib_AddressResolver } from "../../libraries/resolver/Lib_AddressResolve
import { Lib_MerkleUtils } from "../../libraries/utils/Lib_MerkleUtils.sol";
import { Lib_MerkleUtils } from "../../libraries/utils/Lib_MerkleUtils.sol";
import { Lib_MerkleRoot } from "../../libraries/utils/Lib_MerkleRoot.sol";
import { Lib_MerkleRoot } from "../../libraries/utils/Lib_MerkleRoot.sol";
import { TimeboundRingBuffer, Lib_TimeboundRingBuffer } from "../../libraries/utils/Lib_TimeboundRingBuffer.sol";
import { TimeboundRingBuffer, Lib_TimeboundRingBuffer } from "../../libraries/utils/Lib_TimeboundRingBuffer.sol";
import { console } from "@nomiclabs/buidler/console.sol";
/* Interface Imports */
/* Interface Imports */
import { iOVM_BaseChain } from "../../iOVM/chain/iOVM_BaseChain.sol";
import { iOVM_CanonicalTransactionChain } from "../../iOVM/chain/iOVM_CanonicalTransactionChain.sol";
import { iOVM_CanonicalTransactionChain } from "../../iOVM/chain/iOVM_CanonicalTransactionChain.sol";
/* Contract Imports */
/* Contract Imports */
import { OVM_BaseChain } from "./OVM_BaseChain.sol";
import { OVM_BaseChain } from "./OVM_BaseChain.sol";
/* Logging Imports */
import { console } from "@nomiclabs/buidler/console.sol";
/**
/**
* @title OVM_CanonicalTransactionChain
* @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 *
* Constants *
*********/
*************/
event queueTransactionAppended(bytes _queueTransaction, bytes32 timestampAndBlockNumber);
event chainBatchAppended(uint _startingQueueIndex, uint _numQueueElements);
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;
using Lib_TimeboundRingBuffer for TimeboundRingBuffer;
TimeboundRingBuffer internal queue;
TimeboundRingBuffer internal queue;
TimeboundRingBuffer internal chain;
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 *
* Constructor *
...
@@ -82,281 +60,409 @@ contract OVM_CanonicalTransactionChain is OVM_BaseChain, Lib_AddressResolver { /
...
@@ -82,281 +60,409 @@ contract OVM_CanonicalTransactionChain is OVM_BaseChain, Lib_AddressResolver { /
)
)
Lib_AddressResolver(_libAddressManager)
Lib_AddressResolver(_libAddressManager)
{
{
sequencer
Address
= resolve("OVM_Sequencer");
sequencer = resolve("OVM_Sequencer");
forceInclusionPeriodSeconds = _forceInclusionPeriodSeconds;
forceInclusionPeriodSeconds = _forceInclusionPeriodSeconds;
queue.init(100, 50, 10000000000); // TODO: Update once we have arbitrary condition
queue.init(100, 50, 10000000000); // TODO: Update once we have arbitrary condition
batches.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.
* @inheritdoc iOVM_CanonicalTransactionChain
* @param _target Target contract to send the transaction to.
* @param _gasLimit Gas limit for the given transaction.
* @param _data Transaction data.
*/
*/
function enqueue(
function enqueue(
address _target,
address _target,
uint256 _gasLimit,
uint256 _gasLimit,
bytes memory _data
bytes memory _data
)
)
override
public
public
{
{
require(
require(
_data.length <= MAX_ROLLUP_TX_SIZE,
_data.length <= MAX_ROLLUP_TX_SIZE,
"Transaction exceeds maximum rollup data size."
"Transaction exceeds maximum rollup data size."
);
);
require(_gasLimit >= 20000, "Layer 2 gas limit too low to enqueue.");
require(
// Consume l1 gas rate limit queued transactions
_gasLimit >= MIN_ROLLUP_TX_GAS,
uint gasToConsume = _gasLimit/L2_GAS_DISCOUNT_DIVISOR;
"Layer 2 gas limit too low to enqueue."
uint startingGas = gasleft();
);
uint i;
while(startingGas - gasleft() > gasToConsume) {
uint256 gasToConsume = _gasLimit/L2_GAS_DISCOUNT_DIVISOR;
i++; // TODO: Replace this dumb work with minting gas token. (not today)
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,
msg.sender,
_target,
_target,
_gasLimit,
_gasLimit,
_data
_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);
bytes32 transactionHash = keccak256(transaction);
bytes32 timestampAndBlockNumber;
assembly {
timestampAndBlockNumber := or(timestamp(), shl(40, number()))
}
}
function getQueueElement(uint queueIndex) public view returns(Lib_OVMCodec.QueueElement memory) {
queue.push2(transactionHash, timestampAndBlockNumber, bytes28(0));
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
});
}
function getLatestBatchContext() public view returns(uint40 totalElements, uint32 nextQueueIndex) {
emit QueueTransactionAppended(transaction, timestampAndBlockNumber);
bytes28 extraData = batches.getExtraData();
totalElements = uint40(uint256(uint224(extraData & 0x0000000000000000000000000000000000000000000000ffffffffff)));
nextQueueIndex = uint32(bytes4(extraData & 0xffffffffffffffffffffffffffffffffffffffffffffff0000000000));
return (totalElements, nextQueueIndex);
}
}
function makeLatestBatchContext(uint40 totalElements, uint32 nextQueueIndex) public view returns(bytes28) {
/**
bytes28 totalElementsAndNextQueueIndex = bytes28(bytes4(uint32(nextQueueIndex))) | bytes28(uint224(uint40(totalElements)));
* @inheritdoc iOVM_CanonicalTransactionChain
return totalElementsAndNextQueueIndex;
*/
function appendQueueBatch(
uint _numQueuedTransactions
)
override
public
{
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++;
}
}
/****************************************
_appendBatch(
* Public Functions: Batch Manipulation *
Lib_MerkleRoot.getMerkleRoot(leaves),
****************************************/
_numQueuedTransactions,
_numQueuedTransactions
);
emit ChainBatchAppended(
nextQueueIndex - _numQueuedTransactions,
_numQueuedTransactions
);
}
/**
/**
*
Appends a sequencer batch.
*
@inheritdoc iOVM_CanonicalTransactionChain
*/
*/
function appendQueueBatch(uint numQueuedTransactions)
function appendSequencerBatch(
bytes[] memory _transactions,
BatchContext[] memory _contexts,
uint256 _shouldStartAtBatch,
uint _totalElementsToAppend
)
override
public
public
{
{
// Get all of the leaves
require(
(uint40 totalElements, uint32 nextQueueIndex) = getLatestBatchContext();
_shouldStartAtBatch == getTotalBatches(),
bytes32[] memory leaves = new bytes32[](numQueuedTransactions);
"Actual batch start index does not match expected start index."
for (uint i = 0; i < numQueuedTransactions; i++) {
);
leaves[i] = _getQueueLeafHash(nextQueueIndex);
require(
msg.sender == sequencer,
"Function can only be called by the Sequencer."
);
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 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++;
}
for (uint32 i = 0; i < context.numSubsequentQueueTransactions; i++) {
leaves[transactionIndex] = _getQueueLeafHash(nextQueueIndex);
nextQueueIndex++;
nextQueueIndex++;
transactionIndex++;
}
}
}
bytes32 root = _getRoot(leaves);
require(
transactionIndex == _totalElementsToAppend,
"Actual transaction index does not match expected total elements to append."
);
uint256 numQueuedTransactions = _totalElementsToAppend - numSequencerTransactionsProcessed;
_appendBatch(
_appendBatch(
root
,
Lib_MerkleRoot.getMerkleRoot(leaves)
,
numQueuedTransactions
,
_totalElementsToAppend
,
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(
function _getQueueLeafHash(
uint
queueI
ndex
uint
_i
ndex
)
)
internal
internal
view
view
returns(bytes32)
returns (
bytes32 _queueLeafHash
)
{
{
// TODO: Improve this require statement (the `queueIndex*2` is ugly)
Lib_OVMCodec.QueueElement memory element = getQueueElement(_index);
require(queueIndex*2 != queue.getLength(), "Queue index too large.");
TransactionChainElement memory element = TransactionChainElement({
require(
msg.sender == sequencer
|| element.timestamp + forceInclusionPeriodSeconds <= block.timestamp,
"Queue transactions cannot be submitted during the sequencer inclusion period."
);
return _hashTransactionChainElement(
TransactionChainElement({
isSequenced: false,
isSequenced: false,
queueIndex: queueI
ndex,
queueIndex: _i
ndex,
timestamp: 0,
timestamp: 0,
blockNumber: 0,
blockNumber: 0,
txData: hex""
txData: hex""
});
})
require(
msg.sender == sequencerAddress || element.timestamp + forceInclusionPeriodSeconds <= block.timestamp,
"Message sender does not have permission to append this batch"
);
);
return _hashTransactionChainElement(element);
}
}
/**
* 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(
function _appendBatch(
bytes32 transactionRoot,
bytes32
_
transactionRoot,
uint batchSize,
uint
_
batchSize,
uint numQueuedTransactions
uint
_
numQueuedTransactions
)
)
internal
internal
{
{
(uint40 totalElements, uint32 nextQueueIndex) = getLatestBatchContext();
(uint40 totalElements, uint32 nextQueueIndex) =
_
getLatestBatchContext();
Lib_OVMCodec.ChainBatchHeader memory header = Lib_OVMCodec.ChainBatchHeader({
Lib_OVMCodec.ChainBatchHeader memory header = Lib_OVMCodec.ChainBatchHeader({
batchIndex: batches.getLength(),
batchIndex: batches.getLength(),
batchRoot: transactionRoot,
batchRoot:
_
transactionRoot,
batchSize: batchSize,
batchSize:
_
batchSize,
prevTotalElements: totalElements,
prevTotalElements: totalElements,
extraData: hex""
extraData: hex""
});
});
bytes32 batchHeaderHash = _hashBatchHeader(header);
bytes28 latestBatchContext = makeLatestBatchContext(
bytes32 batchHeaderHash = _hashBatchHeader(header);
bytes28 latestBatchContext = _makeLatestBatchContext(
totalElements + uint40(header.batchSize),
totalElements + uint40(header.batchSize),
nextQueueIndex + uint32(numQueuedTransactions)
nextQueueIndex + uint32(
_
numQueuedTransactions)
);
);
batches.push(batchHeaderHash, latestBatchContext);
batches.push(batchHeaderHash, latestBatchContext);
}
}
/**
/**
* Appends a sequencer batch.
* 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 appendSequencerBatch(
function _validateBatchContext(
bytes[] memory _rawTransactions, // 2 byte prefix for how many elements, per element 3 byte prefix.
BatchContext memory _context,
BatchContext[] memory _batchContexts, // 2 byte prefix for how many elements, fixed size elements
uint32 _nextQueueIndex
uint256 _shouldStartAtBatch, // 6 bytes
uint _totalElementsToAppend // 2 btyes
)
)
// override
internal
public // TODO: can we make external? Hopefully so
{
{
require(
if (queue.getLength() == 0) {
_shouldStartAtBatch == getTotalBatches(),
return;
"Batch submission failed: chain length has become larger than expected"
);
require(
msg.sender == sequencerAddress,
"Function can only be called by the Sequencer."
);
// Initialize an array which will contain the leaves of the merkle tree commitment
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]
));
numSequencerTransactionsProcessed++;
transactionIndex++;
}
//////////////////// Process Queue Transactions \\\\\\\\\\\\\\\\\\\\
uint numQueuedTransactions = curContext.numSubsequentQueueTransactions;
for (uint i = 0; i < numQueuedTransactions; i++) {
leaves[transactionIndex] = _getQueueLeafHash(nextQueueIndex);
nextQueueIndex++;
transactionIndex++;
}
}
}
// Make sure the correct number of leaves were calculated
Lib_OVMCodec.QueueElement memory nextQueueElement = getQueueElement(_nextQueueIndex);
require(transactionIndex == _totalElementsToAppend, "Not enough transactions supplied!");
bytes32 root = _getRoot(leaves);
uint numQueuedTransactions = _totalElementsToAppend - numSequencerTransactionsProcessed;
_appendBatch(
root,
_totalElementsToAppend,
numQueuedTransactions
);
emit chainBatchAppended(nextQueueIndex-numQueuedTransactions, numQueuedTransactions);
}
function _validateBatchContext(BatchContext memory context, uint32 nextQueueIndex) internal {
if (nextQueueIndex == 0) {
return;
}
Lib_OVMCodec.QueueElement memory nextQueueElement = getQueueElement(nextQueueIndex);
require(
require(
block.timestamp < nextQueueElement.timestamp + forceInclusionPeriodSeconds,
block.timestamp < nextQueueElement.timestamp + forceInclusionPeriodSeconds,
"Older queue batches must be processed before a new sequencer batch."
"Older queue batches must be processed before a new sequencer batch."
);
);
require(
require(
context.timestamp <= nextQueueElement.timestamp,
_
context.timestamp <= nextQueueElement.timestamp,
"Sequencer transactions timestamp too high"
"Sequencer transactions timestamp too high
.
"
);
);
require(
require(
context.blockNumber <= nextQueueElement.blockNumber,
_
context.blockNumber <= nextQueueElement.blockNumber,
"Sequencer transactions blockNumber too high"
"Sequencer transactions blockNumber too high
.
"
);
);
}
}
function getTotalElements()
/**
override
* Hashes a transaction chain element.
public
* @param _element Chain element to hash.
view
* @return _hash Hash of the chain element.
returns (
*/
uint256 _totalElements
)
{
(uint40 totalElements, uint32 nextQueueIndex) = getLatestBatchContext();
return uint256(totalElements);
}
/******************************************
* Internal Functions: Batch Manipulation *
******************************************/
// TODO docstring
function _hashTransactionChainElement(
function _hashTransactionChainElement(
TransactionChainElement memory _element
TransactionChainElement memory _element
)
)
internal
internal
pure
pure
returns(bytes32)
returns (
bytes32 _hash
)
{
{
return keccak256(abi.encode(
return keccak256(
abi.encode(
_element.isSequenced,
_element.isSequenced,
_element.queueIndex,
_element.queueIndex,
_element.timestamp,
_element.timestamp,
_element.blockNumber,
_element.blockNumber,
_element.txData
_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);
}
}
}
}
packages/contracts/contracts/optimistic-ethereum/iOVM/chain/iOVM_CanonicalTransactionChain.sol
View file @
dcfd3df9
...
@@ -2,6 +2,9 @@
...
@@ -2,6 +2,9 @@
pragma solidity ^0.7.0;
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
pragma experimental ABIEncoderV2;
/* Library Imports */
import { Lib_OVMCodec } from "../../libraries/codec/Lib_OVMCodec.sol";
/* Interface Imports */
/* Interface Imports */
import { iOVM_BaseChain } from "./iOVM_BaseChain.sol";
import { iOVM_BaseChain } from "./iOVM_BaseChain.sol";
...
@@ -10,11 +13,90 @@ import { iOVM_BaseChain } from "./iOVM_BaseChain.sol";
...
@@ -10,11 +13,90 @@ import { iOVM_BaseChain } from "./iOVM_BaseChain.sol";
*/
*/
interface iOVM_CanonicalTransactionChain is iOVM_BaseChain {
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
}
function enqueue(address _target, uint256 _gasLimit, bytes memory _data) external;
/********************
function appendQueueBatch() external;
* Public Functions *
function appendSequencerBatch(bytes[] calldata _batch, uint256 _timestamp) external;
********************/
/**
* 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;
/**
* 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'
...
@@ -2,18 +2,25 @@ import { expect } from '../../../setup'
/* External Imports */
/* External Imports */
import
{
ethers
}
from
'
@nomiclabs/buidler
'
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
{
smockit
,
MockContract
}
from
'
@eth-optimism/smock
'
import
_
from
'
lodash
'
/* Internal Imports */
/* Internal Imports */
import
{
import
{
makeAddressManager
,
makeAddressManager
,
setProxyTarget
,
setProxyTarget
,
FORCE_INCLUSION_PERIOD_SECONDS
,
FORCE_INCLUSION_PERIOD_SECONDS
,
setEthTime
,
NON_ZERO_ADDRESS
,
remove0x
,
getEthTime
,
getNextBlockNumber
,
increaseEthTime
,
increaseEthTime
,
// NON_NULL_BYTES32,
// NON_NULL_BYTES32,
// ZERO_ADDRESS,
// ZERO_ADDRESS,
}
from
'
../../../helpers
'
}
from
'
../../../helpers
'
import
{
defaultAbiCoder
,
keccak256
}
from
'
ethers/lib/utils
'
interface
sequencerBatchContext
{
interface
sequencerBatchContext
{
numSequencedTransactions
:
Number
numSequencedTransactions
:
Number
...
@@ -22,16 +29,81 @@ interface sequencerBatchContext {
...
@@ -22,16 +29,81 @@ interface sequencerBatchContext {
blockNumber
:
Number
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
'
,
()
=>
{
describe
(
'
OVM_CanonicalTransactionChain
'
,
()
=>
{
let
signer
:
Signer
let
signer
:
Signer
let
sequencer
:
Signer
before
(
async
()
=>
{
before
(
async
()
=>
{
;[
signer
]
=
await
ethers
.
getSigners
()
;[
signer
,
sequencer
]
=
await
ethers
.
getSigners
()
})
})
let
AddressManager
:
Contract
let
AddressManager
:
Contract
before
(
async
()
=>
{
before
(
async
()
=>
{
AddressManager
=
await
makeAddressManager
()
AddressManager
=
await
makeAddressManager
()
await
AddressManager
.
setAddress
(
'
OVM_Sequencer
'
,
await
signer
.
getAddress
())
await
AddressManager
.
setAddress
(
'
OVM_Sequencer
'
,
await
sequencer
.
getAddress
()
)
})
})
let
Mock__OVM_L1ToL2TransactionQueue
:
MockContract
let
Mock__OVM_L1ToL2TransactionQueue
:
MockContract
...
@@ -63,66 +135,614 @@ describe('OVM_CanonicalTransactionChain', () => {
...
@@ -63,66 +135,614 @@ describe('OVM_CanonicalTransactionChain', () => {
})
})
describe
(
'
enqueue
'
,
()
=>
{
describe
(
'
enqueue
'
,
()
=>
{
it
(
'
should store queued elements correctly
'
,
async
()
=>
{
const
target
=
NON_ZERO_ADDRESS
await
OVM_CanonicalTransactionChain
.
enqueue
(
'
0x
'
+
'
01
'
.
repeat
(
20
),
25000
,
'
0x1234
'
)
const
gasLimit
=
500
_000
const
firstQueuedElement
=
await
OVM_CanonicalTransactionChain
.
getQueueElement
(
0
)
const
data
=
'
0x
'
+
'
12
'
.
repeat
(
1234
)
// Sanity check that the blockNumber is non-zero
expect
(
firstQueuedElement
.
blockNumber
).
to
.
not
.
equal
(
0
)
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 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 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
(
'
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 append queued elements correctly
'
,
async
()
=>
{
it
(
`should be able to append
${
size
}
elements`
,
async
()
=>
{
await
OVM_CanonicalTransactionChain
.
enqueue
(
'
0x
'
+
'
01
'
.
repeat
(
20
),
25000
,
'
0x1234
'
)
await
expect
(
OVM_CanonicalTransactionChain
.
appendQueueBatch
(
size
))
// Increase the time to ensure we can append the queued tx
.
to
.
emit
(
OVM_CanonicalTransactionChain
,
'
ChainBatchAppended
'
)
await
increaseEthTime
(
ethers
.
provider
,
100000000
)
.
withArgs
(
0
,
size
)
await
OVM_CanonicalTransactionChain
.
appendQueueBatch
(
1
)
// Sanity check that the batch was appended
expect
(
await
OVM_CanonicalTransactionChain
.
getTotalElements
()).
to
.
equal
(
1
)
})
})
it
(
'
should append multiple queued elements correctly
'
,
async
()
=>
{
it
(
`should revert if appending
${
size
}
+ 1 elements`
,
async
()
=>
{
await
OVM_CanonicalTransactionChain
.
enqueue
(
'
0x
'
+
'
01
'
.
repeat
(
20
),
25000
,
'
0x1234
'
)
await
expect
(
await
OVM_CanonicalTransactionChain
.
enqueue
(
'
0x
'
+
'
01
'
.
repeat
(
20
),
25000
,
'
0x1234
'
)
OVM_CanonicalTransactionChain
.
appendQueueBatch
(
size
+
1
)
// Increase the time to ensure we can append the queued tx
).
to
.
be
.
revertedWith
(
'
Index too large.
'
)
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
)
}
})
})
})
})
describe
(
'
appendSequencerBatch
'
,
()
=>
{
describe
(
'
appendSequencerBatch
'
,
()
=>
{
it
(
'
should append a batch with just one batch
'
,
async
()
=>
{
beforeEach
(()
=>
{
// Try out appending
OVM_CanonicalTransactionChain
=
OVM_CanonicalTransactionChain
.
connect
(
const
testBatchContext
:
sequencerBatchContext
=
{
sequencer
numSequencedTransactions
:
1
,
)
})
it
(
'
should revert if expected start does not match current total batches
'
,
async
()
=>
{
await
expect
(
OVM_CanonicalTransactionChain
.
appendSequencerBatch
(
[
'
0x1234
'
],
[
{
numSequencedTransactions
:
0
,
numSubsequentQueueTransactions
:
0
,
numSubsequentQueueTransactions
:
0
,
timestamp
:
0
,
timestamp
:
0
,
blockNumber
:
0
blockNumber
:
0
,
}
},
],
1234
,
1
)
).
to
.
be
.
revertedWith
(
'
Actual batch start index does not match expected start index.
'
)
})
await
OVM_CanonicalTransactionChain
.
appendSequencerBatch
(
it
(
'
should revert if not called by the sequencer
'
,
async
()
=>
{
[
'
0x1212
'
],
await
expect
(
[
testBatchContext
],
OVM_CanonicalTransactionChain
.
connect
(
signer
).
appendSequencerBatch
(
[
'
0x1234
'
],
[
{
numSequencedTransactions
:
0
,
numSubsequentQueueTransactions
:
0
,
timestamp
:
0
,
blockNumber
:
0
,
},
],
0
,
0
,
1
1
)
)
expect
(
await
OVM_CanonicalTransactionChain
.
getTotalElements
()).
to
.
equal
(
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
)
})
})
})
})
it
(
'
should append a batch with 1 sequencer tx and a queue tx
'
,
async
()
=>
{
const
testBatchContext
:
sequencerBatchContext
=
{
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
,
numSequencedTransactions
:
1
,
numSubsequentQueueTransactions
:
1
,
numSubsequentQueueTransactions
:
1
,
timestamp
:
3
,
timestamp
:
timestamp
,
blockNumber
:
3
blockNumber
:
Math
.
max
(
blockNumber
,
0
),
}
}
await
OVM_CanonicalTransactionChain
.
enqueue
(
'
0x
'
+
'
01
'
.
repeat
(
20
),
25000
,
'
0x1234
'
)
})
await
OVM_CanonicalTransactionChain
.
appendSequencerBatch
(
[
'
0x1212
'
],
transactions
=
[...
Array
(
size
)].
map
((
el
,
idx
)
=>
{
[
testBatchContext
],
return
'
0x
'
+
'
12
'
+
'
34
'
.
repeat
(
idx
)
})
})
it
(
'
should append the batch
'
,
async
()
=>
{
await
expect
(
OVM_CanonicalTransactionChain
.
appendSequencerBatch
(
transactions
,
contexts
,
0
,
0
,
2
size
*
2
)
)
)
expect
(
await
OVM_CanonicalTransactionChain
.
getTotalElements
()).
to
.
equal
(
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'
...
@@ -9,14 +9,14 @@ import { Contract, Signer } from 'ethers'
import
{
import
{
NON_NULL_BYTES32
,
NON_NULL_BYTES32
,
makeHexString
,
makeHexString
,
increaseEthTime
increaseEthTime
,
}
from
'
../../../helpers
'
}
from
'
../../../helpers
'
const
numToBytes32
=
(
num
:
Number
):
string
=>
{
const
numToBytes32
=
(
num
:
Number
):
string
=>
{
if
(
num
<
0
||
num
>
255
)
{
if
(
num
<
0
||
num
>
255
)
{
throw
new
Error
(
'
Unsupported number.
'
)
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
return
'
0x
'
+
'
00
'
.
repeat
(
31
)
+
strNum
}
}
...
@@ -29,8 +29,14 @@ describe('Lib_TimeboundRingBuffer', () => {
...
@@ -29,8 +29,14 @@ describe('Lib_TimeboundRingBuffer', () => {
let
Lib_TimeboundRingBuffer
:
Contract
let
Lib_TimeboundRingBuffer
:
Contract
const
NON_NULL_BYTES28
=
makeHexString
(
'
01
'
,
28
)
const
NON_NULL_BYTES28
=
makeHexString
(
'
01
'
,
28
)
const
pushNum
=
(
num
:
Number
)
=>
Lib_TimeboundRingBuffer
.
push
(
numToBytes32
(
num
),
NON_NULL_BYTES28
)
const
pushNum
=
(
num
:
Number
)
=>
const
push2Nums
=
(
num1
:
Number
,
num2
:
Number
)
=>
Lib_TimeboundRingBuffer
.
push2
(
numToBytes32
(
num1
),
numToBytes32
(
num2
),
NON_NULL_BYTES28
)
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
'
,
()
=>
{
describe
(
'
push with no timeout
'
,
()
=>
{
beforeEach
(
async
()
=>
{
beforeEach
(
async
()
=>
{
...
@@ -69,11 +75,15 @@ describe('Lib_TimeboundRingBuffer', () => {
...
@@ -69,11 +75,15 @@ describe('Lib_TimeboundRingBuffer', () => {
})
})
it
(
'
should revert if index is too old
'
,
async
()
=>
{
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
()
=>
{
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', () => {
...
@@ -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
()
=>
{
it
(
'
should push a single value which extends the array
'
,
async
()
=>
{
await
pushNum
(
2
)
await
pushNum
(
2
)
...
@@ -151,7 +162,9 @@ describe('Lib_TimeboundRingBuffer', () => {
...
@@ -151,7 +162,9 @@ describe('Lib_TimeboundRingBuffer', () => {
it
(
'
should return the expected extra data
'
,
async
()
=>
{
it
(
'
should return the expected extra data
'
,
async
()
=>
{
await
Lib_TimeboundRingBuffer
.
push
(
NON_NULL_BYTES32
,
NON_NULL_BYTES28
)
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', () => {
...
@@ -168,17 +181,25 @@ describe('Lib_TimeboundRingBuffer', () => {
it
(
'
should disallow deletions which are too old
'
,
async
()
=>
{
it
(
'
should disallow deletions which are too old
'
,
async
()
=>
{
push2Nums
(
4
,
5
)
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
()
=>
{
it
(
'
should not allow get to be called on an old value even after deletion
'
,
async
()
=>
{
pushNum
(
4
)
pushNum
(
4
)
expect
(
await
Lib_TimeboundRingBuffer
.
getMaxSize
()).
to
.
equal
(
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
)
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
(
0
)).
to
.
be
.
revertedWith
(
await
expect
(
Lib_TimeboundRingBuffer
.
get
(
4
)).
to
.
be
.
revertedWith
(
"
Index too large.
"
)
'
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
(
1
)).
to
.
equal
(
numToBytes32
(
1
))
expect
(
await
Lib_TimeboundRingBuffer
.
get
(
3
)).
to
.
equal
(
numToBytes32
(
3
))
expect
(
await
Lib_TimeboundRingBuffer
.
get
(
3
)).
to
.
equal
(
numToBytes32
(
3
))
})
})
...
...
packages/contracts/test/helpers/utils/eth-time.ts
View file @
dcfd3df9
...
@@ -24,3 +24,7 @@ export const getBlockTime = async (
...
@@ -24,3 +24,7 @@ export const getBlockTime = async (
await
provider
.
send
(
'
evm_mine
'
,
[])
await
provider
.
send
(
'
evm_mine
'
,
[])
return
(
await
provider
.
getBlock
(
block
)).
timestamp
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 @@
...
@@ -31,7 +31,8 @@
},
},
"linterOptions"
:
{
"linterOptions"
:
{
"exclude"
:
[
"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