Commit 6619834b authored by Kelvin Fichter's avatar Kelvin Fichter Committed by GitHub

Adds CTC verification functions, removes BaseChain (#24)

* Upgraded to new RingBuffer

* First pass at verification function

* Removed BaseChain as a contract

* Update OVM_CanonicalTransactionChain.spec.ts

* Removed unused chain variable

* Added overwriting logic to CTC

* Corrected CTC event emit

* Remove timebound ring buffer tests
parent 5026308f
......@@ -179,8 +179,8 @@ contract OVM_L1CrossDomainMessenger is iOVM_L1CrossDomainMessenger, OVM_BaseCros
{
return (
ovmStateCommitmentChain.insideFraudProofWindow(_proof.stateRootBatchHeader) == false
&& ovmStateCommitmentChain.verifyElement(
abi.encodePacked(_proof.stateRoot),
&& ovmStateCommitmentChain.verifyStateCommitment(
_proof.stateRoot,
_proof.stateRootBatchHeader,
_proof.stateRootProof
)
......
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
/* Interface Imports */
import { iOVM_BaseChain } from "../../iOVM/chain/iOVM_BaseChain.sol";
/* Library Imports */
import { Lib_OVMCodec } from "../../libraries/codec/Lib_OVMCodec.sol";
import { Lib_MerkleUtils } from "../../libraries/utils/Lib_MerkleUtils.sol";
import { TimeboundRingBuffer, Lib_TimeboundRingBuffer } from "../../libraries/utils/Lib_TimeboundRingBuffer.sol";
/**
* @title OVM_BaseChain
*/
contract OVM_BaseChain is iOVM_BaseChain {
/*******************************
* Contract Variables: Batches *
*******************************/
using Lib_TimeboundRingBuffer for TimeboundRingBuffer;
TimeboundRingBuffer internal batches;
uint256 internal totalBatches;
uint256 internal totalElements;
/***************
* Constructor *
***************/
constructor()
{
// TODO: Add propper customization
batches.init(4, 2, 100000);
}
/*************************************
* Public Functions: Batch Retrieval *
*************************************/
/**
* Gets the total number of submitted elements.
* @return _totalElements Total submitted elements.
*/
function getTotalElements()
virtual
override
public
view
returns (
uint256 _totalElements
)
{
return uint256(uint224(batches.getExtraData()));
}
/**
* Gets the total number of submitted batches.
* @return _totalBatches Total submitted batches.
*/
function getTotalBatches()
override
public
view
returns (
uint256 _totalBatches
)
{
return uint256(batches.getLength());
}
/****************************************
* Public Functions: Batch Verification *
****************************************/
/**
* Verifies an inclusion proof for a given element.
* @param _element Element to verify.
* @param _batchHeader Header of the batch in which this element was included.
* @param _proof Inclusion proof for the element.
* @return _verified Whether or not the element was included in the batch.
*/
function verifyElement(
bytes calldata _element,
Lib_OVMCodec.ChainBatchHeader memory _batchHeader,
Lib_OVMCodec.ChainInclusionProof memory _proof
)
override
public
view
returns (
bool _verified
)
{
require(
_hashBatchHeader(_batchHeader) == batches.get(uint32(_batchHeader.batchIndex)),
"Invalid batch header."
);
require(
Lib_MerkleUtils.verify(
_batchHeader.batchRoot,
_element,
_proof.index,
_proof.siblings
),
"Invalid inclusion proof."
);
return true;
}
/******************************************
* Internal Functions: Batch Modification *
******************************************/
/**
* Appends a batch to the chain.
* @param _batchHeader Batch header to append.
*/
function _appendBatch(
Lib_OVMCodec.ChainBatchHeader memory _batchHeader
)
internal
{
bytes32 batchHeaderHash = _hashBatchHeader(_batchHeader);
batches.push(batchHeaderHash, bytes28(uint224(getTotalElements() + _batchHeader.batchSize)));
}
/**
* Appends a batch to the chain.
* @param _elements Elements within the batch.
* @param _extraData Any extra data to append to the batch.
*/
function _appendBatch(
bytes[] memory _elements,
bytes memory _extraData
)
internal
{
Lib_OVMCodec.ChainBatchHeader memory batchHeader = Lib_OVMCodec.ChainBatchHeader({
batchIndex: uint(batches.getLength()),
batchRoot: Lib_MerkleUtils.getMerkleRoot(_elements),
batchSize: _elements.length,
prevTotalElements: totalElements,
extraData: _extraData
});
_appendBatch(batchHeader);
}
/**
* Appends a batch to the chain.
* @param _elements Elements within the batch.
*/
function _appendBatch(
bytes[] memory _elements
)
internal
{
_appendBatch(
_elements,
bytes('')
);
}
/**
* Removes a batch from the chain.
* @param _batchHeader Header of the batch to remove.
*/
function _deleteBatch(
Lib_OVMCodec.ChainBatchHeader memory _batchHeader
)
internal
{
require(
_batchHeader.batchIndex < batches.getLength(),
"Invalid batch index."
);
require(
_hashBatchHeader(_batchHeader) == batches.get(uint32(_batchHeader.batchIndex)),
"Invalid batch header."
);
totalElements = _batchHeader.prevTotalElements;
batches.deleteElementsAfter(uint32(_batchHeader.batchIndex - 1), bytes28(uint224(totalElements)));
}
/**
* Calculates a hash for a given batch header.
* @param _batchHeader Header to hash.
* @return _hash Hash of the header.
*/
function _hashBatchHeader(
Lib_OVMCodec.ChainBatchHeader memory _batchHeader
)
internal
pure
returns (
bytes32 _hash
)
{
return keccak256(abi.encodePacked(
_batchHeader.batchRoot,
_batchHeader.batchSize,
_batchHeader.prevTotalElements,
_batchHeader.extraData
));
}
}
......@@ -6,15 +6,12 @@ pragma experimental ABIEncoderV2;
import { Lib_OVMCodec } from "../../libraries/codec/Lib_OVMCodec.sol";
import { Lib_AddressResolver } from "../../libraries/resolver/Lib_AddressResolver.sol";
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 { Lib_RingBuffer, iRingBufferOverwriter } from "../../libraries/utils/Lib_RingBuffer.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";
import { OVM_ExecutionManager } from "../execution/OVM_ExecutionManager.sol";
/* Logging Imports */
......@@ -23,7 +20,9 @@ import { console } from "@nomiclabs/buidler/console.sol";
/**
* @title OVM_CanonicalTransactionChain
*/
contract OVM_CanonicalTransactionChain is iOVM_CanonicalTransactionChain, OVM_BaseChain, Lib_AddressResolver {
contract OVM_CanonicalTransactionChain is iOVM_CanonicalTransactionChain, Lib_AddressResolver {
using Lib_RingBuffer for Lib_RingBuffer.RingBuffer;
/*************
* Constants *
......@@ -32,7 +31,8 @@ contract OVM_CanonicalTransactionChain is iOVM_CanonicalTransactionChain, OVM_Ba
uint256 constant public MIN_ROLLUP_TX_GAS = 20000;
uint256 constant public MAX_ROLLUP_TX_SIZE = 10000;
uint256 constant public L2_GAS_DISCOUNT_DIVISOR = 10;
// Encoding Constants
// Encoding constants (all in bytes)
uint256 constant internal BATCH_CONTEXT_SIZE = 16;
uint256 constant internal BATCH_CONTEXT_LENGTH_POS = 12;
uint256 constant internal BATCH_CONTEXT_START_POS = 15;
......@@ -48,20 +48,14 @@ contract OVM_CanonicalTransactionChain is iOVM_CanonicalTransactionChain, OVM_Ba
uint256 internal lastOVMTimestamp;
address internal sequencer;
address internal decompressionPrecompileAddress;
using Lib_TimeboundRingBuffer for TimeboundRingBuffer;
TimeboundRingBuffer internal queue;
TimeboundRingBuffer internal chain;
Lib_RingBuffer.RingBuffer internal batches;
Lib_RingBuffer.RingBuffer internal queue;
/***************
* Constructor *
***************/
/**
* @param _libAddressManager Address of the Address Manager.
* @param _forceInclusionPeriodSeconds Period during which only the sequencer can submit.
*/
constructor(
address _libAddressManager,
uint256 _forceInclusionPeriodSeconds
......@@ -72,8 +66,17 @@ contract OVM_CanonicalTransactionChain is iOVM_CanonicalTransactionChain, OVM_Ba
decompressionPrecompileAddress = resolve("OVM_DecompressionPrecompileAddress");
forceInclusionPeriodSeconds = _forceInclusionPeriodSeconds;
queue.init(100, 50, 10000000000); // TODO: Update once we have arbitrary condition
batches.init(2, 50, 0); // TODO: Update once we have arbitrary condition
batches.init(
16,
Lib_OVMCodec.RING_BUFFER_CTC_BATCHES,
iRingBufferOverwriter(resolve("OVM_StateCommitmentChain"))
);
queue.init(
16,
Lib_OVMCodec.RING_BUFFER_CTC_QUEUE,
iRingBufferOverwriter(resolve("OVM_StateCommitmentChain"))
);
}
......@@ -81,18 +84,35 @@ contract OVM_CanonicalTransactionChain is iOVM_CanonicalTransactionChain, OVM_Ba
* Public Functions *
********************/
/**
* @inheritdoc iOVM_CanonicalTransactionChain
*/
function getTotalElements()
override(OVM_BaseChain, iOVM_BaseChain)
override
public
view
returns (
uint256 _totalElements
)
{
(uint40 totalElements,) = _getLatestBatchContext();
(uint40 totalElements,) = _getBatchExtraData();
return uint256(totalElements);
}
/**
* @inheritdoc iOVM_CanonicalTransactionChain
*/
function getTotalBatches()
override
public
view
returns (
uint256 _totalBatches
)
{
return uint256(batches.getLength());
}
/**
* @inheritdoc iOVM_CanonicalTransactionChain
*/
......@@ -106,15 +126,15 @@ contract OVM_CanonicalTransactionChain is iOVM_CanonicalTransactionChain, OVM_Ba
Lib_OVMCodec.QueueElement memory _element
)
{
uint32 trueIndex = uint32(_index * 2);
uint40 trueIndex = uint40(_index * 2);
bytes32 queueRoot = queue.get(trueIndex);
bytes32 timestampAndBlockNumber = queue.get(trueIndex + 1);
uint40 elementTimestamp;
uint32 elementBlockNumber;
uint40 elementBlockNumber;
assembly {
elementTimestamp := and(timestampAndBlockNumber, 0x000000000000000000000000000000000000000000000000000000ffffffffff)
elementBlockNumber := shr(40, and(timestampAndBlockNumber, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000))
elementTimestamp := and(timestampAndBlockNumber, 0x000000000000000000000000000000000000000000000000000000FFFFFFFFFF)
elementBlockNumber := shr(40, and(timestampAndBlockNumber, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000))
}
return Lib_OVMCodec.QueueElement({
......@@ -137,14 +157,17 @@ contract OVM_CanonicalTransactionChain is iOVM_CanonicalTransactionChain, OVM_Ba
{
require(
_data.length <= MAX_ROLLUP_TX_SIZE,
"Transaction exceeds maximum rollup data size."
"Transaction exceeds maximum rollup transaction data size."
);
require(
_gasLimit >= MIN_ROLLUP_TX_GAS,
"Layer 2 gas limit too low to enqueue."
"Transaction gas limit too low to enqueue."
);
// 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.
uint256 gasToConsume = _gasLimit/L2_GAS_DISCOUNT_DIVISOR;
uint256 startingGas = gasleft();
......@@ -155,10 +178,6 @@ contract OVM_CanonicalTransactionChain is iOVM_CanonicalTransactionChain, OVM_Ba
"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;
......@@ -166,29 +185,33 @@ contract OVM_CanonicalTransactionChain is iOVM_CanonicalTransactionChain, OVM_Ba
i++;
}
bytes memory transaction = abi.encode(
bytes32 transactionHash = keccak256(
abi.encode(
msg.sender,
_target,
_gasLimit,
_data
)
);
bytes32 transactionHash = keccak256(transaction);
bytes32 timestampAndBlockNumber;
assembly {
timestampAndBlockNumber := or(timestamp(), shl(40, number()))
timestampAndBlockNumber := timestamp()
timestampAndBlockNumber := or(timestampAndBlockNumber, shl(40, number()))
}
queue.push2(transactionHash, timestampAndBlockNumber, bytes28(0));
queue.push2(
transactionHash,
timestampAndBlockNumber
);
(, uint32 nextQueueIndex) = _getLatestBatchContext();
// TODO: Evaluate if we need timestamp
uint40 queueIndex = queue.getLength() / 2;
emit TransactionEnqueued(
msg.sender,
_target,
_gasLimit,
_data,
nextQueueIndex - 1,
queueIndex - 1,
block.timestamp
);
}
......@@ -197,7 +220,7 @@ contract OVM_CanonicalTransactionChain is iOVM_CanonicalTransactionChain, OVM_Ba
* @inheritdoc iOVM_CanonicalTransactionChain
*/
function appendQueueBatch(
uint _numQueuedTransactions
uint256 _numQueuedTransactions
)
override
public
......@@ -207,16 +230,15 @@ contract OVM_CanonicalTransactionChain is iOVM_CanonicalTransactionChain, OVM_Ba
"Must append more than zero transactions."
);
(, uint32 nextQueueIndex) = _getLatestBatchContext();
uint40 nextQueueIndex = _getNextQueueIndex();
bytes32[] memory leaves = new bytes32[](_numQueuedTransactions);
for (uint i = 0; i < _numQueuedTransactions; i++) {
for (uint256 i = 0; i < _numQueuedTransactions; i++) {
leaves[i] = _getQueueLeafHash(nextQueueIndex);
nextQueueIndex++;
}
_appendBatch(
Lib_MerkleRoot.getMerkleRoot(leaves),
Lib_MerkleUtils.getMerkleRoot(leaves),
_numQueuedTransactions,
_numQueuedTransactions
);
......@@ -231,29 +253,21 @@ contract OVM_CanonicalTransactionChain is iOVM_CanonicalTransactionChain, OVM_Ba
/**
* @inheritdoc iOVM_CanonicalTransactionChain
*/
function appendSequencerBatch( // USES CUSTOM ENCODING FOR EFFICIENCY PURPOSES
// uint40 _shouldStartAtBatch,
// uint24 _totalElementsToAppend,
// BatchContext[] _contexts,
// bytes[] _transactionDataFields
)
function appendSequencerBatch()
override
public
{
uint40 _shouldStartAtBatch;
uint24 _totalElementsToAppend;
uint24 _contextsLength;
uint40 shouldStartAtBatch;
uint24 totalElementsToAppend;
uint24 numContexts;
assembly {
// First 5 bytes after MethodId is _shouldStartAtBatch
_shouldStartAtBatch := shr(216, calldataload(4))
// Next 3 bytes is _totalElementsToAppend
_totalElementsToAppend := shr(232, calldataload(9))
// And the last 3 bytes is the _contextsLength
_contextsLength := shr(232, calldataload(12))
shouldStartAtBatch := shr(216, calldataload(4))
totalElementsToAppend := shr(232, calldataload(9))
numContexts := shr(232, calldataload(12))
}
require(
_shouldStartAtBatch == getTotalBatches(),
shouldStartAtBatch == getTotalBatches(),
"Actual batch start index does not match expected start index."
);
......@@ -263,58 +277,43 @@ contract OVM_CanonicalTransactionChain is iOVM_CanonicalTransactionChain, OVM_Ba
);
require(
_contextsLength > 0,
numContexts > 0,
"Must provide at least one batch context."
);
require(
_totalElementsToAppend > 0,
totalElementsToAppend > 0,
"Must append at least one element."
);
bytes32[] memory leaves = new bytes32[](_totalElementsToAppend);
uint32 transactionIndex = 0;
uint32 numSequencerTransactionsProcessed = 0;
uint32 nextSequencerTransactionPosition = uint32(BATCH_CONTEXT_START_POS + BATCH_CONTEXT_SIZE * _contextsLength);
uint theCalldataSize;
uint40 nextTransactionPtr = uint40(BATCH_CONTEXT_START_POS + BATCH_CONTEXT_SIZE * numContexts);
uint256 calldataSize;
assembly {
theCalldataSize := calldatasize()
calldataSize := calldatasize()
}
require(theCalldataSize >= nextSequencerTransactionPosition, "Not enough BatchContexts provided.");
require(
calldataSize >= nextTransactionPtr,
"Not enough BatchContexts provided."
);
(, uint32 nextQueueIndex) = _getLatestBatchContext();
bytes32[] memory leaves = new bytes32[](totalElementsToAppend);
uint32 transactionIndex = 0;
uint32 numSequencerTransactionsProcessed = 0;
uint40 nextQueueIndex = _getNextQueueIndex();
for (uint32 i = 0; i < _contextsLength; i++) {
for (uint32 i = 0; i < numContexts; i++) {
BatchContext memory context = _getBatchContext(i);
_validateBatchContext(context, nextQueueIndex);
for (uint32 j = 0; j < context.numSequencedTransactions; j++) {
uint256 txDataLength;
assembly {
// 3 byte txDataLength
txDataLength := shr(232, calldataload(nextSequencerTransactionPosition))
txDataLength := shr(232, calldataload(nextTransactionPtr))
}
bytes memory _chainElement = new bytes(BYTES_TILL_TX_DATA + txDataLength);
bytes32 leafHash;
uint _timestamp = context.timestamp;
uint _blockNumber = context.blockNumber;
assembly {
let chainElementStart := add(_chainElement, 0x20)
mstore8(chainElementStart, 1)
mstore8(add(chainElementStart, 1), 0)
mstore(add(chainElementStart, 2), _timestamp)
mstore(add(chainElementStart, 34), _blockNumber)
// Store the rest of the transaction
calldatacopy(add(chainElementStart, BYTES_TILL_TX_DATA), add(nextSequencerTransactionPosition, 3), txDataLength)
// Calculate the hash
leafHash := keccak256(chainElementStart, add(BYTES_TILL_TX_DATA, txDataLength))
}
leaves[transactionIndex] = leafHash;
nextSequencerTransactionPosition += uint32(TX_DATA_HEADER_SIZE + txDataLength);
leaves[transactionIndex] = _getSequencerLeafHash(context, nextTransactionPtr, txDataLength);
nextTransactionPtr += uint40(TX_DATA_HEADER_SIZE + txDataLength);
numSequencerTransactionsProcessed++;
transactionIndex++;
}
......@@ -327,18 +326,19 @@ contract OVM_CanonicalTransactionChain is iOVM_CanonicalTransactionChain, OVM_Ba
}
require(
theCalldataSize == nextSequencerTransactionPosition,
calldataSize == nextTransactionPtr,
"Not all sequencer transactions were processed."
);
require(
transactionIndex == _totalElementsToAppend,
transactionIndex == totalElementsToAppend,
"Actual transaction index does not match expected total elements to append."
);
uint256 numQueuedTransactions = _totalElementsToAppend - numSequencerTransactionsProcessed;
uint40 numQueuedTransactions = totalElementsToAppend - numSequencerTransactionsProcessed;
_appendBatch(
Lib_MerkleRoot.getMerkleRoot(leaves),
_totalElementsToAppend,
Lib_MerkleUtils.getMerkleRoot(leaves),
totalElementsToAppend,
numQueuedTransactions
);
......@@ -350,30 +350,38 @@ contract OVM_CanonicalTransactionChain is iOVM_CanonicalTransactionChain, OVM_Ba
}
/**
* Verifies whether a transaction is included in the chain.
* @return _isVerified True if the transaction exists in the CTC, false if not.
* @inheritdoc iOVM_CanonicalTransactionChain
*/
function verifyTransaction(
Lib_OVMCodec.Transaction memory _transaction,
TransactionChainElement memory _txChainElement,
Lib_OVMCodec.TransactionChainElement memory _txChainElement,
Lib_OVMCodec.ChainBatchHeader memory _batchHeader,
Lib_OVMCodec.ChainInclusionProof memory _inclusionProof
)
override
public
view
returns (
bool _isVerified
bool
)
{
if (_txChainElement.isSequenced == true) {
return _verifySequencerTransaction(_transaction, _txChainElement, _inclusionProof);
return _verifySequencerTransaction(
_transaction,
_txChainElement,
_batchHeader,
_inclusionProof
);
} else {
return _verifyQueueTransaction(_transaction, _txChainElement.queueIndex, _inclusionProof);
return _verifyQueueTransaction(
_transaction,
_txChainElement.queueIndex,
_batchHeader,
_inclusionProof
);
}
}
/**********************
* Internal Functions *
**********************/
......@@ -381,34 +389,30 @@ contract OVM_CanonicalTransactionChain is iOVM_CanonicalTransactionChain, OVM_Ba
/**
* Returns the BatchContext located at a particular index.
* @param _index The index of the BatchContext
* @return _context The BatchContext at the specified index.
* @return The BatchContext at the specified index.
*/
function _getBatchContext(
uint _index
uint256 _index
)
internal
view
pure
returns (
BatchContext memory _context
BatchContext memory
)
{
// Batch contexts always start at byte 12:
// 4[method_id] + 5[_shouldStartAtBatch] + 3[_totalElementsToAppend] + 3[numBatchContexts]
uint contextPosition = 15 + _index * BATCH_CONTEXT_SIZE;
uint numSequencedTransactions;
uint numSubsequentQueueTransactions;
uint ctxTimestamp;
uint ctxBlockNumber;
uint256 contextPtr = 15 + _index * BATCH_CONTEXT_SIZE;
uint256 numSequencedTransactions;
uint256 numSubsequentQueueTransactions;
uint256 ctxTimestamp;
uint256 ctxBlockNumber;
assembly {
// 3 byte numSequencedTransactions
numSequencedTransactions := shr(232, calldataload(contextPosition))
// 3 byte numSubsequentQueueTransactions
numSubsequentQueueTransactions := shr(232, calldataload(add(contextPosition, 3)))
// 5 byte timestamp
ctxTimestamp := shr(216, calldataload(add(contextPosition, 6)))
// 5 byte blockNumber
ctxBlockNumber := shr(216, calldataload(add(contextPosition, 11)))
numSequencedTransactions := shr(232, calldataload(contextPtr))
numSubsequentQueueTransactions := shr(232, calldataload(add(contextPtr, 3)))
ctxTimestamp := shr(216, calldataload(add(contextPtr, 6)))
ctxBlockNumber := shr(216, calldataload(add(contextPtr, 11)))
}
return BatchContext({
numSequencedTransactions: numSequencedTransactions,
numSubsequentQueueTransactions: numSubsequentQueueTransactions,
......@@ -417,67 +421,88 @@ contract OVM_CanonicalTransactionChain is iOVM_CanonicalTransactionChain, OVM_Ba
});
}
/**
* Returns the index of the next element to be enqueued.
* @return Index for the next queue element.
*/
function _getNextQueueIndex()
internal
view
returns (
uint40
)
{
(, uint40 nextQueueIndex) = _getBatchExtraData();
return nextQueueIndex;
}
/**
* Parses the batch context from the extra data.
* @return _totalElements Total number of elements submitted.
* @return _nextQueueIndex Index of the next queue element.
* @return Total number of elements submitted.
* @return Index of the next queue element.
*/
function _getLatestBatchContext()
function _getBatchExtraData()
internal
view
returns (
uint40 _totalElements,
uint32 _nextQueueIndex
uint40,
uint40
)
{
bytes28 extraData = batches.getExtraData();
bytes27 extraData = batches.getExtraData();
uint40 totalElements;
uint32 nextQueueIndex;
uint40 nextQueueIndex;
assembly {
totalElements := and(shr(32, extraData), 0x000000000000000000000000000000000000000000000000000000ffffffffff)
nextQueueIndex := shr(40, and(shr(32, extraData), 0xffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000))
extraData := shr(40, extraData)
totalElements := and(extraData, 0x000000000000000000000000000000000000000000000000000000FFFFFFFFFF)
nextQueueIndex := shr(40, and(extraData, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000))
}
return (totalElements, nextQueueIndex);
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.
* @return Encoded batch context.
*/
function _makeLatestBatchContext(
function _makeBatchExtraData(
uint40 _totalElements,
uint32 _nextQueueIndex
uint40 _nextQueueIndex
)
internal
pure
returns (
bytes28 _context
bytes27
)
{
bytes28 totalElementsAndNextQueueIndex;
bytes27 extraData;
assembly {
totalElementsAndNextQueueIndex := shl(32, or(_totalElements, shl(40, _nextQueueIndex)))
extraData := _totalElements
extraData := or(extraData, shl(40, _nextQueueIndex))
extraData := shl(40, extraData)
}
return totalElementsAndNextQueueIndex;
return extraData;
}
/**
* 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.
* @return Hash of the queue element.
*/
function _getQueueLeafHash(
uint _index
uint256 _index
)
internal
view
returns (
bytes32 _queueLeafHash
bytes32
)
{
Lib_OVMCodec.QueueElement memory element = getQueueElement(_index);
......@@ -489,7 +514,7 @@ contract OVM_CanonicalTransactionChain is iOVM_CanonicalTransactionChain, OVM_Ba
);
return _hashTransactionChainElement(
TransactionChainElement({
Lib_OVMCodec.TransactionChainElement({
isSequenced: false,
queueIndex: _index,
timestamp: 0,
......@@ -499,6 +524,86 @@ contract OVM_CanonicalTransactionChain is iOVM_CanonicalTransactionChain, OVM_Ba
);
}
/**
* Retrieves the hash of a sequencer element.
* @param _context Batch context for the given element.
* @param _nextTransactionPtr Pointer to the next transaction in the calldata.
* @param _txDataLength Length of the transaction item.
* @return Hash of the sequencer element.
*/
function _getSequencerLeafHash(
BatchContext memory _context,
uint256 _nextTransactionPtr,
uint256 _txDataLength
)
internal
pure
returns (
bytes32
)
{
bytes memory chainElement = new bytes(BYTES_TILL_TX_DATA + _txDataLength);
uint256 ctxTimestamp = _context.timestamp;
uint256 ctxBlockNumber = _context.blockNumber;
bytes32 leafHash;
assembly {
let chainElementStart := add(chainElement, 0x20)
mstore8(chainElementStart, 1)
mstore8(add(chainElementStart, 1), 0)
mstore(add(chainElementStart, 2), ctxTimestamp)
mstore(add(chainElementStart, 34), ctxBlockNumber)
calldatacopy(add(chainElementStart, BYTES_TILL_TX_DATA), add(_nextTransactionPtr, 3), _txDataLength)
leafHash := keccak256(chainElementStart, add(BYTES_TILL_TX_DATA, _txDataLength))
}
return leafHash;
}
/**
* Retrieves the hash of a sequencer element.
* @param _txChainElement The chain element which is hashed to calculate the leaf.
* @return Hash of the sequencer element.
*/
function _getSequencerLeafHash(
Lib_OVMCodec.TransactionChainElement memory _txChainElement
)
internal
view
returns(
bytes32
)
{
bytes memory txData = _txChainElement.txData;
uint256 txDataLength = _txChainElement.txData.length;
bytes memory chainElement = new bytes(BYTES_TILL_TX_DATA + txDataLength);
uint256 ctxTimestamp = _txChainElement.timestamp;
uint256 ctxBlockNumber = _txChainElement.blockNumber;
bytes32 leafHash;
assembly {
let chainElementStart := add(chainElement, 0x20)
mstore8(chainElementStart, 1)
mstore8(add(chainElementStart, 1), 0)
mstore(add(chainElementStart, 2), ctxTimestamp)
mstore(add(chainElementStart, 34), ctxBlockNumber)
pop(staticcall(gas(), 0x04, add(txData, 0x20), txDataLength, add(chainElementStart, 66), txDataLength))
leafHash := keccak256(chainElementStart, add(BYTES_TILL_TX_DATA, txDataLength))
}
return leafHash;
}
/**
* Inserts a batch into the chain of batches.
* @param _transactionRoot Root of the transaction tree for this batch.
......@@ -507,12 +612,12 @@ contract OVM_CanonicalTransactionChain is iOVM_CanonicalTransactionChain, OVM_Ba
*/
function _appendBatch(
bytes32 _transactionRoot,
uint _batchSize,
uint _numQueuedTransactions
uint256 _batchSize,
uint256 _numQueuedTransactions
)
internal
{
(uint40 totalElements, uint32 nextQueueIndex) = _getLatestBatchContext();
(uint40 totalElements, uint40 nextQueueIndex) = _getBatchExtraData();
Lib_OVMCodec.ChainBatchHeader memory header = Lib_OVMCodec.ChainBatchHeader({
batchIndex: batches.getLength(),
......@@ -522,10 +627,10 @@ contract OVM_CanonicalTransactionChain is iOVM_CanonicalTransactionChain, OVM_Ba
extraData: hex""
});
bytes32 batchHeaderHash = _hashBatchHeader(header);
bytes28 latestBatchContext = _makeLatestBatchContext(
bytes32 batchHeaderHash = Lib_OVMCodec.hashBatchHeader(header);
bytes27 latestBatchContext = _makeBatchExtraData(
totalElements + uint40(header.batchSize),
nextQueueIndex + uint32(_numQueuedTransactions)
nextQueueIndex + uint40(_numQueuedTransactions)
);
batches.push(batchHeaderHash, latestBatchContext);
......@@ -538,7 +643,7 @@ contract OVM_CanonicalTransactionChain is iOVM_CanonicalTransactionChain, OVM_Ba
*/
function _validateBatchContext(
BatchContext memory _context,
uint32 _nextQueueIndex
uint40 _nextQueueIndex
)
internal
view
......@@ -568,15 +673,15 @@ contract OVM_CanonicalTransactionChain is iOVM_CanonicalTransactionChain, OVM_Ba
/**
* Hashes a transaction chain element.
* @param _element Chain element to hash.
* @return _hash Hash of the chain element.
* @return Hash of the chain element.
*/
function _hashTransactionChainElement(
TransactionChainElement memory _element
Lib_OVMCodec.TransactionChainElement memory _element
)
internal
pure
returns (
bytes32 _hash
bytes32
)
{
return keccak256(
......@@ -590,117 +695,136 @@ contract OVM_CanonicalTransactionChain is iOVM_CanonicalTransactionChain, OVM_Ba
);
}
/************************************************
* Internal Functions: Transaction Verification *
************************************************/
/**
* Verifies a sequencer transaction, returning true if it was indeed included in the CTC
* @param _transaction The transaction we are verifying inclusion of.
* @param _txChainElement The chain element that the transaction is claimed to be a part of.
* @param _batchHeader Header of the batch the transaction was included in.
* @param _inclusionProof An inclusion proof into the CTC at a particular index.
* @return _isVerified True if the transaction was included in the specified location, else false.
* @return True if the transaction was included in the specified location, else false.
*/
function _verifySequencerTransaction(
Lib_OVMCodec.Transaction memory _transaction,
TransactionChainElement memory _txChainElement,
Lib_OVMCodec.TransactionChainElement memory _txChainElement,
Lib_OVMCodec.ChainBatchHeader memory _batchHeader,
Lib_OVMCodec.ChainInclusionProof memory _inclusionProof
)
internal
view
returns (
bool _isVerified
)
{
OVM_ExecutionManager em = OVM_ExecutionManager(resolve("OVM_ExecutionManager"));
uint256 gasLimit = em.getMaxTransactionGasLimit();
address decompressionAddress = decompressionPrecompileAddress;
bytes32 leafHash = _getSequencerChainElementLeafHash(_txChainElement);
// TODO: Verify inclusion proof points to the computed leafHash
// require(verifyElement(_inclusionProof, leafHash), "Invalid sequencer tx: inclusion proof invalid.")
require(_transaction.blockNumber == _txChainElement.blockNumber, "Invalid sequencer tx: blockNumber.");
require(_transaction.timestamp == _txChainElement.timestamp, "Invalid sequencer tx: timestamp.");
require(_transaction.entrypoint == decompressionAddress, "Invalid sequencer tx: entrypoint.");
require(_transaction.gasLimit == gasLimit, "Invalid sequencer tx: gasLimit.");
require(keccak256(_transaction.data) == keccak256(_txChainElement.txData), "Invalid sequencer tx: data.");
require(_transaction.l1TxOrigin == address(0), "Invalid sequencer tx: l1TxOrigin.");
require(_transaction.l1QueueOrigin == Lib_OVMCodec.QueueOrigin.SEQUENCER_QUEUE, "Invalid sequencer tx: l1QueueOrigin.");
return true;
}
/**
* Computes the sequencerChainElement leaf hash
* @param _txChainElement The chain element which is hashed to calculate the leaf.
* @return _leafHash The hash of the chain element (using the special chain element leaf encoding).
*/
function _getSequencerChainElementLeafHash(
TransactionChainElement memory _txChainElement
)
internal
view
returns(
bytes32 _leafHash
bool
)
{
bytes memory txData = _txChainElement.txData;
uint256 txDataLength = _txChainElement.txData.length;
bytes memory _chainElement = new bytes(BYTES_TILL_TX_DATA + txDataLength);
bytes32 leafHash;
uint _timestamp = _txChainElement.timestamp;
uint _blockNumber = _txChainElement.blockNumber;
OVM_ExecutionManager ovmExecutionManager = OVM_ExecutionManager(resolve("OVM_ExecutionManager"));
uint256 gasLimit = ovmExecutionManager.getMaxTransactionGasLimit();
bytes32 leafHash = _getSequencerLeafHash(_txChainElement);
assembly {
let chainElementStart := add(_chainElement, 0x20)
mstore8(chainElementStart, 1)
mstore8(add(chainElementStart, 1), 0)
mstore(add(chainElementStart, 2), _timestamp)
mstore(add(chainElementStart, 34), _blockNumber)
require(
_verifyElement(
leafHash,
_batchHeader,
_inclusionProof
),
"Invalid Sequencer transaction inclusion proof."
);
// Copy the txData into memory using identity precompile
pop(staticcall(gas(), 0x04, add(txData, 0x20), txDataLength, add(chainElementStart, 66), txDataLength))
require(
_transaction.blockNumber == _txChainElement.blockNumber
&& _transaction.timestamp == _txChainElement.timestamp
&& _transaction.entrypoint == decompressionPrecompileAddress
&& _transaction.gasLimit == gasLimit
&& _transaction.l1TxOrigin == address(0)
&& _transaction.l1QueueOrigin == Lib_OVMCodec.QueueOrigin.SEQUENCER_QUEUE
&& keccak256(_transaction.data) == keccak256(_txChainElement.txData),
"Invalid Sequencer transaction."
);
// Calculate the hash
leafHash := keccak256(chainElementStart, add(BYTES_TILL_TX_DATA, txDataLength))
}
return _leafHash;
return true;
}
/**
* Verifies a queue transaction, returning true if it was indeed included in the CTC
* @param _transaction The transaction we are verifying inclusion of.
* @param _queueIndex The queueIndex of the queued transaction.
* @param _batchHeader Header of the batch the transaction was included in.
* @param _inclusionProof An inclusion proof into the CTC at a particular index (should point to queue tx).
* @return _isVerified True if the transaction was included in the specified location, else false.
* @return True if the transaction was included in the specified location, else false.
*/
function _verifyQueueTransaction(
Lib_OVMCodec.Transaction memory _transaction,
uint256 _queueIndex,
Lib_OVMCodec.ChainBatchHeader memory _batchHeader,
Lib_OVMCodec.ChainInclusionProof memory _inclusionProof
)
internal
view
returns (
bool _isVerified
bool
)
{
bytes32 leafHash = _getQueueLeafHash(_inclusionProof.index);
// TODO: Verify inclusion proof
// require(verifyElement(_inclusionProof, leafHash), "Invalid queue tx: inclusion proof invalid.")
Lib_OVMCodec.QueueElement memory ele = getQueueElement(_queueIndex);
bytes memory txEncoded = abi.encode(
require(
_verifyElement(
leafHash,
_batchHeader,
_inclusionProof
),
"Invalid Queue transaction inclusion proof."
);
bytes32 transactionHash = keccak256(
abi.encode(
_transaction.l1TxOrigin,
_transaction.entrypoint,
_transaction.gasLimit,
_transaction.data
)
);
Lib_OVMCodec.QueueElement memory el = getQueueElement(_queueIndex);
require(
el.queueRoot == transactionHash
&& el.timestamp == _transaction.timestamp
&& el.blockNumber == _transaction.blockNumber,
"Invalid Queue transaction."
);
return true;
}
/**
* Verifies a batch inclusion proof.
* @param _element Hash of the element to verify a proof for.
* @param _batchHeader Header of the batch in which the element was included.
* @param _proof Merkle inclusion proof for the element.
*/
function _verifyElement(
bytes32 _element,
Lib_OVMCodec.ChainBatchHeader memory _batchHeader,
Lib_OVMCodec.ChainInclusionProof memory _proof
)
internal
view
returns (
bool
)
{
require(
Lib_OVMCodec.hashBatchHeader(_batchHeader) == batches.get(uint32(_batchHeader.batchIndex)),
"Invalid batch header."
);
bytes32 transactionHash = keccak256(txEncoded);
require(ele.queueRoot == transactionHash, "Invalid queue tx: transaction hash does not match.");
require(ele.timestamp == _transaction.timestamp, "Invalid queue tx: timestamp does not match.");
require(ele.blockNumber == _transaction.blockNumber, "Invalid queue tx: blockNumber does not match.");
require(
Lib_MerkleUtils.verify(
_batchHeader.batchRoot,
_element,
_proof.index,
_proof.siblings
),
"Invalid inclusion proof."
);
return true;
}
}
......@@ -5,19 +5,20 @@ pragma experimental ABIEncoderV2;
/* Library Imports */
import { Lib_OVMCodec } from "../../libraries/codec/Lib_OVMCodec.sol";
import { Lib_AddressResolver } from "../../libraries/resolver/Lib_AddressResolver.sol";
import { Lib_MerkleUtils } from "../../libraries/utils/Lib_MerkleUtils.sol";
import { Lib_RingBuffer, iRingBufferOverwriter } from "../../libraries/utils/Lib_RingBuffer.sol";
/* Interface Imports */
import { iOVM_FraudVerifier } from "../../iOVM/verification/iOVM_FraudVerifier.sol";
import { iOVM_StateCommitmentChain } from "../../iOVM/chain/iOVM_StateCommitmentChain.sol";
import { iOVM_CanonicalTransactionChain } from "../../iOVM/chain/iOVM_CanonicalTransactionChain.sol";
/* Contract Imports */
import { OVM_BaseChain } from "./OVM_BaseChain.sol";
/**
* @title OVM_StateCommitmentChain
*/
contract OVM_StateCommitmentChain is iOVM_StateCommitmentChain, OVM_BaseChain, Lib_AddressResolver {
contract OVM_StateCommitmentChain is iOVM_StateCommitmentChain, iRingBufferOverwriter, Lib_AddressResolver {
using Lib_RingBuffer for Lib_RingBuffer.RingBuffer;
/*************
* Constants *
......@@ -26,10 +27,13 @@ contract OVM_StateCommitmentChain is iOVM_StateCommitmentChain, OVM_BaseChain, L
uint256 constant public FRAUD_PROOF_WINDOW = 7 days;
/*******************************************
* Contract Variables: Contract References *
*******************************************/
/*************
* Variables *
*************/
uint256 internal lastDeletableIndex;
uint256 internal lastDeletableTimestamp;
Lib_RingBuffer.RingBuffer internal batches;
iOVM_CanonicalTransactionChain internal ovmCanonicalTransactionChain;
iOVM_FraudVerifier internal ovmFraudVerifier;
......@@ -48,16 +52,50 @@ contract OVM_StateCommitmentChain is iOVM_StateCommitmentChain, OVM_BaseChain, L
{
ovmCanonicalTransactionChain = iOVM_CanonicalTransactionChain(resolve("OVM_CanonicalTransactionChain"));
ovmFraudVerifier = iOVM_FraudVerifier(resolve("OVM_FraudVerifier"));
batches.init(
16,
Lib_OVMCodec.RING_BUFFER_SCC_BATCHES,
iRingBufferOverwriter(address(this))
);
}
/****************************************
* Public Functions: Batch Manipulation *
****************************************/
/********************
* Public Functions *
********************/
/**
* Appends a batch of state roots to the chain.
* @param _batch Batch of state roots.
* @inheritdoc iOVM_StateCommitmentChain
*/
function getTotalElements()
virtual
override
public
view
returns (
uint256 _totalElements
)
{
return uint256(uint216(batches.getExtraData()));
}
/**
* @inheritdoc iOVM_StateCommitmentChain
*/
function getTotalBatches()
override
public
view
returns (
uint256 _totalBatches
)
{
return uint256(batches.getLength());
}
/**
* @inheritdoc iOVM_StateCommitmentChain
*/
function appendStateBatch(
bytes32[] memory _batch
......@@ -89,8 +127,7 @@ contract OVM_StateCommitmentChain is iOVM_StateCommitmentChain, OVM_BaseChain, L
}
/**
* Deletes all state roots after (and including) a given batch.
* @param _batchHeader Header of the batch to start deleting from.
* @inheritdoc iOVM_StateCommitmentChain
*/
function deleteStateBatch(
Lib_OVMCodec.ChainBatchHeader memory _batchHeader
......@@ -111,11 +148,42 @@ contract OVM_StateCommitmentChain is iOVM_StateCommitmentChain, OVM_BaseChain, L
_deleteBatch(_batchHeader);
}
/**
* @inheritdoc iOVM_StateCommitmentChain
*/
function verifyStateCommitment(
bytes32 _element,
Lib_OVMCodec.ChainBatchHeader memory _batchHeader,
Lib_OVMCodec.ChainInclusionProof memory _proof
)
override
public
view
returns (
bool
)
{
require(
Lib_OVMCodec.hashBatchHeader(_batchHeader) == batches.get(uint32(_batchHeader.batchIndex)),
"Invalid batch header."
);
require(
Lib_MerkleUtils.verify(
_batchHeader.batchRoot,
_element,
_proof.index,
_proof.siblings
),
"Invalid inclusion proof."
);
/**********************************
* Public Functions: Batch Status *
**********************************/
return true;
}
/**
* @inheritdoc iOVM_StateCommitmentChain
*/
function insideFraudProofWindow(
Lib_OVMCodec.ChainBatchHeader memory _batchHeader
)
......@@ -138,4 +206,134 @@ contract OVM_StateCommitmentChain is iOVM_StateCommitmentChain, OVM_BaseChain, L
return timestamp + FRAUD_PROOF_WINDOW > block.timestamp;
}
/**
* @inheritdoc iOVM_StateCommitmentChain
*/
function setLastDeletableIndex(
Lib_OVMCodec.ChainBatchHeader memory _stateBatchHeader,
Lib_OVMCodec.Transaction memory _transaction,
Lib_OVMCodec.TransactionChainElement memory _txChainElement,
Lib_OVMCodec.ChainBatchHeader memory _txBatchHeader,
Lib_OVMCodec.ChainInclusionProof memory _txInclusionProof
)
override
public
{
require(
Lib_OVMCodec.hashBatchHeader(_stateBatchHeader) == batches.get(uint32(_stateBatchHeader.batchIndex)),
"Invalid batch header."
);
require(
insideFraudProofWindow(_stateBatchHeader) == false,
"Batch header must be outside of fraud proof window to be deletable."
);
require(
_stateBatchHeader.batchIndex > lastDeletableIndex,
"Batch index must be greater than last deletable index."
);
require(
ovmCanonicalTransactionChain.verifyTransaction(
_transaction,
_txChainElement,
_txBatchHeader,
_txInclusionProof
),
"Invalid transaction proof."
);
lastDeletableIndex = _stateBatchHeader.batchIndex;
lastDeletableTimestamp = _transaction.timestamp;
}
/**
* @inheritdoc iRingBufferOverwriter
*/
function canOverwrite(
bytes32 _id,
uint256 _index
)
override
public
view
returns (
bool
)
{
if (_id == Lib_OVMCodec.RING_BUFFER_CTC_QUEUE) {
return ovmCanonicalTransactionChain.getQueueElement(_index / 2).timestamp < lastDeletableTimestamp;
} else {
return _index < lastDeletableIndex;
}
}
/**********************
* Internal Functions *
**********************/
/**
* Appends a batch to the chain.
* @param _batchHeader Batch header to append.
*/
function _appendBatch(
Lib_OVMCodec.ChainBatchHeader memory _batchHeader
)
internal
{
batches.push(
Lib_OVMCodec.hashBatchHeader(_batchHeader),
bytes27(uint216(getTotalElements() + _batchHeader.batchSize))
);
}
/**
* Appends a batch to the chain.
* @param _elements Elements within the batch.
* @param _extraData Any extra data to append to the batch.
*/
function _appendBatch(
bytes[] memory _elements,
bytes memory _extraData
)
internal
{
Lib_OVMCodec.ChainBatchHeader memory batchHeader = Lib_OVMCodec.ChainBatchHeader({
batchIndex: uint256(batches.getLength()),
batchRoot: Lib_MerkleUtils.getMerkleRoot(_elements),
batchSize: _elements.length,
prevTotalElements: getTotalElements(),
extraData: _extraData
});
_appendBatch(batchHeader);
}
/**
* Removes a batch from the chain.
* @param _batchHeader Header of the batch to remove.
*/
function _deleteBatch(
Lib_OVMCodec.ChainBatchHeader memory _batchHeader
)
internal
{
require(
_batchHeader.batchIndex < batches.getLength(),
"Invalid batch index."
);
require(
Lib_OVMCodec.hashBatchHeader(_batchHeader) == batches.get(uint32(_batchHeader.batchIndex)),
"Invalid batch header."
);
batches.deleteElementsAfterInclusive(
uint40(_batchHeader.batchIndex),
bytes27(uint216(_batchHeader.prevTotalElements))
);
}
}
......@@ -82,6 +82,7 @@ contract OVM_FraudVerifier is iOVM_FraudVerifier, Lib_AddressResolver {
* @param _preStateRootBatchHeader Batch header for the provided pre-state root.
* @param _preStateRootProof Inclusion proof for the provided pre-state root.
* @param _transaction OVM transaction claimed to be fraudulent.
* @param _txChainElement OVM transaction chain element.
* @param _transactionBatchHeader Batch header for the provided transaction.
* @param _transactionProof Inclusion proof for the provided transaction.
*/
......@@ -90,6 +91,7 @@ contract OVM_FraudVerifier is iOVM_FraudVerifier, Lib_AddressResolver {
Lib_OVMCodec.ChainBatchHeader memory _preStateRootBatchHeader,
Lib_OVMCodec.ChainInclusionProof memory _preStateRootProof,
Lib_OVMCodec.Transaction memory _transaction,
Lib_OVMCodec.TransactionChainElement memory _txChainElement,
Lib_OVMCodec.ChainBatchHeader memory _transactionBatchHeader,
Lib_OVMCodec.ChainInclusionProof memory _transactionProof
)
......@@ -101,7 +103,7 @@ contract OVM_FraudVerifier is iOVM_FraudVerifier, Lib_AddressResolver {
}
require(
_verifyStateRoot(
ovmStateCommitmentChain.verifyStateCommitment(
_preStateRoot,
_preStateRootBatchHeader,
_preStateRootProof
......@@ -110,8 +112,9 @@ contract OVM_FraudVerifier is iOVM_FraudVerifier, Lib_AddressResolver {
);
require(
_verifyTransaction(
ovmCanonicalTransactionChain.verifyTransaction(
_transaction,
_txChainElement,
_transactionBatchHeader,
_transactionProof
),
......@@ -161,7 +164,7 @@ contract OVM_FraudVerifier is iOVM_FraudVerifier, Lib_AddressResolver {
);
require(
_verifyStateRoot(
ovmStateCommitmentChain.verifyStateCommitment(
_preStateRoot,
_preStateRootBatchHeader,
_preStateRootProof
......@@ -170,7 +173,7 @@ contract OVM_FraudVerifier is iOVM_FraudVerifier, Lib_AddressResolver {
);
require(
_verifyStateRoot(
ovmStateCommitmentChain.verifyStateCommitment(
_postStateRoot,
_postStateRootBatchHeader,
_postStateRootProof
......@@ -209,54 +212,4 @@ contract OVM_FraudVerifier is iOVM_FraudVerifier, Lib_AddressResolver {
{
return address(transitioners[_preStateRoot]) != address(0);
}
/**
* Verifies inclusion of a state root.
* @param _stateRoot State root to verify
* @param _stateRootBatchHeader Batch header for the provided state root.
* @param _stateRootProof Inclusion proof for the provided state root.
* @return _verified Whether or not the root was included.
*/
function _verifyStateRoot(
bytes32 _stateRoot,
Lib_OVMCodec.ChainBatchHeader memory _stateRootBatchHeader,
Lib_OVMCodec.ChainInclusionProof memory _stateRootProof
)
internal
view
returns (
bool _verified
)
{
return ovmStateCommitmentChain.verifyElement(
abi.encodePacked(_stateRoot),
_stateRootBatchHeader,
_stateRootProof
);
}
/**
* Verifies inclusion of a given transaction.
* @param _transaction OVM transaction to verify.
* @param _transactionBatchHeader Batch header for the provided transaction.
* @param _transactionProof Inclusion proof for the provided transaction.
* @return _verified Whether or not the transaction was included.
*/
function _verifyTransaction(
Lib_OVMCodec.Transaction memory _transaction,
Lib_OVMCodec.ChainBatchHeader memory _transactionBatchHeader,
Lib_OVMCodec.ChainInclusionProof memory _transactionProof
)
internal
view
returns (
bool _verified
)
{
return ovmCanonicalTransactionChain.verifyElement(
Lib_OVMCodec.encodeTransaction(_transaction),
_transactionBatchHeader,
_transactionProof
);
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
/* Library Imports */
import { Lib_OVMCodec } from "../../libraries/codec/Lib_OVMCodec.sol";
/**
* @title iOVM_BaseChain
*/
interface iOVM_BaseChain {
/*************************************
* Public Functions: Batch Retrieval *
*************************************/
function getTotalElements() external view returns (uint256 _totalElements);
function getTotalBatches() external view returns (uint256 _totalBatches);
/****************************************
* Public Functions: Batch Verification *
****************************************/
function verifyElement(
bytes calldata _element,
Lib_OVMCodec.ChainBatchHeader calldata _batchHeader,
Lib_OVMCodec.ChainInclusionProof calldata _proof
) external view returns (bool _verified);
}
......@@ -5,13 +5,10 @@ pragma experimental ABIEncoderV2;
/* Library Imports */
import { Lib_OVMCodec } from "../../libraries/codec/Lib_OVMCodec.sol";
/* Interface Imports */
import { iOVM_BaseChain } from "./iOVM_BaseChain.sol";
/**
* @title iOVM_CanonicalTransactionChain
*/
interface iOVM_CanonicalTransactionChain is iOVM_BaseChain {
interface iOVM_CanonicalTransactionChain {
/**********
* Events *
......@@ -50,19 +47,33 @@ interface iOVM_CanonicalTransactionChain is iOVM_BaseChain {
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 *
********************/
/**
* Retrieves the total number of elements submitted.
* @return _totalElements Total submitted elements.
*/
function getTotalElements()
external
view
returns (
uint256 _totalElements
);
/**
* Retrieves the total number of batches submitted.
* @return _totalBatches Total submitted batches.
*/
function getTotalBatches()
external
view
returns (
uint256 _totalBatches
);
/**
* Gets the queue element at a particular index.
* @param _index Index of the queue element to access.
......@@ -87,7 +98,8 @@ interface iOVM_CanonicalTransactionChain is iOVM_BaseChain {
address _target,
uint256 _gasLimit,
bytes memory _data
) external;
)
external;
/**
* Appends a given number of queued transactions as a single batch.
......@@ -95,19 +107,42 @@ interface iOVM_CanonicalTransactionChain is iOVM_BaseChain {
*/
function appendQueueBatch(
uint256 _numQueuedTransactions
) external;
)
external;
/**
* Allows the sequencer to append a batch of transactions.
* param _shouldStartAtBatch Specific batch we expect to start appending to.
* param _totalElementsToAppend Total number of batch elements we expect to append.
* param _contexts Array of batch contexts.
* param _transactionDataFields Array of raw transaction data.
* @dev This function uses a custom encoding scheme for efficiency reasons.
* .param _shouldStartAtBatch Specific batch we expect to start appending to.
* .param _totalElementsToAppend Total number of batch elements we expect to append.
* .param _contexts Array of batch contexts.
* .param _transactionDataFields Array of raw transaction data.
*/
function appendSequencerBatch( // USES CUSTOM ENCODING FOR EFFICIENCY PURPOSES
function appendSequencerBatch(
// uint40 _shouldStartAtBatch,
// uint24 _totalElementsToAppend,
// BatchContext[] _contexts,
// bytes[] _transactionDataFields
) external;
)
external;
/**
* Verifies whether a transaction is included in the chain.
* @param _transaction Transaction to verify.
* @param _txChainElement Transaction chain element corresponding to the transaction.
* @param _batchHeader Header of the batch the transaction was included in.
* @param _inclusionProof Inclusion proof for the provided transaction chain element.
* @return True if the transaction exists in the CTC, false if not.
*/
function verifyTransaction(
Lib_OVMCodec.Transaction memory _transaction,
Lib_OVMCodec.TransactionChainElement memory _txChainElement,
Lib_OVMCodec.ChainBatchHeader memory _batchHeader,
Lib_OVMCodec.ChainInclusionProof memory _inclusionProof
)
external
view
returns (
bool
);
}
......@@ -2,27 +2,103 @@
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
/* Interface Imports */
import { iOVM_BaseChain } from "./iOVM_BaseChain.sol";
/* Library Imports */
import { Lib_OVMCodec } from "../../libraries/codec/Lib_OVMCodec.sol";
/**
* @title iOVM_StateCommitmentChain
*/
interface iOVM_StateCommitmentChain is iOVM_BaseChain {
interface iOVM_StateCommitmentChain {
/********************
* Public Functions *
********************/
/**
* Retrieves the total number of elements submitted.
* @return _totalElements Total submitted elements.
*/
function getTotalElements()
external
view
returns (
uint256 _totalElements
);
/**
* Retrieves the total number of batches submitted.
* @return _totalBatches Total submitted batches.
*/
function getTotalBatches()
external
view
returns (
uint256 _totalBatches
);
/****************************************
* Public Functions: Batch Manipulation *
****************************************/
/**
* Appends a batch of state roots to the chain.
* @param _batch Batch of state roots.
*/
function appendStateBatch(
bytes32[] calldata _batch
)
external;
/**
* Deletes all state roots after (and including) a given batch.
* @param _batchHeader Header of the batch to start deleting from.
*/
function deleteStateBatch(
Lib_OVMCodec.ChainBatchHeader memory _batchHeader
)
external;
function appendStateBatch(bytes32[] calldata _batch) external;
function deleteStateBatch(Lib_OVMCodec.ChainBatchHeader memory _batchHeader) external;
/**
* Verifies a batch inclusion proof.
* @param _element Hash of the element to verify a proof for.
* @param _batchHeader Header of the batch in which the element was included.
* @param _proof Merkle inclusion proof for the element.
*/
function verifyStateCommitment(
bytes32 _element,
Lib_OVMCodec.ChainBatchHeader memory _batchHeader,
Lib_OVMCodec.ChainInclusionProof memory _proof
)
external
view
returns (
bool _verified
);
/**********************************
* Public Functions: Batch Status *
**********************************/
/**
* Checks whether a given batch is still inside its fraud proof window.
* @param _batchHeader Header of the batch to check.
* @return _inside Whether or not the batch is inside the fraud proof window.
*/
function insideFraudProofWindow(
Lib_OVMCodec.ChainBatchHeader memory _batchHeader
)
external
view
returns (
bool _inside
);
function insideFraudProofWindow(Lib_OVMCodec.ChainBatchHeader memory _batchHeader) external view returns (bool _inside);
/**
* Sets the last batch index that can be deleted.
* @param _stateBatchHeader Proposed batch header that can be deleted.
* @param _transaction Transaction to verify.
* @param _txChainElement Transaction chain element corresponding to the transaction.
* @param _txBatchHeader Header of the batch the transaction was included in.
* @param _txInclusionProof Inclusion proof for the provided transaction chain element.
*/
function setLastDeletableIndex(
Lib_OVMCodec.ChainBatchHeader memory _stateBatchHeader,
Lib_OVMCodec.Transaction memory _transaction,
Lib_OVMCodec.TransactionChainElement memory _txChainElement,
Lib_OVMCodec.ChainBatchHeader memory _txBatchHeader,
Lib_OVMCodec.ChainInclusionProof memory _txInclusionProof
)
external;
}
......@@ -29,6 +29,7 @@ interface iOVM_FraudVerifier {
Lib_OVMCodec.ChainBatchHeader calldata _preStateRootBatchHeader,
Lib_OVMCodec.ChainInclusionProof calldata _preStateRootProof,
Lib_OVMCodec.Transaction calldata _transaction,
Lib_OVMCodec.TransactionChainElement calldata _txChainElement,
Lib_OVMCodec.ChainBatchHeader calldata _transactionBatchHeader,
Lib_OVMCodec.ChainInclusionProof calldata _transactionProof
) external;
......
......@@ -21,6 +21,11 @@ library Lib_OVMCodec {
bytes32 constant internal KECCAK256_RLP_NULL_BYTES = keccak256(RLP_NULL_BYTES);
bytes32 constant internal KECCAK256_NULL_BYTES = keccak256(NULL_BYTES);
// Ring buffer IDs
bytes32 constant internal RING_BUFFER_SCC_BATCHES = keccak256("RING_BUFFER_SCC_BATCHES");
bytes32 constant internal RING_BUFFER_CTC_BATCHES = keccak256("RING_BUFFER_CTC_BATCHES");
bytes32 constant internal RING_BUFFER_CTC_QUEUE = keccak256("RING_BUFFER_CTC_QUEUE");
/*********
* Enums *
......@@ -80,10 +85,18 @@ library Lib_OVMCodec {
bytes data;
}
struct TransactionChainElement {
bool isSequenced;
uint256 queueIndex; // QUEUED TX ONLY
uint256 timestamp; // SEQUENCER TX ONLY
uint256 blockNumber; // SEQUENCER TX ONLY
bytes txData; // SEQUENCER TX ONLY
}
struct QueueElement {
bytes32 queueRoot;
uint40 timestamp;
uint32 blockNumber;
uint40 blockNumber;
}
struct EOATransaction {
......@@ -236,4 +249,28 @@ library Lib_OVMCodec {
codeHash: Lib_RLPReader.readBytes32(accountState[3])
});
}
/**
* Calculates a hash for a given batch header.
* @param _batchHeader Header to hash.
* @return _hash Hash of the header.
*/
function hashBatchHeader(
Lib_OVMCodec.ChainBatchHeader memory _batchHeader
)
internal
pure
returns (
bytes32 _hash
)
{
return keccak256(
abi.encode(
_batchHeader.batchRoot,
_batchHeader.batchSize,
_batchHeader.prevTotalElements,
_batchHeader.extraData
)
);
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0;
// author: cobaltedge
library Lib_MerkleRoot {
function getMerkleRoot(bytes32[] memory elements)
internal view returns (bytes32) {
// compute tree depth
uint pow2 = 1;
uint depth = 0;
while (pow2 < elements.length) {
pow2 <<= 1;
depth++;
}
bytes memory buf = new bytes(64);
bytes32 left; bytes32 right;
for (uint i = 0; i < elements.length / 2; i++) {
left = elements[2 * i ];
right = elements[2 * i + 1];
assembly {
mstore(add(buf, 32), left)
mstore(add(buf, 64), right)
}
elements[i] = keccak256(buf);
}
for (uint i = elements.length; i < pow2 >> 1; i++) {
elements[i] = 0x0000000000000000000000000000000000000000000000000000000000000000;
}
uint diff = (pow2 - elements.length) / 2;
uint pow2_ = pow2 >> 1;
for (uint d = 2; d <= depth; d++) {
pow2_ >>= 1;
diff /= 2;
uint midpoint = pow2_ - diff;
for (uint i = 0; i < midpoint; i++) {
left = elements[2 * i ];
right = elements[2 * i + 1];
assembly {
mstore(add(buf, 32), left)
mstore(add(buf, 64), right)
}
elements[i] = keccak256(buf);
}
for (uint i = midpoint; i < pow2_; i++) {
elements[i] = 0x0000000000000000000000000000000000000000000000000000000000000000;
}
}
return elements[0];
}
}
\ No newline at end of file
......@@ -82,7 +82,7 @@ library Lib_MerkleUtils {
function verify(
bytes32 _root,
bytes memory _leaf,
bytes32 _leaf,
uint256 _path,
bytes32[] memory _siblings
)
......@@ -92,7 +92,7 @@ library Lib_MerkleUtils {
bool _verified
)
{
bytes32 computedRoot = keccak256(_leaf);
bytes32 computedRoot = _leaf;
for (uint256 i = 0; i < _siblings.length; i++) {
bytes32 sibling = _siblings[i];
......@@ -108,6 +108,26 @@ library Lib_MerkleUtils {
return computedRoot == _root;
}
function verify(
bytes32 _root,
bytes memory _leaf,
uint256 _path,
bytes32[] memory _siblings
)
internal
pure
returns (
bool _verified
)
{
return verify(
_root,
keccak256(_leaf),
_path,
_siblings
);
}
function _getDefaultHashes(
uint256 _length
)
......
pragma solidity ^0.7.0;
/* Logging Imports */
import { console } from "@nomiclabs/buidler/console.sol";
interface iRingBufferOverwriter {
function canOverwrite(bytes32 _id, uint256 _index) external returns (bool);
}
library Lib_RingBuffer {
using Lib_RingBuffer for RingBuffer;
/***********
* Structs *
***********/
struct Buffer {
uint256 length;
mapping (uint256 => bytes32) buf;
}
struct RingBuffer {
bytes32 id;
iRingBufferOverwriter overwriter;
bytes32 contextA;
bytes32 contextB;
Buffer bufferA;
Buffer bufferB;
}
struct RingBufferContext {
// contextA
uint40 globalIndex;
bytes27 extraData;
// contextB
uint64 currBufferIndex;
uint40 prevResetIndex;
uint40 currResetIndex;
}
/*************
* Constants *
*************/
uint256 constant MIN_CAPACITY = 16;
/**********************
* Internal Functions *
**********************/
/**
* Utility for initializing a ring buffer object.
* @param _self Buffer to access.
* @param _initialBufferSize Initial length of both sub-buffers.
* @param _overwriter Address of the overwriter contract.
*/
function init(
RingBuffer storage _self,
uint256 _initialBufferSize,
bytes32 _id,
iRingBufferOverwriter _overwriter
)
internal
{
_self.bufferA.length = _initialBufferSize;
_self.bufferB.length = _initialBufferSize;
_self.id = _id;
_self.overwriter = _overwriter;
}
/**
* Pushes a single element to the buffer.
* @param _self Buffer to access.
* @param _value Value to push to the buffer.
* @param _extraData Optional global extra data.
*/
function push(
RingBuffer storage _self,
bytes32 _value,
bytes27 _extraData
)
internal
{
RingBufferContext memory ctx = _self.getContext();
Buffer storage currBuffer = _self.getBuffer(ctx.currBufferIndex);
// Set a minimum capacity.
if (currBuffer.length == 0) {
currBuffer.length = MIN_CAPACITY;
}
// Check if we need to expand the buffer.
if (ctx.globalIndex - ctx.currResetIndex >= currBuffer.length) {
bool canOverwrite;
try _self.overwriter.canOverwrite(_self.id, ctx.currResetIndex) returns (bool _canOverwrite) {
canOverwrite = _canOverwrite;
} catch {
// Just in case canOverwrite is broken.
canOverwrite = false;
}
if (canOverwrite) {
// We're going to overwrite the inactive buffer.
// Bump the buffer index, reset the delete offset, and set our reset indices.
ctx.currBufferIndex++;
ctx.prevResetIndex = ctx.currResetIndex;
ctx.currResetIndex = ctx.globalIndex;
// Swap over to the next buffer.
currBuffer = _self.getBuffer(ctx.currBufferIndex);
} else {
// We're not overwriting yet, double the length of the current buffer.
currBuffer.length *= 2;
}
}
// Index to write to is the difference of the global and reset indices.
uint256 writeHead = ctx.globalIndex - ctx.currResetIndex;
currBuffer.buf[writeHead] = _value;
// Bump the global index and insert our extra data, then save the context.
ctx.globalIndex++;
ctx.extraData = _extraData;
_self.setContext(ctx);
}
/**
* Pushes a single element to the buffer.
* @param _self Buffer to access.
* @param _value Value to push to the buffer.
*/
function push(
RingBuffer storage _self,
bytes32 _value
)
internal
{
RingBufferContext memory ctx = _self.getContext();
_self.push(
_value,
ctx.extraData
);
}
/**
* Pushes a two elements to the buffer.
* @param _self Buffer to access.
* @param _valueA First value to push to the buffer.
* @param _valueA Second value to push to the buffer.
* @param _extraData Optional global extra data.
*/
function push2(
RingBuffer storage _self,
bytes32 _valueA,
bytes32 _valueB,
bytes27 _extraData
)
internal
{
_self.push(_valueA, _extraData);
_self.push(_valueB, _extraData);
}
/**
* Pushes a two elements to the buffer.
* @param _self Buffer to access.
* @param _valueA First value to push to the buffer.
* @param _valueA Second value to push to the buffer.
*/
function push2(
RingBuffer storage _self,
bytes32 _valueA,
bytes32 _valueB
)
internal
{
RingBufferContext memory ctx = _self.getContext();
_self.push2(
_valueA,
_valueB,
ctx.extraData
);
}
/**
* Retrieves an element from the buffer.
* @param _self Buffer to access.
* @param _index Element index to retrieve.
* @return Value of the element at the given index.
*/
function get(
RingBuffer storage _self,
uint256 _index
)
internal
view
returns (
bytes32
)
{
RingBufferContext memory ctx = _self.getContext();
require(
_index < ctx.globalIndex,
"Index out of bounds."
);
Buffer storage currBuffer = _self.getBuffer(ctx.currBufferIndex);
Buffer storage prevBuffer = _self.getBuffer(ctx.currBufferIndex + 1);
if (_index >= ctx.currResetIndex) {
// We're trying to load an element from the current buffer.
// Relative index is just the difference from the reset index.
uint256 relativeIndex = _index - ctx.currResetIndex;
// Shouldn't happen but why not check.
require(
relativeIndex < currBuffer.length,
"Index out of bounds."
);
return currBuffer.buf[relativeIndex];
} else {
// We're trying to load an element from the previous buffer.
// Relative index is the difference from the reset index in the other direction.
uint256 relativeIndex = ctx.currResetIndex - _index;
// Condition only fails in the case that we deleted and flipped buffers.
require(
ctx.currResetIndex > ctx.prevResetIndex,
"Index out of bounds."
);
// Make sure we're not trying to read beyond the array.
require(
relativeIndex <= prevBuffer.length,
"Index out of bounds."
);
return prevBuffer.buf[prevBuffer.length - relativeIndex];
}
}
/**
* Deletes all elements after (and including) a given index.
* @param _self Buffer to access.
* @param _index Index of the element to delete from (inclusive).
* @param _extraData Optional global extra data.
*/
function deleteElementsAfterInclusive(
RingBuffer storage _self,
uint40 _index,
bytes27 _extraData
)
internal
{
RingBufferContext memory ctx = _self.getContext();
require(
_index < ctx.globalIndex && _index >= ctx.prevResetIndex,
"Index out of bounds."
);
Buffer storage currBuffer = _self.getBuffer(ctx.currBufferIndex);
Buffer storage prevBuffer = _self.getBuffer(ctx.currBufferIndex + 1);
if (_index < ctx.currResetIndex) {
// We're switching back to the previous buffer.
// Reduce the buffer index, set the current reset index back to match the previous one.
// We use the equality of these two values to prevent reading beyond this buffer.
ctx.currBufferIndex--;
ctx.currResetIndex = ctx.prevResetIndex;
}
// Set our global index and extra data, save the context.
ctx.globalIndex = _index;
ctx.extraData = _extraData;
_self.setContext(ctx);
}
/**
* Retrieves the current global index.
* @param _self Buffer to access.
* @return Current global index.
*/
function getLength(
RingBuffer storage _self
)
internal
view
returns (
uint40
)
{
RingBufferContext memory ctx = _self.getContext();
return ctx.globalIndex;
}
/**
* Retrieves the current global extra data.
* @param _self Buffer to access.
* @return Current global extra data.
*/
function getExtraData(
RingBuffer storage _self
)
internal
view
returns (
bytes27
)
{
RingBufferContext memory ctx = _self.getContext();
return ctx.extraData;
}
/**
* Sets the current ring buffer context.
* @param _self Buffer to access.
* @param _ctx Current ring buffer context.
*/
function setContext(
RingBuffer storage _self,
RingBufferContext memory _ctx
)
internal
returns (
bytes32
)
{
bytes32 contextA;
bytes32 contextB;
uint40 globalIndex = _ctx.globalIndex;
bytes27 extraData = _ctx.extraData;
assembly {
contextA := globalIndex
contextA := or(contextA, extraData)
}
uint64 currBufferIndex = _ctx.currBufferIndex;
uint40 prevResetIndex = _ctx.prevResetIndex;
uint40 currResetIndex = _ctx.currResetIndex;
assembly {
contextB := currBufferIndex
contextB := or(contextB, shl(64, prevResetIndex))
contextB := or(contextB, shl(104, currResetIndex))
}
if (_self.contextA != contextA) {
_self.contextA = contextA;
}
if (_self.contextB != contextB) {
_self.contextB = contextB;
}
}
/**
* Retrieves the current ring buffer context.
* @param _self Buffer to access.
* @return Current ring buffer context.
*/
function getContext(
RingBuffer storage _self
)
internal
view
returns (
RingBufferContext memory
)
{
bytes32 contextA = _self.contextA;
bytes32 contextB = _self.contextB;
uint40 globalIndex;
bytes27 extraData;
assembly {
globalIndex := and(contextA, 0x000000000000000000000000000000000000000000000000000000FFFFFFFFFF)
extraData := and(contextA, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000)
}
uint64 currBufferIndex;
uint40 prevResetIndex;
uint40 currResetIndex;
assembly {
currBufferIndex := and(contextB, 0x000000000000000000000000000000000000000000000000FFFFFFFFFFFFFFFF)
prevResetIndex := shr(64, and(contextB, 0x00000000000000000000000000000000000000FFFFFFFFFF0000000000000000))
currResetIndex := shr(104, and(contextB, 0x0000000000000000000000000000FFFFFFFFFF00000000000000000000000000))
}
return RingBufferContext({
globalIndex: globalIndex,
extraData: extraData,
currBufferIndex: currBufferIndex,
prevResetIndex: prevResetIndex,
currResetIndex: currResetIndex
});
}
/**
* Retrieves the a buffer from the ring buffer by index.
* @param _self Buffer to access.
* @param _which Index of the sub buffer to access.
* @return Sub buffer for the index.
*/
function getBuffer(
RingBuffer storage _self,
uint256 _which
)
internal
view
returns (
Buffer storage
)
{
return _which % 2 == 0 ? _self.bufferA : _self.bufferB;
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0;
/* Logging */
import { console } from "@nomiclabs/buidler/console.sol";
struct TimeboundRingBuffer {
mapping(uint=>bytes32) elements;
bytes32 context;
uint32 maxSize;
uint32 maxSizeIncrementAmount;
uint32 deletionOffset;
uint firstElementTimestamp;
uint timeout;
}
/**
* @title Lib_TimeboundRingBuffer
*/
library Lib_TimeboundRingBuffer {
function init(
TimeboundRingBuffer storage _self,
uint32 _startingSize,
uint32 _maxSizeIncrementAmount,
uint _timeout
)
internal
{
_self.maxSize = _startingSize;
_self.maxSizeIncrementAmount = _maxSizeIncrementAmount;
_self.timeout = _timeout;
_self.firstElementTimestamp = block.timestamp;
}
function pushAppendOnly(
TimeboundRingBuffer storage _self,
bytes32 _ele,
bytes28 _extraData
)
internal
{
uint length = _getLength(_self.context);
uint maxSize = _self.maxSize;
if (length == maxSize) {
if (block.timestamp < _self.firstElementTimestamp + _self.timeout) {
_self.maxSize += _self.maxSizeIncrementAmount;
maxSize = _self.maxSize;
}
}
_self.elements[length % maxSize] = _ele;
_self.context = makeContext(uint32(length+1), _extraData);
}
function push(
TimeboundRingBuffer storage _self,
bytes32 _ele,
bytes28 _extraData
)
internal
{
pushAppendOnly(_self, _ele, _extraData);
if (_self.deletionOffset != 0) {
_self.deletionOffset += 1;
}
}
function push2AppendOnly(
TimeboundRingBuffer storage _self,
bytes32 _ele1,
bytes32 _ele2,
bytes28 _extraData
)
internal
{
uint length = _getLength(_self.context);
uint maxSize = _self.maxSize;
if (length + 1 >= maxSize) {
if (block.timestamp < _self.firstElementTimestamp + _self.timeout) {
// Because this is a push2 we need to at least increment by 2
_self.maxSize += _self.maxSizeIncrementAmount > 1 ? _self.maxSizeIncrementAmount : 2;
maxSize = _self.maxSize;
}
}
_self.elements[length % maxSize] = _ele1;
_self.elements[(length + 1) % maxSize] = _ele2;
_self.context = makeContext(uint32(length+2), _extraData);
}
function push2(
TimeboundRingBuffer storage _self,
bytes32 _ele1,
bytes32 _ele2,
bytes28 _extraData
)
internal
{
push2AppendOnly(_self, _ele1, _ele2, _extraData);
if (_self.deletionOffset != 0) {
_self.deletionOffset = _self.deletionOffset == 1 ? 0 : _self.deletionOffset - 2;
}
}
function makeContext(
uint32 _length,
bytes28 _extraData
)
internal
pure
returns(
bytes32
)
{
return bytes32(_extraData) | bytes32(uint256(_length));
}
function getLength(
TimeboundRingBuffer storage _self
)
internal
view
returns(
uint32
)
{
return _getLength(_self.context);
}
function _getLength(
bytes32 context
)
internal
pure
returns(
uint32
)
{
// Length is the last 4 bytes
return uint32(uint256(context & 0x00000000000000000000000000000000000000000000000000000000ffffffff));
}
function getExtraData(
TimeboundRingBuffer storage _self
)
internal
view
returns(
bytes28
)
{
// Extra Data is the first 28 bytes
return bytes28(_self.context & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000);
}
function get(
TimeboundRingBuffer storage _self,
uint32 _index
)
internal
view
returns(
bytes32
)
{
uint length = _getLength(_self.context);
require(_index < length, "Index too large.");
require(length - _index <= _self.maxSize - _self.deletionOffset, "Index too old & has been overridden.");
return _self.elements[_index % _self.maxSize];
}
function deleteElementsAfter(
TimeboundRingBuffer storage _self,
uint32 _index,
bytes28 _extraData
)
internal
{
uint32 length = _getLength(_self.context);
uint32 deletionStartingIndex = _index + 1;
uint32 numDeletedElements = length - deletionStartingIndex;
uint32 newDeletionOffset = _self.deletionOffset + numDeletedElements;
require(deletionStartingIndex < length, "Index too large.");
require(newDeletionOffset <= _self.maxSize, "Attempting to delete too many elements.");
_self.deletionOffset = newDeletionOffset;
_self.context = makeContext(deletionStartingIndex, _extraData);
}
}
\ No newline at end of file
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
/* Library Imports */
import { Lib_RingBuffer, iRingBufferOverwriter } from "../../optimistic-ethereum/libraries/utils/Lib_RingBuffer.sol";
/**
* @title TestLib_RingBuffer
*/
contract TestLib_RingBuffer {
using Lib_RingBuffer for Lib_RingBuffer.RingBuffer;
Lib_RingBuffer.RingBuffer internal buf;
function init(
uint256 _initialBufferSize,
bytes32 _id,
iRingBufferOverwriter _overwriter
)
public
{
buf.init(
_initialBufferSize,
_id,
_overwriter
);
}
function push(
bytes32 _value,
bytes27 _extraData
)
public
{
buf.push(
_value,
_extraData
);
}
function push2(
bytes32 _valueA,
bytes32 _valueB,
bytes27 _extraData
)
public
{
buf.push2(
_valueA,
_valueB,
_extraData
);
}
function get(
uint256 _index
)
public
view
returns (
bytes32
)
{
return buf.get(_index);
}
function deleteElementsAfterInclusive(
uint40 _index,
bytes27 _extraData
)
internal
{
return buf.deleteElementsAfterInclusive(
_index,
_extraData
);
}
function getLength()
internal
view
returns (
uint40
)
{
return buf.getLength();
}
function getExtraData()
internal
view
returns (
bytes27
)
{
return buf.getExtraData();
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
/* Library Imports */
import { TimeboundRingBuffer, Lib_TimeboundRingBuffer } from "../../optimistic-ethereum/libraries/utils/Lib_TimeboundRingBuffer.sol";
/**
* @title TestLib_TimeboundRingBuffer
*/
contract TestLib_TimeboundRingBuffer {
using Lib_TimeboundRingBuffer for TimeboundRingBuffer;
TimeboundRingBuffer public list;
constructor (
uint32 _startingSize,
uint32 _maxSizeIncrementAmount,
uint _timeout
) {
list.init(_startingSize, _maxSizeIncrementAmount, _timeout);
}
function push(bytes32 _ele, bytes28 _extraData) public {
list.push(_ele, _extraData);
}
function push2(bytes32 _ele1, bytes32 _ele2, bytes28 _extraData) public {
list.push2(_ele1, _ele2, _extraData);
}
function get(uint32 _index) public view returns(bytes32) {
return list.get(_index);
}
function deleteElementsAfter(uint32 _index, bytes28 _extraData) public {
return list.deleteElementsAfter(_index, _extraData);
}
function getLength() public view returns(uint32) {
return list.getLength();
}
function getExtraData() public view returns(bytes28) {
return list.getExtraData();
}
function getMaxSize() public view returns(uint32) {
return list.maxSize;
}
function getMaxSizeIncrementAmount() public view returns(uint32) {
return list.maxSizeIncrementAmount;
}
function getFirstElementTimestamp() public view returns(uint) {
return list.firstElementTimestamp;
}
function getTimeout() public view returns(uint) {
return list.timeout;
}
}
\ No newline at end of file
......@@ -212,7 +212,7 @@ describe('OVM_L1CrossDomainMessenger', () => {
})
beforeEach(async () => {
Mock__OVM_StateCommitmentChain.smocked.verifyElement.will.return.with(
Mock__OVM_StateCommitmentChain.smocked.verifyStateCommitment.will.return.with(
true
)
Mock__OVM_StateCommitmentChain.smocked.insideFraudProofWindow.will.return.with(
......@@ -245,7 +245,7 @@ describe('OVM_L1CrossDomainMessenger', () => {
})
it('should revert if provided an invalid state root proof', async () => {
Mock__OVM_StateCommitmentChain.smocked.verifyElement.will.return.with(
Mock__OVM_StateCommitmentChain.smocked.verifyStateCommitment.will.return.with(
false
)
......
......@@ -5,7 +5,7 @@ import { ethers } from '@nomiclabs/buidler'
import { Signer, ContractFactory, Contract, BigNumber } from 'ethers'
import { TransactionResponse } from '@ethersproject/abstract-provider'
import { smockit, MockContract } from '@eth-optimism/smock'
import _ from 'lodash'
import _, { times } from 'lodash'
/* Internal Imports */
import {
......@@ -18,21 +18,36 @@ import {
getEthTime,
getNextBlockNumber,
increaseEthTime,
ZERO_ADDRESS
ZERO_ADDRESS,
} from '../../../helpers'
import { defaultAbiCoder, keccak256 } from 'ethers/lib/utils'
interface sequencerBatchContext {
numSequencedTransactions: Number
numSubsequentQueueTransactions: Number
timestamp: Number
blockNumber: Number
}
const ELEMENT_TEST_SIZES = [1, 2, 4, 8, 16]
const DECOMPRESSION_ADDRESS = '0x4200000000000000000000000000000000000008'
const MAX_GAS_LIMIT = 8_000_000
const getQueueLeafHash = (index: number): string => {
return keccak256(
defaultAbiCoder.encode(
['bool', 'uint256', 'uint256', 'uint256', 'bytes'],
[false, index, 0, 0, '0x']
)
)
}
const getSequencerLeafHash = (
timestamp: number,
blockNumber: number,
data: string
): string => {
return keccak256(
'0x0100' +
remove0x(BigNumber.from(timestamp).toHexString()).padStart(64, '0') +
remove0x(BigNumber.from(blockNumber).toHexString()).padStart(64, '0') +
remove0x(data)
)
}
const getTransactionHash = (
sender: string,
target: string,
......@@ -131,7 +146,7 @@ const encodeBatchContext = (context: BatchContext): string => {
)
}
describe.only('OVM_CanonicalTransactionChain', () => {
describe('OVM_CanonicalTransactionChain', () => {
let signer: Signer
let sequencer: Signer
before(async () => {
......@@ -140,6 +155,7 @@ describe.only('OVM_CanonicalTransactionChain', () => {
let AddressManager: Contract
let Mock__OVM_ExecutionManager: MockContract
let Mock__OVM_StateCommitmentChain: MockContract
before(async () => {
AddressManager = await makeAddressManager()
await AddressManager.setAddress(
......@@ -155,12 +171,23 @@ describe.only('OVM_CanonicalTransactionChain', () => {
await ethers.getContractFactory('OVM_ExecutionManager')
)
Mock__OVM_StateCommitmentChain = smockit(
await ethers.getContractFactory('OVM_StateCommitmentChain')
)
await setProxyTarget(
AddressManager,
'OVM_ExecutionManager',
Mock__OVM_ExecutionManager
)
await setProxyTarget(
AddressManager,
'OVM_StateCommitmentChain',
Mock__OVM_StateCommitmentChain
)
Mock__OVM_StateCommitmentChain.smocked.canOverwrite.will.return.with(false)
Mock__OVM_ExecutionManager.smocked.getMaxTransactionGasLimit.will.return.with(
MAX_GAS_LIMIT
)
......@@ -192,7 +219,9 @@ describe.only('OVM_CanonicalTransactionChain', () => {
await expect(
OVM_CanonicalTransactionChain.enqueue(target, gasLimit, data)
).to.be.revertedWith('Transaction exceeds maximum rollup data size.')
).to.be.revertedWith(
'Transaction exceeds maximum rollup transaction data size.'
)
})
it('should revert if gas limit parameter is not at least MIN_ROLLUP_TX_GAS', async () => {
......@@ -201,7 +230,7 @@ describe.only('OVM_CanonicalTransactionChain', () => {
await expect(
OVM_CanonicalTransactionChain.enqueue(target, gasLimit, data)
).to.be.revertedWith('Layer 2 gas limit too low to enqueue.')
).to.be.revertedWith('Transaction gas limit too low to enqueue.')
})
it('should revert if transaction gas limit does not cover rollup burn', async () => {
......@@ -242,7 +271,7 @@ describe.only('OVM_CanonicalTransactionChain', () => {
it('should revert when accessing a non-existent element', async () => {
await expect(
OVM_CanonicalTransactionChain.getQueueElement(0)
).to.be.revertedWith('Index too large')
).to.be.revertedWith('Index out of bounds.')
})
describe('when the requested element exists', () => {
......@@ -394,7 +423,7 @@ describe.only('OVM_CanonicalTransactionChain', () => {
it('should revert if the queue is empty', async () => {
await expect(
OVM_CanonicalTransactionChain.appendQueueBatch(1)
).to.be.revertedWith('Index too large.')
).to.be.revertedWith('Index out of bounds.')
})
describe('when the queue is not empty', () => {
......@@ -459,7 +488,7 @@ describe.only('OVM_CanonicalTransactionChain', () => {
it(`should revert if appending ${size} + 1 elements`, async () => {
await expect(
OVM_CanonicalTransactionChain.appendQueueBatch(size + 1)
).to.be.revertedWith('Index too large.')
).to.be.revertedWith('Index out of bounds.')
})
})
})
......@@ -473,23 +502,17 @@ describe.only('OVM_CanonicalTransactionChain', () => {
const gasLimit = 500_000
const data = '0x' + '12'.repeat(1234)
const timestamp = (await getEthTime(ethers.provider) + 100)
const timestamp = (await getEthTime(ethers.provider)) + 100
await setEthTime(ethers.provider, timestamp)
await OVM_CanonicalTransactionChain.enqueue(
entrypoint,
gasLimit,
data
)
await OVM_CanonicalTransactionChain.enqueue(entrypoint, gasLimit, data)
const blockNumber = await ethers.provider.getBlockNumber()
await increaseEthTime(
ethers.provider,
FORCE_INCLUSION_PERIOD_SECONDS * 2
)
await increaseEthTime(ethers.provider, FORCE_INCLUSION_PERIOD_SECONDS * 2)
await OVM_CanonicalTransactionChain.appendQueueBatch(1)
expect(await OVM_CanonicalTransactionChain.verifyTransaction(
expect(
await OVM_CanonicalTransactionChain.verifyTransaction(
{
timestamp,
blockNumber,
......@@ -497,32 +520,56 @@ describe.only('OVM_CanonicalTransactionChain', () => {
l1TxOrigin: await OVM_CanonicalTransactionChain.signer.getAddress(),
entrypoint,
gasLimit,
data
data,
},
{
isSequenced: false,
queueIndex: 0,
timestamp: 0,
blockNumber: 0,
txData: '0x00'
txData: '0x',
},
{
batchIndex: 0,
batchRoot: getQueueLeafHash(0),
batchSize: 1,
prevTotalElements: 0,
extraData: '0x',
},
{
index: 0,
siblings: []
siblings: [],
}
)).to.equal(true)
)
).to.equal(true)
})
it('should successfully verify against a valid sequencer transaction', async () => {
const entrypoint = DECOMPRESSION_ADDRESS
const gasLimit = MAX_GAS_LIMIT
const data = '0x' + '12'.repeat(1234)
const timestamp = await getEthTime(ethers.provider) - 10
const blockNumber = await ethers.provider.getBlockNumber() - 1
const timestamp = (await getEthTime(ethers.provider)) - 10
const blockNumber = (await ethers.provider.getBlockNumber()) - 1
// TODO: await OVM_CanonicalTransactionChain.appendQueueBatch(1)
await appendSequencerBatch(
OVM_CanonicalTransactionChain.connect(sequencer),
{
shouldStartAtBatch: 0,
totalElementsToAppend: 1,
contexts: [
{
numSequencedTransactions: 1,
numSubsequentQueueTransactions: 0,
timestamp: timestamp,
blockNumber: blockNumber,
},
],
transactions: [data],
}
)
expect(await OVM_CanonicalTransactionChain.verifyTransaction(
expect(
await OVM_CanonicalTransactionChain.verifyTransaction(
{
timestamp,
blockNumber,
......@@ -530,20 +577,28 @@ describe.only('OVM_CanonicalTransactionChain', () => {
l1TxOrigin: ZERO_ADDRESS,
entrypoint,
gasLimit,
data
data,
},
{
isSequenced: true,
queueIndex: 0,
timestamp,
blockNumber,
txData: data
txData: data,
},
{
batchIndex: 0,
batchRoot: getSequencerLeafHash(timestamp, blockNumber, data),
batchSize: 1,
prevTotalElements: 0,
extraData: '0x',
},
{
index: 0,
siblings: []
siblings: [],
}
)).to.equal(true)
)
).to.equal(true)
})
})
......
......@@ -16,6 +16,16 @@ import {
NULL_BYTES32,
} from '../../../helpers'
const DUMMY_TX_CHAIN_ELEMENTS = [...Array(10)].map(() => {
return {
isSequenced: false,
queueIndex: BigNumber.from(0),
timestamp: BigNumber.from(0),
blockNumber: BigNumber.from(0),
txData: NULL_BYTES32,
}
})
describe('OVM_FraudVerifier', () => {
let AddressManager: Contract
before(async () => {
......@@ -83,7 +93,7 @@ describe('OVM_FraudVerifier', () => {
describe('initializeFraudVerification', () => {
describe('when provided an invalid pre-state root inclusion proof', () => {
before(() => {
Mock__OVM_StateCommitmentChain.smocked.verifyElement.will.return.with(
Mock__OVM_StateCommitmentChain.smocked.verifyStateCommitment.will.return.with(
false
)
})
......@@ -95,6 +105,7 @@ describe('OVM_FraudVerifier', () => {
DUMMY_BATCH_HEADERS[0],
DUMMY_BATCH_PROOFS[0],
DUMMY_OVM_TRANSACTIONS[0],
DUMMY_TX_CHAIN_ELEMENTS[0],
DUMMY_BATCH_HEADERS[0],
DUMMY_BATCH_PROOFS[0]
)
......@@ -104,14 +115,14 @@ describe('OVM_FraudVerifier', () => {
describe('when provided a valid pre-state root inclusion proof', () => {
before(() => {
Mock__OVM_StateCommitmentChain.smocked.verifyElement.will.return.with(
Mock__OVM_StateCommitmentChain.smocked.verifyStateCommitment.will.return.with(
true
)
})
describe('when provided an invalid transaction inclusion proof', () => {
before(() => {
Mock__OVM_CanonicalTransactionChain.smocked.verifyElement.will.return.with(
Mock__OVM_CanonicalTransactionChain.smocked.verifyTransaction.will.return.with(
false
)
})
......@@ -123,6 +134,7 @@ describe('OVM_FraudVerifier', () => {
DUMMY_BATCH_HEADERS[0],
DUMMY_BATCH_PROOFS[0],
DUMMY_OVM_TRANSACTIONS[0],
DUMMY_TX_CHAIN_ELEMENTS[0],
DUMMY_BATCH_HEADERS[0],
DUMMY_BATCH_PROOFS[0]
)
......@@ -132,7 +144,7 @@ describe('OVM_FraudVerifier', () => {
describe('when provided a valid transaction inclusion proof', () => {
before(() => {
Mock__OVM_CanonicalTransactionChain.smocked.verifyElement.will.return.with(
Mock__OVM_CanonicalTransactionChain.smocked.verifyTransaction.will.return.with(
true
)
})
......@@ -144,6 +156,7 @@ describe('OVM_FraudVerifier', () => {
DUMMY_BATCH_HEADERS[0],
DUMMY_BATCH_PROOFS[0],
DUMMY_OVM_TRANSACTIONS[0],
DUMMY_TX_CHAIN_ELEMENTS[0],
DUMMY_BATCH_HEADERS[0],
DUMMY_BATCH_PROOFS[0]
)
......@@ -159,10 +172,10 @@ describe('OVM_FraudVerifier', () => {
describe('finalizeFraudVerification', () => {
beforeEach(async () => {
Mock__OVM_StateCommitmentChain.smocked.verifyElement.will.return.with(
Mock__OVM_StateCommitmentChain.smocked.verifyStateCommitment.will.return.with(
true
)
Mock__OVM_CanonicalTransactionChain.smocked.verifyElement.will.return.with(
Mock__OVM_CanonicalTransactionChain.smocked.verifyTransaction.will.return.with(
true
)
......@@ -171,6 +184,7 @@ describe('OVM_FraudVerifier', () => {
DUMMY_BATCH_HEADERS[0],
DUMMY_BATCH_PROOFS[0],
DUMMY_OVM_TRANSACTIONS[0],
DUMMY_TX_CHAIN_ELEMENTS[0],
DUMMY_BATCH_HEADERS[0],
DUMMY_BATCH_PROOFS[0]
)
......@@ -230,7 +244,7 @@ describe('OVM_FraudVerifier', () => {
describe('when provided an invalid pre-state root inclusion proof', () => {
beforeEach(() => {
Mock__OVM_StateCommitmentChain.smocked.verifyElement.will.return.with(
Mock__OVM_StateCommitmentChain.smocked.verifyStateCommitment.will.return.with(
false
)
})
......@@ -251,14 +265,14 @@ describe('OVM_FraudVerifier', () => {
describe('when provided a valid pre-state root inclusion proof', () => {
before(() => {
Mock__OVM_StateCommitmentChain.smocked.verifyElement.will.return.with(
Mock__OVM_StateCommitmentChain.smocked.verifyStateCommitment.will.return.with(
true
)
})
describe('when provided an invalid post-state root inclusion proof', () => {
beforeEach(() => {
Mock__OVM_StateCommitmentChain.smocked.verifyElement.will.return.with(
Mock__OVM_StateCommitmentChain.smocked.verifyStateCommitment.will.return.with(
(stateRoot: string, ...args: any) => {
return stateRoot !== NON_NULL_BYTES32
}
......@@ -281,7 +295,7 @@ describe('OVM_FraudVerifier', () => {
describe('when provided a valid post-state root inclusion proof', () => {
before(() => {
Mock__OVM_StateCommitmentChain.smocked.verifyElement.will.return.with(
Mock__OVM_StateCommitmentChain.smocked.verifyStateCommitment.will.return.with(
true
)
})
......
/* tslint:disable:no-empty */
import { expect } from '../../../setup'
/* External Imports */
import { ethers } from '@nomiclabs/buidler'
import { Contract, Signer } from 'ethers'
/* Internal Imports */
import {
NON_NULL_BYTES32,
makeHexString,
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)
return '0x' + '00'.repeat(31) + strNum
}
describe('Lib_TimeboundRingBuffer', () => {
let signer: Signer
before(async () => {
;[signer] = await ethers.getSigners()
})
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
)
describe('push with no timeout', () => {
beforeEach(async () => {
Lib_TimeboundRingBuffer = await (
await ethers.getContractFactory('TestLib_TimeboundRingBuffer')
).deploy(4, 1, 0)
for (let i = 0; i < 4; i++) {
await Lib_TimeboundRingBuffer.push(numToBytes32(i), NON_NULL_BYTES28)
}
})
it('should push a single value which increases the length', async () => {
expect(await Lib_TimeboundRingBuffer.getLength()).to.equal(4)
await Lib_TimeboundRingBuffer.push(NON_NULL_BYTES32, NON_NULL_BYTES28)
expect(await Lib_TimeboundRingBuffer.getLength()).to.equal(5)
})
it('should overwrite old values:[0,1,2,3] -> [4,5,2,3]', async () => {
expect(await Lib_TimeboundRingBuffer.get(0)).to.equal(numToBytes32(0))
await Lib_TimeboundRingBuffer.push(numToBytes32(4), NON_NULL_BYTES28)
expect(await Lib_TimeboundRingBuffer.get(4)).to.equal(numToBytes32(4))
await Lib_TimeboundRingBuffer.push(numToBytes32(5), NON_NULL_BYTES28)
expect(await Lib_TimeboundRingBuffer.get(5)).to.equal(numToBytes32(5))
})
})
describe('get()', () => {
before(async () => {
Lib_TimeboundRingBuffer = await (
await ethers.getContractFactory('TestLib_TimeboundRingBuffer')
).deploy(2, 1, 10_000)
await increaseEthTime(ethers.provider, 20_000)
for (let i = 0; i < 4; i++) {
await Lib_TimeboundRingBuffer.push(numToBytes32(i), NON_NULL_BYTES28)
}
})
it('should revert if index is too old', async () => {
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.'
)
})
})
describe('push with timeout', () => {
const startSize = 2
beforeEach(async () => {
Lib_TimeboundRingBuffer = await (
await ethers.getContractFactory('TestLib_TimeboundRingBuffer')
).deploy(startSize, 1, 10_000)
for (let i = 0; i < startSize; i++) {
await pushNum(i)
}
})
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)
const increasedSize = startSize + 1
expect(await Lib_TimeboundRingBuffer.getMaxSize()).to.equal(increasedSize)
await increaseEthTime(ethers.provider, 20_000)
await pushNum(3)
expect(await Lib_TimeboundRingBuffer.getMaxSize()).to.equal(increasedSize) // Shouldn't increase the size this time
expect(await Lib_TimeboundRingBuffer.get(2)).to.equal(numToBytes32(2))
expect(await Lib_TimeboundRingBuffer.get(3)).to.equal(numToBytes32(3))
})
it('should NOT extend the array if the time is not up and extend it when it is', async () => {
await pushJunk()
const increasedSize = startSize + 1
expect(await Lib_TimeboundRingBuffer.getMaxSize()).to.equal(increasedSize)
await increaseEthTime(ethers.provider, 20_000)
// Push the time forward and verify that the time doesn't increment
for (let i = 0; i < increasedSize + 1; i++) {
await pushJunk()
}
expect(await Lib_TimeboundRingBuffer.getMaxSize()).to.equal(increasedSize)
})
})
describe('push2 with timeout', () => {
const startSize = 2
beforeEach(async () => {
Lib_TimeboundRingBuffer = await (
await ethers.getContractFactory('TestLib_TimeboundRingBuffer')
).deploy(startSize, 1, 10_000)
})
it('should push a single value which extends the array', async () => {
await push2Nums(0, 1)
await push2Nums(2, 3)
const increasedSize = startSize + 2
expect(await Lib_TimeboundRingBuffer.getMaxSize()).to.equal(increasedSize)
await increaseEthTime(ethers.provider, 20_000)
await push2Nums(4, 5)
expect(await Lib_TimeboundRingBuffer.getMaxSize()).to.equal(increasedSize) // Shouldn't increase the size this time
for (let i = 2; i < 6; i++) {
expect(await Lib_TimeboundRingBuffer.get(i)).to.equal(numToBytes32(i))
}
})
})
describe('getExtraData', () => {
beforeEach(async () => {
Lib_TimeboundRingBuffer = await (
await ethers.getContractFactory('TestLib_TimeboundRingBuffer')
).deploy(2, 1, 10_000)
})
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
)
})
})
describe('deleteElementsAfter', () => {
// [0,1,2,3] -> [0,1,-,-]
beforeEach(async () => {
Lib_TimeboundRingBuffer = await (
await ethers.getContractFactory('TestLib_TimeboundRingBuffer')
).deploy(4, 1, 0)
for (let i = 0; i < 4; i++) {
pushNum(i)
}
})
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.')
})
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.'
)
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.'
)
expect(await Lib_TimeboundRingBuffer.get(1)).to.equal(numToBytes32(1))
expect(await Lib_TimeboundRingBuffer.get(3)).to.equal(numToBytes32(3))
})
it('should not reduce the overall size of the buffer', async () => {
pushNum(4)
expect(await Lib_TimeboundRingBuffer.get(1)).to.equal(numToBytes32(1))
// We expect that we can still access `1` because the deletionOffset
// will have reduced by 1 after we pushed.
Lib_TimeboundRingBuffer.deleteElementsAfter(3, NON_NULL_BYTES28)
expect(await Lib_TimeboundRingBuffer.get(1)).to.equal(numToBytes32(1))
})
})
})
......@@ -3,7 +3,7 @@ import { ZERO_ADDRESS, NULL_BYTES32 } from '../constants'
export const DUMMY_OVM_TRANSACTIONS = [
{
timestamp: 0,
number: 0,
blockNumber: 0,
l1QueueOrigin: 0,
l1TxOrigin: ZERO_ADDRESS,
entrypoint: ZERO_ADDRESS,
......
......@@ -263,7 +263,7 @@ export class ExecutionManagerTestRunner {
await this.contracts.OVM_ExecutionManager.run(
{
timestamp: step.functionParams.timestamp,
number: 0,
blockNumber: 0,
l1QueueOrigin: step.functionParams.queueOrigin,
l1TxOrigin: step.functionParams.origin,
entrypoint: step.functionParams.entrypoint,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment