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
));
}
}
......@@ -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
)
......
// 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